darknet content should "just work" now. needs better UI support!

This commit is contained in:
Gabriel 2025-08-05 14:10:18 -04:00
parent 734971b883
commit 2d6a417987
4 changed files with 93 additions and 87 deletions

View file

@ -17,6 +17,7 @@ chrono = "0.4.41"
rss_content = { git = "https://code.gabe.rocks/gabriel/rss_content", version = "0.1.1" }
bytes = "1.10.1"
scraper = "0.23.1"
url = "2.5.4"
[profile.dev]
debug=true

View file

@ -1,86 +1,107 @@
use core::panic;
use iced::widget::image::Handle;
use rss::{Channel};
use reqwest::{self, blocking::Client, header::USER_AGENT, Error, Proxy};
use rss::Channel;
use scraper::{Html, Selector};
use url::Url;
const DEFAULT_TOR_PROXY: &str = "socks5h://127.0.0.1:9050";
const DEFAULT_I2P_PROXY: &str = "socks5h://127.0.0.1:4447";
enum Network{
#[derive(PartialEq)]
enum Network {
Clearnet,
Tor,
I2P
I2P,
Unknown,
}
fn get_client(network: Network) -> Result<Client,Error>{
fn url_network(url: &str) -> Network {
match Url::parse(url) {
Ok(u) => match u.host() {
Some(host) => {
let h = host.to_string();
if h.find(".onion").is_some() {
return Network::Tor;
} else if h.find(".i2p").is_some() {
return Network::I2P;
} else {
return Network::Clearnet;
}
}
None => {return Network::Unknown;}
},
Err(e) => {
return Network::Clearnet;
}
}
Network::Clearnet
}
#[test]
fn network_testing(){
assert!(url_network("http://gabe.onion/rss") == Network::Tor);
assert!(url_network("http://gabe.i2p/rss") == Network::I2P);
assert!(url_network("https://gabe.rocks/rss") == Network::Clearnet);
}
fn get_client(network: Network) -> Result<Client, Error> {
//in the long run, support for fancy things like arti & user-configurable proxies will be important, nevermind other networks like reticulum!
match network {
Network::Clearnet => {
return Ok(Client::new());
}
Network::Tor => {
match Client::builder().proxy(Proxy::http(DEFAULT_TOR_PROXY).unwrap()).build() {
Ok(client) => {return Ok(client);}
Err(e) => {return Err(e);}
match Client::builder()
.proxy(Proxy::http(DEFAULT_TOR_PROXY).unwrap())
.build()
{
Ok(client) => {
return Ok(client);
}
Err(e) => {
return Err(e);
}
}
}
Network::I2P => {
match Client::builder().proxy(Proxy::http(DEFAULT_I2P_PROXY).unwrap()).build() {
Ok(client) => {return Ok(client);}
Err(e) => {return Err(e);}
}
}
}
}
pub fn get_onion_content(url: &str) -> Option<String> {
match Client::builder().proxy(
Proxy::http(DEFAULT_TOR_PROXY).unwrap()
).build() {
Ok(client) => {
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;}
match Client::builder()
.proxy(Proxy::http(DEFAULT_I2P_PROXY).unwrap())
.build()
{
Ok(client) => {
return Ok(client);
}
Err(e) => {
return Err(e);
}
}
}
Err(e) => {
println!("Error loading content via Tor\nError:{}",e);
return None;
Network::Unknown => {
println!("Fetching client for unknown network...");
return Ok(Client::new());
}
}
}
Err(e) => {
println!("Failed to get Tor client\nError:{}",e);
return None;
}
}
}
fn get_content(url: &str) -> Option<String> {
let client = Client::new();
let res = client.get(url)
.header(USER_AGENT,"RSS Reader")
.send();
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}
Ok(resp) => match resp.text() {
Ok(body) => return Some(body),
Err(_) => return None,
},
Err(_) => return None,
}
}
pub fn get_feeds(url: &str) -> Option<Vec<Channel>>{
#[test]
fn get_onion_content() {
get_content("http://gabriel262me3lgv3w7xohtesg3laoojmtye644pwirhdm73qmedmsqd.onion/rss");
}
pub fn get_feeds(url: &str) -> Option<Vec<Channel>> {
let page = get_content(url).unwrap();
let doc = Html::parse_document(&page);
let link_selector = Selector::parse("head link").unwrap();
@ -90,7 +111,7 @@ pub fn get_feeds(url: &str) -> Option<Vec<Channel>>{
if rel == "alternate" {
if let Some(href) = e.value().attr("href") {
match load_rss(href) {
Some(c) => {feeds.push(c)}
Some(c) => feeds.push(c),
None => {}
}
}
@ -99,39 +120,37 @@ pub fn get_feeds(url: &str) -> Option<Vec<Channel>>{
}
return match feeds.len() {
0 => None,
_ => {Some(feeds)}
}
_ => Some(feeds),
};
}
pub fn is_feed(url: &str) -> bool {
match get_feeds(url) {
Some(_) => true,
None => false
None => false,
}
}
pub fn load_rss(url: &str) -> Option<Channel>{
pub fn load_rss(url: &str) -> Option<Channel> {
let client = Client::new();
let res = client.get(url)
.header(USER_AGENT,"RSS Reader")
.send();
let res = client.get(url).header(USER_AGENT, "RSS Reader").send();
match res {
Ok(resp) => {
match resp.bytes() {
Ok(body) => {
match Channel::read_from(&*body) {
Ok(channel) => {Some(channel)}
Err(e) => {panic!("Error parsing feed:\n{}",e);}
}
},
Err(_) => { panic!("Empty response")}
Ok(resp) => match resp.bytes() {
Ok(body) => match Channel::read_from(&*body) {
Ok(channel) => Some(channel),
Err(e) => {
panic!("Error parsing feed:\n{}", e);
}
},
Err(_) => {
panic!("Empty response")
}
},
Err(err) => {panic!("Error loading feed.:{}",err)}
Err(err) => {
panic!("Error loading feed.:{}", err)
}
}
}
}
pub fn get_item_image(item: &rss::Item) -> Option<&str> {
// Only bother with itunes:image
print!("{} \n", item.title().unwrap());
@ -152,7 +171,7 @@ pub fn get_item_image(item: &rss::Item) -> Option<&str> {
}
}
}
pub fn download_image(url: &str) -> Option<iced::widget::image::Handle>{
pub fn download_image(url: &str) -> Option<iced::widget::image::Handle> {
match reqwest::blocking::get(url) {
Ok(r) => {
let img: Handle = Handle::from_bytes(r.bytes().unwrap());

View file

@ -7,19 +7,6 @@ fn get_feed_test(){
}
#[test]
fn tor_gabe() {
let onion_gabe = net::get_onion_content("http://gabriel262me3lgv3w7xohtesg3laoojmtye644pwirhdm73qmedmsqd.onion/rss");
match onion_gabe {
Some(b) => {
println!("Successfully loaded over Tor:\n{}",b);
}
None => {
panic!("Failed to load via Tor")
}
}
}
#[test]
fn load_feeds() {
let url = "https://gabe.rocks";

View file

@ -145,7 +145,6 @@ fn view(state: &State) -> Element<'_, Message> {
Page::AllItems => item_list(&state),
Page::ItemView => item_view(&state),
Page::Testing => testing(&state),
_ => {home(&state)}
}
}