Compare commits
No commits in common. "0615cd665fe183f80621cbabdf89644b5b9a1408" and "ba0dde7ec8ce137325f172d27b585107a5604d82" have entirely different histories.
0615cd665f
...
ba0dde7ec8
9 changed files with 26 additions and 20227 deletions
|
|
@ -11,13 +11,12 @@ rusqlite = {version=">=0.34",features=['bundled']}
|
||||||
scraper = "0.23.1"
|
scraper = "0.23.1"
|
||||||
directories = "6.0.0"
|
directories = "6.0.0"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
|
rss_content = { git = "https://code.gabe.rocks/gabriel/rss_content", version = "0.1.1" }
|
||||||
url = "2.5.4"
|
url = "2.5.4"
|
||||||
opml = "1.1.6"
|
opml = "1.1.6"
|
||||||
sha1 = "0.10.6"
|
sha1 = "0.10.6"
|
||||||
bytes = "1.11.1"
|
bytes = "1.11.1"
|
||||||
ego-tree = "0.10.0"
|
|
||||||
#rfd = "0.15.4" (for importing files)
|
#rfd = "0.15.4" (for importing files)
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
debug=true
|
debug=true
|
||||||
incremental = true
|
incremental = true
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ pub fn get_feed_id_by_url(url: &str) -> Option<i64> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn add_feed(url: &str) -> Option<i64> {
|
pub fn add_feed(url: &str) -> Option<i64> {
|
||||||
let feed: Channel;
|
let mut feed: Channel;
|
||||||
match load_rss(url) {
|
match load_rss(url) {
|
||||||
Some(f) => {
|
Some(f) => {
|
||||||
feed = f;
|
feed = f;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ pub mod ui;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod files;
|
pub mod files;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
mod rss_content;
|
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
db::initialize();
|
db::initialize();
|
||||||
|
|
|
||||||
28
src/net.rs
28
src/net.rs
|
|
@ -89,20 +89,28 @@ fn get_client(network: Network) -> Result<Client, Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(url: &str) -> Option<reqwest::blocking::Response> {
|
|
||||||
let client = get_client(url_network(url)).unwrap();
|
|
||||||
client.get(url)
|
|
||||||
.header(USER_AGENT, "RSS Reader")
|
|
||||||
.send()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_content(url: &str) -> Option<String> {
|
pub fn get_content(url: &str) -> Option<String> {
|
||||||
fetch(url)?.text().ok()
|
let client = get_client(url_network(url)).unwrap();
|
||||||
|
let res = client.get(url).header(USER_AGENT, "RSS Reader").send();
|
||||||
|
match res {
|
||||||
|
Ok(resp) => match resp.text() {
|
||||||
|
Ok(body) => return Some(body),
|
||||||
|
Err(_) => return None,
|
||||||
|
},
|
||||||
|
Err(_) => return None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bytes(url: &str) -> Option<bytes::Bytes> {
|
pub fn get_bytes(url: &str) -> Option<bytes::Bytes> {
|
||||||
fetch(url)?.bytes().ok()
|
let client = get_client(url_network(url)).unwrap();
|
||||||
|
let res = client.get(url).header(USER_AGENT, "RSS Reader").send();
|
||||||
|
match res {
|
||||||
|
Ok(resp) => match resp.bytes() {
|
||||||
|
Ok(body) => return Some(body),
|
||||||
|
Err(_) => return None,
|
||||||
|
},
|
||||||
|
Err(_) => return None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retrieve_opml(url: &str) -> Vec<Url> {
|
pub fn retrieve_opml(url: &str) -> Vec<Url> {
|
||||||
|
|
|
||||||
|
|
@ -1,350 +0,0 @@
|
||||||
use scraper::{ElementRef, Html, Node};
|
|
||||||
use iced::widget::markdown;
|
|
||||||
|
|
||||||
/*
|
|
||||||
The goal here is to flatten the DOM as much as possible.
|
|
||||||
paragraphs with fancy formatting are turned into markdown, same with
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Supported content
|
|
||||||
#[derive(Debug,Clone)]
|
|
||||||
pub enum Content {
|
|
||||||
Markdown(String),
|
|
||||||
MarkdownParsed(Vec<markdown::Item>),
|
|
||||||
Image(String),
|
|
||||||
Audio(String),
|
|
||||||
Video(String),
|
|
||||||
Ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_content(c: &str) -> Vec<Content>{
|
|
||||||
process_content(&itemize_content(c)).into_iter().map(|i| {
|
|
||||||
match i {
|
|
||||||
Content::Markdown(s) => {
|
|
||||||
Content::MarkdownParsed(markdown::parse(&s).collect())
|
|
||||||
//this is super lazy but it works....
|
|
||||||
}
|
|
||||||
_ => {i}
|
|
||||||
}
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn markdownify_child(item: &Item) -> String {
|
|
||||||
let mut result = "".to_owned();
|
|
||||||
match markdown_content(&item) {
|
|
||||||
Content::Markdown(s) => {
|
|
||||||
result = result + &s;
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_children(children: &Vec<Item>) -> String {
|
|
||||||
let mut result = "".to_owned();
|
|
||||||
for c in children{
|
|
||||||
result = result + &markdownify_child(c);
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn has_image(children: &Vec<Item>) -> bool {
|
|
||||||
for c in children {
|
|
||||||
match c {
|
|
||||||
Item::Image(_) => {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_video(children: &Vec<Item>) -> bool{
|
|
||||||
for c in children {
|
|
||||||
match c {
|
|
||||||
Item::Video(_) => {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn markdown_content(item: &Item) -> Content {
|
|
||||||
let mut markdown = String::new();
|
|
||||||
match item {
|
|
||||||
Item::Title(n,children) => {
|
|
||||||
markdown = markdown + &"#".repeat(*n) + " " +&process_children(children);
|
|
||||||
},
|
|
||||||
Item::BoldedText(children) => {
|
|
||||||
markdown = format!("**{}**",process_children(children));
|
|
||||||
},
|
|
||||||
Item::EmphasisText(children) => {
|
|
||||||
markdown = format!("*{}*",process_children(children));
|
|
||||||
}
|
|
||||||
Item::Text(s) => {
|
|
||||||
markdown = markdown + s;
|
|
||||||
},
|
|
||||||
Item::Link(href, children) => {
|
|
||||||
markdown = markdown + &format!("[{}]({})",process_children(children),href);
|
|
||||||
}
|
|
||||||
Item::Paragraph(children) => {
|
|
||||||
markdown = markdown + &process_children(children);
|
|
||||||
}
|
|
||||||
Item::UnorderedList(children) => {
|
|
||||||
markdown = markdown + &process_children(children);
|
|
||||||
}
|
|
||||||
Item::OrderedList(children) => {
|
|
||||||
markdown = markdown + &process_children(children);
|
|
||||||
}
|
|
||||||
Item::ListItem(children) => {
|
|
||||||
markdown = "\n- ".to_owned() + &process_children(children);
|
|
||||||
}
|
|
||||||
Item::Code(children) => {
|
|
||||||
markdown = markdown + &format!("```{}```",&process_children(children));
|
|
||||||
}
|
|
||||||
Item::Blockquote(children) => {
|
|
||||||
markdown = markdown + "> " + &process_children(children);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Content::Markdown(markdown)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_media_source(children: &Vec<Item>) -> Option<String> {
|
|
||||||
for c in children {
|
|
||||||
match c {
|
|
||||||
Item::Source(src) => {
|
|
||||||
return Some(src.to_owned());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn media_content(item: &Item) -> Content{
|
|
||||||
match item {
|
|
||||||
Item::Link(_,children) => {
|
|
||||||
for c in children {
|
|
||||||
match c {
|
|
||||||
Item::Video(_) => {return media_content(c);}
|
|
||||||
Item::Audio(_) => {return media_content(c);}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Content::Ignore
|
|
||||||
}
|
|
||||||
Item::Audio(children) => {
|
|
||||||
match get_media_source(children) {
|
|
||||||
Some(s) => {
|
|
||||||
return Content::Audio(s);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Content::Markdown("<Audio Element with no source>".to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item::Video(children) => {
|
|
||||||
match get_media_source(children) {
|
|
||||||
Some(source) => {
|
|
||||||
return Content::Video(source)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Content::Markdown("<Video element with no source>".to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
Content::Markdown(format!("Incorrectly assigned element:{:#?}",item))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn process_content(items: &Vec<Item>) -> Vec<Content> {
|
|
||||||
let mut result: Vec<Content> = Vec::new();
|
|
||||||
//println!("Converting {} items into Content",items.len());
|
|
||||||
for i in items {
|
|
||||||
match i {
|
|
||||||
Item::Title(_,_) => {
|
|
||||||
result.push(markdown_content(i));
|
|
||||||
}
|
|
||||||
Item::Paragraph(_) => {
|
|
||||||
result.push(markdown_content(i));
|
|
||||||
},
|
|
||||||
Item::Link(_,children) => {
|
|
||||||
if has_video(children){
|
|
||||||
result.push(media_content(i));
|
|
||||||
}
|
|
||||||
else if has_image(children){
|
|
||||||
result.push(media_content(i));
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
result.push(markdown_content(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item::UnorderedList(_) => {
|
|
||||||
result.push(markdown_content(i));
|
|
||||||
}
|
|
||||||
Item::OrderedList(_) => {
|
|
||||||
result.push(markdown_content(i));
|
|
||||||
}
|
|
||||||
Item::ListItem(_) => {
|
|
||||||
result.push(markdown_content(i));
|
|
||||||
}
|
|
||||||
Item::Image(src) => {
|
|
||||||
result.push(Content::Image(src.to_owned()));
|
|
||||||
}
|
|
||||||
Item::Video(_) => {
|
|
||||||
result.push(media_content(i));
|
|
||||||
}
|
|
||||||
Item::Audio(_) => {
|
|
||||||
result.push(media_content(i));
|
|
||||||
}
|
|
||||||
Item::Container(children) => {
|
|
||||||
//should actually plan to contain things
|
|
||||||
for c in process_content(children){
|
|
||||||
result.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item::Code(_) => {
|
|
||||||
result.push(markdown_content(i));
|
|
||||||
}
|
|
||||||
Item::Blockquote(_) => {
|
|
||||||
result.push(markdown_content(i));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
result.push(Content::Ignore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
|
||||||
enum Item {
|
|
||||||
Ignore,
|
|
||||||
Title(usize,Vec<Item>),
|
|
||||||
Text(String),
|
|
||||||
//text, links, formatting are all markdown
|
|
||||||
//arguably, for better control it will be best to turn markdown into its own set of items
|
|
||||||
Image(String),
|
|
||||||
Video(Vec<Item>),
|
|
||||||
Audio(Vec<Item>),
|
|
||||||
Source(String),
|
|
||||||
Blockquote(Vec<Item>),
|
|
||||||
Code(Vec<Item>),
|
|
||||||
BoldedText(Vec<Item>),
|
|
||||||
EmphasisText(Vec<Item>),
|
|
||||||
UnorderedList(Vec<Item>),
|
|
||||||
OrderedList(Vec<Item>),
|
|
||||||
ListItem(Vec<Item>),
|
|
||||||
Paragraph(Vec<Item>),
|
|
||||||
Container(Vec<Item>),
|
|
||||||
Link(String,Vec<Item>),
|
|
||||||
Table(Vec<Item>)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn itemize_content(content: &str) -> Vec<Item> {
|
|
||||||
let frag = Html::parse_fragment(content);
|
|
||||||
frag.root_element().children().map(|e|{
|
|
||||||
parse_items(e)
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_children(el: &ElementRef) -> Vec<Item>{
|
|
||||||
el.children().map(|c|{parse_items(c)}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_items(n: ego_tree::NodeRef<'_,Node>) -> Item{
|
|
||||||
if n.value().is_text(){
|
|
||||||
return Item::Text((&n.value().as_text().unwrap()).to_string())
|
|
||||||
}
|
|
||||||
if n.value().is_element(){
|
|
||||||
let el = ElementRef::wrap(n).unwrap();
|
|
||||||
let tag_name = el.value().name();
|
|
||||||
match tag_name {
|
|
||||||
"h1" => {return Item::Title(1, get_children(&el))},
|
|
||||||
"h2" => {return Item::Title(2, get_children(&el))},
|
|
||||||
"h3" => {return Item::Title(3, get_children(&el))},
|
|
||||||
"h4" => {return Item::Title(4, get_children(&el))},
|
|
||||||
"h5" => {return Item::Title(5, get_children(&el))},
|
|
||||||
"h6" => {return Item::Title(6, get_children(&el))},
|
|
||||||
"strong" => {return Item::BoldedText(get_children(&el))},
|
|
||||||
"em" => {return Item::EmphasisText(get_children(&el))},
|
|
||||||
"br" => {return Item::Text("\n".to_owned())},
|
|
||||||
"hr" => {return Item::Text("---".to_owned())}
|
|
||||||
"p" => {
|
|
||||||
return Item::Paragraph(get_children(&el))
|
|
||||||
},
|
|
||||||
"a" => {
|
|
||||||
let href = match el.attr("href") {
|
|
||||||
Some(link) => {link}
|
|
||||||
None => {""}
|
|
||||||
};
|
|
||||||
return Item::Link(href.to_owned(),get_children(&el))
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
"img" => {
|
|
||||||
match el.attr("src") {
|
|
||||||
Some(src) => {
|
|
||||||
return Item::Image(src.to_owned())
|
|
||||||
},
|
|
||||||
None => {return Item::Ignore}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"source" => {
|
|
||||||
match el.attr("src") {
|
|
||||||
Some(src) => {
|
|
||||||
return Item::Source(src.to_owned())
|
|
||||||
},
|
|
||||||
None => {return Item::Ignore}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"video" => {
|
|
||||||
return Item::Video(get_children(&el))
|
|
||||||
}
|
|
||||||
"audio" => {
|
|
||||||
return Item::Audio(get_children(&el))
|
|
||||||
}
|
|
||||||
"ol" => {
|
|
||||||
return Item::OrderedList(get_children(&el))
|
|
||||||
}
|
|
||||||
"ul" => {
|
|
||||||
return Item::UnorderedList(get_children(&el))
|
|
||||||
}
|
|
||||||
"li" => {
|
|
||||||
return Item::ListItem(get_children(&el))
|
|
||||||
}
|
|
||||||
"div" => {
|
|
||||||
return Item::Container(get_children(&el))
|
|
||||||
}
|
|
||||||
"code" => {
|
|
||||||
return Item::Code(get_children(&el))
|
|
||||||
}
|
|
||||||
"blockquote" => {
|
|
||||||
return Item::Blockquote(get_children(&el))
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Item::Ignore
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,67 +0,0 @@
|
||||||
mod example_data;
|
|
||||||
use super::*;
|
|
||||||
use example_data::*;
|
|
||||||
|
|
||||||
fn get_feed(u: &str) -> rss::Channel {
|
|
||||||
rss::Channel::read_from(u.as_bytes()).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fragments() {
|
|
||||||
for f in FRAGMENTS {
|
|
||||||
println!("Processing Fragment:{}",f);
|
|
||||||
let items = itemize_content(f);
|
|
||||||
let content = process_content(&items);
|
|
||||||
println!("Content:\n{:#?}",content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn content_test() {
|
|
||||||
let example_text = Item::Text("Example.com".to_owned());
|
|
||||||
let example_link = Item::Link("https://example.com".to_owned(), [example_text].to_vec());
|
|
||||||
let result = process_content(&[example_link].to_vec());
|
|
||||||
println!("Items to content parse result:\n{:#?}", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn content_display() {
|
|
||||||
let feed = get_feed(example_data::GABE_ROCKS);
|
|
||||||
let content: &Vec<Content> = &process_content(&itemize_content(
|
|
||||||
feed.items.get(4).unwrap().content().unwrap(),
|
|
||||||
));
|
|
||||||
println!("Content: {:#?}", content)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn itemize_feeds() {
|
|
||||||
let _ = FEEDS.map(|u| {
|
|
||||||
let feed = get_feed(u);
|
|
||||||
let results: Vec<_> = feed
|
|
||||||
.items
|
|
||||||
.into_iter()
|
|
||||||
.map(|item| {
|
|
||||||
itemize_content(&item.content.unwrap());
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
//let results: Vec<_> = itemize_content(u);
|
|
||||||
println!(
|
|
||||||
"Evaluated feed\nScanned {} items without errors",
|
|
||||||
results.len()
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn markdownify_feeds() {
|
|
||||||
let _ = FEEDS.map(|u| {
|
|
||||||
let feed = get_feed(u);
|
|
||||||
let results: Vec<_> = feed
|
|
||||||
.items
|
|
||||||
.into_iter()
|
|
||||||
.map(|item| {
|
|
||||||
process_content(&itemize_content(&item.content.unwrap()));
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
println!("Processed {} items without errors", results.len())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -15,8 +15,9 @@ use iced::{
|
||||||
Element,
|
Element,
|
||||||
Length::Fill,
|
Length::Fill,
|
||||||
};
|
};
|
||||||
use crate::rss_content::parse_content;
|
use rss_content::parse_content;
|
||||||
use crate::rss_content::Content;
|
use rss_content::Content;
|
||||||
|
use url::Url;
|
||||||
const ICON: &[u8] = include_bytes!("../assets/icon_placeholder.png");
|
const ICON: &[u8] = include_bytes!("../assets/icon_placeholder.png");
|
||||||
|
|
||||||
pub fn user_interface() -> iced::Result {
|
pub fn user_interface() -> iced::Result {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ use iced::{
|
||||||
widget::{button, column, container, text},
|
widget::{button, column, container, text},
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
use crate::rss_content::Content;
|
use rss_content;
|
||||||
|
use rss_content::Content;
|
||||||
use ui::Message;
|
use ui::Message;
|
||||||
|
|
||||||
pub fn list_feeds() -> iced::widget::Column<'static, Message> {
|
pub fn list_feeds() -> iced::widget::Column<'static, Message> {
|
||||||
|
|
@ -90,7 +91,7 @@ pub fn media_view(state: &'_ ui::State) -> Element<'_, Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn navbar(state: &ui::State) -> Element<'_, Message> {
|
pub fn navbar(state: &ui::State) -> Element<Message> {
|
||||||
match state.page {
|
match state.page {
|
||||||
Page::Home => row::Row::new()
|
Page::Home => row::Row::new()
|
||||||
.push(button("Feeds").on_press(Message::ChangePage(Page::Feeds)))
|
.push(button("Feeds").on_press(Message::ChangePage(Page::Feeds)))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue