1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
use std::ops::{Deref, DerefMut};
use crate::launcher::duckduckgo::QmlDuckDuckGo;
use duckduckgo::{
browser::Browser,
params::{SafeSearch, SearchParams, Toggle},
};
use reqwest::Url;
use serde::Deserialize;
const DUCKDUCKGO: &str = "https://duckduckgo.com";
#[derive(Default, PartialEq)]
pub struct DuckDuckGoContent {
title: String,
description_html: String,
image: Option<Url>,
}
impl From<DuckDuckGoContent> for QmlDuckDuckGo {
fn from(value: DuckDuckGoContent) -> Self {
Self {
title: value.title.into(),
description_html: value.description_html.into(),
image: value.image.map(|u| u.to_string().into()).unwrap_or_default(),
}
}
}
#[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(_) => 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
.filter(|h| !h.is_empty())
.unwrap_or(resp.definition.filter(|d| !d.is_empty()).unwrap_or(alt_desc.clone())),
description_html: resp
.answer
.filter(|a| !a.is_empty())
.unwrap_or(resp.r#abstract.filter(|a| !a.is_empty()).unwrap_or(alt_desc)),
// TODO: try also Results[*].Icon.URL and RelatedTopics[*].Icon.URL
image: resp.image.filter(|i| !i.is_empty()).and_then(|i| {
if !i.is_empty() { Url::parse(&format!("{DUCKDUCKGO}{i}",)).ok() } else { None }
}),
})
}
|