- CREATE DRAFT, CREATE SITE, GET WEBSITE, WAIT keywords added.
All checks were successful
GBCI / build (push) Successful in 18m14s
All checks were successful
GBCI / build (push) Successful in 18m14s
This commit is contained in:
parent
90016ea373
commit
ed4aad72f4
15 changed files with 890 additions and 140 deletions
250
Cargo.lock
generated
250
Cargo.lock
generated
|
@ -1252,6 +1252,19 @@ version = "0.15.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downloader"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ac1e888d6830712d565b2f3a974be3200be9296bc1b03db8251a4cbf18a4a34"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"reqwest 0.12.20",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dtoa"
|
name = "dtoa"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
|
@ -1418,6 +1431,28 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fantoccini"
|
||||||
|
version = "0.19.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65f0fbe245d714b596ba5802b46f937f5ce68dcae0f32f9a70b5c3b04d3c6f64"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"cookie",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"http 0.2.12",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"hyper-rustls 0.23.2",
|
||||||
|
"mime",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
"webdriver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -1451,7 +1486,7 @@ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"spin",
|
"spin 0.9.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1641,6 +1676,7 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
|
"downloader",
|
||||||
"env_logger 0.10.2",
|
"env_logger 0.10.2",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -1651,17 +1687,21 @@ dependencies = [
|
||||||
"mailparse",
|
"mailparse",
|
||||||
"minio",
|
"minio",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
|
"regex",
|
||||||
"reqwest 0.11.27",
|
"reqwest 0.11.27",
|
||||||
"rhai",
|
"rhai",
|
||||||
|
"scraper 0.18.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"smartstring",
|
"smartstring",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"thirtyfour",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2018,6 +2058,21 @@ dependencies = [
|
||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-rustls"
|
||||||
|
version = "0.23.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
|
||||||
|
dependencies = [
|
||||||
|
"http 0.2.12",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"log",
|
||||||
|
"rustls 0.20.9",
|
||||||
|
"rustls-native-certs 0.6.3",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls 0.23.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-rustls"
|
name = "hyper-rustls"
|
||||||
version = "0.27.7"
|
version = "0.27.7"
|
||||||
|
@ -2028,10 +2083,10 @@ dependencies = [
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"rustls 0.23.28",
|
"rustls 0.23.28",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs 0.8.1",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls 0.26.2",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2409,7 +2464,7 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest 0.12.20",
|
"reqwest 0.12.20",
|
||||||
"reqwest-eventsource",
|
"reqwest-eventsource",
|
||||||
"scraper",
|
"scraper 0.20.0",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -2435,7 +2490,7 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"spin",
|
"spin 0.9.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3240,7 +3295,7 @@ dependencies = [
|
||||||
"getrandom 0.3.3",
|
"getrandom 0.3.3",
|
||||||
"lru-slab",
|
"lru-slab",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"ring",
|
"ring 0.17.14",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"rustls 0.23.28",
|
"rustls 0.23.28",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
@ -3461,7 +3516,7 @@ dependencies = [
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"hyper-rustls",
|
"hyper-rustls 0.27.7",
|
||||||
"hyper-tls 0.6.0",
|
"hyper-tls 0.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
@ -3473,7 +3528,7 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"quinn",
|
"quinn",
|
||||||
"rustls 0.23.28",
|
"rustls 0.23.28",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs 0.8.1",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -3481,7 +3536,7 @@ dependencies = [
|
||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
"tokio-rustls",
|
"tokio-rustls 0.26.2",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
@ -3537,6 +3592,21 @@ dependencies = [
|
||||||
"syn 2.0.103",
|
"syn 2.0.103",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.16.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"spin 0.5.2",
|
||||||
|
"untrusted 0.7.1",
|
||||||
|
"web-sys",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
|
@ -3547,7 +3617,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"getrandom 0.2.16",
|
"getrandom 0.2.16",
|
||||||
"libc",
|
"libc",
|
||||||
"untrusted",
|
"untrusted 0.9.0",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3611,13 +3681,25 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.20.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"ring 0.16.20",
|
||||||
|
"sct",
|
||||||
|
"webpki",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.21.12"
|
version = "0.21.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring 0.17.14",
|
||||||
"rustls-webpki 0.101.7",
|
"rustls-webpki 0.101.7",
|
||||||
"sct",
|
"sct",
|
||||||
]
|
]
|
||||||
|
@ -3629,13 +3711,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
|
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ring",
|
"ring 0.17.14",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki 0.103.3",
|
"rustls-webpki 0.103.3",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-native-certs"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
|
||||||
|
dependencies = [
|
||||||
|
"openssl-probe",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"schannel",
|
||||||
|
"security-framework 2.11.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-native-certs"
|
name = "rustls-native-certs"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
@ -3673,8 +3767,8 @@ version = "0.101.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring 0.17.14",
|
||||||
"untrusted",
|
"untrusted 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3683,9 +3777,9 @@ version = "0.103.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
|
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring 0.17.14",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"untrusted",
|
"untrusted 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3715,6 +3809,22 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scraper"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "585480e3719b311b78a573db1c9d9c4c1f8010c2dee4cc59c2efe58ea4dbc3e1"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"cssparser",
|
||||||
|
"ego-tree",
|
||||||
|
"getopts",
|
||||||
|
"html5ever 0.26.0",
|
||||||
|
"once_cell",
|
||||||
|
"selectors",
|
||||||
|
"tendril",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scraper"
|
name = "scraper"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
|
@ -3737,8 +3847,8 @@ version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring 0.17.14",
|
||||||
"untrusted",
|
"untrusted 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3838,6 +3948,7 @@ version = "1.0.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -3853,6 +3964,17 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_repr"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.103",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -3991,6 +4113,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
|
@ -4254,6 +4382,15 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stringmatch"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8c0faab770316c3838f895fc2dfc3a8707ef4da48676f1014e1061ebd583b40"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stringprep"
|
name = "stringprep"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -4447,6 +4584,31 @@ version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thirtyfour"
|
||||||
|
version = "0.30.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60aded5a858cc767f950549a9c0eecd61cb4648ad2c7f0ada5cbd7f441cb6ac3"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"base64 0.13.1",
|
||||||
|
"chrono",
|
||||||
|
"cookie",
|
||||||
|
"fantoccini",
|
||||||
|
"futures",
|
||||||
|
"http 0.2.12",
|
||||||
|
"log",
|
||||||
|
"parking_lot",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_repr",
|
||||||
|
"stringmatch",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
"urlparse",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
|
@ -4615,6 +4777,17 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.23.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
|
||||||
|
dependencies = [
|
||||||
|
"rustls 0.20.9",
|
||||||
|
"tokio",
|
||||||
|
"webpki",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.2"
|
version = "0.26.2"
|
||||||
|
@ -4821,6 +4994,12 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -4844,6 +5023,12 @@ version = "2.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlparse"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "110352d4e9076c67839003c7788d8604e24dcded13e0b375af3efaa8cf468517"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
@ -5026,6 +5211,35 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webdriver"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9973cb72c8587d5ad5efdb91e663d36177dc37725e6c90ca86c626b0cc45c93f"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"bytes",
|
||||||
|
"cookie",
|
||||||
|
"http 0.2.12",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"time",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
|
||||||
|
dependencies = [
|
||||||
|
"ring 0.17.14",
|
||||||
|
"untrusted 0.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.25.4"
|
version = "0.25.4"
|
||||||
|
|
|
@ -12,6 +12,8 @@ actix-cors = "0.6"
|
||||||
actix-multipart = "0.6"
|
actix-multipart = "0.6"
|
||||||
actix-web = "4"
|
actix-web = "4"
|
||||||
actix-ws="0.3.0"
|
actix-ws="0.3.0"
|
||||||
|
thirtyfour = { version = "0.30" }
|
||||||
|
downloader = "0.2.8"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
bytes = "1.1"
|
bytes = "1.1"
|
||||||
|
@ -38,3 +40,6 @@ tokio = { version = "1", features = ["full"] }
|
||||||
tokio-stream = "0.1.17"
|
tokio-stream = "0.1.17"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["fmt"] }
|
tracing-subscriber = { version = "0.3", features = ["fmt"] }
|
||||||
|
scraper = "0.18"
|
||||||
|
urlencoding = "2.1"
|
||||||
|
regex = "1.10"
|
10
src/main.rs
10
src/main.rs
|
@ -1,3 +1,5 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_web::http::header;
|
use actix_web::http::header;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
|
@ -10,6 +12,8 @@ use services::llm::*;
|
||||||
use services::script::*;
|
use services::script::*;
|
||||||
use services::state::*;
|
use services::state::*;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
|
use crate::services::web_automation::BrowserPool;
|
||||||
//use services:: find::*;
|
//use services:: find::*;
|
||||||
mod services;
|
mod services;
|
||||||
|
|
||||||
|
@ -29,11 +33,17 @@ async fn main() -> std::io::Result<()> {
|
||||||
.await
|
.await
|
||||||
.expect("Failed to initialize Minio");
|
.expect("Failed to initialize Minio");
|
||||||
|
|
||||||
|
let browser_pool = Arc::new(BrowserPool::new(
|
||||||
|
"http://localhost:9515".to_string(),
|
||||||
|
5,
|
||||||
|
"/usr/bin/brave-browser-beta".to_string(),
|
||||||
|
));
|
||||||
let app_state = web::Data::new(AppState {
|
let app_state = web::Data::new(AppState {
|
||||||
db: db.into(),
|
db: db.into(),
|
||||||
db_custom: db_custom.into(),
|
db_custom: db_custom.into(),
|
||||||
config: Some(config.clone()),
|
config: Some(config.clone()),
|
||||||
minio_client: minio_client.into(),
|
minio_client: minio_client.into(),
|
||||||
|
browser_pool: browser_pool.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let script_service = ScriptService::new(&app_state.clone());
|
let script_service = ScriptService::new(&app_state.clone());
|
||||||
|
|
|
@ -1,5 +1,21 @@
|
||||||
let items = FIND "gb.rob", "ACTION=EMUL1"
|
let items = FIND "gb.rob", "ACTION=EMUL"
|
||||||
FOR EACH item IN items
|
FOR EACH item IN items
|
||||||
let text = GET "https://pragmatismo.com.br"
|
|
||||||
PRINT item.company
|
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."
|
||||||
|
|
||||||
|
CREATE SITE item.company + "bot", website, "site", prompt
|
||||||
|
|
||||||
|
let to = item.emailcto
|
||||||
|
let subject = "Simulador criado " + item.company
|
||||||
|
let body = "O simulador " + item.company + " foi criado com sucesso. Acesse o site: " + item.company + "bot"
|
||||||
|
|
||||||
|
CREATE DRAFT to, subject, body
|
||||||
|
|
||||||
NEXT item
|
NEXT item
|
|
@ -1,8 +1,9 @@
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod utils;
|
|
||||||
pub mod state;
|
|
||||||
pub mod email;
|
pub mod email;
|
||||||
pub mod keywords;
|
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
pub mod keywords;
|
||||||
pub mod llm;
|
pub mod llm;
|
||||||
pub mod script;
|
pub mod script;
|
||||||
|
pub mod state;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod web_automation;
|
||||||
|
|
|
@ -6,7 +6,6 @@ pub struct AppConfig {
|
||||||
pub server: ServerConfig,
|
pub server: ServerConfig,
|
||||||
pub database: DatabaseConfig,
|
pub database: DatabaseConfig,
|
||||||
pub database_custom: DatabaseConfig,
|
pub database_custom: DatabaseConfig,
|
||||||
|
|
||||||
pub email: EmailConfig,
|
pub email: EmailConfig,
|
||||||
pub ai: AIConfig,
|
pub ai: AIConfig,
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,102 +160,211 @@ fn parse_from_field(from: &str) -> (String, String) {
|
||||||
("Unknown".to_string(), from.to_string())
|
("Unknown".to_string(), from.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[actix_web::post("/emails/suggest-answer/{email_id}")]
|
|
||||||
// pub async fn suggest_answer(
|
|
||||||
// path: web::Path<String>,
|
|
||||||
// state: web::Data<AppState>,
|
|
||||||
// ) -> Result<HttpResponse, actix_web::Error> {
|
|
||||||
// let email_id = path.into_inner();
|
|
||||||
// let config = state
|
|
||||||
// .config
|
|
||||||
// .as_ref()
|
|
||||||
// .ok_or_else(|| ErrorInternalServerError("Configuration not available"))?;
|
|
||||||
|
|
||||||
// // let mut session = create_imap_session(&config.email).await?;
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct SaveDraftRequest {
|
||||||
|
pub to: String,
|
||||||
|
pub subject: String,
|
||||||
|
pub cc: Option<String>,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
// // session
|
#[derive(serde::Serialize)]
|
||||||
// // .select("INBOX")
|
pub struct SaveDraftResponse {
|
||||||
// // .await
|
pub success: bool,
|
||||||
// // .map_err(|e| ErrorInternalServerError(format!("Failed to select INBOX: {:?}", e)))?;
|
pub message: String,
|
||||||
|
pub draft_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
// // let messages = session
|
#[derive(serde::Deserialize)]
|
||||||
// // .fetch(&email_id, "RFC822.HEADER BODY[TEXT]")
|
pub struct GetLatestEmailRequest {
|
||||||
// // .await
|
pub from_email: String,
|
||||||
// // .map_err(|e| ErrorInternalServerError(format!("Failed to fetch email: {:?}", e)))?;
|
}
|
||||||
|
|
||||||
// // let msg = messages
|
#[derive(serde::Serialize)]
|
||||||
// // .iter()
|
pub struct LatestEmailResponse {
|
||||||
// // .next()
|
pub success: bool,
|
||||||
// // .ok_or_else(|| actix_web::error::ErrorNotFound("Email not found"))?;
|
pub email_text: Option<String>,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
// // let header = msg
|
#[actix_web::post("/emails/save_draft")]
|
||||||
// // .header()
|
pub async fn save_draft(
|
||||||
// // .ok_or_else(|| ErrorInternalServerError("No header found"))?;
|
state: web::Data<AppState>,
|
||||||
|
draft_data: web::Json<SaveDraftRequest>,
|
||||||
|
) -> Result<web::Json<SaveDraftResponse>, actix_web::Error> {
|
||||||
|
let config = state
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| ErrorInternalServerError("Configuration not available"))?;
|
||||||
|
|
||||||
// // let body = msg
|
match save_email_draft(&config.email, &draft_data).await {
|
||||||
// // .text()
|
Ok(draft_id) => Ok(web::Json(SaveDraftResponse {
|
||||||
// // .ok_or_else(|| ErrorInternalServerError("No body found"))?;
|
success: true,
|
||||||
|
message: "Draft saved successfully".to_string(),
|
||||||
|
draft_id: Some(draft_id),
|
||||||
|
})),
|
||||||
|
Err(e) => Ok(web::Json(SaveDraftResponse {
|
||||||
|
success: false,
|
||||||
|
message: format!("Failed to save draft: {}", e),
|
||||||
|
draft_id: None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // let header_str = String::from_utf8_lossy(header);
|
|
||||||
// // let mut subject = String::new();
|
|
||||||
// // let mut from_info = String::new();
|
|
||||||
|
|
||||||
// // for line in header_str.lines() {
|
pub async fn save_email_draft(
|
||||||
// // if line.starts_with("Subject: ") {
|
email_config: &EmailConfig,
|
||||||
// // subject = line.strip_prefix("Subject: ").unwrap_or("").to_string();
|
draft_data: &SaveDraftRequest,
|
||||||
// // } else if line.starts_with("From: ") {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
// // from_info = line.strip_prefix("From: ").unwrap_or("").to_string();
|
// Establish connection
|
||||||
// // }
|
let tls = native_tls::TlsConnector::builder().build()?;
|
||||||
// // }
|
let client = imap::connect(
|
||||||
|
(email_config.server.as_str(), 993),
|
||||||
|
email_config.server.as_str(),
|
||||||
|
&tls,
|
||||||
|
)?;
|
||||||
|
|
||||||
// // let body_text = String::from_utf8_lossy(body);
|
// Login
|
||||||
|
let mut session = client.login(&email_config.username, &email_config.password)
|
||||||
|
.map_err(|e| format!("Login failed: {:?}", e))?;
|
||||||
|
|
||||||
// // let response = serde_json::json!({
|
// Select or create Drafts folder
|
||||||
// // "suggested_response": "Thank you for your email. I will review this and get back to you shortly.",
|
if session.select("Drafts").is_err() {
|
||||||
// // "prompt": format!(
|
// Try to create Drafts folder if it doesn't exist
|
||||||
// // "Email from: {}\nSubject: {}\n\nBody:\n{}\n\n---\n\nPlease draft a professional response to this email.",
|
session.create("Drafts")?;
|
||||||
// // from_info, subject, body_text.lines().take(20).collect::<Vec<_>>().join("\n")
|
session.select("Drafts")?;
|
||||||
// // )
|
}
|
||||||
// // });
|
|
||||||
|
|
||||||
// // session.logout().await.ok();
|
// Create email message
|
||||||
// //Ok(HttpResponse::Ok().json(response))
|
let cc_header = draft_data.cc.as_deref()
|
||||||
// }
|
.filter(|cc| !cc.is_empty())
|
||||||
|
.map(|cc| format!("Cc: {}\r\n", cc))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
// #[actix_web::post("/emails/archive/{email_id}")]
|
let email_message = format!(
|
||||||
// pub async fn archive_email(
|
"From: {}\r\nTo: {}\r\n{}Subject: {}\r\nDate: {}\r\n\r\n{}",
|
||||||
// path: web::Path<String>,
|
email_config.username,
|
||||||
// state: web::Data<AppState>,
|
draft_data.to,
|
||||||
// ) -> Result<HttpResponse, actix_web::Error> {
|
cc_header,
|
||||||
// let email_id = path.into_inner();
|
draft_data.subject,
|
||||||
// let config = state
|
chrono::Utc::now().format("%a, %d %b %Y %H:%M:%S +0000"),
|
||||||
// .config
|
draft_data.text
|
||||||
// .as_ref()
|
);
|
||||||
// .ok_or_else(|| ErrorInternalServerError("Configuration not available"))?;
|
|
||||||
|
|
||||||
// let mut session = create_imap_session(&config.email).await?;
|
// Append to Drafts folder
|
||||||
|
session.append("Drafts", &email_message)?;
|
||||||
|
|
||||||
|
session.logout()?;
|
||||||
|
|
||||||
// session
|
Ok(chrono::Utc::now().timestamp().to_string())
|
||||||
// .select("INBOX")
|
}
|
||||||
// .await
|
|
||||||
// .map_err(|e| ErrorInternalServerError(format!("Failed to select INBOX: {:?}", e)))?;
|
|
||||||
|
|
||||||
// // Create Archive folder if it doesn't exist
|
#[actix_web::post("/emails/get_latest_from")]
|
||||||
// session.create("Archive").await.ok(); // Ignore error if folder exists
|
pub async fn get_latest_email_from(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
request: web::Json<GetLatestEmailRequest>,
|
||||||
|
) -> Result<web::Json<LatestEmailResponse>, actix_web::Error> {
|
||||||
|
let config = state
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| ErrorInternalServerError("Configuration not available"))?;
|
||||||
|
|
||||||
// // Move email to Archive folder
|
match fetch_latest_email_from_sender(&config.email, &request.from_email).await {
|
||||||
// session.mv(&email_id, "Archive").await.map_err(|e| {
|
Ok(email_text) => Ok(web::Json(LatestEmailResponse {
|
||||||
// ErrorInternalServerError(format!("Failed to move email to archive: {:?}", e))
|
success: true,
|
||||||
// })?;
|
email_text: Some(email_text),
|
||||||
|
message: "Latest email retrieved successfully".to_string(),
|
||||||
|
})),
|
||||||
|
Err(e) => {
|
||||||
|
if e.to_string().contains("No emails found") {
|
||||||
|
Ok(web::Json(LatestEmailResponse {
|
||||||
|
success: false,
|
||||||
|
email_text: None,
|
||||||
|
message: e.to_string(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(ErrorInternalServerError(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn fetch_latest_email_from_sender(
|
||||||
|
email_config: &EmailConfig,
|
||||||
|
from_email: &str,
|
||||||
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
// Establish connection
|
||||||
|
let tls = native_tls::TlsConnector::builder().build()?;
|
||||||
|
let client = imap::connect(
|
||||||
|
(email_config.server.as_str(), 993),
|
||||||
|
email_config.server.as_str(),
|
||||||
|
&tls,
|
||||||
|
)?;
|
||||||
|
|
||||||
// session.logout().await.ok();
|
// Login
|
||||||
|
let mut session = client.login(&email_config.username, &email_config.password)
|
||||||
|
.map_err(|e| format!("Login failed: {:?}", e))?;
|
||||||
|
|
||||||
|
// Try to select Archive folder first, then fall back to INBOX
|
||||||
|
if session.select("Archive").is_err() {
|
||||||
|
session.select("INBOX")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for emails from the specified sender
|
||||||
|
let search_query = format!("FROM \"{}\"", from_email);
|
||||||
|
let messages = session.search(&search_query)?;
|
||||||
|
|
||||||
|
if messages.is_empty() {
|
||||||
|
session.logout()?;
|
||||||
|
return Err(format!("No emails found from {}", from_email).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest message (highest sequence number)
|
||||||
|
let latest_seq = messages.iter().max().unwrap();
|
||||||
|
|
||||||
|
// Fetch the entire message
|
||||||
|
let messages = session.fetch(latest_seq.to_string(), "RFC822")?;
|
||||||
|
|
||||||
|
let mut email_text = String::new();
|
||||||
|
|
||||||
|
for msg in messages.iter() {
|
||||||
|
let body = msg.body().ok_or("No body found in email")?;
|
||||||
|
|
||||||
|
// Parse the complete email message
|
||||||
|
let parsed = parse_mail(body)?;
|
||||||
|
|
||||||
|
// Extract headers
|
||||||
|
let headers = parsed.get_headers();
|
||||||
|
let subject = headers.get_first_value("Subject").unwrap_or_default();
|
||||||
|
let from = headers.get_first_value("From").unwrap_or_default();
|
||||||
|
let date = headers.get_first_value("Date").unwrap_or_default();
|
||||||
|
let to = headers.get_first_value("To").unwrap_or_default();
|
||||||
|
|
||||||
|
// Extract body text
|
||||||
|
let body_text = if let Some(body_part) = parsed.subparts.iter().find(|p| p.ctype.mimetype == "text/plain") {
|
||||||
|
body_part.get_body().unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
parsed.get_body().unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format the email text ready for reply with headers
|
||||||
|
email_text = format!(
|
||||||
|
"--- Original Message ---\nFrom: {}\nTo: {}\nDate: {}\nSubject: {}\n\n{}\n\n--- Reply Above This Line ---\n\n",
|
||||||
|
from, to, date, subject, body_text
|
||||||
|
);
|
||||||
|
|
||||||
|
break; // We only want the first (and should be only) message
|
||||||
|
}
|
||||||
|
|
||||||
|
session.logout()?;
|
||||||
|
|
||||||
|
if email_text.is_empty() {
|
||||||
|
Err("Failed to extract email content".into())
|
||||||
|
} else {
|
||||||
|
Ok(email_text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ok(HttpResponse::Ok().json(serde_json::json!({
|
|
||||||
// "message": "Email archived successfully",
|
|
||||||
// "email_id": email_id,
|
|
||||||
// "archive_folder": "Archive"
|
|
||||||
// })))
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[actix_web::post("/emails/send")]
|
#[actix_web::post("/emails/send")]
|
||||||
pub async fn send_email(
|
pub async fn send_email(
|
||||||
|
|
|
@ -1,32 +1,62 @@
|
||||||
|
use crate::services::email::SaveDraftRequest;
|
||||||
|
use crate::services::email::{fetch_latest_email_from_sender, save_email_draft};
|
||||||
|
use crate::services::state::AppState;
|
||||||
use rhai::Dynamic;
|
use rhai::Dynamic;
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
use crate::services::state::AppState;
|
pub fn create_draft_keyword(state: &AppState, engine: &mut Engine) {
|
||||||
|
let state_clone = state.clone();
|
||||||
|
|
||||||
pub fn create_draft_keyword(_state: &AppState, engine: &mut Engine) {
|
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(
|
.register_custom_syntax(
|
||||||
&["CREATE", "DRAFT", "$expr$", ",", "$expr$", ",", "$expr$"],
|
&["CREATE", "DRAFT", "$expr$", ",", "$expr$", ",", "$expr$"],
|
||||||
true, // Statement
|
true, // Statement
|
||||||
|context, inputs| {
|
move |context, inputs| {
|
||||||
if inputs.len() < 3 {
|
// Extract arguments
|
||||||
return Err("Not enough arguments for CREATE DRAFT".into());
|
let to = context.eval_expression_tree(&inputs[0])?.to_string();
|
||||||
}
|
let subject = context.eval_expression_tree(&inputs[1])?.to_string();
|
||||||
|
let reply_text = context.eval_expression_tree(&inputs[2])?.to_string();
|
||||||
|
|
||||||
let to = context.eval_expression_tree(&inputs[0])?;
|
// Execute async operations using the same pattern as FIND
|
||||||
let subject = context.eval_expression_tree(&inputs[1])?;
|
let fut = execute_create_draft(&state_clone, &to, &subject, &reply_text);
|
||||||
let body = context.eval_expression_tree(&inputs[2])?;
|
let result =
|
||||||
|
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(fut))
|
||||||
|
.map_err(|e| format!("Draft creation error: {}", e))?;
|
||||||
|
|
||||||
let result = json!({
|
Ok(Dynamic::from(result))
|
||||||
"command": "create_draft",
|
|
||||||
"to": to.to_string(),
|
|
||||||
"subject": subject.to_string(),
|
|
||||||
"body": body.to_string()
|
|
||||||
});
|
|
||||||
println!("CREATE DRAFT executed: {}", result.to_string());
|
|
||||||
Ok(Dynamic::UNIT)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn execute_create_draft(
|
||||||
|
state: &AppState,
|
||||||
|
to: &str,
|
||||||
|
subject: &str,
|
||||||
|
reply_text: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let get_result = fetch_latest_email_from_sender(&state.config.clone().unwrap().email, to.clone()).await;
|
||||||
|
let email_body = if let Ok(get_result_str) = get_result {
|
||||||
|
if !get_result_str.is_empty() {
|
||||||
|
get_result_str + reply_text
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reply_text.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create and save draft
|
||||||
|
let draft_request = SaveDraftRequest {
|
||||||
|
to: to.to_string(),
|
||||||
|
subject: subject.to_string(),
|
||||||
|
cc: None,
|
||||||
|
text: email_body,
|
||||||
|
};
|
||||||
|
|
||||||
|
let save_result = match save_email_draft(&state.config.clone().unwrap().email, &draft_request).await {
|
||||||
|
Ok(_) => Ok("Draft saved successfully".to_string()),
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
};
|
||||||
|
save_result
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use rhai::Dynamic;
|
use rhai::Dynamic;
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
use serde_json::json;
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::services::state::AppState;
|
use crate::services::state::AppState;
|
||||||
|
|
||||||
pub fn create_site_keyword(_state: &AppState, engine: &mut Engine) {
|
pub fn create_site_keyword(_state: &AppState, engine: &mut Engine) {
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.register_custom_syntax(
|
.register_custom_syntax(
|
||||||
&[
|
&[
|
||||||
|
@ -13,28 +13,35 @@ pub fn create_site_keyword(_state: &AppState, engine: &mut Engine) {
|
||||||
"$expr$",
|
"$expr$",
|
||||||
],
|
],
|
||||||
true, // Statement
|
true, // Statement
|
||||||
|context, inputs| {
|
move |context, inputs| {
|
||||||
if inputs.len() < 5 {
|
if inputs.len() < 5 {
|
||||||
return Err("Not enough arguments for CREATE SITE".into());
|
return Err("Not enough arguments for CREATE SITE".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = context.eval_expression_tree(&inputs[0])?;
|
let _name = context.eval_expression_tree(&inputs[0])?;
|
||||||
let company = context.eval_expression_tree(&inputs[1])?;
|
let company = context.eval_expression_tree(&inputs[1])?;
|
||||||
let website = context.eval_expression_tree(&inputs[2])?;
|
let _website = context.eval_expression_tree(&inputs[2])?;
|
||||||
let template = context.eval_expression_tree(&inputs[3])?;
|
let _template = context.eval_expression_tree(&inputs[3])?;
|
||||||
let prompt = context.eval_expression_tree(&inputs[4])?;
|
let prompt = context.eval_expression_tree(&inputs[4])?;
|
||||||
|
|
||||||
let result = json!({
|
// Call the LLM to generate the HTML content
|
||||||
"command": "create_site",
|
let llm_result = context.call_fn::<String>("chat", (prompt.to_string(),))?;
|
||||||
"name": name.to_string(),
|
|
||||||
"company": company.to_string(),
|
// Create the directory structure
|
||||||
"website": website.to_string(),
|
let base_path = "/opt/gbo/tenants/pragmatismo/proxy/data/websites/sites.pragmatismo.com.br";
|
||||||
"template": template.to_string(),
|
let site_name = format!("{}bot", company.to_string());
|
||||||
"prompt": prompt.to_string()
|
let full_path = format!("{}/{}", base_path, site_name);
|
||||||
});
|
|
||||||
println!("CREATE SITE executed: {}", result.to_string());
|
// Create directory if it doesn't exist
|
||||||
|
fs::create_dir_all(&full_path).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Write the HTML file
|
||||||
|
let index_path = Path::new(&full_path).join("index.html");
|
||||||
|
fs::write(index_path, llm_result).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
println!("Site created at: {}", full_path);
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
133
src/services/keywords/get_website.rs
Normal file
133
src/services/keywords/get_website.rs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
use crate::services::{state::AppState, web_automation::BrowserPool};
|
||||||
|
use rhai::{Dynamic, Engine};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use thirtyfour::{By, WebDriver};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
pub fn get_website_keyword(state: &AppState, engine: &mut Engine) {
|
||||||
|
let browser_pool = state.browser_pool.clone(); // Assuming AppState has browser_pool field
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_custom_syntax(
|
||||||
|
&["GET", "WEBSITE", "$expr$", "$expr$"],
|
||||||
|
false,
|
||||||
|
move |context, inputs| {
|
||||||
|
let search_term = context.eval_expression_tree(&inputs[0])?.to_string();
|
||||||
|
let website_hint = context.eval_expression_tree(&inputs[1])?.to_string();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"GET WEBSITE executed - Search: '{}', Hint: '{}'",
|
||||||
|
search_term, website_hint
|
||||||
|
);
|
||||||
|
|
||||||
|
let browser_pool_clone = browser_pool.clone();
|
||||||
|
let fut = execute_headless_browser_search(
|
||||||
|
browser_pool_clone,
|
||||||
|
&search_term,
|
||||||
|
&website_hint,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result =
|
||||||
|
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(fut))
|
||||||
|
.map_err(|e| format!("Headless browser search failed: {}", e))?;
|
||||||
|
|
||||||
|
Ok(Dynamic::from(result))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute_headless_browser_search(
|
||||||
|
browser_pool: Arc<BrowserPool>, // Adjust path as needed
|
||||||
|
search_term: &str,
|
||||||
|
website_hint: &str,
|
||||||
|
) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||||
|
println!(
|
||||||
|
"Starting headless browser search: '{}' targeting '{}'",
|
||||||
|
search_term, website_hint
|
||||||
|
);
|
||||||
|
|
||||||
|
let search_term = search_term.to_string();
|
||||||
|
let website_hint = website_hint.to_string();
|
||||||
|
|
||||||
|
let result = browser_pool
|
||||||
|
.with_browser(|driver| {
|
||||||
|
Box::pin(async move { perform_search(driver, &search_term, &website_hint).await })
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn perform_search(
|
||||||
|
driver: WebDriver,
|
||||||
|
search_term: &str,
|
||||||
|
website_hint: &str,
|
||||||
|
) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||||
|
// Configure the search query
|
||||||
|
let query = if website_hint.trim().is_empty() {
|
||||||
|
search_term.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{} site:{}", search_term, website_hint)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Navigate to DuckDuckGo
|
||||||
|
println!("Navigating to DuckDuckGo...");
|
||||||
|
driver.goto("https://duckduckgo.com").await?;
|
||||||
|
|
||||||
|
// Wait for search box and type query
|
||||||
|
println!("Searching for: {}", query);
|
||||||
|
let search_input = driver.find(By::Name("q")).await?;
|
||||||
|
search_input.click().await?;
|
||||||
|
search_input.send_keys(&query).await?;
|
||||||
|
|
||||||
|
// Submit search by pressing Enter
|
||||||
|
search_input.send_keys("\n").await?;
|
||||||
|
|
||||||
|
// Wait for results to load
|
||||||
|
driver.find(By::Css(".result")).await?;
|
||||||
|
sleep(Duration::from_millis(2000)).await; // Give extra time for JS
|
||||||
|
|
||||||
|
// Extract first result link
|
||||||
|
let results = extract_search_results(&driver).await?;
|
||||||
|
|
||||||
|
if !results.is_empty() {
|
||||||
|
println!("Found {} results", results.len());
|
||||||
|
Ok(results[0].clone())
|
||||||
|
} else {
|
||||||
|
Ok("No results found".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_search_results(
|
||||||
|
driver: &WebDriver,
|
||||||
|
) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
// Try different selectors for search results
|
||||||
|
let selectors = [
|
||||||
|
"a[data-testid='result-title-a']", // Modern DuckDuckGo
|
||||||
|
".result__a", // Classic DuckDuckGo
|
||||||
|
"a.result-link", // Alternative
|
||||||
|
".result a[href]", // Generic result links
|
||||||
|
];
|
||||||
|
|
||||||
|
for selector in &selectors {
|
||||||
|
if let Ok(elements) = driver.find_all(By::Css(selector)).await {
|
||||||
|
for element in elements {
|
||||||
|
if let Ok(Some(href)) = element.attr("href").await {
|
||||||
|
if href.starts_with("http") && !href.contains("duckduckgo.com") {
|
||||||
|
results.push(href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !results.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
|
@ -3,5 +3,7 @@ pub mod create_site;
|
||||||
pub mod find;
|
pub mod find;
|
||||||
pub mod for_next;
|
pub mod for_next;
|
||||||
pub mod get;
|
pub mod get;
|
||||||
|
pub mod get_website;
|
||||||
pub mod print;
|
pub mod print;
|
||||||
pub mod set;
|
pub mod set;
|
||||||
|
pub mod wait;
|
39
src/services/keywords/wait.rs
Normal file
39
src/services/keywords/wait.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use rhai::{Dynamic, Engine};
|
||||||
|
use crate::services::state::AppState;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub fn wait_keyword(_state: &AppState, engine: &mut Engine) {
|
||||||
|
engine.register_custom_syntax(
|
||||||
|
&["WAIT", "$expr$"],
|
||||||
|
false, // Expression, not statement
|
||||||
|
move |context, inputs| {
|
||||||
|
let seconds = context.eval_expression_tree(&inputs[0])?;
|
||||||
|
|
||||||
|
// Convert to number (handle both int and float)
|
||||||
|
let duration_secs = if seconds.is::<i64>() {
|
||||||
|
seconds.cast::<i64>() as f64
|
||||||
|
} else if seconds.is::<f64>() {
|
||||||
|
seconds.cast::<f64>()
|
||||||
|
} else {
|
||||||
|
return Err(format!("WAIT expects a number, got: {}", seconds).into());
|
||||||
|
};
|
||||||
|
|
||||||
|
if duration_secs < 0.0 {
|
||||||
|
return Err("WAIT duration cannot be negative".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap maximum wait time to prevent abuse (e.g., 5 minutes max)
|
||||||
|
let capped_duration = if duration_secs > 300.0 { 300.0 } else { duration_secs };
|
||||||
|
|
||||||
|
println!("WAIT {} seconds (thread sleep)", capped_duration);
|
||||||
|
|
||||||
|
// Use thread::sleep to block only the current thread, not the entire server
|
||||||
|
let duration = Duration::from_secs_f64(capped_duration);
|
||||||
|
thread::sleep(duration);
|
||||||
|
|
||||||
|
println!("WAIT completed after {} seconds", capped_duration);
|
||||||
|
Ok(Dynamic::from(format!("Waited {} seconds", capped_duration)))
|
||||||
|
}
|
||||||
|
).unwrap();
|
||||||
|
}
|
|
@ -4,8 +4,10 @@ use crate::services::keywords::create_site::create_site_keyword;
|
||||||
use crate::services::keywords::find::{find_keyword};
|
use crate::services::keywords::find::{find_keyword};
|
||||||
use crate::services::keywords::for_next::for_keyword;
|
use crate::services::keywords::for_next::for_keyword;
|
||||||
use crate::services::keywords::get::get_keyword;
|
use crate::services::keywords::get::get_keyword;
|
||||||
|
use crate::services::keywords::get_website::get_website_keyword;
|
||||||
use crate::services::keywords::print::print_keyword;
|
use crate::services::keywords::print::print_keyword;
|
||||||
use crate::services::keywords::set::set_keyword;
|
use crate::services::keywords::set::set_keyword;
|
||||||
|
use crate::services::keywords::wait::wait_keyword;
|
||||||
use crate::services::state::AppState;
|
use crate::services::state::AppState;
|
||||||
|
|
||||||
pub struct ScriptService {
|
pub struct ScriptService {
|
||||||
|
@ -25,7 +27,9 @@ impl ScriptService {
|
||||||
find_keyword(state, &mut engine);
|
find_keyword(state, &mut engine);
|
||||||
for_keyword(state, &mut engine);
|
for_keyword(state, &mut engine);
|
||||||
get_keyword(state, &mut engine);
|
get_keyword(state, &mut engine);
|
||||||
|
get_website_keyword(state, &mut engine);
|
||||||
set_keyword(state, &mut engine);
|
set_keyword(state, &mut engine);
|
||||||
|
wait_keyword(state, &mut engine);
|
||||||
print_keyword(state, &mut engine);
|
print_keyword(state, &mut engine);
|
||||||
ScriptService { engine }
|
ScriptService { engine }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use minio::s3::Client;
|
use minio::s3::Client;
|
||||||
|
|
||||||
use crate::services::config::AppConfig;
|
use crate::services::{config::AppConfig, web_automation::BrowserPool};
|
||||||
|
|
||||||
|
|
||||||
// App state shared across all handlers
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub minio_client: Option<Client>,
|
pub minio_client: Option<Client>,
|
||||||
pub config: Option<AppConfig>,
|
pub config: Option<AppConfig>,
|
||||||
pub db: Option<sqlx::PgPool>,
|
pub db: Option<sqlx::PgPool>,
|
||||||
pub db_custom: Option<sqlx::PgPool>,
|
pub db_custom: Option<sqlx::PgPool>,
|
||||||
|
pub browser_pool: Arc<BrowserPool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
178
src/services/web_automation.rs
Normal file
178
src/services/web_automation.rs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use thirtyfour::{DesiredCapabilities, WebDriver};
|
||||||
|
use tokio::fs;
|
||||||
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
|
pub struct BrowserSetup {
|
||||||
|
pub brave_path: String,
|
||||||
|
pub chromedriver_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BrowserPool {
|
||||||
|
webdriver_url: String,
|
||||||
|
semaphore: Semaphore,
|
||||||
|
brave_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrowserPool {
|
||||||
|
pub fn new(webdriver_url: String, max_concurrent: usize, brave_path: String) -> Self {
|
||||||
|
Self {
|
||||||
|
webdriver_url,
|
||||||
|
semaphore: Semaphore::new(max_concurrent),
|
||||||
|
brave_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with_browser<F, T>(&self, f: F) -> Result<T, Box<dyn Error + Send + Sync>>
|
||||||
|
where
|
||||||
|
F: FnOnce(
|
||||||
|
WebDriver,
|
||||||
|
)
|
||||||
|
-> Pin<Box<dyn Future<Output = Result<T, Box<dyn Error + Send + Sync>>> + Send>>
|
||||||
|
+ Send
|
||||||
|
+ 'static,
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
let _permit = self.semaphore.acquire().await?;
|
||||||
|
|
||||||
|
let mut caps = DesiredCapabilities::chrome();
|
||||||
|
caps.set_binary(&self.brave_path)?;
|
||||||
|
caps.add_chrome_arg("--headless=new")?;
|
||||||
|
caps.add_chrome_arg("--disable-gpu")?;
|
||||||
|
caps.add_chrome_arg("--no-sandbox")?;
|
||||||
|
|
||||||
|
let driver = WebDriver::new(&self.webdriver_url, caps).await?;
|
||||||
|
|
||||||
|
// Execute user function
|
||||||
|
let result = f(driver).await;
|
||||||
|
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrowserSetup {
|
||||||
|
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
// Check for Brave installation
|
||||||
|
let brave_path = Self::find_brave().await?;
|
||||||
|
|
||||||
|
// Check for chromedriver
|
||||||
|
let chromedriver_path = Self::setup_chromedriver().await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
brave_path,
|
||||||
|
chromedriver_path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_brave() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let possible_paths = vec![
|
||||||
|
// Windows
|
||||||
|
String::from(r"C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe"),
|
||||||
|
// macOS
|
||||||
|
String::from("/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"),
|
||||||
|
// Linux
|
||||||
|
String::from("/usr/bin/brave-browser"),
|
||||||
|
String::from("/usr/bin/brave"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for path in possible_paths {
|
||||||
|
if fs::metadata(&path).await.is_ok() {
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("Brave browser not found. Please install Brave first.".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup_chromedriver() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let chromedriver_path = String::from(if cfg!(target_os = "windows") {
|
||||||
|
"chromedriver.exe"
|
||||||
|
} else {
|
||||||
|
"chromedriver"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if chromedriver exists
|
||||||
|
if fs::metadata(&chromedriver_path).await.is_err() {
|
||||||
|
println!("Downloading chromedriver...");
|
||||||
|
|
||||||
|
// Note: This URL structure is outdated. Consider using Chrome for Testing endpoints
|
||||||
|
let (base_url, platform) =
|
||||||
|
match (cfg!(target_os = "windows"), cfg!(target_arch = "x86_64")) {
|
||||||
|
(true, true) => (
|
||||||
|
"https://chromedriver.storage.googleapis.com/114.0.5735.90",
|
||||||
|
"win32",
|
||||||
|
),
|
||||||
|
(false, true) if cfg!(target_os = "macos") => (
|
||||||
|
"https://chromedriver.storage.googleapis.com/114.0.5735.90",
|
||||||
|
"mac64",
|
||||||
|
),
|
||||||
|
(false, true) => (
|
||||||
|
"https://chromedriver.storage.googleapis.com/114.0.5735.90",
|
||||||
|
"linux64",
|
||||||
|
),
|
||||||
|
_ => return Err("Unsupported platform".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let _download_url = format!("{}/chromedriver_{}.zip", base_url, platform);
|
||||||
|
|
||||||
|
let zip_path = Path::new("chromedriver.zip");
|
||||||
|
|
||||||
|
|
||||||
|
// Clean up zip file
|
||||||
|
let _ = fs::remove_file(&zip_path).await;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let mut perms = fs::metadata(&chromedriver_path).await?.permissions();
|
||||||
|
perms.set_mode(0o755); // Make executable
|
||||||
|
fs::set_permissions(&chromedriver_path, perms).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(chromedriver_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified BrowserPool initialization
|
||||||
|
pub async fn initialize_browser_pool() -> Result<Arc<BrowserPool>, Box<dyn std::error::Error>> {
|
||||||
|
let setup = BrowserSetup::new().await?;
|
||||||
|
|
||||||
|
// Start chromedriver process if not running
|
||||||
|
if !is_process_running("chromedriver").await {
|
||||||
|
Command::new(&setup.chromedriver_path)
|
||||||
|
.arg("--port=9515")
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
// Give chromedriver time to start
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Arc::new(BrowserPool::new(
|
||||||
|
"http://localhost:9515".to_string(),
|
||||||
|
5, // Max concurrent browsers
|
||||||
|
setup.brave_path,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
Command::new("pgrep")
|
||||||
|
.arg(name)
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue