From a5b89689939351836f56d8a41694195e992ebc6f Mon Sep 17 00:00:00 2001 From: mindfreq <144544047+mindfreq@users.noreply.github.com> Date: Tue, 5 May 2026 14:57:36 +0200 Subject: [PATCH] add_feed_direct --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/commands/feed.rs | 31 ++++++++++++++++++++++++++----- src-tauri/src/lib.rs | 5 +++-- src-tauri/src/parser.rs | 4 ++-- src/bindings.ts | 3 +++ src/hooks/useFeeds.ts | 21 ++++++++++++++++++++- 7 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 76f6312..d11789a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3362,6 +3362,7 @@ dependencies = [ "tauri-plugin-opener", "thiserror 2.0.18", "tokio", + "url", "uuid", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 87222da..7ef4dbf 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -30,3 +30,4 @@ thiserror = "2.0.18" uuid = { version = "1", features = ["v4"] } scraper = "0.22" cached = { version = "0.54", features = ["async"] } +url = "2" diff --git a/src-tauri/src/commands/feed.rs b/src-tauri/src/commands/feed.rs index aba8ec2..1be9367 100644 --- a/src-tauri/src/commands/feed.rs +++ b/src-tauri/src/commands/feed.rs @@ -1,5 +1,7 @@ use cached::proc_macro::cached; +use feed_rs::parser; use reqwest::header::{ACCEPT, ACCEPT_ENCODING}; +use tauri::utils::config::parse; use crate::client::CLIENT; use crate::config::feed_config::Feed; @@ -44,14 +46,33 @@ pub async fn add_feed(url: &str) -> Result { let icon = feed.favicon.as_deref().unwrap_or("favicon.ico"); Feed::add(title, &feed.url, &feed.feed_url, icon) } - None => { - Err(Error::MissingField( - "No RSS/Atom/JSON feed found at this URL".into(), - )) - } + None => Err(Error::MissingField( + "No RSS/Atom/JSON feed found at this URL".into(), + )), } } +#[tauri::command] +pub async fn add_feed_direct(feed_url: &str) -> Result { + // This function for full path, eg: 'https://blog.rust-lang.org/feed' + // + let xml = fetch_text( + feed_url, + "application/rss+xml,application/atom+xml,application/xml,text/xml,*/*;q=0.8", + ) + .await?; + + let feed = parser::parse(xml.as_bytes())?; + + let title = feed.title.map(|t| t.content).unwrap_or("no title".into()); + let icon = feed.icon.map(|i| i.uri).unwrap_or("defult.png".into()); + let url = url::Url::parse(feed_url) + .unwrap() + .origin() + .ascii_serialization(); + Feed::add(&title, &url, feed_url, &icon) +} + #[tauri::command] pub async fn get_feeds() -> Result, Error> { let feeds = Feed::get_all()?; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2d5da22..ea653ce 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3,7 +3,7 @@ pub mod commands; pub mod config; pub mod parser; -use commands::feed::{add_feed, get_entry, get_feed_item, get_feeds, remove_feed}; +use commands::feed::{add_feed, add_feed_direct, get_entry, get_feed_item, get_feeds, remove_feed}; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { @@ -15,10 +15,11 @@ pub fn run() { .plugin(tauri_plugin_opener::init()) .invoke_handler(tauri::generate_handler![ add_feed, + add_feed_direct, get_feeds, remove_feed, get_feed_item, - get_entry + get_entry, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/parser.rs b/src-tauri/src/parser.rs index 4faf5d0..870b6af 100644 --- a/src-tauri/src/parser.rs +++ b/src-tauri/src/parser.rs @@ -179,14 +179,14 @@ fn extract_favicon(document: &Html, base_url: &str) -> Option { } fn resolve_url(base: &str, href: &str) -> String { - if href.starts_with("http://") || href.starts_with("https://") { + if href.starts_with("http") { href.to_string() } else if href.starts_with("//") { format!("https:{}", href) } else if href.starts_with('/') { // رابط مطلق نسبي let base = base.strip_suffix('/').unwrap_or(base); - + format!("{}{}", base, href) } else { // رابط نسبي diff --git a/src/bindings.ts b/src/bindings.ts index 04978cc..faecd38 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -44,6 +44,9 @@ export const commands = { addFeed: (url: string): Promise => tauriInvoke("add_feed", { url }), + addFeedDirect: (feedUrl: string): Promise => + tauriInvoke("add_feed_direct", { feedUrl }), + getFeeds: (): Promise => tauriInvoke("get_feeds"), diff --git a/src/hooks/useFeeds.ts b/src/hooks/useFeeds.ts index c653c8a..1b9566d 100644 --- a/src/hooks/useFeeds.ts +++ b/src/hooks/useFeeds.ts @@ -3,6 +3,22 @@ import { commands, Feed, FeedItem } from "../bindings"; import { UiError, View } from "../types"; import { extractErrorMessage, normalizeErrorMessage } from "../utils"; +const DIRECT_FEED_PATH_PATTERN = /(feed|rss|atom)(\.[a-z0-9]+)?$/i; +const DIRECT_FEED_EXTENSION_PATTERN = /\.(rss|xml|atom|rdf|json)$/i; + +const isLikelyDirectFeedUrl = (value: string): boolean => { + try { + const { pathname } = new URL(value); + const normalizedPath = pathname.toLowerCase(); + return ( + DIRECT_FEED_PATH_PATTERN.test(normalizedPath) || + DIRECT_FEED_EXTENSION_PATTERN.test(normalizedPath) + ); + } catch { + return false; + } +}; + export function useFeeds() { const [feeds, setFeeds] = useState([]); const [loadingFeeds, setLoadingFeeds] = useState(true); @@ -76,7 +92,10 @@ export function useFeeds() { const addFeed = useCallback( async (url: string) => { - const feed = await commands.addFeed(url.trim()); + const normalizedUrl = url.trim(); + const feed = isLikelyDirectFeedUrl(normalizedUrl) + ? await commands.addFeedDirect(normalizedUrl) + : await commands.addFeed(normalizedUrl); setFeeds((prev) => [...prev, feed]); return feed; },