dipper 0.5.3

An out-of-the-box modular dependency injection web application framework.
Documentation
//! Human-machine verification code, support picture and audio.
//!
//! The module offers two ways to create CAPTCHAs. The first way is the most
//! easiest one. Just one line of code is necessary to create a random CAPTCHA
//! of a given category. The category can be either `Easy`, `Medium` or `Hard`.
//! The size of the CAPTCHA is 220x120 pixels and the number of characters is
//! randomly selected between 4 and 6 letters (inclusive).

use std::ops::{Deref, DerefMut};

pub use captcha::{
    CaptchaName, Difficulty,
    filters::{Dots, Noise, Wave},
};

pub const BASE64_PNG_HEAD: &str = "data:image/png;base64,";

pub struct Captcha(captcha::Captcha);

impl Default for Captcha {
    fn default() -> Self {
        Self::new()
    }
}

impl Captcha {
    /// Returns an empty CAPTCHA.
    pub fn new() -> Self {
        Self(captcha::Captcha::new())
    }
    /// Creates a predefined CAPTCHA by its name.
    ///
    /// The CAPTCHAs look similar to the following ones:
    ///
    /// <div style="display: table">
    /// <div style="display: table-row; background-color: #dddddd">
    ///   <div style="display: table-cell; text-align: center; padding:
    /// 10px;">Name</div>   <div style="display: table-cell; text-align:
    /// center; padding: 10px;">Easy</div>   <div style="display:
    /// table-cell; text-align: center; padding: 10px;">Medium</div>
    ///   <div style="display: table-cell; text-align: center; padding:
    /// 10px;">Hard</div> </div>
    ///
    /// <div style="display: table-row;">
    ///   <div style="display: table-cell; vertical-align: top; padding:
    /// 3px;"></div>   <div style="display: table-cell; vertical-align: top;
    /// padding: 3px;"></div>   <div style="display: table-cell;
    /// vertical-align: top; padding: 3px;"></div>   <div style="display:
    /// table-cell; vertical-align: top; padding: 3px;"></div> </div>
    ///
    /// <div style="display: table-row;">
    ///   <div style="display: table-cell; vertical-align: top; padding-left:
    /// 3px;">Amelia</div>   <div style="display: table-cell; padding-left:
    /// 3px;">     <img src="https://github.com/andeya/dipper/raw/main/dipper/src/fnd/captcha/doc/captcha_amelia_easy.png">
    ///   </div>
    ///   <div style="display: table-cell; padding-left: 3px;">
    ///     <img src="https://github.com/andeya/dipper/raw/main/dipper/src/fnd/captcha/doc/captcha_amelia_medium.png">
    ///   </div>
    ///   <div style="display: table-cell; padding-left: 3px;">
    ///     <img src="https://github.com/andeya/dipper/raw/main/dipper/src/fnd/captcha/doc/captcha_amelia_hard.png">
    ///   </div>
    /// </div>
    ///
    /// <div style="display: table-row;">
    ///   <div style="display: table-cell; vertical-align: top; padding-left:
    /// 3px;">Lucy</div>   <div style="display: table-cell; padding-left:
    /// 3px;">     <img src="https://github.com/andeya/dipper/raw/main/dipper/src/fnd/captcha/doc/captcha_lucy_easy.png">
    ///   </div>
    ///   <div style="display: table-cell; padding-left: 3px;">
    ///     <img src="https://github.com/andeya/dipper/raw/main/dipper/src/fnd/captcha/doc/captcha_lucy_medium.png">
    ///   </div>
    ///   <div style="display: table-cell; padding-left: 3px;">
    ///     <img src="https://github.com/andeya/dipper/raw/main/dipper/src/fnd/captcha/doc/captcha_lucy_hard.png">
    ///   </div>
    /// </div>
    ///
    /// <div style="display: table-row;">
    ///   <div style="display: table-cell; vertical-align: top; padding-left:
    /// 3px;">Mila</div>   <div style="display: table-cell; padding-left:
    /// 3px;">     <img src="https://github.com/andeya/dipper/raw/main/dipper/src/fnd/captcha/doc/captcha_mila_easy.png">
    ///   </div>
    ///   <div style="display: table-cell; padding-left: 3px;">
    ///     <img src="https://github.com/andeya/dipper/raw/main/dipper/src/fnd/captcha/doc/captcha_mila_medium.png">
    ///   </div>
    ///   <div style="display: table-cell; padding-left: 3px;">
    ///     <img src="https://github.com/andeya/dipper/raw/main/dipper/src/fnd/captcha/doc/captcha_mila_hard.png">
    ///   </div>
    /// </div>
    ///
    /// </div>
    pub fn by_name(d: Difficulty, t: CaptchaName) -> Self {
        Self(captcha::by_name(d, t))
    }
    /// Returns `(chars, png_base64)` tuple.
    ///
    /// Returns `None` on error.
    pub fn as_base64(&self) -> Option<(String, String)> {
        let chars = self.0.chars_as_string();
        let png_base64 = self.0.as_base64()?;
        Some((chars, format!("{BASE64_PNG_HEAD}{png_base64}")))
    }
    /// Returns `(chars, png_bytes)` tuple.
    ///
    /// Returns `None` on error.
    pub fn as_png(&self) -> Option<(String, Vec<u8>)> {
        let chars = self.0.chars_as_string();
        let png_bytes = self.0.as_png()?;
        Some((chars, png_bytes))
    }
    /// Returns `(chars, WAV)` tuple.
    ///
    /// Warning: Currently this feature is rather limited. The same audio data
    /// is returned for the same letter, i.e. no noise is added. Someone
    /// could solve the CAPTCHA by simply having the audio for each letter
    /// and comparing them with the current challenge.
    pub fn as_wav(&self) -> (String, Vec<Option<Vec<u8>>>) {
        let chars = self.0.chars_as_string();
        let wav = self.0.as_wav();
        (chars, wav)
    }
}

