285 lines
7.4 KiB
Rust
285 lines
7.4 KiB
Rust
/// 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<T, C = Client> {
|
|
// 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<String>);
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RandomAccessToken(pub Arc<Vec<String>>);
|
|
|
|
impl AccessToken for SingleAccessToken {
|
|
fn token(&self) -> &str {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl From<String> 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<String> for RandomAccessToken {
|
|
fn from(s: String) -> Self {
|
|
Self(Arc::new(vec![s]))
|
|
}
|
|
}
|
|
|
|
impl From<Vec<String>> for RandomAccessToken {
|
|
fn from(ts: Vec<String>) -> Self {
|
|
assert!(!ts.is_empty());
|
|
Self(Arc::new(ts))
|
|
}
|
|
}
|
|
|
|
macro_rules! execute {
|
|
($send: expr) => {
|
|
$send
|
|
.send()
|
|
.await
|
|
.and_then(Response::error_for_status)?
|
|
.json::<ApiResult<_>>()
|
|
.await?
|
|
.into()
|
|
};
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, derive_more::From, derive_more::Into)]
|
|
pub struct TelegraphToken(Arc<String>);
|
|
|
|
impl<T> Telegraph<T, Client> {
|
|
pub fn new<AT>(access_token: AT) -> Telegraph<T, Client>
|
|
where
|
|
AT: Into<T>,
|
|
{
|
|
Telegraph {
|
|
client: Client::new(),
|
|
access_token: access_token.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T, C> Telegraph<T, C> {
|
|
pub fn with_proxy<P: HttpRequestBuilder + 'static>(self, proxy: P) -> Telegraph<T, P> {
|
|
Telegraph {
|
|
client: proxy,
|
|
access_token: self.access_token,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T, C> Telegraph<T, C>
|
|
where
|
|
T: AccessToken,
|
|
C: HttpRequestBuilder,
|
|
{
|
|
/// Create page.
|
|
pub async fn create_page(&self, page: &PageCreate) -> Result<Page, TelegraphError> {
|
|
#[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<Page, TelegraphError> {
|
|
#[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<Page, TelegraphError> {
|
|
#[derive(Serialize)]
|
|
struct PageGet<'a> {
|
|
path: &'a str,
|
|
#[serde(flatten)]
|
|
return_content: Option<bool>,
|
|
}
|
|
|
|
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<IT, I>(&self, files: IT) -> Result<Vec<MediaInfo>, TelegraphError>
|
|
where
|
|
IT: IntoIterator<Item = I>,
|
|
I: Into<Cow<'static, [u8]>>,
|
|
{
|
|
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<Vec<MediaInfo>, TelegraphError> = self
|
|
.client
|
|
.post_builder("https://telegra.ph/upload")
|
|
.multipart(form)
|
|
.send()
|
|
.await
|
|
.and_then(Response::error_for_status)?
|
|
.json::<UploadResult>()
|
|
.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::<SingleAccessToken>::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<u8> = reqwest::get("https://t.co/static/images/bird.png")
|
|
.await
|
|
.unwrap()
|
|
.bytes()
|
|
.await
|
|
.unwrap()
|
|
.as_ref()
|
|
.to_owned();
|
|
|
|
let telegraph = Telegraph::<SingleAccessToken>::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::<SingleAccessToken>::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);
|
|
}
|
|
}
|