From e6c20250ea644e71ef02fa0597e87a7c9da52287 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Mon, 1 Jan 2024 18:23:22 +0800 Subject: [PATCH 01/48] build: 2024 Happy New Year! Update toolchain version to 2024-01-01 and update github ci config. --- .github/actions/build/action.yml | 2 +- .github/workflows/build-dev.yml | 7 +--- .github/workflows/build-release.yml | 10 +---- rust-fmt.toml | 5 +++ rust-toolchain.toml | 5 ++- src/main.rs | 58 ++++++++++++++++++----------- 6 files changed, 48 insertions(+), 39 deletions(-) create mode 100644 rust-fmt.toml diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 08a7b37..91c3279 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -18,4 +18,4 @@ runs: - name: Build release if: ${{ inputs.release == 'true' }} shell: bash - run: cargo build --target ${{ inputs.target }} -r + run: cargo build --release --target ${{ inputs.target }} -r diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index a517808..0390cdf 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -39,12 +39,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Setup Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - toolchain: nightly-2023-09-06 - components: rustfmt, clippy + uses: dsherret/rust-toolchain-file@v1 - name: Show Rust toolchain version shell: bash diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 976d5ef..be887ef 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -30,15 +30,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.ref }} # github.ref 变量将自动填充为触发事件的分支或标签名 - name: Setup Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - toolchain: nightly-2023-09-06 - components: rustfmt, clippy + uses: dsherret/rust-toolchain-file@v1 - name: Setup musl-tools if: matrix.targets.target == 'x86_64-unknown-linux-musl' @@ -99,4 +94,3 @@ jobs: uses: softprops/action-gh-release@v1 with: files: ${{ steps.gen-name.outputs.NAME }}.zip - diff --git a/rust-fmt.toml b/rust-fmt.toml new file mode 100644 index 0000000..9829022 --- /dev/null +++ b/rust-fmt.toml @@ -0,0 +1,5 @@ +max_width = 79 # 设置最大行宽为 100 个字符 +tab_spaces = 4 # 设置缩进宽度为 4 个空格 +edition = "2021" # 设置 Rust 版本(根据实际项目版本进行调整) +use_small_heuristics = "Max" # 设置换行策略 +newline_style = "Auto" # 设置换行符风格,根据平台自动选择 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index fac5529..5b6bd4a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,4 @@ [toolchain] -profile = "default" -channel = "nightly-2023-12-27" +profile = "minimal" +channel = "nightly-2024-01-01" +components = [ "rustfmt", "clippy" ] diff --git a/src/main.rs b/src/main.rs index 7b361dc..5ba6d2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ #![feature(try_blocks)] #![feature(if_let_guard)] #![feature(let_chains)] -#![feature(type_name_of_val)] #![feature(iterator_try_collect)] #![feature(iterator_try_reduce)] #![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] @@ -45,6 +44,7 @@ fn show_non_printable_chars(text: String) -> String { .replace("\r\n", &make_red("␍␊\r\n")) } +#[allow(clippy::missing_const_for_fn)] fn panic_if_err(result: &Result) { if let Err(e) = result { panic!("{}", e) @@ -87,19 +87,23 @@ async fn main() -> Result<()> { display::user_info(style, &user_info)? } _ if let Some((skip, take, r#type, align)) = parser::ing::list_ing(&args) => { - let ing_with_comment_iter = infer::>(try { - let ing_api = Ing::new(pat?); - let ing_vec = ing_api.get_list(skip, take, &r#type).await?; - ing_vec.into_iter() - .map(|ing| async { - let result = ing_api.get_comment_list(ing.id).await; - result.map(|comment_vec| (ing, comment_vec)) - }) - .join_all() - .await - .into_iter() - .collect::>>()? - }).map(|vec| vec.into_iter().dyn_rev(rev)); + let ing_with_comment_iter = infer::>( + try { + let ing_api = Ing::new(pat?); + let ing_vec = ing_api.get_list(skip, take, &r#type).await?; + ing_vec + .into_iter() + .map(|ing| async { + let result = ing_api.get_comment_list(ing.id).await; + result.map(|comment_vec| (ing, comment_vec)) + }) + .join_all() + .await + .into_iter() + .collect::>>()? + }, + ) + .map(|vec| vec.into_iter().dyn_rev(rev)); foe.then(|| panic_if_err(&ing_with_comment_iter)); display::list_ing(style, time_style, ing_with_comment_iter, align)? } @@ -113,7 +117,9 @@ async fn main() -> Result<()> { } _ if let Some((content, id)) = parser::ing::comment_ing(&args) => { let content = try { - Ing::new(pat?).comment(id, content.clone(), None, None).await?; + Ing::new(pat?) + .comment(id, content.clone(), None, None) + .await?; content }; foe.then(|| panic_if_err(&content)); @@ -131,7 +137,8 @@ async fn main() -> Result<()> { } _ if let Some(id) = parser::post::show_post_comment(&args) => { let comment_iter = Post::new(pat?) - .get_comment_list(id).await + .get_comment_list(id) + .await .map(|vec| vec.into_iter().dyn_rev(rev)); foe.then(|| panic_if_err(&comment_iter)); display::show_post_comment(style, time_style, comment_iter)? @@ -164,18 +171,26 @@ async fn main() -> Result<()> { let result = Post::new(pat?) .search_site(skip, take, kw) .await - .map(|vec | vec.into_iter().dyn_rev(rev)); + .map(|vec| vec.into_iter().dyn_rev(rev)); foe.then(|| panic_if_err(&result)); display::search_site_post(style, time_style, result)? } _ if let Some(create_cmd) = parser::post::create_post(&args) => { - let CreateCmd { title, body, publish } = create_cmd; + let CreateCmd { + title, + body, + publish, + } = create_cmd; let id = Post::new(pat?).create(title, body, *publish).await; foe.then(|| panic_if_err(&id)); display::create_post(style, &id) } _ if let Some((id, update_cmd)) = parser::post::update_post(&args) => { - let UpdateCmd { title, body, publish } = update_cmd; + let UpdateCmd { + title, + body, + publish, + } = update_cmd; let id = Post::new(pat?).update(id, title, body, publish).await; foe.then(|| panic_if_err(&id)); display::update_post(style, &id) @@ -197,9 +212,8 @@ async fn main() -> Result<()> { display::list_fav(style, time_style, fav_iter)? } - _ if no_operation(&args) => - infer::(Args::command()).render_help().to_string(), - _ => "Invalid usage, follow '--help' for more information".to_owned() + _ if no_operation(&args) => infer::(Args::command()).render_help().to_string(), + _ => "Invalid usage, follow '--help' for more information".to_owned(), }; if global_opt.quiet { From 80bc0860493a985f62d4f32e2ca72c083d467294 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Mon, 1 Jan 2024 23:54:39 +0800 Subject: [PATCH 02/48] refactor: migrate project to lib layout. --- Cargo.lock | 2 +- Cargo.toml | 6 +- src/main.rs | 241 ---------------------------------------------------- 3 files changed, 6 insertions(+), 243 deletions(-) delete mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 7fc3726..dd007c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,7 +227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] -name = "cnb" +name = "cnblogs_lib" version = "0.0.0-dev" dependencies = [ "anyhow", diff --git a/Cargo.toml b/Cargo.toml index edffc32..481b8a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cnb" +name = "cnblogs_lib" # WRN: Version will be updated by CI while create a tag, NERVER change this. version = "0.0.0-dev" edition = "2021" @@ -47,3 +47,7 @@ colored = "2.0.4" terminal_size = "0.2.6" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "cnb" +path = "src/bin/cnb.rs" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 5ba6d2d..0000000 --- a/src/main.rs +++ /dev/null @@ -1,241 +0,0 @@ -#![feature(try_blocks)] -#![feature(if_let_guard)] -#![feature(let_chains)] -#![feature(iterator_try_collect)] -#![feature(iterator_try_reduce)] -#![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] - -use crate::api::auth::session; -use crate::api::fav::Fav; -use crate::api::ing::Ing; -use crate::api::news::News; -use crate::api::post::Post; -use crate::api::user::User; -use crate::args::cmd::post::{CreateCmd, UpdateCmd}; -use crate::args::parser::no_operation; -use crate::args::{parser, Args}; -use crate::infra::fp::currying::eq; -use crate::infra::infer::infer; -use crate::infra::iter::{ExactSizeIteratorExt, IntoIteratorExt}; -use crate::infra::option::OptionExt; -use crate::infra::result::WrapResult; -use anyhow::Result; -use clap::Parser; -use clap::{Command, CommandFactory}; -use colored::Colorize; -use std::env; - -pub mod api; -pub mod args; -pub mod display; -pub mod infra; - -fn show_non_printable_chars(text: String) -> String { - #[inline] - fn make_red(str: &str) -> String { - format!("{}", str.red()) - } - - text.replace(' ', &make_red("·")) - .replace('\0', &make_red("␀\0")) - .replace('\t', &make_red("␉\t")) - .replace('\n', &make_red("␊\n")) - .replace('\r', &make_red("␍\r")) - .replace("\r\n", &make_red("␍␊\r\n")) -} - -#[allow(clippy::missing_const_for_fn)] -fn panic_if_err(result: &Result) { - if let Err(e) = result { - panic!("{}", e) - } -} - -#[tokio::main(flavor = "multi_thread")] -async fn main() -> Result<()> { - let args_vec = env::args().collect::>(); - if args_vec.iter().any(eq(&"--debug".to_owned())) { - dbg!(args_vec); - } - - let args: Args = Args::parse(); - let global_opt = &args.global_opt; - if global_opt.debug { - dbg!(&args); - } - - let pat = global_opt.with_pat.clone().or_eval_result(session::get_pat); - let style = &global_opt.style; - let time_style = &global_opt.time_style; - let rev = args.rev; - let foe = global_opt.fail_on_error; - - let output = match args { - _ if let Some(pat) = parser::user::login(&args) => { - let cfg_path = session::login(pat); - foe.then(|| panic_if_err(&cfg_path)); - display::login(style, &cfg_path) - } - _ if parser::user::logout(&args) => { - let cfg_path = session::logout(); - foe.then(|| panic_if_err(&cfg_path)); - display::logout(style, &cfg_path) - } - _ if parser::user::user_info(&args) => { - let user_info = User::new(pat?).get_info().await; - foe.then(|| panic_if_err(&user_info)); - display::user_info(style, &user_info)? - } - _ if let Some((skip, take, r#type, align)) = parser::ing::list_ing(&args) => { - let ing_with_comment_iter = infer::>( - try { - let ing_api = Ing::new(pat?); - let ing_vec = ing_api.get_list(skip, take, &r#type).await?; - ing_vec - .into_iter() - .map(|ing| async { - let result = ing_api.get_comment_list(ing.id).await; - result.map(|comment_vec| (ing, comment_vec)) - }) - .join_all() - .await - .into_iter() - .collect::>>()? - }, - ) - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&ing_with_comment_iter)); - display::list_ing(style, time_style, ing_with_comment_iter, align)? - } - _ if let Some(content) = parser::ing::publish_ing(&args) => { - let content = try { - Ing::new(pat?).publish(content).await?; - content - }; - foe.then(|| panic_if_err(&content)); - display::publish_ing(style, &content) - } - _ if let Some((content, id)) = parser::ing::comment_ing(&args) => { - let content = try { - Ing::new(pat?) - .comment(id, content.clone(), None, None) - .await?; - content - }; - foe.then(|| panic_if_err(&content)); - display::comment_ing(style, &content) - } - _ if let Some(id) = parser::post::show_post(&args) => { - let entry = Post::new(pat?).get_one(id).await; - foe.then(|| panic_if_err(&entry)); - display::show_post(style, &entry)? - } - _ if let Some(id) = parser::post::show_post_meta(&args) => { - let entry = Post::new(pat?).get_one(id).await; - foe.then(|| panic_if_err(&entry)); - display::show_post_meta(style, time_style, &entry)? - } - _ if let Some(id) = parser::post::show_post_comment(&args) => { - let comment_iter = Post::new(pat?) - .get_comment_list(id) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&comment_iter)); - display::show_post_comment(style, time_style, comment_iter)? - } - _ if let Some((skip, take)) = parser::post::list_post(&args) => { - let meta_iter = Post::new(pat?) - .get_meta_list(skip, take) - .await - .map(|(vec, count)| (vec.into_iter().dyn_rev(rev), count)); - foe.then(|| panic_if_err(&meta_iter)); - display::list_post(style, meta_iter)? - } - _ if let Some(id) = parser::post::delete_post(&args) => { - let id = try { - Post::new(pat?).del_one(id).await?; - id - }; - foe.then(|| panic_if_err(&id)); - display::delete_post(style, &id) - } - _ if let Some((kw, skip, take)) = parser::post::search_self_post(&args) => { - let result = Post::new(pat?) - .search_self(skip, take, kw) - .await - .map(|(vec, count)| (vec.into_iter().dyn_rev(rev), count)); - foe.then(|| panic_if_err(&result)); - display::search_self_post(style, result)? - } - _ if let Some((kw, skip, take)) = parser::post::search_site_post(&args) => { - let result = Post::new(pat?) - .search_site(skip, take, kw) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&result)); - display::search_site_post(style, time_style, result)? - } - _ if let Some(create_cmd) = parser::post::create_post(&args) => { - let CreateCmd { - title, - body, - publish, - } = create_cmd; - let id = Post::new(pat?).create(title, body, *publish).await; - foe.then(|| panic_if_err(&id)); - display::create_post(style, &id) - } - _ if let Some((id, update_cmd)) = parser::post::update_post(&args) => { - let UpdateCmd { - title, - body, - publish, - } = update_cmd; - let id = Post::new(pat?).update(id, title, body, publish).await; - foe.then(|| panic_if_err(&id)); - display::update_post(style, &id) - } - _ if let Some((skip, take)) = parser::news::list_news(&args) => { - let news_iter = News::new(pat?) - .get_list(skip, take) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&news_iter)); - display::list_news(style, time_style, news_iter)? - } - _ if let Some((skip, take)) = parser::fav::list_fav(&args) => { - let fav_iter = Fav::new(pat?) - .get_list(skip, take) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&fav_iter)); - display::list_fav(style, time_style, fav_iter)? - } - - _ if no_operation(&args) => infer::(Args::command()).render_help().to_string(), - _ => "Invalid usage, follow '--help' for more information".to_owned(), - }; - - if global_opt.quiet { - return ().wrap_ok(); - } - - let output = { - let output = if output.ends_with("\n\n") { - output[..output.len() - 1].to_owned() - } else if output.ends_with('\n') { - output - } else { - format!("{}\n", output) - }; - if global_opt.debug { - show_non_printable_chars(output) - } else { - output - } - }; - - print!("{}", output); - - ().wrap_ok() -} From ec62483b3ccc345edc07d90bdc427f5fd1911571 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Mon, 1 Jan 2024 23:56:32 +0800 Subject: [PATCH 03/48] refactor: add lib and bin files. --- README.zh-CN.md | 52 +++++++++ src/apis/mod.rs | 14 +++ src/apis/statuses/mod.rs | 13 +++ src/bin/cnb.rs | 241 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 12 ++ 5 files changed, 332 insertions(+) create mode 100644 README.zh-CN.md create mode 100644 src/apis/mod.rs create mode 100644 src/apis/statuses/mod.rs create mode 100644 src/bin/cnb.rs create mode 100644 src/lib.rs diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..197dea1 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,52 @@ +# Cnblogs 命令行工具 + +[![Build / Release](https://github.com/cnblogs/cli/actions/workflows/build-release.yml/badge.svg)](https://github.com/cnblogs/cli/actions/workflows/build-release.yml) +[![Build / Development](https://github.com/cnblogs/cli/actions/workflows/build-dev.yml/badge.svg)](https://github.com/cnblogs/cli/actions/workflows/build-dev.yml) + +从 CLI 访问 cnblogs。 + +## Cnbogs Cli 设计 + +从Cnblogs的[OpenAPI](https://api.cnblogs.com/help)来说,API主要有以下几类: + +1. Token: 认证 +2. Users: 仅提供当前登录用户信息 +3. Blogs: 博客的CURD及其评论的查看和增加, +4. Marks: 收藏的CURD +5. News: 新闻的查询,新闻评论的CURD +6. Statuses: 闪存CURD。 +7. Questions: 问题相关操作 +8. Edu: 班级相关 +9. Articles: 知识库的查找。 +10. Zzk: 找找看 + +### cli的使用 + +目前cli的使用如下: + +```shell +# Check your post list +cnb post --list +# Check your post +cnb --id 114514 post --show +# Create and publish post +cnb post create --title 'Hello' --body 'world!' --publish +# Change your post body +cnb --id 114514 post update --body 'niconiconiconi' + +# Show ing list +cnb ing list +# Publish ing +cnb ing --publish 'Hello world!' +# Comment to ing +cnb --id 114514 ing --comment 'Awesome!' + +# Check your user infomation +cnb user --info +``` + +大体上使用如上的设计,支持子命令,相关操作的设计按照RESTFUL的思路设计实现,博客的相关操作设计如下: + +```shell +cnb posts [comment] [list,create,query,delete,update] --[id/file/quertset] --[pagesize,pagecount] +``` diff --git a/src/apis/mod.rs b/src/apis/mod.rs new file mode 100644 index 0000000..b2fa28a --- /dev/null +++ b/src/apis/mod.rs @@ -0,0 +1,14 @@ +//! cnblogs 闪存接口模块 +//! +//! 封装[cnblogs Api](https://api.cnblogs.com/Help#0aee001a01835c83a3277a500ffc9040)至以下模块中: +//! +//! - statuses: 闪存相关api。 +//! - blogs: 博客相关 +//! - news: 新闻相关 +//! - questions: 问题相关 +//! - edu: edu 相关 +//! - user: 用户相关 +//! - token: 认证相关 +//! - marks: 收藏相关 + +pub mod statuses; diff --git a/src/apis/statuses/mod.rs b/src/apis/statuses/mod.rs new file mode 100644 index 0000000..2347f22 --- /dev/null +++ b/src/apis/statuses/mod.rs @@ -0,0 +1,13 @@ +//! cnblogs 闪存接口模块 +//! +//! 实现封装[cnblogs Api](https://api.cnblogs.com/Help#0aee001a01835c83a3277a500ffc9040)中的`Statuses`。 +//! +//! - 获取最新一条闪存内容 https://api.cnblogs.com/api/statuses/recent +//! - 发布闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments +//! - 获取闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments +//! - 删除闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments/{id} +//! - 发布闪存 https://api.cnblogs.com/api/statuses +//! - 删除闪存 https://api.cnblogs.com/api/statuses/{id} +//! - 根据类型获取闪存列表 https://api.cnblogs.com/api/statuses/@{type}?pageIndex={pageIndex}&pageSize={pageSize}&tag={tag} +//! - 根据Id获取闪存 https://api.cnblogs.com/api/statuses/{id} +//! diff --git a/src/bin/cnb.rs b/src/bin/cnb.rs new file mode 100644 index 0000000..64fe301 --- /dev/null +++ b/src/bin/cnb.rs @@ -0,0 +1,241 @@ +#![feature(try_blocks)] +#![feature(if_let_guard)] +#![feature(let_chains)] +#![feature(iterator_try_collect)] +#![feature(iterator_try_reduce)] +#![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] + +extern crate cnblogs_lib; + +use anyhow::Result; +use clap::Parser; +use clap::{Command, CommandFactory}; +use cnblogs_lib::api::auth::session; +use cnblogs_lib::api::fav::Fav; +use cnblogs_lib::api::ing::Ing; +use cnblogs_lib::api::news::News; +use cnblogs_lib::api::post::Post; +use cnblogs_lib::api::user::User; +use cnblogs_lib::args::cmd::post::{CreateCmd, UpdateCmd}; +use cnblogs_lib::args::parser::no_operation; +use cnblogs_lib::args::{parser, Args}; +use cnblogs_lib::display; +use cnblogs_lib::infra::fp::currying::eq; +use cnblogs_lib::infra::infer::infer; +use cnblogs_lib::infra::iter::{ExactSizeIteratorExt, IntoIteratorExt}; +use cnblogs_lib::infra::option::OptionExt; +use cnblogs_lib::infra::result::WrapResult; +use colored::Colorize; +use std::env; + +fn show_non_printable_chars(text: String) -> String { + #[inline] + fn make_red(str: &str) -> String { + format!("{}", str.red()) + } + + text.replace(' ', &make_red("·")) + .replace('\0', &make_red("␀\0")) + .replace('\t', &make_red("␉\t")) + .replace('\n', &make_red("␊\n")) + .replace('\r', &make_red("␍\r")) + .replace("\r\n", &make_red("␍␊\r\n")) +} + +#[allow(clippy::missing_const_for_fn)] +fn panic_if_err(result: &Result) { + if let Err(e) = result { + panic!("{}", e) + } +} + +#[tokio::main(flavor = "multi_thread")] +async fn main() -> Result<()> { + let args_vec = env::args().collect::>(); + if args_vec.iter().any(eq(&"--debug".to_owned())) { + dbg!(args_vec); + } + + let args: Args = Args::parse(); + let global_opt = &args.global_opt; + if global_opt.debug { + dbg!(&args); + } + + let pat = global_opt.with_pat.clone().or_eval_result(session::get_pat); + let style = &global_opt.style; + let time_style = &global_opt.time_style; + let rev = args.rev; + let foe = global_opt.fail_on_error; + + let output = match args { + _ if let Some(pat) = parser::user::login(&args) => { + let cfg_path = session::login(pat); + foe.then(|| panic_if_err(&cfg_path)); + display::login(style, &cfg_path) + } + _ if parser::user::logout(&args) => { + let cfg_path = session::logout(); + foe.then(|| panic_if_err(&cfg_path)); + display::logout(style, &cfg_path) + } + _ if parser::user::user_info(&args) => { + let user_info = User::new(pat?).get_info().await; + foe.then(|| panic_if_err(&user_info)); + display::user_info(style, &user_info)? + } + _ if let Some((skip, take, r#type, align)) = parser::ing::list_ing(&args) => { + let ing_with_comment_iter = infer::>( + try { + let ing_api = Ing::new(pat?); + let ing_vec = ing_api.get_list(skip, take, &r#type).await?; + ing_vec + .into_iter() + .map(|ing| async { + let result = ing_api.get_comment_list(ing.id).await; + result.map(|comment_vec| (ing, comment_vec)) + }) + .join_all() + .await + .into_iter() + .collect::>>()? + }, + ) + .map(|vec| vec.into_iter().dyn_rev(rev)); + foe.then(|| panic_if_err(&ing_with_comment_iter)); + display::list_ing(style, time_style, ing_with_comment_iter, align)? + } + _ if let Some(content) = parser::ing::publish_ing(&args) => { + let content = try { + Ing::new(pat?).publish(content).await?; + content + }; + foe.then(|| panic_if_err(&content)); + display::publish_ing(style, &content) + } + _ if let Some((content, id)) = parser::ing::comment_ing(&args) => { + let content = try { + Ing::new(pat?) + .comment(id, content.clone(), None, None) + .await?; + content + }; + foe.then(|| panic_if_err(&content)); + display::comment_ing(style, &content) + } + _ if let Some(id) = parser::post::show_post(&args) => { + let entry = Post::new(pat?).get_one(id).await; + foe.then(|| panic_if_err(&entry)); + display::show_post(style, &entry)? + } + _ if let Some(id) = parser::post::show_post_meta(&args) => { + let entry = Post::new(pat?).get_one(id).await; + foe.then(|| panic_if_err(&entry)); + display::show_post_meta(style, time_style, &entry)? + } + _ if let Some(id) = parser::post::show_post_comment(&args) => { + let comment_iter = Post::new(pat?) + .get_comment_list(id) + .await + .map(|vec| vec.into_iter().dyn_rev(rev)); + foe.then(|| panic_if_err(&comment_iter)); + display::show_post_comment(style, time_style, comment_iter)? + } + _ if let Some((skip, take)) = parser::post::list_post(&args) => { + let meta_iter = Post::new(pat?) + .get_meta_list(skip, take) + .await + .map(|(vec, count)| (vec.into_iter().dyn_rev(rev), count)); + foe.then(|| panic_if_err(&meta_iter)); + display::list_post(style, meta_iter)? + } + _ if let Some(id) = parser::post::delete_post(&args) => { + let id = try { + Post::new(pat?).del_one(id).await?; + id + }; + foe.then(|| panic_if_err(&id)); + display::delete_post(style, &id) + } + _ if let Some((kw, skip, take)) = parser::post::search_self_post(&args) => { + let result = Post::new(pat?) + .search_self(skip, take, kw) + .await + .map(|(vec, count)| (vec.into_iter().dyn_rev(rev), count)); + foe.then(|| panic_if_err(&result)); + display::search_self_post(style, result)? + } + _ if let Some((kw, skip, take)) = parser::post::search_site_post(&args) => { + let result = Post::new(pat?) + .search_site(skip, take, kw) + .await + .map(|vec| vec.into_iter().dyn_rev(rev)); + foe.then(|| panic_if_err(&result)); + display::search_site_post(style, time_style, result)? + } + _ if let Some(create_cmd) = parser::post::create_post(&args) => { + let CreateCmd { + title, + body, + publish, + .. + } = create_cmd; + let id = Post::new(pat?).create(title, body, *publish).await; + foe.then(|| panic_if_err(&id)); + display::create_post(style, &id) + } + _ if let Some((id, update_cmd)) = parser::post::update_post(&args) => { + let UpdateCmd { + title, + body, + publish, + .. + } = update_cmd; + let id = Post::new(pat?).update(id, title, body, publish).await; + foe.then(|| panic_if_err(&id)); + display::update_post(style, &id) + } + _ if let Some((skip, take)) = parser::news::list_news(&args) => { + let news_iter = News::new(pat?) + .get_list(skip, take) + .await + .map(|vec| vec.into_iter().dyn_rev(rev)); + foe.then(|| panic_if_err(&news_iter)); + display::list_news(style, time_style, news_iter)? + } + _ if let Some((skip, take)) = parser::fav::list_fav(&args) => { + let fav_iter = Fav::new(pat?) + .get_list(skip, take) + .await + .map(|vec| vec.into_iter().dyn_rev(rev)); + foe.then(|| panic_if_err(&fav_iter)); + display::list_fav(style, time_style, fav_iter)? + } + + _ if no_operation(&args) => infer::(Args::command()).render_help().to_string(), + _ => "Invalid usage, follow '--help' for more information".to_owned(), + }; + + if global_opt.quiet { + return ().wrap_ok(); + } + + let output = { + let output = if output.ends_with("\n\n") { + output[..output.len() - 1].to_owned() + } else if output.ends_with('\n') { + output + } else { + format!("{}\n", output) + }; + if global_opt.debug { + show_non_printable_chars(output) + } else { + output + } + }; + + print!("{}", output); + + ().wrap_ok() +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..984f7bc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +#![feature(try_blocks)] +#![feature(if_let_guard)] +#![feature(let_chains)] +#![feature(iterator_try_collect)] +#![feature(iterator_try_reduce)] +#![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] + +pub mod api; +pub mod apis; +pub mod args; +pub mod display; +pub mod infra; From 623750fbd9d82d7e38cdb003c8c79d750353cef6 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Fri, 12 Jan 2024 11:34:42 +0800 Subject: [PATCH 04/48] feat: create cnblogs api client wrapper for ing operate. --- src/apis/ing/comment.rs | 77 +++++++++++++++ src/apis/ing/mod.rs | 197 +++++++++++++++++++++++++++++++++++++++ src/apis/mod.rs | 7 +- src/apis/statuses/mod.rs | 13 --- src/apis/token/mod.rs | 134 ++++++++++++++++++++++++++ 5 files changed, 414 insertions(+), 14 deletions(-) create mode 100644 src/apis/ing/comment.rs create mode 100644 src/apis/ing/mod.rs delete mode 100644 src/apis/statuses/mod.rs create mode 100644 src/apis/token/mod.rs diff --git a/src/apis/ing/comment.rs b/src/apis/ing/comment.rs new file mode 100644 index 0000000..163debc --- /dev/null +++ b/src/apis/ing/comment.rs @@ -0,0 +1,77 @@ +//! 闪存评论相关 +//! + +use anyhow::{Ok, Result}; +use reqwest::{Client, Response}; +use serde::{Deserialize, Serialize}; + +use crate::{infra::http::RequestBuilderExt, openapi}; + +/// 闪存评论及评论回复 +/// +/// replay_to: 在web端有一个ReplyToUserId,这里盲猜是这个 +/// parent_comment_id: 0 是对某条闪存评论,如果对闪存评论要回应,这里则是闪存评论的id +/// content: 评论内容。 如果是对闪存评论回应,则应加上`@用户名称` +/// +#[derive(Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(default)] +pub struct StatusComment { + #[serde(skip)] + pub status_id: String, + pub replay_to: u64, + pub parent_comment_id: u64, + pub content: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct IngCommentEntry { + pub id: usize, + pub content: String, + #[serde(rename = "DateAdded")] + pub create_time: String, + pub status_id: usize, + pub user_alias: String, + #[serde(rename = "UserDisplayName")] + pub user_name: String, + pub user_icon_url: String, + pub user_id: usize, + pub user_guid: String, +} + +/// 根据闪存ID发表一个评论 +pub async fn post(token: String, sc: StatusComment) -> Result { + let r = Client::new() + .post(openapi!("/statuses/{}/comments", sc.parent_comment_id)) + .pat_auth(token.as_str()) + .form(&sc) + .send() + .await? + .error_for_status()?; + Ok(r) +} + +/// 根据闪存ID获取评论 +pub async fn get(token: &str, status_id: &str) -> Result> { + let r = Client::new() + .get(openapi!("/statuses/{}/comments", status_id)) + .pat_auth(token) + .send() + .await? + .error_for_status()? + .json() + .await?; + Ok(r) +} + +/// 根据闪存ID和commentid删除评论 +pub async fn delete(token: &str, status_id: &str, comment_id: &str) -> Result<()> { + Client::new() + .delete(openapi!("/statuses/{}/comments/{}", status_id, comment_id)) + .pat_auth(token) + .send() + .await? + .error_for_status()?; + Ok(()) +} diff --git a/src/apis/ing/mod.rs b/src/apis/ing/mod.rs new file mode 100644 index 0000000..7f76ffe --- /dev/null +++ b/src/apis/ing/mod.rs @@ -0,0 +1,197 @@ +//! cnblogs 闪存接口模块 +//! +//! 实现封装[cnblogs Api](https://api.cnblogs.com/Help#0aee001a01835c83a3277a500ffc9040)中的`Statuses`。 +//! +//! - 获取自己最新一条闪存内容 https://api.cnblogs.com/api/statuses/recent +//! - 发布闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments +//! - 获取闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments +//! - 删除闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments/{id} +//! - 发布闪存 https://api.cnblogs.com/api/statuses +//! - 删除闪存 https://api.cnblogs.com/api/statuses/{id} +//! - 根据类型获取闪存列表 https://api.cnblogs.com/api/statuses/@{type}?pageIndex={pageIndex}&pageSize={pageSize}&tag={tag} +//! - 根据Id获取闪存 https://api.cnblogs.com/api/statuses/{id} +//! + +pub mod comment; + +use anyhow::{Ok, Result}; +use reqwest::{Client, Response}; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +use crate::{infra::http::RequestBuilderExt, openapi}; + +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +#[serde(default)] +pub struct IngContent { + pub content: String, + pub is_private: bool, + pub client_type: IngSendFrom, +} + +#[derive(Clone, Debug, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum IngSendFrom { + None = 0, + Ms = 1, + GTalk = 2, + Qq = 3, + Sms = 5, + CellPhone = 6, + Web = 8, + VsCode = 9, + Cli = 13, +} + +impl Default for IngSendFrom { + fn default() -> Self { + return IngSendFrom::Cli; + } +} + +/// 查询条件,用于根据类别查询 +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(default)] +pub struct QeurySet { + #[serde(skip)] + pub types: QueryIngType, + pub page_index: u64, + pub page_size: u64, + #[serde(skip_serializing_if = "String::is_empty")] + pub tag: String, +} + +impl Default for QeurySet { + fn default() -> Self { + return Self { + types: QueryIngType::default(), + page_index: 1, + page_size: 30, + tag: "".to_string(), + }; + } +} + +/// +/// Follow = 1, 关注 +/// Myself = 4, 我的 +/// Public = 5, +/// RecentComment = 6, //新回应 +/// MyComment = 7, 我回应 +/// Tag = 10, tag 必填 +/// Comment = 13 回复我 +/// Mention = 14, +#[derive(Debug)] +pub enum QueryIngType { + Following, + My, + MyComment, + RecentComment, + Mention, + Comment, + All, +} + +impl Default for QueryIngType { + fn default() -> Self { + return Self::All; + } +} + +impl QueryIngType { + fn as_u8(&self) -> u8 { + match self { + QueryIngType::Following => 1, + QueryIngType::My => 4, + QueryIngType::All => 5, + QueryIngType::RecentComment => 6, + QueryIngType::MyComment => 7, + QueryIngType::Mention => 14, + QueryIngType::Comment => 13, + } + } +} + +/// 闪存详细内容。 +/// +/// 用于根据ID查询闪存的结果解析。 +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct IngEntry { + pub id: u64, + pub content: String, + pub is_private: bool, + pub is_lucky: bool, + pub comment_count: u64, + pub date_added: String, + pub user_alias: String, + pub user_display_name: String, + pub user_icon_url: String, + pub user_id: u64, + pub user_guid: String, + pub send_from: u8, + pub icons: String, +} + +pub async fn lastest(token: &str) -> Result { + let c = Client::new() + .get(openapi!("/statuses/recent")) + .pat_auth(token) + .send() + .await? + .error_for_status()?; + Ok(c) +} + +/// 根据条件查询 +/// +/// 如果是tag是,一定要传入Tag,tag是自己想查询的比如Linux,Debian,Python等等。 +/// 页数是从1开始的 +pub async fn query(token: &str, q: &QeurySet) -> Result> { + let r = Client::new() + .get(openapi!("/statuses/@{}", q.types.as_u8())) + .pat_auth(token) + .query(&q) + .send() + .await? + .error_for_status()? + .json::>() + .await?; + Ok(r) +} + +/// 根据ID查询 +pub async fn query_by_id(token: &str, id: &str) -> Result { + let r = Client::new() + .get(openapi!("/statuses/{}", id)) + .pat_auth(token) + .send() + .await? + .error_for_status()? + .json::() + .await?; + Ok(r) +} + +/// 发布一条闪存 +pub async fn post(token: &str, c: &IngContent) -> Result { + let r = Client::new() + .post(openapi!("/statuses")) + .pat_auth(token) + .json(c) + .send() + .await?; + Ok(r) +} + +/// 删除一条闪存 +pub async fn delete(token: &str, id: String) -> Result { + let r = Client::new() + .post(openapi!("/statuses/{}", id)) + .pat_auth(token) + .send() + .await?; + Ok(r) +} diff --git a/src/apis/mod.rs b/src/apis/mod.rs index b2fa28a..67572a9 100644 --- a/src/apis/mod.rs +++ b/src/apis/mod.rs @@ -11,4 +11,9 @@ //! - token: 认证相关 //! - marks: 收藏相关 -pub mod statuses; +pub mod ing; +pub mod token; + +pub const OAUTH_CLIENT: &str = "https://api.cnblogs.com/token"; +pub const OAUTH_TOKEN: &str = "https://oauth.cnblogs.com/connect/token"; +pub const OAUTHORIZE: &str = "https://oauth.cnblogs.com/connect/authorize"; diff --git a/src/apis/statuses/mod.rs b/src/apis/statuses/mod.rs deleted file mode 100644 index 2347f22..0000000 --- a/src/apis/statuses/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! cnblogs 闪存接口模块 -//! -//! 实现封装[cnblogs Api](https://api.cnblogs.com/Help#0aee001a01835c83a3277a500ffc9040)中的`Statuses`。 -//! -//! - 获取最新一条闪存内容 https://api.cnblogs.com/api/statuses/recent -//! - 发布闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments -//! - 获取闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments -//! - 删除闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments/{id} -//! - 发布闪存 https://api.cnblogs.com/api/statuses -//! - 删除闪存 https://api.cnblogs.com/api/statuses/{id} -//! - 根据类型获取闪存列表 https://api.cnblogs.com/api/statuses/@{type}?pageIndex={pageIndex}&pageSize={pageSize}&tag={tag} -//! - 根据Id获取闪存 https://api.cnblogs.com/api/statuses/{id} -//! diff --git a/src/apis/token/mod.rs b/src/apis/token/mod.rs new file mode 100644 index 0000000..d41ff78 --- /dev/null +++ b/src/apis/token/mod.rs @@ -0,0 +1,134 @@ +//! Token +//! +//! TokenApi的封装 +//! +//! OAuth认证,提供两种方式接口。 +//! +//! 1. Client_Credentials +//! 2. Authorization_Code +//! + +use super::{OAUTH_CLIENT, OAUTH_TOKEN}; +use anyhow::Result; +use reqwest::Client; +use serde::{Deserialize, Serialize}; + +/// 认证授权后的授权 +/// +/// # Filed +/// +/// - access_token: Token String +/// - expires_in: 过期时间 +/// - token_type: Token认证方式 +/// - refresh_token: 过期后刷新Token,如果是ClientCredentials,此字段无用。 +/// - id_token: id,如果是ClientCredentials,此字段无用 +/// - scope: 客户端权限 +/// +#[derive(Debug, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct OAuthToken { + pub id_token: String, + pub access_token: String, + pub expires_in: u64, + pub token_type: String, + pub refresh_token: String, + pub scope: String, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(default)] +pub struct ClientCredentialsReq { + pub client_id: String, + pub client_secret: String, + pub grant_type: String, +} + +pub async fn client_credentials(req: ClientCredentialsReq) -> Result { + let c = Client::new().post(OAUTH_CLIENT); + let r = c + .form(&req) + .send() + .await? + .error_for_status()? + .json::() + .await?; + Ok(r) +} + +/// OAuth 获取Token结构体 +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct OauthTokenReq { + // pub client_id: String, // string 是 授权ID client_id + // pub client_secret: String, // string 是 密钥 client_secret + // pub grant_type: String, // string 是 授权模式 authorization_code + #[serde(flatten)] + pub cc: ClientCredentialsReq, + pub code: String, // string 是 授权码 code + pub redirect_uri: String, // string 是 回调地址(默认) https://oauth.cnblogs.com/auth/callback +} + +impl OauthTokenReq { + pub fn new(client_id: String, client_secret: String, code: String) -> Self { + OauthTokenReq { + cc: ClientCredentialsReq { + client_id, + client_secret, + grant_type: "authorization_code".to_string(), + }, + code, + redirect_uri: "https://oauth.cnblogs.com/auth/callback".to_string(), + } + } +} + +/// 获取令牌 +pub async fn authorization_code(req: OauthTokenReq) -> Result { + let c = Client::new().post(OAUTH_TOKEN); + let r = c + .form(&req) + .send() + .await? + .error_for_status()? + .json::() + .await?; + Ok(r) +} + +/// OAuth 获取Token结构体 +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct RefreshTokenReq { + // pub client_id: String, + // pub client_secret: String, + // pub grant_type: String, + #[serde(flatten)] + pub cc: ClientCredentialsReq, + pub refresh_token: String, +} + +impl RefreshTokenReq { + pub fn new(client_id: String, client_secret: String, refresh_token: String) -> Self { + return RefreshTokenReq { + cc: ClientCredentialsReq { + client_id, + client_secret, + grant_type: "refresh_token".to_string(), + }, + refresh_token, + }; + } +} + +/// 刷新令牌 +/// +/// 令牌过期后重新获取。 +pub async fn refresh_token(req: RefreshTokenReq) -> Result { + let c = Client::new().post(OAUTH_TOKEN); + let r = c + .form(&req) + .send() + .await? + .error_for_status()? + .json::() + .await?; + Ok(r) +} From 23b42c3bf3eacb6a984992d1691a72e809396e38 Mon Sep 17 00:00:00 2001 From: Roc Sun <710989028@qq.com> Date: Thu, 18 Jan 2024 18:15:37 +0800 Subject: [PATCH 05/48] feat: add query operate for ing. --- Cargo.toml | 1 + README.zh-CN.md | 13 +++++ src/apis/ing/mod.rs | 52 ++++++++++++----- src/args/cmd/ing.rs | 127 ++++++++++++++++++++++++++++++++++++++++- src/args/parser/ing.rs | 35 ++++++++++++ src/bin/cnb.rs | 8 +++ src/lib.rs | 1 + src/logic/ing.rs | 43 ++++++++++++++ src/logic/mod.rs | 6 ++ 9 files changed, 270 insertions(+), 16 deletions(-) create mode 100644 src/logic/ing.rs create mode 100644 src/logic/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 481b8a3..02684e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT" repository = "https://github.com/cnblogs/cli" keywords = ["cli", "cnblogs", "blog"] categories = ["command-line-utilities"] +default-run = "cnb" [profile.dev] lto = true diff --git a/README.zh-CN.md b/README.zh-CN.md index 197dea1..89a95e9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -50,3 +50,16 @@ cnb user --info ```shell cnb posts [comment] [list,create,query,delete,update] --[id/file/quertset] --[pagesize,pagecount] ``` + +## 闪存cli + +闪存cli设计如下: + +```sh +cnb ing query # 默认10条 +cnb ing query --id 123456 +cnb ing query --page 1 --count 10 +cnb ing query --type All --page 1 --count 10 --tag Linux +cnb ing create --conent hello --private false --lucky false +cnb ing delete --id 123456 +``` diff --git a/src/apis/ing/mod.rs b/src/apis/ing/mod.rs index 7f76ffe..ad17840 100644 --- a/src/apis/ing/mod.rs +++ b/src/apis/ing/mod.rs @@ -15,21 +15,34 @@ pub mod comment; use anyhow::{Ok, Result}; +use clap::{ValueEnum, Parser}; use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::{infra::http::RequestBuilderExt, openapi}; -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] #[serde(default)] pub struct IngContent { pub content: String, pub is_private: bool, + pub lucky: bool, pub client_type: IngSendFrom, } +impl Default for IngContent { + fn default() -> Self { + IngContent { + content: "".to_string(), + is_private: true, + lucky: false, + client_type: IngSendFrom::default(), + } + } +} + #[derive(Clone, Debug, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum IngSendFrom { @@ -56,7 +69,7 @@ impl Default for IngSendFrom { #[serde(default)] pub struct QeurySet { #[serde(skip)] - pub types: QueryIngType, + pub r#type: QueryIngType, pub page_index: u64, pub page_size: u64, #[serde(skip_serializing_if = "String::is_empty")] @@ -66,9 +79,9 @@ pub struct QeurySet { impl Default for QeurySet { fn default() -> Self { return Self { - types: QueryIngType::default(), + r#type: QueryIngType::default(), page_index: 1, - page_size: 30, + page_size: 10, tag: "".to_string(), }; } @@ -83,15 +96,16 @@ impl Default for QeurySet { /// Tag = 10, tag 必填 /// Comment = 13 回复我 /// Mention = 14, -#[derive(Debug)] +#[derive(Debug, Clone, ValueEnum, Parser)] pub enum QueryIngType { - Following, - My, - MyComment, - RecentComment, - Mention, - Comment, - All, + Following = 1, + My = 4, + All = 5, + RecentComment = 6, + MyComment = 7, + Tag = 10, + Comment = 13, + Mention = 14, } impl Default for QueryIngType { @@ -100,6 +114,15 @@ impl Default for QueryIngType { } } +impl From for QueryIngType { + fn from(value: u8) -> Self { + match value { + 1 => Self::Following, + _ => Self::All, + } + } +} + impl QueryIngType { fn as_u8(&self) -> u8 { match self { @@ -108,6 +131,7 @@ impl QueryIngType { QueryIngType::All => 5, QueryIngType::RecentComment => 6, QueryIngType::MyComment => 7, + QueryIngType::Tag => 10, QueryIngType::Mention => 14, QueryIngType::Comment => 13, } @@ -151,7 +175,7 @@ pub async fn lastest(token: &str) -> Result { /// 页数是从1开始的 pub async fn query(token: &str, q: &QeurySet) -> Result> { let r = Client::new() - .get(openapi!("/statuses/@{}", q.types.as_u8())) + .get(openapi!("/statuses/@{}", q.r#type.as_u8())) .pat_auth(token) .query(&q) .send() @@ -163,7 +187,7 @@ pub async fn query(token: &str, q: &QeurySet) -> Result> { } /// 根据ID查询 -pub async fn query_by_id(token: &str, id: &str) -> Result { +pub async fn query_by_id(token: &str, id: &u64) -> Result { let r = Client::new() .get(openapi!("/statuses/{}", id)) .pat_auth(token) diff --git a/src/args/cmd/ing.rs b/src/args/cmd/ing.rs index 974c6bc..fe96460 100644 --- a/src/args/cmd/ing.rs +++ b/src/args/cmd/ing.rs @@ -1,5 +1,8 @@ -use crate::api::ing::IngType; -use clap::{Parser, Subcommand}; +use crate::{ + api::ing::IngType, + apis::{self, ing::QeurySet}, +}; +use clap::{Args, Parser, Subcommand, ValueEnum}; #[derive(Parser, Debug)] #[non_exhaustive] @@ -54,4 +57,124 @@ pub enum Cmd { #[arg(default_value_t = true)] align: bool, }, + + /// 根据条件查询闪存。 + Query(QueryIng), + + /// 创建闪存 + Create(CreateIng), + + /// 根据ID删除闪存 + Delete { + #[arg(long)] + id: Vec, + }, +} + +#[derive(Debug, Args)] +pub struct CreateIng { + /// 闪存内容 + // #[arg(short, long)] + content: Option, + #[arg(short, long, default_value_t = true)] + private: bool, + + #[arg(short, long, default_value_t = false)] + lucky: bool, + + #[arg(short, long)] + tag: Option, +} + +#[derive(Args, Debug)] +struct Create { + /// 闪存内容 + content: String, + + /// 是否私有,默认是全站 + #[arg(short, long, default_value_t = true)] + private: bool, + + /// 是否发布为幸运 + #[arg(short, long, default_value_t = false)] + lucky: bool, + + /// 是否发布在某个标签下,默认不发布标签。 + #[arg(short, long, default_value = "")] + tag: String, +} + +/// 查询参数 +/// 当使用type为 +#[derive(Debug, Args, Clone)] +pub struct QueryIng { + /// 查询类型 + #[arg( + short, + long, + value_name = "TYPE", + default_value_t = QueryType::F, + default_missing_value = "f", + value_enum + )] + pub r#type: QueryType, + /// 分页查询,起始索引是1 + #[arg(short('n'), long, default_value_t = 1)] + pub page_index: u64, + /// 分页查询数量, 默认是10 + #[arg(short('s'), long, default_value_t = 10)] + pub page_size: u64, + /// 按照标签查询 + #[arg(short('g'), long)] + pub tag: Option, + /// 根据ID查询 + #[arg(short, long)] + pub id: Option>, +} + +impl From<&QueryIng> for QeurySet { + fn from(value: &QueryIng) -> Self { + Self { + r#type: value.r#type.clone().into(), + page_index: value.page_index, + page_size: value.page_size, + tag: value.tag.clone().unwrap_or_default(), + } + } +} + +/// 过滤的类型 +#[derive(Debug, Clone, ValueEnum, Parser)] +pub enum QueryType { + /// 关注 + F = 1, + /// 我的 + My = 4, + /// 全站 + P = 5, + /// 新回应 + Rc = 6, + /// 我回应 + Mc = 7, + /// 按照Tag过滤,使用T时,如果没有Query::tag或者站点不存在,则不会有结果。 + T = 10, + /// 回复我 + C = 13, + /// 提到我 + M = 14, +} + +impl From for apis::ing::QueryIngType { + fn from(value: QueryType) -> Self { + match value { + QueryType::F => Self::Following, + QueryType::My => Self::My, + QueryType::P => Self::All, + QueryType::Rc => Self::RecentComment, + QueryType::Mc => Self::MyComment, + QueryType::T => Self::Tag, + QueryType::C => Self::Comment, + QueryType::M => Self::Mention, + } + } } diff --git a/src/args/parser/ing.rs b/src/args/parser/ing.rs index 62ed32b..2c8e42c 100644 --- a/src/args/parser/ing.rs +++ b/src/args/parser/ing.rs @@ -1,4 +1,5 @@ use crate::api::ing::IngType; +use crate::args::cmd::ing::QueryIng; use crate::args::parser::{get_skip, get_take}; use crate::args::{cmd, Args, Cmd}; use crate::infra::option::WrapOption; @@ -67,3 +68,37 @@ pub fn comment_ing(args: &Args) -> Option<(&String, usize)> { } .wrap_some() } + +#[allow(unused)] +pub fn query(args: &Args) -> Option { + match args { + Args { + cmd: + Some(Cmd::Ing(cmd::ing::Opt { + cmd: + Some(cmd::ing::Cmd::Query(QueryIng { + r#type, + page_index, + page_size, + tag, + id, + })), + publish: None, + comment: None, + })), + id: None, + rev: _, + skip, + take, + global_opt: _, + } => QueryIng { + r#type: r#type.clone(), + page_index: page_index.clone(), + page_size: page_size.clone(), + tag: tag.clone(), + id: id.clone(), + }, + _ => return None, + } + .wrap_some() +} diff --git a/src/bin/cnb.rs b/src/bin/cnb.rs index 64fe301..0a5c1ae 100644 --- a/src/bin/cnb.rs +++ b/src/bin/cnb.rs @@ -25,6 +25,7 @@ use cnblogs_lib::infra::infer::infer; use cnblogs_lib::infra::iter::{ExactSizeIteratorExt, IntoIteratorExt}; use cnblogs_lib::infra::option::OptionExt; use cnblogs_lib::infra::result::WrapResult; +use cnblogs_lib::logic::ing::get_ings_and_comments; use colored::Colorize; use std::env; @@ -84,6 +85,13 @@ async fn main() -> Result<()> { foe.then(|| panic_if_err(&user_info)); display::user_info(style, &user_info)? } + + _ if let Some(q) = parser::ing::query(&args) => { + + get_ings_and_comments(pat.unwrap().as_str(), &q).await; + "".to_string() + } + _ if let Some((skip, take, r#type, align)) = parser::ing::list_ing(&args) => { let ing_with_comment_iter = infer::>( try { diff --git a/src/lib.rs b/src/lib.rs index 984f7bc..4c7b191 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,3 +10,4 @@ pub mod apis; pub mod args; pub mod display; pub mod infra; +pub mod logic; diff --git a/src/logic/ing.rs b/src/logic/ing.rs new file mode 100644 index 0000000..a79f2f7 --- /dev/null +++ b/src/logic/ing.rs @@ -0,0 +1,43 @@ +//! 闪存相关逻辑 +//! + +use crate::{ + apis::ing::{query_by_id, query as iq}, + args::cmd::ing::QueryIng, + infra::iter::IntoIteratorExt, +}; + +pub async fn get_ings_and_comments(t: &str, q: &QueryIng) { + println!("{:?}", q); + if let Some(ids) = &q.id { + let a = ids + .iter() + .map(|id| async move { query_by_id(t, id).await }) + .join_all() + .await + ; + + a.iter().for_each(|i| { + match i { + Ok(e) => { + println!("{:?}", e); + } + Err(e) => { + eprintln!("{:?}", e); + } + } + }) + + } + + let a: Result, anyhow::Error> = iq(t, &q.into()).await; + match a { + Ok(e) => { + println!("{:?}", e); + } + Err(e) => { + eprintln!("{:?}", e); + } + } + +} diff --git a/src/logic/mod.rs b/src/logic/mod.rs new file mode 100644 index 0000000..b9ab01d --- /dev/null +++ b/src/logic/mod.rs @@ -0,0 +1,6 @@ +//! cli操作逻辑 +//! +//! 此模块暂定封装操作逻辑,比如是闪存的curd,闪存评论的curd。 +//! + +pub mod ing; \ No newline at end of file From 2ce11434d42230be69b5097e15e6ecce73f252dd Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 23 Jan 2024 23:22:29 +0800 Subject: [PATCH 06/48] refactor: refactor ing command, add query, create, delete subcommands. --- README.zh-CN.md | 15 ++++-- src/apis/ing/comment.rs | 18 +------ src/apis/ing/mod.rs | 58 +++++++--------------- src/args/cmd/ing.rs | 52 ++++++++++---------- src/args/parser/ing.rs | 58 +++++++++++++++++++++- src/bin/cnb.rs | 20 ++++++-- src/logic/ing.rs | 103 ++++++++++++++++++++++++++++++---------- src/logic/mod.rs | 6 +-- 8 files changed, 208 insertions(+), 122 deletions(-) diff --git a/README.zh-CN.md b/README.zh-CN.md index 89a95e9..a58b5d5 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -56,10 +56,15 @@ cnb posts [comment] [list,create,query,delete,update] --[id/file/quertset] --[pa 闪存cli设计如下: ```sh -cnb ing query # 默认10条 -cnb ing query --id 123456 -cnb ing query --page 1 --count 10 -cnb ing query --type All --page 1 --count 10 --tag Linux -cnb ing create --conent hello --private false --lucky false +cnb ing query # 默认10条s +cnb ing query --id 123456 --id 123 +cnb ing query -n 1 -s 10 +cnb ing query --type f -n 2 -s 10 +# 根据tag查找,-g为tag名称 -n 2 -s 10 分页 +cnb ing query -t t -g Linux +cnb ing create hello --private --lucky +cnb ing create hello --private --lucky --tag hello cnb ing delete --id 123456 ``` + +TODO: “提到我”存在解析问题。待完善。 diff --git a/src/apis/ing/comment.rs b/src/apis/ing/comment.rs index 163debc..c6ae97a 100644 --- a/src/apis/ing/comment.rs +++ b/src/apis/ing/comment.rs @@ -5,7 +5,7 @@ use anyhow::{Ok, Result}; use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; -use crate::{infra::http::RequestBuilderExt, openapi}; +use crate::{api::ing::get_comment_list::IngCommentEntry, infra::http::RequestBuilderExt, openapi}; /// 闪存评论及评论回复 /// @@ -24,22 +24,6 @@ pub struct StatusComment { pub content: String, } -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct IngCommentEntry { - pub id: usize, - pub content: String, - #[serde(rename = "DateAdded")] - pub create_time: String, - pub status_id: usize, - pub user_alias: String, - #[serde(rename = "UserDisplayName")] - pub user_name: String, - pub user_icon_url: String, - pub user_id: usize, - pub user_guid: String, -} - /// 根据闪存ID发表一个评论 pub async fn post(token: String, sc: StatusComment) -> Result { let r = Client::new() diff --git a/src/apis/ing/mod.rs b/src/apis/ing/mod.rs index ad17840..8364481 100644 --- a/src/apis/ing/mod.rs +++ b/src/apis/ing/mod.rs @@ -15,12 +15,15 @@ pub mod comment; use anyhow::{Ok, Result}; -use clap::{ValueEnum, Parser}; +use clap::{Parser, ValueEnum}; use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; -use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::{infra::http::RequestBuilderExt, openapi}; +use crate::{ + api::ing::{get_list::IngEntry, IngSendFrom}, + infra::http::RequestBuilderExt, + openapi, +}; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] @@ -43,20 +46,6 @@ impl Default for IngContent { } } -#[derive(Clone, Debug, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum IngSendFrom { - None = 0, - Ms = 1, - GTalk = 2, - Qq = 3, - Sms = 5, - CellPhone = 6, - Web = 8, - VsCode = 9, - Cli = 13, -} - impl Default for IngSendFrom { fn default() -> Self { return IngSendFrom::Cli; @@ -90,12 +79,12 @@ impl Default for QeurySet { /// /// Follow = 1, 关注 /// Myself = 4, 我的 -/// Public = 5, -/// RecentComment = 6, //新回应 +/// Public = 5, 全站 +/// RecentComment = 6, 新回应 /// MyComment = 7, 我回应 /// Tag = 10, tag 必填 /// Comment = 13 回复我 -/// Mention = 14, +/// Mention = 14, @我 #[derive(Debug, Clone, ValueEnum, Parser)] pub enum QueryIngType { Following = 1, @@ -118,6 +107,12 @@ impl From for QueryIngType { fn from(value: u8) -> Self { match value { 1 => Self::Following, + 4 => Self::My, + 6 => Self::RecentComment, + 7 => Self::MyComment, + 10 => Self::Tag, + 13 => Self::Comment, + 14 => Self::Mention, _ => Self::All, } } @@ -138,27 +133,6 @@ impl QueryIngType { } } -/// 闪存详细内容。 -/// -/// 用于根据ID查询闪存的结果解析。 -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct IngEntry { - pub id: u64, - pub content: String, - pub is_private: bool, - pub is_lucky: bool, - pub comment_count: u64, - pub date_added: String, - pub user_alias: String, - pub user_display_name: String, - pub user_icon_url: String, - pub user_id: u64, - pub user_guid: String, - pub send_from: u8, - pub icons: String, -} - pub async fn lastest(token: &str) -> Result { let c = Client::new() .get(openapi!("/statuses/recent")) @@ -211,7 +185,7 @@ pub async fn post(token: &str, c: &IngContent) -> Result { } /// 删除一条闪存 -pub async fn delete(token: &str, id: String) -> Result { +pub async fn delete(token: &str, id: u64) -> Result { let r = Client::new() .post(openapi!("/statuses/{}", id)) .pat_auth(token) diff --git a/src/args/cmd/ing.rs b/src/args/cmd/ing.rs index fe96460..96b050e 100644 --- a/src/args/cmd/ing.rs +++ b/src/args/cmd/ing.rs @@ -1,6 +1,9 @@ use crate::{ - api::ing::IngType, - apis::{self, ing::QeurySet}, + api::ing::{IngType, IngSendFrom}, + apis::{ + self, + ing::{IngContent, QeurySet}, + }, }; use clap::{Args, Parser, Subcommand, ValueEnum}; @@ -59,49 +62,48 @@ pub enum Cmd { }, /// 根据条件查询闪存。 + /// Query(QueryIng), /// 创建闪存 Create(CreateIng), /// 根据ID删除闪存 - Delete { - #[arg(long)] - id: Vec, - }, + Delete { id: Vec }, } #[derive(Debug, Args)] pub struct CreateIng { /// 闪存内容 - // #[arg(short, long)] - content: Option, - #[arg(short, long, default_value_t = true)] - private: bool, - - #[arg(short, long, default_value_t = false)] - lucky: bool, - - #[arg(short, long)] - tag: Option, -} - -#[derive(Args, Debug)] -struct Create { - /// 闪存内容 - content: String, + pub content: String, /// 是否私有,默认是全站 #[arg(short, long, default_value_t = true)] - private: bool, + pub private: bool, /// 是否发布为幸运 #[arg(short, long, default_value_t = false)] - lucky: bool, + pub lucky: bool, /// 是否发布在某个标签下,默认不发布标签。 #[arg(short, long, default_value = "")] - tag: String, + pub tag: String, +} + +impl From for IngContent { + fn from(value: CreateIng) -> Self { + let mut cont = String::new(); + if !value.tag.is_empty() { + cont.push_str(format!("[{}]", value.tag).as_str()) + } + cont.push_str(value.content.as_str()); + Self { + content: cont, + is_private: value.private, + lucky: value.lucky, + client_type: IngSendFrom::Cli + } + } } /// 查询参数 diff --git a/src/args/parser/ing.rs b/src/args/parser/ing.rs index 2c8e42c..e458a47 100644 --- a/src/args/parser/ing.rs +++ b/src/args/parser/ing.rs @@ -1,5 +1,5 @@ use crate::api::ing::IngType; -use crate::args::cmd::ing::QueryIng; +use crate::args::cmd::ing::{QueryIng, CreateIng}; use crate::args::parser::{get_skip, get_take}; use crate::args::{cmd, Args, Cmd}; use crate::infra::option::WrapOption; @@ -102,3 +102,59 @@ pub fn query(args: &Args) -> Option { } .wrap_some() } + + +#[allow(unused)] +pub fn create_ing(args: &Args) -> Option { + match args { + Args { + cmd: + Some(Cmd::Ing(cmd::ing::Opt { + cmd: + Some(cmd::ing::Cmd::Create(CreateIng { + content, + private, + lucky, + tag, + })), + publish: None, + comment: None, + })), + id: None, + rev: _, + skip, + take, + global_opt: _, + } => CreateIng { + content: content.clone(), + private:private.clone(), + lucky: lucky.clone(), + tag: tag.clone(), + }, + _ => return None, + } + .wrap_some() +} + + +#[allow(unused)] +pub fn delete(args: &Args) -> Option> { + match args { + Args { + cmd: + Some(Cmd::Ing(cmd::ing::Opt { + cmd: + Some(cmd::ing::Cmd::Delete{id}), + publish: None, + comment: None, + })), + id: None, + rev: _, + skip, + take, + global_opt: _, + } => id.clone(), + _ => return None, + } + .wrap_some() +} diff --git a/src/bin/cnb.rs b/src/bin/cnb.rs index 0a5c1ae..4523e1d 100644 --- a/src/bin/cnb.rs +++ b/src/bin/cnb.rs @@ -19,13 +19,12 @@ use cnblogs_lib::api::user::User; use cnblogs_lib::args::cmd::post::{CreateCmd, UpdateCmd}; use cnblogs_lib::args::parser::no_operation; use cnblogs_lib::args::{parser, Args}; -use cnblogs_lib::display; +use cnblogs_lib::{display, logic}; use cnblogs_lib::infra::fp::currying::eq; use cnblogs_lib::infra::infer::infer; use cnblogs_lib::infra::iter::{ExactSizeIteratorExt, IntoIteratorExt}; use cnblogs_lib::infra::option::OptionExt; use cnblogs_lib::infra::result::WrapResult; -use cnblogs_lib::logic::ing::get_ings_and_comments; use colored::Colorize; use std::env; @@ -87,8 +86,23 @@ async fn main() -> Result<()> { } _ if let Some(q) = parser::ing::query(&args) => { + let ing_with_comment_iter = logic::ing::get_ings_and_comments(pat.unwrap().as_str(), &q) + .await + .map(|vec| vec.into_iter().dyn_rev(rev)); + + foe.then(|| panic_if_err(&ing_with_comment_iter)); + display::list_ing(style, time_style, ing_with_comment_iter, true)? + } + + _ if let Some(ids) = parser::ing::delete(&args) => { + let a = pat.as_ref().unwrap().as_str(); + logic::ing::delete_by_ing_id(a, ids).await; + "".to_string() + } - get_ings_and_comments(pat.unwrap().as_str(), &q).await; + _ if let Some(ci) = parser::ing::create_ing(&args) => { + let a = pat.as_ref().unwrap().as_str(); + logic::ing::create_ing_with_arg(a, ci).await; "".to_string() } diff --git a/src/logic/ing.rs b/src/logic/ing.rs index a79f2f7..c17f8b6 100644 --- a/src/logic/ing.rs +++ b/src/logic/ing.rs @@ -1,43 +1,94 @@ //! 闪存相关逻辑 //! +use anyhow::{Ok, Result}; + use crate::{ - apis::ing::{query_by_id, query as iq}, - args::cmd::ing::QueryIng, + api::{ + self, + ing::{get_comment_list::IngCommentEntry, get_list::IngEntry}, + }, + apis::ing::{comment, delete, query as iq, query_by_id, post}, + args::cmd::ing::{QueryIng, CreateIng}, infra::iter::IntoIteratorExt, }; -pub async fn get_ings_and_comments(t: &str, q: &QueryIng) { - println!("{:?}", q); +/// 根据queryset查询 +/// TODO: 提到我和我评论的解析存在问题。 +pub async fn get_ings_and_comments( + t: &str, + q: &QueryIng, +) -> Result)>> { if let Some(ids) = &q.id { let a = ids .iter() .map(|id| async move { query_by_id(t, id).await }) .join_all() .await - ; - - a.iter().for_each(|i| { - match i { - Ok(e) => { - println!("{:?}", e); + .into_iter() + .filter(|x| { + if x.is_err() { + eprintln!("{}", x.as_ref().err().unwrap()) } - Err(e) => { - eprintln!("{:?}", e); - } - } - }) - - } - - let a: Result, anyhow::Error> = iq(t, &q.into()).await; - match a { - Ok(e) => { - println!("{:?}", e); - } - Err(e) => { - eprintln!("{:?}", e); - } + x.is_ok() + }) + .map(|x| x.unwrap()) + .collect::>(); + + return get_ing_comments(t, a).await; + } else { + let a = iq(t, &q.into()) + .await? + .into_iter() + .map(|x| x.into()) + .collect::>(); + return get_ing_comments(t, a).await; } +} +// TODO: 分类细化 +/// 初步提取公共部分 +pub async fn get_ing_comments( + t: &str, + i: Vec, +) -> Result)>> { + let a = i + .into_iter() + .map(|ing| async { + let result = comment::get(t, ing.id.to_string().as_str()).await; + result.map(|comment_vec| (ing, comment_vec)) + }) + .join_all() + .await + .into_iter() + .collect::>>()?; + Ok(a) +} + +/// 通过ID删除 +pub async fn delete_by_ing_id(t: &str, ids: Vec) { + ids.into_iter() + .map(|id| async move { delete(t, id).await }) + .join_all() + .await + .iter() + .for_each(|x| { + if x.is_err() { + eprintln!("{:?}", x.as_ref().err().unwrap().to_string()) + } + }); +} + +/// 创建闪存 +pub async fn create_ing_with_arg(t:&str, c: CreateIng) { + let cc = c.into(); + let e = post(t, &cc) + .await + ; + + if e.is_err() { + eprintln!("{:?}", e.as_ref().err().unwrap().to_string()); + } else { + println!("🙈 ! {:?}", cc.content); + } } diff --git a/src/logic/mod.rs b/src/logic/mod.rs index b9ab01d..14d8057 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -1,6 +1,6 @@ //! cli操作逻辑 -//! +//! //! 此模块暂定封装操作逻辑,比如是闪存的curd,闪存评论的curd。 -//! +//! -pub mod ing; \ No newline at end of file +pub mod ing; From 199e5459870b74d4f20f724df8a0bc28b11e9dae Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 24 Jan 2024 15:29:39 +0800 Subject: [PATCH 07/48] fix(security): patch RUSTSEC-2024-0003 vulnerability. --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd007c8..096293d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -483,9 +483,9 @@ checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "h2" -version = "0.3.20" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -493,7 +493,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.0.0", "slab", "tokio", "tokio-util", From bbd563e7d9d27b2c6cdb9678ae3cd976c9cd0497 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 24 Jan 2024 15:55:07 +0800 Subject: [PATCH 08/48] style: format code with rustfmt. --- src/args/cmd/ing.rs | 6 +++--- src/args/parser/ing.rs | 9 +++------ src/bin/cnb.rs | 9 +++++---- src/logic/ing.rs | 10 ++++------ 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/args/cmd/ing.rs b/src/args/cmd/ing.rs index 96b050e..6459a70 100644 --- a/src/args/cmd/ing.rs +++ b/src/args/cmd/ing.rs @@ -1,5 +1,5 @@ use crate::{ - api::ing::{IngType, IngSendFrom}, + api::ing::{IngSendFrom, IngType}, apis::{ self, ing::{IngContent, QeurySet}, @@ -62,7 +62,7 @@ pub enum Cmd { }, /// 根据条件查询闪存。 - /// + /// Query(QueryIng), /// 创建闪存 @@ -101,7 +101,7 @@ impl From for IngContent { content: cont, is_private: value.private, lucky: value.lucky, - client_type: IngSendFrom::Cli + client_type: IngSendFrom::Cli, } } } diff --git a/src/args/parser/ing.rs b/src/args/parser/ing.rs index e458a47..874c869 100644 --- a/src/args/parser/ing.rs +++ b/src/args/parser/ing.rs @@ -1,5 +1,5 @@ use crate::api::ing::IngType; -use crate::args::cmd::ing::{QueryIng, CreateIng}; +use crate::args::cmd::ing::{CreateIng, QueryIng}; use crate::args::parser::{get_skip, get_take}; use crate::args::{cmd, Args, Cmd}; use crate::infra::option::WrapOption; @@ -103,7 +103,6 @@ pub fn query(args: &Args) -> Option { .wrap_some() } - #[allow(unused)] pub fn create_ing(args: &Args) -> Option { match args { @@ -127,7 +126,7 @@ pub fn create_ing(args: &Args) -> Option { global_opt: _, } => CreateIng { content: content.clone(), - private:private.clone(), + private: private.clone(), lucky: lucky.clone(), tag: tag.clone(), }, @@ -136,15 +135,13 @@ pub fn create_ing(args: &Args) -> Option { .wrap_some() } - #[allow(unused)] pub fn delete(args: &Args) -> Option> { match args { Args { cmd: Some(Cmd::Ing(cmd::ing::Opt { - cmd: - Some(cmd::ing::Cmd::Delete{id}), + cmd: Some(cmd::ing::Cmd::Delete { id }), publish: None, comment: None, })), diff --git a/src/bin/cnb.rs b/src/bin/cnb.rs index 4523e1d..298b60a 100644 --- a/src/bin/cnb.rs +++ b/src/bin/cnb.rs @@ -19,12 +19,12 @@ use cnblogs_lib::api::user::User; use cnblogs_lib::args::cmd::post::{CreateCmd, UpdateCmd}; use cnblogs_lib::args::parser::no_operation; use cnblogs_lib::args::{parser, Args}; -use cnblogs_lib::{display, logic}; use cnblogs_lib::infra::fp::currying::eq; use cnblogs_lib::infra::infer::infer; use cnblogs_lib::infra::iter::{ExactSizeIteratorExt, IntoIteratorExt}; use cnblogs_lib::infra::option::OptionExt; use cnblogs_lib::infra::result::WrapResult; +use cnblogs_lib::{display, logic}; use colored::Colorize; use std::env; @@ -86,9 +86,10 @@ async fn main() -> Result<()> { } _ if let Some(q) = parser::ing::query(&args) => { - let ing_with_comment_iter = logic::ing::get_ings_and_comments(pat.unwrap().as_str(), &q) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); + let ing_with_comment_iter = + logic::ing::get_ings_and_comments(pat.unwrap().as_str(), &q) + .await + .map(|vec| vec.into_iter().dyn_rev(rev)); foe.then(|| panic_if_err(&ing_with_comment_iter)); display::list_ing(style, time_style, ing_with_comment_iter, true)? diff --git a/src/logic/ing.rs b/src/logic/ing.rs index c17f8b6..76fd3f9 100644 --- a/src/logic/ing.rs +++ b/src/logic/ing.rs @@ -8,8 +8,8 @@ use crate::{ self, ing::{get_comment_list::IngCommentEntry, get_list::IngEntry}, }, - apis::ing::{comment, delete, query as iq, query_by_id, post}, - args::cmd::ing::{QueryIng, CreateIng}, + apis::ing::{comment, delete, post, query as iq, query_by_id}, + args::cmd::ing::{CreateIng, QueryIng}, infra::iter::IntoIteratorExt, }; @@ -80,11 +80,9 @@ pub async fn delete_by_ing_id(t: &str, ids: Vec) { } /// 创建闪存 -pub async fn create_ing_with_arg(t:&str, c: CreateIng) { +pub async fn create_ing_with_arg(t: &str, c: CreateIng) { let cc = c.into(); - let e = post(t, &cc) - .await - ; + let e = post(t, &cc).await; if e.is_err() { eprintln!("{:?}", e.as_ref().err().unwrap().to_string()); From 00eecef4b8c95ab8e9be4216ea9142d237984f25 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 24 Jan 2024 16:13:38 +0800 Subject: [PATCH 09/48] style: format code with rustfmt. --- src/apis/ing/mod.rs | 28 ++++++++++++++-------------- src/apis/token/mod.rs | 6 +++--- src/args/parser/ing.rs | 8 ++++---- src/logic/ing.rs | 5 ++--- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/apis/ing/mod.rs b/src/apis/ing/mod.rs index 8364481..ee9243f 100644 --- a/src/apis/ing/mod.rs +++ b/src/apis/ing/mod.rs @@ -37,7 +37,7 @@ pub struct IngContent { impl Default for IngContent { fn default() -> Self { - IngContent { + Self { content: "".to_string(), is_private: true, lucky: false, @@ -48,7 +48,7 @@ impl Default for IngContent { impl Default for IngSendFrom { fn default() -> Self { - return IngSendFrom::Cli; + Self::Cli } } @@ -67,12 +67,12 @@ pub struct QeurySet { impl Default for QeurySet { fn default() -> Self { - return Self { + Self { r#type: QueryIngType::default(), page_index: 1, page_size: 10, tag: "".to_string(), - }; + } } } @@ -99,7 +99,7 @@ pub enum QueryIngType { impl Default for QueryIngType { fn default() -> Self { - return Self::All; + Self::All } } @@ -119,16 +119,16 @@ impl From for QueryIngType { } impl QueryIngType { - fn as_u8(&self) -> u8 { + const fn as_u8(&self) -> u8 { match self { - QueryIngType::Following => 1, - QueryIngType::My => 4, - QueryIngType::All => 5, - QueryIngType::RecentComment => 6, - QueryIngType::MyComment => 7, - QueryIngType::Tag => 10, - QueryIngType::Mention => 14, - QueryIngType::Comment => 13, + Self::Following => 1, + Self::My => 4, + Self::All => 5, + Self::RecentComment => 6, + Self::MyComment => 7, + Self::Tag => 10, + Self::Mention => 14, + Self::Comment => 13, } } } diff --git a/src/apis/token/mod.rs b/src/apis/token/mod.rs index d41ff78..be6b877 100644 --- a/src/apis/token/mod.rs +++ b/src/apis/token/mod.rs @@ -69,7 +69,7 @@ pub struct OauthTokenReq { impl OauthTokenReq { pub fn new(client_id: String, client_secret: String, code: String) -> Self { - OauthTokenReq { + Self { cc: ClientCredentialsReq { client_id, client_secret, @@ -107,14 +107,14 @@ pub struct RefreshTokenReq { impl RefreshTokenReq { pub fn new(client_id: String, client_secret: String, refresh_token: String) -> Self { - return RefreshTokenReq { + Self { cc: ClientCredentialsReq { client_id, client_secret, grant_type: "refresh_token".to_string(), }, refresh_token, - }; + } } } diff --git a/src/args/parser/ing.rs b/src/args/parser/ing.rs index 874c869..dc5991c 100644 --- a/src/args/parser/ing.rs +++ b/src/args/parser/ing.rs @@ -93,8 +93,8 @@ pub fn query(args: &Args) -> Option { global_opt: _, } => QueryIng { r#type: r#type.clone(), - page_index: page_index.clone(), - page_size: page_size.clone(), + page_index: *page_index, + page_size: *page_size, tag: tag.clone(), id: id.clone(), }, @@ -126,8 +126,8 @@ pub fn create_ing(args: &Args) -> Option { global_opt: _, } => CreateIng { content: content.clone(), - private: private.clone(), - lucky: lucky.clone(), + private: *private, + lucky: *lucky, tag: tag.clone(), }, _ => return None, diff --git a/src/logic/ing.rs b/src/logic/ing.rs index 76fd3f9..53862ed 100644 --- a/src/logic/ing.rs +++ b/src/logic/ing.rs @@ -35,14 +35,13 @@ pub async fn get_ings_and_comments( .map(|x| x.unwrap()) .collect::>(); - return get_ing_comments(t, a).await; + get_ing_comments(t, a).await } else { let a = iq(t, &q.into()) .await? .into_iter() - .map(|x| x.into()) .collect::>(); - return get_ing_comments(t, a).await; + get_ing_comments(t, a).await } } From a9b407d7ee00de57fa556d76f5d2d099b2ffe45e Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Thu, 25 Jan 2024 00:03:11 +0800 Subject: [PATCH 10/48] feat: change ing default visibility from private to public. --- src/args/cmd/ing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/args/cmd/ing.rs b/src/args/cmd/ing.rs index 6459a70..5305c23 100644 --- a/src/args/cmd/ing.rs +++ b/src/args/cmd/ing.rs @@ -78,7 +78,7 @@ pub struct CreateIng { pub content: String, /// 是否私有,默认是全站 - #[arg(short, long, default_value_t = true)] + #[arg(short, long, default_value_t = false)] pub private: bool, /// 是否发布为幸运 From 8e1160ae9e8e93ef35d0e848973d8522e1c589d3 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 13 Jan 2026 16:44:17 +0800 Subject: [PATCH 11/48] chore: enhance .gitignore coverage - Ignore .DS_Store file - Exclude .vscode and .zed directories --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index ee44a96..f288b1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ .idea +.vscode +.zed + +**/.DS_Store + target From 41690859b417d10b3c759abf429ed4db5416825b Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 13 Jan 2026 16:46:37 +0800 Subject: [PATCH 12/48] build: Update rust toolchain version - Update channel version --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5b6bd4a..f081769 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] profile = "minimal" -channel = "nightly-2024-01-01" +channel = "nightly-2026-01-10" components = [ "rustfmt", "clippy" ] From d90c4973411822b3812dc7fddc3a8d32dbd08934 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 13 Jan 2026 16:47:16 +0800 Subject: [PATCH 13/48] style: configure rustfmt - Add rust-fmt.toml with custom formatting rules. --- rust-fmt.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust-fmt.toml b/rust-fmt.toml index 9829022..24e0279 100644 --- a/rust-fmt.toml +++ b/rust-fmt.toml @@ -1,5 +1,5 @@ -max_width = 79 # 设置最大行宽为 100 个字符 +max_width = 79 # 设置最大行宽为 80 个字符 tab_spaces = 4 # 设置缩进宽度为 4 个空格 -edition = "2021" # 设置 Rust 版本(根据实际项目版本进行调整) +edition = "2024" # 设置 Rust 版本(根据实际项目版本进行调整) use_small_heuristics = "Max" # 设置换行策略 newline_style = "Auto" # 设置换行符风格,根据平台自动选择 From 464443e73275fd2a732af60855ad930040198ba9 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 13 Jan 2026 17:00:01 +0800 Subject: [PATCH 14/48] fix: correct type mismatch between fmt::Error and anyhow::Error ## Problem std::fmt::Error and anyhow::Error have similar names, but are actually distinct types in 2024 edition ## Solution - convert std::fmt::Error to anyhow::Error by map_error function --- src/display/colorful/fav.rs | 11 ++++++----- src/display/colorful/ing.rs | 28 +++++++++++++++++----------- src/display/colorful/news.rs | 11 ++++++----- src/display/colorful/post.rs | 34 ++++++++++++++++++++-------------- src/display/normal/fav.rs | 11 ++++++----- src/display/normal/ing.rs | 26 +++++++++++++++----------- src/display/normal/news.rs | 10 +++++----- src/display/normal/post.rs | 32 ++++++++++++++++++-------------- 8 files changed, 93 insertions(+), 70 deletions(-) diff --git a/src/display/colorful/fav.rs b/src/display/colorful/fav.rs index 37803d1..532becc 100644 --- a/src/display/colorful/fav.rs +++ b/src/display/colorful/fav.rs @@ -25,8 +25,9 @@ pub fn list_fav( { let buf = &mut buf; let create_time = display_cnb_time(&fav.create_time, time_style); - writeln!(buf, "{} {}", create_time.dimmed(), fav.url.dimmed())?; - writeln!(buf, " {}", fav.title)?; + writeln!(buf, "{} {}", create_time.dimmed(), fav.url.dimmed()) + .map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf, " {}", fav.title).map_err(|e| anyhow::anyhow!("{}", e))?; let summary = { fav.summary.width_split(get_term_width() - 4).map_or_else( @@ -40,13 +41,13 @@ pub fn list_fav( ) }; if summary.is_empty().not() { - writeln!(buf, "{}", summary.dimmed())?; + writeln!(buf, "{}", summary.dimmed()).map_err(|e| anyhow::anyhow!("{}", e))?; } } buf }) - .try_fold(String::new(), |mut acc, buf: Result| try { - writeln!(&mut acc, "{}", buf?)?; + .try_fold(String::new(), |mut acc: String, buf: Result| try { + writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; acc }) } diff --git a/src/display/colorful/ing.rs b/src/display/colorful/ing.rs index dc63d96..190debf 100644 --- a/src/display/colorful/ing.rs +++ b/src/display/colorful/ing.rs @@ -30,7 +30,7 @@ pub fn list_ing( { let buf = &mut buf; let create_time = display_cnb_time(&ing.create_time, time_style); - write!(buf, "{}", create_time.dimmed())?; + write!(buf, "{}", create_time.dimmed()).map_err(|e| anyhow::anyhow!("{}", e))?; let send_from_mark = match ing.send_from { IngSendFrom::Cli => Some("CLI"), @@ -40,13 +40,14 @@ pub fn list_ing( _ => None, }; if let Some(mark) = send_from_mark { - write!(buf, " {}", mark.dimmed())?; + write!(buf, " {}", mark.dimmed()).map_err(|e| anyhow::anyhow!("{}", e))?; } if ing.is_lucky { let star_text = ing_star_tag_to_text(&ing.icons); - write!(buf, " {}⭐", star_text.yellow())?; + write!(buf, " {}⭐", star_text.yellow()).map_err(|e| anyhow::anyhow!("{}", e))?; } - writeln!(buf, " {} {}", "#".dimmed(), ing.id.to_string().dimmed())?; + writeln!(buf, " {} {}", "#".dimmed(), ing.id.to_string().dimmed()) + .map_err(|e| anyhow::anyhow!("{}", e))?; let content = if align { let user_name_width = ing.user_name.width_cjk(); let left_width = get_term_width().saturating_sub(user_name_width + 3); @@ -71,7 +72,8 @@ pub fn list_ing( } else { fmt_content(&ing.content) }; - writeln!(buf, " {} {}", ing.user_name.cyan(), content)?; + writeln!(buf, " {} {}", ing.user_name.cyan(), content) + .map_err(|e| anyhow::anyhow!("{}", e))?; let len = comment_list.len(); if len != 0 { @@ -82,27 +84,31 @@ pub fn list_ing( { let buf = &mut buf; if i != max_i { - write!(buf, " │ {}", entry.user_name.blue())?; + write!(buf, " │ {}", entry.user_name.blue()) + .map_err(|e| anyhow::anyhow!("{}", e))?; } else { - write!(buf, " └ {}", entry.user_name.blue())?; + write!(buf, " └ {}", entry.user_name.blue()) + .map_err(|e| anyhow::anyhow!("{}", e))?; } let at_user = get_ing_at_user_tag_text(&entry.content); if at_user.is_empty().not() { - write!(buf, " {}{}", "@".bright_black(), at_user.bright_black())?; + write!(buf, " {}{}", "@".bright_black(), at_user.bright_black()) + .map_err(|e| anyhow::anyhow!("{}", e))?; } let content = { let content = rm_ing_at_user_tag(&entry.content); fmt_content(&content) }; - writeln!(buf, " {}", content.dimmed())?; + writeln!(buf, " {}", content.dimmed()) + .map_err(|e| anyhow::anyhow!("{}", e))?; } buf }, ); - write!(buf, "{}", comment_list_buf?)?; + write!(buf, "{}", comment_list_buf?).map_err(|e| anyhow::anyhow!("{}", e))?; } - writeln!(buf)?; + writeln!(buf).map_err(|e| anyhow::anyhow!("{}", e))?; }; buf }) diff --git a/src/display/colorful/news.rs b/src/display/colorful/news.rs index 93d3a6e..b5e4ffc 100644 --- a/src/display/colorful/news.rs +++ b/src/display/colorful/news.rs @@ -25,8 +25,9 @@ pub fn list_news( let buf = &mut buf; let create_time = display_cnb_time(&news.create_time, time_style); let url = format!("https://news.cnblogs.com/n/{}", news.id); - writeln!(buf, "{} {}", create_time.dimmed(), url.dimmed())?; - writeln!(buf, " {}", news.title)?; + writeln!(buf, "{} {}", create_time.dimmed(), url.dimmed()) + .map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf, " {}", news.title).map_err(|e| anyhow::anyhow!("{}", e))?; let summary = { let summary = format!("{}...", news.summary); @@ -40,12 +41,12 @@ pub fn list_news( }, ) }; - writeln!(buf, "{}", summary.dimmed())?; + writeln!(buf, "{}", summary.dimmed()).map_err(|e| anyhow::anyhow!("{}", e))?; } buf }) - .try_fold(String::new(), |mut acc, buf: Result| try { - writeln!(&mut acc, "{}", buf?)?; + .try_fold(String::new(), |mut acc: String, buf: Result| try { + writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; acc }) } diff --git a/src/display/colorful/post.rs b/src/display/colorful/post.rs index 095f462..206705b 100644 --- a/src/display/colorful/post.rs +++ b/src/display/colorful/post.rs @@ -22,17 +22,19 @@ pub fn list_post( |mut buf, entry| try { { let buf = &mut buf; - write!(buf, "{} {}", "#".dimmed(), entry.id.to_string().dimmed())?; + write!(buf, "{} {}", "#".dimmed(), entry.id.to_string().dimmed()) + .map_err(|e| anyhow::anyhow!("{}", e))?; if entry.is_published { - write!(buf, " {}", "Pub".green())?; + write!(buf, " {}", "Pub".green()).map_err(|e| anyhow::anyhow!("{}", e))?; } else { - write!(buf, " {}", "Dft".yellow())?; + write!(buf, " {}", "Dft".yellow()).map_err(|e| anyhow::anyhow!("{}", e))?; } if entry.is_pinned { - write!(buf, " {}", "Pin".magenta())?; + write!(buf, " {}", "Pin".magenta()).map_err(|e| anyhow::anyhow!("{}", e))?; } - write!(buf, " {}", entry.title.cyan().bold())?; - writeln!(buf)?; + write!(buf, " {}", entry.title.cyan().bold()) + .map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf).map_err(|e| anyhow::anyhow!("{}", e))?; } buf }, @@ -114,8 +116,10 @@ pub fn show_post_comment( let buf = &mut buf; let create_time = display_cnb_time(&comment.create_time, time_style); let floor_text = format!("{}F", comment.floor); - writeln!(buf, "{} {}", create_time.dimmed(), floor_text.dimmed())?; - writeln!(buf, " {} {}", comment.user_name.cyan(), comment.content)?; + writeln!(buf, "{} {}", create_time.dimmed(), floor_text.dimmed()) + .map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf, " {} {}", comment.user_name.cyan(), comment.content) + .map_err(|e| anyhow::anyhow!("{}", e))?; } buf }) @@ -132,7 +136,7 @@ pub fn search_self_post( id_iter.try_fold( format!("{}/{}\n", id_iter.len(), total_count), |mut buf, id| try { - writeln!(&mut buf, "# {}", id)?; + writeln!(&mut buf, "# {}", id).map_err(|e| anyhow::anyhow!("{}", e))?; buf }, ) @@ -153,18 +157,20 @@ pub fn search_site_post( { let buf = &mut buf; let create_time = display_cnb_time(&entry.create_time, time_style); - writeln!(buf, "{} {}", create_time.dimmed(), entry.url.dimmed())?; - writeln!(buf, " {}", entry.title)?; + writeln!(buf, "{} {}", create_time.dimmed(), entry.url.dimmed()) + .map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf, " {}", entry.title).map_err(|e| anyhow::anyhow!("{}", e))?; let view_vote_comment_count = format!( "View {} Vote {} Comment {}", entry.view_count, entry.vote_count, entry.comment_count ); - writeln!(buf, " {}", view_vote_comment_count.dimmed())?; + writeln!(buf, " {}", view_vote_comment_count.dimmed()) + .map_err(|e| anyhow::anyhow!("{}", e))?; } buf }) - .try_fold(String::new(), |mut acc, buf: Result| try { - writeln!(&mut acc, "{}", buf?)?; + .try_fold(String::new(), |mut acc: String, buf: Result| try { + writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; acc }) } diff --git a/src/display/normal/fav.rs b/src/display/normal/fav.rs index 6affe0c..01b46b7 100644 --- a/src/display/normal/fav.rs +++ b/src/display/normal/fav.rs @@ -24,8 +24,9 @@ pub fn list_fav( { let buf = &mut buf; let create_time = display_cnb_time(&fav.create_time, time_style); - writeln!(buf, "{} {}", create_time, fav.url)?; - writeln!(buf, " {}", fav.title)?; + writeln!(buf, "{} {}", create_time, fav.url) + .map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf, " {}", fav.title).map_err(|e| anyhow::anyhow!("{}", e))?; let summary = { fav.summary.width_split(get_term_width() - 4).map_or_else( @@ -39,13 +40,13 @@ pub fn list_fav( ) }; if summary.is_empty().not() { - writeln!(buf, "{}", summary)?; + writeln!(buf, "{}", summary).map_err(|e| anyhow::anyhow!("{}", e))?; } } buf }) - .try_fold(String::new(), |mut acc, buf: Result| try { - writeln!(&mut acc, "{}", buf?)?; + .try_fold(String::new(), |mut acc: String, buf: Result| try { + writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; acc }) } diff --git a/src/display/normal/ing.rs b/src/display/normal/ing.rs index 62c75d0..42dc0a9 100644 --- a/src/display/normal/ing.rs +++ b/src/display/normal/ing.rs @@ -29,7 +29,7 @@ pub fn list_ing( { let buf = &mut buf; let create_time = display_cnb_time(&ing.create_time, time_style); - write!(buf, "{}", create_time)?; + write!(buf, "{}", create_time).map_err(|e| anyhow::anyhow!("{}", e))?; let send_from_mark = match ing.send_from { IngSendFrom::Cli => Some("CLI"), @@ -39,13 +39,13 @@ pub fn list_ing( _ => None, }; if let Some(mark) = send_from_mark { - write!(buf, " {}", mark)?; + write!(buf, " {}", mark).map_err(|e| anyhow::anyhow!("{}", e))?; } if ing.is_lucky { let star_text = ing_star_tag_to_text(&ing.icons); - write!(buf, " {}★", star_text)?; + write!(buf, " {}★", star_text).map_err(|e| anyhow::anyhow!("{}", e))?; } - writeln!(buf, " # {}", ing.id)?; + writeln!(buf, " # {}", ing.id).map_err(|e| anyhow::anyhow!("{}", e))?; let content = if align { let user_name_width = ing.user_name.width_cjk(); let left_width = get_term_width().saturating_sub(user_name_width + 3); @@ -70,7 +70,8 @@ pub fn list_ing( } else { fmt_content(&ing.content) }; - writeln!(buf, " {}: {}", ing.user_name, content)?; + writeln!(buf, " {}: {}", ing.user_name, content) + .map_err(|e| anyhow::anyhow!("{}", e))?; let len = comment_list.len(); if len != 0 { @@ -81,27 +82,30 @@ pub fn list_ing( { let buf = &mut buf; if i != max_i { - write!(buf, " │ {}", entry.user_name)?; + write!(buf, " │ {}", entry.user_name) + .map_err(|e| anyhow::anyhow!("{}", e))?; } else { - write!(buf, " └ {}", entry.user_name)?; + write!(buf, " └ {}", entry.user_name) + .map_err(|e| anyhow::anyhow!("{}", e))?; } let at_user = get_ing_at_user_tag_text(&entry.content); if at_user.is_empty().not() { - write!(buf, " @{}", at_user)?; + write!(buf, " @{}", at_user) + .map_err(|e| anyhow::anyhow!("{}", e))?; } let content = { let content = rm_ing_at_user_tag(&entry.content); fmt_content(&content) }; - writeln!(buf, ": {}", content)?; + writeln!(buf, ": {}", content).map_err(|e| anyhow::anyhow!("{}", e))?; } buf }, ); - write!(buf, "{}", comment_list_buf?)?; + write!(buf, "{}", comment_list_buf?).map_err(|e| anyhow::anyhow!("{}", e))?; } - writeln!(buf)?; + writeln!(buf).map_err(|e| anyhow::anyhow!("{}", e))?; }; buf }) diff --git a/src/display/normal/news.rs b/src/display/normal/news.rs index 1cb47b1..22b946d 100644 --- a/src/display/normal/news.rs +++ b/src/display/normal/news.rs @@ -24,8 +24,8 @@ pub fn list_news( let buf = &mut buf; let create_time = display_cnb_time(&news.create_time, time_style); let url = format!("https://news.cnblogs.com/n/{}", news.id); - writeln!(buf, "{} {}", create_time, url)?; - writeln!(buf, " {}", news.title)?; + writeln!(buf, "{} {}", create_time, url).map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf, " {}", news.title).map_err(|e| anyhow::anyhow!("{}", e))?; let summary = { let summary = format!("{}...", news.summary); @@ -39,12 +39,12 @@ pub fn list_news( }, ) }; - writeln!(buf, "{}", summary)?; + writeln!(buf, "{}", summary).map_err(|e| anyhow::anyhow!("{}", e))?; } buf }) - .try_fold(String::new(), |mut acc, buf: Result| try { - writeln!(&mut acc, "{}", buf?)?; + .try_fold(String::new(), |mut acc: String, buf: Result| try { + writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; acc }) } diff --git a/src/display/normal/post.rs b/src/display/normal/post.rs index 3a9bd9a..f687cbb 100644 --- a/src/display/normal/post.rs +++ b/src/display/normal/post.rs @@ -21,17 +21,17 @@ pub fn list_post( |mut buf, entry| try { { let buf = &mut buf; - write!(buf, "# {}", entry.id)?; + write!(buf, "# {}", entry.id).map_err(|e| anyhow::anyhow!("{}", e))?; if entry.is_published { - write!(buf, " Pub")?; + write!(buf, " Pub").map_err(|e| anyhow::anyhow!("{}", e))?; } else { - write!(buf, " Dft")?; + write!(buf, " Dft").map_err(|e| anyhow::anyhow!("{}", e))?; } if entry.is_pinned { - write!(buf, " Pin")?; + write!(buf, " Pin").map_err(|e| anyhow::anyhow!("{}", e))?; } - write!(buf, " {}", entry.title)?; - writeln!(buf)?; + write!(buf, " {}", entry.title).map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf).map_err(|e| anyhow::anyhow!("{}", e))?; } buf }, @@ -112,8 +112,10 @@ pub fn show_post_comment( { let buf = &mut buf; let create_time = display_cnb_time(&comment.create_time, time_style); - writeln!(buf, "{} {}F", create_time, comment.floor)?; - writeln!(buf, " {} {}", comment.user_name, comment.content)?; + writeln!(buf, "{} {}F", create_time, comment.floor) + .map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf, " {} {}", comment.user_name, comment.content) + .map_err(|e| anyhow::anyhow!("{}", e))?; } buf }) @@ -130,7 +132,7 @@ pub fn search_self_post( id_iter.try_fold( format!("{}/{}\n", id_iter.len(), total_count), |mut buf, id| try { - writeln!(&mut buf, "# {}", id)?; + writeln!(&mut buf, "# {}", id).map_err(|e| anyhow::anyhow!("{}", e))?; buf }, ) @@ -151,18 +153,20 @@ pub fn search_site_post( { let buf = &mut buf; let create_time = display_cnb_time(&entry.create_time, time_style); - writeln!(buf, "{} {}", create_time, entry.url)?; - writeln!(buf, " {}", entry.title)?; + writeln!(buf, "{} {}", create_time, entry.url) + .map_err(|e| anyhow::anyhow!("{}", e))?; + writeln!(buf, " {}", entry.title).map_err(|e| anyhow::anyhow!("{}", e))?; let view_vote_comment_count = format!( "View {} Vote {} Comment {}", entry.view_count, entry.vote_count, entry.comment_count ); - writeln!(buf, " {}", view_vote_comment_count)?; + writeln!(buf, " {}", view_vote_comment_count) + .map_err(|e| anyhow::anyhow!("{}", e))?; } buf }) - .try_fold(String::new(), |mut acc, buf: Result| try { - writeln!(&mut acc, "{}", buf?)?; + .try_fold(String::new(), |mut acc: String, buf: Result| try { + writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; acc }) } From 11e03a876d9414ba42d3ce12b0684e0f01ed9051 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 13 Jan 2026 17:06:50 +0800 Subject: [PATCH 15/48] refactor: clean up code quality warnings - Remove unnecessary stable feature attribute from lib.rs and cnb.rs - Add explicit return value ignoring with let _ = pattern in infra/str.rs --- src/bin/cnb.rs | 1 - src/infra/str.rs | 2 +- src/lib.rs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/bin/cnb.rs b/src/bin/cnb.rs index 298b60a..31bacba 100644 --- a/src/bin/cnb.rs +++ b/src/bin/cnb.rs @@ -1,6 +1,5 @@ #![feature(try_blocks)] #![feature(if_let_guard)] -#![feature(let_chains)] #![feature(iterator_try_collect)] #![feature(iterator_try_reduce)] #![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] diff --git a/src/infra/str.rs b/src/infra/str.rs index 8f2e0b2..046120b 100644 --- a/src/infra/str.rs +++ b/src/infra/str.rs @@ -10,7 +10,7 @@ impl StrExt for str { fn width_split_head(&self, head_width: usize) -> (&str, &str) { let mut left_take = head_width; let mut take_bytes = 0; - self.chars().try_for_each(|c| { + let _ = self.chars().try_for_each(|c| { let current_width = c.width_cjk().unwrap_or(0); if left_take > 0 { if left_take >= current_width { diff --git a/src/lib.rs b/src/lib.rs index 4c7b191..2fa227b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ #![feature(try_blocks)] #![feature(if_let_guard)] -#![feature(let_chains)] #![feature(iterator_try_collect)] #![feature(iterator_try_reduce)] #![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] From 1bb3f59c706bab4d9c2446a6abd7e00494d0f33f Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 13 Jan 2026 17:14:12 +0800 Subject: [PATCH 16/48] refactor: refact impl default can be derived - Remove manual implementation code. - use derive instead of manual implementation. --- src/api/ing/mod.rs | 3 ++- src/apis/ing/mod.rs | 15 ++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/api/ing/mod.rs b/src/api/ing/mod.rs index 56343b3..a9a45ac 100644 --- a/src/api/ing/mod.rs +++ b/src/api/ing/mod.rs @@ -31,7 +31,7 @@ pub enum IngType { //Mention = 14, } -#[derive(Clone, Debug, Serialize_repr, Deserialize_repr)] +#[derive(Clone, Debug, Default, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum IngSendFrom { None = 0, @@ -42,6 +42,7 @@ pub enum IngSendFrom { CellPhone = 6, Web = 8, VsCode = 9, + #[default] Cli = 13, } diff --git a/src/apis/ing/mod.rs b/src/apis/ing/mod.rs index ee9243f..4300351 100644 --- a/src/apis/ing/mod.rs +++ b/src/apis/ing/mod.rs @@ -46,12 +46,6 @@ impl Default for IngContent { } } -impl Default for IngSendFrom { - fn default() -> Self { - Self::Cli - } -} - /// 查询条件,用于根据类别查询 #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -85,10 +79,11 @@ impl Default for QeurySet { /// Tag = 10, tag 必填 /// Comment = 13 回复我 /// Mention = 14, @我 -#[derive(Debug, Clone, ValueEnum, Parser)] +#[derive(Debug, Default, Clone, ValueEnum, Parser)] pub enum QueryIngType { Following = 1, My = 4, + #[default] All = 5, RecentComment = 6, MyComment = 7, @@ -97,12 +92,6 @@ pub enum QueryIngType { Mention = 14, } -impl Default for QueryIngType { - fn default() -> Self { - Self::All - } -} - impl From for QueryIngType { fn from(value: u8) -> Self { match value { From 8520329b4d04a205330596eb5406c40d3b4d0740 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 13 Jan 2026 17:20:26 +0800 Subject: [PATCH 17/48] chore: update all dependencies package version --- Cargo.lock | 1478 ++++++++++++++++++++++++++++++---------------------- Cargo.toml | 42 +- 2 files changed, 889 insertions(+), 631 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 096293d..a0dca83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,37 +1,16 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +version = 4 [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -43,84 +22,83 @@ dependencies = [ [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "backtrace" -version = "0.3.68" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.21.4" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64url" @@ -128,68 +106,62 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33de68096bac8e252e45589f42afd364c1dd28fbb3466ed726a941d5b9727d2c" dependencies = [ - "base64", + "base64 0.21.7", ] [[package]] name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytes" -version = "1.4.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.0.82" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ - "libc", + "find-msvc-tools", + "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.30" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-link", ] [[package]] name = "clap" -version = "4.4.3" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -197,9 +169,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.2" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -210,9 +182,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -222,16 +194,16 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "cnblogs_lib" version = "0.0.0-dev" dependencies = [ "anyhow", - "base64", + "base64 0.22.1", "base64url", "chrono", "clap", @@ -257,32 +229,30 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "2.0.4" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "is-terminal", - "lazy_static", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "darling" -version = "0.20.3" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ "darling_core", "darling_macro", @@ -290,9 +260,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", @@ -304,9 +274,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", @@ -315,48 +285,52 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.7" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ - "serde", + "powerfmt", + "serde_core", ] [[package]] -name = "encoding_rs" -version = "0.8.32" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "cfg-if", + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.2" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "find-msvc-tools" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "fnv" @@ -366,18 +340,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -390,9 +364,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -400,15 +374,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -417,15 +391,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -434,21 +408,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -464,40 +438,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" - -[[package]] -name = "h2" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 2.0.0", - "slab", - "tokio", - "tokio-util", - "tracing", + "r-efi", + "wasip2", ] [[package]] @@ -508,21 +456,15 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -532,97 +474,110 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" -version = "0.5.5" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "http" -version = "0.2.9" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "http-body" -version = "0.4.5" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", - "pin-project-lite", ] [[package]] -name = "httparse" -version = "1.8.0" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] [[package]] -name = "httpdate" -version = "1.0.3" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "0.14.27" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", - "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "pin-utils", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] -name = "hyper-rustls" -version = "0.24.1" +name = "hyper-util" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", "futures-util", "http", + "http-body", "hyper", - "rustls", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", "tokio", - "tokio-rustls", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -634,6 +589,87 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -642,12 +678,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -663,103 +710,98 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.16.1", "serde", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", + "serde_core", ] [[package]] name = "ipnet" -version = "2.8.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] -name = "is-terminal" -version = "0.4.9" +name = "iri-string" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ - "hermit-abi", - "rustix 0.38.8", - "windows-sys", + "memchr", + "serde", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] -name = "linux-raw-sys" -version = "0.4.5" +name = "litemap" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.20" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.6.3" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -767,65 +809,49 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - [[package]] name = "mio" -version = "0.8.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] -name = "num-traits" -version = "0.2.16" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" -dependencies = [ - "autocfg", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] -name = "num_cpus" -version = "1.16.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "hermit-abi", - "libc", + "autocfg", ] [[package]] -name = "object" -version = "0.31.1" +name = "once_cell" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" -dependencies = [ - "memchr", -] +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "once_cell" -version = "1.18.0" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -833,28 +859,28 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -862,46 +888,69 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.32" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -909,27 +958,47 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ - "bitflags 1.3.2", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -939,9 +1008,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -950,134 +1019,91 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" dependencies = [ - "base64", + "base64 0.22.1", "bytes", - "encoding_rs", "futures-core", - "futures-util", - "h2", "http", "http-body", + "http-body-util", "hyper", - "hyper-rustls", - "ipnet", + "hyper-util", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "tokio", - "tokio-rustls", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", ] -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - [[package]] name = "rustix" -version = "0.37.23" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno", - "io-lifetimes", "libc", - "linux-raw-sys 0.3.8", - "windows-sys", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] -name = "rustix" -version = "0.38.8" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" -dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys 0.4.5", - "windows-sys", -] +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "rustls" -version = "0.21.7" +name = "ryu" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] -name = "rustls-pemfile" -version = "1.0.3" +name = "schemars" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ - "base64", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] [[package]] -name = "rustls-webpki" -version = "0.101.4" +name = "schemars" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ - "ring", - "untrusted", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] -[[package]] -name = "ryu" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" - [[package]] name = "scopeguard" version = "1.2.0" @@ -1085,29 +1111,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sct" -version = "0.7.0" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ - "ring", - "untrusted", + "serde_core", + "serde_derive", ] [[package]] -name = "serde" -version = "1.0.188" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1116,20 +1142,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] name = "serde_qs" -version = "0.12.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", @@ -1138,9 +1166,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.16" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", @@ -1161,16 +1189,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.3.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.0.0", - "serde", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.0", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -1178,9 +1208,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.3.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling", "proc-macro2", @@ -1188,97 +1218,111 @@ dependencies = [ "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] [[package]] name = "slab" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.11.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.60.2", ] [[package]] -name = "spin" -version = "0.5.2" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.28" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "terminal_size" -version = "0.2.6" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 0.37.23", - "windows-sys", + "rustix", + "windows-sys 0.60.2", ] [[package]] name = "thiserror" -version = "1.0.45" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dedd246497092a89beedfe2c9f176d44c1b672ea6090edc20544ade01fbb7ea0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.45" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d7b1fadccbbc7e19ea64708629f9d8dccd007c260d66485f20a6d41bc1cf4b3" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -1287,71 +1331,67 @@ dependencies = [ [[package]] name = "time" -version = "0.3.25" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", - "serde", + "num-conv", + "powerfmt", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ + "num-conv", "time-core", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.32.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -1359,116 +1399,116 @@ dependencies = [ ] [[package]] -name = "tokio-rustls" -version = "0.24.1" +name = "tower" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ - "rustls", + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", "tokio", + "tower-layer", + "tower-service", ] [[package]] -name = "tokio-util" -version = "0.7.8" +name = "tower-http" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ + "bitflags", "bytes", - "futures-core", - "futures-sink", + "futures-util", + "http", + "http-body", + "iri-string", "pin-project-lite", - "tokio", - "tracing", + "tower", + "tower-layer", + "tower-service", ] +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] [[package]] name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "unicode-bidi" -version = "0.3.13" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-blocks" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84398c527c802fbf222e5145f220382d60f1878e0e6cb4d22a3080949a8ddcd" +checksum = "6b12e05d9e06373163a9bb6bb8c263c261b396643a99445fe6b9811fd376581b" [[package]] name = "unicode-ident" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "untrusted" -version = "0.7.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "url" -version = "2.4.0" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "want" @@ -1481,52 +1521,50 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasm-bindgen" -version = "0.2.87" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.87" +name = "wasm-bindgen" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ - "bumpalo", - "log", + "cfg-if", "once_cell", - "proc-macro2", - "quote", - "syn", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1534,145 +1572,256 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki-roots" -version = "0.25.2" +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-link", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] [[package]] -name = "windows" -version = "0.48.0" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows-targets", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" -version = "0.48.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.2" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.48.2" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.48.2" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.48.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.48.2" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.2" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.48.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winreg" -version = "0.50.0" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys", -] +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "words-count" @@ -1682,3 +1831,112 @@ checksum = "d28653ddaede5475c44a03e4014ae19f35aa9b231c423228b28963cb873e4869" dependencies = [ "unicode-blocks", ] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" diff --git a/Cargo.toml b/Cargo.toml index 02684e4..f57731d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,35 +20,35 @@ strip = true codegen-units = 1 [dependencies] -anyhow = "1.0.75" -lazy_static = "1.4.0" -base64 = "0.21.4" +anyhow = "1.0.100" +lazy_static = "1.5.0" +base64 = "0.22.1" base64url = "0.1.0" -getrandom = { version = "0.2.10", features = ["js"] } -rand = { version = "0.8.5" } -regex = "1.9.5" +getrandom = "0.3.4" +rand = { version = "0.9.2" } +regex = "1.12.2" words-count = "0.1.6" -unicode-width = "0.1.10" +unicode-width = "0.2.2" -serde = { version = "1.0.188", features = ["derive"] } -serde_qs = "0.12.0" -serde_json = "1.0.107" -serde_with = "3.3.0" -serde_repr = "0.1.16" +serde = { version = "1.0.228", features = ["derive"] } +serde_qs = "0.15.0" +serde_json = "1.0.149" +serde_with = "3.16.1" +serde_repr = "0.1.20" -home = "0.5.5" -chrono = "0.4.30" +home = "0.5.12" +chrono = "0.4.42" mime = "0.3.17" -reqwest = { version = "0.11.20", default-features = false, features = ["json", "rustls-tls"] } -tokio = { version = "1.32.0", features = ["full"] } -futures = "0.3.28" +reqwest = { version = "0.13.1", default-features = false, features = ["json", "query", "form"] } +tokio = { version = "1.49.0", features = ["full"] } +futures = "0.3.31" -clap = { version = "4.4.3", features = ["derive", "wrap_help"] } -colored = "2.0.4" -terminal_size = "0.2.6" +clap = { version = "4.5.54", features = ["derive", "wrap_help"] } +colored = "3.0.0" +terminal_size = "0.4.3" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] name = "cnb" -path = "src/bin/cnb.rs" \ No newline at end of file +path = "src/bin/cnb.rs" From e388061ed8a270d820f884895cef8843c6e2d67d Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 14 Jan 2026 01:52:24 +0800 Subject: [PATCH 18/48] ci: update use action version - actions/checkout v3 -> v4 - actions/upload-artifact v3 -> v4 - softprops/action-gh-release v1 -> v2 --- .github/workflows/build-dev.yml | 8 +++++--- .github/workflows/build-release.yml | 10 +++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index 0390cdf..aedee33 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -3,7 +3,7 @@ name: Build / Development on: push: branches: - - '*' + - 'dev' pull_request_target: types: - edited @@ -34,9 +34,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 1 # 浅克 + persist-credentials: false - name: Setup Rust toolchain uses: dsherret/rust-toolchain-file@v1 @@ -98,7 +100,7 @@ jobs: run: echo 'EXT=.exe' >> $GITHUB_OUTPUT - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ steps.gen-name.outputs.NAME }} path: ./target/${{ matrix.targets.target }}/debug/cnb${{ steps.gen-ext.outputs.EXT }} diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index be887ef..f669d95 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -2,6 +2,8 @@ name: Build / Release on: push: + branches: + - main tags: - v*.*.* @@ -28,9 +30,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.ref }} # github.ref 变量将自动填充为触发事件的分支或标签名 + fetch-depth: 0 + persist-credentials: false - name: Setup Rust toolchain uses: dsherret/rust-toolchain-file@v1 @@ -78,7 +82,7 @@ jobs: run: echo 'EXT=.exe' >> $GITHUB_OUTPUT - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ steps.gen-name.outputs.NAME }} path: ./target/${{ matrix.targets.target }}/release/cnb${{ steps.gen-ext.outputs.EXT }} @@ -91,6 +95,6 @@ jobs: out: ${{ steps.gen-name.outputs.NAME }}.zip - name: Create GitHub release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: ${{ steps.gen-name.outputs.NAME }}.zip From 9a2f453046b1573034dafc0ab8fafdc14060bd56 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 14 Jan 2026 02:01:17 +0800 Subject: [PATCH 19/48] ci: refactor metrix.strategy.target and branch-based execution - change build target - use rust triple name build file - run release action on main pushed - run dev action on dev or pr - delete dev generate file and upload. obly check, test and build. --- .github/workflows/build-dev.yml | 56 ++++++++++++++--------------- .github/workflows/build-release.yml | 21 ++++++----- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index aedee33..42c6337 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -20,18 +20,16 @@ jobs: fail-fast: false matrix: targets: - # aarch64 - - { os: macos-11 , target: aarch64-apple-darwin , alias: aarch64-darwin-macos-11 } - - { os: macos-12 , target: aarch64-apple-darwin , alias: aarch64-darwin-macos-12 } - - { os: macos-13 , target: aarch64-apple-darwin , alias: aarch64-darwin-macos-13 } - # amd64 - - { os: macos-12 , target: x86_64-apple-darwin , alias: amd64-darwin-macos-unknown } - - { os: ubuntu-20.04, target: x86_64-unknown-linux-gnu , alias: amd64-gnu-ubuntu-20.04 } - - { os: ubuntu-22.04, target: x86_64-unknown-linux-gnu , alias: amd64-gnu-ubuntu-22.04 } - - { os: ubuntu-22.04, target: x86_64-unknown-linux-musl, alias: amd64-musl-linux-unknown } - - { os: windows-2019, target: x86_64-pc-windows-msvc , alias: amd64-msvc-windows-2019 } - - { os: windows-2022, target: x86_64-pc-windows-msvc , alias: amd64-msvc-windows-2022 } - + # mac + - { os: macos , target: aarch64-apple-darwin , alias: aarch64-apple-darwin } + - { os: macos , target: x86_64-apple-darwin , alias: x86_64-apple-darwin } + # linux + - { os: linux , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } + - { os: linux , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } + # freebsd + - { os: freebsd , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } + # windows + - { os: windows , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } steps: - name: Checkout uses: actions/checkout@v4 @@ -88,20 +86,20 @@ jobs: target: ${{ matrix.targets.target }} release: false - - name: Generate artifacts name - id: gen-name - shell: bash - run: echo 'NAME=cnb-dev-${{ matrix.targets.alias }}' >> $GITHUB_OUTPUT - - - name: Generate binary extension - id: gen-ext - if: runner.os == 'Windows' - shell: bash - run: echo 'EXT=.exe' >> $GITHUB_OUTPUT - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.gen-name.outputs.NAME }} - path: ./target/${{ matrix.targets.target }}/debug/cnb${{ steps.gen-ext.outputs.EXT }} - if-no-files-found: error + # - name: Generate artifacts name + # id: gen-name + # shell: bash + # run: echo 'NAME=cnb-dev-${{ matrix.targets.alias }}' >> $GITHUB_OUTPUT + + # - name: Generate binary extension + # id: gen-ext + # if: runner.os == 'Windows' + # shell: bash + # run: echo 'EXT=.exe' >> $GITHUB_OUTPUT + + # - name: Upload artifacts + # uses: actions/upload-artifact@v4 + # with: + # name: ${{ steps.gen-name.outputs.NAME }} + # path: ./target/${{ matrix.targets.target }}/debug/cnb${{ steps.gen-ext.outputs.EXT }} + # if-no-files-found: error diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index f669d95..80931f5 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -16,17 +16,16 @@ jobs: fail-fast: false matrix: targets: - # aarch64 - - { os: macos-11 , target: aarch64-apple-darwin , alias: aarch64-darwin-macos-11 } - - { os: macos-12 , target: aarch64-apple-darwin , alias: aarch64-darwin-macos-12 } - - { os: macos-13 , target: aarch64-apple-darwin , alias: aarch64-darwin-macos-13 } - # amd64 - - { os: macos-12 , target: x86_64-apple-darwin , alias: amd64-darwin-macos-unknown } - - { os: ubuntu-20.04, target: x86_64-unknown-linux-gnu , alias: amd64-gnu-ubuntu-20.04 } - - { os: ubuntu-22.04, target: x86_64-unknown-linux-gnu , alias: amd64-gnu-ubuntu-22.04 } - - { os: ubuntu-22.04, target: x86_64-unknown-linux-musl, alias: amd64-musl-linux-unknown } - - { os: windows-2019, target: x86_64-pc-windows-msvc , alias: amd64-msvc-windows-2019 } - - { os: windows-2022, target: x86_64-pc-windows-msvc , alias: amd64-msvc-windows-2022 } + # mac + - { os: macos , target: aarch64-apple-darwin , alias: aarch64-apple-darwin } + - { os: macos , target: x86_64-apple-darwin , alias: x86_64-apple-darwin } + # linux + - { os: linux , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } + - { os: linux , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } + # freebsd + - { os: freebsd , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } + # windows + - { os: windows , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } steps: - name: Checkout From 7505e13af3c9698ed12a35cfe03b510c08d69134 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 14 Jan 2026 17:41:17 +0800 Subject: [PATCH 20/48] style: format code --- src/api/auth/session.rs | 4 ++-- src/api/fav/get_list.rs | 2 +- src/api/ing/comment.rs | 2 +- src/api/ing/get_comment_list.rs | 2 +- src/api/ing/get_list.rs | 2 +- src/api/ing/publish.rs | 2 +- src/api/news/get_body.rs | 2 +- src/api/news/get_list.rs | 2 +- src/api/post/create.rs | 4 ++-- src/api/post/del_one.rs | 2 +- src/api/post/get_comment_list.rs | 2 +- src/api/post/get_count.rs | 2 +- src/api/post/get_meta_list.rs | 4 ++-- src/api/post/get_one.rs | 2 +- src/api/post/get_one_raw.rs | 2 +- src/api/post/search_self.rs | 2 +- src/api/post/search_site.rs | 2 +- src/api/post/update.rs | 4 ++-- src/api/user/info.rs | 2 +- src/apis/ing/mod.rs | 2 +- src/args/parser/fav.rs | 2 +- src/args/parser/ing.rs | 2 +- src/args/parser/news.rs | 2 +- src/args/parser/post.rs | 2 +- src/args/parser/user.rs | 2 +- src/bin/cnb.rs | 2 +- src/display/colorful/ing.rs | 2 +- src/display/colorful/post.rs | 9 ++++----- src/display/normal/ing.rs | 2 +- src/display/normal/post.rs | 9 ++++----- src/infra/http.rs | 10 +++------- src/infra/iter.rs | 2 +- src/infra/json.rs | 4 ++-- src/infra/terminal.rs | 2 +- 34 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/api/auth/session.rs b/src/api/auth/session.rs index 60aa621..7a56b44 100644 --- a/src/api/auth/session.rs +++ b/src/api/auth/session.rs @@ -1,8 +1,8 @@ use crate::infra::result::WrapResult; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use home::home_dir; use std::fs; -use std::fs::{metadata, remove_file, File}; +use std::fs::{File, metadata, remove_file}; use std::io::Write; use std::path::{Path, PathBuf}; diff --git a/src/api/fav/get_list.rs b/src/api/fav/get_list.rs index 82f2120..bc3892a 100644 --- a/src/api/fav/get_list.rs +++ b/src/api/fav/get_list.rs @@ -1,5 +1,5 @@ use crate::api::fav::Fav; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; use crate::infra::result::WrapResult; diff --git a/src/api/ing/comment.rs b/src/api/ing/comment.rs index 53f8395..9d9e837 100644 --- a/src/api/ing/comment.rs +++ b/src/api/ing/comment.rs @@ -1,5 +1,5 @@ use crate::api::ing::Ing; -use crate::infra::http::{unit_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, unit_or_err}; use crate::openapi; use anyhow::Result; use serde::{Deserialize, Serialize}; diff --git a/src/api/ing/get_comment_list.rs b/src/api/ing/get_comment_list.rs index 59ac871..f992c7d 100644 --- a/src/api/ing/get_comment_list.rs +++ b/src/api/ing/get_comment_list.rs @@ -1,5 +1,5 @@ use crate::api::ing::Ing; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; use crate::openapi; diff --git a/src/api/ing/get_list.rs b/src/api/ing/get_list.rs index 5eca27a..9a1f6af 100644 --- a/src/api/ing/get_list.rs +++ b/src/api/ing/get_list.rs @@ -1,5 +1,5 @@ use crate::api::ing::{Ing, IngSendFrom, IngType}; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; use crate::infra::result::WrapResult; diff --git a/src/api/ing/publish.rs b/src/api/ing/publish.rs index d3efdd0..733e738 100644 --- a/src/api/ing/publish.rs +++ b/src/api/ing/publish.rs @@ -1,5 +1,5 @@ use crate::api::ing::{Ing, IngSendFrom}; -use crate::infra::http::{unit_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, unit_or_err}; use crate::openapi; use anyhow::Result; use serde_json::json; diff --git a/src/api/news/get_body.rs b/src/api/news/get_body.rs index 82a2f35..221618d 100644 --- a/src/api/news/get_body.rs +++ b/src/api/news/get_body.rs @@ -1,6 +1,6 @@ use crate::api::news::News; use crate::blog_backend; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use anyhow::Result; impl News { diff --git a/src/api/news/get_list.rs b/src/api/news/get_list.rs index 23ff0b3..a3acfab 100644 --- a/src/api/news/get_list.rs +++ b/src/api/news/get_list.rs @@ -1,5 +1,5 @@ use crate::api::news::News; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; use crate::infra::result::WrapResult; diff --git a/src/api/post/create.rs b/src/api/post/create.rs index 437dd34..b0e6398 100644 --- a/src/api/post/create.rs +++ b/src/api/post/create.rs @@ -1,10 +1,10 @@ use crate::api::post::Post; use crate::blog_backend; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; use anyhow::Result; -use serde_json::{json, Value}; +use serde_json::{Value, json}; impl Post { pub async fn create(&self, title: &str, body: &str, publish: bool) -> Result { diff --git a/src/api/post/del_one.rs b/src/api/post/del_one.rs index 02b03c6..02d4688 100644 --- a/src/api/post/del_one.rs +++ b/src/api/post/del_one.rs @@ -1,6 +1,6 @@ use crate::api::post::Post; use crate::blog_backend; -use crate::infra::http::{unit_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, unit_or_err}; use anyhow::Result; impl Post { diff --git a/src/api/post/get_comment_list.rs b/src/api/post/get_comment_list.rs index e14162f..44df2db 100644 --- a/src/api/post/get_comment_list.rs +++ b/src/api/post/get_comment_list.rs @@ -1,6 +1,6 @@ use crate::api::post::Post; use crate::api::user::User; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; use crate::openapi; diff --git a/src/api/post/get_count.rs b/src/api/post/get_count.rs index 3c0e4c2..85f2a73 100644 --- a/src/api/post/get_count.rs +++ b/src/api/post/get_count.rs @@ -1,6 +1,6 @@ use crate::api::post::Post; use crate::blog_backend; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; use anyhow::Result; diff --git a/src/api/post/get_meta_list.rs b/src/api/post/get_meta_list.rs index befa829..8ebe8aa 100644 --- a/src/api/post/get_meta_list.rs +++ b/src/api/post/get_meta_list.rs @@ -1,7 +1,7 @@ -use crate::api::post::get_one::PostEntry; use crate::api::post::Post; +use crate::api::post::get_one::PostEntry; use crate::blog_backend; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; use crate::infra::result::WrapResult; diff --git a/src/api/post/get_one.rs b/src/api/post/get_one.rs index 4fedee6..e912a25 100644 --- a/src/api/post/get_one.rs +++ b/src/api/post/get_one.rs @@ -1,6 +1,6 @@ use crate::api::post::Post; use crate::blog_backend; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; use anyhow::Result; diff --git a/src/api/post/get_one_raw.rs b/src/api/post/get_one_raw.rs index ebf24e2..b1068fd 100644 --- a/src/api/post/get_one_raw.rs +++ b/src/api/post/get_one_raw.rs @@ -1,6 +1,6 @@ use crate::api::post::Post; use crate::blog_backend; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::result::WrapResult; use anyhow::Result; use serde_json::Value; diff --git a/src/api/post/search_self.rs b/src/api/post/search_self.rs index b48bb59..7340765 100644 --- a/src/api/post/search_self.rs +++ b/src/api/post/search_self.rs @@ -1,6 +1,6 @@ use crate::api::post::Post; use crate::blog_backend; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; use crate::infra::result::WrapResult; diff --git a/src/api/post/search_site.rs b/src/api/post/search_site.rs index fe02b48..74631ff 100644 --- a/src/api/post/search_site.rs +++ b/src/api/post/search_site.rs @@ -1,5 +1,5 @@ use crate::api::post::Post; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; use crate::infra::result::WrapResult; diff --git a/src/api/post/update.rs b/src/api/post/update.rs index 79e689d..ffc455f 100644 --- a/src/api/post/update.rs +++ b/src/api/post/update.rs @@ -1,10 +1,10 @@ use crate::api::post::Post; use crate::blog_backend; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; use anyhow::Result; -use serde_json::{json, Value}; +use serde_json::{Value, json}; impl Post { pub async fn update( diff --git a/src/api/user/info.rs b/src/api/user/info.rs index 19df873..d4ca02b 100644 --- a/src/api/user/info.rs +++ b/src/api/user/info.rs @@ -1,5 +1,5 @@ use crate::api::user::User; -use crate::infra::http::{body_or_err, RequestBuilderExt}; +use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; use crate::openapi; diff --git a/src/apis/ing/mod.rs b/src/apis/ing/mod.rs index 4300351..b891227 100644 --- a/src/apis/ing/mod.rs +++ b/src/apis/ing/mod.rs @@ -20,7 +20,7 @@ use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; use crate::{ - api::ing::{get_list::IngEntry, IngSendFrom}, + api::ing::{IngSendFrom, get_list::IngEntry}, infra::http::RequestBuilderExt, openapi, }; diff --git a/src/args/parser/fav.rs b/src/args/parser/fav.rs index 71fc9af..215650c 100644 --- a/src/args/parser/fav.rs +++ b/src/args/parser/fav.rs @@ -1,5 +1,5 @@ use crate::args::parser::{get_skip, get_take}; -use crate::args::{cmd, Args, Cmd}; +use crate::args::{Args, Cmd, cmd}; use crate::infra::option::WrapOption; pub fn list_fav(args: &Args) -> Option<(usize, usize)> { diff --git a/src/args/parser/ing.rs b/src/args/parser/ing.rs index dc5991c..8de24a8 100644 --- a/src/args/parser/ing.rs +++ b/src/args/parser/ing.rs @@ -1,7 +1,7 @@ use crate::api::ing::IngType; use crate::args::cmd::ing::{CreateIng, QueryIng}; use crate::args::parser::{get_skip, get_take}; -use crate::args::{cmd, Args, Cmd}; +use crate::args::{Args, Cmd, cmd}; use crate::infra::option::WrapOption; pub fn list_ing(args: &Args) -> Option<(usize, usize, IngType, bool)> { diff --git a/src/args/parser/news.rs b/src/args/parser/news.rs index 83d72f8..96cc999 100644 --- a/src/args/parser/news.rs +++ b/src/args/parser/news.rs @@ -1,5 +1,5 @@ use crate::args::parser::{get_skip, get_take}; -use crate::args::{cmd, Args, Cmd}; +use crate::args::{Args, Cmd, cmd}; use crate::infra::option::WrapOption; pub fn list_news(args: &Args) -> Option<(usize, usize)> { diff --git a/src/args/parser/post.rs b/src/args/parser/post.rs index c668d93..3d75a2a 100644 --- a/src/args/parser/post.rs +++ b/src/args/parser/post.rs @@ -1,6 +1,6 @@ use crate::args::cmd::post::{CreateCmd, UpdateCmd}; use crate::args::parser::{get_skip, get_take}; -use crate::args::{cmd, Args, Cmd}; +use crate::args::{Args, Cmd, cmd}; use crate::infra::option::WrapOption; pub fn list_post(args: &Args) -> Option<(usize, usize)> { diff --git a/src/args/parser/user.rs b/src/args/parser/user.rs index a44ad76..63524ad 100644 --- a/src/args/parser/user.rs +++ b/src/args/parser/user.rs @@ -1,4 +1,4 @@ -use crate::args::{cmd, Args, Cmd, GlobalOpt}; +use crate::args::{Args, Cmd, GlobalOpt, cmd}; use crate::infra::option::WrapOption; pub fn login(args: &Args) -> Option<&String> { diff --git a/src/bin/cnb.rs b/src/bin/cnb.rs index 31bacba..87b229e 100644 --- a/src/bin/cnb.rs +++ b/src/bin/cnb.rs @@ -17,7 +17,7 @@ use cnblogs_lib::api::post::Post; use cnblogs_lib::api::user::User; use cnblogs_lib::args::cmd::post::{CreateCmd, UpdateCmd}; use cnblogs_lib::args::parser::no_operation; -use cnblogs_lib::args::{parser, Args}; +use cnblogs_lib::args::{Args, parser}; use cnblogs_lib::infra::fp::currying::eq; use cnblogs_lib::infra::infer::infer; use cnblogs_lib::infra::iter::{ExactSizeIteratorExt, IntoIteratorExt}; diff --git a/src/display/colorful/ing.rs b/src/display/colorful/ing.rs index 190debf..1d4fdc6 100644 --- a/src/display/colorful/ing.rs +++ b/src/display/colorful/ing.rs @@ -1,7 +1,7 @@ use crate::api::ing::get_comment_list::IngCommentEntry; use crate::api::ing::get_list::IngEntry; use crate::api::ing::{ - fmt_content, get_ing_at_user_tag_text, ing_star_tag_to_text, rm_ing_at_user_tag, IngSendFrom, + IngSendFrom, fmt_content, get_ing_at_user_tag_text, ing_star_tag_to_text, rm_ing_at_user_tag, }; use crate::args::TimeStyle; use crate::display::colorful::fmt_err; diff --git a/src/display/colorful/post.rs b/src/display/colorful/post.rs index 206705b..43c95bb 100644 --- a/src/display/colorful/post.rs +++ b/src/display/colorful/post.rs @@ -84,14 +84,13 @@ pub fn show_post_meta(time_style: &TimeStyle, entry: &Result) -> Resu let words_count = words_count::count(body).words; writeln!(buf, "Words {}", words_count)?; } - if let Some(tags) = &entry.tags { - if let Some(tags_text) = tags + if let Some(tags) = &entry.tags + && let Some(tags_text) = tags .clone() .into_iter() .reduce(|acc, tag| format!("{}, {}", acc, tag)) - { - writeln!(buf, "Tags {}", tags_text)?; - } + { + writeln!(buf, "Tags {}", tags_text)?; } let create_time = display_cnb_time(&entry.create_time, time_style); writeln!(buf, "Create {}", create_time)?; diff --git a/src/display/normal/ing.rs b/src/display/normal/ing.rs index 42dc0a9..beea460 100644 --- a/src/display/normal/ing.rs +++ b/src/display/normal/ing.rs @@ -1,7 +1,7 @@ use crate::api::ing::get_comment_list::IngCommentEntry; use crate::api::ing::get_list::IngEntry; use crate::api::ing::{ - fmt_content, get_ing_at_user_tag_text, ing_star_tag_to_text, rm_ing_at_user_tag, IngSendFrom, + IngSendFrom, fmt_content, get_ing_at_user_tag_text, ing_star_tag_to_text, rm_ing_at_user_tag, }; use crate::args::TimeStyle; use crate::display::normal::fmt_err; diff --git a/src/display/normal/post.rs b/src/display/normal/post.rs index f687cbb..f703f22 100644 --- a/src/display/normal/post.rs +++ b/src/display/normal/post.rs @@ -81,14 +81,13 @@ pub fn show_post_meta(time_style: &TimeStyle, entry: &Result) -> Resu let words_count = words_count::count(body).words; writeln!(buf, "Words {}", words_count)?; } - if let Some(tags) = &entry.tags { - if let Some(tags_text) = tags + if let Some(tags) = &entry.tags + && let Some(tags_text) = tags .clone() .into_iter() .reduce(|acc, tag| format!("{}, {}", acc, tag)) - { - writeln!(buf, "Tags {}", tags_text)?; - } + { + writeln!(buf, "Tags {}", tags_text)?; } let create_time = display_cnb_time(&entry.create_time, time_style); writeln!(buf, "Create {}", create_time)?; diff --git a/src/infra/http.rs b/src/infra/http.rs index 5f34a4f..6ff633b 100644 --- a/src/infra/http.rs +++ b/src/infra/http.rs @@ -1,6 +1,6 @@ use crate::infra::result::WrapResult; -use anyhow::bail; use anyhow::Result; +use anyhow::bail; use reqwest::header::AUTHORIZATION; use reqwest::{RequestBuilder, Response}; use std::ops::Not; @@ -10,16 +10,12 @@ pub const PAT: &str = "pat"; #[macro_export] macro_rules! bearer { - ($token:expr) => {{ - format!("Bearer {}", $token) - }}; + ($token:expr) => {{ format!("Bearer {}", $token) }}; } #[macro_export] macro_rules! basic { - ($token:expr) => {{ - format!("Basic {}", $token) - }}; + ($token:expr) => {{ format!("Basic {}", $token) }}; } pub trait RequestBuilderExt { diff --git a/src/infra/iter.rs b/src/infra/iter.rs index 1a7aafa..eef6322 100644 --- a/src/infra/iter.rs +++ b/src/infra/iter.rs @@ -1,4 +1,4 @@ -use futures::future::{join_all, JoinAll}; +use futures::future::{JoinAll, join_all}; use std::future::Future; pub trait IteratorExt: Iterator { diff --git a/src/infra/json.rs b/src/infra/json.rs index d17fec7..cf93cde 100644 --- a/src/infra/json.rs +++ b/src/infra/json.rs @@ -1,6 +1,6 @@ -use anyhow::{anyhow, Result}; -use serde::de::DeserializeOwned; +use anyhow::{Result, anyhow}; use serde::Serialize; +use serde::de::DeserializeOwned; use serde_json::Value; pub fn serialize(val: T) -> Result diff --git a/src/infra/terminal.rs b/src/infra/terminal.rs index 7312952..88d2d17 100644 --- a/src/infra/terminal.rs +++ b/src/infra/terminal.rs @@ -1,4 +1,4 @@ -use terminal_size::{terminal_size, Width}; +use terminal_size::{Width, terminal_size}; pub fn get_term_width() -> usize { let (Width(width), _) = terminal_size().expect("Can not get terminal size"); From af8fca36a21680709411a3c3f8599427e270cf66 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 14 Jan 2026 17:43:13 +0800 Subject: [PATCH 21/48] chore: allow reqwest default featuers --- Cargo.lock | 627 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 4 +- 2 files changed, 623 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0dca83..92c5030 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,6 +88,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" version = "0.21.7" @@ -134,15 +156,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.42" @@ -198,6 +234,15 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "cnblogs_lib" version = "0.0.0-dev" @@ -209,7 +254,7 @@ dependencies = [ "clap", "colored", "futures", - "getrandom", + "getrandom 0.3.4", "home", "lazy_static", "mime", @@ -242,6 +287,36 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -304,12 +379,27 @@ dependencies = [ "syn", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -347,6 +437,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.31" @@ -436,6 +532,19 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -443,9 +552,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -530,6 +660,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -541,6 +672,22 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.19" @@ -560,9 +707,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -748,6 +897,38 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.83" @@ -797,6 +978,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "memchr" version = "2.7.6" @@ -847,6 +1034,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "parking_lot" version = "0.12.5" @@ -921,6 +1114,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.43" @@ -958,11 +1207,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom", + "getrandom 0.3.4", ] [[package]] @@ -1031,21 +1280,30 @@ checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", + "mime", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -1055,6 +1313,26 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "1.1.3" @@ -1068,6 +1346,81 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -1080,6 +1433,24 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.9.0" @@ -1110,6 +1481,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.228" @@ -1161,7 +1555,7 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -1268,6 +1662,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.114" @@ -1299,6 +1699,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "terminal_size" version = "0.4.3" @@ -1309,13 +1730,33 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1370,6 +1811,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.49.0" @@ -1398,6 +1854,29 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" version = "0.5.3" @@ -1486,6 +1965,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -1510,6 +1995,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -1602,6 +2097,34 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -1643,6 +2166,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -1661,6 +2195,24 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -1688,6 +2240,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1721,6 +2288,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -1733,6 +2306,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -1745,6 +2324,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1769,6 +2354,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -1781,6 +2372,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1793,6 +2390,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1805,6 +2408,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1902,6 +2511,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index f57731d..33e8e06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "cnblogs_lib" # WRN: Version will be updated by CI while create a tag, NERVER change this. version = "0.0.0-dev" -edition = "2021" +edition = "2024" description = "Cnblogs' command line tool" license = "MIT" repository = "https://github.com/cnblogs/cli" @@ -39,7 +39,7 @@ serde_repr = "0.1.20" home = "0.5.12" chrono = "0.4.42" mime = "0.3.17" -reqwest = { version = "0.13.1", default-features = false, features = ["json", "query", "form"] } +reqwest = { version = "0.13.1", features = ["json", "query", "form"] } tokio = { version = "1.49.0", features = ["full"] } futures = "0.3.31" From d0d56c934a960ad98dbe9265b1c79ee45fbe3550 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Sat, 17 Jan 2026 18:32:12 +0800 Subject: [PATCH 22/48] refactor: backup api source code --- src/{api => api_bak}/auth/mod.rs | 0 src/{api => api_bak}/auth/session.rs | 0 src/{api => api_bak}/fav/get_list.rs | 2 +- src/{api => api_bak}/fav/mod.rs | 0 src/{api => api_bak}/ing/comment.rs | 2 +- src/{api => api_bak}/ing/get_comment_list.rs | 2 +- src/{api => api_bak}/ing/get_list.rs | 2 +- src/{api => api_bak}/ing/mod.rs | 0 src/{api => api_bak}/ing/publish.rs | 2 +- src/{api => api_bak}/mod.rs | 6 +++--- src/{api => api_bak}/news/get_body.rs | 2 +- src/{api => api_bak}/news/get_list.rs | 2 +- src/{api => api_bak}/news/mod.rs | 0 src/{api => api_bak}/post/create.rs | 2 +- src/{api => api_bak}/post/del_one.rs | 2 +- src/{api => api_bak}/post/get_comment_list.rs | 4 ++-- src/{api => api_bak}/post/get_count.rs | 2 +- src/{api => api_bak}/post/get_meta_list.rs | 4 ++-- src/{api => api_bak}/post/get_one.rs | 2 +- src/{api => api_bak}/post/get_one_raw.rs | 2 +- src/{api => api_bak}/post/mod.rs | 0 src/{api => api_bak}/post/search.rs | 0 src/{api => api_bak}/post/search_self.rs | 2 +- src/{api => api_bak}/post/search_site.rs | 2 +- src/{api => api_bak}/post/update.rs | 2 +- src/{api => api_bak}/user/info.rs | 2 +- src/{api => api_bak}/user/mod.rs | 0 src/apis/ing/comment.rs | 2 +- src/apis/ing/mod.rs | 2 +- src/args/cmd/ing.rs | 2 +- src/args/parser/ing.rs | 2 +- src/bin/cnb.rs | 12 ++++++------ src/display/colorful/fav.rs | 2 +- src/display/colorful/ing.rs | 6 +++--- src/display/colorful/news.rs | 2 +- src/display/colorful/post.rs | 6 +++--- src/display/colorful/user.rs | 2 +- src/display/json/fav.rs | 2 +- src/display/json/ing.rs | 4 ++-- src/display/json/news.rs | 2 +- src/display/json/post.rs | 6 +++--- src/display/json/user.rs | 2 +- src/display/mod.rs | 16 ++++++++-------- src/display/normal/fav.rs | 2 +- src/display/normal/ing.rs | 6 +++--- src/display/normal/news.rs | 2 +- src/display/normal/post.rs | 6 +++--- src/display/normal/user.rs | 2 +- src/lib.rs | 2 +- src/logic/ing.rs | 6 +++--- 50 files changed, 71 insertions(+), 71 deletions(-) rename src/{api => api_bak}/auth/mod.rs (100%) rename src/{api => api_bak}/auth/session.rs (100%) rename src/{api => api_bak}/fav/get_list.rs (98%) rename src/{api => api_bak}/fav/mod.rs (100%) rename src/{api => api_bak}/ing/comment.rs (97%) rename src/{api => api_bak}/ing/get_comment_list.rs (97%) rename src/{api => api_bak}/ing/get_list.rs (97%) rename src/{api => api_bak}/ing/mod.rs (100%) rename src/{api => api_bak}/ing/publish.rs (93%) rename src/{api => api_bak}/mod.rs (84%) rename src/{api => api_bak}/news/get_body.rs (93%) rename src/{api => api_bak}/news/get_list.rs (98%) rename src/{api => api_bak}/news/mod.rs (100%) rename src/{api => api_bak}/post/create.rs (96%) rename src/{api => api_bak}/post/del_one.rs (93%) rename src/{api => api_bak}/post/get_comment_list.rs (95%) rename src/{api => api_bak}/post/get_count.rs (96%) rename src/{api => api_bak}/post/get_meta_list.rs (96%) rename src/{api => api_bak}/post/get_one.rs (97%) rename src/{api => api_bak}/post/get_one_raw.rs (98%) rename src/{api => api_bak}/post/mod.rs (100%) rename src/{api => api_bak}/post/search.rs (100%) rename src/{api => api_bak}/post/search_self.rs (99%) rename src/{api => api_bak}/post/search_site.rs (99%) rename src/{api => api_bak}/post/update.rs (97%) rename src/{api => api_bak}/user/info.rs (97%) rename src/{api => api_bak}/user/mod.rs (100%) diff --git a/src/api/auth/mod.rs b/src/api_bak/auth/mod.rs similarity index 100% rename from src/api/auth/mod.rs rename to src/api_bak/auth/mod.rs diff --git a/src/api/auth/session.rs b/src/api_bak/auth/session.rs similarity index 100% rename from src/api/auth/session.rs rename to src/api_bak/auth/session.rs diff --git a/src/api/fav/get_list.rs b/src/api_bak/fav/get_list.rs similarity index 98% rename from src/api/fav/get_list.rs rename to src/api_bak/fav/get_list.rs index bc3892a..201f311 100644 --- a/src/api/fav/get_list.rs +++ b/src/api_bak/fav/get_list.rs @@ -1,4 +1,4 @@ -use crate::api::fav::Fav; +use crate::api_bak::fav::Fav; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; diff --git a/src/api/fav/mod.rs b/src/api_bak/fav/mod.rs similarity index 100% rename from src/api/fav/mod.rs rename to src/api_bak/fav/mod.rs diff --git a/src/api/ing/comment.rs b/src/api_bak/ing/comment.rs similarity index 97% rename from src/api/ing/comment.rs rename to src/api_bak/ing/comment.rs index 9d9e837..c1b620e 100644 --- a/src/api/ing/comment.rs +++ b/src/api_bak/ing/comment.rs @@ -1,4 +1,4 @@ -use crate::api::ing::Ing; +use crate::api_bak::ing::Ing; use crate::infra::http::{RequestBuilderExt, unit_or_err}; use crate::openapi; use anyhow::Result; diff --git a/src/api/ing/get_comment_list.rs b/src/api_bak/ing/get_comment_list.rs similarity index 97% rename from src/api/ing/get_comment_list.rs rename to src/api_bak/ing/get_comment_list.rs index f992c7d..ff3c4ec 100644 --- a/src/api/ing/get_comment_list.rs +++ b/src/api_bak/ing/get_comment_list.rs @@ -1,4 +1,4 @@ -use crate::api::ing::Ing; +use crate::api_bak::ing::Ing; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; diff --git a/src/api/ing/get_list.rs b/src/api_bak/ing/get_list.rs similarity index 97% rename from src/api/ing/get_list.rs rename to src/api_bak/ing/get_list.rs index 9a1f6af..f40608b 100644 --- a/src/api/ing/get_list.rs +++ b/src/api_bak/ing/get_list.rs @@ -1,4 +1,4 @@ -use crate::api::ing::{Ing, IngSendFrom, IngType}; +use crate::api_bak::ing::{Ing, IngSendFrom, IngType}; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; diff --git a/src/api/ing/mod.rs b/src/api_bak/ing/mod.rs similarity index 100% rename from src/api/ing/mod.rs rename to src/api_bak/ing/mod.rs diff --git a/src/api/ing/publish.rs b/src/api_bak/ing/publish.rs similarity index 93% rename from src/api/ing/publish.rs rename to src/api_bak/ing/publish.rs index 733e738..2cd51e9 100644 --- a/src/api/ing/publish.rs +++ b/src/api_bak/ing/publish.rs @@ -1,4 +1,4 @@ -use crate::api::ing::{Ing, IngSendFrom}; +use crate::api_bak::ing::{Ing, IngSendFrom}; use crate::infra::http::{RequestBuilderExt, unit_or_err}; use crate::openapi; use anyhow::Result; diff --git a/src/api/mod.rs b/src/api_bak/mod.rs similarity index 84% rename from src/api/mod.rs rename to src/api_bak/mod.rs index 94018dd..4244ec2 100644 --- a/src/api/mod.rs +++ b/src/api_bak/mod.rs @@ -9,7 +9,7 @@ pub const BLOG_BACKEND: &str = "https://i.cnblogs.com/api"; #[macro_export] macro_rules! blog_backend { ($($arg:tt)*) => {{ - use $crate::api::BLOG_BACKEND; + use $crate::api_bak::BLOG_BACKEND; format!("{}{}", BLOG_BACKEND, format_args!($($arg)*)) }}; } @@ -18,7 +18,7 @@ pub const OPENAPI: &str = "https://api.cnblogs.com/api"; #[macro_export] macro_rules! openapi { ($($arg:tt)*) => {{ - use $crate::api::OPENAPI; + use $crate::api_bak::OPENAPI; format!("{}{}", OPENAPI, format_args!($($arg)*)) }}; } @@ -27,7 +27,7 @@ pub const OAUTH: &str = "https://oauth.cnblogs.com"; #[macro_export] macro_rules! oauth { ($($arg:tt)*) => {{ - use $crate::api::OAUTH; + use $crate::api_bak::OAUTH; format!("{}{}", OAUTH, format_args!($($arg)*)) }}; } diff --git a/src/api/news/get_body.rs b/src/api_bak/news/get_body.rs similarity index 93% rename from src/api/news/get_body.rs rename to src/api_bak/news/get_body.rs index 221618d..5b69187 100644 --- a/src/api/news/get_body.rs +++ b/src/api_bak/news/get_body.rs @@ -1,4 +1,4 @@ -use crate::api::news::News; +use crate::api_bak::news::News; use crate::blog_backend; use crate::infra::http::{RequestBuilderExt, body_or_err}; use anyhow::Result; diff --git a/src/api/news/get_list.rs b/src/api_bak/news/get_list.rs similarity index 98% rename from src/api/news/get_list.rs rename to src/api_bak/news/get_list.rs index a3acfab..b91caf4 100644 --- a/src/api/news/get_list.rs +++ b/src/api_bak/news/get_list.rs @@ -1,4 +1,4 @@ -use crate::api::news::News; +use crate::api_bak::news::News; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; diff --git a/src/api/news/mod.rs b/src/api_bak/news/mod.rs similarity index 100% rename from src/api/news/mod.rs rename to src/api_bak/news/mod.rs diff --git a/src/api/post/create.rs b/src/api_bak/post/create.rs similarity index 96% rename from src/api/post/create.rs rename to src/api_bak/post/create.rs index b0e6398..e0a74f8 100644 --- a/src/api/post/create.rs +++ b/src/api_bak/post/create.rs @@ -1,4 +1,4 @@ -use crate::api::post::Post; +use crate::api_bak::post::Post; use crate::blog_backend; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; diff --git a/src/api/post/del_one.rs b/src/api_bak/post/del_one.rs similarity index 93% rename from src/api/post/del_one.rs rename to src/api_bak/post/del_one.rs index 02d4688..433fc1c 100644 --- a/src/api/post/del_one.rs +++ b/src/api_bak/post/del_one.rs @@ -1,4 +1,4 @@ -use crate::api::post::Post; +use crate::api_bak::post::Post; use crate::blog_backend; use crate::infra::http::{RequestBuilderExt, unit_or_err}; use anyhow::Result; diff --git a/src/api/post/get_comment_list.rs b/src/api_bak/post/get_comment_list.rs similarity index 95% rename from src/api/post/get_comment_list.rs rename to src/api_bak/post/get_comment_list.rs index 44df2db..cf211cf 100644 --- a/src/api/post/get_comment_list.rs +++ b/src/api_bak/post/get_comment_list.rs @@ -1,5 +1,5 @@ -use crate::api::post::Post; -use crate::api::user::User; +use crate::api_bak::post::Post; +use crate::api_bak::user::User; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; diff --git a/src/api/post/get_count.rs b/src/api_bak/post/get_count.rs similarity index 96% rename from src/api/post/get_count.rs rename to src/api_bak/post/get_count.rs index 85f2a73..7ab971e 100644 --- a/src/api/post/get_count.rs +++ b/src/api_bak/post/get_count.rs @@ -1,4 +1,4 @@ -use crate::api::post::Post; +use crate::api_bak::post::Post; use crate::blog_backend; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; diff --git a/src/api/post/get_meta_list.rs b/src/api_bak/post/get_meta_list.rs similarity index 96% rename from src/api/post/get_meta_list.rs rename to src/api_bak/post/get_meta_list.rs index 8ebe8aa..2f2dd57 100644 --- a/src/api/post/get_meta_list.rs +++ b/src/api_bak/post/get_meta_list.rs @@ -1,5 +1,5 @@ -use crate::api::post::Post; -use crate::api::post::get_one::PostEntry; +use crate::api_bak::post::Post; +use crate::api_bak::post::get_one::PostEntry; use crate::blog_backend; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; diff --git a/src/api/post/get_one.rs b/src/api_bak/post/get_one.rs similarity index 97% rename from src/api/post/get_one.rs rename to src/api_bak/post/get_one.rs index e912a25..905f983 100644 --- a/src/api/post/get_one.rs +++ b/src/api_bak/post/get_one.rs @@ -1,4 +1,4 @@ -use crate::api::post::Post; +use crate::api_bak::post::Post; use crate::blog_backend; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; diff --git a/src/api/post/get_one_raw.rs b/src/api_bak/post/get_one_raw.rs similarity index 98% rename from src/api/post/get_one_raw.rs rename to src/api_bak/post/get_one_raw.rs index b1068fd..3004bf2 100644 --- a/src/api/post/get_one_raw.rs +++ b/src/api_bak/post/get_one_raw.rs @@ -1,4 +1,4 @@ -use crate::api::post::Post; +use crate::api_bak::post::Post; use crate::blog_backend; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::result::WrapResult; diff --git a/src/api/post/mod.rs b/src/api_bak/post/mod.rs similarity index 100% rename from src/api/post/mod.rs rename to src/api_bak/post/mod.rs diff --git a/src/api/post/search.rs b/src/api_bak/post/search.rs similarity index 100% rename from src/api/post/search.rs rename to src/api_bak/post/search.rs diff --git a/src/api/post/search_self.rs b/src/api_bak/post/search_self.rs similarity index 99% rename from src/api/post/search_self.rs rename to src/api_bak/post/search_self.rs index 7340765..d792c03 100644 --- a/src/api/post/search_self.rs +++ b/src/api_bak/post/search_self.rs @@ -1,4 +1,4 @@ -use crate::api::post::Post; +use crate::api_bak::post::Post; use crate::blog_backend; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; diff --git a/src/api/post/search_site.rs b/src/api_bak/post/search_site.rs similarity index 99% rename from src/api/post/search_site.rs rename to src/api_bak/post/search_site.rs index 74631ff..40db4ac 100644 --- a/src/api/post/search_site.rs +++ b/src/api_bak/post/search_site.rs @@ -1,4 +1,4 @@ -use crate::api::post::Post; +use crate::api_bak::post::Post; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::iter::IntoIteratorExt; use crate::infra::json; diff --git a/src/api/post/update.rs b/src/api_bak/post/update.rs similarity index 97% rename from src/api/post/update.rs rename to src/api_bak/post/update.rs index ffc455f..36e927a 100644 --- a/src/api/post/update.rs +++ b/src/api_bak/post/update.rs @@ -1,4 +1,4 @@ -use crate::api::post::Post; +use crate::api_bak::post::Post; use crate::blog_backend; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; diff --git a/src/api/user/info.rs b/src/api_bak/user/info.rs similarity index 97% rename from src/api/user/info.rs rename to src/api_bak/user/info.rs index d4ca02b..4809864 100644 --- a/src/api/user/info.rs +++ b/src/api_bak/user/info.rs @@ -1,4 +1,4 @@ -use crate::api::user::User; +use crate::api_bak::user::User; use crate::infra::http::{RequestBuilderExt, body_or_err}; use crate::infra::json; use crate::infra::result::WrapResult; diff --git a/src/api/user/mod.rs b/src/api_bak/user/mod.rs similarity index 100% rename from src/api/user/mod.rs rename to src/api_bak/user/mod.rs diff --git a/src/apis/ing/comment.rs b/src/apis/ing/comment.rs index c6ae97a..e6c5fb8 100644 --- a/src/apis/ing/comment.rs +++ b/src/apis/ing/comment.rs @@ -5,7 +5,7 @@ use anyhow::{Ok, Result}; use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; -use crate::{api::ing::get_comment_list::IngCommentEntry, infra::http::RequestBuilderExt, openapi}; +use crate::{api_bak::ing::get_comment_list::IngCommentEntry, infra::http::RequestBuilderExt, openapi}; /// 闪存评论及评论回复 /// diff --git a/src/apis/ing/mod.rs b/src/apis/ing/mod.rs index b891227..eb6137b 100644 --- a/src/apis/ing/mod.rs +++ b/src/apis/ing/mod.rs @@ -20,7 +20,7 @@ use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; use crate::{ - api::ing::{IngSendFrom, get_list::IngEntry}, + api_bak::ing::{IngSendFrom, get_list::IngEntry}, infra::http::RequestBuilderExt, openapi, }; diff --git a/src/args/cmd/ing.rs b/src/args/cmd/ing.rs index 5305c23..748d681 100644 --- a/src/args/cmd/ing.rs +++ b/src/args/cmd/ing.rs @@ -1,5 +1,5 @@ use crate::{ - api::ing::{IngSendFrom, IngType}, + api_bak::ing::{IngSendFrom, IngType}, apis::{ self, ing::{IngContent, QeurySet}, diff --git a/src/args/parser/ing.rs b/src/args/parser/ing.rs index 8de24a8..146a380 100644 --- a/src/args/parser/ing.rs +++ b/src/args/parser/ing.rs @@ -1,4 +1,4 @@ -use crate::api::ing::IngType; +use crate::api_bak::ing::IngType; use crate::args::cmd::ing::{CreateIng, QueryIng}; use crate::args::parser::{get_skip, get_take}; use crate::args::{Args, Cmd, cmd}; diff --git a/src/bin/cnb.rs b/src/bin/cnb.rs index 87b229e..5d5d438 100644 --- a/src/bin/cnb.rs +++ b/src/bin/cnb.rs @@ -9,12 +9,12 @@ extern crate cnblogs_lib; use anyhow::Result; use clap::Parser; use clap::{Command, CommandFactory}; -use cnblogs_lib::api::auth::session; -use cnblogs_lib::api::fav::Fav; -use cnblogs_lib::api::ing::Ing; -use cnblogs_lib::api::news::News; -use cnblogs_lib::api::post::Post; -use cnblogs_lib::api::user::User; +use cnblogs_lib::api_bak::auth::session; +use cnblogs_lib::api_bak::fav::Fav; +use cnblogs_lib::api_bak::ing::Ing; +use cnblogs_lib::api_bak::news::News; +use cnblogs_lib::api_bak::post::Post; +use cnblogs_lib::api_bak::user::User; use cnblogs_lib::args::cmd::post::{CreateCmd, UpdateCmd}; use cnblogs_lib::args::parser::no_operation; use cnblogs_lib::args::{Args, parser}; diff --git a/src/display/colorful/fav.rs b/src/display/colorful/fav.rs index 532becc..3015690 100644 --- a/src/display/colorful/fav.rs +++ b/src/display/colorful/fav.rs @@ -1,4 +1,4 @@ -use crate::api::fav::get_list::FavEntry; +use crate::api_bak::fav::get_list::FavEntry; use crate::args::TimeStyle; use crate::display::colorful::fmt_err; use crate::infra::result::WrapResult; diff --git a/src/display/colorful/ing.rs b/src/display/colorful/ing.rs index 1d4fdc6..dc26477 100644 --- a/src/display/colorful/ing.rs +++ b/src/display/colorful/ing.rs @@ -1,6 +1,6 @@ -use crate::api::ing::get_comment_list::IngCommentEntry; -use crate::api::ing::get_list::IngEntry; -use crate::api::ing::{ +use crate::api_bak::ing::get_comment_list::IngCommentEntry; +use crate::api_bak::ing::get_list::IngEntry; +use crate::api_bak::ing::{ IngSendFrom, fmt_content, get_ing_at_user_tag_text, ing_star_tag_to_text, rm_ing_at_user_tag, }; use crate::args::TimeStyle; diff --git a/src/display/colorful/news.rs b/src/display/colorful/news.rs index b5e4ffc..b7dc555 100644 --- a/src/display/colorful/news.rs +++ b/src/display/colorful/news.rs @@ -1,4 +1,4 @@ -use crate::api::news::get_list::NewsEntry; +use crate::api_bak::news::get_list::NewsEntry; use crate::args::TimeStyle; use crate::display::colorful::fmt_err; use crate::infra::result::WrapResult; diff --git a/src/display/colorful/post.rs b/src/display/colorful/post.rs index 43c95bb..6e2a168 100644 --- a/src/display/colorful/post.rs +++ b/src/display/colorful/post.rs @@ -1,6 +1,6 @@ -use crate::api::post::get_comment_list::PostCommentEntry; -use crate::api::post::get_one::PostEntry; -use crate::api::post::search_site::SearchResultEntry; +use crate::api_bak::post::get_comment_list::PostCommentEntry; +use crate::api_bak::post::get_one::PostEntry; +use crate::api_bak::post::search_site::SearchResultEntry; use crate::args::TimeStyle; use crate::display::colorful::fmt_err; use crate::infra::result::WrapResult; diff --git a/src/display/colorful/user.rs b/src/display/colorful/user.rs index 9793aec..e15e313 100644 --- a/src/display/colorful/user.rs +++ b/src/display/colorful/user.rs @@ -1,4 +1,4 @@ -use crate::api::user::info::UserInfo; +use crate::api_bak::user::info::UserInfo; use crate::display::colorful::fmt_err; use crate::infra::result::WrapResult; use anyhow::Result; diff --git a/src/display/json/fav.rs b/src/display/json/fav.rs index 65509ad..ba6f9ab 100644 --- a/src/display/json/fav.rs +++ b/src/display/json/fav.rs @@ -1,4 +1,4 @@ -use crate::api::fav::get_list::FavEntry; +use crate::api_bak::fav::get_list::FavEntry; use crate::display::json::{fmt_err, fmt_ok}; use anyhow::Result; diff --git a/src/display/json/ing.rs b/src/display/json/ing.rs index 5128195..45a02e9 100644 --- a/src/display/json/ing.rs +++ b/src/display/json/ing.rs @@ -1,5 +1,5 @@ -use crate::api::ing::get_comment_list::IngCommentEntry; -use crate::api::ing::get_list::IngEntry; +use crate::api_bak::ing::get_comment_list::IngCommentEntry; +use crate::api_bak::ing::get_list::IngEntry; use crate::display::json::{fmt_err, fmt_ok}; use anyhow::Result; use serde_json::json; diff --git a/src/display/json/news.rs b/src/display/json/news.rs index cd5687f..8c4efbb 100644 --- a/src/display/json/news.rs +++ b/src/display/json/news.rs @@ -1,4 +1,4 @@ -use crate::api::news::get_list::NewsEntry; +use crate::api_bak::news::get_list::NewsEntry; use crate::display::json::{fmt_err, fmt_ok}; use anyhow::Result; diff --git a/src/display/json/post.rs b/src/display/json/post.rs index 5e118c9..8c77c53 100644 --- a/src/display/json/post.rs +++ b/src/display/json/post.rs @@ -1,6 +1,6 @@ -use crate::api::post::get_comment_list::PostCommentEntry; -use crate::api::post::get_one::PostEntry; -use crate::api::post::search_site::SearchResultEntry; +use crate::api_bak::post::get_comment_list::PostCommentEntry; +use crate::api_bak::post::get_one::PostEntry; +use crate::api_bak::post::search_site::SearchResultEntry; use crate::display::json::{fmt_err, fmt_ok, fmt_result}; use anyhow::Result; use serde_json::json; diff --git a/src/display/json/user.rs b/src/display/json/user.rs index 9552003..b43c420 100644 --- a/src/display/json/user.rs +++ b/src/display/json/user.rs @@ -1,4 +1,4 @@ -use crate::api::user::info::UserInfo; +use crate::api_bak::user::info::UserInfo; use crate::display::json::fmt_result; use anyhow::Result; use serde_json::json; diff --git a/src/display/mod.rs b/src/display/mod.rs index c7f29ae..86aeeca 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -1,11 +1,11 @@ -use crate::api::fav::get_list::FavEntry; -use crate::api::ing::get_comment_list::IngCommentEntry; -use crate::api::ing::get_list::IngEntry; -use crate::api::news::get_list::NewsEntry; -use crate::api::post::get_comment_list::PostCommentEntry; -use crate::api::post::get_one::PostEntry; -use crate::api::post::search_site::SearchResultEntry; -use crate::api::user::info::UserInfo; +use crate::api_bak::fav::get_list::FavEntry; +use crate::api_bak::ing::get_comment_list::IngCommentEntry; +use crate::api_bak::ing::get_list::IngEntry; +use crate::api_bak::news::get_list::NewsEntry; +use crate::api_bak::post::get_comment_list::PostCommentEntry; +use crate::api_bak::post::get_one::PostEntry; +use crate::api_bak::post::search_site::SearchResultEntry; +use crate::api_bak::user::info::UserInfo; use crate::args::{Style, TimeStyle}; use crate::infra::result::WrapResult; use anyhow::Result; diff --git a/src/display/normal/fav.rs b/src/display/normal/fav.rs index 01b46b7..30dda4d 100644 --- a/src/display/normal/fav.rs +++ b/src/display/normal/fav.rs @@ -1,4 +1,4 @@ -use crate::api::fav::get_list::FavEntry; +use crate::api_bak::fav::get_list::FavEntry; use crate::args::TimeStyle; use crate::display::normal::fmt_err; use crate::infra::result::WrapResult; diff --git a/src/display/normal/ing.rs b/src/display/normal/ing.rs index beea460..42c377f 100644 --- a/src/display/normal/ing.rs +++ b/src/display/normal/ing.rs @@ -1,6 +1,6 @@ -use crate::api::ing::get_comment_list::IngCommentEntry; -use crate::api::ing::get_list::IngEntry; -use crate::api::ing::{ +use crate::api_bak::ing::get_comment_list::IngCommentEntry; +use crate::api_bak::ing::get_list::IngEntry; +use crate::api_bak::ing::{ IngSendFrom, fmt_content, get_ing_at_user_tag_text, ing_star_tag_to_text, rm_ing_at_user_tag, }; use crate::args::TimeStyle; diff --git a/src/display/normal/news.rs b/src/display/normal/news.rs index 22b946d..0539c00 100644 --- a/src/display/normal/news.rs +++ b/src/display/normal/news.rs @@ -1,4 +1,4 @@ -use crate::api::news::get_list::NewsEntry; +use crate::api_bak::news::get_list::NewsEntry; use crate::args::TimeStyle; use crate::display::normal::fmt_err; use crate::infra::result::WrapResult; diff --git a/src/display/normal/post.rs b/src/display/normal/post.rs index f703f22..385e835 100644 --- a/src/display/normal/post.rs +++ b/src/display/normal/post.rs @@ -1,6 +1,6 @@ -use crate::api::post::get_comment_list::PostCommentEntry; -use crate::api::post::get_one::PostEntry; -use crate::api::post::search_site::SearchResultEntry; +use crate::api_bak::post::get_comment_list::PostCommentEntry; +use crate::api_bak::post::get_one::PostEntry; +use crate::api_bak::post::search_site::SearchResultEntry; use crate::args::TimeStyle; use crate::display::normal::fmt_err; use crate::infra::result::WrapResult; diff --git a/src/display/normal/user.rs b/src/display/normal/user.rs index 95971be..82325e4 100644 --- a/src/display/normal/user.rs +++ b/src/display/normal/user.rs @@ -1,4 +1,4 @@ -use crate::api::user::info::UserInfo; +use crate::api_bak::user::info::UserInfo; use crate::display::normal::fmt_err; use crate::infra::result::WrapResult; use anyhow::Result; diff --git a/src/lib.rs b/src/lib.rs index 2fa227b..920dcec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ #![feature(iterator_try_reduce)] #![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] -pub mod api; +pub mod api_bak; pub mod apis; pub mod args; pub mod display; diff --git a/src/logic/ing.rs b/src/logic/ing.rs index 53862ed..0ae6e0e 100644 --- a/src/logic/ing.rs +++ b/src/logic/ing.rs @@ -4,7 +4,7 @@ use anyhow::{Ok, Result}; use crate::{ - api::{ + api_bak::{ self, ing::{get_comment_list::IngCommentEntry, get_list::IngEntry}, }, @@ -18,7 +18,7 @@ use crate::{ pub async fn get_ings_and_comments( t: &str, q: &QueryIng, -) -> Result)>> { +) -> Result)>> { if let Some(ids) = &q.id { let a = ids .iter() @@ -40,7 +40,7 @@ pub async fn get_ings_and_comments( let a = iq(t, &q.into()) .await? .into_iter() - .collect::>(); + .collect::>(); get_ing_comments(t, a).await } } From 685241c4d44add3b011cb0588ecf43822d6a1d4d Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Sat, 17 Jan 2026 19:31:54 +0800 Subject: [PATCH 23/48] refactor: abstract context for cli --- Cargo.toml | 6 ++- src/context/mod.rs | 114 ++++++++++++++++++++++++++++++++++++++++++ src/context/output.rs | 78 +++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 src/context/mod.rs create mode 100644 src/context/output.rs diff --git a/Cargo.toml b/Cargo.toml index 33e8e06..2d4e038 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ serde_with = "3.16.1" serde_repr = "0.1.20" home = "0.5.12" -chrono = "0.4.42" +chrono = { version = "0.4.42", features = ["serde"] } mime = "0.3.17" reqwest = { version = "0.13.1", features = ["json", "query", "form"] } tokio = { version = "1.49.0", features = ["full"] } @@ -46,6 +46,10 @@ futures = "0.3.31" clap = { version = "4.5.54", features = ["derive", "wrap_help"] } colored = "3.0.0" terminal_size = "0.4.3" +anstream = "0.6.21" +anstyle = "1.0.13" +owo-colors = "4.2.3" +const_format = "0.2.35" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/context/mod.rs b/src/context/mod.rs new file mode 100644 index 0000000..52b8985 --- /dev/null +++ b/src/context/mod.rs @@ -0,0 +1,114 @@ +pub mod output; + +use core::time; +use std::{fmt, fs, io::Read, path::PathBuf}; + +use anyhow::{anyhow, Ok, Result}; +use owo_colors::OwoColorize; +use reqwest::{ + header::{self, HeaderMap}, Client, + ClientBuilder, +}; + +use crate::context::output::Terminal; + +const FILENAME: &str = ".cnblogs/token"; + +pub struct Context { + pub terminal: Terminal, + pub token: String, + pub client: Client, + pub headers: HeaderMap, + pub home_dir: PathBuf, + pub file: PathBuf, + pub full_path: PathBuf, + pub json: bool, +} + +impl Context { + pub fn new() -> anyhow::Result { + Self::new_with_token("".to_string()) + } + + pub fn new_with_token(token: String) -> anyhow::Result { + let mut terminal = Terminal::new(); + let mut token = token; + let home_dir = home::home_dir().ok_or_else(|| anyhow!("未获取到家目录,退出。"))?; + let file = PathBuf::from(FILENAME); + let full_path = home_dir.join(&file); + let mut headers = HeaderMap::new(); + + if !full_path.exists() { + let _ = terminal.writeln(format!("缓存文件 `{}` 不存在", FILENAME).red()); + if !full_path + .parent() + .ok_or_else(|| anyhow!("检查`~/.cnblogs`文件夹失败"))? + .exists() + { + fs::create_dir_all( + full_path + .parent() + .ok_or_else(|| anyhow!("创建`~/.cnblogs`文件夹失败"))?, + )?; + } + let _ = fs::File::create(full_path.clone())?; + } + + if token.is_empty() { + let _ = fs::File::open(full_path.clone())?.read_to_string(&mut token); + } else { + fs::write(full_path.clone(), token.as_bytes())?; + } + + if !token.is_empty() { + let header_value = format!("Bearer {token}"); + headers.append(header::AUTHORIZATION, header_value.parse()?); + } + + let client = ClientBuilder::new() + .default_headers(headers.clone()) + .connect_timeout(time::Duration::from_secs(10)) + .https_only(true) + .build()?; + + Ok(Self { + terminal, + token, + client, + headers, + home_dir, + file, + full_path, + json: false, + }) + } + + pub const fn set_json(&mut self, json: bool) { + self.json = json; + } + + pub fn update_auth_header(&mut self) -> anyhow::Result<()> { + let header_value = format!("Bearer {}", self.token); + self.headers + .insert(header::AUTHORIZATION, header_value.parse()?); + Ok(()) + } + + pub fn update_cache_file(&self) -> Result<()> { + fs::write(&self.full_path, self.token.as_bytes())?; + Ok(()) + } + + pub fn update_token(&mut self, token: String) -> anyhow::Result<()> { + if !token.is_empty() { + self.token = token; + self.update_auth_header()?; + self.update_cache_file()?; + } + Ok(()) + } + + pub fn print_message(&mut self, msg: T) -> Result<()> { + self.terminal.writeln(msg) + } +} diff --git a/src/context/output.rs b/src/context/output.rs new file mode 100644 index 0000000..aa5f3ca --- /dev/null +++ b/src/context/output.rs @@ -0,0 +1,78 @@ +//! +//! 输出统一管理 +//! + +use std::fmt; +use std::fmt::Display; +use std::io::IsTerminal; +use std::io::Write; + +use anstream::{AutoStream, ColorChoice}; +use anstyle::{AnsiColor, Color, Style}; +use anyhow::Ok; +use anyhow::Result; +use serde::Serialize; + +#[derive(Debug)] +pub struct Terminal { + pub stdout: AutoStream, + pub stderr: AutoStream, + pub color_choice: ColorChoice, +} + +impl Terminal { + pub fn new() -> Self { + let use_color = std::io::stdout().is_terminal(); + + let color_choice = if use_color { + ColorChoice::Auto + } else { + ColorChoice::Never + }; + let stdout = AutoStream::new(std::io::stdout(), color_choice); + let stderr = AutoStream::new(std::io::stderr(), color_choice); + Self { + stdout, + stderr, + color_choice, + } + } + + pub fn write(&mut self, msg: impl Display) -> Result<()> { + Ok(write!(self.stdout, "{}", msg)?) + } + + pub fn json(&mut self, msg: impl fmt::Display + Serialize) -> Result<()> { + Ok(writeln!(self.stdout, "{}", serde_json::to_string(&msg)?)?) + } + + /// 打印信息(普通输出) + pub fn writeln(&mut self, message: impl fmt::Display) -> Result<()> { + Ok(writeln!(self.stdout, "{}", message)?) + } + + /// 清除当前行 + pub fn clear_line(&mut self) -> Result<()> { + Ok(write!(self.stdout, "\r\x1b[K")?) + } +} + +impl Default for Terminal { + fn default() -> Self { + Self::new() + } +} +pub const fn blue_style() -> Style { + Style::new().fg_color(Some(Color::Ansi(AnsiColor::Blue))) +} + +pub const fn green_style() -> Style { + Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green))) +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Verbosity { + Verbose, + Normal, + Qiuet, +} From d2937f85d4c50354a5b0b9e2eabf1a5cde137e1d Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Sat, 17 Jan 2026 19:37:30 +0800 Subject: [PATCH 24/48] refactor: complete ing commands --- Cargo.lock | 36 ++++++++ src/api/ing.rs | 100 ++++++++++++++++++++++ src/api/mod.rs | 11 +++ src/api/urls.rs | 10 +++ src/api/user.rs | 12 +++ src/commands/auth.rs | 22 +++++ src/commands/fav.rs | 6 ++ src/commands/ing.rs | 191 +++++++++++++++++++++++++++++++++++++++++++ src/commands/mod.rs | 78 ++++++++++++++++++ src/commands/news.rs | 6 ++ src/commands/post.rs | 15 ++++ src/display/ing.rs | 41 ++++++++++ src/display/mod.rs | 7 ++ src/lib.rs | 7 +- src/logic/ing.rs | 103 +++++++++++++++++++++++ src/logic/mod.rs | 19 +++++ src/models/ing.rs | 108 ++++++++++++++++++++++++ src/models/mod.rs | 41 ++++++++++ src/models/user.rs | 45 ++++++++++ src/tools/http.rs | 77 +++++++++++++++++ src/tools/mod.rs | 15 ++++ src/tools/strings.rs | 80 ++++++++++++++++++ src/tools/timer.rs | 24 ++++++ 23 files changed, 1053 insertions(+), 1 deletion(-) create mode 100644 src/api/ing.rs create mode 100644 src/api/mod.rs create mode 100644 src/api/urls.rs create mode 100644 src/api/user.rs create mode 100644 src/commands/auth.rs create mode 100644 src/commands/fav.rs create mode 100644 src/commands/ing.rs create mode 100644 src/commands/mod.rs create mode 100644 src/commands/news.rs create mode 100644 src/commands/post.rs create mode 100644 src/display/ing.rs create mode 100644 src/models/ing.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/user.rs create mode 100644 src/tools/http.rs create mode 100644 src/tools/mod.rs create mode 100644 src/tools/strings.rs create mode 100644 src/tools/timer.rs diff --git a/Cargo.lock b/Cargo.lock index 92c5030..3a9ac02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,17 +247,21 @@ dependencies = [ name = "cnblogs_lib" version = "0.0.0-dev" dependencies = [ + "anstream", + "anstyle", "anyhow", "base64 0.22.1", "base64url", "chrono", "clap", "colored", + "const_format", "futures", "getrandom 0.3.4", "home", "lazy_static", "mime", + "owo-colors", "rand", "regex", "reqwest", @@ -297,6 +301,26 @@ dependencies = [ "memchr", ] +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1040,6 +1064,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1965,6 +1995,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/src/api/ing.rs b/src/api/ing.rs new file mode 100644 index 0000000..e5136f0 --- /dev/null +++ b/src/api/ing.rs @@ -0,0 +1,100 @@ +//! +//! 闪存API +//! +//! raw_*类型直接返回anyhow::Result,提供原始的reponse,供自定义处理逻辑。 +//! + +use anyhow::{Ok, Result}; +use reqwest::{Client, Response}; +use serde::Serialize; +use serde_json::json; + +use crate::{ + api::urls::{COMMENTS_PATH, STATUS}, + models::ing::{IngComment, IngDetail, IngInfo}, +}; + +/// 创建闪存 +/// +/// content: +pub async fn create_statuses( + c: &Client, + content: impl Serialize + Send + Sync, +) -> Result { + raw_create_status(c, content).await +} + +pub async fn get_status_with_comment(c: &Client, status: IngInfo) -> Result { + if status.comment_count < 1 { + return Ok(IngDetail { + status, + comments: vec![], + }); + } + let comments = list_comments(c, status.id).await?; + Ok(IngDetail { status, comments }) +} + +pub async fn get_status(c: &Client, id: u64) -> Result { + Ok(raw_get_status(c, id).await?.json().await?) +} + +pub async fn list_comments(c: &Client, id: u64) -> Result> { + Ok(raw_list_comments(c, id).await?.json().await?) +} + +pub async fn list_statuses( + c: &Client, + path: &str, + params: impl Serialize + Send + Sync, +) -> Result> { + let resp = raw_list_statuses(c, path, params).await?; + Ok(resp.error_for_status()?.json().await?) +} + +pub async fn raw_list_comments(c: &Client, id: u64) -> Result { + let url = format!("{}{}/{}", STATUS, id, COMMENTS_PATH); + Ok(c.get(url).send().await?) +} + +pub async fn raw_list_statuses( + c: &Client, + path: &str, + params: impl Serialize + Send + Sync, +) -> Result { + let url = format!("{}@{}", STATUS, path); + Ok(c.get(url).query(¶ms).send().await?) +} + +pub async fn raw_create_status( + c: &Client, + content: impl Serialize + Send + Sync, +) -> Result { + let url = STATUS.to_string(); + Ok(c.post(url).json(&content).send().await?) +} + +pub async fn raw_create_comment(c: &Client, id: u64, content: String) -> Result { + let url = format!("{}/{}/{}", STATUS, id, COMMENTS_PATH); + let res = json!({"content": content}); + Ok(c.post(url).json(&res).send().await?) +} + +pub async fn raw_delete_status(c: &Client, id: u64) -> Result { + let url = format!("{}{}", STATUS, id); + Ok(c.delete(url).send().await?) +} + +pub async fn raw_delete_status_comment( + c: &Client, + status_id: u64, + comment_id: u64, +) -> Result { + let url = format!("{}{}/{}/{}", STATUS, status_id, COMMENTS_PATH, comment_id); + Ok(c.delete(url).send().await?) +} + +pub async fn raw_get_status(c: &Client, id: u64) -> Result { + let url = format!("{}{}", STATUS, id); + Ok(c.get(url).send().await?) +} diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..a7cad72 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,11 @@ +//! +//! API +//! +//! 负责HTTP的调用,返回对应的response +//! + +pub mod ing; +pub mod urls; +pub mod user; + +pub struct Client {} diff --git a/src/api/urls.rs b/src/api/urls.rs new file mode 100644 index 0000000..a6c70dd --- /dev/null +++ b/src/api/urls.rs @@ -0,0 +1,10 @@ +use const_format::formatcp; + +pub const BLOG_BACKEND: &str = "https://i.cnblogs.com/api"; +pub const OPENAPI: &str = "https://api.cnblogs.com/api"; +pub const OAUTH: &str = "https://oauth.cnblogs.com"; + +// 修复:正确的参数数量 +pub const USER: &str = formatcp!("{}/user", OPENAPI); +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 new file mode 100644 index 0000000..a0732ba --- /dev/null +++ b/src/api/user.rs @@ -0,0 +1,12 @@ +use anyhow::Result; +use reqwest::{Client, Response}; + +use crate::{api::urls, models::user::UserInfo, tools::IntoAnyhowResult}; + +pub async fn raw_user_info(c: &Client) -> Result { + c.get(urls::USER).send().await.into_anyhow_result() +} + +pub async fn user_info(c: &Client) -> Result { + raw_user_info(c).await?.json().await.into_anyhow_result() +} diff --git a/src/commands/auth.rs b/src/commands/auth.rs new file mode 100644 index 0000000..31bd075 --- /dev/null +++ b/src/commands/auth.rs @@ -0,0 +1,22 @@ +//! +//! 认证子命令 +//! + +use clap::{Args, Subcommand}; + +#[derive(Debug, Args)] +pub struct Authenticate { + #[clap(subcommand)] + pub commands: AuthenticateSubCommands, +} + +/// 认证子命令 +/// +/// +#[derive(Debug, Subcommand)] +pub enum AuthenticateSubCommands { + Login { token: String }, + Logout, + Status, + Token, +} diff --git a/src/commands/fav.rs b/src/commands/fav.rs new file mode 100644 index 0000000..5bb42d8 --- /dev/null +++ b/src/commands/fav.rs @@ -0,0 +1,6 @@ +use clap::Subcommand; + +#[derive(Debug, Subcommand)] +pub enum FaverateAction { + List, +} diff --git a/src/commands/ing.rs b/src/commands/ing.rs new file mode 100644 index 0000000..ffb5a0b --- /dev/null +++ b/src/commands/ing.rs @@ -0,0 +1,191 @@ +use anyhow::anyhow; +use clap::{Args, Subcommand, ValueEnum, builder::NonEmptyStringValueParser}; +use serde::Serialize; +use serde_json::json; + +use crate::commands::validate_non_zero_id; + +#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] +pub enum IngType { + #[clap(name = "all", help = "查询所有闪存内容")] + All, + /// following 简写查询关注的人的闪存 + #[clap(name = "fo")] + Following, + /// 查询@我的闪存 + // #[clap(name = "mention")] + // Mention, + /// 查询我的闪存 + // #[clap(name = "my")] + My, + /// 查询回复我的闪存 + // #[clap(name = "comment")] + // Comment, + /// recent comment 简写 查询最近的评论闪存 + #[clap(name = "rc")] + RecentComment, + /// recent comment 简写 查询我发布的评论闪存 + #[clap(name = "myc")] + MyComment, + /// 按标签查询闪存(需配合 --tag 参数) + #[clap(name = "tag")] + Tag, +} + +impl IngType { + pub const fn as_str(&self) -> &str { + match self { + Self::All => "all", + Self::Following => "following", + // Self::Mention => "mention", + Self::My => "my", + // Self::Comment => "comment", + Self::RecentComment => "recentcomment", + Self::MyComment => "mycomment", + Self::Tag => "tag", + } + } +} + +/// 闪存的发布,查询,删除,评论等操作 +#[derive(Debug, Subcommand)] +pub enum IngAction { + Create(IngContent), + Delete(IngDelete), + List(IngListArg), + Replay(IngReplayContent), + Show(IngShowDetail), +} + +/// 浏览闪存,可结合type和option进一步筛选。 +#[derive(Debug, Args, Serialize)] +pub struct IngListArg { + /// 闪存查询类型(可选值:all/following/mention/my/comments/recent-comments/my-comments/tag) + #[clap(value_enum, default_value_t = IngType::All)] + #[serde(skip)] + pub r#type: IngType, + + /// 分页页码(从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, + + /// 按照标签名查询,注意 仅当type=tag时,标签名必须。其余可选。 + #[arg( + long, + required_if_eq("type", "tag"), + value_parser = NonEmptyStringValueParser::new(), + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub tag: Option, + + /// no commnet,关闭评论,只显示闪存。 + #[arg( + default_value = "false", + long = "no", + // help = "no-comment,用于控制是否显示评论。" + )] + #[serde(skip)] + pub no_comment: bool, +} + +/// 发布闪存,操作示例: +/// +/// cnb ing create "Hello World" +/// +/// cnb ing create --tag "lucky" +/// +/// cnb ing create "Hello World" --tag lucky --tag cnb +#[derive(Debug, Args)] +pub struct IngContent { + /// 闪存正文,闪存内容空时,tag为必须 + #[clap(value_parser = NonEmptyStringValueParser::new())] + pub content: Option, + + /// content是否公开,默认公开 + #[arg(long = "is-private", short = 'p')] + pub is_private: bool, + + /// tag标签,可选,如lucky,python + #[arg(short = 't', + long = "tag", + // required_if_eq("content", "None"), + requires_if("false", "content"), + value_parser = NonEmptyStringValueParser::new())] + pub tag: Vec, +} + +impl IngContent { + pub fn to_json(self) -> impl Serialize { + let mut content = String::new(); + self.tag.iter().for_each(|x| { + if x.contains("[") && x.contains("]") { + content.push_str(x); + } else { + content.push_str(&format!("[{}]", x)); + } + }); + + if let Some(cc) = self.content { + content.push_str(&cc); + } + + json!({ + "content": content, + "isPrivate": false, + "clientType": 13, + }) + } + + pub fn validate(&self) -> anyhow::Result<()> { + if self.content.is_none() && self.tag.is_empty() { + return Err(anyhow!("当 content 为空时,tag 必须提供")); + } + Ok(()) + } +} + +/// 根据提供的闪存id删除对应闪存 +/// 也可以同时提供闪存id和commentid删除对应的评论 +#[derive(Debug, Args)] +pub struct IngDelete { + #[arg( + required = true, + // value_delimiter = ',', + value_parser = validate_non_zero_id, + help = "要删除的ID列表(用逗号分隔),不能包含0" + )] + pub id: u64, + + #[arg(long = "cid", help = "根据闪存id和commentid删除评论。", value_parser = validate_non_zero_id)] + pub comment_id: Option, +} + +/// 评论回复,to实现@功能, +#[derive(Debug, Args)] +pub struct IngReplayContent { + #[clap(value_parser = NonEmptyStringValueParser::new())] + pub content: String, + + #[arg(long, value_parser = validate_non_zero_id, required = true)] + pub id: u64, + // #[arg(long = "to", short = 't', value_parser = NonEmptyStringValueParser::new())] + // pub to: Vec, +} + +/// 根据ID展示闪存 +#[derive(Debug, Args)] +pub struct IngShowDetail { + /// 闪存ID + #[clap(value_parser =validate_non_zero_id)] + pub id: u64, + + /// 是否显示评论 + #[arg(long, short = 'c', default_value_t = false)] + pub comments: bool, +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..729da77 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,78 @@ +pub mod auth; +pub mod fav; +pub mod ing; +pub mod news; +pub mod post; + +use std::fmt; + +use clap::{Parser, Subcommand, ValueEnum}; +#[derive(Debug, Parser)] +#[command(name = "cnblogs", about = "博客园CLI工具", version)] +pub struct Cli { + #[arg(long, short = 'j', default_value = "false", global = true)] + pub json: bool, + #[arg(long, short = 'v', global = true, help = "显示详细信息")] + pub verbose: bool, + #[arg(long, short = 's', global = true, default_value_t = Style::Pretty, help = "输出样式")] + pub style: Style, + #[command(subcommand)] + pub commands: Commands, +} + +#[derive(Debug, Subcommand)] +pub enum Commands { + Auth(auth::Authenticate), + Ing { + #[command(subcommand)] + action: ing::IngAction, + }, + Post(post::PostCommand), + News, + Fav, +} + +#[derive(Clone, Debug, ValueEnum, PartialEq, Eq)] +pub enum Style { + #[clap(name = "json", help = "JSON格式输出")] + Json, + #[clap(name = "pretty", help = "美化输出格式输出")] + Pretty, + #[clap(name = "quiet", help = "禁用输出")] + Quiet, +} + +impl fmt::Display for Style { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Json => write!(f, "json"), + Self::Pretty => write!(f, "pretty"), + Self::Quiet => write!(f, "quiet"), + } + } +} + +// 验证不能为零 +fn validate_non_zero_id(s: &str) -> Result { + let id = s + .parse::() + .map_err(|_| format!("'{}' 不是有效的数字", s))?; + + if id == 0 { + Err("ID不能为0".to_string()) + } else { + Ok(id) + } +} + +pub fn validate_non_string(s: &str) -> Result { + let res = s + .parse::() + .map_err(|_| format!("'{}' 不是有效的字符串", s))?; + + if res.is_empty() { + Err("ID不能为0".to_string()) + } else { + Ok(res) + } +} diff --git a/src/commands/news.rs b/src/commands/news.rs new file mode 100644 index 0000000..123ecdc --- /dev/null +++ b/src/commands/news.rs @@ -0,0 +1,6 @@ +use clap::Subcommand; + +#[derive(Debug, Subcommand)] +pub enum NewsAction { + List, +} diff --git a/src/commands/post.rs b/src/commands/post.rs new file mode 100644 index 0000000..70b547a --- /dev/null +++ b/src/commands/post.rs @@ -0,0 +1,15 @@ +use clap::{Args, Subcommand}; + +#[derive(Debug, Args)] +pub struct PostCommand { + #[clap(subcommand)] + pub subcommands: PostAction, +} + +#[derive(Debug, Subcommand)] +pub enum PostAction { + Create, + List, + Update, + Show, +} diff --git a/src/display/ing.rs b/src/display/ing.rs new file mode 100644 index 0000000..a4e5749 --- /dev/null +++ b/src/display/ing.rs @@ -0,0 +1,41 @@ +// use colored::Colorize; +use owo_colors::OwoColorize; + +pub fn tag_lucky(is_lucky: bool) -> String { + if is_lucky { + "[幸运星]".yellow().to_string() + } else { + "".to_string() + } +} + +pub fn tag_public(is_private: bool) -> String { + if is_private { + "[未公开]".yellow().to_string() + } else { + "[公开]".blue().to_string() + } +} + +pub fn tag_sender(i: u8) -> String { + match i { + 0 => "[none]".blue().to_string(), + 1 => "[ms]".blue().to_string(), + 2 => "[gtalk]".blue().to_string(), + 3 => "[qq]".blue().to_string(), + 5 => "[sms]".blue().to_string(), + 6 => "[mobile]".blue().to_string(), + 8 => "[web]".blue().to_string(), + 9 => "[vscode]".blue().to_string(), + 13 => "[cnb]".blue().to_string(), + _ => "[unknow]".blue().to_string(), + } +} + +pub fn tag_comment_count(c: u64) -> String { + if c > 0 { + format!("[{}评论]", c) + } else { + "".to_string() + } +} diff --git a/src/display/mod.rs b/src/display/mod.rs index 86aeeca..07a289a 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -9,12 +9,19 @@ use crate::api_bak::user::info::UserInfo; use crate::args::{Style, TimeStyle}; use crate::infra::result::WrapResult; use anyhow::Result; +use serde::Serialize; +use std::fmt::Display; use std::path::PathBuf; mod colorful; +pub mod ing; mod json; mod normal; +pub trait Format: Serialize + Display { + fn into_formats(self) -> String; +} + pub fn login(style: &Style, cfg_path: &Result) -> String { match style { Style::Colorful => colorful::user::login(cfg_path), diff --git a/src/lib.rs b/src/lib.rs index 920dcec..c9b110c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,11 +2,16 @@ #![feature(if_let_guard)] #![feature(iterator_try_collect)] #![feature(iterator_try_reduce)] -#![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] +#![warn(clippy::nursery, clippy::cargo_common_metadata)] +pub mod api; pub mod api_bak; pub mod apis; pub mod args; +pub mod commands; +pub mod context; pub mod display; pub mod infra; pub mod logic; +pub mod models; +pub mod tools; diff --git a/src/logic/ing.rs b/src/logic/ing.rs index 0ae6e0e..a587508 100644 --- a/src/logic/ing.rs +++ b/src/logic/ing.rs @@ -3,13 +3,19 @@ use anyhow::{Ok, Result}; +use crate::context::Context; +use crate::tools::http::IntoNoParseResult; use crate::{ + api, api_bak::{ self, ing::{get_comment_list::IngCommentEntry, get_list::IngEntry}, }, apis::ing::{comment, delete, post, query as iq, query_by_id}, args::cmd::ing::{CreateIng, QueryIng}, + commands::ing::{ + IngAction, IngContent, IngDelete, IngListArg, IngReplayContent, IngShowDetail, + }, infra::iter::IntoIteratorExt, }; @@ -89,3 +95,100 @@ pub async fn create_ing_with_arg(t: &str, c: CreateIng) { println!("🙈 ! {:?}", cc.content); } } + +pub async fn endpoint(cmd: IngAction, ctx: &mut Context) -> Result<()> { + match cmd { + IngAction::List(action) => handle_list_action(action, ctx).await?, + IngAction::Create(action) => handle_create_action(action, ctx).await?, + IngAction::Delete(action) => handle_delete_action(action, ctx).await?, + IngAction::Replay(action) => handle_replay_action(action, ctx).await?, + IngAction::Show(action) => handle_show_action(action, ctx).await?, + }; + Ok(()) +} + +/// 查看实现 +async fn handle_list_action(action: IngListArg, ctx: &mut Context) -> Result<()> { + let resp = api::ing::list_statuses(&ctx.client, action.r#type.as_str(), &action).await?; + + match (action.no_comment, ctx.json) { + // 不显示评论,json格式输出。 + (true, true) => { + ctx.terminal.writeln(serde_json::to_string_pretty(&resp)?)?; + } + // 不显示评论,pretty输出。 + (true, false) => { + resp.into_iter() + .for_each(|x| ctx.terminal.writeln(x.format()).unwrap()); + } + // 显示评论,json格式输出。 + (false, true) => { + for i in resp { + let ing = api::ing::get_status_with_comment(&ctx.client, i).await?; + ctx.terminal.writeln(serde_json::to_string_pretty(&ing)?)?; + } + } + // 显示评论,pretty输出。 + (false, false) => { + for v in resp { + ctx.terminal.writeln( + api::ing::get_status_with_comment(&ctx.client, v) + .await? + .format(), + )?; + } + } + } + + Ok(()) +} + +/// 删除实现 +async fn handle_delete_action(action: IngDelete, ctx: &mut Context) -> Result<()> { + let a = if let Some(cid) = action.comment_id { + api::ing::raw_delete_status_comment(&ctx.client, action.id, cid) + .await? + .into_no_parse_result() + .await? + } else { + api::ing::raw_delete_status(&ctx.client, action.id) + .await? + .into_no_parse_result() + .await? + }; + ctx.terminal.writeln(a.into_format()) +} + +/// 发布闪存实现 +async fn handle_create_action(action: IngContent, ctx: &mut Context) -> Result<()> { + action.validate()?; + let a = api::ing::raw_create_status(&ctx.client, action.to_json()) + .await? + .into_no_parse_result() + .await?; + + ctx.terminal.writeln(a.into_format()) +} + +/// 回复功能实现 +async fn handle_replay_action(action: IngReplayContent, ctx: &mut Context) -> Result<()> { + let a = api::ing::raw_create_comment(&ctx.client, action.id, action.content) + .await? + .into_no_parse_result() + .await?; + + ctx.terminal.writeln(a.into_format()) +} + +/// 处理单条展示信息 +async fn handle_show_action(action: IngShowDetail, ctx: &mut Context) -> Result<()> { + let res = api::ing::get_status(&ctx.client, action.id).await?; + + // 显示评论 + if action.comments { + let r = api::ing::get_status_with_comment(&ctx.client, res).await?; + ctx.terminal.writeln(r.format()) + } else { + ctx.terminal.writeln(res.format()) + } +} diff --git a/src/logic/mod.rs b/src/logic/mod.rs index 14d8057..dff3590 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -3,4 +3,23 @@ //! 此模块暂定封装操作逻辑,比如是闪存的curd,闪存评论的curd。 //! +use anyhow::Result; + +use crate::{ + commands::{Cli, Commands}, +}; +use crate::context::Context; + pub mod ing; + +pub async fn run(cli: Cli, ctx: &mut Context) -> Result<()> { + match cli.commands { + Commands::Auth(_a) => {} + Commands::Fav => {} + Commands::Ing { action } => ing::endpoint(action, ctx).await?, + Commands::News => {} + Commands::Post(_p) => {} + } + + Ok(()) +} diff --git a/src/models/ing.rs b/src/models/ing.rs new file mode 100644 index 0000000..9b1d7c9 --- /dev/null +++ b/src/models/ing.rs @@ -0,0 +1,108 @@ +use chrono::NaiveDateTime; +use owo_colors::OwoColorize; +use serde::{Deserialize, Serialize}; + +use crate::{ + display::ing::{tag_comment_count, tag_lucky, tag_public, tag_sender}, + tools::strings::{ExtractAtPeople, ExtractIconAlt}, + tools::timer::DateFormatExt, +}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct IngInfo { + pub id: u64, + pub content: String, + pub is_private: bool, + pub is_lucky: bool, + pub comment_count: u64, + pub date_added: chrono::NaiveDateTime, + pub user_alias: String, + pub user_display_name: String, + pub user_icon_url: String, + pub user_id: u64, + pub user_guid: String, + pub send_from: u8, + pub icons: String, +} + +impl IngInfo { + pub fn format(self) -> String { + let content = self.content.extract_name().unwrap(); + let icon_tag = self.icons.extract_tag().unwrap(); + format!( + "{name}:{content}{icon_tag}{emoji:>4}{time}[#{id}]{lucky}{public}{sender}{comments}", + name = self.user_display_name.blue(), + content = content, + icon_tag = icon_tag, + emoji = "📎", + time = self.date_added.as_time_age().blue(), + id = self.id.bright_green(), + lucky = tag_lucky(self.is_lucky), + public = tag_public(self.is_private), + sender = tag_sender(self.send_from), + comments = tag_comment_count(self.comment_count) + ) + // + // format!( + // "[#{id}]{name}:{content}{icon_tag}{emoji:>4}{time}{lucky}{public}{sender}{comments}", + // name = self.user_display_name.blue(), + // content = content, + // icon_tag = icon_tag, + // emoji = "📎", + // time = self.date_added.as_time_ago().blue(), + // id = self.id.bright_green(), + // lucky = tag_lucky(self.is_lucky), + // public = tag_public(self.is_private), + // sender = tag_sender(self.send_from), + // comments = tag_comment_count(self.comment_count) + // ) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct IngComment { + pub id: u64, + pub content: String, + pub date_added: NaiveDateTime, + pub status_id: u64, + pub user_alias: String, + pub user_display_name: String, + pub user_icon_url: String, + pub user_id: u64, + pub user_guid: String, +} + +impl IngComment { + pub fn format(self) -> String { + let content = self.content.extract_name().unwrap(); + // let icon_tag = self.icons.extract_tag().unwrap(); + format!( + "{name}:{content}{emoji:>4}{time}[#{id}]", + name = self.user_display_name.blue(), + content = content, + emoji = "📎", + time = self.date_added.as_time_age(), + id = self.id.bright_green(), + ) + } +} + +#[derive(Debug, Serialize)] +pub struct IngDetail { + #[serde(flatten)] + pub status: IngInfo, + pub comments: Vec, +} + +impl IngDetail { + pub fn format(self) -> String { + let res = format!("{}\n", self.status.format()); + + self.comments.into_iter().fold(res, |mut acc, x| { + acc = format!("{}{:>4}{}\n", acc, "", x.format()); + acc + }) + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..ff4f7e3 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,41 @@ +use owo_colors::OwoColorize; +use serde::Deserialize; + +pub mod ing; +pub mod user; + +#[derive(Debug, Deserialize, Default)] +#[serde(rename_all = "PascalCase")] +pub struct NoParseResult { + #[serde(skip)] + pub status_code: u16, + #[serde(skip)] + pub is_success: bool, + // pub target_site: String, + pub message: String, + // pub data: String, + // pub inner_exception: String, + // pub help_link: String, + // pub source: String, + // pub h_result: u64, + // pub stack_trace: String, +} + +impl NoParseResult { + pub fn into_format(self) -> String { + if self.is_success { + format!( + "[{info}] {status_code}", + info = "Ok".bright_green(), + status_code = self.status_code, + ) + } else { + format!( + "[{info}] {status_code}: {msg}", + info = "Error".red(), + status_code = self.status_code, + msg = self.message + ) + } + } +} diff --git a/src/models/user.rs b/src/models/user.rs new file mode 100644 index 0000000..bb8681d --- /dev/null +++ b/src/models/user.rs @@ -0,0 +1,45 @@ +use owo_colors::OwoColorize; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct UserInfo { + pub user_id: String, + #[serde(rename = "SpaceUserID")] + pub space_user_id: u64, + pub account_id: u64, + pub blog_id: u64, + pub display_name: String, + pub face: String, + pub avatar: String, + pub seniority: String, + pub blog_app: String, + pub following_count: u64, + pub follower_count: u64, + pub is_vip: bool, + pub joined: String, +} + +impl UserInfo { + /// 提取公共的用户信息格式化逻辑 + pub fn format_user_info(&self) -> String { + let mut info = Vec::new(); + + info.push(if self.is_vip { + format!("{}[VIP]", self.display_name).red().to_string() + } else { + self.display_name.to_string().blue().to_string() + }); + + info.push(format!("ID:{}", self.account_id)); + info.push(format!("加入时间:{}", self.joined)); + info.push(format!("博客:https://www.cnblogs.com/{}", self.blog_app)); + info.push("📊 数据统计".into()); + info.push(format!("├─ 关注:{} 人 ", self.following_count)); + info.push(format!("├─ 粉丝:{} 人 ", self.follower_count)); + if !self.seniority.is_empty() { + info.push(format!("└─ 园龄:{}", self.seniority)); + } + info.join("\n") + } +} diff --git a/src/tools/http.rs b/src/tools/http.rs new file mode 100644 index 0000000..6c28e90 --- /dev/null +++ b/src/tools/http.rs @@ -0,0 +1,77 @@ +use anyhow::Result; +// use owo_colors::OwoColorize; +use reqwest::Response; + +use crate::models::NoParseResult; + +// #[allow(async_fn_in_trait)] +// pub trait ResponseExt { +// async fn ensure_success(self) -> Result; +// } + +// impl ResponseExt for Response { +// async fn ensure_success(self) -> Result { +// let status = self.status(); + +// if status.is_success() { +// Ok(self) +// } else { +// let body = self.text().await?; +// anyhow::bail!( +// "HTTP Error: status={} {}, body: {}", +// status.as_u16(), +// status.canonical_reason().unwrap_or("Unknown"), +// body +// ) +// } +// } +// } + +// #[allow(async_fn_in_trait)] +// pub trait ResponseWrite { +// async fn write_to_stdout(self) -> Result<()>; +// } + +// impl ResponseWrite for Response { +// async fn write_to_stdout(self) -> Result<()> { +// let status = self.status(); +// if status.is_success() { +// println!("{}", format!("Ok: {}", status).bright_green()); +// } else { +// let body = self.text().await?; +// println!( +// "{}", +// format!( +// "HTTP Error: status={} {}, body: {}", +// status.as_u16(), +// status.canonical_reason().unwrap_or("Unknown"), +// body +// ) +// .red() +// ); +// } +// Ok(()) +// } +// } + +#[allow(async_fn_in_trait)] +pub trait IntoNoParseResult { + async fn into_no_parse_result(self) -> Result; +} + +impl IntoNoParseResult for Response { + async fn into_no_parse_result(self) -> Result { + let mut res = NoParseResult::default(); + if self.status().is_success() { + res.status_code = self.status().as_u16(); + res.is_success = self.status().is_success(); + } else { + let status = self.status(); + res = self.json().await?; + res.status_code = status.as_u16(); + res.is_success = status.is_success(); + } + + Ok(res) + } +} diff --git a/src/tools/mod.rs b/src/tools/mod.rs new file mode 100644 index 0000000..dbdf916 --- /dev/null +++ b/src/tools/mod.rs @@ -0,0 +1,15 @@ +pub mod http; +pub mod strings; +pub mod timer; + +use anyhow::Result; + +pub trait IntoAnyhowResult { + fn into_anyhow_result(self) -> Result; +} + +impl IntoAnyhowResult for reqwest::Result { + fn into_anyhow_result(self) -> Result { + Ok(self?) + } +} diff --git a/src/tools/strings.rs b/src/tools/strings.rs new file mode 100644 index 0000000..1240162 --- /dev/null +++ b/src/tools/strings.rs @@ -0,0 +1,80 @@ +use anyhow::{Result, anyhow}; +use owo_colors::OwoColorize; + +// 预编译正则表达式以提升性能 +lazy_static::lazy_static! { + pub static ref AT_PEOPLE_REGEX: regex::Regex = + regex::Regex::new(r"]*>([^<]*)") + .expect("无效的 @ 人员正则表达式"); + + pub static ref ICON_ALT_REGEX: regex::Regex = + regex::Regex::new(r#"]*alt\s*=\s*["']([^"']*)["'][^>]*>"#) + .expect("无效的图标正则表达式"); +} +/// 如果文本中还有a标签链接,则进一步处理,将超链接替换成链接内容, +/// 主要是针对@xxx情况。 +pub trait ExtractAtPeople { + fn extract_name(self) -> Result; +} + +impl ExtractAtPeople for String { + fn extract_name(self) -> Result { + // 使用预编译的正则表达式 + let re = &AT_PEOPLE_REGEX; + + if !re.is_match(&self) { + return Ok(self); + } + + let mut last_end = 0; + let mut output = Self::with_capacity(self.len()); + + // 更高效的单次遍历替换 + for caps in re.captures_iter(&self) { + let m = caps.get(0).ok_or_else(|| anyhow!("未找到匹配项"))?; + let link_text = caps.get(1).ok_or_else(|| anyhow!("未找到链接文本"))?; + + // 添加匹配前的文本 + output.push_str(&self[last_end..m.start()]); + + // 添加着色后的链接文本 + output.push_str(&link_text.as_str().blue().to_string()); + + last_end = m.end(); + } + + // 添加最后一段文本 + if last_end < self.len() { + output.push_str(&self[last_end..]); + } + + Ok(output) + } +} + +pub trait ExtractIconAlt { + fn extract_tag(self) -> Result; +} + +impl ExtractIconAlt for String { + fn extract_tag(self) -> Result { + // 使用预编译的正则表达式 + let re = &ICON_ALT_REGEX; + + if let Some(caps) = re.captures(&self) { + let alt_text = caps + .get(1) + .ok_or_else(|| anyhow!("未找到 alt 文本"))? + .as_str(); + + // 根据内容决定颜色 + if alt_text.contains("VIP") { + Ok(format!("{}", alt_text.red())) + } else { + Ok(format!("{}", alt_text.bright_yellow())) + } + } else { + Ok(Self::new()) + } + } +} diff --git a/src/tools/timer.rs b/src/tools/timer.rs new file mode 100644 index 0000000..993ac3d --- /dev/null +++ b/src/tools/timer.rs @@ -0,0 +1,24 @@ +use chrono::{Datelike, NaiveDateTime}; + +pub trait DateFormatExt { + fn as_time_age(&self) -> String; +} + +impl DateFormatExt for NaiveDateTime { + fn as_time_age(&self) -> String { + let now = chrono::Local::now().naive_local(); + let year = now.year() - self.year(); + if year.ge(&1) { + return self.format("[%y年%m月%d日%H:%M]").to_string(); + }; + + let seconds = (now - *self).num_seconds(); + match seconds { + secs if secs < 60 => format!("[{}秒前]", secs), + secs if secs < 3600 => format!("[{}分钟前]", secs / 60), + secs if secs < 86400 => format!("[{}小时前]", secs / 3600), + secs if secs < 604800 => format!("[{}天前]", secs / 86400), + _ => self.format("[%y年%m月%d日 %H:%M]").to_string(), + } + } +} From c07f7bc422cc025f52da43a38b1885882d58f8f7 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Sat, 17 Jan 2026 21:39:01 +0800 Subject: [PATCH 25/48] refactor: complete auth commands - use `auth` command replace `user` --- src/apis/ing/comment.rs | 4 ++- src/commands/auth.rs | 8 +++--- src/context/mod.rs | 30 ++++++++++++--------- src/logic/auth.rs | 60 +++++++++++++++++++++++++++++++++++++++++ src/logic/mod.rs | 10 ++++--- 5 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 src/logic/auth.rs diff --git a/src/apis/ing/comment.rs b/src/apis/ing/comment.rs index e6c5fb8..d0897c7 100644 --- a/src/apis/ing/comment.rs +++ b/src/apis/ing/comment.rs @@ -5,7 +5,9 @@ use anyhow::{Ok, Result}; use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; -use crate::{api_bak::ing::get_comment_list::IngCommentEntry, infra::http::RequestBuilderExt, openapi}; +use crate::{ + api_bak::ing::get_comment_list::IngCommentEntry, infra::http::RequestBuilderExt, openapi, +}; /// 闪存评论及评论回复 /// diff --git a/src/commands/auth.rs b/src/commands/auth.rs index 31bd075..33f92bb 100644 --- a/src/commands/auth.rs +++ b/src/commands/auth.rs @@ -10,13 +10,15 @@ pub struct Authenticate { pub commands: AuthenticateSubCommands, } -/// 认证子命令 -/// -/// +/// 提供通过access token登录,状态查询,退出,显示当前token功能 #[derive(Debug, Subcommand)] pub enum AuthenticateSubCommands { + /// 用户登录,需提供access token。 Login { token: String }, + /// 用户退出 Logout, + /// 查看登录状态,登录后会显示用户信息 Status, + /// 显示当前登录token Token, } diff --git a/src/context/mod.rs b/src/context/mod.rs index 52b8985..b86a62f 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -3,11 +3,11 @@ pub mod output; use core::time; use std::{fmt, fs, io::Read, path::PathBuf}; -use anyhow::{anyhow, Ok, Result}; +use anyhow::{Ok, Result, anyhow}; use owo_colors::OwoColorize; use reqwest::{ - header::{self, HeaderMap}, Client, - ClientBuilder, + Client, ClientBuilder, + header::{self, HeaderMap}, }; use crate::context::output::Terminal; @@ -26,11 +26,11 @@ pub struct Context { } impl Context { - pub fn new() -> anyhow::Result { + pub fn new() -> Result { Self::new_with_token("".to_string()) } - pub fn new_with_token(token: String) -> anyhow::Result { + pub fn new_with_token(token: String) -> Result { let mut terminal = Terminal::new(); let mut token = token; let home_dir = home::home_dir().ok_or_else(|| anyhow!("未获取到家目录,退出。"))?; @@ -87,7 +87,7 @@ impl Context { self.json = json; } - pub fn update_auth_header(&mut self) -> anyhow::Result<()> { + pub fn update_auth_header(&mut self) -> Result<()> { let header_value = format!("Bearer {}", self.token); self.headers .insert(header::AUTHORIZATION, header_value.parse()?); @@ -99,16 +99,20 @@ impl Context { Ok(()) } - pub fn update_token(&mut self, token: String) -> anyhow::Result<()> { - if !token.is_empty() { - self.token = token; - self.update_auth_header()?; - self.update_cache_file()?; - } - Ok(()) + pub fn update_token(&mut self, token: String) -> Result<()> { + // if !token.is_empty() { + self.token = token; + self.update_auth_header()?; + self.update_cache_file() + // } + // Ok(()) } pub fn print_message(&mut self, msg: T) -> Result<()> { self.terminal.writeln(msg) } + + pub fn clean(&mut self) -> Result<()> { + self.update_token("".to_string()) + } } diff --git a/src/logic/auth.rs b/src/logic/auth.rs new file mode 100644 index 0000000..0af7993 --- /dev/null +++ b/src/logic/auth.rs @@ -0,0 +1,60 @@ +//! +//! 认证模块 +//! + +use anyhow::Result; +use owo_colors::OwoColorize; +use reqwest::{StatusCode, header}; + +use crate::commands::auth::{Authenticate, AuthenticateSubCommands}; +use crate::context::Context; +use crate::tools::http::IntoNoParseResult; +use crate::{api, models}; + +pub async fn endpoint(cmd: Authenticate, ctx: &mut Context) -> anyhow::Result<()> { + match cmd.commands { + AuthenticateSubCommands::Login { token } => handle_login(token, ctx).await, + AuthenticateSubCommands::Logout => handle_logout(ctx), + AuthenticateSubCommands::Status => user_info(ctx).await, + AuthenticateSubCommands::Token => handle_print_token(ctx), + } +} + +async fn handle_login(token: String, ctx: &mut Context) -> Result<()> { + let header_value = format!("Bearer {}", token); + ctx.headers + .insert(header::AUTHORIZATION, header_value.parse()?); + let resp = api::user::raw_user_info(&ctx.client).await?; + + if resp.status().eq(&StatusCode::UNAUTHORIZED) { + let _ = ctx + .terminal + .writeln(format!("Token `{}`错误。请输入正确的token。", token).red()); + } + + if resp.status().is_success() { + ctx.update_token(token)?; + let p = resp.json::().await?; + + ctx.terminal + .writeln(format!("🎉 欢迎,{}!", p.display_name))?; + } else { + let r = resp.into_no_parse_result().await?; + ctx.terminal.writeln(r.into_format())?; + } + Ok(()) +} + +fn handle_print_token(ctx: &mut Context) -> Result<()> { + ctx.terminal + .writeln(format!("[Token]: {}", ctx.token.bright_green())) +} + +async fn user_info(ctx: &mut Context) -> Result<()> { + let user = api::user::user_info(&ctx.client).await?; + ctx.terminal.writeln(user.format_user_info()) +} + +fn handle_logout(ctx: &mut Context) -> Result<()> { + ctx.clean() +} diff --git a/src/logic/mod.rs b/src/logic/mod.rs index dff3590..e0c6045 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -1,3 +1,4 @@ +//! //! cli操作逻辑 //! //! 此模块暂定封装操作逻辑,比如是闪存的curd,闪存评论的curd。 @@ -5,16 +6,17 @@ use anyhow::Result; -use crate::{ - commands::{Cli, Commands}, -}; +use crate::commands::{Cli, Commands}; use crate::context::Context; +pub mod auth; pub mod ing; pub async fn run(cli: Cli, ctx: &mut Context) -> Result<()> { match cli.commands { - Commands::Auth(_a) => {} + Commands::Auth(action) => { + auth::endpoint(action, ctx).await?; + } Commands::Fav => {} Commands::Ing { action } => ing::endpoint(action, ctx).await?, Commands::News => {} From f4b023bb24a8dd2e434e8fcffd57c0d9b1de8824 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Sat, 17 Jan 2026 19:21:52 +0800 Subject: [PATCH 26/48] fix: fix auth login logic - Fix login logic - Fix login url - Refactor context file check --- src/api/urls.rs | 2 +- src/commands/auth.rs | 7 ++++-- src/context/mod.rs | 51 ++++++++++++++++++++++++++------------------ src/logic/auth.rs | 13 ++++++----- 4 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/api/urls.rs b/src/api/urls.rs index a6c70dd..819707b 100644 --- a/src/api/urls.rs +++ b/src/api/urls.rs @@ -5,6 +5,6 @@ pub const OPENAPI: &str = "https://api.cnblogs.com/api"; pub const OAUTH: &str = "https://oauth.cnblogs.com"; // 修复:正确的参数数量 -pub const USER: &str = formatcp!("{}/user", OPENAPI); +pub const USER: &str = formatcp!("{}/users", OPENAPI); pub const STATUS: &str = formatcp!("{}/statuses/", OPENAPI); pub const COMMENTS_PATH: &str = "comments"; diff --git a/src/commands/auth.rs b/src/commands/auth.rs index 33f92bb..677a01c 100644 --- a/src/commands/auth.rs +++ b/src/commands/auth.rs @@ -2,7 +2,7 @@ //! 认证子命令 //! -use clap::{Args, Subcommand}; +use clap::{Args, Subcommand, builder::NonEmptyStringValueParser}; #[derive(Debug, Args)] pub struct Authenticate { @@ -14,7 +14,10 @@ pub struct Authenticate { #[derive(Debug, Subcommand)] pub enum AuthenticateSubCommands { /// 用户登录,需提供access token。 - Login { token: String }, + Login { + #[clap(value_parser = NonEmptyStringValueParser::new())] + token: String, + }, /// 用户退出 Logout, /// 查看登录状态,登录后会显示用户信息 diff --git a/src/context/mod.rs b/src/context/mod.rs index b86a62f..6d5d387 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -1,7 +1,12 @@ pub mod output; use core::time; -use std::{fmt, fs, io::Read, path::PathBuf}; +use std::{ + fmt, + fs::{self, File}, + io::{Read, Write}, + path::PathBuf, +}; use anyhow::{Ok, Result, anyhow}; use owo_colors::OwoColorize; @@ -14,6 +19,7 @@ use crate::context::output::Terminal; const FILENAME: &str = ".cnblogs/token"; +#[derive(Debug)] pub struct Context { pub terminal: Terminal, pub token: String, @@ -31,33 +37,19 @@ impl Context { } pub fn new_with_token(token: String) -> Result { - let mut terminal = Terminal::new(); + let terminal = Terminal::new(); let mut token = token; - let home_dir = home::home_dir().ok_or_else(|| anyhow!("未获取到家目录,退出。"))?; + let home_dir = + home::home_dir().ok_or_else(|| anyhow!("无法获取用户家目录,退出。".red()))?; let file = PathBuf::from(FILENAME); let full_path = home_dir.join(&file); + let mut cache = Self::ensure_file(full_path.clone())?; let mut headers = HeaderMap::new(); - if !full_path.exists() { - let _ = terminal.writeln(format!("缓存文件 `{}` 不存在", FILENAME).red()); - if !full_path - .parent() - .ok_or_else(|| anyhow!("检查`~/.cnblogs`文件夹失败"))? - .exists() - { - fs::create_dir_all( - full_path - .parent() - .ok_or_else(|| anyhow!("创建`~/.cnblogs`文件夹失败"))?, - )?; - } - let _ = fs::File::create(full_path.clone())?; - } - if token.is_empty() { - let _ = fs::File::open(full_path.clone())?.read_to_string(&mut token); + let _ = cache.read_to_string(&mut token); } else { - fs::write(full_path.clone(), token.as_bytes())?; + cache.write_all(token.as_bytes())?; } if !token.is_empty() { @@ -83,6 +75,23 @@ impl Context { }) } + pub fn ensure_file(full_path: PathBuf) -> Result { + let cnblogs = full_path + .parent() + .ok_or_else(|| anyhow!("获取`~/.cnblogs`文件夹失败, 退出。"))?; + + if !full_path.exists() { + if !cnblogs.exists() { + fs::create_dir_all(cnblogs)?; + } + Ok(fs::File::create(full_path.clone())?) + } else if full_path.exists() { + Ok(fs::File::open(full_path)?) + } else { + Ok(fs::File::create(full_path.clone())?) + } + } + pub const fn set_json(&mut self, json: bool) { self.json = json; } diff --git a/src/logic/auth.rs b/src/logic/auth.rs index 0af7993..4a3b0bf 100644 --- a/src/logic/auth.rs +++ b/src/logic/auth.rs @@ -4,7 +4,8 @@ use anyhow::Result; use owo_colors::OwoColorize; -use reqwest::{StatusCode, header}; +use reqwest::header::{AUTHORIZATION, HeaderMap}; +use reqwest::{ClientBuilder, StatusCode}; use crate::commands::auth::{Authenticate, AuthenticateSubCommands}; use crate::context::Context; @@ -22,9 +23,11 @@ pub async fn endpoint(cmd: Authenticate, ctx: &mut Context) -> anyhow::Result<() async fn handle_login(token: String, ctx: &mut Context) -> Result<()> { let header_value = format!("Bearer {}", token); - ctx.headers - .insert(header::AUTHORIZATION, header_value.parse()?); - let resp = api::user::raw_user_info(&ctx.client).await?; + let mut header = HeaderMap::new(); + header.insert(AUTHORIZATION, header_value.parse()?); + + let client = ClientBuilder::new().default_headers(header).build()?; + let resp = api::user::raw_user_info(&client).await?; if resp.status().eq(&StatusCode::UNAUTHORIZED) { let _ = ctx @@ -37,7 +40,7 @@ async fn handle_login(token: String, ctx: &mut Context) -> Result<()> { let p = resp.json::().await?; ctx.terminal - .writeln(format!("🎉 欢迎,{}!", p.display_name))?; + .writeln(format!("🎉 欢迎,{}!", p.display_name.bright_green()))?; } else { let r = resp.into_no_parse_result().await?; ctx.terminal.writeln(r.into_format())?; From 04cb01f2a91b9e10e4a78be7cac7c3247d640364 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Sat, 17 Jan 2026 19:25:26 +0800 Subject: [PATCH 27/48] refactor: introduce IntoAnyhowResult trait for error conversion --- src/api/ing.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/api/ing.rs b/src/api/ing.rs index e5136f0..ffb9eb5 100644 --- a/src/api/ing.rs +++ b/src/api/ing.rs @@ -12,6 +12,7 @@ use serde_json::json; use crate::{ api::urls::{COMMENTS_PATH, STATUS}, models::ing::{IngComment, IngDetail, IngInfo}, + tools::IntoAnyhowResult, }; /// 创建闪存 @@ -36,11 +37,19 @@ pub async fn get_status_with_comment(c: &Client, status: IngInfo) -> Result Result { - Ok(raw_get_status(c, id).await?.json().await?) + raw_get_status(c, id) + .await? + .json() + .await + .into_anyhow_result() } pub async fn list_comments(c: &Client, id: u64) -> Result> { - Ok(raw_list_comments(c, id).await?.json().await?) + raw_list_comments(c, id) + .await? + .json() + .await + .into_anyhow_result() } pub async fn list_statuses( @@ -49,12 +58,12 @@ pub async fn list_statuses( params: impl Serialize + Send + Sync, ) -> Result> { let resp = raw_list_statuses(c, path, params).await?; - Ok(resp.error_for_status()?.json().await?) + resp.error_for_status()?.json().await.into_anyhow_result() } pub async fn raw_list_comments(c: &Client, id: u64) -> Result { let url = format!("{}{}/{}", STATUS, id, COMMENTS_PATH); - Ok(c.get(url).send().await?) + c.get(url).send().await.into_anyhow_result() } pub async fn raw_list_statuses( @@ -63,7 +72,7 @@ pub async fn raw_list_statuses( params: impl Serialize + Send + Sync, ) -> Result { let url = format!("{}@{}", STATUS, path); - Ok(c.get(url).query(¶ms).send().await?) + c.get(url).query(¶ms).send().await.into_anyhow_result() } pub async fn raw_create_status( @@ -71,18 +80,18 @@ pub async fn raw_create_status( content: impl Serialize + Send + Sync, ) -> Result { let url = STATUS.to_string(); - Ok(c.post(url).json(&content).send().await?) + c.post(url).json(&content).send().await.into_anyhow_result() } pub async fn raw_create_comment(c: &Client, id: u64, content: String) -> Result { let url = format!("{}/{}/{}", STATUS, id, COMMENTS_PATH); let res = json!({"content": content}); - Ok(c.post(url).json(&res).send().await?) + c.post(url).json(&res).send().await.into_anyhow_result() } pub async fn raw_delete_status(c: &Client, id: u64) -> Result { let url = format!("{}{}", STATUS, id); - Ok(c.delete(url).send().await?) + c.delete(url).send().await.into_anyhow_result() } pub async fn raw_delete_status_comment( @@ -91,10 +100,10 @@ pub async fn raw_delete_status_comment( comment_id: u64, ) -> Result { let url = format!("{}{}/{}/{}", STATUS, status_id, COMMENTS_PATH, comment_id); - Ok(c.delete(url).send().await?) + c.delete(url).send().await.into_anyhow_result() } pub async fn raw_get_status(c: &Client, id: u64) -> Result { let url = format!("{}{}", STATUS, id); - Ok(c.get(url).send().await?) + c.get(url).send().await.into_anyhow_result() } From 197e1833e7eba06d27afccaaaea519043fd06f4b Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Mon, 19 Jan 2026 00:35:10 +0800 Subject: [PATCH 28/48] feat: add cache data structure --- src/context/config.rs | 136 ++++++++++++++++++++++++++++++++++++++++++ src/context/mod.rs | 108 +++++++++------------------------ src/logic/auth.rs | 12 ++-- 3 files changed, 173 insertions(+), 83 deletions(-) create mode 100644 src/context/config.rs diff --git a/src/context/config.rs b/src/context/config.rs new file mode 100644 index 0000000..d3c6b46 --- /dev/null +++ b/src/context/config.rs @@ -0,0 +1,136 @@ +use std::fs::File; +use std::{ + fs, + io::{Read, Write}, + path::PathBuf, +}; + +use anyhow::{Result, anyhow}; +use owo_colors::OwoColorize; +use serde::{Deserialize, Serialize}; + +use crate::models::user::UserInfo; + +const CACHE_DIR: &str = ".cnblogs"; +const CACHE: &str = "token"; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Cache { + pub id: u64, + pub blog_id: u64, + pub blog_app: String, + pub username: String, + pub token: String, +} + +impl Cache { + pub fn from_bytes(buf: &[u8]) -> Result { + Ok(serde_json::from_slice(buf)?) + } + + pub fn to_bytes(&self) -> Result> { + Ok(serde_json::to_vec(self)?) + } + + /// 检查 token 是否为空 + pub fn is_token_empty(&self) -> bool { + self.token.trim().is_empty() + } + + /// 验证缓存数据的有效性 + pub fn is_valid(&self) -> bool { + !self.is_token_empty() && self.id > 0 + } +} + +impl From for Cache { + fn from(value: UserInfo) -> Self { + Self { + id: value.account_id, + blog_id: value.blog_id, + blog_app: value.blog_app, + username: value.display_name, + token: "".to_string(), + } + } +} + +#[derive(Debug)] +pub struct CacheDir { + pub cache_dir: PathBuf, + pub cache_file: PathBuf, + pub home_dir: PathBuf, +} + +impl CacheDir { + pub fn new() -> Result { + let home_dir = + home::home_dir().ok_or_else(|| anyhow!("无法获取用户家目录,退出。".red()))?; + let cache_dir = PathBuf::from(CACHE_DIR); + let cache_file = PathBuf::from(CACHE); + + Ok(Self { + cache_dir, + cache_file, + home_dir, + }) + } + + /// 初始化,检查文件夹和目录是否存在,如果不存在则创建 + pub fn init(&self) -> Result<()> { + self.ensure_dir()?; + self.ensure_file() + } + + /// 获取缓存目录的完整路径 + pub fn full_cache_dir(&self) -> PathBuf { + self.home_dir.join(&self.cache_dir) + } + + /// 获取缓存文件的完整路径 + pub fn full_cache_file(&self) -> PathBuf { + self.full_cache_dir().join(&self.cache_file) + } + + /// 检查缓存目录是否存在,不存在创建。 + pub fn ensure_dir(&self) -> Result<()> { + let p = self.full_cache_dir(); + if !p.exists() { + fs::create_dir_all(p)?; + } + Ok(()) + } + + /// 检查缓存文件是否存在,不存在创建。 + pub fn ensure_file(&self) -> Result<()> { + let p = self.full_cache_file(); + if !p.exists() { + fs::File::create(p)?; + } + Ok(()) + } + + /// 打开文件 + pub fn open_file(&self) -> Result { + Ok( + // Arc::new( + // RwLock::new( + fs::File::open(self.full_cache_file())?, // ), // ) + ) + } + + /// 写入缓存文件 + pub fn write(&self, buf: &[u8]) -> Result<()> { + let mut f = self.open_file()?; + f.write_all(buf)?; + Ok(()) + } + + /// 读取缓存文件 + pub fn read(&self) -> Result> { + let mut buf = vec![]; + let mut f = self.open_file()?; + f.read_to_end(&mut buf)?; + Ok(buf) + } +} diff --git a/src/context/mod.rs b/src/context/mod.rs index 6d5d387..9e2c4b8 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -1,127 +1,77 @@ +pub mod config; pub mod output; use core::time; -use std::{ - fmt, - fs::{self, File}, - io::{Read, Write}, - path::PathBuf, -}; +use std::fmt; -use anyhow::{Ok, Result, anyhow}; -use owo_colors::OwoColorize; +use anyhow::{Ok, Result}; use reqwest::{ Client, ClientBuilder, header::{self, HeaderMap}, }; -use crate::context::output::Terminal; +use crate::context::{ + config::{Cache, CacheDir}, + output::Terminal, +}; -const FILENAME: &str = ".cnblogs/token"; +// const FILENAME: &str = ".cnblogs/token"; #[derive(Debug)] pub struct Context { pub terminal: Terminal, - pub token: String, pub client: Client, - pub headers: HeaderMap, - pub home_dir: PathBuf, - pub file: PathBuf, - pub full_path: PathBuf, pub json: bool, + pub cache: Cache, + pub path: CacheDir, } impl Context { pub fn new() -> Result { - Self::new_with_token("".to_string()) - } - - pub fn new_with_token(token: String) -> Result { + let path = CacheDir::new()?; + path.init()?; + let buf = path.read()?; + let cache = Cache::from_bytes(&buf)?; let terminal = Terminal::new(); - let mut token = token; - let home_dir = - home::home_dir().ok_or_else(|| anyhow!("无法获取用户家目录,退出。".red()))?; - let file = PathBuf::from(FILENAME); - let full_path = home_dir.join(&file); - let mut cache = Self::ensure_file(full_path.clone())?; let mut headers = HeaderMap::new(); - if token.is_empty() { - let _ = cache.read_to_string(&mut token); - } else { - cache.write_all(token.as_bytes())?; - } - - if !token.is_empty() { - let header_value = format!("Bearer {token}"); + if !cache.token.is_empty() { + let header_value = format!("Bearer {}", cache.token); headers.append(header::AUTHORIZATION, header_value.parse()?); } let client = ClientBuilder::new() - .default_headers(headers.clone()) + .default_headers(headers) .connect_timeout(time::Duration::from_secs(10)) .https_only(true) .build()?; Ok(Self { terminal, - token, client, - headers, - home_dir, - file, - full_path, json: false, + cache, + path, }) } - pub fn ensure_file(full_path: PathBuf) -> Result { - let cnblogs = full_path - .parent() - .ok_or_else(|| anyhow!("获取`~/.cnblogs`文件夹失败, 退出。"))?; - - if !full_path.exists() { - if !cnblogs.exists() { - fs::create_dir_all(cnblogs)?; - } - Ok(fs::File::create(full_path.clone())?) - } else if full_path.exists() { - Ok(fs::File::open(full_path)?) - } else { - Ok(fs::File::create(full_path.clone())?) - } - } - pub const fn set_json(&mut self, json: bool) { self.json = json; } - pub fn update_auth_header(&mut self) -> Result<()> { - let header_value = format!("Bearer {}", self.token); - self.headers - .insert(header::AUTHORIZATION, header_value.parse()?); - Ok(()) - } - - pub fn update_cache_file(&self) -> Result<()> { - fs::write(&self.full_path, self.token.as_bytes())?; - Ok(()) - } - - pub fn update_token(&mut self, token: String) -> Result<()> { - // if !token.is_empty() { - self.token = token; - self.update_auth_header()?; - self.update_cache_file() - // } - // Ok(()) - } - pub fn print_message(&mut self, msg: T) -> Result<()> { self.terminal.writeln(msg) } - pub fn clean(&mut self) -> Result<()> { - self.update_token("".to_string()) + /// 清空缓存信息 + pub fn clean(&self) -> Result<()> { + let c = Cache::default(); + let buf = c.to_bytes()?; + self.path.write(&buf) + } + + /// 保存之缓存文件 + pub fn save_cache(&self, cache: Cache) -> Result<()> { + self.path.write(&cache.to_bytes()?) } } diff --git a/src/logic/auth.rs b/src/logic/auth.rs index 4a3b0bf..35dd859 100644 --- a/src/logic/auth.rs +++ b/src/logic/auth.rs @@ -9,6 +9,7 @@ use reqwest::{ClientBuilder, StatusCode}; use crate::commands::auth::{Authenticate, AuthenticateSubCommands}; use crate::context::Context; +use crate::context::config::Cache; use crate::tools::http::IntoNoParseResult; use crate::{api, models}; @@ -36,11 +37,14 @@ async fn handle_login(token: String, ctx: &mut Context) -> Result<()> { } if resp.status().is_success() { - ctx.update_token(token)?; let p = resp.json::().await?; + let name = p.display_name.clone(); + + let c: Cache = p.into(); + ctx.save_cache(c)?; ctx.terminal - .writeln(format!("🎉 欢迎,{}!", p.display_name.bright_green()))?; + .writeln(format!("🎉 欢迎,{}!", name.bright_green()))?; } else { let r = resp.into_no_parse_result().await?; ctx.terminal.writeln(r.into_format())?; @@ -50,7 +54,7 @@ async fn handle_login(token: String, ctx: &mut Context) -> Result<()> { fn handle_print_token(ctx: &mut Context) -> Result<()> { ctx.terminal - .writeln(format!("[Token]: {}", ctx.token.bright_green())) + .writeln(format!("[Token]: {}", ctx.cache.token.bright_green())) } async fn user_info(ctx: &mut Context) -> Result<()> { @@ -58,6 +62,6 @@ async fn user_info(ctx: &mut Context) -> Result<()> { ctx.terminal.writeln(user.format_user_info()) } -fn handle_logout(ctx: &mut Context) -> Result<()> { +fn handle_logout(ctx: &Context) -> Result<()> { ctx.clean() } From 284cd34f83b28bb1ee11b64f6884656652b61c6b Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 20 Jan 2026 04:03:52 +0800 Subject: [PATCH 29/48] fix: save token to cache file --- src/context/config.rs | 14 +++----------- src/context/mod.rs | 3 ++- src/logic/auth.rs | 5 ++++- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/context/config.rs b/src/context/config.rs index d3c6b46..f632695 100644 --- a/src/context/config.rs +++ b/src/context/config.rs @@ -15,6 +15,7 @@ const CACHE_DIR: &str = ".cnblogs"; const CACHE: &str = "token"; #[derive(Debug, Serialize, Deserialize, Default)] +#[serde(default)] pub struct Cache { pub id: u64, pub blog_id: u64, @@ -110,18 +111,9 @@ impl CacheDir { Ok(()) } - /// 打开文件 - pub fn open_file(&self) -> Result { - Ok( - // Arc::new( - // RwLock::new( - fs::File::open(self.full_cache_file())?, // ), // ) - ) - } - /// 写入缓存文件 pub fn write(&self, buf: &[u8]) -> Result<()> { - let mut f = self.open_file()?; + let mut f = File::create(self.full_cache_file())?; f.write_all(buf)?; Ok(()) } @@ -129,7 +121,7 @@ impl CacheDir { /// 读取缓存文件 pub fn read(&self) -> Result> { let mut buf = vec![]; - let mut f = self.open_file()?; + let mut f = File::open(self.full_cache_file())?; f.read_to_end(&mut buf)?; Ok(buf) } diff --git a/src/context/mod.rs b/src/context/mod.rs index 9e2c4b8..22dc400 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -31,13 +31,14 @@ impl Context { let path = CacheDir::new()?; path.init()?; let buf = path.read()?; - let cache = Cache::from_bytes(&buf)?; + let cache = Cache::from_bytes(&buf).unwrap_or_default(); let terminal = Terminal::new(); let mut headers = HeaderMap::new(); if !cache.token.is_empty() { let header_value = format!("Bearer {}", cache.token); headers.append(header::AUTHORIZATION, header_value.parse()?); + headers.append("authorization-type", "pat".parse()?); } let client = ClientBuilder::new() diff --git a/src/logic/auth.rs b/src/logic/auth.rs index 35dd859..b8bf01f 100644 --- a/src/logic/auth.rs +++ b/src/logic/auth.rs @@ -40,7 +40,8 @@ async fn handle_login(token: String, ctx: &mut Context) -> Result<()> { let p = resp.json::().await?; let name = p.display_name.clone(); - let c: Cache = p.into(); + let mut c: Cache = p.into(); + c.token = token; ctx.save_cache(c)?; ctx.terminal @@ -59,6 +60,8 @@ fn handle_print_token(ctx: &mut Context) -> Result<()> { async fn user_info(ctx: &mut Context) -> Result<()> { let user = api::user::user_info(&ctx.client).await?; + let c: Cache = user.clone().into(); + ctx.save_cache(c)?; ctx.terminal.writeln(user.format_user_info()) } From 11520a16b91fa44300f6fdd7ed6af93cbaadaf00 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 20 Jan 2026 04:08:07 +0800 Subject: [PATCH 30/48] refactor: improve list and show for post subcommands --- Cargo.lock | 511 ++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/api/mod.rs | 1 + src/api/post.rs | 52 +++++ src/api/urls.rs | 5 +- src/commands/post.rs | 43 +++- src/logic/mod.rs | 9 +- src/logic/post.rs | 51 +++++ src/models/ing.rs | 2 +- src/models/mod.rs | 7 +- src/models/post.rs | 121 ++++++++++ 11 files changed, 785 insertions(+), 19 deletions(-) create mode 100644 src/api/post.rs create mode 100644 src/logic/post.rs create mode 100644 src/models/post.rs diff --git a/Cargo.lock b/Cargo.lock index 3a9ac02..c2a93e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,10 +259,11 @@ dependencies = [ "futures", "getrandom 0.3.4", "home", + "html2md", "lazy_static", "mime", "owo-colors", - "rand", + "rand 0.9.2", "regex", "reqwest", "serde", @@ -270,9 +271,10 @@ dependencies = [ "serde_qs", "serde_repr", "serde_with", + "termimad", "terminal_size", "tokio", - "unicode-width", + "unicode-width 0.2.2", "words-count", ] @@ -321,6 +323,24 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "coolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980c2afde4af43d6a05c5be738f9eae595cff86dce1f38f88b95058a98c027f3" +dependencies = [ + "crossterm", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -347,6 +367,115 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crokey" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51360853ebbeb3df20c76c82aecf43d387a62860f1a59ba65ab51f00eea85aad" +dependencies = [ + "crokey-proc_macros", + "crossterm", + "once_cell", + "serde", + "strict", +] + +[[package]] +name = "crokey-proc_macros" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf1a727caeb5ee5e0a0826a97f205a9cf84ee964b0b48239fef5214a00ae439" +dependencies = [ + "crossterm", + "proc-macro2", + "quote", + "strict", + "syn", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "darling" version = "0.21.3" @@ -392,6 +521,28 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -403,6 +554,15 @@ dependencies = [ "syn", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dunce" version = "1.0.5" @@ -467,6 +627,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.31" @@ -635,6 +805,34 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "html2md" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cff9891f2e0d9048927fbdfc28b11bf378f6a93c7ba70b23d0fbee9af6071b4" +dependencies = [ + "html5ever", + "jni 0.19.0", + "lazy_static", + "markup5ever_rcdom", + "percent-encoding", + "regex", +] + +[[package]] +name = "html5ever" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "http" version = "1.4.0" @@ -921,6 +1119,20 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", +] + [[package]] name = "jni" version = "0.21.1" @@ -963,6 +1175,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-regex" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5c13b6857ade4c8ee05c3c3dc97d2ab5415d691213825b90d3211c425c1f907" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a95c68db5d41694cea563c86a4ba4dc02141c16ef64814108cb23def4d5438" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -987,6 +1222,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -1008,6 +1249,38 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + [[package]] name = "memchr" version = "2.7.6" @@ -1020,6 +1293,15 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimad" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b688969b16915f3ecadc7829d5b7779dee4977e503f767f34136803d5c06f" +dependencies = [ + "once_cell", +] + [[package]] name = "mio" version = "1.1.1" @@ -1027,10 +1309,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "num-conv" version = "0.1.0" @@ -1099,6 +1388,44 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1135,6 +1462,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro2" version = "1.0.105" @@ -1174,7 +1507,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand", + "rand 0.9.2", "ring", "rustc-hash", "rustls", @@ -1215,6 +1548,15 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" @@ -1222,7 +1564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.5", ] [[package]] @@ -1232,9 +1574,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rand_core" version = "0.9.5" @@ -1363,6 +1711,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.3" @@ -1420,7 +1777,7 @@ checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", - "jni", + "jni 0.21.1", "log", "once_cell", "rustls", @@ -1534,6 +1891,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -1648,6 +2011,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -1658,6 +2042,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.11" @@ -1686,6 +2076,37 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strict" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42444fea5b87a39db4218d9422087e66a85d0e7a0963a439b07bcdf91804006" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1750,6 +2171,33 @@ dependencies = [ "libc", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termimad" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "889a9370996b74cf46016ce35b96c248a9ac36d69aab1d112b3e09bc33affa49" +dependencies = [ + "coolor", + "crokey", + "crossbeam", + "lazy-regex", + "minimad", + "serde", + "thiserror 2.0.17", + "unicode-width 0.1.14", +] + [[package]] name = "terminal_size" version = "0.4.3" @@ -1989,6 +2437,18 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-width" version = "0.2.2" @@ -2019,6 +2479,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2152,6 +2618,22 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -2161,6 +2643,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -2483,6 +2971,17 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "xml5ever" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" +dependencies = [ + "log", + "mac", + "markup5ever", +] + [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 2d4e038..615224f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,8 @@ anstream = "0.6.21" anstyle = "1.0.13" owo-colors = "4.2.3" const_format = "0.2.35" +html2md = "0.2.15" +termimad = "0.34.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/api/mod.rs b/src/api/mod.rs index a7cad72..11da013 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -5,6 +5,7 @@ //! pub mod ing; +pub mod post; pub mod urls; pub mod user; diff --git a/src/api/post.rs b/src/api/post.rs new file mode 100644 index 0000000..47c3015 --- /dev/null +++ b/src/api/post.rs @@ -0,0 +1,52 @@ +use anyhow::Result; +use reqwest::{Client, Response}; +use serde::Serialize; + +use crate::{ + api::urls::{BLOG_POST_PREFIX, POST_PREFIX}, + models::post::PostInfo, + tools::IntoAnyhowResult, +}; + +pub async fn list_someone_post( + c: &Client, + blog_app: &String, + params: &impl Serialize, +) -> Result> { + // let a = raw_list_someone_post(c, blog_app).await?; + // a.json().await.into_anyhow_result() + raw_list_someone_post(c, blog_app, params) + .await? + .json() + .await + .into_anyhow_result() +} + +pub async fn show_post_detail() {} + +/// 获取指定用户的随笔列表(支持分页) +/// +/// ## 参数 +/// +/// - `c`: 已初始化的HTTP客户端实例 +/// - `blog_app`: 博客用户的唯一标识(博客园中的博客别名) +/// - `params`: 查询参数,pageIndex和pageIndex两个参数,需要能实现serilize。 +/// +/// ## 查询参数要求 +/// +/// 参数应实现 `Serialize` trait,通常包含: +/// - `pageIndex`: u64 - 页码(从0或1开始,取决于API规范) +/// - `pageSize`: u64 - 每页记录数 +pub async fn raw_list_someone_post( + c: &Client, + blog_app: &String, + params: &impl Serialize, +) -> Result { + let url = format!("{}/{}/{}", POST_PREFIX, blog_app, "posts"); + c.get(url).query(params).send().await.into_anyhow_result() +} + +pub async fn raw_show_post(c: &Client, id: u64) -> Result { + let url = format!("{}/{}/body", BLOG_POST_PREFIX, id); + c.get(url).send().await.into_anyhow_result() +} diff --git a/src/api/urls.rs b/src/api/urls.rs index 819707b..55dd900 100644 --- a/src/api/urls.rs +++ b/src/api/urls.rs @@ -4,7 +4,10 @@ pub const BLOG_BACKEND: &str = "https://i.cnblogs.com/api"; 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 STATUS: &str = formatcp!("{}/statuses/", OPENAPI); pub const COMMENTS_PATH: &str = "comments"; + +pub const POST_PREFIX: &str = formatcp!("{}/blogs", OPENAPI); +pub const BLOG_POST_PREFIX: &str = formatcp!("{}/blogposts", OPENAPI); + diff --git a/src/commands/post.rs b/src/commands/post.rs index 70b547a..e6f5dbc 100644 --- a/src/commands/post.rs +++ b/src/commands/post.rs @@ -1,15 +1,50 @@ -use clap::{Args, Subcommand}; +use clap::{Args, Subcommand, builder::NonEmptyStringValueParser}; +use serde::Serialize; + +use crate::commands::validate_non_zero_id; #[derive(Debug, Args)] pub struct PostCommand { #[clap(subcommand)] - pub subcommands: PostAction, + pub commands: PostAction, } #[derive(Debug, Subcommand)] pub enum PostAction { Create, - List, + List(ListArgs), + Replay, + Show(ShowArgs), Update, - Show, +} + +/// 随笔列表,可根据博客名称获取。 +#[derive(Debug, Args, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ListArgs { + /// 博客名称,api接口的blog_app,默认当前用户,也可以指定。 + #[arg( + value_name = "Blog name", + help = "博客名称/标识符(输入时不能为空字符串)", + value_parser = NonEmptyStringValueParser::new() + )] + #[serde(skip)] + pub name: Option, + + /// 分页页码(从1开始) + #[arg(long = "page-index", default_value_t = 1)] + pub page_index: u64, + + /// 每页显示的条数,默认20 + #[arg(long = "page-size", default_value_t = 10)] + pub page_size: u64, +} + +#[derive(Debug, Args)] +pub struct ShowArgs { + #[clap(value_parser = validate_non_zero_id)] + pub id: u64, + + #[arg(long, defalut_value_t = false)] + pub comment: bool, } diff --git a/src/logic/mod.rs b/src/logic/mod.rs index e0c6045..af8ff62 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -4,14 +4,15 @@ //! 此模块暂定封装操作逻辑,比如是闪存的curd,闪存评论的curd。 //! +pub mod auth; +pub mod ing; +pub mod post; + use anyhow::Result; use crate::commands::{Cli, Commands}; use crate::context::Context; -pub mod auth; -pub mod ing; - pub async fn run(cli: Cli, ctx: &mut Context) -> Result<()> { match cli.commands { Commands::Auth(action) => { @@ -20,7 +21,7 @@ pub async fn run(cli: Cli, ctx: &mut Context) -> Result<()> { Commands::Fav => {} Commands::Ing { action } => ing::endpoint(action, ctx).await?, Commands::News => {} - Commands::Post(_p) => {} + Commands::Post(action) => post::endpoint(action, ctx).await?, } Ok(()) diff --git a/src/logic/post.rs b/src/logic/post.rs new file mode 100644 index 0000000..c0f943c --- /dev/null +++ b/src/logic/post.rs @@ -0,0 +1,51 @@ +use anyhow::Result; +use termimad::MadSkin; + +use crate::{ + api, + commands::post::{ListArgs, PostAction, PostCommand, ShowArgs}, + context::Context, +}; + +pub async fn endpoint(cmd: PostCommand, ctx: &mut Context) -> anyhow::Result<()> { + match cmd.commands { + PostAction::Create => handle(ctx).await, + PostAction::List(arg) => handle_list(arg, ctx).await, + PostAction::Replay => handle(ctx).await, + PostAction::Show(arg) => handle_show(arg, ctx).await, + PostAction::Update => handle(ctx).await, + } +} + +async fn handle(_ctx: &mut Context) -> Result<()> { + Ok(()) +} + +async fn handle_list(arg: ListArgs, ctx: &mut Context) -> Result<()> { + let name = if let Some(n) = &arg.name { + n + } else { + &ctx.cache.blog_app + }; + + let post = api::post::list_someone_post(&ctx.client, name, &arg).await?; + for i in post { + ctx.terminal.writeln(i.into_format())?; + } + Ok(()) +} + +async fn handle_show(arg: ShowArgs, ctx: &mut Context) -> Result<()> { + let resp = api::post::raw_show_post(&ctx.client, arg.id) + .await? + .text() + .await?; + + // TODO: 优化 + // 处理快平台 + let resp: String = serde_json::from_str(&resp)?; + let md = html2md::parse_html(&resp); + let mds = MadSkin::default_light(); + mds.write_text_on(&mut ctx.terminal.stdout, &md)?; + Ok(()) +} diff --git a/src/models/ing.rs b/src/models/ing.rs index 9b1d7c9..dc1eadd 100644 --- a/src/models/ing.rs +++ b/src/models/ing.rs @@ -16,7 +16,7 @@ pub struct IngInfo { pub is_private: bool, pub is_lucky: bool, pub comment_count: u64, - pub date_added: chrono::NaiveDateTime, + pub date_added: NaiveDateTime, pub user_alias: String, pub user_display_name: String, pub user_icon_url: String, diff --git a/src/models/mod.rs b/src/models/mod.rs index ff4f7e3..81fdbe3 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,9 +1,10 @@ -use owo_colors::OwoColorize; -use serde::Deserialize; - pub mod ing; +pub mod post; pub mod user; +use owo_colors::OwoColorize; +use serde::Deserialize; + #[derive(Debug, Deserialize, Default)] #[serde(rename_all = "PascalCase")] pub struct NoParseResult { diff --git a/src/models/post.rs b/src/models/post.rs new file mode 100644 index 0000000..2a30366 --- /dev/null +++ b/src/models/post.rs @@ -0,0 +1,121 @@ +use chrono::NaiveDateTime; +use owo_colors::OwoColorize; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default)] +#[serde(default)] +#[serde(rename_all = "PascalCase")] +pub struct PostInfo { + pub id: u64, + pub title: String, + pub url: String, + pub description: String, + pub author: String, + pub blog_app: String, + pub avatar: String, + pub post_date: NaiveDateTime, + pub view_count: u32, + pub comment_count: u32, + pub digg_count: u32, +} + +impl PostInfo { + pub fn into_format(self) -> String { + let view_count = if self.view_count != 0 { + format!("[阅读数:{}]", self.view_count) + } else { + "".to_string() + }; + + let comment_count = if self.comment_count != 0 { + format!("[评论数:{}]", self.comment_count) + } else { + "".to_string() + }; + + let digg_count = if self.digg_count != 0 { + format!("[点赞数:{}]", self.digg_count) + } else { + "".to_string() + }; + format!( + "[#{id}] {title}{space:>4}[{author}]{view_count}{comment_count}{digg_count}", + id = self.id.bright_green(), + title = self.title.bold().bright_magenta(), + author = self.author, + space = "", + // date = self.post_date + ) + } + + pub fn as_markdown_header(&self) -> String { + format!("# {title}\n", title = self.title) + } + + pub fn as_markdown_foot(&self) -> String { + format!( + "\n[阅读数:{vc}] [评论数:{cc}] [点赞数:{dc}] [创建时间:{time}]", + vc = self.view_count, + cc = self.comment_count, + dc = self.digg_count, + time = self.post_date, + ) + } +} + +pub struct PostContent { + pub info: PostInfo, + pub content: String, +} + +impl PostContent { + pub fn into_format(self) -> String { + let body = html2md::parse_html(&self.content); + + format!( + "{header}{body}{footer}", + header = self.info.as_markdown_header(), + footer = self.info.as_markdown_foot() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const A: &str = r#"[ + { + "Id": 19276632, + "Title": "Android 模拟器root权限", + "Url": "https://www.cnblogs.com/linga/p/19276632", + "Description": "前置: adb,Adnroid Studio Emulator,在命令行可执行,或者通过绝对路径执行 创建模拟器 首先,启动Android Studio并创建一个模拟器AVD(Android虚拟设备)。在创建AVD时请务必注意服务类型(Google Play Store,Google APIs,An", + "Author": "咕咚!", + "BlogApp": "linga", + "Avatar": "https://pic.cnblogs.com/face/1284904/20180727165140.png", + "PostDate": "2025-11-27T11:32:00", + "ViewCount": 83, + "CommentCount": 0, + "DiggCount": 0 + }, + { + "Id": 18193317, + "Title": "Docker 编译安装Nginx正向代理", + "Url": "https://www.cnblogs.com/linga/p/18193317", + "Description": "先记录一波正向代理 目录: . ├── Dockerfile ├── Dockerfile.bak ├── nginx-1.24.0.tar.gz ├── nginx.conf ├── openssl-3.3.0.tar.gz ├── pcre2-10.43.tar.bz2 ├── pcre-8.4", + "Author": "咕咚!", + "BlogApp": "linga", + "Avatar": "https://pic.cnblogs.com/face/1284904/20180727165140.png", + "PostDate": "2024-05-15T10:28:00", + "ViewCount": 256, + "CommentCount": 0, + "DiggCount": 0 + } +]"#; + + #[test] + fn test_parse() { + let a: Vec = serde_json::from_str(A).unwrap(); + assert_eq!(a[0].id, 19276632) + } +} From 188077dae4b36103fdc05459015c39a8c81dd1bf Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 20 Jan 2026 13:27:03 +0800 Subject: [PATCH 31/48] fix: thread safety for future sending and sync --- src/api/post.rs | 6 +++--- src/api/urls.rs | 1 - src/commands/post.rs | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/api/post.rs b/src/api/post.rs index 47c3015..d44e3d3 100644 --- a/src/api/post.rs +++ b/src/api/post.rs @@ -11,7 +11,7 @@ use crate::{ pub async fn list_someone_post( c: &Client, blog_app: &String, - params: &impl Serialize, + params: impl Serialize + Send + Sync, ) -> Result> { // let a = raw_list_someone_post(c, blog_app).await?; // a.json().await.into_anyhow_result() @@ -40,10 +40,10 @@ pub async fn show_post_detail() {} pub async fn raw_list_someone_post( c: &Client, blog_app: &String, - params: &impl Serialize, + params: impl Serialize + Send + Sync, ) -> Result { let url = format!("{}/{}/{}", POST_PREFIX, blog_app, "posts"); - c.get(url).query(params).send().await.into_anyhow_result() + c.get(url).query(¶ms).send().await.into_anyhow_result() } pub async fn raw_show_post(c: &Client, id: u64) -> Result { diff --git a/src/api/urls.rs b/src/api/urls.rs index 55dd900..a37922d 100644 --- a/src/api/urls.rs +++ b/src/api/urls.rs @@ -10,4 +10,3 @@ pub const COMMENTS_PATH: &str = "comments"; pub const POST_PREFIX: &str = formatcp!("{}/blogs", OPENAPI); pub const BLOG_POST_PREFIX: &str = formatcp!("{}/blogposts", OPENAPI); - diff --git a/src/commands/post.rs b/src/commands/post.rs index e6f5dbc..51c435b 100644 --- a/src/commands/post.rs +++ b/src/commands/post.rs @@ -44,7 +44,7 @@ pub struct ListArgs { pub struct ShowArgs { #[clap(value_parser = validate_non_zero_id)] pub id: u64, - - #[arg(long, defalut_value_t = false)] + + #[arg(long, default_value_t = false)] pub comment: bool, } From 9f0f2791d89587df599d3815399ef4a776cbbbb4 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 01:56:13 +0800 Subject: [PATCH 32/48] refactor(post): add show and replay subcommands --- src/api/post.rs | 45 +++++++++++++++++++++++++++++ src/api/urls.rs | 1 + src/commands/post.rs | 68 ++++++++++++++++++++++++++++++++++++++------ src/logic/post.rs | 42 +++++++++++++++++++++------ 4 files changed, 140 insertions(+), 16 deletions(-) diff --git a/src/api/post.rs b/src/api/post.rs index d44e3d3..7b4e4ec 100644 --- a/src/api/post.rs +++ b/src/api/post.rs @@ -50,3 +50,48 @@ pub async fn raw_show_post(c: &Client, id: u64) -> Result { let url = format!("{}/{}/body", BLOG_POST_PREFIX, id); c.get(url).send().await.into_anyhow_result() } + +/// 获取指定id的随笔评论 +/// +/// ## 参数 +/// +/// - `c`: 已初始化的HTTP客户端实例 +/// - `blog_app`: 博客园的博客名称 +/// - `id`: 随笔ID +pub async fn raw_list_comments( + c: &Client, + blog_app: &String, + id: u64, + params: impl Serialize + Send + Sync, +) -> Result { + c.get(gen_comments_url(blog_app, id)) + .query(¶ms) + .send() + .await + .into_anyhow_result() +} + +pub async fn raw_create_comment( + c: &Client, + blog_app: &String, + id: u64, + params: impl Serialize + Send + Sync, +) -> Result { + c.post(gen_comments_url(blog_app, id)) + .json(¶ms) + .send() + .await + .into_anyhow_result() +} + +// pub async fn raw_post_detail(c: &Client, id: u64) -> Result { +// let url = format!("{}/{}", BLOG_BACKEND_POST, id); +// c.get(url).send().await.into_anyhow_result() +// } + +fn gen_comments_url(blog_app: &String, id: u64) -> String { + format!( + "{}/{}/{}/{}/{}", + POST_PREFIX, blog_app, "posts", id, "comments" + ) +} diff --git a/src/api/urls.rs b/src/api/urls.rs index a37922d..da7b75f 100644 --- a/src/api/urls.rs +++ b/src/api/urls.rs @@ -10,3 +10,4 @@ pub const COMMENTS_PATH: &str = "comments"; pub const POST_PREFIX: &str = formatcp!("{}/blogs", OPENAPI); pub const BLOG_POST_PREFIX: &str = formatcp!("{}/blogposts", OPENAPI); +pub const BLOG_BACKEND_POST: &str = formatcp!("{}/potst", BLOG_BACKEND); diff --git a/src/commands/post.rs b/src/commands/post.rs index 51c435b..d453031 100644 --- a/src/commands/post.rs +++ b/src/commands/post.rs @@ -11,11 +11,11 @@ pub struct PostCommand { #[derive(Debug, Subcommand)] pub enum PostAction { - Create, + // Create, List(ListArgs), - Replay, + Replay(ReplayArgs), Show(ShowArgs), - Update, + // Update, } /// 随笔列表,可根据博客名称获取。 @@ -25,7 +25,7 @@ pub struct ListArgs { /// 博客名称,api接口的blog_app,默认当前用户,也可以指定。 #[arg( value_name = "Blog name", - help = "博客名称/标识符(输入时不能为空字符串)", + help = "博客名称,api接口的blog_app,默认当前用户,也可以指定。", value_parser = NonEmptyStringValueParser::new() )] #[serde(skip)] @@ -40,11 +40,63 @@ pub struct ListArgs { pub page_size: u64, } -#[derive(Debug, Args)] +/// 展示随笔内容 +#[derive(Debug, Args, Serialize)] pub struct ShowArgs { - #[clap(value_parser = validate_non_zero_id)] + /// 随笔ID,必传 + #[serde(skip)] + #[clap(value_parser = validate_non_zero_id, required = true)] + pub id: u64, + + /// 展示评论,默认关闭评论,选择显示评论,则不展示随笔内容 + /// + /// 目前API调用存在问题,未实现此功能,占位。 + #[serde(skip)] + #[arg(long, short = 'c', default_value_t = false)] + pub comments: bool, + + /// 分页页码(从1开始) + #[arg( + default_value_t = 1, + long = "page-index", + required_if_eq("comments", "true"), + value_parser = validate_non_zero_id)] + pub page_index: u64, + + /// 每页显示的条数,默认20 + #[arg( + long = "page-size", + default_value_t = 20, + required_if_eq("comments", "true"), + value_parser = validate_non_zero_id + )] + pub page_size: u64, +} + +/// 博文评论回复 +#[derive(Debug, Args, Serialize)] +pub struct ReplayArgs { + /// 评论内容,必传 + #[serde(rename = "body")] + #[clap(value_parser = NonEmptyStringValueParser::new(), required = true)] + pub content: String, + + /// 随笔ID,必传 + #[serde(skip)] + #[arg(long, value_parser = validate_non_zero_id, required = true)] pub id: u64, - #[arg(long, default_value_t = false)] - pub comment: bool, + #[serde(skip)] + #[arg( + long, + value_name = "Blog name", + help = "博客名称,api接口的blog_app,默认当前用户,也可以指定。", + value_parser = NonEmptyStringValueParser::new() + )] + /// 回复博客园名称,不传为当前登录账号的博客名称。 + pub name: Option, } + +/// 博文评论回复 +#[derive(Debug, Args, Serialize)] +pub struct CreateArgs {} diff --git a/src/logic/post.rs b/src/logic/post.rs index c0f943c..dafe551 100644 --- a/src/logic/post.rs +++ b/src/logic/post.rs @@ -1,26 +1,23 @@ use anyhow::Result; +use owo_colors::OwoColorize; use termimad::MadSkin; use crate::{ api, - commands::post::{ListArgs, PostAction, PostCommand, ShowArgs}, + commands::post::{ListArgs, PostAction, PostCommand, ReplayArgs, ShowArgs}, context::Context, }; pub async fn endpoint(cmd: PostCommand, ctx: &mut Context) -> anyhow::Result<()> { match cmd.commands { - PostAction::Create => handle(ctx).await, + // PostAction::Create => handle(ctx).await, PostAction::List(arg) => handle_list(arg, ctx).await, - PostAction::Replay => handle(ctx).await, + PostAction::Replay(arg) => handle_replay(arg, ctx).await, PostAction::Show(arg) => handle_show(arg, ctx).await, - PostAction::Update => handle(ctx).await, + // PostAction::Update => handle(ctx).await, } } -async fn handle(_ctx: &mut Context) -> Result<()> { - Ok(()) -} - async fn handle_list(arg: ListArgs, ctx: &mut Context) -> Result<()> { let name = if let Some(n) = &arg.name { n @@ -35,6 +32,27 @@ async fn handle_list(arg: ListArgs, ctx: &mut Context) -> Result<()> { Ok(()) } +async fn handle_replay(arg: ReplayArgs, ctx: &mut Context) -> Result<()> { + let name = if let Some(n) = &arg.name { + n + } else { + &ctx.cache.blog_app + }; + let resp = api::post::raw_create_comment(&ctx.client, name, arg.id, &arg).await?; + + if resp.status().is_success() { + ctx.terminal + .writeln(format!("[Ok] {}", resp.status().bright_green())) + } else { + ctx.terminal.writeln(format!( + "[Err] {}: {}", + resp.status().red(), + resp.text().await?.red() + )) + } + // Ok(()) +} + async fn handle_show(arg: ShowArgs, ctx: &mut Context) -> Result<()> { let resp = api::post::raw_show_post(&ctx.client, arg.id) .await? @@ -49,3 +67,11 @@ async fn handle_show(arg: ShowArgs, ctx: &mut Context) -> Result<()> { mds.write_text_on(&mut ctx.terminal.stdout, &md)?; Ok(()) } + +/// API 调用401,目前无法使用。 +#[allow(unused)] +async fn handle_comments(arg: ShowArgs, ctx: &mut Context) -> Result<()> { + let resp = api::post::raw_list_comments(&ctx.client, &ctx.cache.blog_app, arg.id, arg).await?; + ctx.terminal.writeln(resp.status())?; + ctx.terminal.writeln(resp.text().await?) +} From 071c8a505344d68f5e3751cfdff9a8c3c7c49291 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 02:14:14 +0800 Subject: [PATCH 33/48] refactor(user): rename auth command to user --- src/commands/mod.rs | 4 ++-- src/commands/{auth.rs => user.rs} | 6 +++--- src/logic/mod.rs | 6 +++--- src/logic/{auth.rs => user.rs} | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) rename src/commands/{auth.rs => user.rs} (84%) rename src/logic/{auth.rs => user.rs} (80%) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 729da77..8b40bbb 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,8 +1,8 @@ -pub mod auth; pub mod fav; pub mod ing; pub mod news; pub mod post; +pub mod user; use std::fmt; @@ -22,7 +22,7 @@ pub struct Cli { #[derive(Debug, Subcommand)] pub enum Commands { - Auth(auth::Authenticate), + User(user::UserCommand), Ing { #[command(subcommand)] action: ing::IngAction, diff --git a/src/commands/auth.rs b/src/commands/user.rs similarity index 84% rename from src/commands/auth.rs rename to src/commands/user.rs index 677a01c..8b0f81f 100644 --- a/src/commands/auth.rs +++ b/src/commands/user.rs @@ -5,14 +5,14 @@ use clap::{Args, Subcommand, builder::NonEmptyStringValueParser}; #[derive(Debug, Args)] -pub struct Authenticate { +pub struct UserCommand { #[clap(subcommand)] - pub commands: AuthenticateSubCommands, + pub commands: UserAction, } /// 提供通过access token登录,状态查询,退出,显示当前token功能 #[derive(Debug, Subcommand)] -pub enum AuthenticateSubCommands { +pub enum UserAction { /// 用户登录,需提供access token。 Login { #[clap(value_parser = NonEmptyStringValueParser::new())] diff --git a/src/logic/mod.rs b/src/logic/mod.rs index af8ff62..fcfcca8 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -4,9 +4,9 @@ //! 此模块暂定封装操作逻辑,比如是闪存的curd,闪存评论的curd。 //! -pub mod auth; pub mod ing; pub mod post; +pub mod user; use anyhow::Result; @@ -15,8 +15,8 @@ use crate::context::Context; pub async fn run(cli: Cli, ctx: &mut Context) -> Result<()> { match cli.commands { - Commands::Auth(action) => { - auth::endpoint(action, ctx).await?; + Commands::User(action) => { + user::endpoint(action, ctx).await?; } Commands::Fav => {} Commands::Ing { action } => ing::endpoint(action, ctx).await?, diff --git a/src/logic/auth.rs b/src/logic/user.rs similarity index 80% rename from src/logic/auth.rs rename to src/logic/user.rs index b8bf01f..f01bdab 100644 --- a/src/logic/auth.rs +++ b/src/logic/user.rs @@ -7,18 +7,18 @@ use owo_colors::OwoColorize; use reqwest::header::{AUTHORIZATION, HeaderMap}; use reqwest::{ClientBuilder, StatusCode}; -use crate::commands::auth::{Authenticate, AuthenticateSubCommands}; +use crate::commands::user::{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: Authenticate, ctx: &mut Context) -> anyhow::Result<()> { +pub async fn endpoint(cmd: UserCommand, ctx: &mut Context) -> anyhow::Result<()> { match cmd.commands { - AuthenticateSubCommands::Login { token } => handle_login(token, ctx).await, - AuthenticateSubCommands::Logout => handle_logout(ctx), - AuthenticateSubCommands::Status => user_info(ctx).await, - AuthenticateSubCommands::Token => handle_print_token(ctx), + UserAction::Login { token } => handle_login(token, ctx).await, + UserAction::Logout => handle_logout(ctx), + UserAction::Status => user_info(ctx).await, + UserAction::Token => handle_print_token(ctx), } } From 9d62e9a0fe00107fc4e5107acad401a1244ed1e6 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 03:07:28 +0800 Subject: [PATCH 34/48] refactor(news): improve news command --- src/api/mod.rs | 1 + src/api/news.rs | 19 ++++++++++++ src/commands/mod.rs | 2 +- src/commands/news.rs | 23 ++++++++++++-- src/logic/mod.rs | 3 +- src/logic/news.rs | 21 +++++++++++++ src/models/mod.rs | 1 + src/models/news.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 src/api/news.rs create mode 100644 src/logic/news.rs create mode 100644 src/models/news.rs diff --git a/src/api/mod.rs b/src/api/mod.rs index 11da013..5fa97a7 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -5,6 +5,7 @@ //! pub mod ing; +pub mod news; pub mod post; pub mod urls; pub mod user; diff --git a/src/api/news.rs b/src/api/news.rs new file mode 100644 index 0000000..95a5476 --- /dev/null +++ b/src/api/news.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use reqwest::{Client, Response}; +use serde::Serialize; + +use crate::{api::urls::OPENAPI, models::news::NewsInfo, tools::IntoAnyhowResult}; + +pub async fn list_news(c: &Client, page: impl Serialize + Send + Sync) -> Result> { + raw_list_news(c, page) + .await? + .json() + .await + .into_anyhow_result() +} + +pub async fn raw_list_news(c: &Client, page: impl Serialize + Send + Sync) -> Result { + let url = format!("{}/{}", OPENAPI, "NewsItems"); + println!("{}", url); + c.get(url).query(&page).send().await.into_anyhow_result() +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 8b40bbb..2489ca2 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -28,7 +28,7 @@ pub enum Commands { action: ing::IngAction, }, Post(post::PostCommand), - News, + News(news::NewsCommand), Fav, } diff --git a/src/commands/news.rs b/src/commands/news.rs index 123ecdc..d973698 100644 --- a/src/commands/news.rs +++ b/src/commands/news.rs @@ -1,6 +1,25 @@ -use clap::Subcommand; +use clap::{Args, Subcommand}; +use serde::Serialize; + +#[derive(Debug, Args)] +pub struct NewsCommand { + #[clap(subcommand)] + pub commands: NewsAction, +} #[derive(Debug, Subcommand)] pub enum NewsAction { - List, + List(ListArgs), +} + +#[derive(Debug, Args, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ListArgs { + /// 分页页码(从1开始) + #[arg(long = "page-index", default_value_t = 1)] + pub page_index: u64, + + /// 每页显示的条数,默认20 + #[arg(long = "page-size", default_value_t = 10)] + pub page_size: u64, } diff --git a/src/logic/mod.rs b/src/logic/mod.rs index fcfcca8..db56842 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -5,6 +5,7 @@ //! pub mod ing; +pub mod news; pub mod post; pub mod user; @@ -20,7 +21,7 @@ pub async fn run(cli: Cli, ctx: &mut Context) -> Result<()> { } Commands::Fav => {} Commands::Ing { action } => ing::endpoint(action, ctx).await?, - Commands::News => {} + Commands::News(action) => news::endpoint(action, ctx).await?, Commands::Post(action) => post::endpoint(action, ctx).await?, } diff --git a/src/logic/news.rs b/src/logic/news.rs new file mode 100644 index 0000000..c1e7c97 --- /dev/null +++ b/src/logic/news.rs @@ -0,0 +1,21 @@ +use anyhow::Result; + +use crate::{ + api, + commands::news::{ListArgs, NewsAction, NewsCommand}, + context::Context, +}; + +pub async fn endpoint(cmd: NewsCommand, ctx: &mut Context) -> Result<()> { + match cmd.commands { + NewsAction::List(arg) => handle_list(arg, ctx).await, + } +} + +async fn handle_list(arg: ListArgs, ctx: &mut Context) -> Result<()> { + api::news::list_news(&ctx.client, arg) + .await? + .into_iter() + .for_each(|x| ctx.terminal.writeln(x.into_format()).unwrap()); + Ok(()) +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 81fdbe3..d0c246e 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,4 +1,5 @@ pub mod ing; +pub mod news; pub mod post; pub mod user; diff --git a/src/models/news.rs b/src/models/news.rs new file mode 100644 index 0000000..58ab79f --- /dev/null +++ b/src/models/news.rs @@ -0,0 +1,71 @@ +use chrono::NaiveDateTime; +use owo_colors::OwoColorize; +use serde::{Deserialize, Serialize}; +use termimad::crossterm::style::Stylize; + +use crate::tools::timer::DateFormatExt; + +// { +// "Id": 813263, +// "Title": "OpenAI偷袭,谷歌掀桌!2026开年第一场AI大战太精彩", +// "Summary": "新智元报道 编辑:KingHZ 谷歌强势回应 OpenAI:开源 TranslateGemma 模型,支持 55 种语言,效率惊人!12B 参数超越 27B 基线,手机端轻松运行,真正速通「巴别塔」。 语言的边界,正被 AI 一一抹平。 OpenAI 悄悄发布了翻译产品 ChatGPT Transl", +// "TopicId": 1, +// "TopicIcon": "https://img2023.cnblogs.com/news_topic/20230322145212853-1130832439.png", +// "ViewCount": 31, +// "CommentCount": 0, +// "DiggCount": 0, +// "DateAdded": "2026-01-20T15:31:00+08:00" +// } + +#[derive(Debug, Deserialize, Serialize, Default)] +#[serde(rename_all = "PascalCase")] +#[serde(default)] +pub struct NewsInfo { + pub id: u64, + pub title: String, + pub summary: String, + pub topic_id: u64, + pub topic_icon: String, + pub view_count: u64, + pub comment_count: u64, + pub digg_count: u64, + pub date_added: NaiveDateTime, +} + +impl NewsInfo { + pub fn into_format(self) -> String { + format!( + "{title}\n{summary}\n[#{id}][posted@ {date}][浏览:{vc}][评论:{cc}][点赞:{dc}]\n", + title = self.title.bold().cyan(), + summary = self.summary, + id = self.id.bright_green(), + date = self.date_added.as_time_age(), + vc = self.view_count, + cc = self.comment_count, + dc = self.digg_count, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse() { + let a = r#"{ + "Id": 813264, + "Title": "马斯克兑现承诺,开源X推荐算法!100% AI驱动,0人工规则", + "Summary": "新智元报道 编辑:定慧 马斯克兑现承诺,X平台全新推荐算法正式开源!这套由 Grok 驱动的 AI 系统,完全取代了人工规则,通过 15 种行为预测精准计算每条帖子的命运。 1 月 11 日,马斯克在X平台上发了一条帖子,宣布将在 7 天内开源X平台全新的推荐算法。 他还承诺,此后每 4 周重复一次", + "TopicId": 570, + "TopicIcon": "https://images0.cnblogs.com/news_topic/20150702090016187.png", + "ViewCount": 37, + "CommentCount": 0, + "DiggCount": 2, + "DateAdded": "2026-01-20T15:40:00+08:00" + },"#; + + let aa: NewsInfo = serde_json::from_str(a).unwrap(); + assert_eq!(aa.id, 813264); + } +} From d3c32ca30f0f3a0c5cbef0c756b0c438bfc4c809 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 03:50:36 +0800 Subject: [PATCH 35/48] refactor(news): refactor fav command --- src/api/fav.rs | 24 ++++++++++++++++++++++++ src/api/mod.rs | 1 + src/commands/fav.rs | 23 +++++++++++++++++++++-- src/commands/mod.rs | 2 +- src/logic/fav.rs | 20 ++++++++++++++++++++ src/logic/mod.rs | 3 ++- src/models/fav.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/models/mod.rs | 1 + 8 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 src/api/fav.rs create mode 100644 src/logic/fav.rs create mode 100644 src/models/fav.rs diff --git a/src/api/fav.rs b/src/api/fav.rs new file mode 100644 index 0000000..5fd1919 --- /dev/null +++ b/src/api/fav.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use reqwest::{Client, Response}; +use serde::Serialize; + +use crate::{api::urls::OPENAPI, models::fav::FavInfo, tools::IntoAnyhowResult}; + +pub async fn list_bookmarks( + c: &Client, + page: impl Serialize + Send + Sync, +) -> Result> { + raw_list_bookmarks(c, page) + .await? + .json() + .await + .into_anyhow_result() +} + +pub async fn raw_list_bookmarks( + c: &Client, + page: impl Serialize + Send + Sync, +) -> Result { + let url = format!("{}/{}", OPENAPI, "Bookmarks"); + c.get(url).query(&page).send().await.into_anyhow_result() +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 5fa97a7..1f9d008 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -4,6 +4,7 @@ //! 负责HTTP的调用,返回对应的response //! +pub mod fav; pub mod ing; pub mod news; pub mod post; diff --git a/src/commands/fav.rs b/src/commands/fav.rs index 5bb42d8..4e25fbc 100644 --- a/src/commands/fav.rs +++ b/src/commands/fav.rs @@ -1,6 +1,25 @@ -use clap::Subcommand; +use clap::{Args, Subcommand}; +use serde::Serialize; + +#[derive(Debug, Args)] +pub struct FaverateCommand { + #[clap(subcommand)] + pub commands: FaverateAction, +} #[derive(Debug, Subcommand)] pub enum FaverateAction { - List, + List(ListArgs), +} + +#[derive(Debug, Args, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ListArgs { + /// 分页页码(从1开始) + #[arg(long = "page-index", default_value_t = 1)] + pub page_index: u64, + + /// 每页显示的条数,默认20 + #[arg(long = "page-size", default_value_t = 10)] + pub page_size: u64, } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 2489ca2..da736d5 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -29,7 +29,7 @@ pub enum Commands { }, Post(post::PostCommand), News(news::NewsCommand), - Fav, + Fav(fav::FaverateCommand), } #[derive(Clone, Debug, ValueEnum, PartialEq, Eq)] diff --git a/src/logic/fav.rs b/src/logic/fav.rs new file mode 100644 index 0000000..cf71a15 --- /dev/null +++ b/src/logic/fav.rs @@ -0,0 +1,20 @@ +use anyhow::Result; + +use crate::{ + api, + commands::fav::{FaverateAction, FaverateCommand, ListArgs}, + context::Context, +}; + +pub async fn endpoint(cmd: FaverateCommand, ctx: &mut Context) -> Result<()> { + match cmd.commands { + FaverateAction::List(arg) => handle_list(arg, ctx).await, + } +} +async fn handle_list(arg: ListArgs, ctx: &mut Context) -> Result<()> { + api::fav::list_bookmarks(&ctx.client, arg) + .await? + .into_iter() + .for_each(|x| ctx.terminal.writeln(x.into_format()).unwrap()); + Ok(()) +} diff --git a/src/logic/mod.rs b/src/logic/mod.rs index db56842..290ee2d 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -4,6 +4,7 @@ //! 此模块暂定封装操作逻辑,比如是闪存的curd,闪存评论的curd。 //! +pub mod fav; pub mod ing; pub mod news; pub mod post; @@ -19,7 +20,7 @@ pub async fn run(cli: Cli, ctx: &mut Context) -> Result<()> { Commands::User(action) => { user::endpoint(action, ctx).await?; } - Commands::Fav => {} + Commands::Fav(action) => fav::endpoint(action, ctx).await?, Commands::Ing { action } => ing::endpoint(action, ctx).await?, Commands::News(action) => news::endpoint(action, ctx).await?, Commands::Post(action) => post::endpoint(action, ctx).await?, diff --git a/src/models/fav.rs b/src/models/fav.rs new file mode 100644 index 0000000..11f00ff --- /dev/null +++ b/src/models/fav.rs @@ -0,0 +1,38 @@ +// "WzLinkId": 5262184, +// "Title": "MongoDB数据库 - suoning - 博客园", +// "LinkUrl": "https://www.cnblogs.com/suoning/p/6759367.html#3682005", +// "Summary": "", +// "Tags": [], +// "DateAdded": "2019-04-05T23:33:05.263", +// "FromCNBlogs": true + +use chrono::NaiveDateTime; +use owo_colors::OwoColorize; +use serde::Deserialize; + +use crate::tools::timer::DateFormatExt; + +#[derive(Debug, Deserialize, Default)] +#[serde(rename_all = "PascalCase")] +#[serde(default)] +pub struct FavInfo { + pub wz_link_id: u64, + pub title: String, + pub link_url: String, + pub summary: Option, + pub tags: Vec, + pub date_added: NaiveDateTime, + pub from_c_n_blogs: bool, +} + +impl FavInfo { + pub fn into_format(self) -> String { + format!( + "{title} [#{id}][收藏@ {date}]\n{link}\n", + title = self.title.bold().cyan(), + id = self.wz_link_id.bright_green(), + date = self.date_added.as_time_age(), + link = self.link_url.blue(), + ) + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs index d0c246e..bb70b45 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,3 +1,4 @@ +pub mod fav; pub mod ing; pub mod news; pub mod post; From 17c5fa5af1ef9820f59f5675683ddddc3150635e Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 03:53:32 +0800 Subject: [PATCH 36/48] chore: remove unnecessary println, traits and imports --- src/api/news.rs | 1 - src/models/news.rs | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/api/news.rs b/src/api/news.rs index 95a5476..543d898 100644 --- a/src/api/news.rs +++ b/src/api/news.rs @@ -14,6 +14,5 @@ pub async fn list_news(c: &Client, page: impl Serialize + Send + Sync) -> Result pub async fn raw_list_news(c: &Client, page: impl Serialize + Send + Sync) -> Result { let url = format!("{}/{}", OPENAPI, "NewsItems"); - println!("{}", url); c.get(url).query(&page).send().await.into_anyhow_result() } diff --git a/src/models/news.rs b/src/models/news.rs index 58ab79f..91d80b7 100644 --- a/src/models/news.rs +++ b/src/models/news.rs @@ -1,7 +1,6 @@ use chrono::NaiveDateTime; use owo_colors::OwoColorize; -use serde::{Deserialize, Serialize}; -use termimad::crossterm::style::Stylize; +use serde::Deserialize; use crate::tools::timer::DateFormatExt; @@ -17,7 +16,7 @@ use crate::tools::timer::DateFormatExt; // "DateAdded": "2026-01-20T15:31:00+08:00" // } -#[derive(Debug, Deserialize, Serialize, Default)] +#[derive(Debug, Deserialize, Default)] #[serde(rename_all = "PascalCase")] #[serde(default)] pub struct NewsInfo { From 111b70c30842ded7e5f1a3f5bf0d35f8fe0d1165 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 04:04:49 +0800 Subject: [PATCH 37/48] refactor(cli): finish restructuring - Reorganize command modules into logical structure - Standardize command-line argument parsing - Improve help documentation --- src/api_bak/auth/mod.rs | 1 - src/api_bak/auth/session.rs | 52 ------ src/api_bak/fav/get_list.rs | 61 ------ src/api_bak/fav/mod.rs | 12 -- src/api_bak/ing/comment.rs | 42 ----- src/api_bak/ing/get_comment_list.rs | 42 ----- src/api_bak/ing/get_list.rs | 73 -------- src/api_bak/ing/mod.rs | 97 ---------- src/api_bak/ing/publish.rs | 26 --- src/api_bak/mod.rs | 33 ---- src/api_bak/news/get_body.rs | 19 -- src/api_bak/news/get_list.rs | 54 ------ src/api_bak/news/mod.rs | 12 -- src/api_bak/post/create.rs | 35 ---- src/api_bak/post/del_one.rs | 18 -- src/api_bak/post/get_comment_list.rs | 45 ----- src/api_bak/post/get_count.rs | 31 ---- src/api_bak/post/get_meta_list.rs | 62 ------- src/api_bak/post/get_one.rs | 56 ------ src/api_bak/post/get_one_raw.rs | 76 -------- src/api_bak/post/mod.rs | 20 -- src/api_bak/post/search.rs | 94 ---------- src/api_bak/post/search_self.rs | 94 ---------- src/api_bak/post/search_site.rs | 145 --------------- src/api_bak/post/update.rs | 49 ----- src/api_bak/user/info.rs | 46 ----- src/api_bak/user/mod.rs | 11 -- src/apis/ing/comment.rs | 63 ------- src/apis/ing/mod.rs | 184 ------------------ src/apis/mod.rs | 19 -- src/apis/token/mod.rs | 134 -------------- src/args/cmd/fav.rs | 12 -- src/args/cmd/ing.rs | 182 ------------------ src/args/cmd/mod.rs | 27 --- src/args/cmd/news.rs | 12 -- src/args/cmd/post.rs | 151 --------------- src/args/cmd/user.rs | 27 --- src/args/mod.rs | 126 ------------- src/args/parser/fav.rs | 22 --- src/args/parser/ing.rs | 157 ---------------- src/args/parser/mod.rs | 29 --- src/args/parser/news.rs | 22 --- src/args/parser/post.rs | 231 ----------------------- src/args/parser/user.rs | 58 ------ src/bin/cnb.rs | 266 +-------------------------- src/display/colorful/fav.rs | 53 ------ src/display/colorful/ing.rs | 115 ------------ src/display/colorful/mod.rs | 22 --- src/display/colorful/news.rs | 52 ------ src/display/colorful/post.rs | 175 ------------------ src/display/colorful/user.rs | 47 ----- src/display/json/fav.rs | 14 -- src/display/json/ing.rs | 25 --- src/display/json/mod.rs | 35 ---- src/display/json/news.rs | 14 -- src/display/json/post.rs | 74 -------- src/display/json/user.rs | 19 -- src/display/mod.rs | 189 ------------------- src/display/normal/fav.rs | 52 ------ src/display/normal/ing.rs | 112 ----------- src/display/normal/mod.rs | 21 --- src/display/normal/news.rs | 50 ----- src/display/normal/post.rs | 171 ----------------- src/display/normal/user.rs | 46 ----- src/infra/fp.rs | 33 ---- src/infra/http.rs | 68 ------- src/infra/infer.rs | 4 - src/infra/iter.rs | 47 ----- src/infra/json.rs | 21 --- src/infra/mod.rs | 11 -- src/infra/option.rs | 27 --- src/infra/result.rs | 52 ------ src/infra/str.rs | 94 ---------- src/infra/terminal.rs | 6 - src/infra/time.rs | 67 ------- src/infra/vec.rs | 11 -- src/lib.rs | 12 +- src/logic/ing.rs | 85 +-------- 78 files changed, 12 insertions(+), 4840 deletions(-) delete mode 100644 src/api_bak/auth/mod.rs delete mode 100644 src/api_bak/auth/session.rs delete mode 100644 src/api_bak/fav/get_list.rs delete mode 100644 src/api_bak/fav/mod.rs delete mode 100644 src/api_bak/ing/comment.rs delete mode 100644 src/api_bak/ing/get_comment_list.rs delete mode 100644 src/api_bak/ing/get_list.rs delete mode 100644 src/api_bak/ing/mod.rs delete mode 100644 src/api_bak/ing/publish.rs delete mode 100644 src/api_bak/mod.rs delete mode 100644 src/api_bak/news/get_body.rs delete mode 100644 src/api_bak/news/get_list.rs delete mode 100644 src/api_bak/news/mod.rs delete mode 100644 src/api_bak/post/create.rs delete mode 100644 src/api_bak/post/del_one.rs delete mode 100644 src/api_bak/post/get_comment_list.rs delete mode 100644 src/api_bak/post/get_count.rs delete mode 100644 src/api_bak/post/get_meta_list.rs delete mode 100644 src/api_bak/post/get_one.rs delete mode 100644 src/api_bak/post/get_one_raw.rs delete mode 100644 src/api_bak/post/mod.rs delete mode 100644 src/api_bak/post/search.rs delete mode 100644 src/api_bak/post/search_self.rs delete mode 100644 src/api_bak/post/search_site.rs delete mode 100644 src/api_bak/post/update.rs delete mode 100644 src/api_bak/user/info.rs delete mode 100644 src/api_bak/user/mod.rs delete mode 100644 src/apis/ing/comment.rs delete mode 100644 src/apis/ing/mod.rs delete mode 100644 src/apis/mod.rs delete mode 100644 src/apis/token/mod.rs delete mode 100644 src/args/cmd/fav.rs delete mode 100644 src/args/cmd/ing.rs delete mode 100644 src/args/cmd/mod.rs delete mode 100644 src/args/cmd/news.rs delete mode 100644 src/args/cmd/post.rs delete mode 100644 src/args/cmd/user.rs delete mode 100644 src/args/mod.rs delete mode 100644 src/args/parser/fav.rs delete mode 100644 src/args/parser/ing.rs delete mode 100644 src/args/parser/mod.rs delete mode 100644 src/args/parser/news.rs delete mode 100644 src/args/parser/post.rs delete mode 100644 src/args/parser/user.rs delete mode 100644 src/display/colorful/fav.rs delete mode 100644 src/display/colorful/ing.rs delete mode 100644 src/display/colorful/mod.rs delete mode 100644 src/display/colorful/news.rs delete mode 100644 src/display/colorful/post.rs delete mode 100644 src/display/colorful/user.rs delete mode 100644 src/display/json/fav.rs delete mode 100644 src/display/json/ing.rs delete mode 100644 src/display/json/mod.rs delete mode 100644 src/display/json/news.rs delete mode 100644 src/display/json/post.rs delete mode 100644 src/display/json/user.rs delete mode 100644 src/display/normal/fav.rs delete mode 100644 src/display/normal/ing.rs delete mode 100644 src/display/normal/mod.rs delete mode 100644 src/display/normal/news.rs delete mode 100644 src/display/normal/post.rs delete mode 100644 src/display/normal/user.rs delete mode 100644 src/infra/fp.rs delete mode 100644 src/infra/http.rs delete mode 100644 src/infra/infer.rs delete mode 100644 src/infra/iter.rs delete mode 100644 src/infra/json.rs delete mode 100644 src/infra/mod.rs delete mode 100644 src/infra/option.rs delete mode 100644 src/infra/result.rs delete mode 100644 src/infra/str.rs delete mode 100644 src/infra/terminal.rs delete mode 100644 src/infra/time.rs delete mode 100644 src/infra/vec.rs diff --git a/src/api_bak/auth/mod.rs b/src/api_bak/auth/mod.rs deleted file mode 100644 index f52f1c4..0000000 --- a/src/api_bak/auth/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod session; diff --git a/src/api_bak/auth/session.rs b/src/api_bak/auth/session.rs deleted file mode 100644 index 7a56b44..0000000 --- a/src/api_bak/auth/session.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::infra::result::WrapResult; -use anyhow::{Result, anyhow}; -use home::home_dir; -use std::fs; -use std::fs::{File, metadata, remove_file}; -use std::io::Write; -use std::path::{Path, PathBuf}; - -fn remove_pat(path: &Path) -> Result<()> { - if metadata(path).is_ok() { - remove_file(path)?; - } - ().wrap_ok() -} - -fn save_pat(pat: &str, path: &Path) -> Result<()> { - let mut file = File::create(path)?; - file.write_all(pat.as_bytes())?; - ().wrap_ok() -} - -fn get_cfg_path() -> Result { - let home = home_dir().ok_or_else(|| anyhow!("Can not get home dir"))?; - home.join(".cnbrc").wrap_ok() -} - -pub fn login(pat: &str) -> Result { - let cfg_path = get_cfg_path()?; - let cfg_path = cfg_path.as_path(); - - remove_pat(cfg_path)?; - save_pat(pat, cfg_path)?; - - cfg_path.to_owned().wrap_ok() -} - -pub fn logout() -> Result { - let cfg_path = get_cfg_path()?; - let cfg_path = cfg_path.as_path(); - - remove_pat(cfg_path)?; - - cfg_path.to_owned().wrap_ok() -} - -pub fn get_pat() -> Result { - let cfg_path = get_cfg_path()?; - let cfg_path = cfg_path.as_path(); - - fs::read_to_string(cfg_path) - .map_err(|e| anyhow!("Can not read {:?}, please login first ({})", cfg_path, e)) -} diff --git a/src/api_bak/fav/get_list.rs b/src/api_bak/fav/get_list.rs deleted file mode 100644 index 201f311..0000000 --- a/src/api_bak/fav/get_list.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::api_bak::fav::Fav; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::iter::IntoIteratorExt; -use crate::infra::json; -use crate::infra::result::WrapResult; -use crate::infra::vec::VecExt; -use crate::openapi; -use anyhow::Result; -use serde::{Deserialize, Serialize}; -use std::ops::ControlFlow; - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct FavEntry { - pub title: String, - #[serde(rename = "LinkUrl")] - pub url: String, - pub summary: String, - pub tags: Vec, - #[serde(rename = "DateAdded")] - pub create_time: String, -} - -impl Fav { - pub async fn get_list(&self, skip: usize, take: usize) -> Result> { - let client = &reqwest::Client::new(); - - let range = (skip + 1)..=(skip + take); - let cf = range - .map(|i| async move { - let req = { - let url = openapi!("/bookmarks"); - let query = [("pageIndex", i), ("pageSize", 1)]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let body = body_or_err(resp).await?; - - json::deserialize::>(&body)? - .pop() - .wrap_ok::() - }) - .join_all() - .await - .into_iter() - .try_fold(vec![], |acc, it| match it { - Ok(maybe) => match maybe { - Some(entry) => ControlFlow::Continue(acc.chain_push(entry)), - None => ControlFlow::Break(Ok(acc)), - }, - Err(e) => ControlFlow::Break(Err(e)), - }); - - match cf { - ControlFlow::Continue(vec) => Ok(vec), - ControlFlow::Break(result) => result, - } - } -} diff --git a/src/api_bak/fav/mod.rs b/src/api_bak/fav/mod.rs deleted file mode 100644 index a1e5984..0000000 --- a/src/api_bak/fav/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod get_list; - -// Aka cnblogs wz -pub struct Fav { - pat: String, -} - -impl Fav { - pub const fn new(pat: String) -> Self { - Self { pat } - } -} diff --git a/src/api_bak/ing/comment.rs b/src/api_bak/ing/comment.rs deleted file mode 100644 index c1b620e..0000000 --- a/src/api_bak/ing/comment.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::api_bak::ing::Ing; -use crate::infra::http::{RequestBuilderExt, unit_or_err}; -use crate::openapi; -use anyhow::Result; -use serde::{Deserialize, Serialize}; - -impl Ing { - pub async fn comment( - &self, - ing_id: usize, - content: String, - reply_to: Option, - parent_comment_id: Option, - ) -> Result<()> { - let client = reqwest::Client::new(); - - let req = { - let url = openapi!("/statuses/{}/comments", ing_id); - let body = { - #[serde_with::skip_serializing_none] - #[derive(Clone, Debug, Serialize, Deserialize)] - struct Body { - #[serde(rename(serialize = "replyTo"))] - reply_to: Option, - #[serde(rename(serialize = "parentCommentId"))] - parent_comment_id: Option, - content: String, - } - Body { - reply_to, - parent_comment_id, - content, - } - }; - client.post(url).json(&body).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - unit_or_err(resp).await - } -} diff --git a/src/api_bak/ing/get_comment_list.rs b/src/api_bak/ing/get_comment_list.rs deleted file mode 100644 index ff3c4ec..0000000 --- a/src/api_bak/ing/get_comment_list.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::api_bak::ing::Ing; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::json; -use crate::infra::result::WrapResult; -use crate::openapi; -use anyhow::Result; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct IngCommentEntry { - pub id: usize, - pub content: String, - #[serde(rename = "DateAdded")] - pub create_time: String, - pub status_id: usize, - pub user_alias: String, - #[serde(rename = "UserDisplayName")] - pub user_name: String, - pub user_icon_url: String, - pub user_id: usize, - pub user_guid: String, -} - -impl Ing { - pub async fn get_comment_list(&self, ing_id: usize) -> Result> { - let client = reqwest::Client::new(); - - let req = { - let url = openapi!("/statuses/{}/comments", ing_id); - client.get(url).pat_auth(&self.pat) - }; - let resp = req.send().await?; - - let entry_vec = { - let body = body_or_err(resp).await?; - json::deserialize::>(&body)? - }; - - entry_vec.wrap_ok() - } -} diff --git a/src/api_bak/ing/get_list.rs b/src/api_bak/ing/get_list.rs deleted file mode 100644 index f40608b..0000000 --- a/src/api_bak/ing/get_list.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::api_bak::ing::{Ing, IngSendFrom, IngType}; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::iter::IntoIteratorExt; -use crate::infra::json; -use crate::infra::result::WrapResult; -use crate::infra::vec::VecExt; -use crate::openapi; -use anyhow::Result; -use serde::{Deserialize, Serialize}; -use std::ops::ControlFlow; - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct IngEntry { - pub id: usize, - pub content: String, - pub is_private: bool, - pub is_lucky: bool, - pub comment_count: usize, - #[serde(rename = "DateAdded")] - pub create_time: String, - pub user_alias: String, - #[serde(rename = "UserDisplayName")] - pub user_name: String, - pub user_icon_url: String, - pub user_id: usize, - pub user_guid: String, - pub send_from: IngSendFrom, - #[serde(rename = "Icons")] - pub icons: String, -} - -impl Ing { - pub async fn get_list( - &self, - skip: usize, - take: usize, - ing_type: &IngType, - ) -> Result> { - let client = &reqwest::Client::new(); - - let range = (skip + 1)..=(skip + take); - let cf = range - .map(|i| async move { - let req = { - let url = openapi!("/statuses/@{}", ing_type.clone() as usize); - let query = [("pageIndex", i), ("pageSize", 1)]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let body = body_or_err(resp).await?; - - json::deserialize::>(&body)?.pop().wrap_ok() - }) - .join_all() - .await - .into_iter() - .try_fold(vec![], |acc, it| match it { - Ok(maybe) => match maybe { - Some(entry) => ControlFlow::Continue(acc.chain_push(entry)), - None => ControlFlow::Break(Ok(acc)), - }, - Err(e) => ControlFlow::Break(Err(e)), - }); - - match cf { - ControlFlow::Continue(vec) => Ok(vec), - ControlFlow::Break(result) => result, - } - } -} diff --git a/src/api_bak/ing/mod.rs b/src/api_bak/ing/mod.rs deleted file mode 100644 index a9a45ac..0000000 --- a/src/api_bak/ing/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -pub mod comment; -pub mod publish; - -use clap::{Parser, ValueEnum}; -use lazy_static::lazy_static; -use regex::Regex; -use serde_repr::{Deserialize_repr, Serialize_repr}; - -pub mod get_comment_list; -pub mod get_list; - -pub struct Ing { - pat: String, -} - -impl Ing { - pub const fn new(pat: String) -> Self { - Self { pat } - } -} - -#[derive(Clone, Debug, Parser, ValueEnum)] -pub enum IngType { - Follow = 1, - Myself = 4, - Public = 5, - //RecentComment = 6, - MyComment = 7, - //Tag = 10, - //Comment = 13, - //Mention = 14, -} - -#[derive(Clone, Debug, Default, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum IngSendFrom { - None = 0, - Ms = 1, - GTalk = 2, - Qq = 3, - Sms = 5, - CellPhone = 6, - Web = 8, - VsCode = 9, - #[default] - Cli = 13, -} - -pub fn ing_star_tag_to_text(tag: &str) -> String { - lazy_static! { - static ref REGEX: Regex = - Regex::new(r#""#).expect("Invalid regexp"); - } - let caps = REGEX - .captures(tag) - .unwrap_or_else(|| panic!("No captures for: {}", tag)); - let text = caps.get(1).expect("No capture at index 1").as_str(); - text.to_string() -} - -pub fn fmt_content(content: &str) -> String { - lazy_static! { - static ref REGEX: Regex = - Regex::new(r#"(@.*?)"#) - .expect("Invalid regexp"); - } - REGEX.captures(content).map_or_else( - || content.to_owned(), - |caps| { - let at_user = caps.get(1).expect("No capture at index 1").as_str(); - REGEX.replace(content, at_user).to_string() - }, - ) -} - -pub fn rm_ing_at_user_tag(text: &str) -> String { - lazy_static! { - static ref REGEX: Regex = - Regex::new(r#"(@.*?):"#) - .expect("Invalid regexp"); - } - REGEX.replace(text, "").to_string() -} - -pub fn get_ing_at_user_tag_text(text: &str) -> String { - lazy_static! { - static ref REGEX: Regex = - Regex::new(r#"@(.*?):"#) - .expect("Invalid regexp"); - } - REGEX.captures(text).map_or_else(String::new, |caps| { - caps.get(1) - .expect("No capture at index 1") - .as_str() - .to_string() - }) -} diff --git a/src/api_bak/ing/publish.rs b/src/api_bak/ing/publish.rs deleted file mode 100644 index 2cd51e9..0000000 --- a/src/api_bak/ing/publish.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::api_bak::ing::{Ing, IngSendFrom}; -use crate::infra::http::{RequestBuilderExt, unit_or_err}; -use crate::openapi; -use anyhow::Result; -use serde_json::json; - -impl Ing { - pub async fn publish(&self, content: &str) -> Result<()> { - let client = reqwest::Client::new(); - - let req = { - let url = openapi!("/statuses"); - let body = json!({ - "content": content, - "isPrivate": false, - "clientType": IngSendFrom::Cli, - }); - - client.post(url).json(&body).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - unit_or_err(resp).await - } -} diff --git a/src/api_bak/mod.rs b/src/api_bak/mod.rs deleted file mode 100644 index 4244ec2..0000000 --- a/src/api_bak/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub mod auth; -pub mod fav; -pub mod ing; -pub mod news; -pub mod post; -pub mod user; - -pub const BLOG_BACKEND: &str = "https://i.cnblogs.com/api"; -#[macro_export] -macro_rules! blog_backend { - ($($arg:tt)*) => {{ - use $crate::api_bak::BLOG_BACKEND; - format!("{}{}", BLOG_BACKEND, format_args!($($arg)*)) - }}; -} - -pub const OPENAPI: &str = "https://api.cnblogs.com/api"; -#[macro_export] -macro_rules! openapi { - ($($arg:tt)*) => {{ - use $crate::api_bak::OPENAPI; - format!("{}{}", OPENAPI, format_args!($($arg)*)) - }}; -} - -pub const OAUTH: &str = "https://oauth.cnblogs.com"; -#[macro_export] -macro_rules! oauth { - ($($arg:tt)*) => {{ - use $crate::api_bak::OAUTH; - format!("{}{}", OAUTH, format_args!($($arg)*)) - }}; -} diff --git a/src/api_bak/news/get_body.rs b/src/api_bak/news/get_body.rs deleted file mode 100644 index 5b69187..0000000 --- a/src/api_bak/news/get_body.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::api_bak::news::News; -use crate::blog_backend; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use anyhow::Result; - -impl News { - pub async fn get_body(&self, id: usize) -> Result { - let client = reqwest::Client::new(); - - let req = { - let url = blog_backend!("newsitems/{}/body", id); - client.get(url).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - body_or_err(resp).await - } -} diff --git a/src/api_bak/news/get_list.rs b/src/api_bak/news/get_list.rs deleted file mode 100644 index b91caf4..0000000 --- a/src/api_bak/news/get_list.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::api_bak::news::News; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::iter::IntoIteratorExt; -use crate::infra::json; -use crate::infra::result::WrapResult; -use crate::openapi; -use anyhow::Result; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct NewsEntry { - pub id: usize, - pub title: String, - pub summary: String, - pub topic_id: usize, - #[serde(rename = "TopicIcon")] - pub topic_icon_url: Option, - pub view_count: usize, - pub comment_count: usize, - pub digg_count: usize, - #[serde(rename = "DateAdded")] - pub create_time: String, -} - -impl News { - pub async fn get_list(&self, skip: usize, take: usize) -> Result> { - let client = &reqwest::Client::new(); - - let range = (skip + 1)..=(skip + take); - range - .map(|i| async move { - let req = { - let url = openapi!("/newsitems"); - let query = [("pageIndex", i), ("pageSize", 1)]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let entry = { - let body = body_or_err(resp).await?; - let [entry, ..] = json::deserialize::<[NewsEntry; 1]>(&body)?; - entry - }; - - entry.wrap_ok::() - }) - .join_all() - .await - .into_iter() - .collect() - } -} diff --git a/src/api_bak/news/mod.rs b/src/api_bak/news/mod.rs deleted file mode 100644 index 24c04b2..0000000 --- a/src/api_bak/news/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod get_body; -pub mod get_list; - -pub struct News { - pat: String, -} - -impl News { - pub const fn new(pat: String) -> Self { - Self { pat } - } -} diff --git a/src/api_bak/post/create.rs b/src/api_bak/post/create.rs deleted file mode 100644 index e0a74f8..0000000 --- a/src/api_bak/post/create.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::api_bak::post::Post; -use crate::blog_backend; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::json; -use crate::infra::result::WrapResult; -use anyhow::Result; -use serde_json::{Value, json}; - -impl Post { - pub async fn create(&self, title: &str, body: &str, publish: bool) -> Result { - let client = reqwest::Client::new(); - - let req = { - let url = blog_backend!("/posts"); - let body = json!({ - "postType": 1, - "title": title, - "postBody": body, - "isPublished": publish, - "displayOnHomePage": true - }); - client.post(url).json(&body).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let id = { - let body = body_or_err(resp).await?; - let json = json::deserialize::(&body)?; - json["id"].as_u64().expect("as_u64 failed for `id`") as usize - }; - - id.wrap_ok() - } -} diff --git a/src/api_bak/post/del_one.rs b/src/api_bak/post/del_one.rs deleted file mode 100644 index 433fc1c..0000000 --- a/src/api_bak/post/del_one.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::api_bak::post::Post; -use crate::blog_backend; -use crate::infra::http::{RequestBuilderExt, unit_or_err}; -use anyhow::Result; - -impl Post { - pub async fn del_one(&self, id: usize) -> Result<()> { - let client = reqwest::Client::new(); - - let req = { - let url = blog_backend!("/posts/{}", id); - client.delete(url).pat_auth(&self.pat) - }; - let resp = req.send().await?; - - unit_or_err(resp).await - } -} diff --git a/src/api_bak/post/get_comment_list.rs b/src/api_bak/post/get_comment_list.rs deleted file mode 100644 index cf211cf..0000000 --- a/src/api_bak/post/get_comment_list.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::api_bak::post::Post; -use crate::api_bak::user::User; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::json; -use crate::infra::result::WrapResult; -use crate::openapi; -use anyhow::Result; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct PostCommentEntry { - pub id: usize, - #[serde(rename = "Body")] - pub content: String, - #[serde(rename = "Author")] - pub user_name: String, - #[serde(rename = "AuthorUrl")] - pub user_home_url: String, - #[serde(rename = "FaceUrl")] - pub avatar_url: String, - pub floor: usize, - #[serde(rename = "DateAdded")] - pub create_time: String, -} - -impl Post { - pub async fn get_comment_list(&self, post_id: usize) -> Result> { - let blog_app = User::new(self.pat.to_owned()).get_info().await?.blog_app; - let client = reqwest::Client::new(); - - let req = { - let url = openapi!("/blogs/{}/posts/{}/comments", blog_app, post_id); - client.get(url).pat_auth(&self.pat) - }; - let resp = req.send().await?; - - let entry_vec = { - let body = body_or_err(resp).await?; - json::deserialize::>(&body)? - }; - - entry_vec.wrap_ok() - } -} diff --git a/src/api_bak/post/get_count.rs b/src/api_bak/post/get_count.rs deleted file mode 100644 index 7ab971e..0000000 --- a/src/api_bak/post/get_count.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::api_bak::post::Post; -use crate::blog_backend; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::json; -use crate::infra::result::WrapResult; -use anyhow::Result; -use serde_json::Value; - -impl Post { - pub async fn get_count(&self) -> Result { - let client = reqwest::Client::new(); - - let req = { - let url = blog_backend!("/posts/list"); - let query = [('t', 1), ('p', 1), ('s', 1)]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let count = { - let body = body_or_err(resp).await?; - let json = json::deserialize::(&body)?; - json["postsCount"] - .as_u64() - .expect("as_u64 failed for `postsCount`") as usize - }; - - count.wrap_ok() - } -} diff --git a/src/api_bak/post/get_meta_list.rs b/src/api_bak/post/get_meta_list.rs deleted file mode 100644 index 2f2dd57..0000000 --- a/src/api_bak/post/get_meta_list.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::api_bak::post::Post; -use crate::api_bak::post::get_one::PostEntry; -use crate::blog_backend; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::iter::IntoIteratorExt; -use crate::infra::json; -use crate::infra::result::WrapResult; -use anyhow::Result; -use serde_json::Value; - -/* -Fields only available over blog_backend!("/posts/list?{}", query): - aggCount: number - feedBackCount: number - isInSiteCandidate: boolean - isInSiteHome: boolean - postConfig: number - viewCount: number - webCount: number -*/ - -impl Post { - pub async fn get_meta_list(&self, skip: usize, take: usize) -> Result<(Vec, usize)> { - // WRN: - // This impl has low performance but robust - // Current API of blog backend is buggy - // It's not worth to design a more efficient impl - let client = &reqwest::Client::new(); - - // total_count is used for patch the buggy blog backend API - // If index is greater than the max page index, API will still return the last page - let total_count = self.get_count().await?; - - let range = (skip + 1)..=(skip + take).min(total_count); - let vec = range - .map(|i| async move { - let req = { - let url = blog_backend!("/posts/list"); - let query = [('t', 1), ('p', i), ('s', 1)]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let entry = { - let body = body_or_err(resp).await?; - let json = json::deserialize::(&body)?["postList"].take(); - - let [entry, ..] = serde_json::from_value::<[PostEntry; 1]>(json)?; - entry - }; - - entry.wrap_ok() - }) - .join_all() - .await - .into_iter() - .collect::>>(); - - (vec?, total_count).wrap_ok() - } -} diff --git a/src/api_bak/post/get_one.rs b/src/api_bak/post/get_one.rs deleted file mode 100644 index 905f983..0000000 --- a/src/api_bak/post/get_one.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::api_bak::post::Post; -use crate::blog_backend; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::json; -use crate::infra::result::WrapResult; -use anyhow::Result; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -// TODO: not elegant -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct PostEntry { - pub id: usize, - pub title: String, - pub url: String, - - #[serde(rename = "datePublished")] - pub create_time: String, - #[serde(rename = "dateUpdated")] - pub modify_time: String, - - pub is_draft: bool, - pub is_pinned: bool, - pub is_published: bool, - - // WRN: - // Limited by the design of blog backend API - // None implies that this filed is not fetched from server yet but DOSE NOT MEAN IT NOT EXIST - #[serde(rename = "feedBackCount")] - pub comment_count: Option, - #[serde(rename = "postBody")] - pub body: Option, - pub tags: Option>, -} - -impl Post { - pub async fn get_one(&self, id: usize) -> Result { - let client = reqwest::Client::new(); - - let req = { - let url = blog_backend!("/posts/{}", id); - client.get(url).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let entry = { - let body = body_or_err(resp).await?; - let json = json::deserialize::(&body)?["blogPost"].take(); - serde_json::from_value::(json)? - }; - - entry.wrap_ok() - } -} diff --git a/src/api_bak/post/get_one_raw.rs b/src/api_bak/post/get_one_raw.rs deleted file mode 100644 index 3004bf2..0000000 --- a/src/api_bak/post/get_one_raw.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::api_bak::post::Post; -use crate::blog_backend; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::result::WrapResult; -use anyhow::Result; -use serde_json::Value; - -/* -Fields only available over blog_backend!("/posts/{}", id): - postBody: string - categoryIds: [] - collectionIds: [] - inSiteCandidate: boolean - inSiteHome: boolean - siteCategoryId: null - blogTeamIds: [] - displayOnHomePage: boolean - isAllowComments: boolean - includeInMainSyndication: boolean - isOnlyForRegisterUser: boolean - isUpdateDateAdded: boolean - description: string - featuredImage: null - tags: [] - password: null - autoDesc: string - changePostType: boolean - blogId: number - author: string - removeScript: boolean - clientInfo: null - changeCreatedTime: boolean - canChangeCreatedTime: boolean - isContributeToImpressiveBugActivity: boolean - usingEditorId: null - sourceUrl: null - -Fields available over blog_backend!("/posts/{}", id) and blog_backend!("/posts/list?{}", query): - id: number - postType: PostType - accessPermission: AccessPermission - title: string - url: string - entryName: null - datePublished: string - dateUpdated: string - isMarkdown: boolean - isDraft: boolean - isPinned: boolean - isPublished: boolean -*/ - -impl Post { - /** - Get raw json from remote - - Use this while it's hard to deserialize to struct - **/ - pub async fn get_one_raw(&self, id: usize) -> Result { - let client = reqwest::Client::new(); - - let req = { - let url = blog_backend!("/posts/{}", id); - client.get(url).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let mut json = { - let body = body_or_err(resp).await?; - serde_json::from_str::(&body) - }?; - - json["blogPost"].take().wrap_ok() - } -} diff --git a/src/api_bak/post/mod.rs b/src/api_bak/post/mod.rs deleted file mode 100644 index e5cf72a..0000000 --- a/src/api_bak/post/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -pub mod create; -pub mod del_one; -pub mod get_comment_list; -pub mod get_count; -pub mod get_meta_list; -pub mod get_one; -pub mod get_one_raw; -pub mod search_self; -pub mod search_site; -pub mod update; - -pub struct Post { - pat: String, -} - -impl Post { - pub const fn new(pat: String) -> Self { - Self { pat } - } -} diff --git a/src/api_bak/post/search.rs b/src/api_bak/post/search.rs deleted file mode 100644 index baff137..0000000 --- a/src/api_bak/post/search.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::api::post::Post; -use crate::blog_backend; -use crate::infra::http::{body_or_err, RequestBuilderExt}; -use crate::infra::iter::IntoIteratorExt; -use crate::infra::json; -use crate::infra::result::WrapResult; -use anyhow::Result; -use serde_json::Value; -use std::collections::HashSet; -use std::iter; - -impl Post { - pub async fn search( - &self, - skip: usize, - take: usize, - keyword: &str, - ) -> Result<(Vec, usize)> { - let client = &reqwest::Client::new(); - - // total_count is used for patch the buggy blog backend API - // If index is greater than the max page index, API will still return the last page - let total_count = { - let req = { - let url = blog_backend!("/posts/list"); - let query = [ - ("t", 1.to_string()), - ("p", 1.to_string()), - ("s", 1.to_string()), - ("search", keyword.to_string()), - ]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - let resp = req.send().await?; - - // total_count - { - let body = body_or_err(resp).await?; - let json = json::deserialize::(&body)?; - json["postsCount"] - .as_u64() - .expect("as_u64 failed for `postsCount`") as usize - } - }; - - let range = (skip + 1)..=(skip + take).min(total_count); - let id_list = range - .map(|i| async move { - let req = { - let url = blog_backend!("/posts/list"); - let query = [ - ("t", 1.to_string()), - ("p", i.to_string()), - ("s", 1.to_string()), - ("search", keyword.to_string()), - ]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - let resp = req.send().await?; - - let id_list = { - let body = body_or_err(resp).await?; - let mut json = json::deserialize::(&body)?; - let post_id = { - let json = json["postList"].take(); - let [post, ..] = serde_json::from_value::<[Value; 1]>(json)?; - post["id"].as_u64().expect("as_u64 failed for `id`") as usize - }; - let zzk_post_id_list = { - let json = json["zzkSearchResult"]["postIds"].take(); - serde_json::from_value::>(json) - }?; - - zzk_post_id_list - .into_iter() - .chain(iter::once(post_id)) - .collect::>() - }; - - id_list.wrap_ok::() - }) - .join_all() - .await - .into_iter() - .collect::>>()? - .into_iter() - .flatten() - .collect::>() - .into_iter() - .collect::>(); - - (id_list, total_count).wrap_ok() - } -} diff --git a/src/api_bak/post/search_self.rs b/src/api_bak/post/search_self.rs deleted file mode 100644 index d792c03..0000000 --- a/src/api_bak/post/search_self.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::api_bak::post::Post; -use crate::blog_backend; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::iter::IntoIteratorExt; -use crate::infra::json; -use crate::infra::result::WrapResult; -use anyhow::Result; -use serde_json::Value; -use std::collections::HashSet; -use std::iter; - -impl Post { - pub async fn search_self( - &self, - skip: usize, - take: usize, - keyword: &str, - ) -> Result<(Vec, usize)> { - let client = &reqwest::Client::new(); - - // total_count is used for patch the buggy blog backend API - // If index is greater than the max page index, API will still return the last page - let total_count = { - let req = { - let url = blog_backend!("/posts/list"); - let query = [ - ("t", 1.to_string()), - ("p", 1.to_string()), - ("s", 1.to_string()), - ("search", keyword.to_string()), - ]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - let resp = req.send().await?; - - // total_count - { - let body = body_or_err(resp).await?; - let json = json::deserialize::(&body)?; - json["postsCount"] - .as_u64() - .expect("as_u64 failed for `postsCount`") as usize - } - }; - - let range = (skip + 1)..=(skip + take).min(total_count); - let id_list = range - .map(|i| async move { - let req = { - let url = blog_backend!("/posts/list"); - let query = [ - ("t", 1.to_string()), - ("p", i.to_string()), - ("s", 1.to_string()), - ("search", keyword.to_string()), - ]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - let resp = req.send().await?; - - let id_list = { - let body = body_or_err(resp).await?; - let mut json = json::deserialize::(&body)?; - let post_id = { - let json = json["postList"].take(); - let [post, ..] = serde_json::from_value::<[Value; 1]>(json)?; - post["id"].as_u64().expect("as_u64 failed for `id`") as usize - }; - let zzk_post_id_list = { - let json = json["zzkSearchResult"]["postIds"].take(); - serde_json::from_value::>(json) - }?; - - zzk_post_id_list - .into_iter() - .chain(iter::once(post_id)) - .collect::>() - }; - - id_list.wrap_ok::() - }) - .join_all() - .await - .into_iter() - .collect::>>()? - .into_iter() - .flatten() - .collect::>() - .into_iter() - .collect::>(); - - (id_list, total_count).wrap_ok() - } -} diff --git a/src/api_bak/post/search_site.rs b/src/api_bak/post/search_site.rs deleted file mode 100644 index 40db4ac..0000000 --- a/src/api_bak/post/search_site.rs +++ /dev/null @@ -1,145 +0,0 @@ -use crate::api_bak::post::Post; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::iter::IntoIteratorExt; -use crate::infra::json; -use crate::infra::result::WrapResult; -use crate::openapi; -use anyhow::Result; -use serde::{Deserialize, Serialize}; -use std::ops::Range; - -// HACK: -// Convert skip and take to page index range while page size is 10 -fn get_page_index_range(skip: usize, take: usize) -> Range { - let page_size = 10; - let start_index = skip / page_size + 1; - let end_index = if take == 0 { - 0 - } else { - ((take + skip) as f64 / page_size as f64).ceil() as usize - }; - start_index..end_index + 1 -} - -// HACK: -// Convert skip and take to a range to slice the vec while page size is 10 -const fn get_slice_range(skip: usize, take: usize) -> Range { - let skip_left = skip - (skip / 10) * 10; - skip_left..skip_left + take -} - -#[test] -fn test_get_page_index_range_and_get_slice_range() { - fn f(skip: usize, take: usize) -> (Range, Range) { - ( - get_page_index_range(skip, take), - get_slice_range(skip, take), - ) - } - - assert_eq!(f(0, 00), (1..1, 0..00)); - assert_eq!(f(0, 01), (1..2, 0..01)); - assert_eq!(f(0, 09), (1..2, 0..09)); - assert_eq!(f(0, 10), (1..2, 0..10)); - assert_eq!(f(0, 11), (1..3, 0..11)); - assert_eq!(f(0, 19), (1..3, 0..19)); - assert_eq!(f(0, 20), (1..3, 0..20)); - assert_eq!(f(0, 21), (1..4, 0..21)); - - assert_eq!(f(1, 00), (1..1, 1..01)); - assert_eq!(f(1, 01), (1..2, 1..02)); - assert_eq!(f(1, 09), (1..2, 1..10)); - assert_eq!(f(1, 10), (1..3, 1..11)); - assert_eq!(f(1, 11), (1..3, 1..12)); - assert_eq!(f(1, 19), (1..3, 1..20)); - assert_eq!(f(1, 20), (1..4, 1..21)); - assert_eq!(f(1, 21), (1..4, 1..22)); - assert_eq!(f(1, 29), (1..4, 1..30)); - - assert_eq!(f(9, 00), (1..1, 9..09)); - assert_eq!(f(9, 01), (1..2, 9..10)); - assert_eq!(f(9, 09), (1..3, 9..18)); - assert_eq!(f(9, 10), (1..3, 9..19)); - assert_eq!(f(9, 11), (1..3, 9..20)); - assert_eq!(f(9, 19), (1..4, 9..28)); - assert_eq!(f(9, 20), (1..4, 9..29)); - assert_eq!(f(9, 21), (1..4, 9..30)); - assert_eq!(f(9, 29), (1..5, 9..38)); - - assert_eq!(f(10, 00), (2..1, 0..00)); - assert_eq!(f(10, 01), (2..3, 0..01)); - assert_eq!(f(10, 09), (2..3, 0..09)); - assert_eq!(f(10, 10), (2..3, 0..10)); - assert_eq!(f(10, 11), (2..4, 0..11)); - assert_eq!(f(10, 19), (2..4, 0..19)); - assert_eq!(f(10, 20), (2..4, 0..20)); - assert_eq!(f(10, 21), (2..5, 0..21)); -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SearchResultEntry { - #[serde(rename = "Title")] - pub title: String, - #[serde(rename = "Content")] - pub summary: String, - - #[serde(rename = "UserName")] - pub user_name: String, - - #[serde(rename = "VoteTimes")] - pub vote_count: usize, - #[serde(rename = "ViewTimes")] - pub view_count: usize, - #[serde(rename = "CommentTimes")] - pub comment_count: usize, - - #[serde(rename = "PublishTime")] - pub create_time: String, - #[serde(rename = "Uri")] - pub url: String, -} - -impl Post { - pub async fn search_site( - &self, - skip: usize, - take: usize, - keyword: &str, - ) -> Result> { - let client = &reqwest::Client::new(); - - let slice_range = get_slice_range(skip, take); - - let entry_vec = { - let entry_vec = get_page_index_range(skip, take) - .map(|i| async move { - let req = { - let url = openapi!("/zzkdocuments/blog"); - let query = [ - ("pageIndex", i.to_string()), - ("keyWords", keyword.to_string()), - ]; - client.get(url).query(&query).pat_auth(&self.pat) - }; - let resp = req.send().await?; - - let body = body_or_err(resp).await?; - json::deserialize::>(&body) - }) - .join_all() - .await - .into_iter() - .collect::>>>()? - .concat(); - - entry_vec - .into_iter() - .enumerate() - .filter(|(i, _)| slice_range.contains(i)) - .map(|(_, entry)| entry) - .collect::>() - }; - - entry_vec.wrap_ok() - } -} diff --git a/src/api_bak/post/update.rs b/src/api_bak/post/update.rs deleted file mode 100644 index 36e927a..0000000 --- a/src/api_bak/post/update.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::api_bak::post::Post; -use crate::blog_backend; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::json; -use crate::infra::result::WrapResult; -use anyhow::Result; -use serde_json::{Value, json}; - -impl Post { - pub async fn update( - &self, - id: usize, - title: &Option, - body: &Option, - publish: &Option, - ) -> Result { - let client = reqwest::Client::new(); - - let req = { - let url = blog_backend!("/posts"); - - let json = { - let mut json = self.get_one_raw(id).await?; - if let Some(title) = title { - json["title"] = json!(title) - } - if let Some(body) = body { - json["postBody"] = json!(body) - } - if let Some(publish) = publish { - json["isPublished"] = json!(publish) - } - json - }; - - client.post(url).json(&json).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let id = { - let body = body_or_err(resp).await?; - let json = json::deserialize::(&body)?; - json["id"].as_u64().expect("as_u64 failed for `id`") as usize - }; - - id.wrap_ok() - } -} diff --git a/src/api_bak/user/info.rs b/src/api_bak/user/info.rs deleted file mode 100644 index 4809864..0000000 --- a/src/api_bak/user/info.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::api_bak::user::User; -use crate::infra::http::{RequestBuilderExt, body_or_err}; -use crate::infra::json; -use crate::infra::result::WrapResult; -use crate::openapi; -use anyhow::Result; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "PascalCase")] -pub struct UserInfo { - pub user_id: String, - #[serde(rename = "SpaceUserID")] - pub space_user_id: usize, - pub blog_id: usize, - pub display_name: String, - pub face: String, - pub avatar: String, - pub seniority: String, - pub blog_app: String, - pub following_count: usize, - #[serde(rename = "FollowerCount")] - pub followers_count: usize, - pub is_vip: bool, - pub joined: String, -} - -impl User { - pub async fn get_info(&self) -> Result { - let client = reqwest::Client::new(); - - let req = { - let url = openapi!("/users"); - client.get(url).pat_auth(&self.pat) - }; - - let resp = req.send().await?; - - let user_info = { - let body = body_or_err(resp).await?; - json::deserialize::(&body)? - }; - - user_info.wrap_ok() - } -} diff --git a/src/api_bak/user/mod.rs b/src/api_bak/user/mod.rs deleted file mode 100644 index 97b22a5..0000000 --- a/src/api_bak/user/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod info; - -pub struct User { - pat: String, -} - -impl User { - pub const fn new(pat: String) -> Self { - Self { pat } - } -} diff --git a/src/apis/ing/comment.rs b/src/apis/ing/comment.rs deleted file mode 100644 index d0897c7..0000000 --- a/src/apis/ing/comment.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! 闪存评论相关 -//! - -use anyhow::{Ok, Result}; -use reqwest::{Client, Response}; -use serde::{Deserialize, Serialize}; - -use crate::{ - api_bak::ing::get_comment_list::IngCommentEntry, infra::http::RequestBuilderExt, openapi, -}; - -/// 闪存评论及评论回复 -/// -/// replay_to: 在web端有一个ReplyToUserId,这里盲猜是这个 -/// parent_comment_id: 0 是对某条闪存评论,如果对闪存评论要回应,这里则是闪存评论的id -/// content: 评论内容。 如果是对闪存评论回应,则应加上`@用户名称` -/// -#[derive(Debug, Default, Deserialize, Serialize)] -#[serde(rename_all = "PascalCase")] -#[serde(default)] -pub struct StatusComment { - #[serde(skip)] - pub status_id: String, - pub replay_to: u64, - pub parent_comment_id: u64, - pub content: String, -} - -/// 根据闪存ID发表一个评论 -pub async fn post(token: String, sc: StatusComment) -> Result { - let r = Client::new() - .post(openapi!("/statuses/{}/comments", sc.parent_comment_id)) - .pat_auth(token.as_str()) - .form(&sc) - .send() - .await? - .error_for_status()?; - Ok(r) -} - -/// 根据闪存ID获取评论 -pub async fn get(token: &str, status_id: &str) -> Result> { - let r = Client::new() - .get(openapi!("/statuses/{}/comments", status_id)) - .pat_auth(token) - .send() - .await? - .error_for_status()? - .json() - .await?; - Ok(r) -} - -/// 根据闪存ID和commentid删除评论 -pub async fn delete(token: &str, status_id: &str, comment_id: &str) -> Result<()> { - Client::new() - .delete(openapi!("/statuses/{}/comments/{}", status_id, comment_id)) - .pat_auth(token) - .send() - .await? - .error_for_status()?; - Ok(()) -} diff --git a/src/apis/ing/mod.rs b/src/apis/ing/mod.rs deleted file mode 100644 index eb6137b..0000000 --- a/src/apis/ing/mod.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! cnblogs 闪存接口模块 -//! -//! 实现封装[cnblogs Api](https://api.cnblogs.com/Help#0aee001a01835c83a3277a500ffc9040)中的`Statuses`。 -//! -//! - 获取自己最新一条闪存内容 https://api.cnblogs.com/api/statuses/recent -//! - 发布闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments -//! - 获取闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments -//! - 删除闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments/{id} -//! - 发布闪存 https://api.cnblogs.com/api/statuses -//! - 删除闪存 https://api.cnblogs.com/api/statuses/{id} -//! - 根据类型获取闪存列表 https://api.cnblogs.com/api/statuses/@{type}?pageIndex={pageIndex}&pageSize={pageSize}&tag={tag} -//! - 根据Id获取闪存 https://api.cnblogs.com/api/statuses/{id} -//! - -pub mod comment; - -use anyhow::{Ok, Result}; -use clap::{Parser, ValueEnum}; -use reqwest::{Client, Response}; -use serde::{Deserialize, Serialize}; - -use crate::{ - api_bak::ing::{IngSendFrom, get_list::IngEntry}, - infra::http::RequestBuilderExt, - openapi, -}; - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -#[serde(default)] -pub struct IngContent { - pub content: String, - pub is_private: bool, - pub lucky: bool, - pub client_type: IngSendFrom, -} - -impl Default for IngContent { - fn default() -> Self { - Self { - content: "".to_string(), - is_private: true, - lucky: false, - client_type: IngSendFrom::default(), - } - } -} - -/// 查询条件,用于根据类别查询 -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[serde(default)] -pub struct QeurySet { - #[serde(skip)] - pub r#type: QueryIngType, - pub page_index: u64, - pub page_size: u64, - #[serde(skip_serializing_if = "String::is_empty")] - pub tag: String, -} - -impl Default for QeurySet { - fn default() -> Self { - Self { - r#type: QueryIngType::default(), - page_index: 1, - page_size: 10, - tag: "".to_string(), - } - } -} - -/// -/// Follow = 1, 关注 -/// Myself = 4, 我的 -/// Public = 5, 全站 -/// RecentComment = 6, 新回应 -/// MyComment = 7, 我回应 -/// Tag = 10, tag 必填 -/// Comment = 13 回复我 -/// Mention = 14, @我 -#[derive(Debug, Default, Clone, ValueEnum, Parser)] -pub enum QueryIngType { - Following = 1, - My = 4, - #[default] - All = 5, - RecentComment = 6, - MyComment = 7, - Tag = 10, - Comment = 13, - Mention = 14, -} - -impl From for QueryIngType { - fn from(value: u8) -> Self { - match value { - 1 => Self::Following, - 4 => Self::My, - 6 => Self::RecentComment, - 7 => Self::MyComment, - 10 => Self::Tag, - 13 => Self::Comment, - 14 => Self::Mention, - _ => Self::All, - } - } -} - -impl QueryIngType { - const fn as_u8(&self) -> u8 { - match self { - Self::Following => 1, - Self::My => 4, - Self::All => 5, - Self::RecentComment => 6, - Self::MyComment => 7, - Self::Tag => 10, - Self::Mention => 14, - Self::Comment => 13, - } - } -} - -pub async fn lastest(token: &str) -> Result { - let c = Client::new() - .get(openapi!("/statuses/recent")) - .pat_auth(token) - .send() - .await? - .error_for_status()?; - Ok(c) -} - -/// 根据条件查询 -/// -/// 如果是tag是,一定要传入Tag,tag是自己想查询的比如Linux,Debian,Python等等。 -/// 页数是从1开始的 -pub async fn query(token: &str, q: &QeurySet) -> Result> { - let r = Client::new() - .get(openapi!("/statuses/@{}", q.r#type.as_u8())) - .pat_auth(token) - .query(&q) - .send() - .await? - .error_for_status()? - .json::>() - .await?; - Ok(r) -} - -/// 根据ID查询 -pub async fn query_by_id(token: &str, id: &u64) -> Result { - let r = Client::new() - .get(openapi!("/statuses/{}", id)) - .pat_auth(token) - .send() - .await? - .error_for_status()? - .json::() - .await?; - Ok(r) -} - -/// 发布一条闪存 -pub async fn post(token: &str, c: &IngContent) -> Result { - let r = Client::new() - .post(openapi!("/statuses")) - .pat_auth(token) - .json(c) - .send() - .await?; - Ok(r) -} - -/// 删除一条闪存 -pub async fn delete(token: &str, id: u64) -> Result { - let r = Client::new() - .post(openapi!("/statuses/{}", id)) - .pat_auth(token) - .send() - .await?; - Ok(r) -} diff --git a/src/apis/mod.rs b/src/apis/mod.rs deleted file mode 100644 index 67572a9..0000000 --- a/src/apis/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! cnblogs 闪存接口模块 -//! -//! 封装[cnblogs Api](https://api.cnblogs.com/Help#0aee001a01835c83a3277a500ffc9040)至以下模块中: -//! -//! - statuses: 闪存相关api。 -//! - blogs: 博客相关 -//! - news: 新闻相关 -//! - questions: 问题相关 -//! - edu: edu 相关 -//! - user: 用户相关 -//! - token: 认证相关 -//! - marks: 收藏相关 - -pub mod ing; -pub mod token; - -pub const OAUTH_CLIENT: &str = "https://api.cnblogs.com/token"; -pub const OAUTH_TOKEN: &str = "https://oauth.cnblogs.com/connect/token"; -pub const OAUTHORIZE: &str = "https://oauth.cnblogs.com/connect/authorize"; diff --git a/src/apis/token/mod.rs b/src/apis/token/mod.rs deleted file mode 100644 index be6b877..0000000 --- a/src/apis/token/mod.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Token -//! -//! TokenApi的封装 -//! -//! OAuth认证,提供两种方式接口。 -//! -//! 1. Client_Credentials -//! 2. Authorization_Code -//! - -use super::{OAUTH_CLIENT, OAUTH_TOKEN}; -use anyhow::Result; -use reqwest::Client; -use serde::{Deserialize, Serialize}; - -/// 认证授权后的授权 -/// -/// # Filed -/// -/// - access_token: Token String -/// - expires_in: 过期时间 -/// - token_type: Token认证方式 -/// - refresh_token: 过期后刷新Token,如果是ClientCredentials,此字段无用。 -/// - id_token: id,如果是ClientCredentials,此字段无用 -/// - scope: 客户端权限 -/// -#[derive(Debug, Serialize, Deserialize, Default)] -#[serde(default)] -pub struct OAuthToken { - pub id_token: String, - pub access_token: String, - pub expires_in: u64, - pub token_type: String, - pub refresh_token: String, - pub scope: String, -} - -#[derive(Serialize, Deserialize, Debug, Default)] -#[serde(default)] -pub struct ClientCredentialsReq { - pub client_id: String, - pub client_secret: String, - pub grant_type: String, -} - -pub async fn client_credentials(req: ClientCredentialsReq) -> Result { - let c = Client::new().post(OAUTH_CLIENT); - let r = c - .form(&req) - .send() - .await? - .error_for_status()? - .json::() - .await?; - Ok(r) -} - -/// OAuth 获取Token结构体 -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct OauthTokenReq { - // pub client_id: String, // string 是 授权ID client_id - // pub client_secret: String, // string 是 密钥 client_secret - // pub grant_type: String, // string 是 授权模式 authorization_code - #[serde(flatten)] - pub cc: ClientCredentialsReq, - pub code: String, // string 是 授权码 code - pub redirect_uri: String, // string 是 回调地址(默认) https://oauth.cnblogs.com/auth/callback -} - -impl OauthTokenReq { - pub fn new(client_id: String, client_secret: String, code: String) -> Self { - Self { - cc: ClientCredentialsReq { - client_id, - client_secret, - grant_type: "authorization_code".to_string(), - }, - code, - redirect_uri: "https://oauth.cnblogs.com/auth/callback".to_string(), - } - } -} - -/// 获取令牌 -pub async fn authorization_code(req: OauthTokenReq) -> Result { - let c = Client::new().post(OAUTH_TOKEN); - let r = c - .form(&req) - .send() - .await? - .error_for_status()? - .json::() - .await?; - Ok(r) -} - -/// OAuth 获取Token结构体 -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct RefreshTokenReq { - // pub client_id: String, - // pub client_secret: String, - // pub grant_type: String, - #[serde(flatten)] - pub cc: ClientCredentialsReq, - pub refresh_token: String, -} - -impl RefreshTokenReq { - pub fn new(client_id: String, client_secret: String, refresh_token: String) -> Self { - Self { - cc: ClientCredentialsReq { - client_id, - client_secret, - grant_type: "refresh_token".to_string(), - }, - refresh_token, - } - } -} - -/// 刷新令牌 -/// -/// 令牌过期后重新获取。 -pub async fn refresh_token(req: RefreshTokenReq) -> Result { - let c = Client::new().post(OAUTH_TOKEN); - let r = c - .form(&req) - .send() - .await? - .error_for_status()? - .json::() - .await?; - Ok(r) -} diff --git a/src/args/cmd/fav.rs b/src/args/cmd/fav.rs deleted file mode 100644 index 420755b..0000000 --- a/src/args/cmd/fav.rs +++ /dev/null @@ -1,12 +0,0 @@ -use clap::Parser; - -#[derive(Parser, Debug)] -#[non_exhaustive] -pub struct Opt { - #[arg(verbatim_doc_comment)] - /// Show favorite list, order by time in DESC - /// Example: cnb fav --list - #[arg(long)] - #[arg(short = 'l')] - pub list: bool, -} diff --git a/src/args/cmd/ing.rs b/src/args/cmd/ing.rs deleted file mode 100644 index 748d681..0000000 --- a/src/args/cmd/ing.rs +++ /dev/null @@ -1,182 +0,0 @@ -use crate::{ - api_bak::ing::{IngSendFrom, IngType}, - apis::{ - self, - ing::{IngContent, QeurySet}, - }, -}; -use clap::{Args, Parser, Subcommand, ValueEnum}; - -#[derive(Parser, Debug)] -#[non_exhaustive] -pub struct Opt { - #[command(subcommand)] - pub cmd: Option, - - #[arg(verbatim_doc_comment)] - /// Publish ing with specific content - /// Example: cnb ing --publish 'Hello world' - /// The visibility of ing is public - /// * - #[arg(long)] - #[arg(short = 'p')] - #[arg(visible_alias = "pub")] - #[arg(value_name = "CONTENT")] - pub publish: Option, - - #[arg(verbatim_doc_comment)] - /// Comment ing with specific content - /// Example: cnb --id 114514 ing --comment 'Hello world' - /// You should also specify the id of the ing via --id - #[arg(long)] - #[arg(short = 'c')] - #[arg(value_name = "CONTENT")] - pub comment: Option, -} - -#[derive(Debug, Subcommand)] -#[non_exhaustive] -pub enum Cmd { - #[clap(verbatim_doc_comment)] - /// Show ing list, order by time in DESC - /// Example: cnb ing list - /// * - #[clap(visible_alias = "l")] - List { - #[arg(verbatim_doc_comment)] - /// Ing type to show - /// Example: cnb ing list --type myself - /// * - #[arg(long)] - #[arg(value_name = "TYPE")] - #[arg(default_value = "public")] - r#type: Option, - - #[arg(verbatim_doc_comment)] - /// Align ing content to user name automatically - /// Example: cnb ing list --align - #[arg(long)] - #[arg(value_name = "BOOL")] - #[arg(default_value_t = true)] - align: bool, - }, - - /// 根据条件查询闪存。 - /// - Query(QueryIng), - - /// 创建闪存 - Create(CreateIng), - - /// 根据ID删除闪存 - Delete { id: Vec }, -} - -#[derive(Debug, Args)] -pub struct CreateIng { - /// 闪存内容 - pub content: String, - - /// 是否私有,默认是全站 - #[arg(short, long, default_value_t = false)] - pub private: bool, - - /// 是否发布为幸运 - #[arg(short, long, default_value_t = false)] - pub lucky: bool, - - /// 是否发布在某个标签下,默认不发布标签。 - #[arg(short, long, default_value = "")] - pub tag: String, -} - -impl From for IngContent { - fn from(value: CreateIng) -> Self { - let mut cont = String::new(); - if !value.tag.is_empty() { - cont.push_str(format!("[{}]", value.tag).as_str()) - } - cont.push_str(value.content.as_str()); - Self { - content: cont, - is_private: value.private, - lucky: value.lucky, - client_type: IngSendFrom::Cli, - } - } -} - -/// 查询参数 -/// 当使用type为 -#[derive(Debug, Args, Clone)] -pub struct QueryIng { - /// 查询类型 - #[arg( - short, - long, - value_name = "TYPE", - default_value_t = QueryType::F, - default_missing_value = "f", - value_enum - )] - pub r#type: QueryType, - /// 分页查询,起始索引是1 - #[arg(short('n'), long, default_value_t = 1)] - pub page_index: u64, - /// 分页查询数量, 默认是10 - #[arg(short('s'), long, default_value_t = 10)] - pub page_size: u64, - /// 按照标签查询 - #[arg(short('g'), long)] - pub tag: Option, - /// 根据ID查询 - #[arg(short, long)] - pub id: Option>, -} - -impl From<&QueryIng> for QeurySet { - fn from(value: &QueryIng) -> Self { - Self { - r#type: value.r#type.clone().into(), - page_index: value.page_index, - page_size: value.page_size, - tag: value.tag.clone().unwrap_or_default(), - } - } -} - -/// 过滤的类型 -#[derive(Debug, Clone, ValueEnum, Parser)] -pub enum QueryType { - /// 关注 - F = 1, - /// 我的 - My = 4, - /// 全站 - P = 5, - /// 新回应 - Rc = 6, - /// 我回应 - Mc = 7, - /// 按照Tag过滤,使用T时,如果没有Query::tag或者站点不存在,则不会有结果。 - T = 10, - /// 回复我 - C = 13, - /// 提到我 - M = 14, -} - -impl From for apis::ing::QueryIngType { - fn from(value: QueryType) -> Self { - match value { - QueryType::F => Self::Following, - QueryType::My => Self::My, - QueryType::P => Self::All, - QueryType::Rc => Self::RecentComment, - QueryType::Mc => Self::MyComment, - QueryType::T => Self::Tag, - QueryType::C => Self::Comment, - QueryType::M => Self::Mention, - } - } -} diff --git a/src/args/cmd/mod.rs b/src/args/cmd/mod.rs deleted file mode 100644 index e112102..0000000 --- a/src/args/cmd/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -pub mod fav; -pub mod ing; -pub mod news; -pub mod post; -pub mod user; - -use clap::Subcommand; - -#[derive(Debug, Subcommand)] -#[non_exhaustive] -pub enum Cmd { - /// User operations - #[clap(visible_alias = "u")] - User(user::Opt), - /// Ing operations - #[clap(visible_alias = "i")] - Ing(ing::Opt), - /// Post operations - #[clap(visible_alias = "p")] - Post(post::Opt), - /// News operations - #[clap(visible_alias = "n")] - News(news::Opt), - /// Favorite operations - #[clap(visible_alias = "f")] - Fav(fav::Opt), -} diff --git a/src/args/cmd/news.rs b/src/args/cmd/news.rs deleted file mode 100644 index f6aa52d..0000000 --- a/src/args/cmd/news.rs +++ /dev/null @@ -1,12 +0,0 @@ -use clap::Parser; - -#[derive(Parser, Debug)] -#[non_exhaustive] -pub struct Opt { - #[arg(verbatim_doc_comment)] - /// Show news list, order by time in DESC - /// Example: cnb news --list - #[arg(long)] - #[arg(short = 'l')] - pub list: bool, -} diff --git a/src/args/cmd/post.rs b/src/args/cmd/post.rs deleted file mode 100644 index a0ee3b9..0000000 --- a/src/args/cmd/post.rs +++ /dev/null @@ -1,151 +0,0 @@ -use clap::{Parser, Subcommand}; - -#[derive(Parser, Debug)] -#[non_exhaustive] -pub struct Opt { - #[clap(verbatim_doc_comment)] - /// Show title and content of a specific post - /// Example: cnb --id 114514 post --show - /// You should also specify the id of the post via --id - #[arg(long)] - #[arg(short = 's')] - pub show: bool, - - #[arg(verbatim_doc_comment)] - /// Show metadata of a specific post - /// Example: cnb --id 114514 post --show-meta - /// You should also specify the id of the post via --id - /// * - #[arg(long)] - #[arg(visible_alias = "sm")] - pub show_meta: bool, - - #[arg(verbatim_doc_comment)] - /// Show comment list of post, order by time in DESC - /// Example: cnb --id 114514 post --show-comment - /// You should also specify the id of the post via --id - /// * - #[arg(long)] - #[arg(visible_alias = "sc")] - pub show_comment: bool, - - #[arg(verbatim_doc_comment)] - /// Show post list, order by time in DESC - /// Example: cnb post --list - /// should in range [0,100] - /// If greater than 100, it will be set to 100 - #[arg(long)] - #[arg(short = 'l')] - pub list: bool, - - #[arg(verbatim_doc_comment)] - /// Delete post - /// Example: cnb --id 114514 post --delete - /// You should also specify the id of the post via --id - /// * - #[arg(long)] - #[arg(visible_alias = "del")] - pub delete: bool, - - #[command(subcommand)] - pub cmd: Option, -} - -#[derive(Parser, Debug)] -#[non_exhaustive] -pub struct CreateCmd { - #[arg(verbatim_doc_comment)] - /// Set post title - /// Example: cnb post create --title 'Title' --body 'Body' - #[arg(long)] - #[arg(value_name = "TITLE")] - pub title: String, - - #[arg(verbatim_doc_comment)] - /// Set post body - /// Example: cnb post create --title 'Title' --body 'Body' - #[arg(long)] - #[arg(value_name = "BODY")] - pub body: String, - - #[arg(verbatim_doc_comment)] - /// Set post status to publish - /// Example: cnb post create --title 'Title' --body 'Body' --publish - /// * - #[arg(long)] - #[arg(visible_alias = "pub")] - pub publish: bool, -} - -#[derive(Parser, Debug)] -#[non_exhaustive] -pub struct UpdateCmd { - #[arg(verbatim_doc_comment)] - /// Set post title - /// Example: cnb --id 114514 post update --title 'Title' - #[arg(long)] - #[arg(value_name = "TITLE")] - pub title: Option, - - #[arg(verbatim_doc_comment)] - /// Set post body - /// Example: cnb --id 114514 post update --body 'Body' - #[arg(long)] - #[arg(value_name = "BODY")] - pub body: Option, - - #[arg(verbatim_doc_comment)] - /// Set post publish state - /// Example: cnb --id 114514 post update --publish true - /// * - #[arg(long)] - #[arg(value_name = "BOOL")] - #[arg(visible_alias = "pub")] - pub publish: Option, -} - -#[derive(Parser, Debug)] -#[non_exhaustive] -pub struct SearchCmd { - #[arg(verbatim_doc_comment)] - /// Search self post - /// Example: cnb post search --self 'Keyword' - #[arg(long)] - #[arg(long = "self")] - #[arg(value_name = "KEYWORD")] - pub self_keyword: Option, - - #[arg(verbatim_doc_comment)] - /// Search site post - /// Example: cnb post search --site 'Keyword' - #[arg(long)] - #[arg(long = "site")] - #[arg(value_name = "KEYWORD")] - pub site_keyword: Option, -} - -#[derive(Debug, Subcommand)] -#[non_exhaustive] -pub enum Cmd { - #[clap(verbatim_doc_comment)] - /// Create post - /// Example: cnb post create --title 'Title' --body 'Body' - /// * - #[clap(visible_alias = "c")] - Create(CreateCmd), - - #[clap(verbatim_doc_comment)] - /// Update post - /// Example: cnb --id 114514 post update --title 'Title' - /// You should also specify the id of the post via --id - /// * - #[clap(visible_alias = "u")] - Update(UpdateCmd), - - #[clap(verbatim_doc_comment)] - /// Search post - /// Example: cnb post search --self 'Keyword' - /// * - #[clap(visible_alias = "s")] - Search(SearchCmd), -} diff --git a/src/args/cmd/user.rs b/src/args/cmd/user.rs deleted file mode 100644 index a38dff4..0000000 --- a/src/args/cmd/user.rs +++ /dev/null @@ -1,27 +0,0 @@ -use clap::Parser; - -#[derive(Parser, Debug)] -#[non_exhaustive] -pub struct Opt { - #[arg(verbatim_doc_comment)] - /// Login with your personal access token (PAT) - /// Example: cnb user --login 'FOOBARBAZ' - /// PAT will be saved in ~/.cnbrc - /// You can create PAT in https://account.cnblogs.com/tokens - #[arg(long)] - #[arg(value_name = "PAT")] - pub login: Option, - - #[arg(verbatim_doc_comment)] - /// Logout and remove ~/.cnbrc - /// Example: cnb user --logout - #[arg(long)] - pub logout: bool, - - #[arg(verbatim_doc_comment)] - /// Show user info - /// Example: cnb user --info - #[arg(long)] - #[arg(short = 'i')] - pub info: bool, -} diff --git a/src/args/mod.rs b/src/args/mod.rs deleted file mode 100644 index 57855c5..0000000 --- a/src/args/mod.rs +++ /dev/null @@ -1,126 +0,0 @@ -pub mod cmd; -pub mod parser; - -use crate::args::cmd::Cmd; -use clap::{Parser, ValueEnum}; - -#[derive(Clone, Debug, Parser, ValueEnum)] -pub enum Style { - Colorful, - Normal, - Json, -} - -#[derive(Clone, Debug, Parser, ValueEnum)] -pub enum TimeStyle { - Friendly, - Normal, -} - -#[derive(Parser, Debug)] -#[non_exhaustive] -pub struct GlobalOpt { - #[arg(verbatim_doc_comment)] - /// Execute with specific PAT - /// Example: cnb --with-pat 'FOOBARBAZ' post --list - /// Your PAT in ~/.cnbrc will be ignored in this execution if it exists - /// Please login if you don't want to input PAT everytime, try 'cnb user --help' for more details - #[arg(long)] - #[arg(value_name = "PAT")] - pub with_pat: Option, - - #[arg(verbatim_doc_comment)] - /// Execute in debug mode, this will print some messages for the developer - /// Example: cnb --debug ing list - /// THIS OPTION IS UNSTABLE FOREVER and any output from it may change in the future - /// You should NEVER rely on the output while you turn this option on - /// * - #[arg(long)] - #[clap(visible_alias = "dbg")] - pub debug: bool, - - #[arg(verbatim_doc_comment)] - /// Configure the output style - /// Example: cnb --style json ing list - /// * - #[arg(long)] - #[arg(value_enum)] - #[arg(hide_possible_values = true)] - #[arg(default_value_t = Style::Colorful)] - #[arg(value_name = "NAME")] - pub style: Style, - - #[arg(verbatim_doc_comment)] - /// Configure the time style - /// Example: cnb --style normal ing list - /// This option does not affect the output of '--style json' - /// * - #[arg(long)] - #[arg(value_enum)] - #[arg(hide_possible_values = true)] - #[arg(default_value_t = TimeStyle::Friendly)] - #[arg(value_name = "NAME")] - pub time_style: TimeStyle, - - #[arg(verbatim_doc_comment)] - /// Fail if error occurred - /// Example: cnb --fail-on-error ing list - /// * - #[arg(long)] - #[clap(visible_alias = "foe")] - #[arg(default_value_t = false)] - pub fail_on_error: bool, - - #[arg(verbatim_doc_comment)] - /// Suppress all output - /// Example: cnb --quiet ing list - /// * - #[arg(long)] - #[clap(visible_alias = "silent")] - #[arg(default_value_t = false)] - pub quiet: bool, -} - -#[derive(Parser, Debug)] -#[command(author, about, long_about = None, version)] -#[non_exhaustive] -pub struct Args { - #[command(subcommand)] - pub cmd: Option, - #[clap(flatten)] - pub global_opt: GlobalOpt, - - #[arg(verbatim_doc_comment)] - /// Provide ID required by other options - /// Example: cnb --id 114514 post --show - #[arg(long)] - pub id: Option, - - #[arg(verbatim_doc_comment)] - /// Reverse list output - /// Example: cnb --rev ing list - #[arg(long)] - pub rev: bool, - - #[arg(verbatim_doc_comment)] - /// Skip items while request list - /// Example: cnb --skip 2 ing list - /// Use this option to save network I/O if some items of the list output are not needed - /// If this option is required but not specified, it will be set to 0 - #[arg(long)] - #[arg(short = 's')] - #[arg(value_name = "LENGTH")] - pub skip: Option, - - #[arg(verbatim_doc_comment)] - /// Take items while request list - /// Example: cnb --take 2 ing list - /// Use this option to save network I/O if only a subset of the list output are required - /// should be in the range [0,100] - /// If is greater than 100, it will be set to 100 - /// If this option is required but not specified, it will be set to 8 - #[arg(long)] - #[arg(short = 't')] - #[arg(value_name = "LENGTH")] - pub take: Option, -} diff --git a/src/args/parser/fav.rs b/src/args/parser/fav.rs deleted file mode 100644 index 215650c..0000000 --- a/src/args/parser/fav.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::args::parser::{get_skip, get_take}; -use crate::args::{Args, Cmd, cmd}; -use crate::infra::option::WrapOption; - -pub fn list_fav(args: &Args) -> Option<(usize, usize)> { - match args { - Args { - cmd: Some(Cmd::Fav(cmd::fav::Opt { list: true })), - id: None, - rev: _, - skip, - take, - global_opt: _, - } => { - let skip = get_skip(skip); - let take = get_take(take); - (skip, take) - } - _ => return None, - } - .wrap_some() -} diff --git a/src/args/parser/ing.rs b/src/args/parser/ing.rs deleted file mode 100644 index 146a380..0000000 --- a/src/args/parser/ing.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crate::api_bak::ing::IngType; -use crate::args::cmd::ing::{CreateIng, QueryIng}; -use crate::args::parser::{get_skip, get_take}; -use crate::args::{Args, Cmd, cmd}; -use crate::infra::option::WrapOption; - -pub fn list_ing(args: &Args) -> Option<(usize, usize, IngType, bool)> { - match args { - Args { - cmd: - Some(Cmd::Ing(cmd::ing::Opt { - cmd: Some(cmd::ing::Cmd::List { r#type, align }), - publish: None, - comment: None, - })), - id: None, - rev: _, - skip, - take, - global_opt: _, - } => { - let skip = get_skip(skip); - let take = get_take(take); - let r#type = r#type.clone().unwrap_or(IngType::Public); - (skip, take, r#type, *align) - } - _ => return None, - } - .wrap_some() -} - -pub fn publish_ing(args: &Args) -> Option<&String> { - match args { - Args { - cmd: - Some(Cmd::Ing(cmd::ing::Opt { - cmd: None, - publish: Some(content), - comment: None, - })), - id: None, - rev: false, - skip: None, - take: None, - global_opt: _, - } => content, - _ => return None, - } - .wrap_some() -} - -pub fn comment_ing(args: &Args) -> Option<(&String, usize)> { - match args { - Args { - cmd: - Some(Cmd::Ing(cmd::ing::Opt { - cmd: None, - publish: None, - comment: Some(content), - })), - id: Some(id), - rev: false, - skip: None, - take: None, - global_opt: _, - } => (content, *id), - _ => return None, - } - .wrap_some() -} - -#[allow(unused)] -pub fn query(args: &Args) -> Option { - match args { - Args { - cmd: - Some(Cmd::Ing(cmd::ing::Opt { - cmd: - Some(cmd::ing::Cmd::Query(QueryIng { - r#type, - page_index, - page_size, - tag, - id, - })), - publish: None, - comment: None, - })), - id: None, - rev: _, - skip, - take, - global_opt: _, - } => QueryIng { - r#type: r#type.clone(), - page_index: *page_index, - page_size: *page_size, - tag: tag.clone(), - id: id.clone(), - }, - _ => return None, - } - .wrap_some() -} - -#[allow(unused)] -pub fn create_ing(args: &Args) -> Option { - match args { - Args { - cmd: - Some(Cmd::Ing(cmd::ing::Opt { - cmd: - Some(cmd::ing::Cmd::Create(CreateIng { - content, - private, - lucky, - tag, - })), - publish: None, - comment: None, - })), - id: None, - rev: _, - skip, - take, - global_opt: _, - } => CreateIng { - content: content.clone(), - private: *private, - lucky: *lucky, - tag: tag.clone(), - }, - _ => return None, - } - .wrap_some() -} - -#[allow(unused)] -pub fn delete(args: &Args) -> Option> { - match args { - Args { - cmd: - Some(Cmd::Ing(cmd::ing::Opt { - cmd: Some(cmd::ing::Cmd::Delete { id }), - publish: None, - comment: None, - })), - id: None, - rev: _, - skip, - take, - global_opt: _, - } => id.clone(), - _ => return None, - } - .wrap_some() -} diff --git a/src/args/parser/mod.rs b/src/args/parser/mod.rs deleted file mode 100644 index fe6ba72..0000000 --- a/src/args/parser/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub mod fav; -pub mod ing; -pub mod news; -pub mod post; -pub mod user; - -use crate::args::{Args, GlobalOpt}; - -fn get_skip(skip: &Option) -> usize { - skip.unwrap_or(0) -} - -fn get_take(take: &Option) -> usize { - take.unwrap_or(8).min(100) -} - -pub const fn no_operation(args: &Args) -> bool { - matches!( - args, - Args { - cmd: None, - id: None, - rev: false, - skip: None, - take: None, - global_opt: GlobalOpt { with_pat: None, .. } - } - ) -} diff --git a/src/args/parser/news.rs b/src/args/parser/news.rs deleted file mode 100644 index 96cc999..0000000 --- a/src/args/parser/news.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::args::parser::{get_skip, get_take}; -use crate::args::{Args, Cmd, cmd}; -use crate::infra::option::WrapOption; - -pub fn list_news(args: &Args) -> Option<(usize, usize)> { - match args { - Args { - cmd: Some(Cmd::News(cmd::news::Opt { list: true })), - id: None, - rev: _, - skip, - take, - global_opt: _, - } => { - let skip = get_skip(skip); - let take = get_take(take); - (skip, take) - } - _ => return None, - } - .wrap_some() -} diff --git a/src/args/parser/post.rs b/src/args/parser/post.rs deleted file mode 100644 index 3d75a2a..0000000 --- a/src/args/parser/post.rs +++ /dev/null @@ -1,231 +0,0 @@ -use crate::args::cmd::post::{CreateCmd, UpdateCmd}; -use crate::args::parser::{get_skip, get_take}; -use crate::args::{Args, Cmd, cmd}; -use crate::infra::option::WrapOption; - -pub fn list_post(args: &Args) -> Option<(usize, usize)> { - match args { - Args { - cmd: - Some(Cmd::Post(cmd::post::Opt { - show: false, - show_meta: false, - show_comment: false, - list: true, - delete: false, - cmd: None, - })), - id: None, - rev: _, - skip, - take, - global_opt: _, - } => { - let skip = get_skip(skip); - let take = get_take(take); - (skip, take) - } - _ => return None, - } - .wrap_some() -} - -pub fn show_post(args: &Args) -> Option { - match args { - Args { - cmd: - Some(Cmd::Post(cmd::post::Opt { - show: true, - show_meta: false, - show_comment: false, - list: false, - delete: false, - cmd: None, - })), - id: Some(id), - rev: false, - skip: None, - take: None, - global_opt: _, - } => *id, - _ => return None, - } - .wrap_some() -} - -pub fn show_post_meta(args: &Args) -> Option { - match args { - Args { - cmd: - Some(Cmd::Post(cmd::post::Opt { - show: false, - show_meta: true, - show_comment: false, - list: false, - delete: false, - cmd: None, - })), - id: Some(id), - rev: false, - skip: None, - take: None, - global_opt: _, - } => *id, - _ => return None, - } - .wrap_some() -} - -pub fn show_post_comment(args: &Args) -> Option { - match args { - Args { - cmd: - Some(Cmd::Post(cmd::post::Opt { - show: false, - show_meta: false, - show_comment: true, - list: false, - delete: false, - cmd: None, - })), - id: Some(id), - rev: _, - skip: None, - take: None, - global_opt: _, - } => *id, - _ => return None, - } - .wrap_some() -} - -pub fn search_self_post(args: &Args) -> Option<(&String, usize, usize)> { - match args { - Args { - cmd: - Some(Cmd::Post(cmd::post::Opt { - show: false, - show_meta: false, - show_comment: false, - list: false, - delete: false, - cmd: - Some(cmd::post::Cmd::Search(cmd::post::SearchCmd { - self_keyword: Some(keyword), - site_keyword: None, - })), - })), - id: None, - rev: _, - skip, - take, - global_opt: _, - } => { - let skip = get_skip(skip); - let take = get_take(take); - (keyword, skip, take) - } - _ => return None, - } - .wrap_some() -} - -pub fn search_site_post(args: &Args) -> Option<(&String, usize, usize)> { - match args { - Args { - cmd: - Some(Cmd::Post(cmd::post::Opt { - show: false, - show_meta: false, - show_comment: false, - list: false, - delete: false, - cmd: - Some(cmd::post::Cmd::Search(cmd::post::SearchCmd { - self_keyword: None, - site_keyword: Some(keyword), - })), - })), - id: None, - rev: _, - skip, - take, - global_opt: _, - } => { - let skip = get_skip(skip); - let take = get_take(take); - (keyword, skip, take) - } - _ => return None, - } - .wrap_some() -} - -pub fn delete_post(args: &Args) -> Option { - match args { - Args { - cmd: - Some(Cmd::Post(cmd::post::Opt { - show: false, - show_meta: false, - show_comment: false, - list: false, - delete: true, - cmd: None, - })), - id: Some(id), - rev: false, - skip: None, - take: None, - global_opt: _, - } => *id, - _ => return None, - } - .wrap_some() -} - -pub fn create_post(args: &Args) -> Option<&CreateCmd> { - match args { - Args { - cmd: - Some(Cmd::Post(cmd::post::Opt { - show: false, - show_meta: false, - show_comment: false, - list: false, - delete: false, - cmd: Some(cmd::post::Cmd::Create(cmd)), - })), - id: None, - rev: _, - skip: None, - take: None, - global_opt: _, - } => cmd, - _ => return None, - } - .wrap_some() -} - -pub fn update_post(args: &Args) -> Option<(usize, &UpdateCmd)> { - match args { - Args { - cmd: - Some(Cmd::Post(cmd::post::Opt { - show: false, - show_meta: false, - show_comment: false, - list: false, - delete: false, - cmd: Some(cmd::post::Cmd::Update(cmd)), - })), - id: Some(id), - rev: _, - skip: None, - take: None, - global_opt: _, - } => (*id, cmd), - _ => return None, - } - .wrap_some() -} diff --git a/src/args/parser/user.rs b/src/args/parser/user.rs deleted file mode 100644 index 63524ad..0000000 --- a/src/args/parser/user.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::args::{Args, Cmd, GlobalOpt, cmd}; -use crate::infra::option::WrapOption; - -pub fn login(args: &Args) -> Option<&String> { - match args { - Args { - cmd: - Some(Cmd::User(cmd::user::Opt { - login: Some(pat), - logout: false, - info: false, - })), - id: None, - rev: false, - skip: None, - take: None, - global_opt: GlobalOpt { with_pat: None, .. }, - } => pat, - _ => return None, - } - .wrap_some() -} - -pub const fn logout(args: &Args) -> bool { - matches!( - args, - Args { - cmd: Some(Cmd::User(cmd::user::Opt { - login: None, - logout: true, - info: false, - })), - id: None, - rev: false, - skip: None, - take: None, - global_opt: GlobalOpt { with_pat: None, .. }, - } - ) -} - -pub const fn user_info(args: &Args) -> bool { - matches!( - args, - Args { - cmd: Some(Cmd::User(cmd::user::Opt { - login: None, - logout: false, - info: true, - })), - id: None, - rev: false, - skip: None, - take: None, - global_opt: _, - } - ) -} diff --git a/src/bin/cnb.rs b/src/bin/cnb.rs index 5d5d438..3cc27a8 100644 --- a/src/bin/cnb.rs +++ b/src/bin/cnb.rs @@ -1,263 +1,13 @@ -#![feature(try_blocks)] -#![feature(if_let_guard)] -#![feature(iterator_try_collect)] -#![feature(iterator_try_reduce)] -#![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] - -extern crate cnblogs_lib; - -use anyhow::Result; use clap::Parser; -use clap::{Command, CommandFactory}; -use cnblogs_lib::api_bak::auth::session; -use cnblogs_lib::api_bak::fav::Fav; -use cnblogs_lib::api_bak::ing::Ing; -use cnblogs_lib::api_bak::news::News; -use cnblogs_lib::api_bak::post::Post; -use cnblogs_lib::api_bak::user::User; -use cnblogs_lib::args::cmd::post::{CreateCmd, UpdateCmd}; -use cnblogs_lib::args::parser::no_operation; -use cnblogs_lib::args::{Args, parser}; -use cnblogs_lib::infra::fp::currying::eq; -use cnblogs_lib::infra::infer::infer; -use cnblogs_lib::infra::iter::{ExactSizeIteratorExt, IntoIteratorExt}; -use cnblogs_lib::infra::option::OptionExt; -use cnblogs_lib::infra::result::WrapResult; -use cnblogs_lib::{display, logic}; -use colored::Colorize; -use std::env; - -fn show_non_printable_chars(text: String) -> String { - #[inline] - fn make_red(str: &str) -> String { - format!("{}", str.red()) - } - - text.replace(' ', &make_red("·")) - .replace('\0', &make_red("␀\0")) - .replace('\t', &make_red("␉\t")) - .replace('\n', &make_red("␊\n")) - .replace('\r', &make_red("␍\r")) - .replace("\r\n", &make_red("␍␊\r\n")) -} - -#[allow(clippy::missing_const_for_fn)] -fn panic_if_err(result: &Result) { - if let Err(e) = result { - panic!("{}", e) - } -} +use cnblogs_lib::context::Context; +use cnblogs_lib::{commands::Cli, logic}; #[tokio::main(flavor = "multi_thread")] -async fn main() -> Result<()> { - let args_vec = env::args().collect::>(); - if args_vec.iter().any(eq(&"--debug".to_owned())) { - dbg!(args_vec); - } - - let args: Args = Args::parse(); - let global_opt = &args.global_opt; - if global_opt.debug { - dbg!(&args); - } - - let pat = global_opt.with_pat.clone().or_eval_result(session::get_pat); - let style = &global_opt.style; - let time_style = &global_opt.time_style; - let rev = args.rev; - let foe = global_opt.fail_on_error; - - let output = match args { - _ if let Some(pat) = parser::user::login(&args) => { - let cfg_path = session::login(pat); - foe.then(|| panic_if_err(&cfg_path)); - display::login(style, &cfg_path) - } - _ if parser::user::logout(&args) => { - let cfg_path = session::logout(); - foe.then(|| panic_if_err(&cfg_path)); - display::logout(style, &cfg_path) - } - _ if parser::user::user_info(&args) => { - let user_info = User::new(pat?).get_info().await; - foe.then(|| panic_if_err(&user_info)); - display::user_info(style, &user_info)? - } - - _ if let Some(q) = parser::ing::query(&args) => { - let ing_with_comment_iter = - logic::ing::get_ings_and_comments(pat.unwrap().as_str(), &q) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); - - foe.then(|| panic_if_err(&ing_with_comment_iter)); - display::list_ing(style, time_style, ing_with_comment_iter, true)? - } - - _ if let Some(ids) = parser::ing::delete(&args) => { - let a = pat.as_ref().unwrap().as_str(); - logic::ing::delete_by_ing_id(a, ids).await; - "".to_string() - } - - _ if let Some(ci) = parser::ing::create_ing(&args) => { - let a = pat.as_ref().unwrap().as_str(); - logic::ing::create_ing_with_arg(a, ci).await; - "".to_string() - } - - _ if let Some((skip, take, r#type, align)) = parser::ing::list_ing(&args) => { - let ing_with_comment_iter = infer::>( - try { - let ing_api = Ing::new(pat?); - let ing_vec = ing_api.get_list(skip, take, &r#type).await?; - ing_vec - .into_iter() - .map(|ing| async { - let result = ing_api.get_comment_list(ing.id).await; - result.map(|comment_vec| (ing, comment_vec)) - }) - .join_all() - .await - .into_iter() - .collect::>>()? - }, - ) - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&ing_with_comment_iter)); - display::list_ing(style, time_style, ing_with_comment_iter, align)? - } - _ if let Some(content) = parser::ing::publish_ing(&args) => { - let content = try { - Ing::new(pat?).publish(content).await?; - content - }; - foe.then(|| panic_if_err(&content)); - display::publish_ing(style, &content) - } - _ if let Some((content, id)) = parser::ing::comment_ing(&args) => { - let content = try { - Ing::new(pat?) - .comment(id, content.clone(), None, None) - .await?; - content - }; - foe.then(|| panic_if_err(&content)); - display::comment_ing(style, &content) - } - _ if let Some(id) = parser::post::show_post(&args) => { - let entry = Post::new(pat?).get_one(id).await; - foe.then(|| panic_if_err(&entry)); - display::show_post(style, &entry)? - } - _ if let Some(id) = parser::post::show_post_meta(&args) => { - let entry = Post::new(pat?).get_one(id).await; - foe.then(|| panic_if_err(&entry)); - display::show_post_meta(style, time_style, &entry)? - } - _ if let Some(id) = parser::post::show_post_comment(&args) => { - let comment_iter = Post::new(pat?) - .get_comment_list(id) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&comment_iter)); - display::show_post_comment(style, time_style, comment_iter)? - } - _ if let Some((skip, take)) = parser::post::list_post(&args) => { - let meta_iter = Post::new(pat?) - .get_meta_list(skip, take) - .await - .map(|(vec, count)| (vec.into_iter().dyn_rev(rev), count)); - foe.then(|| panic_if_err(&meta_iter)); - display::list_post(style, meta_iter)? - } - _ if let Some(id) = parser::post::delete_post(&args) => { - let id = try { - Post::new(pat?).del_one(id).await?; - id - }; - foe.then(|| panic_if_err(&id)); - display::delete_post(style, &id) - } - _ if let Some((kw, skip, take)) = parser::post::search_self_post(&args) => { - let result = Post::new(pat?) - .search_self(skip, take, kw) - .await - .map(|(vec, count)| (vec.into_iter().dyn_rev(rev), count)); - foe.then(|| panic_if_err(&result)); - display::search_self_post(style, result)? - } - _ if let Some((kw, skip, take)) = parser::post::search_site_post(&args) => { - let result = Post::new(pat?) - .search_site(skip, take, kw) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&result)); - display::search_site_post(style, time_style, result)? - } - _ if let Some(create_cmd) = parser::post::create_post(&args) => { - let CreateCmd { - title, - body, - publish, - .. - } = create_cmd; - let id = Post::new(pat?).create(title, body, *publish).await; - foe.then(|| panic_if_err(&id)); - display::create_post(style, &id) - } - _ if let Some((id, update_cmd)) = parser::post::update_post(&args) => { - let UpdateCmd { - title, - body, - publish, - .. - } = update_cmd; - let id = Post::new(pat?).update(id, title, body, publish).await; - foe.then(|| panic_if_err(&id)); - display::update_post(style, &id) - } - _ if let Some((skip, take)) = parser::news::list_news(&args) => { - let news_iter = News::new(pat?) - .get_list(skip, take) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&news_iter)); - display::list_news(style, time_style, news_iter)? - } - _ if let Some((skip, take)) = parser::fav::list_fav(&args) => { - let fav_iter = Fav::new(pat?) - .get_list(skip, take) - .await - .map(|vec| vec.into_iter().dyn_rev(rev)); - foe.then(|| panic_if_err(&fav_iter)); - display::list_fav(style, time_style, fav_iter)? - } - - _ if no_operation(&args) => infer::(Args::command()).render_help().to_string(), - _ => "Invalid usage, follow '--help' for more information".to_owned(), - }; - - if global_opt.quiet { - return ().wrap_ok(); - } - - let output = { - let output = if output.ends_with("\n\n") { - output[..output.len() - 1].to_owned() - } else if output.ends_with('\n') { - output - } else { - format!("{}\n", output) - }; - if global_opt.debug { - show_non_printable_chars(output) - } else { - output - } - }; - - print!("{}", output); +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); - ().wrap_ok() + let mut ctx = Context::new()?; + ctx.json = cli.json; + logic::run(cli, &mut ctx).await?; + Ok(()) } diff --git a/src/display/colorful/fav.rs b/src/display/colorful/fav.rs deleted file mode 100644 index 3015690..0000000 --- a/src/display/colorful/fav.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::api_bak::fav::get_list::FavEntry; -use crate::args::TimeStyle; -use crate::display::colorful::fmt_err; -use crate::infra::result::WrapResult; -use crate::infra::str::StrExt; -use crate::infra::terminal::get_term_width; -use crate::infra::time::display_cnb_time; -use anyhow::Result; -use colored::Colorize; -use std::fmt::Write; -use std::ops::Not; - -pub fn list_fav( - time_style: &TimeStyle, - fav_iter: Result>, -) -> Result { - let fav_iter = match fav_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - fav_iter - .map(|fav| try { - let mut buf = String::new(); - { - let buf = &mut buf; - let create_time = display_cnb_time(&fav.create_time, time_style); - writeln!(buf, "{} {}", create_time.dimmed(), fav.url.dimmed()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf, " {}", fav.title).map_err(|e| anyhow::anyhow!("{}", e))?; - - let summary = { - fav.summary.width_split(get_term_width() - 4).map_or_else( - || fav.summary.clone(), - |vec| { - vec.into_iter() - .map(|line| format!(" {}", line)) - .collect::>() - .join("\n") - }, - ) - }; - if summary.is_empty().not() { - writeln!(buf, "{}", summary.dimmed()).map_err(|e| anyhow::anyhow!("{}", e))?; - } - } - buf - }) - .try_fold(String::new(), |mut acc: String, buf: Result| try { - writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; - acc - }) -} diff --git a/src/display/colorful/ing.rs b/src/display/colorful/ing.rs deleted file mode 100644 index dc26477..0000000 --- a/src/display/colorful/ing.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::api_bak::ing::get_comment_list::IngCommentEntry; -use crate::api_bak::ing::get_list::IngEntry; -use crate::api_bak::ing::{ - IngSendFrom, fmt_content, get_ing_at_user_tag_text, ing_star_tag_to_text, rm_ing_at_user_tag, -}; -use crate::args::TimeStyle; -use crate::display::colorful::fmt_err; -use crate::infra::result::WrapResult; -use crate::infra::str::StrExt; -use crate::infra::terminal::get_term_width; -use crate::infra::time::display_cnb_time; -use anyhow::Result; -use colored::Colorize; -use std::fmt::Write; -use std::ops::Not; -use unicode_width::UnicodeWidthStr; - -// TODO: rm unnecessary line divider -pub fn list_ing( - time_style: &TimeStyle, - ing_with_comment_iter: Result)>>, - align: bool, -) -> Result { - let mut ing_with_comment_iter = match ing_with_comment_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - ing_with_comment_iter.try_fold(String::new(), |mut buf, (ing, comment_list)| try { - { - let buf = &mut buf; - let create_time = display_cnb_time(&ing.create_time, time_style); - write!(buf, "{}", create_time.dimmed()).map_err(|e| anyhow::anyhow!("{}", e))?; - - let send_from_mark = match ing.send_from { - IngSendFrom::Cli => Some("CLI"), - IngSendFrom::CellPhone => Some("Mobile"), - IngSendFrom::VsCode => Some("VSCode"), - IngSendFrom::Web => Some("Web"), - _ => None, - }; - if let Some(mark) = send_from_mark { - write!(buf, " {}", mark.dimmed()).map_err(|e| anyhow::anyhow!("{}", e))?; - } - if ing.is_lucky { - let star_text = ing_star_tag_to_text(&ing.icons); - write!(buf, " {}⭐", star_text.yellow()).map_err(|e| anyhow::anyhow!("{}", e))?; - } - writeln!(buf, " {} {}", "#".dimmed(), ing.id.to_string().dimmed()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - let content = if align { - let user_name_width = ing.user_name.width_cjk(); - let left_width = get_term_width().saturating_sub(user_name_width + 3); - fmt_content(&ing.content) - .width_split(left_width) - .map_or_else( - || ing.content.clone(), - |lines| { - if comment_list.is_empty().not() { - lines.join("\n").replace( - '\n', - &format!("\n │{}", " ".repeat(user_name_width - 2)), - ) - } else { - lines.join("\n").replace( - '\n', - &format!("\n{}", " ".repeat(user_name_width + 3)), - ) - } - }, - ) - } else { - fmt_content(&ing.content) - }; - writeln!(buf, " {} {}", ing.user_name.cyan(), content) - .map_err(|e| anyhow::anyhow!("{}", e))?; - - let len = comment_list.len(); - if len != 0 { - let max_i = len - 1; - let comment_list_buf: Result = comment_list.iter().enumerate().try_fold( - String::new(), - |mut buf, (i, entry)| try { - { - let buf = &mut buf; - if i != max_i { - write!(buf, " │ {}", entry.user_name.blue()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } else { - write!(buf, " └ {}", entry.user_name.blue()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } - let at_user = get_ing_at_user_tag_text(&entry.content); - if at_user.is_empty().not() { - write!(buf, " {}{}", "@".bright_black(), at_user.bright_black()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } - let content = { - let content = rm_ing_at_user_tag(&entry.content); - fmt_content(&content) - }; - writeln!(buf, " {}", content.dimmed()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }, - ); - write!(buf, "{}", comment_list_buf?).map_err(|e| anyhow::anyhow!("{}", e))?; - } - - writeln!(buf).map_err(|e| anyhow::anyhow!("{}", e))?; - }; - buf - }) -} diff --git a/src/display/colorful/mod.rs b/src/display/colorful/mod.rs deleted file mode 100644 index ca1e682..0000000 --- a/src/display/colorful/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub mod fav; -pub mod ing; -pub mod news; -pub mod post; -pub mod user; - -use anyhow::Result; -use colored::Colorize; -use std::fmt::Display; - -#[inline] -pub fn fmt_err(e: &anyhow::Error) -> String { - format!("{}: {}", "Err".red(), e) -} - -#[inline] -pub fn fmt_result(result: &Result) -> String { - match result { - Ok(t) => format!("{}: {}", "Ok".green(), t), - Err(e) => fmt_err(e), - } -} diff --git a/src/display/colorful/news.rs b/src/display/colorful/news.rs deleted file mode 100644 index b7dc555..0000000 --- a/src/display/colorful/news.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::api_bak::news::get_list::NewsEntry; -use crate::args::TimeStyle; -use crate::display::colorful::fmt_err; -use crate::infra::result::WrapResult; -use crate::infra::str::StrExt; -use crate::infra::terminal::get_term_width; -use crate::infra::time::display_cnb_time; -use anyhow::Result; -use colored::Colorize; -use std::fmt::Write; - -pub fn list_news( - time_style: &TimeStyle, - news_iter: Result>, -) -> Result { - let news_iter = match news_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - news_iter - .map(|news| try { - let mut buf = String::new(); - { - let buf = &mut buf; - let create_time = display_cnb_time(&news.create_time, time_style); - let url = format!("https://news.cnblogs.com/n/{}", news.id); - writeln!(buf, "{} {}", create_time.dimmed(), url.dimmed()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf, " {}", news.title).map_err(|e| anyhow::anyhow!("{}", e))?; - - let summary = { - let summary = format!("{}...", news.summary); - summary.width_split(get_term_width() - 4).map_or_else( - || summary.clone(), - |vec| { - vec.into_iter() - .map(|line| format!(" {}", line)) - .collect::>() - .join("\n") - }, - ) - }; - writeln!(buf, "{}", summary.dimmed()).map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }) - .try_fold(String::new(), |mut acc: String, buf: Result| try { - writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; - acc - }) -} diff --git a/src/display/colorful/post.rs b/src/display/colorful/post.rs deleted file mode 100644 index 6e2a168..0000000 --- a/src/display/colorful/post.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crate::api_bak::post::get_comment_list::PostCommentEntry; -use crate::api_bak::post::get_one::PostEntry; -use crate::api_bak::post::search_site::SearchResultEntry; -use crate::args::TimeStyle; -use crate::display::colorful::fmt_err; -use crate::infra::result::WrapResult; -use crate::infra::time::display_cnb_time; -use anyhow::Result; -use colored::Colorize; -use std::fmt::Write; - -pub fn list_post( - result: Result<(impl ExactSizeIterator, usize)>, -) -> Result { - let (mut entry_iter, total_count) = match result { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - entry_iter.try_fold( - format!("{}/{}\n", entry_iter.len(), total_count), - |mut buf, entry| try { - { - let buf = &mut buf; - write!(buf, "{} {}", "#".dimmed(), entry.id.to_string().dimmed()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - if entry.is_published { - write!(buf, " {}", "Pub".green()).map_err(|e| anyhow::anyhow!("{}", e))?; - } else { - write!(buf, " {}", "Dft".yellow()).map_err(|e| anyhow::anyhow!("{}", e))?; - } - if entry.is_pinned { - write!(buf, " {}", "Pin".magenta()).map_err(|e| anyhow::anyhow!("{}", e))?; - } - write!(buf, " {}", entry.title.cyan().bold()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf).map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }, - ) -} - -pub fn show_post(entry: &Result) -> Result { - let entry = match entry { - Ok(entry) => entry, - Err(e) => return fmt_err(e).wrap_ok(), - }; - - let mut buf = String::new(); - { - let buf = &mut buf; - writeln!(buf, "{}\n", entry.title.cyan().bold())?; - if let Some(body) = &entry.body { - writeln!(buf, "{}", body)?; - } - } - buf.wrap_ok() -} - -pub fn show_post_meta(time_style: &TimeStyle, entry: &Result) -> Result { - let entry = match entry { - Ok(entry) => entry, - Err(e) => return fmt_err(e).wrap_ok(), - }; - - let mut buf = String::new(); - { - let buf = &mut buf; - writeln!(buf, "Title {}", entry.title.cyan().bold())?; - { - write!(buf, "Status")?; - if entry.is_published { - write!(buf, " {}", "Published".green())?; - } else { - write!(buf, " {}", "Draft".yellow())?; - } - if entry.is_pinned { - write!(buf, " {}", "Pinned".magenta())?; - } - writeln!(buf)?; - }; - if let Some(body) = &entry.body { - let words_count = words_count::count(body).words; - writeln!(buf, "Words {}", words_count)?; - } - if let Some(tags) = &entry.tags - && let Some(tags_text) = tags - .clone() - .into_iter() - .reduce(|acc, tag| format!("{}, {}", acc, tag)) - { - writeln!(buf, "Tags {}", tags_text)?; - } - let create_time = display_cnb_time(&entry.create_time, time_style); - writeln!(buf, "Create {}", create_time)?; - let modify_time = display_cnb_time(&entry.create_time, time_style); - writeln!(buf, "Modify {}", modify_time)?; - writeln!(buf, "Link https:{}", entry.url)?; - } - buf.wrap_ok() -} - -pub fn show_post_comment( - time_style: &TimeStyle, - comment_iter: Result>, -) -> Result { - let mut comment_iter = match comment_iter { - Ok(entry) => entry, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - comment_iter.try_fold(String::new(), |mut buf, comment| try { - { - let buf = &mut buf; - let create_time = display_cnb_time(&comment.create_time, time_style); - let floor_text = format!("{}F", comment.floor); - writeln!(buf, "{} {}", create_time.dimmed(), floor_text.dimmed()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf, " {} {}", comment.user_name.cyan(), comment.content) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }) -} - -pub fn search_self_post( - result: Result<(impl ExactSizeIterator, usize)>, -) -> Result { - let (mut id_iter, total_count) = match result { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - id_iter.try_fold( - format!("{}/{}\n", id_iter.len(), total_count), - |mut buf, id| try { - writeln!(&mut buf, "# {}", id).map_err(|e| anyhow::anyhow!("{}", e))?; - buf - }, - ) -} - -pub fn search_site_post( - time_style: &TimeStyle, - entry_iter: Result>, -) -> Result { - let entry_iter = match entry_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - entry_iter - .map(|entry| try { - let mut buf = String::new(); - { - let buf = &mut buf; - let create_time = display_cnb_time(&entry.create_time, time_style); - writeln!(buf, "{} {}", create_time.dimmed(), entry.url.dimmed()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf, " {}", entry.title).map_err(|e| anyhow::anyhow!("{}", e))?; - let view_vote_comment_count = format!( - "View {} Vote {} Comment {}", - entry.view_count, entry.vote_count, entry.comment_count - ); - writeln!(buf, " {}", view_vote_comment_count.dimmed()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }) - .try_fold(String::new(), |mut acc: String, buf: Result| try { - writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; - acc - }) -} diff --git a/src/display/colorful/user.rs b/src/display/colorful/user.rs deleted file mode 100644 index e15e313..0000000 --- a/src/display/colorful/user.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::api_bak::user::info::UserInfo; -use crate::display::colorful::fmt_err; -use crate::infra::result::WrapResult; -use anyhow::Result; -use colored::Colorize; -use std::fmt::Write; -use std::path::PathBuf; - -pub fn login(cfg_path: &Result) -> String { - match cfg_path { - Ok(pb) => format!("PAT was saved in {:?}", pb), - Err(e) => fmt_err(e), - } -} - -pub fn logout(cfg_path: &Result) -> String { - match cfg_path { - Ok(pb) => format!("{:?} was successfully removed", pb), - Err(e) => fmt_err(e), - } -} - -pub fn user_info(info: &Result) -> Result { - let info = match info { - Ok(info) => info, - Err(e) => return fmt_err(e).wrap_ok(), - }; - - let mut buf = String::new(); - { - let buf = &mut buf; - write!(buf, "{}", info.display_name.cyan())?; - if info.is_vip { - write!(buf, " {}", " VIP ".on_blue())?; - } - writeln!(buf)?; - writeln!( - buf, - "{} Following {} Followers", - info.following_count, info.followers_count - )?; - writeln!(buf, "ID {}", info.blog_id)?; - writeln!(buf, "Joined {}", info.joined)?; - writeln!(buf, "Blog https://www.cnblogs.com/{}", info.blog_app)?; - } - buf.wrap_ok() -} diff --git a/src/display/json/fav.rs b/src/display/json/fav.rs deleted file mode 100644 index ba6f9ab..0000000 --- a/src/display/json/fav.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::api_bak::fav::get_list::FavEntry; -use crate::display::json::{fmt_err, fmt_ok}; -use anyhow::Result; - -pub fn list_fav(fav_iter: Result>) -> String { - let fav_iter = match fav_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e), - }; - - let vec = fav_iter.collect::>(); - - fmt_ok(vec) -} diff --git a/src/display/json/ing.rs b/src/display/json/ing.rs deleted file mode 100644 index 45a02e9..0000000 --- a/src/display/json/ing.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::api_bak::ing::get_comment_list::IngCommentEntry; -use crate::api_bak::ing::get_list::IngEntry; -use crate::display::json::{fmt_err, fmt_ok}; -use anyhow::Result; -use serde_json::json; - -pub fn list_ing( - ing_with_comment_list: Result)>>, -) -> String { - let ing_with_comment_list = match ing_with_comment_list { - Ok(o) => o, - Err(e) => return fmt_err(&e), - }; - - let json_vec = ing_with_comment_list - .map(|(entry, comment_list)| { - json!({ - "entry": entry, - "comment_list": comment_list - }) - }) - .collect::>(); - - fmt_ok(json_vec) -} diff --git a/src/display/json/mod.rs b/src/display/json/mod.rs deleted file mode 100644 index 4a441f4..0000000 --- a/src/display/json/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub mod fav; -pub mod ing; -pub mod news; -pub mod post; -pub mod user; - -use anyhow::Result; -use serde::Serialize; -use serde_json::json; -use std::fmt::Display; - -#[inline] -pub fn fmt_ok(t: impl Serialize) -> String { - let json = json!({ - "is_ok": true, - "msg": t - }); - json.to_string() -} - -#[inline] -pub fn fmt_err(e: impl ToString) -> String { - let json = json!({ - "is_ok": false, - "msg": e.to_string() - }); - json.to_string() -} - -pub fn fmt_result(result: &Result) -> String { - match result { - Ok(t) => fmt_ok(t), - Err(e) => fmt_err(e), - } -} diff --git a/src/display/json/news.rs b/src/display/json/news.rs deleted file mode 100644 index 8c4efbb..0000000 --- a/src/display/json/news.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::api_bak::news::get_list::NewsEntry; -use crate::display::json::{fmt_err, fmt_ok}; -use anyhow::Result; - -pub fn list_news(news_iter: Result>) -> String { - let news_iter = match news_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e), - }; - - let vec = news_iter.collect::>(); - - fmt_ok(vec) -} diff --git a/src/display/json/post.rs b/src/display/json/post.rs deleted file mode 100644 index 8c77c53..0000000 --- a/src/display/json/post.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::api_bak::post::get_comment_list::PostCommentEntry; -use crate::api_bak::post::get_one::PostEntry; -use crate::api_bak::post::search_site::SearchResultEntry; -use crate::display::json::{fmt_err, fmt_ok, fmt_result}; -use anyhow::Result; -use serde_json::json; - -pub fn list_post(result: Result<(impl ExactSizeIterator, usize)>) -> String { - let (entry_iter, total_count) = match result { - Ok(o) => o, - Err(e) => return fmt_err(&e), - }; - - let vec = entry_iter.collect::>(); - let json = json!({ - "listed_count": vec.len(), - "total_count": total_count, - "entry_list": vec, - }); - fmt_ok(json) -} - -pub fn show_post(entry: &Result) -> String { - let json = entry.as_ref().map(|entry| { - json!({ - "title": entry.title, - "body": entry.body - }) - }); - fmt_result(&json) -} - -pub fn show_post_meta(entry: &Result) -> String { - fmt_result(entry) -} - -pub fn show_post_comment( - comment_iter: Result>, -) -> String { - let comment_iter = match comment_iter { - Ok(entry) => entry, - Err(e) => return fmt_err(&e), - }; - - let comment_vec = comment_iter.collect::>(); - fmt_ok(comment_vec) -} - -pub fn search_self_post(result: Result<(impl ExactSizeIterator, usize)>) -> String { - let (id_iter, total_count) = match result { - Ok(o) => o, - Err(e) => return fmt_err(&e), - }; - - let id_list = id_iter.collect::>(); - let json = json!({ - "listed_count": id_list.len(), - "total_count": total_count, - "id_list": id_list, - }); - fmt_ok(json) -} - -pub fn search_site_post( - entry_iter: Result>, -) -> String { - let entry_iter = match entry_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e), - }; - - let entry_vec = entry_iter.collect::>(); - fmt_ok(entry_vec) -} diff --git a/src/display/json/user.rs b/src/display/json/user.rs deleted file mode 100644 index b43c420..0000000 --- a/src/display/json/user.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::api_bak::user::info::UserInfo; -use crate::display::json::fmt_result; -use anyhow::Result; -use serde_json::json; -use std::path::PathBuf; - -pub fn login(cfg_path: &Result) -> String { - let json = cfg_path.as_ref().map(|pb| json!({"cfg_path":pb})); - fmt_result(&json) -} - -pub fn logout(cfg_path: &Result) -> String { - let json = cfg_path.as_ref().map(|pb| json!({"cfg_path":pb})); - fmt_result(&json) -} - -pub fn user_info(info: &Result) -> String { - fmt_result(info) -} diff --git a/src/display/mod.rs b/src/display/mod.rs index 07a289a..fab8107 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -1,190 +1 @@ -use crate::api_bak::fav::get_list::FavEntry; -use crate::api_bak::ing::get_comment_list::IngCommentEntry; -use crate::api_bak::ing::get_list::IngEntry; -use crate::api_bak::news::get_list::NewsEntry; -use crate::api_bak::post::get_comment_list::PostCommentEntry; -use crate::api_bak::post::get_one::PostEntry; -use crate::api_bak::post::search_site::SearchResultEntry; -use crate::api_bak::user::info::UserInfo; -use crate::args::{Style, TimeStyle}; -use crate::infra::result::WrapResult; -use anyhow::Result; -use serde::Serialize; -use std::fmt::Display; -use std::path::PathBuf; - -mod colorful; pub mod ing; -mod json; -mod normal; - -pub trait Format: Serialize + Display { - fn into_formats(self) -> String; -} - -pub fn login(style: &Style, cfg_path: &Result) -> String { - match style { - Style::Colorful => colorful::user::login(cfg_path), - Style::Normal => normal::user::login(cfg_path), - Style::Json => json::user::login(cfg_path), - } -} - -pub fn logout(style: &Style, cfg_path: &Result) -> String { - match style { - Style::Colorful => colorful::user::logout(cfg_path), - Style::Normal => normal::user::logout(cfg_path), - Style::Json => json::user::logout(cfg_path), - } -} - -pub fn user_info(style: &Style, user_info: &Result) -> Result { - match style { - Style::Colorful => colorful::user::user_info(user_info), - Style::Normal => normal::user::user_info(user_info), - Style::Json => json::user::user_info(user_info).wrap_ok(), - } -} - -pub fn list_ing( - style: &Style, - time_style: &TimeStyle, - ing_with_comment_iter: Result)>>, - align: bool, -) -> Result { - match style { - Style::Colorful => colorful::ing::list_ing(time_style, ing_with_comment_iter, align), - Style::Normal => normal::ing::list_ing(time_style, ing_with_comment_iter, align), - Style::Json => json::ing::list_ing(ing_with_comment_iter).wrap_ok(), - } -} - -pub fn publish_ing(style: &Style, result: &Result<&String>) -> String { - match style { - Style::Colorful => colorful::fmt_result(result), - Style::Normal => normal::fmt_result(result), - Style::Json => json::fmt_result(result), - } -} - -pub fn comment_ing(style: &Style, result: &Result<&String>) -> String { - match style { - Style::Colorful => colorful::fmt_result(result), - Style::Normal => normal::fmt_result(result), - Style::Json => json::fmt_result(result), - } -} - -pub fn show_post(style: &Style, entry: &Result) -> Result { - match style { - Style::Colorful => colorful::post::show_post(entry), - Style::Normal => normal::post::show_post(entry), - Style::Json => json::post::show_post(entry).wrap_ok(), - } -} - -pub fn list_post( - style: &Style, - result: Result<(impl ExactSizeIterator, usize)>, -) -> Result { - match style { - Style::Colorful => colorful::post::list_post(result), - Style::Normal => normal::post::list_post(result), - Style::Json => json::post::list_post(result).wrap_ok(), - } -} - -pub fn show_post_meta( - style: &Style, - time_style: &TimeStyle, - entry: &Result, -) -> Result { - match style { - Style::Colorful => colorful::post::show_post_meta(time_style, entry), - Style::Normal => normal::post::show_post_meta(time_style, entry), - Style::Json => json::post::show_post_meta(entry).wrap_ok(), - } -} - -pub fn show_post_comment( - style: &Style, - time_style: &TimeStyle, - comment_iter: Result>, -) -> Result { - match style { - Style::Colorful => colorful::post::show_post_comment(time_style, comment_iter), - Style::Normal => normal::post::show_post_comment(time_style, comment_iter), - Style::Json => json::post::show_post_comment(comment_iter).wrap_ok(), - } -} - -pub fn delete_post(style: &Style, result: &Result) -> String { - match style { - Style::Colorful => colorful::fmt_result(result), - Style::Normal => normal::fmt_result(result), - Style::Json => json::fmt_result(result), - } -} - -pub fn search_self_post( - style: &Style, - result: Result<(impl ExactSizeIterator, usize)>, -) -> Result { - match style { - Style::Colorful => colorful::post::search_self_post(result), - Style::Normal => normal::post::search_self_post(result), - Style::Json => json::post::search_self_post(result).wrap_ok(), - } -} - -pub fn search_site_post( - style: &Style, - time_style: &TimeStyle, - entry_iter: Result>, -) -> Result { - match style { - Style::Colorful => colorful::post::search_site_post(time_style, entry_iter), - Style::Normal => normal::post::search_site_post(time_style, entry_iter), - Style::Json => json::post::search_site_post(entry_iter).wrap_ok(), - } -} - -pub fn create_post(style: &Style, result: &Result) -> String { - match style { - Style::Colorful => colorful::fmt_result(result), - Style::Normal => normal::fmt_result(result), - Style::Json => json::fmt_result(result), - } -} - -pub fn update_post(style: &Style, result: &Result) -> String { - match style { - Style::Colorful => colorful::fmt_result(result), - Style::Normal => normal::fmt_result(result), - Style::Json => json::fmt_result(result), - } -} - -pub fn list_news( - style: &Style, - time_style: &TimeStyle, - news_iter: Result>, -) -> Result { - match style { - Style::Colorful => colorful::news::list_news(time_style, news_iter), - Style::Normal => normal::news::list_news(time_style, news_iter), - Style::Json => json::news::list_news(news_iter).wrap_ok(), - } -} - -pub fn list_fav( - style: &Style, - time_style: &TimeStyle, - fav_iter: Result>, -) -> Result { - match style { - Style::Colorful => colorful::fav::list_fav(time_style, fav_iter), - Style::Normal => normal::fav::list_fav(time_style, fav_iter), - Style::Json => json::fav::list_fav(fav_iter).wrap_ok(), - } -} diff --git a/src/display/normal/fav.rs b/src/display/normal/fav.rs deleted file mode 100644 index 30dda4d..0000000 --- a/src/display/normal/fav.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::api_bak::fav::get_list::FavEntry; -use crate::args::TimeStyle; -use crate::display::normal::fmt_err; -use crate::infra::result::WrapResult; -use crate::infra::str::StrExt; -use crate::infra::terminal::get_term_width; -use crate::infra::time::display_cnb_time; -use anyhow::Result; -use std::fmt::Write; -use std::ops::Not; - -pub fn list_fav( - time_style: &TimeStyle, - fav_iter: Result>, -) -> Result { - let fav_iter = match fav_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - fav_iter - .map(|fav| try { - let mut buf = String::new(); - { - let buf = &mut buf; - let create_time = display_cnb_time(&fav.create_time, time_style); - writeln!(buf, "{} {}", create_time, fav.url) - .map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf, " {}", fav.title).map_err(|e| anyhow::anyhow!("{}", e))?; - - let summary = { - fav.summary.width_split(get_term_width() - 4).map_or_else( - || fav.summary.clone(), - |vec| { - vec.into_iter() - .map(|line| format!(" {}", line)) - .collect::>() - .join("\n") - }, - ) - }; - if summary.is_empty().not() { - writeln!(buf, "{}", summary).map_err(|e| anyhow::anyhow!("{}", e))?; - } - } - buf - }) - .try_fold(String::new(), |mut acc: String, buf: Result| try { - writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; - acc - }) -} diff --git a/src/display/normal/ing.rs b/src/display/normal/ing.rs deleted file mode 100644 index 42c377f..0000000 --- a/src/display/normal/ing.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::api_bak::ing::get_comment_list::IngCommentEntry; -use crate::api_bak::ing::get_list::IngEntry; -use crate::api_bak::ing::{ - IngSendFrom, fmt_content, get_ing_at_user_tag_text, ing_star_tag_to_text, rm_ing_at_user_tag, -}; -use crate::args::TimeStyle; -use crate::display::normal::fmt_err; -use crate::infra::result::WrapResult; -use crate::infra::str::StrExt; -use crate::infra::terminal::get_term_width; -use crate::infra::time::display_cnb_time; -use anyhow::Result; -use std::fmt::Write; -use std::ops::Not; -use unicode_width::UnicodeWidthStr; - -// TODO: rm unnecessary line divider -pub fn list_ing( - time_style: &TimeStyle, - ing_with_comment_list: Result)>>, - align: bool, -) -> Result { - let mut ing_with_comment_list = match ing_with_comment_list { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - ing_with_comment_list.try_fold(String::new(), |mut buf, (ing, comment_list)| try { - { - let buf = &mut buf; - let create_time = display_cnb_time(&ing.create_time, time_style); - write!(buf, "{}", create_time).map_err(|e| anyhow::anyhow!("{}", e))?; - - let send_from_mark = match ing.send_from { - IngSendFrom::Cli => Some("CLI"), - IngSendFrom::CellPhone => Some("Mobile"), - IngSendFrom::VsCode => Some("VSCode"), - IngSendFrom::Web => Some("Web"), - _ => None, - }; - if let Some(mark) = send_from_mark { - write!(buf, " {}", mark).map_err(|e| anyhow::anyhow!("{}", e))?; - } - if ing.is_lucky { - let star_text = ing_star_tag_to_text(&ing.icons); - write!(buf, " {}★", star_text).map_err(|e| anyhow::anyhow!("{}", e))?; - } - writeln!(buf, " # {}", ing.id).map_err(|e| anyhow::anyhow!("{}", e))?; - let content = if align { - let user_name_width = ing.user_name.width_cjk(); - let left_width = get_term_width().saturating_sub(user_name_width + 3); - fmt_content(&ing.content) - .width_split(left_width) - .map_or_else( - || ing.content.clone(), - |lines| { - if comment_list.is_empty().not() { - lines.join("\n").replace( - '\n', - &format!("\n │{}", " ".repeat(user_name_width - 2)), - ) - } else { - lines.join("\n").replace( - '\n', - &format!("\n{}", " ".repeat(user_name_width + 3)), - ) - } - }, - ) - } else { - fmt_content(&ing.content) - }; - writeln!(buf, " {}: {}", ing.user_name, content) - .map_err(|e| anyhow::anyhow!("{}", e))?; - - let len = comment_list.len(); - if len != 0 { - let max_i = len - 1; - let comment_list_buf: Result = comment_list.iter().enumerate().try_fold( - String::new(), - |mut buf, (i, entry)| try { - { - let buf = &mut buf; - if i != max_i { - write!(buf, " │ {}", entry.user_name) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } else { - write!(buf, " └ {}", entry.user_name) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } - let at_user = get_ing_at_user_tag_text(&entry.content); - if at_user.is_empty().not() { - write!(buf, " @{}", at_user) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } - let content = { - let content = rm_ing_at_user_tag(&entry.content); - fmt_content(&content) - }; - writeln!(buf, ": {}", content).map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }, - ); - write!(buf, "{}", comment_list_buf?).map_err(|e| anyhow::anyhow!("{}", e))?; - } - - writeln!(buf).map_err(|e| anyhow::anyhow!("{}", e))?; - }; - buf - }) -} diff --git a/src/display/normal/mod.rs b/src/display/normal/mod.rs deleted file mode 100644 index 2c941b9..0000000 --- a/src/display/normal/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub mod fav; -pub mod ing; -pub mod news; -pub mod post; -pub mod user; - -use anyhow::Result; -use std::fmt::Display; - -#[inline] -pub fn fmt_err(e: &anyhow::Error) -> String { - format!("Err: {}", e) -} - -#[inline] -pub fn fmt_result(result: &Result) -> String { - match result { - Ok(t) => format!("Ok: {}", t), - Err(e) => fmt_err(e), - } -} diff --git a/src/display/normal/news.rs b/src/display/normal/news.rs deleted file mode 100644 index 0539c00..0000000 --- a/src/display/normal/news.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::api_bak::news::get_list::NewsEntry; -use crate::args::TimeStyle; -use crate::display::normal::fmt_err; -use crate::infra::result::WrapResult; -use crate::infra::str::StrExt; -use crate::infra::terminal::get_term_width; -use crate::infra::time::display_cnb_time; -use anyhow::Result; -use std::fmt::Write; - -pub fn list_news( - time_style: &TimeStyle, - news_iter: Result>, -) -> Result { - let news_iter = match news_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - news_iter - .map(|news| try { - let mut buf = String::new(); - { - let buf = &mut buf; - let create_time = display_cnb_time(&news.create_time, time_style); - let url = format!("https://news.cnblogs.com/n/{}", news.id); - writeln!(buf, "{} {}", create_time, url).map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf, " {}", news.title).map_err(|e| anyhow::anyhow!("{}", e))?; - - let summary = { - let summary = format!("{}...", news.summary); - summary.width_split(get_term_width() - 4).map_or_else( - || summary.clone(), - |vec| { - vec.into_iter() - .map(|line| format!(" {}", line)) - .collect::>() - .join("\n") - }, - ) - }; - writeln!(buf, "{}", summary).map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }) - .try_fold(String::new(), |mut acc: String, buf: Result| try { - writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; - acc - }) -} diff --git a/src/display/normal/post.rs b/src/display/normal/post.rs deleted file mode 100644 index 385e835..0000000 --- a/src/display/normal/post.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::api_bak::post::get_comment_list::PostCommentEntry; -use crate::api_bak::post::get_one::PostEntry; -use crate::api_bak::post::search_site::SearchResultEntry; -use crate::args::TimeStyle; -use crate::display::normal::fmt_err; -use crate::infra::result::WrapResult; -use crate::infra::time::display_cnb_time; -use anyhow::Result; -use std::fmt::Write; - -pub fn list_post( - result: Result<(impl ExactSizeIterator, usize)>, -) -> Result { - let (mut entry_iter, total_count) = match result { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - entry_iter.try_fold( - format!("{}/{}\n", entry_iter.len(), total_count), - |mut buf, entry| try { - { - let buf = &mut buf; - write!(buf, "# {}", entry.id).map_err(|e| anyhow::anyhow!("{}", e))?; - if entry.is_published { - write!(buf, " Pub").map_err(|e| anyhow::anyhow!("{}", e))?; - } else { - write!(buf, " Dft").map_err(|e| anyhow::anyhow!("{}", e))?; - } - if entry.is_pinned { - write!(buf, " Pin").map_err(|e| anyhow::anyhow!("{}", e))?; - } - write!(buf, " {}", entry.title).map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf).map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }, - ) -} - -pub fn show_post(entry: &Result) -> Result { - let entry = match entry { - Ok(entry) => entry, - Err(e) => return fmt_err(e).wrap_ok(), - }; - - let mut buf = String::new(); - { - let buf = &mut buf; - writeln!(buf, "{}\n", entry.title)?; - if let Some(body) = &entry.body { - writeln!(buf, "{}", body)?; - } - } - buf.wrap_ok() -} - -pub fn show_post_meta(time_style: &TimeStyle, entry: &Result) -> Result { - let entry = match entry { - Ok(entry) => entry, - Err(e) => return fmt_err(e).wrap_ok(), - }; - - let mut buf = String::new(); - { - let buf = &mut buf; - writeln!(buf, "Title {}", entry.title)?; - { - write!(buf, "Status")?; - if entry.is_published { - write!(buf, " Published")?; - } else { - write!(buf, " Draft")?; - } - if entry.is_pinned { - write!(buf, " Pinned")?; - } - writeln!(buf)?; - }; - if let Some(body) = &entry.body { - let words_count = words_count::count(body).words; - writeln!(buf, "Words {}", words_count)?; - } - if let Some(tags) = &entry.tags - && let Some(tags_text) = tags - .clone() - .into_iter() - .reduce(|acc, tag| format!("{}, {}", acc, tag)) - { - writeln!(buf, "Tags {}", tags_text)?; - } - let create_time = display_cnb_time(&entry.create_time, time_style); - writeln!(buf, "Create {}", create_time)?; - let modify_time = display_cnb_time(&entry.create_time, time_style); - writeln!(buf, "Modify {}", modify_time)?; - writeln!(buf, "Link https:{}", entry.url)?; - } - buf.wrap_ok() -} - -pub fn show_post_comment( - time_style: &TimeStyle, - comment_iter: Result>, -) -> Result { - let mut comment_iter = match comment_iter { - Ok(entry) => entry, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - comment_iter.try_fold(String::new(), |mut buf, comment| try { - { - let buf = &mut buf; - let create_time = display_cnb_time(&comment.create_time, time_style); - writeln!(buf, "{} {}F", create_time, comment.floor) - .map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf, " {} {}", comment.user_name, comment.content) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }) -} - -pub fn search_self_post( - result: Result<(impl ExactSizeIterator, usize)>, -) -> Result { - let (mut id_iter, total_count) = match result { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - id_iter.try_fold( - format!("{}/{}\n", id_iter.len(), total_count), - |mut buf, id| try { - writeln!(&mut buf, "# {}", id).map_err(|e| anyhow::anyhow!("{}", e))?; - buf - }, - ) -} - -pub fn search_site_post( - time_style: &TimeStyle, - entry_iter: Result>, -) -> Result { - let entry_iter = match entry_iter { - Ok(o) => o, - Err(e) => return fmt_err(&e).wrap_ok(), - }; - - entry_iter - .map(|entry| try { - let mut buf = String::new(); - { - let buf = &mut buf; - let create_time = display_cnb_time(&entry.create_time, time_style); - writeln!(buf, "{} {}", create_time, entry.url) - .map_err(|e| anyhow::anyhow!("{}", e))?; - writeln!(buf, " {}", entry.title).map_err(|e| anyhow::anyhow!("{}", e))?; - let view_vote_comment_count = format!( - "View {} Vote {} Comment {}", - entry.view_count, entry.vote_count, entry.comment_count - ); - writeln!(buf, " {}", view_vote_comment_count) - .map_err(|e| anyhow::anyhow!("{}", e))?; - } - buf - }) - .try_fold(String::new(), |mut acc: String, buf: Result| try { - writeln!(&mut acc, "{}", buf?).map_err(|e| anyhow::anyhow!("{}", e))?; - acc - }) -} diff --git a/src/display/normal/user.rs b/src/display/normal/user.rs deleted file mode 100644 index 82325e4..0000000 --- a/src/display/normal/user.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::api_bak::user::info::UserInfo; -use crate::display::normal::fmt_err; -use crate::infra::result::WrapResult; -use anyhow::Result; -use std::fmt::Write; -use std::path::PathBuf; - -pub fn login(cfg_path: &Result) -> String { - match cfg_path { - Ok(pb) => format!("PAT was saved in {:?}", pb), - Err(e) => fmt_err(e), - } -} - -pub fn logout(cfg_path: &Result) -> String { - match cfg_path { - Ok(pb) => format!("{:?} was successfully removed", pb), - Err(e) => fmt_err(e), - } -} - -pub fn user_info(info: &Result) -> Result { - let info = match info { - Ok(info) => info, - Err(e) => return fmt_err(e).wrap_ok(), - }; - - let mut buf = String::new(); - { - let buf = &mut buf; - write!(buf, "{}", info.display_name)?; - if info.is_vip { - write!(buf, " VIP")?; - } - writeln!(buf)?; - writeln!( - buf, - "{} Following {} Followers", - info.following_count, info.followers_count - )?; - writeln!(buf, "ID {}", info.blog_id)?; - writeln!(buf, "Joined {}", info.joined)?; - writeln!(buf, "Blog https://www.cnblogs.com/{}", info.blog_app)?; - } - buf.wrap_ok() -} diff --git a/src/infra/fp.rs b/src/infra/fp.rs deleted file mode 100644 index e421b12..0000000 --- a/src/infra/fp.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub mod currying { - #[inline] - pub fn id(x: X) -> impl Fn(X) -> X - where - X: Clone, - { - move |_| x.clone() - } - - #[inline] - pub fn eq(a: T) -> impl Fn(T) -> bool - where - T: PartialEq, - { - move |b| a == b - } - - #[inline] - pub fn lt(a: T) -> impl Fn(T) -> bool - where - T: PartialOrd, - { - move |b| a < b - } - - #[inline] - pub fn gt(a: T) -> impl Fn(T) -> bool - where - T: PartialOrd, - { - move |b| a > b - } -} diff --git a/src/infra/http.rs b/src/infra/http.rs deleted file mode 100644 index 6ff633b..0000000 --- a/src/infra/http.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::infra::result::WrapResult; -use anyhow::Result; -use anyhow::bail; -use reqwest::header::AUTHORIZATION; -use reqwest::{RequestBuilder, Response}; -use std::ops::Not; - -pub const AUTHORIZATION_TYPE: &str = "Authorization-Type"; -pub const PAT: &str = "pat"; - -#[macro_export] -macro_rules! bearer { - ($token:expr) => {{ format!("Bearer {}", $token) }}; -} - -#[macro_export] -macro_rules! basic { - ($token:expr) => {{ format!("Basic {}", $token) }}; -} - -pub trait RequestBuilderExt { - fn pat_auth(self, pat: &str) -> RequestBuilder; -} - -impl RequestBuilderExt for RequestBuilder { - fn pat_auth(self, pat: &str) -> RequestBuilder { - let builder = self.header(AUTHORIZATION, bearer!(pat)); - builder.header(AUTHORIZATION_TYPE, PAT) - } -} - -pub trait VecExt { - fn into_query_string(self) -> String; -} - -impl VecExt for Vec<(K, V)> { - fn into_query_string(self) -> String { - self.into_iter() - .map(|(k, v)| { - let s_k = k.to_string(); - let s_v = v.to_string(); - format!("{}={}", s_k, s_v) - }) - .fold(String::new(), |acc, q| format!("{acc}&{q}")) - } -} - -pub async fn unit_or_err(resp: Response) -> Result<()> { - let code = resp.status(); - let body = resp.text().await?; - - if code.is_success().not() { - bail!("{}: {}", code, body); - } - - ().wrap_ok() -} - -pub async fn body_or_err(resp: Response) -> Result { - let code = resp.status(); - let body = resp.text().await?; - - if code.is_success() { - body.wrap_ok() - } else { - bail!("{}: {}", code, body) - } -} diff --git a/src/infra/infer.rs b/src/infra/infer.rs deleted file mode 100644 index 2110108..0000000 --- a/src/infra/infer.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// Use this to infer type for val, as same as type ascription -pub const fn infer(val: T) -> T { - val -} diff --git a/src/infra/iter.rs b/src/infra/iter.rs deleted file mode 100644 index eef6322..0000000 --- a/src/infra/iter.rs +++ /dev/null @@ -1,47 +0,0 @@ -use futures::future::{JoinAll, join_all}; -use std::future::Future; - -pub trait IteratorExt: Iterator { - #[inline] - fn dyn_rev<'t>(self, rev: bool) -> Box + 't> - where - Self: DoubleEndedIterator + Sized + 't, - { - if rev { - Box::new(self.rev()) - } else { - Box::new(self) - } - } -} - -impl IteratorExt for I where I: Iterator {} - -pub trait ExactSizeIteratorExt: ExactSizeIterator { - #[inline] - fn dyn_rev<'t>(self, rev: bool) -> Box + 't> - where - Self: DoubleEndedIterator + Sized + 't, - { - if rev { - Box::new(self.rev()) - } else { - Box::new(self) - } - } -} - -impl ExactSizeIteratorExt for I where I: ExactSizeIterator {} - -pub trait IntoIteratorExt: IntoIterator { - #[inline] - fn join_all(self) -> JoinAll - where - Self::Item: Future, - Self: Sized, - { - join_all(self) - } -} - -impl IntoIteratorExt for I where I: IntoIterator {} diff --git a/src/infra/json.rs b/src/infra/json.rs deleted file mode 100644 index cf93cde..0000000 --- a/src/infra/json.rs +++ /dev/null @@ -1,21 +0,0 @@ -use anyhow::{Result, anyhow}; -use serde::Serialize; -use serde::de::DeserializeOwned; -use serde_json::Value; - -pub fn serialize(val: T) -> Result -where - T: Serialize, -{ - serde_json::to_value::(val) - .map_err(|e| anyhow!(e)) - .map(|v| v.to_string()) -} - -pub fn deserialize(json: &str) -> Result -where - T: DeserializeOwned, -{ - let val: Value = serde_json::from_str(json)?; - serde_json::from_value::(val).map_err(|e| anyhow!(e)) -} diff --git a/src/infra/mod.rs b/src/infra/mod.rs deleted file mode 100644 index 3c483e1..0000000 --- a/src/infra/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod fp; -pub mod http; -pub mod infer; -pub mod iter; -pub mod json; -pub mod option; -pub mod result; -pub mod str; -pub mod terminal; -pub mod time; -pub mod vec; diff --git a/src/infra/option.rs b/src/infra/option.rs deleted file mode 100644 index e93f9d8..0000000 --- a/src/infra/option.rs +++ /dev/null @@ -1,27 +0,0 @@ -pub trait WrapOption -where - Self: Sized, -{ - #[inline] - fn wrap_some(self) -> Option { - Some(self) - } -} - -impl WrapOption for T {} - -pub trait OptionExt { - fn or_eval_result(self, f: F) -> Result - where - F: FnOnce() -> Result; -} - -impl OptionExt for Option { - #[inline] - fn or_eval_result(self, f: F) -> Result - where - F: FnOnce() -> Result, - { - self.map_or_else(f, |val| Ok(val)) - } -} diff --git a/src/infra/result.rs b/src/infra/result.rs deleted file mode 100644 index 0aad5f3..0000000 --- a/src/infra/result.rs +++ /dev/null @@ -1,52 +0,0 @@ -use anyhow::Result; - -pub trait WrapResult -where - Self: Sized, -{ - #[inline] - fn wrap_ok(self) -> Result { - Ok(self) - } - #[inline] - fn wrap_err(self) -> Result { - Err(self) - } -} - -impl WrapResult for T {} - -pub type HomoResult = Result; - -pub trait ResultExt { - fn err_to_string(self) -> Result - where - E: ToString; - - fn homo_string(self) -> HomoResult - where - O: ToString, - E: ToString; -} - -impl ResultExt for Result { - #[inline] - fn err_to_string(self) -> Result - where - E: ToString, - { - self.map_err(|e| e.to_string()) - } - - #[inline] - fn homo_string(self) -> HomoResult - where - O: ToString, - E: ToString, - { - match self { - Ok(o) => Ok(o.to_string()), - Err(e) => Err(e.to_string()), - } - } -} diff --git a/src/infra/str.rs b/src/infra/str.rs deleted file mode 100644 index 046120b..0000000 --- a/src/infra/str.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::ops::ControlFlow; -use unicode_width::UnicodeWidthChar; - -pub trait StrExt { - fn width_split_head(&self, head_width: usize) -> (&str, &str); - fn width_split(&self, width: usize) -> Option>; -} - -impl StrExt for str { - fn width_split_head(&self, head_width: usize) -> (&str, &str) { - let mut left_take = head_width; - let mut take_bytes = 0; - let _ = self.chars().try_for_each(|c| { - let current_width = c.width_cjk().unwrap_or(0); - if left_take > 0 { - if left_take >= current_width { - left_take -= current_width; - take_bytes += c.len_utf8(); - ControlFlow::Continue(()) - } else { - left_take = 0; - ControlFlow::Break(()) - } - } else { - ControlFlow::Break(()) - } - }); - self.split_at(take_bytes) - } - - fn width_split(&self, width: usize) -> Option> { - let mut vec = vec![]; - let mut str = self; - loop { - let (head, tail) = str.width_split_head(width); - // No split strategy exist, return None - if head.is_empty() { - return None; - } - vec.push(head); - if tail.is_empty() { - break; - } - str = tail; - } - Some(vec) - } -} - -#[test] -fn test_width_split_head() { - let text = "测试test⭐"; - assert_eq!(text.width_split_head(0), ("", "测试test⭐")); - assert_eq!(text.width_split_head(1), ("", "测试test⭐")); - assert_eq!(text.width_split_head(2), ("测", "试test⭐")); - assert_eq!(text.width_split_head(3), ("测", "试test⭐")); - assert_eq!(text.width_split_head(4), ("测试", "test⭐")); - assert_eq!(text.width_split_head(5), ("测试t", "est⭐")); - assert_eq!(text.width_split_head(9), ("测试test", "⭐")); - assert_eq!(text.width_split_head(10), ("测试test⭐", "")); - assert_eq!(text.width_split_head(11), ("测试test⭐", "")); -} - -#[test] -fn test_width_split() { - use crate::infra::option::WrapOption; - let text = "测试test⭐测试test⭐"; - assert_eq!(text.width_split(0), None); - assert_eq!(text.width_split(1), None); - assert_eq!( - text.width_split(2), - vec!["测", "试", "te", "st", "⭐", "测", "试", "te", "st", "⭐"].wrap_some() - ); - assert_eq!( - text.width_split(3), - vec!["测", "试t", "est", "⭐", "测", "试t", "est", "⭐"].wrap_some() - ); - assert_eq!( - text.width_split(4), - vec!["测试", "test", "⭐测", "试te", "st⭐"].wrap_some() - ); - assert_eq!( - text.width_split(19), - vec!["测试test⭐测试test", "⭐"].wrap_some() - ); - assert_eq!( - text.width_split(20), - vec!["测试test⭐测试test⭐"].wrap_some() - ); - assert_eq!( - text.width_split(21), - vec!["测试test⭐测试test⭐"].wrap_some() - ); -} diff --git a/src/infra/terminal.rs b/src/infra/terminal.rs deleted file mode 100644 index 88d2d17..0000000 --- a/src/infra/terminal.rs +++ /dev/null @@ -1,6 +0,0 @@ -use terminal_size::{Width, terminal_size}; - -pub fn get_term_width() -> usize { - let (Width(width), _) = terminal_size().expect("Can not get terminal size"); - width as usize -} diff --git a/src/infra/time.rs b/src/infra/time.rs deleted file mode 100644 index aeb5faf..0000000 --- a/src/infra/time.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::args::TimeStyle; -use chrono::{DateTime, Datelike, Local, TimeZone, Timelike, Utc}; -use std::fmt::Display; - -pub fn display_cnb_time(time_str: &str, time_style: &TimeStyle) -> String { - let rfc3339 = patch_rfc3339(time_str); - let dt = DateTime::parse_from_rfc3339(&rfc3339) - .unwrap_or_else(|_| panic!("Invalid RFC3339: {}", rfc3339)) - .with_timezone(&Utc); - - match time_style { - TimeStyle::Friendly => fmt_time_to_string_friendly(dt.into(), Local::now()), - TimeStyle::Normal => dt.format("%y-%-m-%-d %-H:%M").to_string(), - } -} - -// HACK: -// Sometimes cnblogs' web API returns time string like: "2023-09-12T14:07:00" or "2019-02-06T08:45:53.94" -// This will patch it to standard RFC3339 format -fn patch_rfc3339(time_str: &str) -> String { - if time_str.len() != 25 { - let u8vec: Vec<_> = time_str.bytes().take(19).collect(); - format!( - "{}+08:00", - String::from_utf8(u8vec) - .unwrap_or_else(|_| panic!("Can not patch time string: {}", time_str)) - ) - } else { - time_str.to_owned() - } -} - -fn fmt_time_to_string_friendly(time_to_fmt: DateTime, current_time: DateTime) -> String -where - T: TimeZone, - ::Offset: Display, -{ - let diff = current_time.clone() - time_to_fmt.clone(); - match diff { - // In the future - _ if diff.num_milliseconds() < 0 => time_to_fmt.format("%y-%-m-%-d %-H:%M").to_string(), - // Same year... - _ if time_to_fmt.year() != current_time.year() => { - time_to_fmt.format("%Y-%m-%d").to_string() - } - _ if time_to_fmt.month() != current_time.month() => { - time_to_fmt.format("%m-%d %H:%M").to_string() - } - _ if time_to_fmt.day() != current_time.day() => { - let postfix = match time_to_fmt.day() { - 1 => "st", - 2 => "nd", - 3 => "rd", - _ => "th", - }; - time_to_fmt - .format(&format!("%d{} %H:%M", postfix)) - .to_string() - } - _ if time_to_fmt.hour() != current_time.hour() => time_to_fmt.format("%H:%M").to_string(), - // Within an hour - _ if diff.num_seconds() < 30 => "Now".to_owned(), - _ if diff.num_minutes() < 3 => "Recently".to_owned(), - _ if diff.num_minutes() < 30 => format!("{}m", diff.num_minutes()), - _ => time_to_fmt.format("%H:%M").to_string(), - } -} diff --git a/src/infra/vec.rs b/src/infra/vec.rs deleted file mode 100644 index 06d70c1..0000000 --- a/src/infra/vec.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub trait VecExt { - fn chain_push(self, item: T) -> Vec; -} - -impl VecExt for Vec { - #[inline] - fn chain_push(mut self, item: T) -> Self { - self.push(item); - self - } -} diff --git a/src/lib.rs b/src/lib.rs index c9b110c..776a72f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,11 @@ -#![feature(try_blocks)] -#![feature(if_let_guard)] -#![feature(iterator_try_collect)] -#![feature(iterator_try_reduce)] -#![warn(clippy::nursery, clippy::cargo_common_metadata)] +//! +//! 博客园 API和cli的封装 +//! pub mod api; -pub mod api_bak; -pub mod apis; -pub mod args; pub mod commands; pub mod context; pub mod display; -pub mod infra; pub mod logic; pub mod models; pub mod tools; diff --git a/src/logic/ing.rs b/src/logic/ing.rs index a587508..8c0b65b 100644 --- a/src/logic/ing.rs +++ b/src/logic/ing.rs @@ -1,3 +1,4 @@ +//! //! 闪存相关逻辑 //! @@ -7,95 +8,11 @@ use crate::context::Context; use crate::tools::http::IntoNoParseResult; use crate::{ api, - api_bak::{ - self, - ing::{get_comment_list::IngCommentEntry, get_list::IngEntry}, - }, - apis::ing::{comment, delete, post, query as iq, query_by_id}, - args::cmd::ing::{CreateIng, QueryIng}, commands::ing::{ IngAction, IngContent, IngDelete, IngListArg, IngReplayContent, IngShowDetail, }, - infra::iter::IntoIteratorExt, }; -/// 根据queryset查询 -/// TODO: 提到我和我评论的解析存在问题。 -pub async fn get_ings_and_comments( - t: &str, - q: &QueryIng, -) -> Result)>> { - if let Some(ids) = &q.id { - let a = ids - .iter() - .map(|id| async move { query_by_id(t, id).await }) - .join_all() - .await - .into_iter() - .filter(|x| { - if x.is_err() { - eprintln!("{}", x.as_ref().err().unwrap()) - } - x.is_ok() - }) - .map(|x| x.unwrap()) - .collect::>(); - - get_ing_comments(t, a).await - } else { - let a = iq(t, &q.into()) - .await? - .into_iter() - .collect::>(); - get_ing_comments(t, a).await - } -} - -// TODO: 分类细化 -/// 初步提取公共部分 -pub async fn get_ing_comments( - t: &str, - i: Vec, -) -> Result)>> { - let a = i - .into_iter() - .map(|ing| async { - let result = comment::get(t, ing.id.to_string().as_str()).await; - result.map(|comment_vec| (ing, comment_vec)) - }) - .join_all() - .await - .into_iter() - .collect::>>()?; - Ok(a) -} - -/// 通过ID删除 -pub async fn delete_by_ing_id(t: &str, ids: Vec) { - ids.into_iter() - .map(|id| async move { delete(t, id).await }) - .join_all() - .await - .iter() - .for_each(|x| { - if x.is_err() { - eprintln!("{:?}", x.as_ref().err().unwrap().to_string()) - } - }); -} - -/// 创建闪存 -pub async fn create_ing_with_arg(t: &str, c: CreateIng) { - let cc = c.into(); - let e = post(t, &cc).await; - - if e.is_err() { - eprintln!("{:?}", e.as_ref().err().unwrap().to_string()); - } else { - println!("🙈 ! {:?}", cc.content); - } -} - pub async fn endpoint(cmd: IngAction, ctx: &mut Context) -> Result<()> { match cmd { IngAction::List(action) => handle_list_action(action, ctx).await?, From 8e4d726675b5fd1b87e6ea3d3bc7cc86e144ca46 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 04:25:49 +0800 Subject: [PATCH 38/48] build: switch Rust toolchain from nightly to stable --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f081769..85bd069 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] profile = "minimal" -channel = "nightly-2026-01-10" +channel = "stable" components = [ "rustfmt", "clippy" ] From 30f0239f7f60f89b0fa5e2030d1a2bc498759cb2 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 04:31:19 +0800 Subject: [PATCH 39/48] chore(deps): remove unused dependencies --- Cargo.lock | 437 ++++++----------------------------------------------- Cargo.toml | 58 +++---- 2 files changed, 71 insertions(+), 424 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2a93e8..bf35e16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.2" +version = "1.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86" dependencies = [ "aws-lc-sys", "zeroize", @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8" dependencies = [ "cc", "cmake", @@ -110,27 +110,12 @@ dependencies = [ "fs_extra", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64url" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33de68096bac8e252e45589f42afd364c1dd28fbb3466ed726a941d5b9727d2c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "bitflags" version = "2.10.0" @@ -151,9 +136,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -181,9 +166,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -250,32 +235,19 @@ dependencies = [ "anstream", "anstyle", "anyhow", - "base64 0.22.1", - "base64url", "chrono", "clap", - "colored", "const_format", - "futures", - "getrandom 0.3.4", "home", "html2md", "lazy_static", - "mime", "owo-colors", - "rand 0.9.2", "regex", "reqwest", "serde", "serde_json", - "serde_qs", - "serde_repr", - "serde_with", "termimad", - "terminal_size", "tokio", - "unicode-width 0.2.2", - "words-count", ] [[package]] @@ -284,15 +256,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "colored" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "combine" version = "4.6.7" @@ -476,51 +439,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "deranged" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", - "serde_core", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -569,12 +487,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - [[package]] name = "encoding_rs" version = "0.8.35" @@ -602,9 +514,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "fnv" @@ -637,21 +549,6 @@ dependencies = [ "new_debug_unreachable", ] -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.31" @@ -659,7 +556,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -668,34 +564,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "futures-sink" version = "0.3.31" @@ -714,13 +582,8 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -765,19 +628,13 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap", "slab", "tokio", "tokio-util", "tracing", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.16.1" @@ -790,12 +647,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "home" version = "0.5.12" @@ -916,7 +767,7 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-core", @@ -1041,12 +892,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.1.0" @@ -1068,17 +913,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.13.0" @@ -1086,9 +920,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", + "hashbrown", ] [[package]] @@ -1167,9 +999,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -1320,12 +1152,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-traits" version = "0.2.19" @@ -1349,9 +1175,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl-probe" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "owo-colors" @@ -1447,12 +1273,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1491,7 +1311,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -1513,7 +1333,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -1601,26 +1421,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "regex" version = "1.12.2" @@ -1656,7 +1456,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", @@ -1761,9 +1561,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -1798,9 +1598,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -1838,30 +1638,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -1940,28 +1716,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_qs" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" -dependencies = [ - "percent-encoding", - "serde", - "thiserror 2.0.17", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1974,37 +1728,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.13.0", - "schemars 0.9.0", - "schemars 1.2.0", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "shlex" version = "1.3.0" @@ -2194,8 +1917,8 @@ dependencies = [ "lazy-regex", "minimad", "serde", - "thiserror 2.0.17", - "unicode-width 0.1.14", + "thiserror 2.0.18", + "unicode-width", ] [[package]] @@ -2219,11 +1942,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2239,46 +1962,15 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "time" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" - -[[package]] -name = "time-macros" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinystr" version = "0.8.2" @@ -2425,12 +2117,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "unicode-blocks" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b12e05d9e06373163a9bb6bb8c263c261b396643a99445fe6b9811fd376581b" - [[package]] name = "unicode-ident" version = "1.0.22" @@ -2449,12 +2135,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -2524,18 +2204,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -2546,11 +2226,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -2559,9 +2240,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2569,9 +2250,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -2582,18 +2263,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -2737,15 +2418,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" @@ -2952,18 +2624,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "words-count" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d28653ddaede5475c44a03e4014ae19f35aa9b231c423228b28963cb873e4869" -dependencies = [ - "unicode-blocks", -] +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -3087,6 +2750,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml index 615224f..15ac92b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,51 +10,35 @@ keywords = ["cli", "cnblogs", "blog"] categories = ["command-line-utilities"] default-run = "cnb" -[profile.dev] -lto = true -strip = true +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[profile.release] -lto = true -strip = true -codegen-units = 1 +[[bin]] +name = "cnb" +path = "src/bin/cnb.rs" [dependencies] +anstream = "0.6.21" +anstyle = "1.0.13" anyhow = "1.0.100" -lazy_static = "1.5.0" -base64 = "0.22.1" -base64url = "0.1.0" -getrandom = "0.3.4" -rand = { version = "0.9.2" } -regex = "1.12.2" -words-count = "0.1.6" -unicode-width = "0.2.2" - -serde = { version = "1.0.228", features = ["derive"] } -serde_qs = "0.15.0" -serde_json = "1.0.149" -serde_with = "3.16.1" -serde_repr = "0.1.20" - -home = "0.5.12" chrono = { version = "0.4.42", features = ["serde"] } -mime = "0.3.17" -reqwest = { version = "0.13.1", features = ["json", "query", "form"] } -tokio = { version = "1.49.0", features = ["full"] } -futures = "0.3.31" - clap = { version = "4.5.54", features = ["derive", "wrap_help"] } -colored = "3.0.0" -terminal_size = "0.4.3" -anstream = "0.6.21" -anstyle = "1.0.13" -owo-colors = "4.2.3" const_format = "0.2.35" +home = "0.5.12" html2md = "0.2.15" +lazy_static = "1.5.0" +owo-colors = "4.2.3" +regex = "1.12.2" +reqwest = { version = "0.13.1", features = ["json", "query", "form"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" termimad = "0.34.1" +tokio = { version = "1.49.0", features = ["full"] } -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[profile.dev] +lto = true +strip = true -[[bin]] -name = "cnb" -path = "src/bin/cnb.rs" +[profile.release] +lto = true +strip = true +codegen-units = 1 From 62435026ea4def2f1872361439748d0eb0501509 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 10:31:03 +0800 Subject: [PATCH 40/48] fix: repair the url path for ing comment --- src/api/ing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/ing.rs b/src/api/ing.rs index ffb9eb5..24cae8f 100644 --- a/src/api/ing.rs +++ b/src/api/ing.rs @@ -84,7 +84,7 @@ pub async fn raw_create_status( } pub async fn raw_create_comment(c: &Client, id: u64, content: String) -> Result { - let url = format!("{}/{}/{}", STATUS, id, COMMENTS_PATH); + let url = format!("{}{}/{}", STATUS, id, COMMENTS_PATH); let res = json!({"content": content}); c.post(url).json(&res).send().await.into_anyhow_result() } From 445b11e00fb70b86fd1883414a57247717beb4c4 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 20:01:48 +0800 Subject: [PATCH 41/48] chore: disable optimize in develop mode. --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 15ac92b..fe5c0cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,10 +34,6 @@ serde_json = "1.0.149" termimad = "0.34.1" tokio = { version = "1.49.0", features = ["full"] } -[profile.dev] -lto = true -strip = true - [profile.release] lto = true strip = true From 828f04a0c6291ac57b562fdb7f204b367dc5d0c8 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Wed, 21 Jan 2026 20:05:45 +0800 Subject: [PATCH 42/48] docs: update README --- README.md | 201 ++++++++++++++++++++++++++++++++-------- README.zh-CN.md | 237 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 345 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 520c86e..ee3c420 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,191 @@ -# Cnblogs' command line tool +**Languages:** [English](README.md) | [简体中文](README.zh-CN.md) +# Cnblogs' command line tool + [![Build / Release](https://github.com/cnblogs/cli/actions/workflows/build-release.yml/badge.svg)](https://github.com/cnblogs/cli/actions/workflows/build-release.yml) [![Build / Development](https://github.com/cnblogs/cli/actions/workflows/build-dev.yml/badge.svg)](https://github.com/cnblogs/cli/actions/workflows/build-dev.yml) -Access cnblogs form CLI. +Access and manage your cnblogs content directly form the command line. -## Usage +## Features -To use `cnb` directly, add it to your environment variables is required. +- 📝 Manage Content: Create, view, and interact with posts, moments, and comments +- 🔐 Secure Authentication: Personal Access Token (PAT) based authentication +- ⚡ Fast & Lightweight: Built with Rust for optimal performance +- 🔧 Cross-Platform: Available for Windows, macOS, and Linux +- 📚 Intuitive Interface: Familiar CLI patterns and comprehensive help system -### Login +## Installation -You need to get your PAT from [https://account.cnblogs.com/settings/tokens](https://account.cnblogs.com/settings/tokens) to use this tool. +### Download Pre-built Binaries (Recommended) -Then run `cnb user --login 'YOUR_PAT_HERE'`. This will save your PAT to `~/.cnbrc`. +Download the latest release for your platform from the [Releases page](https://github.com/cnblogs/cli/releases). -If you want to logout, run `cnb user --logout` or just remove `~/.cnbrc`. +#### Quick Install (macOS/Linux) -### Examples +```sh +# Download and install cnb +curl -fSL -O https://github.com/cnblogs/cli/releases/download/v$version/cnb-$version-$arch-$os.zip +unzip -d . cnb-$version-$arch-$os.zip +mv ./cnb ~/.local/bin/ +``` -It's time to enjoy cnblogs. +### Build from Source -Here are some simple examples: +Versions prior to 0.2.1 (inclusive) require a Rust nightly toolchain(channel=2026-01-10+), and versions after 0.2.1 switch to the stable toolchain (1.92+) -```shell -# Check your post list -cnb post --list -# Check your post -cnb --id 114514 post --show -# Create and publish post -cnb post create --title 'Hello' --body 'world!' --publish -# Change your post body -cnb --id 114514 post update --body 'niconiconiconi' +If you have a Rust compilation environment locally, you can install or build it using Cargo. -# Show ing list -cnb ing list -# Publish ing -cnb ing --publish 'Hello world!' -# Comment to ing -cnb --id 114514 ing --comment 'Awesome!' +Cargo Install + +```sh +# from repo main +cargo install --git https://github.com/cnblogs/cli.git + +# Or local install +# Clone repository +git clone --depth 1 https://github.com/cnblogs/cli.git -# Check your user infomation -cnb user --info +cargo install --path ./cli --bin cnb ``` -For more information, try `cnb --help`. +Build from Source -## Installation +```sh +# Clone repository +git clone --depth 1 https://github.com/cnblogs/cli.git +cd cli -### From releases +# Build release version +cargo build --release --bin cnb -[Releases](https://github.com/cnblogs/cli/releases) +# The binary will be available at ./target/release/cnb +``` -### Build locally +## Quick Start -This tool requires nightly toolchains(1.74.0+) to build. +### 1. Get Your Personal Access Token -```shell -git clone --depth 1 https://github.com/cnblogs/cli.git -cd cli -cargo build -r +1. Visit [https://account.cnblogs.com/settings/tokens](https://account.cnblogs.com/settings/tokens) +2. Click "Generate New Token" +3. Copy the generated token (you won't be able to see it again) + +### 2. Login + +```sh +# login (recommended) +cnb user login YOUR_PAT_TOKEN + +# Verify login status +cnb user status ``` -Or get binaries from [CI](https://github.com/cnblogs/cli/actions) artifacts. +This will save your PAT to `~/.cnblogs/token` + +If you want to log out, run `cnb user logout` or just remove `~/.cnblogs`. + +## Command Reference + +### Command Usage + +```sh +cnb [option] [arg] +``` + +### Available Commands + +| Command | Description | Available Subcommands | +|---------|------------------|---------------------------------------------| +| `user` | User module | `login`, `logout`, `status` | +| `ing` | Moments module | `create`, `delete`, `list`, `show`, `reply` | +| `post` | posts module | `list`, `show`, `reply` | +| `news` | news module | `list` | +| `fav` | bookmarks module | `list` | + +### Usage Examples + +It's time to enjoy cnblogs. + +Here are some simple examples: + +```sh +# Check your post list +cnb post list +# Check your post +cnb post show 114514 + +# Show ing list +cnb ing list +cnb ing list my --page-index 1 --page-size 10 + +# Publish ing +cnb ing create 'Hello world!' +cnb ing create 'Hello world!' --tag lucky + +# Comment to ing +cnb ing replay 'Awesome!' --id 114514 +``` + +For more information, try `cnb --help` or `cnb help`. + +## Project Structure + +```text +cli/ +├── Cargo.lock +├── Cargo.toml +├── LICENSE +├── README.md +├── README.zh-CN.md +├── rust-fmt.toml +├── rust-toolchain.toml +├── shell.nix +└── src + ├── api # CNBlogs API interaction module + │ ├── fav.rs # Favorites/bookmarks API endpoints + │ ├── ing.rs # Moments/statuses API endpoints + │ ├── mod.rs # Module exports for API layer + │ ├── news.rs # News API endpoints + │ ├── post.rs # Blog posts API endpoints + │ ├── urls.rs # URL generator for API request construction + │ └── user.rs # User API endpoints (login, profile, etc.) + ├── bin # Binary entry point + │ └── cnb.rs # CLI main executable (argument parsing and routing) + ├── commands # CLI command implementations + │ ├── fav.rs + │ ├── ing.rs + │ ├── mod.rs + │ ├── news.rs + │ ├── post.rs + │ └── user.rs + ├── context # Context management (configuration, state, output) + │ ├── config.rs # Configuration file reading/writing and management + │ ├── mod.rs # Context module exports + │ └── output.rs # Output formatting control (JSON, table, text, etc.) + ├── display # Data display and formatting module + │ ├── ing.rs # Moment data display formatting + │ └── mod.rs # Display module exports + ├── lib.rs # Library crate root, exports public interfaces + ├── logic # Business logic layer (orchestrates operations) + │ ├── fav.rs + │ ├── ing.rs + │ ├── mod.rs + │ ├── news.rs + │ ├── post.rs + │ └── user.rs + ├── models # Data model definitions and formats + │ ├── fav.rs + │ ├── ing.rs + │ ├── mod.rs + │ ├── news.rs + │ ├── post.rs + │ └── user.rs + └── tools # Utility functions and extensions + ├── http.rs # Extensions to reqwest. + ├── mod.rs + ├── strings.rs # Extensions to String. + └── timer.rs # Extensions to chrono. +``` ## License diff --git a/README.zh-CN.md b/README.zh-CN.md index a58b5d5..4eaf220 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -3,68 +3,197 @@ [![Build / Release](https://github.com/cnblogs/cli/actions/workflows/build-release.yml/badge.svg)](https://github.com/cnblogs/cli/actions/workflows/build-release.yml) [![Build / Development](https://github.com/cnblogs/cli/actions/workflows/build-dev.yml/badge.svg)](https://github.com/cnblogs/cli/actions/workflows/build-dev.yml) -从 CLI 访问 cnblogs。 - -## Cnbogs Cli 设计 - -从Cnblogs的[OpenAPI](https://api.cnblogs.com/help)来说,API主要有以下几类: - -1. Token: 认证 -2. Users: 仅提供当前登录用户信息 -3. Blogs: 博客的CURD及其评论的查看和增加, -4. Marks: 收藏的CURD -5. News: 新闻的查询,新闻评论的CURD -6. Statuses: 闪存CURD。 -7. Questions: 问题相关操作 -8. Edu: 班级相关 -9. Articles: 知识库的查找。 -10. Zzk: 找找看 - -### cli的使用 - -目前cli的使用如下: - -```shell -# Check your post list -cnb post --list -# Check your post -cnb --id 114514 post --show -# Create and publish post -cnb post create --title 'Hello' --body 'world!' --publish -# Change your post body -cnb --id 114514 post update --body 'niconiconiconi' - -# Show ing list -cnb ing list -# Publish ing -cnb ing --publish 'Hello world!' -# Comment to ing -cnb --id 114514 ing --comment 'Awesome!' +直接从命令行访问和管理您的博客园内容。 + +## 功能特性 + +- 📝 内容管理: 创建、查看和互动博客文章、闪存和评论 +- 🔐 安全认证: 基于个人访问令牌 (PAT) 的身份验证 +- ⚡ 快速轻量: 使用 Rust 构建,性能优异 +- 🔧 跨平台: 支持 Windows、macOS 和 Linux +- 📚 直观界面: 熟悉的 CLI 模式和全面的帮助系统 + +## 安装方法 + +### 下载预编译二进制文件(推荐) + +从[发布页面](https://github.com/cnblogs/cli/releases)下载适用于您平台的最新版本。 + +#### 快速安装(macOS/Linux) + +```sh +# 下载并安装 cnb +curl -fSL -O https://github.com/cnblogs/cli/releases/download/v$version/cnb-$version-$arch-$os.zip +unzip -d . cnb-$version-$arch-$os.zip +mv ./cnb ~/.local/bin/ +``` + +Windows PowerShell + +```powershell +# 下载并解压 +Invoke-WebRequest -Uri "https://github.com/cnblogs/cli/releases/latest/download/cnb-x86_64-pc-windows-msvc.zip" -OutFile "cnb.zip" +Expand-Archive -Path "cnb.zip" -DestinationPath "." +# 将 cnb.exe 添加到 PATH 环境变量 +``` + +### 从源码构建 + +`v0.2.1`之前的版本需要nightly版本,`channel`推荐`2026-01-10`以上。最新版本已切换至`stable`版本(1.95+)。 + +Cargo安装 + +```sh +# from repo main +cargo install --git https://github.com/cnblogs/cli.git + +# Or local install +# Clone repository +git clone --depth 1 https://github.com/cnblogs/cli.git + +cargo install --path ./cli --bin cnb +``` + +源码编译 + +```sh +# 克隆仓库 +git clone --depth 1 https://github.com/cnblogs/cli.git +cd cli + +# 构建发布版本 +cargo build --release --bin cnb + +# 二进制文件位于 ./target/release/cnb(或 Windows 上的 cnb.exe) +``` + +## 快速开始 + +### 1. 获取个人访问令牌 -# Check your user infomation -cnb user --info +1. 访问[https://account.cnblogs.com/settings/tokens](https://account.cnblogs.com/settings/tokens) +2. 点击"生成新令牌" +3. 复制生成的令牌(以后将无法再次查看) + +### 2. 登录 + +```bash +# 登录(推荐) +cnb user login YOUR_PAT_TOKEN + +# 验证登录状态 +cnb user status ``` -大体上使用如上的设计,支持子命令,相关操作的设计按照RESTFUL的思路设计实现,博客的相关操作设计如下: +您的令牌安全地存储在 `~/.cnblogs/token`(Windows:`%USERPROFILE%\.cnblogs\token`)。 -```shell -cnb posts [comment] [list,create,query,delete,update] --[id/file/quertset] --[pagesize,pagecount] +## 命令参考 + +### 命令格式 + +```bash +cnb <命令> <子命令> [选项] [参数] ``` -## 闪存cli +### 命令参考 + +| 命令 | 描述 | 可用子命令 | +|--------|------|---------------------------------------------| +| `user` | 用户模块 | `login`, `logout`, `status` | +| `ing` | 闪存管理 | `create`, `delete`, `list`, `show`, `reply` | +| `post` | 博客文章 | `list`, `show`, `reply` | +| `news` | 新闻 | `list` | +| `fav` | 书签 | `list` | -闪存cli设计如下: +### 使用示例 + +以下级几个简单的示例: ```sh -cnb ing query # 默认10条s -cnb ing query --id 123456 --id 123 -cnb ing query -n 1 -s 10 -cnb ing query --type f -n 2 -s 10 -# 根据tag查找,-g为tag名称 -n 2 -s 10 分页 -cnb ing query -t t -g Linux -cnb ing create hello --private --lucky -cnb ing create hello --private --lucky --tag hello -cnb ing delete --id 123456 +# 查看你的随笔 +cnb post list +# 查看随笔内容 +cnb post show 114514 + +# 闪存相关操作 +cnb ing list +cnb ing list my --page-index 1 --page-size 10 + +# 发布闪存 +cnb ing create 'Hello world!' +cnb ing create 'Hello world!' --tag lucky + +# 发布评论 +cnb ing replay 'Awesome!' --id 114514 ``` -TODO: “提到我”存在解析问题。待完善。 +更多使用信息请通过`cnb --help`或者`cnb help`查询 + +## 项目结构 + +```text +cli/ +├── Cargo.lock +├── Cargo.toml +├── LICENSE +├── README.md +├── README.zh-CN.md +├── rust-fmt.toml +├── rust-toolchain.toml +├── shell.nix +└── src + ├── api # 博客园API交互模块 + │ ├── fav.rs # 书签API + │ ├── ing.rs # 闪存API + │ ├── mod.rs # Module exports for API layer + │ ├── news.rs # 新闻API + │ ├── post.rs # 随笔API + │ ├── urls.rs # 生成API的url + │ └── user.rs # 用户API + ├── bin # 二进制目录 + │ └── cnb.rs # cli可执行文件 + ├── commands # CLI 命令实现 + │ ├── fav.rs + │ ├── ing.rs + │ ├── mod.rs + │ ├── news.rs + │ ├── post.rs + │ └── user.rs + ├── context # 上下文管理 + │ ├── config.rs # 配置文件 + │ ├── mod.rs # 模块管理,Context实现 + │ └── output.rs # 输出管理 + ├── display # 显示相关 + │ ├── ing.rs # 闪存相关`trait`的定义和实现 + │ └── mod.rs + ├── lib.rs # lib + ├── logic # 实现逻辑 + │ ├── fav.rs + │ ├── ing.rs + │ ├── mod.rs + │ ├── news.rs + │ ├── post.rs + │ └── user.rs + ├── models # 模型定义和格式化输出, + │ ├── fav.rs + │ ├── ing.rs + │ ├── mod.rs + │ ├── news.rs + │ ├── post.rs + │ └── user.rs + └── tools # 工具模块,定义一些拓展和函数 + ├── http.rs # reqwest的拓展 + ├── mod.rs + ├── strings.rs # String的拓展 + └── timer.rs # chrono的拓展 +``` + +## 许可证 + +[MIT](https://raw.githubusercontent.com/cnblogs/cli/main/LICENSE) + +## 反馈 + +反馈我们十分期待你对本项目的看法,欢迎随时留言交流! + +[Issues](https://github.com/cnblogs/cli/issues) From 029f13624e550a13b9d37b5055f9a92607254d44 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Thu, 22 Jan 2026 20:50:03 +0800 Subject: [PATCH 43/48] fix(ci): repair workflow broken in e388061 --- .github/workflows/build-dev.yml | 12 ++++++------ .github/workflows/build-release.yml | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index 42c6337..a951590 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -21,15 +21,15 @@ jobs: matrix: targets: # mac - - { os: macos , target: aarch64-apple-darwin , alias: aarch64-apple-darwin } - - { os: macos , target: x86_64-apple-darwin , alias: x86_64-apple-darwin } + - { os: macos-latest , target: aarch64-apple-darwin , alias: aarch64-apple-darwin } + - { os: macos-latest , target: x86_64-apple-darwin , alias: x86_64-apple-darwin } # linux - - { os: linux , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } - - { os: linux , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } + - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } + - { os: ubuntu-latest , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } # freebsd - - { os: freebsd , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } + - { os: ubuntu-latest , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } # windows - - { os: windows , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } + - { os: windows-latest , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 80931f5..2abff0a 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -17,15 +17,15 @@ jobs: matrix: targets: # mac - - { os: macos , target: aarch64-apple-darwin , alias: aarch64-apple-darwin } - - { os: macos , target: x86_64-apple-darwin , alias: x86_64-apple-darwin } + - { os: macos-latest , target: aarch64-apple-darwin , alias: aarch64-apple-darwin } + - { os: macos-latest , target: x86_64-apple-darwin , alias: x86_64-apple-darwin } # linux - - { os: linux , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } - - { os: linux , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } + - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } + - { os: ubuntu-latest , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } # freebsd - - { os: freebsd , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } + - { os: ubuntu-latest , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } # windows - - { os: windows , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } + - { os: windows-latest , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } steps: - name: Checkout From 3ee9ba25244636959137243103d6024e456446df Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Thu, 22 Jan 2026 23:15:11 +0800 Subject: [PATCH 44/48] fix: update date handling in NewsInfo and enhance DateFormatExt for UTC --- src/models/news.rs | 7 +++--- src/tools/timer.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/models/news.rs b/src/models/news.rs index 91d80b7..c072b6e 100644 --- a/src/models/news.rs +++ b/src/models/news.rs @@ -1,4 +1,4 @@ -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use owo_colors::OwoColorize; use serde::Deserialize; @@ -24,11 +24,12 @@ pub struct NewsInfo { pub title: String, pub summary: String, pub topic_id: u64, - pub topic_icon: String, + pub topic_icon: Option, pub view_count: u64, pub comment_count: u64, pub digg_count: u64, - pub date_added: NaiveDateTime, + #[serde(with = "crate::tools::timer::rfc3339_or_naive")] + pub date_added: DateTime, } impl NewsInfo { diff --git a/src/tools/timer.rs b/src/tools/timer.rs index 993ac3d..6da407f 100644 --- a/src/tools/timer.rs +++ b/src/tools/timer.rs @@ -1,4 +1,4 @@ -use chrono::{Datelike, NaiveDateTime}; +use chrono::{DateTime, Datelike, Local, NaiveDateTime, TimeZone, Utc}; pub trait DateFormatExt { fn as_time_age(&self) -> String; @@ -22,3 +22,64 @@ impl DateFormatExt for NaiveDateTime { } } } + +impl DateFormatExt for DateTime { + fn as_time_age(&self) -> String { + // 将UTC时间转换为本地时间 + let local_time = self.with_timezone(&Local); + let now = Local::now(); + + // 计算年份差 + let year = now.year() - local_time.year(); + if year >= 1 { + return local_time.format("[%y年%m月%d日 %H:%M]").to_string(); + } + + // 计算秒数差 + let seconds = (now - local_time).num_seconds(); + match seconds { + secs if secs < 60 => format!("[{}秒前]", secs), + secs if secs < 3600 => format!("[{}分钟前]", secs / 60), + secs if secs < 86400 => format!("[{}小时前]", secs / 3600), + secs if secs < 604800 => format!("[{}天前]", secs / 86400), + _ => local_time.format("[%y年%m月%d日 %H:%M]").to_string(), + } + } +} + +/// 自定义反序列化模块 +pub mod rfc3339_or_naive { + use super::*; + use serde::{Deserialize, Deserializer, Serializer, de}; + + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&dt.to_rfc3339()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + + // 首先尝试解析RFC3339格式(带时区) + if let Ok(dt) = DateTime::parse_from_rfc3339(&s) { + return Ok(dt.with_timezone(&Utc)); + } + + // 然后尝试解析不带时区的ISO8601格式 + if let Ok(ndt) = NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S") { + return Ok(Utc.from_utc_datetime(&ndt)); + } + + // 还可以尝试其他格式 + if let Ok(ndt) = NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S") { + return Ok(Utc.from_utc_datetime(&ndt)); + } + + Err(de::Error::custom(format!("无效的时间格式: {}", s))) + } +} From 959b10aca2525dc538fc59fc1f8cda9609f1b7d1 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Fri, 23 Jan 2026 00:28:48 +0800 Subject: [PATCH 45/48] feat: clean up commented code and improve date parsing in DateFormatExt --- src/models/news.rs | 13 ------------- src/tools/timer.rs | 15 ++++++++++----- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/models/news.rs b/src/models/news.rs index c072b6e..3c07f19 100644 --- a/src/models/news.rs +++ b/src/models/news.rs @@ -4,21 +4,8 @@ use serde::Deserialize; use crate::tools::timer::DateFormatExt; -// { -// "Id": 813263, -// "Title": "OpenAI偷袭,谷歌掀桌!2026开年第一场AI大战太精彩", -// "Summary": "新智元报道 编辑:KingHZ 谷歌强势回应 OpenAI:开源 TranslateGemma 模型,支持 55 种语言,效率惊人!12B 参数超越 27B 基线,手机端轻松运行,真正速通「巴别塔」。 语言的边界,正被 AI 一一抹平。 OpenAI 悄悄发布了翻译产品 ChatGPT Transl", -// "TopicId": 1, -// "TopicIcon": "https://img2023.cnblogs.com/news_topic/20230322145212853-1130832439.png", -// "ViewCount": 31, -// "CommentCount": 0, -// "DiggCount": 0, -// "DateAdded": "2026-01-20T15:31:00+08:00" -// } - #[derive(Debug, Deserialize, Default)] #[serde(rename_all = "PascalCase")] -#[serde(default)] pub struct NewsInfo { pub id: u64, pub title: String, diff --git a/src/tools/timer.rs b/src/tools/timer.rs index 6da407f..bd1372d 100644 --- a/src/tools/timer.rs +++ b/src/tools/timer.rs @@ -49,7 +49,10 @@ impl DateFormatExt for DateTime { /// 自定义反序列化模块 pub mod rfc3339_or_naive { + use std::time::Duration; + use super::*; + use serde::{Deserialize, Deserializer, Serializer, de}; pub fn serialize(dt: &DateTime, serializer: S) -> Result @@ -70,14 +73,16 @@ pub mod rfc3339_or_naive { return Ok(dt.with_timezone(&Utc)); } - // 然后尝试解析不带时区的ISO8601格式 - if let Ok(ndt) = NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S") { + // 2026-01-22T18:35:49.593 + if let Ok(ndt) = NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%.3f") { + let ndt = ndt - Duration::from_hours(8); return Ok(Utc.from_utc_datetime(&ndt)); } - // 还可以尝试其他格式 - if let Ok(ndt) = NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S") { - return Ok(Utc.from_utc_datetime(&ndt)); + // 2026-01-22T18:35:49 + if let Ok(ndt) = NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S") { + let ndt = ndt - Duration::from_hours(8); + return Ok(Utc.from_local_datetime(&ndt).unwrap()); } Err(de::Error::custom(format!("无效的时间格式: {}", s))) From eff8a14fdf74d6bb3f802c1737c921282fe2205e Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Fri, 23 Jan 2026 00:37:23 +0800 Subject: [PATCH 46/48] ci: remove the FreeBSD target from the build workflow --- .github/workflows/build-dev.yml | 2 +- .github/workflows/build-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index a951590..e8addf0 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -27,7 +27,7 @@ jobs: - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } - { os: ubuntu-latest , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } # freebsd - - { os: ubuntu-latest , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } + # - { os: ubuntu-latest , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } # windows - { os: windows-latest , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } steps: diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 2abff0a..a518bc8 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -23,7 +23,7 @@ jobs: - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } - { os: ubuntu-latest , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } # freebsd - - { os: ubuntu-latest , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } + # - { os: ubuntu-latest , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } # windows - { os: windows-latest , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } From 9a3ccf343b3c9f4da8bffa2f71fd2f20bc6712c3 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Fri, 23 Jan 2026 00:41:00 +0800 Subject: [PATCH 47/48] fix(tests): correct JSON syntax in test data for NewsInfo --- src/models/news.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/news.rs b/src/models/news.rs index 3c07f19..b697306 100644 --- a/src/models/news.rs +++ b/src/models/news.rs @@ -50,7 +50,7 @@ mod tests { "CommentCount": 0, "DiggCount": 2, "DateAdded": "2026-01-20T15:40:00+08:00" - },"#; + }"#; let aa: NewsInfo = serde_json::from_str(a).unwrap(); assert_eq!(aa.id, 813264); From bddb45eca5bf3e741373320f2c1d9963354d0d08 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 27 Jan 2026 00:00:41 +0800 Subject: [PATCH 48/48] ci: refactor and enhance GitHub Actions workflow - fix logic for fetching gen-name on Windows platform - fix argument handling in `Compress-Archive` command - remove verbose argument from test steps for cleaner logs - add commit lint check to PR validation process - fetch version number directly from Cargo.toml - implement automatic Git tagging based on Cargo.toml version --- .github/actions/check/action.yml | 4 +- .github/actions/make-archive/action.yml | 2 +- .github/actions/test/action.yml | 6 +- .github/workflows/build-dev.yml | 105 ------------------------ .github/workflows/build-release.yml | 99 ---------------------- .github/workflows/pr-check.yml | 47 +++++++++++ .github/workflows/release.yml | 102 +++++++++++++++++++++++ 7 files changed, 154 insertions(+), 211 deletions(-) delete mode 100644 .github/workflows/build-dev.yml delete mode 100644 .github/workflows/build-release.yml create mode 100644 .github/workflows/pr-check.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/actions/check/action.yml b/.github/actions/check/action.yml index 6a593e5..9d6610d 100644 --- a/.github/actions/check/action.yml +++ b/.github/actions/check/action.yml @@ -6,7 +6,9 @@ runs: - name: Audit shell: bash run: | - cargo install cargo-audit + if ! command -v cargo-audit &> /dev/null; then + cargo install cargo-audit + fi cargo audit - name: Fmt diff --git a/.github/actions/make-archive/action.yml b/.github/actions/make-archive/action.yml index 299d3ea..198fa96 100644 --- a/.github/actions/make-archive/action.yml +++ b/.github/actions/make-archive/action.yml @@ -20,4 +20,4 @@ runs: if: runner.os == 'Windows' shell: pwsh run: | - Compress-Archive ${{ inputs.files }} ${{ inputs.out }} + Compress-Archive -Path "${{ inputs.files }}" -DestinationPath "${{ inputs.out }}" diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index 2695a65..86354f5 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -5,8 +5,4 @@ runs: steps: - name: Test dev shell: bash - run: cargo test --verbose - - - name: Test release - shell: bash - run: cargo test --verbose --release + run: cargo test diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml deleted file mode 100644 index e8addf0..0000000 --- a/.github/workflows/build-dev.yml +++ /dev/null @@ -1,105 +0,0 @@ -name: Build / Development - -on: - push: - branches: - - 'dev' - pull_request_target: - types: - - edited - - opened - - reopened - - synchronize - -jobs: - build-dev: - name: ${{ matrix.targets.alias }} - runs-on: ${{ matrix.targets.os }} - - strategy: - fail-fast: false - matrix: - targets: - # mac - - { os: macos-latest , target: aarch64-apple-darwin , alias: aarch64-apple-darwin } - - { os: macos-latest , target: x86_64-apple-darwin , alias: x86_64-apple-darwin } - # linux - - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } - - { os: ubuntu-latest , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } - # freebsd - # - { os: ubuntu-latest , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } - # windows - - { os: windows-latest , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 1 # 浅克 - persist-credentials: false - - - name: Setup Rust toolchain - uses: dsherret/rust-toolchain-file@v1 - - - name: Show Rust toolchain version - shell: bash - run: | - cargo -V - cargo clippy -V - cargo fmt -- -V - rustc -V - - - name: Setup musl-tools - if: matrix.targets.target == 'x86_64-unknown-linux-musl' - shell: bash - run: sudo apt -y install musl-tools - - - name: Add target - uses: ./.github/actions/add-target - with: - target: ${{ matrix.targets.target }} - - - name: Setup Rust cache - uses: Swatinem/rust-cache@v2 - with: - prefix-key: ${{ matrix.targets.alias }} - - - name: Generate version - id: gen-version - shell: bash - run: echo 'VERSION=0.0.0-${{ github.sha }}' >> $GITHUB_OUTPUT - - - name: Replace version - uses: ./.github/actions/replace-version - with: - version: ${{ steps.gen-version.outputs.VERSION }} - - - name: Run check - uses: ./.github/actions/check - - - name: Run test - uses: ./.github/actions/test - - - name: Run build - uses: ./.github/actions/build - with: - target: ${{ matrix.targets.target }} - release: false - - # - name: Generate artifacts name - # id: gen-name - # shell: bash - # run: echo 'NAME=cnb-dev-${{ matrix.targets.alias }}' >> $GITHUB_OUTPUT - - # - name: Generate binary extension - # id: gen-ext - # if: runner.os == 'Windows' - # shell: bash - # run: echo 'EXT=.exe' >> $GITHUB_OUTPUT - - # - name: Upload artifacts - # uses: actions/upload-artifact@v4 - # with: - # name: ${{ steps.gen-name.outputs.NAME }} - # path: ./target/${{ matrix.targets.target }}/debug/cnb${{ steps.gen-ext.outputs.EXT }} - # if-no-files-found: error diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml deleted file mode 100644 index a518bc8..0000000 --- a/.github/workflows/build-release.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Build / Release - -on: - push: - branches: - - main - tags: - - v*.*.* - -jobs: - build-release: - name: ${{ matrix.targets.alias }} - runs-on: ${{ matrix.targets.os }} - - strategy: - fail-fast: false - matrix: - targets: - # mac - - { os: macos-latest , target: aarch64-apple-darwin , alias: aarch64-apple-darwin } - - { os: macos-latest , target: x86_64-apple-darwin , alias: x86_64-apple-darwin } - # linux - - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , alias: x86_64-unknown-linux-gnu } - - { os: ubuntu-latest , target: x86_64-unknown-linux-musl, alias: x86_64-unknown-linux-musl } - # freebsd - # - { os: ubuntu-latest , target: x86_64-unknown-freebsd , alias: x86_64-unknown-freebsd } - # windows - - { os: windows-latest , target: x86_64-pc-windows-msvc , alias: x86_64-pc-windows-msvc } - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} # github.ref 变量将自动填充为触发事件的分支或标签名 - fetch-depth: 0 - persist-credentials: false - - - name: Setup Rust toolchain - uses: dsherret/rust-toolchain-file@v1 - - - name: Setup musl-tools - if: matrix.targets.target == 'x86_64-unknown-linux-musl' - shell: bash - run: sudo apt -y install musl-tools - - - name: Add target - uses: ./.github/actions/add-target - with: - target: ${{ matrix.targets.target }} - - - name: Setup Rust cache - uses: Swatinem/rust-cache@v2 - with: - prefix-key: ${{ matrix.targets.alias }} - - - name: Generate version - id: gen-version - shell: bash - run: echo 'VERSION=${{ github.ref_name }}' | sed 's/v//' >> $GITHUB_OUTPUT - - - name: Replace version - uses: ./.github/actions/replace-version - with: - version: ${{ steps.gen-version.outputs.VERSION }} - - - name: Run build - uses: ./.github/actions/build - with: - target: ${{ matrix.targets.target }} - release: true - - - name: Generate artifacts name - id: gen-name - shell: bash - run: echo 'NAME=cnb-${{ steps.gen-version.outputs.VERSION }}-${{ matrix.targets.alias }}' >> $GITHUB_OUTPUT - - - name: Generate binary extension - id: gen-ext - if: runner.os == 'Windows' - shell: bash - run: echo 'EXT=.exe' >> $GITHUB_OUTPUT - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.gen-name.outputs.NAME }} - path: ./target/${{ matrix.targets.target }}/release/cnb${{ steps.gen-ext.outputs.EXT }} - if-no-files-found: error - - - name: Archive binary - uses: ./.github/actions/make-archive - with: - files: ./target/${{ matrix.targets.target }}/release/cnb${{ steps.gen-ext.outputs.EXT }} - out: ${{ steps.gen-name.outputs.NAME }}.zip - - - name: Create GitHub release - uses: softprops/action-gh-release@v2 - with: - files: ${{ steps.gen-name.outputs.NAME }}.zip diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 0000000..e661be2 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,47 @@ +name: Build / Development + +on: + pull_request: + types: + - opened + - reopened + - synchronize + +jobs: + commit-check: + name: Commit Check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # 必须,才能拿到完整 commit history + + - name: Lint commits + uses: wagoid/commitlint-github-action@v6 + + pr-check: + needs: commit-check + name: Pull Request Check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 # 浅克隆以加快速度 + persist-credentials: false + + - name: Setup Rust toolchain + uses: dsherret/rust-toolchain-file@v1 + + - name: Fmt + run: cargo fmt --check + + - name: Clippy + run: cargo clippy -- -D warnings + + - name: Test + run: cargo test + + - name: Build Dev + run: cargo build \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..066964b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,102 @@ +name: Build / Release + +on: + push: + branches: + - main + +permissions: + contents: write + + +jobs: + + prepare: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + should_release: ${{ steps.check.outputs.should_release }} + + steps: + - uses: actions/checkout@v4 + + - name: Read version from Cargo.toml + id: version + run: | + VERSION=$(grep '^version' Cargo.toml | sed 's/.*= "\(.*\)"/\1/') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Check if release exists + id: check + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="v${{ steps.version.outputs.version }}" + if gh release view "$TAG" >/dev/null 2>&1; then + echo "should_release=false" >> $GITHUB_OUTPUT + else + echo "should_release=true" >> $GITHUB_OUTPUT + fi + + - name: Create and push tag + if: steps.check.outputs.should_release == 'true' + run: | + TAG="v${{ steps.version.outputs.version }}" + git tag "$TAG" + git push origin "$TAG" + + build-release: + needs: prepare + if: needs.prepare.outputs.should_release == 'true' + name: ${{ matrix.targets.alias }} + runs-on: ${{ matrix.targets.os }} + + strategy: + fail-fast: false + matrix: + targets: + - { os: macos-latest, target: aarch64-apple-darwin, alias: aarch64-apple-darwin } + - { os: macos-latest, target: x86_64-apple-darwin, alias: x86_64-apple-darwin } + - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu, alias: x86_64-unknown-linux-gnu } + - { os: ubuntu-latest, target: x86_64-unknown-linux-musl,alias: x86_64-unknown-linux-musl} + - { os: windows-latest, target: x86_64-pc-windows-msvc, alias: x86_64-pc-windows-msvc } + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: dsherret/rust-toolchain-file@v1 + + - name: Setup musl-tools + if: matrix.targets.target == 'x86_64-unknown-linux-musl' + shell: bash + run: sudo apt -y install musl-tools + + - name: Add target + uses: ./.github/actions/add-target + with: + target: ${{ matrix.targets.target }} + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + prefix-key: ${{ matrix.targets.alias }} + + - name: Run build + uses: ./.github/actions/build + with: + target: ${{ matrix.targets.target }} + release: true + + - name: Archive binary + uses: ./.github/actions/make-archive + with: + files: ./target/${{ matrix.targets.target }}/release/cnb${{ matrix.targets.target == 'x86_64-pc-windows-msvc' && '.exe' || '' }} + out: cnb-${{ needs.prepare.outputs.version }}-${{ matrix.targets.target }}.zip + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ needs.prepare.outputs.version }} + files: cnb-${{ needs.prepare.outputs.version }}-${{ matrix.targets.target }}.zip