impl Deref for Captcha {
    type Target = captcha::Captcha;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Captcha {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::*;

    #[test]
    fn captcha_amelia_easy() {
        let captcha = Captcha::by_name(Difficulty::Easy, CaptchaName::Amelia);
        let chars = captcha.chars_as_string();
        println!("chars={chars}");
        captcha
            .save(Path::new("src/fnd/captcha/doc/captcha_amelia_easy.png"))
            .unwrap();
    }

    #[test]
    fn captcha_amelia_medium() {
        let captcha = Captcha::by_name(Difficulty::Medium, CaptchaName::Amelia);
        let chars = captcha.chars_as_string();
        println!("chars={chars}");
        captcha
            .save(Path::new("src/fnd/captcha/doc/captcha_amelia_medium.png"))
            .unwrap();
    }

    #[test]
    fn captcha_amelia_hard() {
        let captcha = Captcha::by_name(Difficulty::Hard, CaptchaName::Amelia);
        let chars = captcha.chars_as_string();
        println!("chars={chars}");
        captcha
            .save(Path::new("src/fnd/captcha/doc/captcha_amelia_hard.png"))
            .unwrap();
    }

    #[test]
    fn captcha_lucy_easy() {
        let captcha = Captcha::by_name(Difficulty::Easy, CaptchaName::Lucy);
        let chars = captcha.chars_as_string();
        println!("chars={chars}");
        captcha
            .save(Path::new("src/fnd/captcha/doc/captcha_lucy_easy.png"))
            .unwrap();
    }

    #[test]
    fn captcha_lucy_medium() {
        let captcha = Captcha::by_name(Difficulty::Medium, CaptchaName::Lucy);
        let chars = captcha.chars_as_string();
        println!("chars={chars}");
        captcha
            .save(Path::new("src/fnd/captcha/doc/captcha_lucy_medium.png"))
            .unwrap();
    }

    #[test]
    fn captcha_lucy_hard() {
        let captcha = Captcha::by_name(Difficulty::Hard, CaptchaName::Lucy);
        let chars = captcha.chars_as_string();
        println!("chars={chars}");
        captcha
            .save(Path::new("src/fnd/captcha/doc/captcha_lucy_hard.png"))
            .unwrap();
    }

    #[test]
    fn captcha_mila_easy() {
        let captcha = Captcha::by_name(Difficulty::Easy, CaptchaName::Mila);
        let chars = captcha.chars_as_string();
        println!("chars={chars}");
        captcha
            .save(Path::new("src/fnd/captcha/doc/captcha_mila_easy.png"))
            .unwrap();
    }

    #[test]
    fn captcha_mila_medium() {
        let captcha = Captcha::by_name(Difficulty::Medium, CaptchaName::Mila);
        let chars = captcha.chars_as_string();
        println!("chars={chars}");
        captcha
            .save(Path::new("src/fnd/captcha/doc/captcha_mila_medium.png"))
            .unwrap();
    }

    #[test]
    fn captcha_mila_hard() {
        let captcha = Captcha::by_name(Difficulty::Hard, CaptchaName::Mila);
        let chars = captcha.chars_as_string();
        println!("chars={chars}");
        captcha
            .save(Path::new("src/fnd/captcha/doc/captcha_mila_hard.png"))
            .unwrap();
    }

    // To be more flexible you can build CAPTCHAs manually as well.
    #[test]
    fn captcha_flexible() {
        let mut captcha = Captcha::new();
        captcha
            .add_chars(5)
            .apply_filter(Noise::new(0.4))
            .apply_filter(Wave::new(2.0, 20.0).horizontal())
            .apply_filter(Wave::new(2.0, 20.0).vertical())
            .view(220, 120)
            .apply_filter(Dots::new(15));
        let (chars, base64) = captcha.as_base64().unwrap();
        println!("chars={chars}\n{base64}");
    }
}