use crate::api::client::{endpoints, GdeltClient};
use crate::error::Result;
use serde::{Deserialize, Serialize};
use tracing::instrument;
#[derive(Debug, Clone)]
pub struct DocApi {
client: GdeltClient,
}
impl DocApi {
pub fn new(client: GdeltClient) -> Self {
Self { client }
}
#[instrument(skip(self))]
pub async fn search(&self, params: DocSearchParams) -> Result<DocSearchResponse> {
let url = self.build_search_url(¶ms);
self.client.get_json(&url).await
}
#[instrument(skip(self))]
pub async fn timeline(&self, params: DocTimelineParams) -> Result<DocTimelineResponse> {
let url = self.build_timeline_url(¶ms);
self.client.get_json(&url).await
}
#[instrument(skip(self))]
pub async fn wordcloud(&self, params: DocWordcloudParams) -> Result<DocWordcloudResponse> {
let url = self.build_wordcloud_url(¶ms);
self.client.get_json(&url).await
}
#[instrument(skip(self))]
pub async fn tonechart(&self, params: DocTonechartParams) -> Result<DocTonechartResponse> {
let url = self.build_tonechart_url(¶ms);
self.client.get_json(&url).await
}
fn build_search_url(&self, params: &DocSearchParams) -> String {
let mut query_params = vec![
("query", params.query.as_str()),
("mode", "ArtList"),
("format", "json"),
];
if let Some(ref timespan) = params.timespan {
query_params.push(("timespan", timespan));
}
if let Some(ref start) = params.start_datetime {
query_params.push(("startdatetime", start));
}
if let Some(ref end) = params.end_datetime {
query_params.push(("enddatetime", end));
}
if let Some(max) = params.max_records {
let max_str = max.to_string();
query_params.push(("maxrecords", Box::leak(max_str.into_boxed_str())));
}
if let Some(ref sort) = params.sort {
query_params.push(("sort", sort));
}
if let Some(ref lang) = params.source_lang {
query_params.push(("sourcelang", lang));
}
if let Some(ref country) = params.source_country {
query_params.push(("sourcecountry", country));
}
if let Some(ref domain) = params.domain {
query_params.push(("domain", domain));
}
if let Some(ref theme) = params.theme {
query_params.push(("theme", theme));
}
if let Some(tone_min) = params.tone_min {
let tone_str = tone_min.to_string();
query_params.push(("tonemin", Box::leak(tone_str.into_boxed_str())));
}
if let Some(tone_max) = params.tone_max {
let tone_str = tone_max.to_string();
query_params.push(("tonemax", Box::leak(tone_str.into_boxed_str())));
}
GdeltClient::build_url(endpoints::DOC_API, &query_params)
}
fn build_timeline_url(&self, params: &DocTimelineParams) -> String {
let mode = match params.mode.as_deref() {
Some("vol") | None => "TimelineVol",
Some("volraw") => "TimelineVolRaw",
Some("tone") => "TimelineTone",
Some("lang") => "TimelineLang",
Some("sourcecountry") => "TimelineSourceCountry",
_ => "TimelineVol",
};
let mut query_params = vec![
("query", params.query.as_str()),
("mode", mode),
("format", "json"),
];
if let Some(ref timespan) = params.timespan {
query_params.push(("timespan", timespan));
}
if let Some(smooth) = params.smooth {
let smooth_str = smooth.to_string();
query_params.push(("smooth", Box::leak(smooth_str.into_boxed_str())));
}
GdeltClient::build_url(endpoints::DOC_API, &query_params)
}
fn build_wordcloud_url(&self, params: &DocWordcloudParams) -> String {
let query_params = vec![
("query", params.query.as_str()),
("mode", "WordCloudJson"),
("format", "json"),
("timespan", params.timespan.as_deref().unwrap_or("24h")),
];
GdeltClient::build_url(endpoints::DOC_API, &query_params)
}
fn build_tonechart_url(&self, params: &DocTonechartParams) -> String {
let mut query_params = vec![
("query", params.query.as_str()),
("mode", "ToneChart"),
("format", "json"),
];
if let Some(ref timespan) = params.timespan {
query_params.push(("timespan", timespan));
}
GdeltClient::build_url(endpoints::DOC_API, &query_params)
}
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct DocSearchParams {
pub query: String,
pub timespan: Option<String>,
pub start_datetime: Option<String>,
pub end_datetime: Option<String>,
pub max_records: Option<u32>,
pub sort: Option<String>,
pub source_lang: Option<String>,
pub source_country: Option<String>,
pub domain: Option<String>,
pub theme: Option<String>,
pub tone_min: Option<f64>,
pub tone_max: Option<f64>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct DocTimelineParams {
pub query: String,
pub mode: Option<String>,
pub timespan: Option<String>,
pub smooth: Option<u8>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct DocWordcloudParams {
pub query: String,
pub timespan: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct DocTonechartParams {
pub query: String,
pub timespan: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocSearchResponse {
#[serde(default)]
pub articles: Vec<Article>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Article {
pub url: String,
#[serde(default)]
pub url_mobile: Option<String>,
pub title: String,
#[serde(default)]
pub seendate: String,
#[serde(default)]
pub socialimage: Option<String>,
#[serde(default)]
pub domain: String,
#[serde(default)]
pub language: String,
#[serde(default)]
pub sourcecountry: String,
#[serde(default)]
pub tone: Option<f64>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocTimelineResponse {
#[serde(default)]
pub timeline: Vec<TimelineEntry>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TimelineEntry {
pub date: String,
#[serde(default)]
pub value: f64,
#[serde(default)]
pub series: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocWordcloudResponse {
#[serde(default)]
pub wordcloud: Vec<WordCloudEntry>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WordCloudEntry {
pub word: String,
pub weight: f64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocTonechartResponse {
#[serde(default)]
pub tonechart: Vec<ToneBin>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ToneBin {
pub bin: i32,
#[serde(default)]
pub count: u32,
#[serde(default)]
pub toparts: Vec<ToneArticle>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ToneArticle {
#[serde(default)]
pub url: String,
#[serde(default)]
pub title: String,
}