1use ansi_term::Style;
2use chrono::offset::Utc;
3use chrono::DateTime;
4use dialoguer::{theme::SimpleTheme, MultiSelect};
5use feed_rs::parser;
6use futures::stream::StreamExt;
7use itertools::Itertools;
8use reqwest::Client;
9use serde::{Deserialize, Serialize};
10use std::cell::RefCell;
11use std::fs;
12use std::fs::File;
13use std::io::Write;
14use std::path::Path;
15
16#[derive(Debug, Clone)]
17pub struct ProcessedFeed {
18 pub title: String,
19 pub items: Vec<String>,
20}
21
22impl std::fmt::Display for ProcessedFeed {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(
25 f,
26 "📖 {}\n\t{}",
27 Style::new().bold().paint(&self.title),
28 format!("{}", self.items.iter().format("\n\t"))
29 )
30 }
31}
32
33#[derive(Debug, Serialize, Deserialize, Clone)]
34pub struct Feed {
35 pub uri: String,
36 pub last_accessed: String,
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40pub struct ConfigObj {
41 pub feeds: Vec<Feed>,
42}
43
44pub fn find_config() -> std::path::PathBuf {
45 let homedir: std::path::PathBuf = dirs::home_dir().expect("no home dir");
46 Path::new(&homedir).join(".hemrc")
48}
49
50fn config_to_rust() -> Result<ConfigObj, Box<dyn std::error::Error>> {
51 let config_path = find_config();
52 let config = match fs::read_to_string(&config_path) {
53 Ok(config) => config,
54 Err(e) => {
55 if e.kind() == std::io::ErrorKind::NotFound {
56 eprintln!("Didn't find a .hemrc, creating it now...");
57 let mut configfile = fs::OpenOptions::new()
59 .read(true)
60 .write(true)
61 .create_new(true)
62 .open(&config_path)?;
63 configfile.write_all(r#"{"feeds": []}"#.as_bytes())?;
64 let contents = String::from(r#"{"feeds": []}"#);
65 contents
66 } else {
67 return Err(Box::from("Catastrophe!"));
68 }
69 }
70 };
71 Ok(serde_json::from_str(&config).unwrap())
72}
73
74pub fn rust_to_config(content: &[u8]) {
75 let mut file = match File::create(find_config()) {
76 Err(why) => panic!("config file access failed: {}", why),
77 Ok(file) => file,
78 };
79 file.write_all(content)
80 .expect("Writing to .hemrc failed :(");
81}
82
83pub fn add_feed(feed: &str) {
84 let mut my_feeds: ConfigObj = config_to_rust().unwrap();
85 my_feeds.feeds.push(Feed {
86 uri: feed.to_owned(),
87 last_accessed: Utc::now().to_rfc3339().to_owned(),
88 });
89 rust_to_config(serde_json::to_string(&my_feeds).unwrap().as_bytes());
90}
91
92pub fn list_feeds() {
93 let config: ConfigObj = config_to_rust().unwrap();
94 for f in config.feeds {
96 println!("{}", f.uri);
97 }
98}
99
100pub fn get_uris_and_update() -> Vec<Feed> {
101 let mut config = config_to_rust().unwrap();
102 let mut uris: Vec<Feed> = Vec::new();
103 let len = config.feeds.len();
104 for i in 0..len {
105 let x = config.feeds[i].to_owned();
106 uris.push(x);
107 config.feeds[i].last_accessed = Utc::now().to_rfc3339().to_owned();
108 }
109 rust_to_config(serde_json::to_string(&config).unwrap().as_bytes());
110 uris
111}
112
113pub fn remove() {
114 let mut config: ConfigObj = config_to_rust().unwrap();
115 let mut uris: Vec<String> = Vec::new();
116 let feeds_list = &config.feeds;
117 for f in feeds_list {
118 uris.push(f.uri.clone());
119 }
120 let multiselected = uris;
121 let mut selections = MultiSelect::with_theme(&SimpleTheme)
122 .with_prompt("Use arrow keys to move up or down. Press the space bar to select a feed. Press enter when you're done to remove all selected feeds")
123 .items(&multiselected[..])
124 .interact()
125 .unwrap();
126
127 if selections.is_empty() {
129 println!("You did not select anything :(");
130 } else {
131 println!("Removing these feeds:");
132 selections.reverse();
133 for selection in selections {
134 println!(" {}", multiselected[selection]);
135 config.feeds.remove(selection);
136 }
137 }
138 rust_to_config(serde_json::to_string(&config).unwrap().as_bytes())
139}
140
141pub async fn read_feed_fast(num: usize) -> Result<Vec<ProcessedFeed>, Box<dyn std::error::Error>> {
142 let client = &Client::builder().build()?;
143
144 let config_obj = config_to_rust().unwrap();
145 if config_obj.feeds.len() == 0 {
146 return Err(Box::from(
147 "Your feeds list is empty! use `hem add` to add a feed.",
148 ));
149 };
150 let processed = RefCell::new(Vec::<ProcessedFeed>::new());
151 let fetches = futures::stream::iter(config_obj.feeds.into_iter().map(|feed| {
152 let y = &processed;
153 async move {
154 match client.get(&feed.uri).send().await {
155 Ok(resp) => match resp.text().await {
156 Ok(text) => {
157 let feed = parser::parse(text.as_bytes()).unwrap();
158 let title = feed.title.unwrap();
159 let title_owned = title.content.to_owned();
160
161 let entries = feed.entries.iter().enumerate();
162 let mut processed_items = Vec::<String>::new();
163 for (j, e) in entries {
164 if j < num {
165 let e_title = e.title.as_ref().unwrap();
166 processed_items.push(format!(
167 "{} \n\t {}\n",
168 Style::new().italic().paint(e_title.content.clone()),
169 e.links[0].href
170 ));
171 } else {
172 break;
173 }
174 }
175 let feed_to_add = ProcessedFeed {
176 title: title_owned,
177 items: processed_items,
178 };
179 y.borrow_mut().push(feed_to_add);
180 }
181 Err(_) => {
182 println!("ERROR reading {}", feed.uri);
183 }
184 },
185 Err(_) => {
186 println!("ERROR reading {}", feed.uri);
187 }
188 };
189 }
190 }))
191 .buffer_unordered(20)
192 .collect::<Vec<()>>();
193
194 fetches.await;
195 let x = processed.borrow();
196 Ok(x.to_vec())
197}
198
199pub async fn read_feed_fast_duration() -> Result<Vec<ProcessedFeed>, Box<dyn std::error::Error>> {
200 let client = &Client::builder().build()?;
201
202 let uris = get_uris_and_update();
209 if uris.len() == 0 {
210 return Err(Box::from(
211 "Your feeds list is empty! use `hem add` to add a feed.",
212 ));
213 }
214 let processed = RefCell::new(Vec::<ProcessedFeed>::new());
215 let fetches = futures::stream::iter(uris.into_iter().map(|config_feed| {
216 let y = &processed;
217 async move {
218 match client.get(&config_feed.uri).send().await {
219 Ok(resp) => match resp.text().await {
220 Ok(text) => {
221 let feed = parser::parse(text.as_bytes()).unwrap();
222 let last_accessed_parsed = DateTime::from(
223 DateTime::parse_from_rfc3339(&config_feed.last_accessed).unwrap(),
224 );
225 let title = feed.title.unwrap();
226 let title_owned = title.content.to_owned();
227
228 let entries = feed.entries.iter().enumerate();
229 let mut processed_items = Vec::<String>::new();
230 let mut entry_date;
231 for (j, e) in entries {
232 if e.updated.is_none() {
233 entry_date = e.published.unwrap();
234 } else {
235 entry_date = e.updated.unwrap();
236 }
237 let entry_duration = last_accessed_parsed - entry_date; if j < 5 && entry_duration.num_seconds() < 0 {
239 let e_title = e.title.as_ref().unwrap();
240 processed_items.push(format!(
241 "{} \n\t {}\n",
242 Style::new().italic().paint(e_title.content.clone()),
243 e.links[0].href
244 ));
245 } else {
246 break;
247 }
248 }
249 if processed_items.len() == 0 {
250 processed_items = vec![String::from("Nothing new here...")];
251 }
252 let feed_to_add = ProcessedFeed {
253 title: title_owned,
254 items: processed_items,
255 };
256 y.borrow_mut().push(feed_to_add);
257 }
258 Err(_) => {
259 println!("ERROR reading {}", config_feed.uri);
260 }
261 },
262 Err(_) => {
263 println!("ERROR reading {}", config_feed.uri);
264 }
265 };
266
267 }
269 }))
270 .buffer_unordered(20)
271 .collect::<Vec<()>>();
272 fetches.await;
273 let x = processed.borrow();
274 Ok(x.to_vec())
276}