Multi Feeds + Ron rss save
This commit is contained in:
@@ -1,22 +1,35 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use tauri::State;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
pub struct RssConfig {
|
||||
pub urls: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NewsState {
|
||||
pub openrouter_key: Mutex<String>,
|
||||
pub rss_url: Mutex<String>, // Neu: Speicher für die RSS-URL
|
||||
pub rss_config: Mutex<RssConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NewsArticle {
|
||||
pub content: String,
|
||||
pub author: String,
|
||||
pub created_at: u64,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
fn get_config_path(app: &AppHandle) -> PathBuf {
|
||||
app.path()
|
||||
.app_config_dir()
|
||||
.unwrap_or_else(|_| PathBuf::from("."))
|
||||
.join("rss.ron")
|
||||
}
|
||||
|
||||
// Bestehender Command für den API Key
|
||||
#[tauri::command]
|
||||
pub async fn save_openrouter_key(key: String, state: State<'_, NewsState>) -> Result<(), String> {
|
||||
let mut lock = state.openrouter_key.lock().map_err(|_| "Lock failed")?;
|
||||
@@ -24,11 +37,63 @@ pub async fn save_openrouter_key(key: String, state: State<'_, NewsState>) -> Re
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// NEU: Command zum Speichern der RSS URL
|
||||
#[tauri::command]
|
||||
pub async fn save_rss_url(url: String, state: State<'_, NewsState>) -> Result<(), String> {
|
||||
let mut lock = state.rss_url.lock().map_err(|_| "Lock failed")?;
|
||||
*lock = url.trim().to_string();
|
||||
pub async fn load_rss_config(
|
||||
app: AppHandle,
|
||||
state: State<'_, NewsState>,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let path = get_config_path(&app);
|
||||
|
||||
// Initialisierung mit Standard-Feeds, falls Datei nicht existiert
|
||||
if !path.exists() {
|
||||
let default_urls = vec![
|
||||
"https://www.nasa.gov/news-release/feed/".to_string(),
|
||||
"https://www.heise.de/rss/heise-atom.xml".to_string(),
|
||||
];
|
||||
|
||||
let config = RssConfig {
|
||||
urls: default_urls.clone(),
|
||||
};
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
let ron_str = ron::to_string(&config).map_err(|e| e.to_string())?;
|
||||
fs::write(&path, ron_str).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut lock = state.rss_config.lock().unwrap();
|
||||
lock.urls = default_urls.clone();
|
||||
|
||||
return Ok(default_urls);
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||
let config: RssConfig = ron::from_str(&content).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut lock = state.rss_config.lock().unwrap();
|
||||
*lock = config.clone();
|
||||
Ok(config.urls)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_rss_urls(
|
||||
urls: Vec<String>,
|
||||
app: AppHandle,
|
||||
state: State<'_, NewsState>,
|
||||
) -> Result<(), String> {
|
||||
let config = RssConfig { urls };
|
||||
let path = get_config_path(&app);
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
let ron_str = ron::to_string(&config).map_err(|e| e.to_string())?;
|
||||
fs::write(path, ron_str).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut lock = state.rss_config.lock().unwrap();
|
||||
*lock = config;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -39,79 +104,74 @@ pub async fn fetch_ai_news(state: State<'_, NewsState>) -> Result<Vec<NewsArticl
|
||||
.lock()
|
||||
.map_err(|_| "Lock failed")?
|
||||
.clone();
|
||||
|
||||
if key.is_empty() {
|
||||
return Err("API Key fehlt.".to_string());
|
||||
return Err("API Key fehlt.".into());
|
||||
}
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let body = serde_json::json!({
|
||||
"model": "minimax/minimax-m2.1",
|
||||
"messages": [
|
||||
{"role": "system", "content": "Kurze News, 1-2 Sätze, Deutsch."},
|
||||
{"role": "user", "content": "Tech-News."}
|
||||
]
|
||||
});
|
||||
|
||||
let response = client
|
||||
.post("https://openrouter.ai/api/v1/chat/completions")
|
||||
.header("Authorization", format!("Bearer {}", key))
|
||||
.json(&body)
|
||||
.json(&serde_json::json!({
|
||||
"model": "minimax/minimax-m2.1",
|
||||
"messages": [
|
||||
{"role": "system", "content": "Kurze News, 1-2 Sätze, Deutsch."},
|
||||
{"role": "user", "content": "Tech-News."}
|
||||
]
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let json_res: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
|
||||
let content = json_res["choices"][0]["message"]["content"]
|
||||
let json: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
|
||||
let content = json["choices"][0]["message"]["content"]
|
||||
.as_str()
|
||||
.unwrap_or("Fehler")
|
||||
.to_string();
|
||||
|
||||
Ok(vec![NewsArticle {
|
||||
content,
|
||||
author: "OpenRouter AI".to_string(),
|
||||
created_at: 0,
|
||||
author: "OpenRouter AI".into(),
|
||||
created_at: "Gerade eben".into(),
|
||||
}])
|
||||
}
|
||||
|
||||
// VERBESSERT: Lädt nun den echten Feed von der gespeicherten URL
|
||||
#[tauri::command]
|
||||
pub async fn fetch_rss_news(state: State<'_, NewsState>) -> Result<Vec<NewsArticle>, String> {
|
||||
let url = state.rss_url.lock().map_err(|_| "Lock failed")?.clone();
|
||||
let urls = state.rss_config.lock().unwrap().urls.clone();
|
||||
let mut all_articles = Vec::new();
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
if url.is_empty() {
|
||||
return Err("RSS URL fehlt. Bitte in den Einstellungen eintragen.".to_string());
|
||||
for url in urls {
|
||||
if let Ok(res) = client.get(&url).send().await {
|
||||
if let Ok(bytes) = res.bytes().await {
|
||||
if let Ok(feed) = feed_rs::parser::parse(&bytes[..]) {
|
||||
let source = feed
|
||||
.title
|
||||
.map(|t| t.content)
|
||||
.unwrap_or_else(|| "RSS".into());
|
||||
|
||||
for entry in feed.entries.into_iter().take(3) {
|
||||
let date = entry
|
||||
.published
|
||||
.or(entry.updated)
|
||||
.map(|d| d.format("%d.%m.%Y %H:%M").to_string())
|
||||
.unwrap_or_else(|| "--.--.----".into());
|
||||
|
||||
all_articles.push(NewsArticle {
|
||||
content: format!(
|
||||
"### {}\n\n{}",
|
||||
entry.title.map(|t| t.content).unwrap_or_default(),
|
||||
entry.summary.map(|s| s.content).unwrap_or_default()
|
||||
),
|
||||
author: source.clone(),
|
||||
created_at: date,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let response = reqwest::get(&url)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let feed = feed_rs::parser::parse(&response[..]).map_err(|e| e.to_string())?;
|
||||
|
||||
let articles = feed
|
||||
.entries
|
||||
.into_iter()
|
||||
.take(5)
|
||||
.map(|entry| NewsArticle {
|
||||
content: format!(
|
||||
"### {}\n\n{}",
|
||||
entry.title.map(|t| t.content).unwrap_or_default(),
|
||||
entry
|
||||
.summary
|
||||
.map(|s| s.content)
|
||||
.unwrap_or_else(|| "Kein Inhalt".to_string())
|
||||
),
|
||||
author: feed
|
||||
.title
|
||||
.as_ref()
|
||||
.map(|t| t.content.clone())
|
||||
.unwrap_or_else(|| "RSS".into()),
|
||||
created_at: entry.updated.map(|d| d.timestamp() as u64).unwrap_or(0),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(articles)
|
||||
Ok(all_articles)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user