pub mod error;
#[cfg(test)]
mod tests;
pub use crate::error::{ApiError, ApiErrorKind};
use failure::ResultExt;
use reqwest::{multipart, Client, StatusCode};
use serde_json::{self, Value};
use url::Url;
pub struct NewsBlurApi {
base_uri: Url,
username: String,
password: String,
cookie: Option<String>,
}
impl NewsBlurApi {
pub fn new(url: &Url, username: &str, password: &str, cookie: Option<String>) -> Self {
let url_tmp = Url::parse(&(url.as_str().to_owned() + "/")).unwrap();
NewsBlurApi {
base_uri: url_tmp,
username: username.to_string(),
password: password.to_string(),
cookie,
}
}
pub async fn login(&mut self, client: &Client) -> Result<String, ApiError> {
let form = multipart::Form::new()
.text("username", self.username.clone())
.text("password", self.password.clone());
let api_url: Url = self.base_uri.join("api/login").context(ApiErrorKind::Url)?;
let response = client
.post(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.multipart(form)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let cookie = response
.cookies()
.next()
.ok_or(ApiErrorKind::AccessDenied)?;
let cookie_string = format!("{}={}", cookie.name(), cookie.value());
self.cookie = Some(cookie_string.clone());
return Ok(cookie_string);
}
pub async fn logout(&self, client: &Client) -> Result<(), ApiError> {
let api_url: Url = self
.base_uri
.join("api/logout")
.context(ApiErrorKind::Url)?;
let response = client
.post(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
Ok(())
}
pub async fn signup(&self, _client: &Client) -> Result<(), ApiError> {
panic!("Unimplemented");
}
pub async fn search_feed(&self, client: &Client, address: &str) -> Result<(), ApiError> {
let form = multipart::Form::new().text("address", address.to_string());
let api_url: Url = self
.base_uri
.join("rss_feeds/search_feed")
.context(ApiErrorKind::Url)?;
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.multipart(form)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
Ok(())
}
pub async fn get_feeds(&self, client: &Client) -> Result<Value, ApiError> {
let form = multipart::Form::new().text("include_favicons", "false");
let api_url: Url = self
.base_uri
.join("reader/feeds")
.context(ApiErrorKind::Url)?;
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.multipart(form)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_json = response.json().await.unwrap();
Ok(response_json)
}
pub async fn favicons(&self, client: &Client, feed_id: &str) -> Result<Value, ApiError> {
let form = multipart::Form::new().text("feed_ids", feed_id.to_string());
let api_url: Url = self
.base_uri
.join("reader/favicons")
.context(ApiErrorKind::Url)?;
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.multipart(form)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_json = response.json().await.unwrap();
Ok(response_json)
}
pub async fn get_original_page(&self, client: &Client, id: &str) -> Result<String, ApiError> {
let request = format!("reader/page/{}", id);
let api_url: Url = self.base_uri.join(&request).context(ApiErrorKind::Url)?;
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_text = response.text().await.unwrap();
Ok(response_text)
}
pub async fn get_original_text(&self, client: &Client, id: &str) -> Result<String, ApiError> {
let api_url: Url = self
.base_uri
.join("rss_feeds/original_text")
.context(ApiErrorKind::Url)?;
let mut query = Vec::new();
query.push(("story_hash", id.to_string()));
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.query(&query)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_text = response.text().await.unwrap();
Ok(response_text)
}
pub async fn refresh_feeds(&self, client: &Client) -> Result<Value, ApiError> {
let api_url: Url = self
.base_uri
.join("reader/refresh_feeds")
.context(ApiErrorKind::Url)?;
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_json = response.json().await.unwrap();
Ok(response_json)
}
pub async fn get_read_stories(&self, client: &Client, page: u32) -> Result<Value, ApiError> {
let mut query = Vec::new();
query.push(("page", format!("{}", page)));
let api_url: Url = self
.base_uri
.join("reader/read_stories")
.context(ApiErrorKind::Url)?;
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.query(&query)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_json = response.json().await.unwrap();
Ok(response_json)
}
pub async fn get_stories(
&self,
client: &Client,
id: &str,
include_content: bool,
page: u32,
) -> Result<Value, ApiError> {
let request = format!("reader/feed/{}", id);
let mut query = Vec::new();
if include_content {
query.push(("include_content", "true".to_string()));
} else {
query.push(("include_content", "false".to_string()));
}
query.push(("page", format!("{}", page)));
let api_url: Url = self.base_uri.join(&request).context(ApiErrorKind::Url)?;
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.query(&query)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_json = response.json().await.unwrap();
Ok(response_json)
}
pub async fn mark_stories_read(
&self,
client: &Client,
story_hash: &str,
) -> Result<(), ApiError> {
let form = multipart::Form::new().text("story_hash", story_hash.to_string());
let api_url: Url = self
.base_uri
.join("reader/mark_story_hashes_as_read")
.context(ApiErrorKind::Url)?;
let response = client
.post(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.multipart(form)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
Ok(())
}
pub async fn mark_story_unread(
&self,
client: &Client,
story_hash: &str,
) -> Result<(), ApiError> {
let form = multipart::Form::new().text("story_hash", story_hash.to_string());
let api_url: Url = self
.base_uri
.join("reader/mark_story_hash_as_unread")
.context(ApiErrorKind::Url)?;
let response = client
.post(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.multipart(form)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
Ok(())
}
pub async fn mark_story_hash_as_starred(
&self,
client: &Client,
story_hash: &str,
) -> Result<(), ApiError> {
let form = multipart::Form::new().text("story_hash", story_hash.to_string());
let api_url: Url = self
.base_uri
.join("reader/mark_story_hash_as_starred")
.context(ApiErrorKind::Url)?;
let response = client
.post(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.multipart(form)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
Ok(())
}
pub async fn mark_story_hash_as_unstarred(
&self,
client: &Client,
story_hash: &str,
) -> Result<(), ApiError> {
let form = multipart::Form::new().text("story_hash", story_hash.to_string());
let api_url: Url = self
.base_uri
.join("reader/mark_story_hash_as_unstarred")
.context(ApiErrorKind::Url)?;
let response = client
.post(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.multipart(form)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
Ok(())
}
pub async fn get_unread_story_hashes(&self, client: &Client) -> Result<Value, ApiError> {
let api_url: Url = self
.base_uri
.join("reader/unread_story_hashes")
.context(ApiErrorKind::Url)?;
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_json = response.json().await.unwrap();
Ok(response_json)
}
pub async fn get_stared_story_hashes(&self, client: &Client) -> Result<Value, ApiError> {
let api_url: Url = self
.base_uri
.join("reader/starred_story_hashes")
.context(ApiErrorKind::Url)?;
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_json = response.json().await.unwrap();
Ok(response_json)
}
pub async fn get_river_stories(
&self,
client: &Client,
hashes: &[&str],
) -> Result<Value, ApiError> {
let api_url: Url = self
.base_uri
.join("reader/river_stories")
.context(ApiErrorKind::Url)?;
let mut query = Vec::new();
for hash in hashes {
query.push(("h", hash));
}
let response = client
.get(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.query(&query)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
let response_json = response.json().await.unwrap();
Ok(response_json)
}
pub async fn mark_feed_read(&self, client: &Client, feed_id: &str) -> Result<(), ApiError> {
let form = multipart::Form::new().text("feed_id", feed_id.to_string());
let api_url: Url = self
.base_uri
.join("reader/mark_feed_as_read")
.context(ApiErrorKind::Url)?;
let response = client
.post(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.multipart(form)
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
Ok(())
}
pub async fn mark_all_read(&self, client: &Client) -> Result<(), ApiError> {
let api_url: Url = self
.base_uri
.join("reader/mark_all_as_read")
.context(ApiErrorKind::Url)?;
let response = client
.post(api_url)
.header(reqwest::header::USER_AGENT, "curl/7.64.0")
.header(reqwest::header::COOKIE, self.cookie.as_ref().unwrap())
.send()
.await
.context(ApiErrorKind::Http)?;
let status = response.status();
if status != StatusCode::OK {
return Err(ApiErrorKind::AccessDenied.into());
}
Ok(())
}
}