use anyhow::{Context, Result};
use reqwest::Client;
use reqwest::multipart::{Form, Part};
use serde::Deserialize;
use tracing::debug;
use super::parse_json;
pub const API_URL: &str = "https://api.pixhost.to/images";
#[derive(Deserialize)]
struct Response {
show_url: String,
}
pub async fn upload(client: &Client, data: Vec<u8>, url: &str) -> Result<String> {
let form = Form::new()
.text("content_type", "0")
.part("img", Part::bytes(data).file_name("image"));
let resp = client
.post(url)
.multipart(form)
.send()
.await
.context("failed to send request to pixhost")?;
let resp: Response = parse_json(resp, "pixhost").await?;
let show_url = &resp.show_url;
if let Ok(direct) = extract_direct_link(client, show_url).await {
return Ok(direct);
}
Ok(show_url.clone())
}
async fn extract_direct_link(client: &Client, show_url: &str) -> Result<String> {
let page = client
.get(show_url)
.send()
.await?
.text()
.await
.context("failed to read pixhost show page")?;
let show_path = show_url
.split_once("/show/")
.map(|(_, path)| path)
.context("unexpected show_url format")?;
let pattern = format!("/images/{show_path}");
for segment in page.split('"') {
if segment.contains(&pattern) && segment.starts_with("https://") {
return Ok(segment.to_owned());
}
}
debug!("Response text:\n{page}");
anyhow::bail!("direct link not found in pixhost show page")
}
#[cfg(test)]
#[expect(clippy::unwrap_used)]
mod tests {
use wiremock::matchers::method;
use wiremock::{Mock, MockServer, ResponseTemplate};
use super::*;
#[tokio::test]
async fn test_upload_extracts_direct_link() {
let mock_server = MockServer::start().await;
let show_url = format!("{}/show/865/361824612_upload.png", mock_server.uri());
let direct_url = "https://img865.pixhost.to/images/865/361824612_upload.png";
Mock::given(method("POST"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"name": "upload.png",
"show_url": show_url,
"th_url": "https://t865.pixhost.to/thumbs/865/361824612_upload.png"
})))
.mount(&mock_server)
.await;
Mock::given(method("GET"))
.respond_with(
ResponseTemplate::new(200).set_body_string(format!(r#"<img src="{direct_url}">"#)),
)
.mount(&mock_server)
.await;
let client = Client::new();
let url = upload(&client, vec![1, 2, 3], &mock_server.uri())
.await
.unwrap();
assert_eq!(url, direct_url);
}
#[tokio::test]
async fn test_upload_fallback_to_show_url() {
let mock_server = MockServer::start().await;
let show_url = format!("{}/show/865/361824612_upload.png", mock_server.uri());
Mock::given(method("POST"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"name": "upload.png",
"show_url": show_url,
"th_url": "https://t865.pixhost.to/thumbs/865/361824612_upload.png"
})))
.mount(&mock_server)
.await;
let client = Client::new();
let url = upload(&client, vec![1, 2, 3], &mock_server.uri())
.await
.unwrap();
assert_eq!(url, show_url);
}
}