diff options
| author | littleblack111 <littleblack11111@gmail.com> | 2026-07-31 21:59:42 +0800 |
|---|---|---|
| committer | littleblack111 <littleblack11111@gmail.com> | 2026-07-31 22:23:29 +0800 |
| commit | 1d6da847b7075b4babf3409b9445db0b54677893 (patch) | |
| tree | 1dd6141f2450bef7d35cd3bd36331b5b258f5e85 /qml_plugin/src/web/duckduckgo.rs | |
| parent | 586f32bb99c7539f2d89ac617b86a62166b30993 (diff) | |
qml_plugin: duckduckgo
Diffstat (limited to 'qml_plugin/src/web/duckduckgo.rs')
| -rw-r--r-- | qml_plugin/src/web/duckduckgo.rs | 127 |
1 files changed, 127 insertions, 0 deletions
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<Url>, +} + +#[derive(Debug, Deserialize)] +pub struct Infobox { + pub content: Vec<InfoboxContent>, +} + +impl Deref for Infobox { + type Target = Vec<InfoboxContent>; + + 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<DuckDuckGoContent> { + 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::<Infobox>) + { + 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()), + }, + ) +} |
