/// Telegraph API Client pub use error::TelegraphError; pub mod types; pub const MAX_SINGLE_FILE_SIZE: usize = 5 * 1024 * 1024; mod error; use std::{borrow::Cow, sync::Arc}; use reqwest::{ multipart::{Form, Part}, Client, Response, }; use serde::Serialize; use crate::http_proxy::HttpRequestBuilder; use self::{ error::{ApiResult, UploadResult}, types::{MediaInfo, Page, PageCreate, PageEdit}, }; #[derive(Debug, Clone)] pub struct Telegraph { // http client client: C, // access token access_token: T, } pub trait AccessToken { fn token(&self) -> &str; fn select_token(&self, _path: &str) -> &str { Self::token(self) } } #[derive(Debug, Clone)] pub struct SingleAccessToken(pub Arc); #[derive(Debug, Clone)] pub struct RandomAccessToken(pub Arc>); impl AccessToken for SingleAccessToken { fn token(&self) -> &str { &self.0 } } impl From for SingleAccessToken { fn from(s: String) -> Self { Self(Arc::new(s)) } } impl AccessToken for RandomAccessToken { fn token(&self) -> &str { use rand::prelude::SliceRandom; self.0 .choose(&mut rand::thread_rng()) .expect("token list must contains at least one element") } } impl From for RandomAccessToken { fn from(s: String) -> Self { Self(Arc::new(vec![s])) } } impl From> for RandomAccessToken { fn from(ts: Vec) -> Self { assert!(!ts.is_empty()); Self(Arc::new(ts)) } } macro_rules! execute { ($send: expr) => { $send .send() .await .and_then(Response::error_for_status)? .json::>() .await? .into() }; } #[derive(Debug, Clone, PartialEq, Eq, derive_more::From, derive_more::Into)] pub struct TelegraphToken(Arc); impl Telegraph { pub fn new(access_token: AT) -> Telegraph where AT: Into, { Telegraph { client: Client::new(), access_token: access_token.into(), } } } impl Telegraph { pub fn with_proxy(self, proxy: P) -> Telegraph { Telegraph { client: proxy, access_token: self.access_token, } } } impl Telegraph where T: AccessToken, C: HttpRequestBuilder, { /// Create page. pub async fn create_page(&self, page: &PageCreate) -> Result { #[derive(Serialize)] struct PagePostWithToken<'a> { access_token: &'a str, #[serde(flatten)] page: &'a PageCreate, } let to_post = PagePostWithToken { access_token: self.access_token.token(), page, }; execute!(self .client .post_builder("https://api.telegra.ph/createPage") .form(&to_post)) } /// Edit page. pub async fn edit_page(&self, page: &PageEdit) -> Result { #[derive(Serialize)] struct PageEditWithToken<'a> { access_token: &'a str, #[serde(flatten)] page: &'a PageEdit, } let to_post = PageEditWithToken { access_token: self.access_token.select_token(&page.path), page, }; execute!(self .client .post_builder("https://api.telegra.ph/editPage") .form(&to_post)) } /// Get page. /// path: Path to the Telegraph page (in the format Title-12-31, i.e. everything /// that comes after http://telegra.ph/) pub async fn get_page(&self, path: &str) -> Result { #[derive(Serialize)] struct PageGet<'a> { path: &'a str, #[serde(flatten)] return_content: Option, } let to_post = PageGet { path, return_content: Some(true), }; execute!(self .client .post_builder("https://api.telegra.ph/getPage") .form(&to_post)) } /// Upload file. /// If the result is Ok, it's length must eq to files'. pub async fn upload(&self, files: IT) -> Result, TelegraphError> where IT: IntoIterator, I: Into>, { let mut form = Form::new(); let mut cnt = 0; for (idx, data) in files.into_iter().enumerate() { let part = Part::bytes(data).file_name(idx.to_string()); form = form.part(idx.to_string(), part); cnt += 1; } let r: Result, TelegraphError> = self .client .post_builder("https://telegra.ph/upload") .multipart(form) .send() .await .and_then(Response::error_for_status)? .json::() .await? .into(); // Here we check if server returns the same amount as files posted r.and_then(|x| { if x.len() != cnt { Err(TelegraphError::Server) } else { Ok(x) } }) } } #[cfg(test)] mod tests { use crate::telegraph::{ types::{Node, PageCreate}, SingleAccessToken, Telegraph, }; use super::types::{NodeElement, NodeElementAttr, Tag}; pub const TELEGRAPH_TOKEN: &str = "f42d3570f95412b59b08d64450049e4d609b1f2a57657fce6ce8acc908aa"; #[ignore] #[tokio::test] async fn demo_create_page() { let telegraph = Telegraph::::new(TELEGRAPH_TOKEN.to_string()); let page = PageCreate { title: "title".to_string(), content: vec![Node::Text("test text".to_string())], author_name: Some("test_author".to_string()), author_url: Some("https://t.co".to_string()), }; let page = telegraph.create_page(&page).await.unwrap(); println!("test page: {:?}", page); } #[ignore] #[tokio::test] async fn demo_upload() { let demo_image: Vec = reqwest::get("https://t.co/static/images/bird.png") .await .unwrap() .bytes() .await .unwrap() .as_ref() .to_owned(); let telegraph = Telegraph::::new(TELEGRAPH_TOKEN.to_string()); let ret = telegraph .upload(Some(demo_image)) .await .unwrap() .pop() .unwrap(); println!("uploaded file link: {}", ret.src); } #[ignore] #[tokio::test] async fn demo_create_images_page() { let telegraph = Telegraph::::new(TELEGRAPH_TOKEN.to_string()); let node = Node::NodeElement(NodeElement { tag: Tag::Img, attrs: Some(NodeElementAttr { src: Some("https://telegra.ph/file/e31b40e99b0c028601ccb.png".to_string()), href: None, }), children: None, }); let page = PageCreate { title: "title".to_string(), content: vec![node], author_name: Some("test_author".to_string()), author_url: None, }; let page = telegraph.create_page(&page).await.unwrap(); println!("test page: {:?}", page); } }