From 1d6da847b7075b4babf3409b9445db0b54677893 Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Fri, 31 Jul 2026 21:59:42 +0800 Subject: qml_plugin: duckduckgo --- qml_plugin/src/web/duckduckgo.rs | 127 +++++++++++++++++++++++++++++++++++++++ qml_plugin/src/web/google.rs | 0 2 files changed, 127 insertions(+) create mode 100644 qml_plugin/src/web/duckduckgo.rs create mode 100644 qml_plugin/src/web/google.rs (limited to 'qml_plugin/src/web') diff --git a/qml_plugin/src/web/duckduckgo.rs b/qml_plugin/src/web/duckduckgo.rs new file mode 100644 index 0000000..f80e4c8 --- /dev/null +++ b/qml_plugin/src/web/duckduckgo.rs @@ -0,0 +1,127 @@ +use std::ops::{Deref, DerefMut}; + +use duckduckgo::{ + browser::Browser, + params::{SafeSearch, SearchParams, Toggle}, +}; +use reqwest::Url; +use serde::Deserialize; + +const DUCKDUCKGO: &str = "https://duckduckgo.com"; + +pub struct DuckDuckGoContent { + title: String, + description_html: String, + preview_image: Option, +} + +#[derive(Debug, Deserialize)] +pub struct Infobox { + pub content: Vec, +} + +impl Deref for Infobox { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.content + } +} + +impl DerefMut for Infobox { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.content + } +} + +#[derive(Debug, Deserialize)] +pub struct InfoboxContent { + pub data_type: String, + pub label: String, + pub value: InfoboxValue, + pub wiki_order: String, // Kept as String since the JSON had "101" in quotes +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum InfoboxValue { + Entity(EntityValue), + String(String), +} + +#[derive(Debug, Deserialize)] +pub struct EntityValue { + #[serde(rename = "entity-type")] + pub entity_type: String, + pub id: String, + #[serde(rename = "numeric-id")] + pub numeric_id: u32, +} + +pub async fn query(input: &str) -> anyhow::Result { + let resp = Browser::new() + .get_api_response( + input, + Some( + &SearchParams::new() + .safe_search(SafeSearch::Off) + .full_urls(Toggle::On), + ), + ) + .await?; + + let infobox = match resp + .info_box + .map(serde_json::from_value::) + { + Some(Ok(o)) => Some(o), + _ => None, + }; + + let alt_desc = resp + .entity + .unwrap_or( + match infobox { + Some(i) => i + .iter() + .find(|i| i.data_type == "wd_description" || i.data_type == "official_website") + .map_or( + String::new(), + |i| { + if i.data_type == "official_website" { + i.label + .clone() + } else { + match i.value { + InfoboxValue::Entity(ref entity_value) => unreachable!(), + InfoboxValue::String(ref s) => s.to_string(), + } + } + }, + ), + // TODO: find better way to get title from infobox + None => String::new(), + }, + ); + + Ok( + DuckDuckGoContent { + title: resp + .heading + .unwrap_or( + resp.definition + .unwrap_or(alt_desc.clone()), + ), + description_html: resp + .answer + .unwrap_or( + resp.r#abstract + .unwrap_or(alt_desc), + ), + // TODO: try also Results[*].Icon.URL and RelatedTopics[*].Icon.URL + preview_image: resp + .image + .and_then(|i| Url::parse(&(DUCKDUCKGO.to_owned() + &i)).ok()), + }, + ) +} diff --git a/qml_plugin/src/web/google.rs b/qml_plugin/src/web/google.rs new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3