aboutsummaryrefslogtreecommitdiffstats
path: root/src/commands/qr.rs
blob: c0b8af009f8c2f242b9d6f2ccf8b8e608123f322 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use poise::serenity_prelude as serenity;

use crate::Data;
use crate::error::{Error, Result};
use crate::lang;

type Context<'a> = poise::Context<'a, Data, Error>;

/// QR group
#[poise::command(slash_command, subcommands("dekodowanie", "generowanie",))]
pub async fn qr(_ctx: Context<'_>) -> Result<()> {
    Ok(())
}

/// Generate a QR code from text
#[poise::command(slash_command)]
pub async fn generowanie(
    ctx: Context<'_>,
    #[description = "Zawartość kodu QR"] text: String,
) -> Result<()> {
    ctx.defer().await?;

    let code = qrcode::QrCode::new(text.as_bytes())?;
    let image = code.render::<image::Luma<u8>>().build();

    let scaled = image::imageops::resize(
        &image,
        image.width() * 10,
        image.height() * 10,
        image::imageops::Nearest,
    );

    let mut buf = std::io::Cursor::new(Vec::new());
    scaled.write_to(&mut buf, image::ImageFormat::Png)?;

    ctx.send(
        poise::CreateReply::default()
            .content(lang::L_QR_GENERATED)
            .attachment(serenity::CreateAttachment::bytes(
                buf.into_inner(),
                "qrcode.png",
            )),
    )
    .await?;
    Ok(())
}

/// Decode a QR code from an uploaded image
#[poise::command(slash_command)]
pub async fn dekodowanie(
    ctx: Context<'_>,
    #[description = "Obraz zawierający kod QR"] image: serenity::Attachment,
) -> Result<()> {
    ctx.defer().await?;

    let img_data = ctx
        .data()
        .http_client
        .get(&image.url)
        .send()
        .await?
        .bytes()
        .await?;
    // Decoding is CPU-heavy: keep it off the async runtime.
    let img_data = img_data.to_vec();
    let decoded = tokio::task::spawn_blocking(move || {
        let img = image::load_from_memory(&img_data).ok()?;
        let gray = img.to_luma8();
        decode_qr_simple(&gray)
    })
    .await?;
    match decoded {
        Some(text) => {
            ctx.say(lang::fmt_qr_decoded(&text)).await?;
        }
        None => {
            ctx.say(lang::L_QR_DECODE_FAILED).await?;
        }
    }
    Ok(())
}

fn decode_qr_simple(img: &image::ImageBuffer<image::Luma<u8>, Vec<u8>>) -> Option<String> {
    rxing::helpers::detect_in_luma(img.as_raw().to_vec(), img.width(), img.height(), None)
        .ok()
        .map(|r| r.getText().to_owned())
}

pub fn commands() -> Vec<poise::Command<Data, Error>> {
    vec![qr()]
}