use reqwest::header::{HeaderValue, CONTENT_TYPE};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Debug, Deserialize)]
pub struct MemeTemplate {
id: String,
name: String,
url: Url,
width: u32,
height: u32,
box_count: u32,
}
impl MemeTemplate {
pub fn id(&self) -> &str {
&self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn url(&self) -> &Url {
&self.url
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn box_count(&self) -> u32 {
self.box_count
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum Response<T> {
SuccessResponse {
success: bool,
data: T,
},
FailureResponse {
success: bool,
error_message: String,
},
}
impl<T> Response<T> {
fn convert(self) -> Result<T> {
match self {
Response::SuccessResponse { data, .. } => Ok(data),
Response::FailureResponse { error_message, .. } => Err(Error::ApiError(error_message)),
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum CaptionFont {
Impact,
Arial,
}
#[derive(Debug, Serialize)]
pub struct CaptionBox {
text: String,
x: Option<u32>,
y: Option<u32>,
width: Option<u32>,
height: Option<u32>,
color: Option<String>,
outline_color: Option<String>,
}
pub struct CaptionBoxBuilder {
text: String,
x: Option<u32>,
y: Option<u32>,
width: Option<u32>,
height: Option<u32>,
color: Option<String>,
outline_color: Option<String>,
}
impl CaptionBoxBuilder {
pub fn new<S: Into<String>>(text: S) -> Self {
CaptionBoxBuilder {
text: text.into(),
x: None,
y: None,
width: None,
height: None,
color: None,
outline_color: None,
}
}
pub fn dimension(mut self, x: u32, y: u32, width: u32, height: u32) -> Self {
self.x = Some(x);
self.y = Some(y);
self.width = Some(width);
self.height = Some(height);
self
}
pub fn color<S: Into<String>>(mut self, color: S) -> Self {
self.color = Some(color.into());
self
}
pub fn outline_color<S: Into<String>>(mut self, outline_color: S) -> Self {
self.outline_color = Some(outline_color.into());
self
}
pub fn build(self) -> CaptionBox {
CaptionBox {
text: self.text,
x: self.x,
y: self.y,
width: self.width,
height: self.height,
color: self.color,
outline_color: self.outline_color,
}
}
}
#[derive(Debug, Serialize)]
pub struct CaptionBoxesRequest {
template_id: String,
font: Option<CaptionFont>,
max_font_size: Option<u32>,
boxes: Vec<CaptionBox>,
}
pub struct CaptionBoxesRequestBuilder {
template_id: String,
font: Option<CaptionFont>,
max_font_size: Option<u32>,
boxes: Vec<CaptionBox>,
}
impl CaptionBoxesRequestBuilder {
pub fn new<S: Into<String>>(template_id: S) -> Self {
CaptionBoxesRequestBuilder {
template_id: template_id.into(),
font: None,
max_font_size: None,
boxes: Vec::with_capacity(2),
}
}
pub fn font(mut self, font: CaptionFont) -> Self {
self.font = Some(font);
self
}
pub fn max_font_size(mut self, max_font_size: u32) -> Self {
self.max_font_size = Some(max_font_size);
self
}
pub fn caption_box(mut self, caption_box: CaptionBox) -> Self {
self.boxes.push(caption_box);
self
}
pub fn build(self) -> CaptionBoxesRequest {
CaptionBoxesRequest {
template_id: self.template_id,
font: self.font,
max_font_size: self.max_font_size,
boxes: self.boxes,
}
}
}
#[derive(Debug, Deserialize)]
pub struct CaptionImageResponse {
url: Url,
page_url: Url,
}
impl CaptionImageResponse {
pub fn url(&self) -> &Url {
&self.url
}
pub fn page_url(&self) -> &Url {
&self.page_url
}
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("HTTP request/response error")]
Reqwest(#[from] reqwest::Error),
#[error("form querystring de/serialization error")]
SerdeQs(#[from] serde_qs::Error),
#[error("API error: {0}")]
ApiError(String),
}
pub type Result<T> = std::result::Result<T, crate::Error>;
pub struct Client {
client: reqwest::Client,
}
impl Client {
pub fn new() -> Self {
Client {
client: reqwest::Client::new(),
}
}
async fn client_memes(client: &reqwest::Client) -> Result<Vec<MemeTemplate>> {
#[derive(Debug, Deserialize)]
struct MemeTemplatesData {
memes: Vec<MemeTemplate>,
}
client
.get("https://api.imgflip.com/get_memes")
.send()
.await?
.error_for_status()?
.json::<Response<MemeTemplatesData>>()
.await?
.convert()
.map(|r| r.memes)
}
pub async fn memes(&self) -> Result<Vec<MemeTemplate>> {
Self::client_memes(&self.client).await
}
}
pub struct AccountClient {
username: String,
password: String,
client: reqwest::Client,
}
impl AccountClient {
pub fn new<S: Into<String>>(username: S, password: S) -> Self {
AccountClient {
client: reqwest::Client::new(),
username: username.into(),
password: password.into(),
}
}
pub async fn caption_image(
&self,
image_caption: CaptionBoxesRequest,
) -> Result<CaptionImageResponse> {
#[derive(Debug, Serialize)]
struct RequestAuthWrapper<T> {
#[serde(flatten)]
request: T,
username: String,
password: String,
}
self.client
.post("https://api.imgflip.com/caption_image")
.header(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
)
.body(serde_qs::to_string(&RequestAuthWrapper {
request: image_caption,
username: self.username.clone(),
password: self.password.clone(),
})?)
.send()
.await?
.error_for_status()?
.json::<Response<CaptionImageResponse>>()
.await?
.convert()
}
pub async fn memes(&self) -> Result<Vec<MemeTemplate>> {
Client::client_memes(&self.client).await
}
}