From bcb7703ea69abda823397da2273b5e68ba0e146a Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Tue, 29 Jul 2025 21:39:24 -0300 Subject: [PATCH] - More keywords. --- Cargo.lock | 189 +++++++++++++++++++++++ Cargo.toml | 3 +- src/main.rs | 6 +- src/prompts/business/data-enrichment.bas | 8 +- src/scripts/containers/email.sh | 2 +- src/scripts/utils/add-drive-user.sh | 27 ++++ src/scripts/utils/set-limits.sh | 6 +- src/services/keywords/create_draft.rs | 2 +- src/services/keywords/llm_keyword.rs | 27 ++++ src/services/keywords/mod.rs | 1 + src/services/llm.rs | 16 +- src/services/script.rs | 2 + src/services/utils.rs | 97 +++++++++++- src/services/web_automation.rs | 36 +++-- 14 files changed, 384 insertions(+), 38 deletions(-) create mode 100644 src/scripts/utils/add-drive-user.sh create mode 100644 src/services/keywords/llm_keyword.rs diff --git a/Cargo.lock b/Cargo.lock index ecc1f21..3562662 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,6 +268,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.12" @@ -383,6 +394,15 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arrayvec" version = "0.5.2" @@ -806,6 +826,15 @@ dependencies = [ "bytes", ] +[[package]] +name = "bzip2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" +dependencies = [ + "libbz2-rs-sys", +] + [[package]] name = "cc" version = "1.2.27" @@ -854,6 +883,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -904,6 +943,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.4.0" @@ -1110,6 +1155,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + [[package]] name = "der" version = "0.7.10" @@ -1141,6 +1192,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "derive_builder" version = "0.20.2" @@ -1475,6 +1537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -1702,6 +1765,7 @@ dependencies = [ "tracing", "tracing-subscriber", "urlencoding", + "zip", ] [[package]] @@ -2333,6 +2397,15 @@ dependencies = [ "hashbrown 0.15.4", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -2532,12 +2605,38 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "libbz2-rs-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775bf80d5878ab7c2b1080b5351a48b2f737d9f6f8b383574eebcc22be0dfccb" + [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "liblzma" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.15" @@ -2555,6 +2654,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -3030,6 +3138,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3230,6 +3348,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -4052,6 +4176,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "similar" version = "2.7.0" @@ -5699,6 +5829,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] [[package]] name = "zerotrie" @@ -5733,6 +5877,51 @@ dependencies = [ "syn 2.0.103", ] +[[package]] +name = "zip" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.3", + "hmac", + "indexmap", + "liblzma", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" + +[[package]] +name = "zopfli" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index 1b24bea..b624999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,4 +42,5 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt"] } scraper = "0.18" urlencoding = "2.1" -regex = "1.10" \ No newline at end of file +regex = "1.10" +zip = "4.3.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8a875d6..631b119 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use services::script::*; use services::state::*; use sqlx::PgPool; -use crate::services::web_automation::BrowserPool; +use crate::services::web_automation::{initialize_browser_pool, BrowserPool}; //use services:: find::*; mod services; @@ -38,6 +38,10 @@ async fn main() -> std::io::Result<()> { 5, "/usr/bin/brave-browser-beta".to_string(), )); + initialize_browser_pool() + .await + .expect("Failed to initialize browser pool"); + let app_state = web::Data::new(AppState { db: db.into(), db_custom: db_custom.into(), diff --git a/src/prompts/business/data-enrichment.bas b/src/prompts/business/data-enrichment.bas index a3de98f..9c060ad 100644 --- a/src/prompts/business/data-enrichment.bas +++ b/src/prompts/business/data-enrichment.bas @@ -1,14 +1,16 @@ -let items = FIND "gb.rob", "ACTION=EMUL" +let items = FIND "gb.rob", "ACTION=EMUL1" FOR EACH item IN items + PRINT item.company let website = GET WEBSITE item.company "website" PRINT website + WAIT 10 let page = GET website let prompt = "Create a website for " + item.company + " with the following details: " + page - let alias = REWRITE "Return a single word for {item.company} like a token, no spaces, no special characters, no numbers, no uppercase letters." + let alias = LLM "Return a single word for {item.company} like a token, no spaces, no special characters, no numbers, no uppercase letters." CREATE SITE item.company + "bot", website, "site", prompt @@ -18,4 +20,4 @@ FOR EACH item IN items CREATE DRAFT to, subject, body -NEXT item \ No newline at end of file +NEXT item diff --git a/src/scripts/containers/email.sh b/src/scripts/containers/email.sh index 81f583c..fda2429 100644 --- a/src/scripts/containers/email.sh +++ b/src/scripts/containers/email.sh @@ -42,7 +42,7 @@ echo "nameserver $PARAM_DNS_INTERNAL_IP" > /etc/resolv.conf apt install resolvconf -y apt-get update && apt-get install -y wget libcap2-bin -wget -O /tmp/stalwart.tar.gz https://github.com/stalwartlabs/stalwart/releases/download/v0.12.4/stalwart-x86_64-unknown-linux-gnu.tar.gz +wget -O /tmp/stalwart.tar.gz https://github.com/stalwartlabs/stalwart/releases/download/v0.13.1/stalwart-x86_64-unknown-linux-gnu.tar.gz tar -xzf /tmp/stalwart.tar.gz -C /tmp mkdir -p /opt/gbo/bin diff --git a/src/scripts/utils/add-drive-user.sh b/src/scripts/utils/add-drive-user.sh new file mode 100644 index 0000000..0f691e2 --- /dev/null +++ b/src/scripts/utils/add-drive-user.sh @@ -0,0 +1,27 @@ +export BOT_ID= +./mc alias set minio http://localhost:9000 user pass +./mc admin user add minio $BOT_ID + +cat > $BOT_ID-policy.json < Result { - let get_result = fetch_latest_email_from_sender(&state.config.clone().unwrap().email, to.clone()).await; + let get_result = fetch_latest_email_from_sender(&state.config.clone().unwrap().email, to).await; let email_body = if let Ok(get_result_str) = get_result { if !get_result_str.is_empty() { get_result_str + reply_text diff --git a/src/services/keywords/llm_keyword.rs b/src/services/keywords/llm_keyword.rs new file mode 100644 index 0000000..9866aff --- /dev/null +++ b/src/services/keywords/llm_keyword.rs @@ -0,0 +1,27 @@ +use rhai::{Dynamic, Engine}; +use crate::services::{state::AppState, utils::call_llm}; + +pub fn llm_keyword(state: &AppState, engine: &mut Engine) { + + let ai_config = state.config.clone().unwrap().ai.clone(); + + engine.register_custom_syntax( + &["LLM", "$string$"], // Syntax: LLM "text to process" + false, // Expression, not statement + move |context, inputs| { + let text = context.eval_expression_tree(&inputs[0])?; + let text_str = text.to_string(); + + println!("LLM processing text: {}", text_str); + + // Use the same pattern as GET + + let fut = call_llm(&text_str, &ai_config); + let result = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(fut) + }).map_err(|e| format!("LLM call failed: {}", e))?; + + Ok(Dynamic::from(result)) + } + ).unwrap(); +} diff --git a/src/services/keywords/mod.rs b/src/services/keywords/mod.rs index 56601da..5395822 100644 --- a/src/services/keywords/mod.rs +++ b/src/services/keywords/mod.rs @@ -4,6 +4,7 @@ pub mod find; pub mod for_next; pub mod get; pub mod get_website; +pub mod llm_keyword; pub mod print; pub mod set; pub mod wait; \ No newline at end of file diff --git a/src/services/llm.rs b/src/services/llm.rs index b5212a0..98deb1c 100644 --- a/src/services/llm.rs +++ b/src/services/llm.rs @@ -8,7 +8,7 @@ use langchain_rust::{ chain::{Chain, LLMChainBuilder}, fmt_message, fmt_template, language_models::llm::LLM, - llm::{openai::OpenAI, AzureConfig}, + llm::{openai::OpenAI}, message_formatter, prompt::HumanMessagePromptTemplate, prompt_args, @@ -16,15 +16,7 @@ use langchain_rust::{ template_fstring, }; -use crate::services::{config::AIConfig, state::AppState}; - -pub fn from_config(config: &AIConfig) -> AzureConfig { - AzureConfig::default() - .with_api_key(&config.key) - .with_api_base(&config.endpoint) - .with_api_version(&config.version) - .with_deployment_id(&config.instance) -} +use crate::services::{ state::AppState, utils::azure_from_config}; #[derive(serde::Deserialize)] struct ChatRequest { @@ -50,7 +42,7 @@ pub async fn chat( web::Json(request): web::Json, state: web::Data, ) -> Result { - let azure_config = from_config(&state.config.clone().unwrap().ai); + let azure_config = azure_from_config(&state.config.clone().unwrap().ai); let open_ai = OpenAI::new(azure_config); // Parse the context JSON @@ -107,7 +99,7 @@ pub async fn chat_stream( web::Json(request): web::Json, state: web::Data, ) -> Result { - let azure_config = from_config(&state.config.clone().unwrap().ai); + let azure_config = azure_from_config(&state.config.clone().unwrap().ai); let open_ai = OpenAI::new(azure_config); let prompt = message_formatter![ diff --git a/src/services/script.rs b/src/services/script.rs index 7dc661d..ed01272 100644 --- a/src/services/script.rs +++ b/src/services/script.rs @@ -5,6 +5,7 @@ use crate::services::keywords::find::{find_keyword}; use crate::services::keywords::for_next::for_keyword; use crate::services::keywords::get::get_keyword; use crate::services::keywords::get_website::get_website_keyword; +use crate::services::keywords::llm_keyword::llm_keyword; use crate::services::keywords::print::print_keyword; use crate::services::keywords::set::set_keyword; use crate::services::keywords::wait::wait_keyword; @@ -26,6 +27,7 @@ impl ScriptService { create_site_keyword(state, &mut engine); find_keyword(state, &mut engine); for_keyword(state, &mut engine); + llm_keyword(state, &mut engine); get_keyword(state, &mut engine); get_website_keyword(state, &mut engine); set_keyword(state, &mut engine); diff --git a/src/services/utils.rs b/src/services/utils.rs index 181956c..4d81419 100644 --- a/src/services/utils.rs +++ b/src/services/utils.rs @@ -1,3 +1,6 @@ +use crate::services::config::AIConfig; +use langchain_rust::llm::OpenAI; +use langchain_rust::{language_models::llm::LLM, llm::AzureConfig}; use log::{debug, warn}; use rhai::{Array, Dynamic}; use serde_json::{json, Value}; @@ -5,9 +8,76 @@ use smartstring::SmartString; use sqlx::Column; // Required for .name() method use sqlx::TypeInfo; // Required for .type_info() method use sqlx::{postgres::PgRow, Row}; -use std::error::Error; use sqlx::{Decode, Type}; +use std::error::Error; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; +use tokio_stream::StreamExt; +use zip::ZipArchive; +use tokio::fs::File as TokioFile; +use reqwest::Client; +use tokio::io::AsyncWriteExt; + +pub fn azure_from_config(config: &AIConfig) -> AzureConfig { + AzureConfig::default() + .with_api_key(&config.key) + .with_api_base(&config.endpoint) + .with_api_version(&config.version) + .with_deployment_id(&config.instance) +} + +pub async fn call_llm( + text: &str, + ai_config: &AIConfig, +) -> Result> { + let azure_config = azure_from_config(&ai_config.clone()); + let open_ai = OpenAI::new(azure_config); + + // Directly use the input text as prompt + let prompt = text.to_string(); + + // Call LLM and return the raw text response + match open_ai.invoke(&prompt).await { + Ok(response_text) => Ok(response_text), + Err(err) => { + eprintln!("Error invoking LLM API: {}", err); + Err(Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to invoke LLM API", + ))) + } + } +} + +pub fn extract_zip_recursive( + zip_path: &Path, + destination_path: &Path, +) -> Result<(), Box> { + let file = File::open(zip_path)?; + let buf_reader = BufReader::new(file); + let mut archive = ZipArchive::new(buf_reader)?; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + let outpath = destination_path.join(file.mangled_name()); + + if file.is_dir() { + std::fs::create_dir_all(&outpath)?; + } else { + if let Some(parent) = outpath.parent() { + if !parent.exists() { + std::fs::create_dir_all(&parent)?; + } + } + let mut outfile = File::create(&outpath)?; + std::io::copy(&mut file, &mut outfile)?; + } + } + + Ok(()) +} pub fn row_to_json(row: PgRow) -> Result> { let mut result = serde_json::Map::new(); let columns = row.columns(); @@ -22,7 +92,9 @@ pub fn row_to_json(row: PgRow) -> Result> { "INT8" | "int8" => handle_nullable_type::(&row, i, column_name), "FLOAT4" | "float4" => handle_nullable_type::(&row, i, column_name), "FLOAT8" | "float8" => handle_nullable_type::(&row, i, column_name), - "TEXT" | "VARCHAR" | "text" | "varchar" => handle_nullable_type::(&row, i, column_name), + "TEXT" | "VARCHAR" | "text" | "varchar" => { + handle_nullable_type::(&row, i, column_name) + } "BOOL" | "bool" => handle_nullable_type::(&row, i, column_name), "JSON" | "JSONB" | "json" | "jsonb" => handle_json(&row, i, column_name), _ => { @@ -57,7 +129,6 @@ where } } - fn handle_json(row: &PgRow, idx: usize, col_name: &str) -> Value { // First try to get as Option match row.try_get::, _>(idx) { @@ -125,3 +196,23 @@ pub fn to_array(value: Dynamic) -> Array { Array::from([value]) } } + +pub async fn download_file(url: &str, output_path: &str) -> Result<(), Box> { + let client = Client::new(); + let response = client.get(url).send().await?; + + if response.status().is_success() { + let mut file = TokioFile::create(output_path).await?; + + let mut stream = response.bytes_stream(); + + while let Some(chunk) = stream.next().await { + file.write_all(&chunk?).await?; + } + debug!("File downloaded successfully to {}", output_path); + } else { + return Err("Failed to download file".into()); + } + + Ok(()) +} diff --git a/src/services/web_automation.rs b/src/services/web_automation.rs index e168350..ff180aa 100644 --- a/src/services/web_automation.rs +++ b/src/services/web_automation.rs @@ -1,6 +1,9 @@ +use crate::services::utils; +use log::debug; +use std::env; +use std::env::temp_dir; use std::error::Error; use std::future::Future; -use std::path::Path; use std::pin::Pin; use std::process::Command; use std::sync::Arc; @@ -47,11 +50,10 @@ impl BrowserPool { caps.add_chrome_arg("--no-sandbox")?; let driver = WebDriver::new(&self.webdriver_url, caps).await?; - + // Execute user function let result = f(driver).await; - result } } @@ -91,11 +93,8 @@ impl BrowserSetup { } async fn setup_chromedriver() -> Result> { - let chromedriver_path = String::from(if cfg!(target_os = "windows") { - "chromedriver.exe" - } else { - "chromedriver" - }); + let mut chromedriver_path = env::current_exe()?.parent().unwrap().to_path_buf(); + chromedriver_path.push("chromedriver"); // Check if chromedriver exists if fs::metadata(&chromedriver_path).await.is_err() { @@ -119,14 +118,26 @@ impl BrowserSetup { _ => return Err("Unsupported platform".into()), }; - let _download_url = format!("{}/chromedriver_{}.zip", base_url, platform); - - let zip_path = Path::new("chromedriver.zip"); + let download_url = format!("{}/chromedriver_{}.zip", base_url, platform); + let mut zip_path = temp_dir(); + zip_path.push("chromedriver.zip"); + utils::download_file(&download_url, &zip_path.to_str().unwrap()).await?; + + let extract_result = utils::extract_zip_recursive(&zip_path, &chromedriver_path); + if let Err(e) = extract_result { + debug!("Error extracting ZIP: {}", e); + } // Clean up zip file let _ = fs::remove_file(&zip_path).await; + if cfg!(target_os = "windows") { + chromedriver_path.push("chromedriver.exe"); + } else { + chromedriver_path.push("chromedriver"); + } + #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; @@ -136,7 +147,7 @@ impl BrowserSetup { } } - Ok(chromedriver_path) + Ok(chromedriver_path.to_string_lossy().to_string()) } } @@ -165,7 +176,6 @@ async fn is_process_running(name: &str) -> bool { if cfg!(target_os = "windows") { Command::new("tasklist") .output() - .map(|o| String::from_utf8_lossy(&o.stdout).contains(name)) .unwrap_or(false) } else {