add_feed_direct
This commit is contained in:
parent
0ab56a7279
commit
a5b8968993
7 changed files with 56 additions and 10 deletions
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
|
|
@ -3362,6 +3362,7 @@ dependencies = [
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,3 +30,4 @@ thiserror = "2.0.18"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
scraper = "0.22"
|
scraper = "0.22"
|
||||||
cached = { version = "0.54", features = ["async"] }
|
cached = { version = "0.54", features = ["async"] }
|
||||||
|
url = "2"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use cached::proc_macro::cached;
|
use cached::proc_macro::cached;
|
||||||
|
use feed_rs::parser;
|
||||||
use reqwest::header::{ACCEPT, ACCEPT_ENCODING};
|
use reqwest::header::{ACCEPT, ACCEPT_ENCODING};
|
||||||
|
use tauri::utils::config::parse;
|
||||||
|
|
||||||
use crate::client::CLIENT;
|
use crate::client::CLIENT;
|
||||||
use crate::config::feed_config::Feed;
|
use crate::config::feed_config::Feed;
|
||||||
|
|
@ -44,14 +46,33 @@ pub async fn add_feed(url: &str) -> Result<Feed, Error> {
|
||||||
let icon = feed.favicon.as_deref().unwrap_or("favicon.ico");
|
let icon = feed.favicon.as_deref().unwrap_or("favicon.ico");
|
||||||
Feed::add(title, &feed.url, &feed.feed_url, icon)
|
Feed::add(title, &feed.url, &feed.feed_url, icon)
|
||||||
}
|
}
|
||||||
None => {
|
None => Err(Error::MissingField(
|
||||||
Err(Error::MissingField(
|
"No RSS/Atom/JSON feed found at this URL".into(),
|
||||||
"No RSS/Atom/JSON feed found at this URL".into(),
|
)),
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn add_feed_direct(feed_url: &str) -> Result<Feed, Error> {
|
||||||
|
// 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]
|
#[tauri::command]
|
||||||
pub async fn get_feeds() -> Result<Vec<Feed>, Error> {
|
pub async fn get_feeds() -> Result<Vec<Feed>, Error> {
|
||||||
let feeds = Feed::get_all()?;
|
let feeds = Feed::get_all()?;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ pub mod commands;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod parser;
|
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)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
|
@ -15,10 +15,11 @@ pub fn run() {
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
add_feed,
|
add_feed,
|
||||||
|
add_feed_direct,
|
||||||
get_feeds,
|
get_feeds,
|
||||||
remove_feed,
|
remove_feed,
|
||||||
get_feed_item,
|
get_feed_item,
|
||||||
get_entry
|
get_entry,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|
|
||||||
|
|
@ -179,14 +179,14 @@ fn extract_favicon(document: &Html, base_url: &str) -> Option<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_url(base: &str, href: &str) -> String {
|
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()
|
href.to_string()
|
||||||
} else if href.starts_with("//") {
|
} else if href.starts_with("//") {
|
||||||
format!("https:{}", href)
|
format!("https:{}", href)
|
||||||
} else if href.starts_with('/') {
|
} else if href.starts_with('/') {
|
||||||
// رابط مطلق نسبي
|
// رابط مطلق نسبي
|
||||||
let base = base.strip_suffix('/').unwrap_or(base);
|
let base = base.strip_suffix('/').unwrap_or(base);
|
||||||
|
|
||||||
format!("{}{}", base, href)
|
format!("{}{}", base, href)
|
||||||
} else {
|
} else {
|
||||||
// رابط نسبي
|
// رابط نسبي
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ export const commands = {
|
||||||
addFeed: (url: string): Promise<Feed> =>
|
addFeed: (url: string): Promise<Feed> =>
|
||||||
tauriInvoke<Feed>("add_feed", { url }),
|
tauriInvoke<Feed>("add_feed", { url }),
|
||||||
|
|
||||||
|
addFeedDirect: (feedUrl: string): Promise<Feed> =>
|
||||||
|
tauriInvoke<Feed>("add_feed_direct", { feedUrl }),
|
||||||
|
|
||||||
getFeeds: (): Promise<Feed[]> =>
|
getFeeds: (): Promise<Feed[]> =>
|
||||||
tauriInvoke<Feed[]>("get_feeds"),
|
tauriInvoke<Feed[]>("get_feeds"),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,22 @@ import { commands, Feed, FeedItem } from "../bindings";
|
||||||
import { UiError, View } from "../types";
|
import { UiError, View } from "../types";
|
||||||
import { extractErrorMessage, normalizeErrorMessage } from "../utils";
|
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() {
|
export function useFeeds() {
|
||||||
const [feeds, setFeeds] = useState<Feed[]>([]);
|
const [feeds, setFeeds] = useState<Feed[]>([]);
|
||||||
const [loadingFeeds, setLoadingFeeds] = useState(true);
|
const [loadingFeeds, setLoadingFeeds] = useState(true);
|
||||||
|
|
@ -76,7 +92,10 @@ export function useFeeds() {
|
||||||
|
|
||||||
const addFeed = useCallback(
|
const addFeed = useCallback(
|
||||||
async (url: string) => {
|
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]);
|
setFeeds((prev) => [...prev, feed]);
|
||||||
return feed;
|
return feed;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue