/*****************************************************************************\ | █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® | | ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | | ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ | | ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | | █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ | | | | General Bots Copyright (c) pragmatismo.com.br. All rights reserved. | | Licensed under the AGPL-3.0. | | | | According to our dual licensing model, this program can be used either | | under the terms of the GNU Affero General Public License, version 3, | | or under a proprietary license. | | | | The texts of the GNU Affero General Public License with an additional | | permission and of our proprietary license can be found at and | | in the LICENSE file you have received along with this program. | | | | This program is distributed in the hope that it will be useful, | | but WITHOUT ANY WARRANTY, without even the implied warranty of | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | GNU Affero General Public License for more details. | | | | "General Bots" is a registered trademark of pragmatismo.com.br. | | The licensing of the program under the AGPLv3 does not imply a | | trademark license. Therefore any rights, title and interest in | | our trademarks remain entirely with us. | | | \*****************************************************************************/ use crate::core::shared::models::UserSession; use crate::core::shared::state::AppState; use log::{error, trace}; use png::{BitDepth, ColorType, Encoder}; use qrcode::QrCode; use rhai::{Dynamic, Engine}; use std::fs::File; use std::io::BufWriter; use std::path::Path; use std::sync::Arc; use uuid::Uuid; pub fn register_qrcode_keywords(state: Arc, user: UserSession, engine: &mut Engine) { register_qr_code_keyword(state.clone(), user.clone(), engine); register_qr_code_with_size_keyword(state.clone(), user.clone(), engine); register_qr_code_full_keyword(state, user, engine); } pub fn register_qr_code_keyword(state: Arc, user: UserSession, engine: &mut Engine) { let state_clone = Arc::clone(&state); let user_clone = user; engine .register_custom_syntax(["QR", "CODE", "$expr$"], false, move |context, inputs| { let data = context.eval_expression_tree(&inputs[0])?.to_string(); trace!("QR CODE: Generating QR code for data: {}", data); let state_for_task = Arc::clone(&state_clone); let user_for_task = user_clone.clone(); let (tx, rx) = std::sync::mpsc::channel(); std::thread::spawn(move || { let result = execute_qr_code_generation(&state_for_task, &user_for_task, &data, 256, None); if tx.send(result).is_err() { error!("Failed to send QR CODE result from thread"); } }); match rx.recv_timeout(std::time::Duration::from_secs(30)) { Ok(Ok(result)) => Ok(Dynamic::from(result)), Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( format!("QR CODE failed: {}", e).into(), rhai::Position::NONE, ))), Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { Err(Box::new(rhai::EvalAltResult::ErrorRuntime( "QR CODE generation timed out".into(), rhai::Position::NONE, ))) } Err(e) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( format!("QR CODE thread failed: {}", e).into(), rhai::Position::NONE, ))), } }) .expect("valid syntax registration"); } pub fn register_qr_code_with_size_keyword( state: Arc, user: UserSession, engine: &mut Engine, ) { let state_clone = Arc::clone(&state); let user_clone = user; engine .register_custom_syntax( ["QR", "CODE", "$expr$", ",", "$expr$"], false, move |context, inputs| { let data = context.eval_expression_tree(&inputs[0])?.to_string(); let size = context .eval_expression_tree(&inputs[1])? .as_int() .unwrap_or(256) as u32; trace!( "QR_CODE: Generating QR code with size {} for: {}", size, data ); let state_for_task = Arc::clone(&state_clone); let user_for_task = user_clone.clone(); let (tx, rx) = std::sync::mpsc::channel(); std::thread::spawn(move || { let result = execute_qr_code_generation( &state_for_task, &user_for_task, &data, size, None, ); if tx.send(result).is_err() { error!("Failed to send QR_CODE result from thread"); } }); match rx.recv_timeout(std::time::Duration::from_secs(30)) { Ok(Ok(result)) => Ok(Dynamic::from(result)), Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( format!("QR_CODE failed: {}", e).into(), rhai::Position::NONE, ))), Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { Err(Box::new(rhai::EvalAltResult::ErrorRuntime( "QR_CODE generation timed out".into(), rhai::Position::NONE, ))) } Err(e) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( format!("QR_CODE thread failed: {}", e).into(), rhai::Position::NONE, ))), } }, ) .expect("valid syntax registration"); } pub fn register_qr_code_full_keyword(state: Arc, user: UserSession, engine: &mut Engine) { let state_clone = Arc::clone(&state); let user_clone = user; engine .register_custom_syntax( ["QR_CODE", "$expr$", ",", "$expr$", ",", "$expr$"], false, move |context, inputs| { let data = context.eval_expression_tree(&inputs[0])?.to_string(); let size = context .eval_expression_tree(&inputs[1])? .as_int() .unwrap_or(256) as u32; let output_path = context.eval_expression_tree(&inputs[2])?.to_string(); trace!( "QR_CODE: Generating QR code with size {} to {} for: {}", size, output_path, data ); let state_for_task = Arc::clone(&state_clone); let user_for_task = user_clone.clone(); let (tx, rx) = std::sync::mpsc::channel(); std::thread::spawn(move || { let result = execute_qr_code_generation( &state_for_task, &user_for_task, &data, size, Some(&output_path), ); if tx.send(result).is_err() { error!("Failed to send QR_CODE result from thread"); } }); match rx.recv_timeout(std::time::Duration::from_secs(30)) { Ok(Ok(result)) => Ok(Dynamic::from(result)), Ok(Err(e)) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( format!("QR_CODE failed: {}", e).into(), rhai::Position::NONE, ))), Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { Err(Box::new(rhai::EvalAltResult::ErrorRuntime( "QR_CODE generation timed out".into(), rhai::Position::NONE, ))) } Err(e) => Err(Box::new(rhai::EvalAltResult::ErrorRuntime( format!("QR_CODE thread failed: {}", e).into(), rhai::Position::NONE, ))), } }, ) .expect("valid syntax registration"); } fn execute_qr_code_generation( state: &AppState, user: &UserSession, data: &str, size: u32, output_path: Option<&str>, ) -> Result> { let code = QrCode::new(data.as_bytes())?; let matrix = code.to_colors(); let qr_width = code.width(); let scale = (size as usize) / qr_width; let actual_size = qr_width * scale; let mut pixels: Vec = Vec::with_capacity(actual_size * actual_size); for y in 0..actual_size { for x in 0..actual_size { let qr_x = x / scale; let qr_y = y / scale; let idx = qr_y * qr_width + qr_x; let is_dark = matrix .get(idx) .map(|c| *c == qrcode::Color::Dark) .unwrap_or(false); pixels.push(if is_dark { 0 } else { 255 }); } } let work_path = crate::core::shared::utils::get_work_path(); let data_dir = state .config .as_ref() .map(|c| c.data_dir.as_str()) .unwrap_or(&work_path); let final_path = match output_path { Some(path) => { if Path::new(path).is_absolute() { path.to_string() } else { format!("{}/bots/{}/gbdrive/{}", data_dir, user.bot_id, path) } } None => { let filename = format!("qrcode_{}.png", Uuid::new_v4()); let base_path = format!("{}/bots/{}/gbdrive", data_dir, user.bot_id); std::fs::create_dir_all(&base_path)?; format!("{}/{}", base_path, filename) } }; if let Some(parent) = Path::new(&final_path).parent() { std::fs::create_dir_all(parent)?; } let file = File::create(&final_path)?; let w = &mut BufWriter::new(file); let mut encoder = Encoder::new(w, actual_size as u32, actual_size as u32); encoder.set_color(ColorType::Grayscale); encoder.set_depth(BitDepth::Eight); let mut writer = encoder.write_header()?; writer.write_image_data(&pixels)?; trace!("QR code generated: {}", final_path); Ok(final_path) } pub fn generate_qr_code_colored( data: &str, size: u32, foreground: [u8; 3], background: [u8; 3], output_path: &str, ) -> Result> { let code = QrCode::new(data.as_bytes())?; let matrix = code.to_colors(); let qr_width = code.width(); let scale = (size as usize) / qr_width; let actual_size = qr_width * scale; let mut pixels: Vec = Vec::with_capacity(actual_size * actual_size * 3); for y in 0..actual_size { for x in 0..actual_size { let qr_x = x / scale; let qr_y = y / scale; let idx = qr_y * qr_width + qr_x; let is_dark = matrix .get(idx) .map(|c| *c == qrcode::Color::Dark) .unwrap_or(false); let color = if is_dark { foreground } else { background }; pixels.extend_from_slice(&color); } } let file = File::create(output_path)?; let w = &mut BufWriter::new(file); let mut encoder = Encoder::new(w, actual_size as u32, actual_size as u32); encoder.set_color(ColorType::Rgb); encoder.set_depth(BitDepth::Eight); let mut writer = encoder.write_header()?; writer.write_image_data(&pixels)?; Ok(output_path.to_string()) } pub fn generate_qr_code_with_logo( data: &str, size: u32, _logo_path: &str, output_path: &str, ) -> Result> { let code = QrCode::with_error_correction_level(data.as_bytes(), qrcode::EcLevel::H)?; let matrix = code.to_colors(); let qr_width = code.width(); let scale = (size as usize) / qr_width; let actual_size = qr_width * scale; let logo_size = actual_size / 5; let logo_start = (actual_size - logo_size) / 2; let logo_end = logo_start + logo_size; let mut pixels: Vec = Vec::with_capacity(actual_size * actual_size * 4); for y in 0..actual_size { for x in 0..actual_size { if x >= logo_start && x < logo_end && y >= logo_start && y < logo_end { pixels.extend_from_slice(&[255, 255, 255, 255]); } else { let qr_x = x / scale; let qr_y = y / scale; let idx = qr_y * qr_width + qr_x; let is_dark = matrix .get(idx) .map(|c| *c == qrcode::Color::Dark) .unwrap_or(false); if is_dark { pixels.extend_from_slice(&[0, 0, 0, 255]); } else { pixels.extend_from_slice(&[255, 255, 255, 255]); } } } } let file = File::create(output_path)?; let w = &mut BufWriter::new(file); let mut encoder = Encoder::new(w, actual_size as u32, actual_size as u32); encoder.set_color(ColorType::Rgba); encoder.set_depth(BitDepth::Eight); let mut writer = encoder.write_header()?; writer.write_image_data(&pixels)?; trace!("QR code with logo placeholder generated: {}", output_path); Ok(output_path.to_string()) } #[cfg(test)] mod tests { use qrcode::QrCode; #[test] fn test_qr_code_generation() { let result = QrCode::new(b"https://example.com"); assert!(result.is_ok()); } #[test] fn test_qr_code_with_unicode() { let result = QrCode::new("Hello 世界 🌍".as_bytes()); assert!(result.is_ok()); } #[test] fn test_qr_code_long_data() { let long_data = "A".repeat(1000); let result = QrCode::new(long_data.as_bytes()); assert!(result.is_ok()); } #[test] fn test_qr_code_url() { let url = "https://example.com/path?param=value&other=123"; let result = QrCode::new(url.as_bytes()); assert!(result.is_ok()); } #[test] fn test_qr_code_json() { let json = r#"{"id": 123, "name": "Test", "active": true}"#; let result = QrCode::new(json.as_bytes()); assert!(result.is_ok()); } }