mod error;
pub mod models;
#[cfg(test)]
mod tests;
pub use crate::error::{ApiError, ApiErrorKind};
use crate::models::{FeverError, Groups, Feeds, FavIcons, Items, Links, UnreadItems, SavedItems, ItemStatus
};
use failure::ResultExt;
use log::error;
use reqwest::{Client, StatusCode, multipart::Form};
use url::Url;
type FeedID = u64;
type GroupID = u64;
type FeedGroupID = u64;
type ItemID = u64;
type IconID = u64;
type LinkID = u64;
pub struct FeverApi {
base_uri: Url,
api_key: String,
}
impl FeverApi {
pub fn new(url: &Url, username: String, password: String) -> Self {
let base_uri = url.clone();
let auth = format!("{}:{}", username, password);
let api_key = md5::compute(auth);
FeverApi {
base_uri: base_uri,
api_key: format!("{:?}", api_key),
}
}
async fn post_request(
&self,
client: &Client,
query: String,
) -> Result<String, ApiError> {
let full_query = format!("{}{}", "?api&", query);
let api_url: Url = self.base_uri.join(&full_query).context(ApiErrorKind::Url)?;
let form = Form::new().text("api_key", self.api_key.clone());
let response = client
.post(api_url.clone())
.multipart(form)
.header("api_key", self.api_key.clone())
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
let response = response.text().await.context(ApiErrorKind::Http)?;
if status != StatusCode::OK {
let error: FeverError = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
error!("Fever API: {}", error.error_message);
return Err(ApiErrorKind::Fever(error))?;
}
Ok(response)
}
async fn check_auth(response: &String) -> Result<(),ApiError> {
let tmp: serde_json::Value = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
if tmp["auth"].as_i64().unwrap() == 0 {
return Err(ApiErrorKind::AccessDenied)?;
}
Ok(())
}
pub async fn valid_credentials(&self,
client: &Client,
) -> Result<bool, ApiError> {
let response = self.post_request(&client, "".to_string()).await?;
let tmp: serde_json::Value = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
if tmp["auth"].as_i64().unwrap() == 0 {
return Ok(false);
}
Ok(true)
}
pub async fn get_api_version(&self,
client: &Client,
) -> Result<i64, ApiError> {
let response = self.post_request(&client, "".to_string()).await?;
let result: serde_json::Value = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(result["api_version"].as_i64().unwrap())
}
pub async fn get_groups(&self,
client: &Client,
) -> Result<Groups, ApiError> {
let response = self.post_request(&client, "groups".to_string()).await?;
Self::check_auth(&response).await?;
let groups: Groups = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(groups)
}
pub async fn get_feeds(&self,
client: &Client,
) -> Result<Feeds, ApiError> {
let response = self.post_request(&client, "feeds".to_string()).await?;
Self::check_auth(&response).await?;
let feeds: Feeds = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(feeds)
}
pub async fn get_favicons(&self,
client: &Client,
) -> Result<FavIcons, ApiError> {
let response = self.post_request(&client, "favicons".to_string()).await?;
Self::check_auth(&response).await?;
let favicons: FavIcons = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(favicons)
}
pub async fn get_items(&self,
client: &Client,
) -> Result<Items, ApiError> {
let response = self.post_request(&client, "items".to_string()).await?;
Self::check_auth(&response).await?;
let items: Items = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(items)
}
pub async fn get_items_since(&self,
id: ItemID,
client: &Client,
) -> Result<Items, ApiError> {
let query = format!("items&since_id={}", id);
let response = self.post_request(&client, query).await?;
Self::check_auth(&response).await?;
let items: Items = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(items)
}
pub async fn get_items_max(&self,
id: ItemID,
client: &Client,
) -> Result<Items, ApiError> {
let query = format!("items&max_id={}", id);
let response = self.post_request(&client, query).await?;
Self::check_auth(&response).await?;
let items: Items = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(items)
}
pub async fn get_items_with(&self,
ids: Vec<ItemID>,
client: &Client,
) -> Result<Items, ApiError> {
let list = ids.iter().map(ToString::to_string).collect::<Vec<String>>().join(",");
let query = format!("items&with_ids={}", list);
let response = self.post_request(&client, query).await?;
Self::check_auth(&response).await?;
let items: Items = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(items)
}
pub async fn get_links(&self,
client: &Client,
) -> Result<Links, ApiError> {
let response = self.post_request(&client, "links".to_string()).await?;
Self::check_auth(&response).await?;
let links: Links = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(links)
}
pub async fn get_links_with(&self,
offset: usize,
days: usize,
page: usize,
client: &Client,
) -> Result<Links, ApiError> {
let query = format!("links&offset={}&range={}&page={}", offset, days, page);
let response = self.post_request(&client, query).await?;
Self::check_auth(&response).await?;
let links: Links = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
Ok(links)
}
pub async fn get_unread_items(&self,
client: &Client,
) -> Result<UnreadItems, ApiError> {
let response = self.post_request(&client, "unread_item_ids".to_string()).await?;
Self::check_auth(&response).await?;
let json: serde_json::Value = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
let list : Vec<ItemID>;
let tmp = json["unread_item_ids"].as_str().unwrap();
if tmp != "" {
list = tmp.split(",").map(|x| x.parse::<ItemID>().unwrap()).collect::<Vec<ItemID>>();
} else {
list = Vec::new();
}
let refresh = json["last_refreshed_on_time"].as_str().unwrap();
let items: UnreadItems = UnreadItems{ last_refreshed_on_time: refresh.to_string(), unread_item_ids: list };
Ok(items)
}
pub async fn get_saved_items(&self,
client: &Client,
) -> Result<SavedItems, ApiError> {
let response = self.post_request(&client, "saved_item_ids".to_string()).await?;
Self::check_auth(&response).await?;
let json: serde_json::Value = serde_json::from_str(&response).context(ApiErrorKind::Json)?;
let list : Vec<ItemID>;
let tmp = json["saved_item_ids"].as_str().unwrap();
if tmp != "" {
list = tmp.split(",").map(|x| x.parse::<ItemID>().unwrap()).collect::<Vec<ItemID>>();
} else {
list = Vec::new();
}
let refresh = json["last_refreshed_on_time"].as_str().unwrap();
let items: SavedItems = SavedItems{ last_refreshed_on_time: refresh.to_string(), saved_item_ids: list };
Ok(items)
}
pub async fn mark_item(&self,
status: ItemStatus,
id: ItemID,
client: &Client,
) -> Result<(), ApiError> {
let state: &str = status.into();
let query = format!("&mark=item&as={}&id={}", state, id);
let response = self.post_request(&client, query).await?;
Self::check_auth(&response).await?;
Ok(())
}
async fn mark_feed_or_group(&self,
target: String,
status: ItemStatus,
id: isize,
before: String,
client: &Client,
) -> Result<(), ApiError> {
let state: &str = status.into();
let query = format!("&mark={}&as={}&id={}&before_{}", target, state, id, before);
let response = self.post_request(&client, query).await?;
Self::check_auth(&response).await?;
Ok(())
}
pub async fn mark_group(&self,
status: ItemStatus,
id: isize,
before: String,
client: &Client,
) -> Result<(), ApiError> {
self.mark_feed_or_group("group".to_string(), status, id, before, client).await
}
pub async fn mark_feed(&self,
status: ItemStatus,
id: isize,
before: String,
client: &Client,
) -> Result<(), ApiError> {
self.mark_feed_or_group("feed".to_string(), status, id, before, client).await
}
}