Skip to content

Commit 57680ea

Browse files
committed
feat: list fav
1 parent f756117 commit 57680ea

File tree

12 files changed

+239
-0
lines changed

12 files changed

+239
-0
lines changed

src/api/fav/get_list.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use crate::api::fav::Fav;
2+
use crate::infra::http::{body_or_err, RequestBuilderExt, VecExt as HttpVecExt};
3+
use crate::infra::iter::IntoIteratorExt;
4+
use crate::infra::json;
5+
use crate::infra::result::IntoResult;
6+
use crate::infra::vec::VecExt;
7+
use crate::openapi;
8+
use anyhow::Result;
9+
use serde::{Deserialize, Serialize};
10+
use std::ops::ControlFlow;
11+
12+
#[derive(Clone, Debug, Serialize, Deserialize)]
13+
pub struct FavEntry {
14+
#[serde(rename = "Title")]
15+
pub title: String,
16+
#[serde(rename = "LinkUrl")]
17+
pub url: String,
18+
#[serde(rename = "Summary")]
19+
pub summary: String,
20+
#[serde(rename = "Tags")]
21+
pub tags: Vec<String>,
22+
#[serde(rename = "DateAdded")]
23+
pub create_time: String,
24+
}
25+
26+
impl Fav {
27+
pub async fn get_list(&self, skip: usize, take: usize) -> Result<Vec<FavEntry>> {
28+
let client = &reqwest::Client::new();
29+
30+
let range = (skip + 1)..=(skip + take);
31+
let cf = range
32+
.map(|i| async move {
33+
let req = {
34+
let query = vec![("pageIndex", i), ("pageSize", 1)].into_query_string();
35+
let url = openapi!("/Bookmarks?{}", query);
36+
client.get(url).pat_auth(&self.pat)
37+
};
38+
39+
let resp = req.send().await?;
40+
41+
let body = body_or_err(resp).await?;
42+
43+
json::deserialize::<Vec<FavEntry>>(&body)?
44+
.pop()
45+
.into_ok::<anyhow::Error>()
46+
})
47+
.join_all()
48+
.await
49+
.into_iter()
50+
.try_fold(vec![], |acc, it| match it {
51+
Ok(maybe) => match maybe {
52+
Some(entry) => ControlFlow::Continue(acc.chain_push(entry)),
53+
None => ControlFlow::Break(Ok(acc)),
54+
},
55+
Err(e) => ControlFlow::Break(Err(e)),
56+
});
57+
58+
match cf {
59+
ControlFlow::Continue(vec) => Ok(vec),
60+
ControlFlow::Break(result) => result,
61+
}
62+
}
63+
}

src/api/fav/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
pub mod get_list;
2+
3+
// Aka cnblogs wz
4+
pub struct Fav {
5+
pat: String,
6+
}
7+
8+
impl Fav {
9+
pub const fn new(pat: String) -> Self {
10+
Self { pat }
11+
}
12+
}

src/api/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod auth;
2+
pub mod fav;
23
pub mod ing;
34
pub mod news;
45
pub mod post;

src/args/cmd/fav.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use clap::Parser;
2+
3+
#[derive(Parser, Debug)]
4+
pub struct Opt {
5+
#[arg(verbatim_doc_comment)]
6+
/// Show favorite list, order by time in DESC
7+
/// Example: cnb fav --list
8+
#[arg(long)]
9+
#[arg(short = 'l')]
10+
pub list: bool,
11+
}

src/args/cmd/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod fav;
12
pub mod ing;
23
pub mod news;
34
pub mod post;
@@ -19,4 +20,7 @@ pub enum Cmd {
1920
/// News operations
2021
#[clap(visible_alias = "n")]
2122
News(news::Opt),
23+
/// Favorite operations
24+
#[clap(visible_alias = "f")]
25+
Fav(fav::Opt),
2226
}

src/args/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub enum TimeStyle {
1717
Normal,
1818
}
1919

