#[cfg(feature = "blocking")]
pub mod blocking;
pub mod error;
pub mod types;
pub use error::*;
pub use types::*;
use libxml::{
parser::Parser,
tree::{self, NodeType},
};
use reqwest::{
multipart::{Form, Part},
Client,
};
use std::{collections::HashMap, fs::File, io::Read, path::Path};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Default)]
pub struct AccountBuilder {
access_token: Option<String>,
short_name: String,
author_name: Option<String>,
author_url: Option<String>,
}
impl AccountBuilder {
pub fn new(short_name: &str) -> Self {
AccountBuilder {
short_name: short_name.to_owned(),
..Default::default()
}
}
pub fn short_name(mut self, short_name: &str) -> Self {
self.short_name = short_name.to_owned();
self
}
pub fn access_token(mut self, access_token: &str) -> Self {
self.access_token = Some(access_token.to_owned());
self
}
pub fn author_name(mut self, author_name: &str) -> Self {
self.author_name = Some(author_name.to_owned());
self
}
pub fn author_url(mut self, author_url: &str) -> Self {
self.author_url = Some(author_url.to_owned());
self
}
pub async fn create(mut self) -> Result<Telegraph> {
if self.access_token.is_none() {
let account = Telegraph::create_account(
&self.short_name,
self.author_name.as_ref().map(|s| &**s),
self.author_url.as_ref().map(|s| &**s),
)
.await?;
self.access_token = Some(account.access_token.unwrap());
}
Ok(Telegraph {
client: Client::new(),
access_token: self.access_token.unwrap(),
short_name: self.short_name.to_owned(),
author_name: self.author_name.unwrap_or(self.short_name),
author_url: self.author_url,
})
}
pub async fn edit(self) -> Result<Telegraph> {
let response = Client::new()
.get("https://api.telegra.ph/editAccountInfo")
.query(&[
("access_token", self.access_token.as_ref().unwrap()),
("short_name", &self.short_name),
("author_name", self.author_name.as_ref().unwrap()),
(
"author_url",
self.author_url.as_ref().unwrap_or(&String::new()),
),
])
.send()
.await?;
let json: Result<Account> = response.json::<ApiResult<Account>>().await?.into();
let json = json?;
Ok(Telegraph {
client: Client::new(),
access_token: self.access_token.unwrap(),
short_name: json.short_name.clone().unwrap(),
author_name: json.author_name.or(json.short_name).unwrap(),
author_url: json.author_url,
})
}
}
#[derive(Debug)]
pub struct Telegraph {
client: Client,
access_token: String,
short_name: String,
author_name: String,
author_url: Option<String>,
}
impl Telegraph {
pub fn new(short_name: &str) -> AccountBuilder {
AccountBuilder {
short_name: short_name.to_owned(),
..Default::default()
}
}
pub(crate) async fn create_account<'a, S, T>(
short_name: &str,
author_name: S,
author_url: T,
) -> Result<Account>
where
T: Into<Option<&'a str>>,
S: Into<Option<&'a str>>,
{
let mut params = HashMap::new();
params.insert("short_name", short_name);
if let Some(author_name) = author_name.into() {
params.insert("author_name", author_name);
}
if let Some(author_url) = author_url.into() {
params.insert("author_url", author_url);
}
let response = Client::new()
.get("https://api.telegra.ph/createAccount")
.query(¶ms)
.send()
.await?;
response.json::<ApiResult<Account>>().await?.into()
}
pub async fn create_page(
&self,
title: &str,
content: &str,
return_content: bool,
) -> Result<Page> {
let response = self
.client
.post("https://api.telegra.ph/createPage")
.form(&[
("access_token", &*self.access_token),
("title", title),
("author_name", &*self.author_name),
(
"author_url",
self.author_url.as_ref().map(|s| &**s).unwrap_or(""),
),
("content", content),
("return_content", &*return_content.to_string()),
])
.send()
.await?;
response.json::<ApiResult<Page>>().await?.into()
}
pub fn edit_account_info(self) -> AccountBuilder {
AccountBuilder {
access_token: Some(self.access_token),
short_name: self.short_name,
author_name: Some(self.author_name),
author_url: self.author_url,
}
}
pub async fn edit_page(
&self,
path: &str,
title: &str,
content: &str,
return_content: bool,
) -> Result<Page> {
let response = self
.client
.post("https://api.telegra.ph/editPage")
.form(&[
("access_token", &*self.access_token),
("path", path),
("title", title),
("author_name", &*self.author_name),
(
"author_url",
self.author_url.as_ref().map(|s| &**s).unwrap_or(""),
),
("content", content),
("return_content", &*return_content.to_string()),
])
.send()
.await?;
response.json::<ApiResult<Page>>().await?.into()
}
pub async fn get_account_info(&self, fields: &[&str]) -> Result<Account> {
let response = self
.client
.get("https://api.telegra.ph/getAccountInfo")
.query(&[
("access_token", &self.access_token),
("fields", &serde_json::to_string(fields).unwrap()),
])
.send()
.await?;
response.json::<ApiResult<Account>>().await?.into()
}
pub async fn get_page(path: &str, return_content: bool) -> Result<Page> {
let response = Client::new()
.get(&format!("https://api.telegra.ph/getPage/{}", path))
.query(&[("return_content", return_content.to_string())])
.send()
.await?;
response.json::<ApiResult<Page>>().await?.into()
}
pub async fn get_page_list(&self, offset: i32, limit: i32) -> Result<PageList> {
let response = self
.client
.get("https://api.telegra.ph/getPageList")
.query(&[
("access_token", &self.access_token),
("offset", &offset.to_string()),
("limit", &limit.to_string()),
])
.send()
.await?;
response.json::<ApiResult<PageList>>().await?.into()
}
pub async fn get_views(path: &str, time: &[i32]) -> Result<PageViews> {
let params = ["year", "month", "day", "hour"]
.iter()
.zip(time)
.collect::<HashMap<_, _>>();
let response = Client::new()
.get(&format!("https://api.telegra.ph/getViews/{}", path))
.query(¶ms)
.send()
.await?;
response.json::<ApiResult<PageViews>>().await?.into()
}
pub async fn revoke_access_token(&mut self) -> Result<Account> {
let response = self
.client
.get("https://api.telegra.ph/revokeAccessToken")
.query(&[("access_token", &self.access_token)])
.send()
.await?;
let json: Result<Account> = response.json::<ApiResult<Account>>().await?.into();
if json.is_ok() {
self.access_token = json
.as_ref()
.unwrap()
.access_token
.as_ref()
.unwrap()
.to_owned();
}
json
}
#[cfg(feature = "upload")]
pub async fn upload<P: AsRef<Path>>(files: &[P]) -> Result<Vec<UploadResult>> {
let mut form = Form::new();
for (idx, name) in files.iter().enumerate() {
let bytes = read_to_bytes(name)?;
let part = Part::bytes(bytes)
.file_name(idx.to_string())
.mime_str(&guess_mime(name))?;
form = form.part(idx.to_string(), part);
}
let response = Client::new()
.post("https://telegra.ph/upload")
.multipart(form)
.send()
.await?;
Ok(response.json::<Vec<UploadResult>>().await?)
}
}
#[cfg(feature = "upload")]
fn guess_mime<P: AsRef<Path>>(path: P) -> String {
let mime = mime_guess::from_path(path).first_or(mime_guess::mime::TEXT_PLAIN);
let mut s = format!("{}/{}", mime.type_(), mime.subtype());
if let Some(suffix) = mime.suffix() {
s.push('+');
s.push_str(suffix.as_str());
}
s
}
#[cfg(feature = "upload")]
fn read_to_bytes<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
let mut bytes = vec![];
let mut file = File::open(path)?;
file.read_to_end(&mut bytes)?;
Ok(bytes)
}
pub fn html_to_node(html: &str) -> String {
let parser = Parser::default_html();
let document = parser.parse_string(html).unwrap();
let node = document
.get_root_element()
.unwrap()
.get_first_element_child()
.unwrap();
let nodes = node
.get_child_nodes()
.into_iter()
.map(|node| html_to_node_inner(&node))
.collect::<Vec<_>>();
serde_json::to_string(&nodes).unwrap()
}
fn html_to_node_inner(node: &tree::Node) -> Option<Node> {
match node.get_type() {
Some(NodeType::TextNode) => Some(Node::Text(node.get_content())),
Some(NodeType::ElementNode) => Some(Node::NodeElement(NodeElement {
tag: node.get_name(),
attrs: {
let attrs = node.get_attributes();
if attrs.is_empty() {
None
} else {
Some(attrs)
}
},
children: {
let childs = node.get_child_nodes();
if childs.is_empty() {
None
} else {
childs
.into_iter()
.map(|node| html_to_node_inner(&node))
.collect::<Option<Vec<_>>>()
}
},
})),
_ => None,
}
}
#[cfg(test)]
mod tests {
use crate::Telegraph;
#[test]
fn html_to_node() {
let html = r#"<a>Text</a><p>img:<img src="https://me"></p>"#;
println!("{}", super::html_to_node(html));
}
#[tokio::test]
async fn create_and_revoke_account() {
let result = Telegraph::create_account("sample", "a", None).await;
println!("{:?}", result);
assert!(result.is_ok());
let mut telegraph = Telegraph::new("test")
.access_token(&result.unwrap().access_token.unwrap().to_owned())
.create()
.await
.unwrap();
let result = telegraph.revoke_access_token().await;
println!("{:?}", result);
assert!(result.is_ok());
}
#[tokio::test]
async fn edit_account_info() {
let result = Telegraph::new("test")
.access_token("b968da509bb76866c35425099bc0989a5ec3b32997d55286c657e6994bbb")
.create()
.await
.unwrap()
.edit_account_info()
.short_name("wow")
.edit()
.await;
println!("{:?}", result);
assert!(result.is_ok());
}
#[tokio::test]
async fn get_account_info() {
let result = Telegraph::new("test")
.access_token("b968da509bb76866c35425099bc0989a5ec3b32997d55286c657e6994bbb")
.create()
.await
.unwrap()
.get_account_info(&["short_name"])
.await;
println!("{:?}", result);
assert!(result.is_ok());
}
#[tokio::test]
async fn create_get_edit_page() {
let telegraph = Telegraph::new("test")
.access_token("b968da509bb76866c35425099bc0989a5ec3b32997d55286c657e6994bbb")
.create()
.await
.unwrap();
let page = telegraph
.create_page(
"OVO",
r#"[{"tag":"p","children":["Hello,+world!"]}]"#,
false,
)
.await;
println!("{:?}", page);
assert!(page.is_ok());
let page = Telegraph::get_page(&page.unwrap().path, true).await;
println!("{:?}", page);
assert!(page.is_ok());
let page = telegraph
.edit_page(
&page.unwrap().path,
"QAQ",
r#"[{"tag":"p","children":["Goodbye,+world!"]}]"#,
false,
)
.await;
println!("{:?}", page);
assert!(page.is_ok());
}
#[tokio::test]
async fn get_page_list() {
let telegraph = Telegraph::new("test")
.access_token("b968da509bb76866c35425099bc0989a5ec3b32997d55286c657e6994bbb")
.create()
.await
.unwrap();
let page_list = telegraph.get_page_list(0, 3).await;
println!("{:?}", page_list);
assert!(page_list.is_ok());
}
#[tokio::test]
async fn get_views() {
let views = Telegraph::get_views("Sample-Page-12-15", &vec![2016, 12]).await;
println!("{:?}", views);
assert!(views.is_ok());
}
#[ignore]
#[tokio::test]
#[cfg(feature = "upload")]
async fn upload() {
let images = Telegraph::upload(&vec!["1.jpeg", "2.jpeg"]).await;
println!("{:?}", images);
assert!(images.is_ok());
}
}