use std::fmt;
use serde::{Deserialize, Deserializer};
use crate::common::*;
use crate::tweet::Tweet;
use crate::{auth, error, links};
pub fn search<S: Into<CowStr>>(query: S) -> SearchBuilder {
SearchBuilder {
query: query.into(),
lang: None,
result_type: None,
count: None,
until: None,
geocode: None,
since_id: None,
max_id: None,
}
}
#[derive(Debug, Copy, Clone)]
pub enum ResultType {
Recent,
Popular,
Mixed,
}
impl fmt::Display for ResultType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ResultType::Recent => write!(f, "recent"),
ResultType::Popular => write!(f, "popular"),
ResultType::Mixed => write!(f, "mixed"),
}
}
}
pub enum Distance {
Miles(f32),
Kilometers(f32),
}
#[must_use = "SearchBuilder is lazy and won't do anything unless `call`ed"]
pub struct SearchBuilder {
query: CowStr,
lang: Option<CowStr>,
result_type: Option<ResultType>,
count: Option<u32>,
until: Option<(u32, u32, u32)>,
geocode: Option<(f32, f32, Distance)>,
since_id: Option<u64>,
max_id: Option<u64>,
}
impl SearchBuilder {
pub fn lang<S: Into<CowStr>>(self, lang: S) -> Self {
SearchBuilder {
lang: Some(lang.into()),
..self
}
}
pub fn result_type(self, result_type: ResultType) -> Self {
SearchBuilder {
result_type: Some(result_type),
..self
}
}
pub fn count(self, count: u32) -> Self {
SearchBuilder {
count: Some(count),
..self
}
}
pub fn until(self, year: u32, month: u32, day: u32) -> Self {
SearchBuilder {
until: Some((year, month, day)),
..self
}
}
pub fn geocode(self, latitude: f32, longitude: f32, radius: Distance) -> Self {
SearchBuilder {
geocode: Some((latitude, longitude, radius)),
..self
}
}
pub fn since_tweet(self, since_id: u64) -> Self {
SearchBuilder {
since_id: Some(since_id),
..self
}
}
pub fn max_tweet(self, max_id: u64) -> Self {
SearchBuilder {
max_id: Some(max_id),
..self
}
}
pub async fn call(self, token: &auth::Token) -> Result<Response<SearchResult>, error::Error> {
let params = ParamList::new()
.extended_tweets()
.add_param("q", self.query)
.add_opt_param("lang", self.lang)
.add_opt_param("result_type", self.result_type.map_string())
.add_opt_param("count", self.count.map_string())
.add_opt_param("since_id", self.since_id.map_string())
.add_opt_param("max_id", self.max_id.map_string())
.add_opt_param(
"until",
self.until
.map(|(year, month, day)| format!("{}-{}-{}", year, month, day)),
)
.add_opt_param(
"geocode",
self.geocode.map(|(lat, lon, radius)| match radius {
Distance::Miles(r) => format!("{:.6},{:.6},{}mi", lat, lon, r),
Distance::Kilometers(r) => format!("{:.6},{:.6},{}km", lat, lon, r),
}),
);
let req = get(links::statuses::SEARCH, token, Some(¶ms));
let mut resp = request_with_json_response::<SearchResult>(req).await?;
resp.response.params = Some(params);
Ok(resp)
}
}
#[derive(Debug, Deserialize)]
struct RawSearch {
search_metadata: RawSearchMetaData,
statuses: Vec<Tweet>,
}
#[derive(Debug, Deserialize)]
struct RawSearchMetaData {
completed_in: f64,
max_id: u64,
next_results: Option<String>,
query: String,
refresh_url: Option<String>,
count: u64,
since_id: u64,
}
impl<'de> Deserialize<'de> for SearchResult {
fn deserialize<D>(deser: D) -> Result<SearchResult, D::Error>
where
D: Deserializer<'de>,
{
let raw = RawSearch::deserialize(deser)?;
Ok(SearchResult {
statuses: raw.statuses,
query: raw.search_metadata.query,
max_id: raw.search_metadata.max_id,
since_id: raw.search_metadata.since_id,
params: None,
})
}
}
#[derive(Debug)]
pub struct SearchResult {
pub statuses: Vec<Tweet>,
pub query: String,
pub max_id: u64,
pub since_id: u64,
params: Option<ParamList>,
}
impl SearchResult {
pub async fn older(&self, token: &auth::Token) -> Result<Response<SearchResult>, error::Error> {
let mut params = self
.params
.as_ref()
.cloned()
.unwrap_or_default()
.extended_tweets();
params.remove("since_id");
if let Some(min_id) = self.statuses.iter().map(|t| t.id).min() {
params.add_param_ref("max_id", (min_id - 1).to_string());
} else {
params.remove("max_id");
}
let req = get(links::statuses::SEARCH, token, Some(¶ms));
let mut resp = request_with_json_response::<SearchResult>(req).await?;
resp.response.params = Some(params);
Ok(resp)
}
pub async fn newer(&self, token: &auth::Token) -> Result<Response<SearchResult>, error::Error> {
let mut params = self
.params
.as_ref()
.cloned()
.unwrap_or_default()
.extended_tweets();
params.remove("max_id");
if let Some(max_id) = self.statuses.iter().map(|t| t.id).max() {
params.add_param_ref("since_id", max_id.to_string());
} else {
params.remove("since_id");
}
let req = get(links::statuses::SEARCH, token, Some(¶ms));
let mut resp = request_with_json_response::<SearchResult>(req).await?;
resp.response.params = Some(params);
Ok(resp)
}
}