20+
// TODO: flatten options in struct?
2021
#[derive(Debug, Parser)]
2122
#[command(author, about, long_about = None, version)]
2223
pub struct Args {

src/args/parser.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,3 +457,27 @@ pub fn list_news(args: &Args) -> Option<(usize, usize)> {
457457
}
458458
.into_some()
459459
}
460+
461+
pub fn list_fav(args: &Args) -> Option<(usize, usize)> {
462+
match args {
463+
Args {
464+
cmd: Some(Cmd::Fav(cmd::fav::Opt { list: true })),
465+
id: None,
466+
with_pat: _,
467+
rev: _,
468+
skip,
469+
take,
470+
debug: _,
471+
style: _,
472+
time_style: _,
473+
fail_on_error: _,
474+
quiet: _,
475+
} => {
476+
let skip = get_skip(skip);
477+
let take = get_take(take);
478+
(skip, take)
479+
}
480+
_ => return None,
481+
}
482+
.into_some()
483+
}

src/display/colorful.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::api::fav::get_list::FavEntry;
12
use crate::api::ing::get_comment_list::IngCommentEntry;
23
use crate::api::ing::get_list::IngEntry;
34
use crate::api::ing::{
@@ -348,3 +349,48 @@ pub fn list_news(
348349
acc
349350
})
350351
}
352+
353+
// TODO: lift out rev option
354+
pub fn list_fav(
355+
time_style: &TimeStyle,
356+
fav_list: &Result<Vec<FavEntry>>,
357+
rev: bool,
358+
) -> Result<String> {
359+
let fav_list = match fav_list {
360+
Ok(o) => o,
361+
Err(e) => return fmt_err(e).into_ok(),
362+
};
363+
364+
fav_list
365+
.iter()
366+
.dyn_rev(rev)
367+
.map(|fav| try {
368+
let mut buf = String::new();
369+
{
370+
let buf = &mut buf;
371+
let create_time = display_cnb_time(&fav.create_time, time_style);
372+
writeln!(buf, "{} {}", create_time.dimmed(), fav.url.dimmed())?;
373+
writeln!(buf, " {}", fav.title)?;
374+
375+
let summary = {
376+
fav.summary.width_split(get_term_width() - 4).map_or_else(
377+
|| fav.summary.clone(),
378+
|vec| {
379+
vec.into_iter()
380+
.map(|line| format!(" {}", line))
381+
.collect::<Vec<_>>()
382+
.join("\n")
383+
},
384+
)
385+
};
386+
if summary.is_empty().not() {
387+
writeln!(buf, "{}", summary.dimmed())?;
388+
}
389+
}
390+
buf
391+
})
392+
.try_fold(String::new(), |mut acc, buf: Result<String>| try {
393+
write!(&mut acc, "\n{}", buf?)?;
394+
acc
395+
})
396+
}

src/display/json.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::api::fav::get_list::FavEntry;
12
use crate::api::ing::get_comment_list::IngCommentEntry;
23
use crate::api::ing::get_list::IngEntry;
34
use crate::api::news::get_list::NewsEntry;
@@ -139,3 +140,14 @@ pub fn list_news(news_list: &Result<Vec<NewsEntry>>, rev: bool) -> Result<String
139140

140141
json::serialize(vec.clone())
141142
}
143+
144+
pub fn list_fav(news_list: &Result<Vec<FavEntry>>, rev: bool) -> Result<String> {
145+
let news_list = match news_list {
146+
Ok(o) => o,
147+
Err(e) => return fmt_err(e).into_ok(),
148+
};
149+
150+
let vec = news_list.iter().dyn_rev(rev).collect::<Vec<_>>();
151+
152+
json::serialize(vec.clone())
153+
}

src/display/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::api::fav::get_list::FavEntry;
12
use crate::api::ing::get_comment_list::IngCommentEntry;
23
use crate::api::ing::get_list::IngEntry;
34
use crate::api::news::get_list::NewsEntry;
@@ -160,3 +161,16 @@ pub fn list_news(
160161
Style::Json => json::list_news(news_list, rev),
161162
}
162163
}
164+
165+
pub fn list_fav(
166+
style: &Style,
167+
time_style: &TimeStyle,
168+
fav_list: &Result<Vec<FavEntry>>,
169+
rev: bool,
170+
) -> Result<String> {
171+
match style {
172+
Style::Colorful => colorful::list_fav(time_style, fav_list, rev),
173+
Style::Normal => normal::list_fav(time_style, fav_list, rev),
174+
Style::Json => json::list_fav(fav_list, rev),
175+
}
176+
}

0 commit comments

Comments
 (0)