yodaspeak/
client.rs

1use crate::Error;
2use reqwest::header::{
3    HeaderMap,
4    HeaderValue,
5    ACCEPT,
6};
7use scraper::{
8    Html,
9    Selector,
10};
11use std::sync::LazyLock;
12
13static ACCEPT_VALUE: HeaderValue = HeaderValue::from_static("*/*");
14
15/// The client
16#[derive(Debug, Clone)]
17pub struct Client {
18    /// The inner http client
19    pub client: reqwest::Client,
20}
21
22impl Client {
23    /// Make a new client
24    pub fn new() -> Self {
25        let mut headers = HeaderMap::new();
26        headers.insert(ACCEPT, ACCEPT_VALUE.clone());
27
28        let client = reqwest::Client::builder()
29            .user_agent("yodaspeak-rs")
30            .default_headers(headers)
31            .build()
32            .expect("failed to build client");
33
34        Self { client }
35    }
36
37    /// Translate an input.
38    pub async fn translate(&self, data: &str) -> Result<String, Error> {
39        let text = self
40            .client
41            .post("https://www.yodaspeak.co.uk/index.php")
42            .form(&[("YodaMe", data), ("go", "Convert to Yoda-Speak!")])
43            .send()
44            .await?
45            .error_for_status()?
46            .text()
47            .await?;
48
49        tokio::task::spawn_blocking(move || {
50            static SELECTOR: LazyLock<Selector> = LazyLock::new(|| {
51                Selector::parse("#result textarea").expect("failed to parse `SELECTOR`")
52            });
53            let html = Html::parse_document(&text);
54
55            let result = html
56                .select(&SELECTOR)
57                .next()
58                .and_then(|el| el.text().next())
59                .ok_or(Error::MissingResult)?;
60
61            Ok(result.to_string())
62        })
63        .await?
64    }
65}
66
67impl Default for Client {
68    fn default() -> Self {
69        Self::new()
70    }
71}