fix: two root-cause bug fixes for web_search
Bug 1 — writer.shutdown() kills Node socket before response: Rust called shutdown() on the write half immediately after writing the command. This sent EOF to the Node.js daemon, whose safeWrite() checks socket.destroyed and silently bails. Fixed by dropping writer instead. Bug 2 — daemon has zero macro support: The daemon's execute() never handles macro_name/query. Only url matters for the navigate action. Fixed search() to resolve all 12 macros (@google_search, @youtube_search, etc.) to actual search URLs in Rust before sending navigate with cmd.url set.
This commit is contained in:
@@ -330,7 +330,11 @@ impl BrowserClient {
|
|||||||
|
|
||||||
let (reader, mut writer) = stream.into_split();
|
let (reader, mut writer) = stream.into_split();
|
||||||
writer.write_all(format!("{}\n", payload).as_bytes()).await.map_err(BrowserError::Io)?;
|
writer.write_all(format!("{}\n", payload).as_bytes()).await.map_err(BrowserError::Io)?;
|
||||||
writer.shutdown().await.map_err(BrowserError::Io)?;
|
// Don't call writer.shutdown() — it sends EOF to the Node.js daemon,
|
||||||
|
// which causes the socket to close before the async response arrives.
|
||||||
|
// The daemon's safeWrite() checks socket.destroyed and bails silently.
|
||||||
|
// The socket pair (reader/writer) will be dropped after we read the response.
|
||||||
|
drop(writer);
|
||||||
|
|
||||||
let mut buf_reader = BufReader::new(reader);
|
let mut buf_reader = BufReader::new(reader);
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
@@ -729,12 +733,58 @@ impl BrowserClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search via macro.
|
/// Search via macro — resolves macro names to actual search URLs
|
||||||
|
/// since the camoufox-browser daemon does not handle macros internally.
|
||||||
pub async fn search(&self, macro_name: &str, query: &str) -> BrowserResult<NavigationResult> {
|
pub async fn search(&self, macro_name: &str, query: &str) -> BrowserResult<NavigationResult> {
|
||||||
|
let url = match macro_name {
|
||||||
|
"@google_search" | "google" => {
|
||||||
|
format!("https://www.google.com/search?q={}&hl=en", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@youtube_search" | "youtube" => {
|
||||||
|
format!("https://www.youtube.com/results?search_query={}", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@amazon_search" | "amazon" => {
|
||||||
|
format!("https://www.amazon.com/s?k={}", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@reddit_search" | "reddit" => {
|
||||||
|
format!("https://www.reddit.com/search/?q={}", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@wikipedia_search" | "wikipedia" => {
|
||||||
|
format!("https://en.wikipedia.org/w/index.php?search={}", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@twitter_search" | "@x_search" | "twitter" | "x" => {
|
||||||
|
format!("https://x.com/search?q={}&src=typed_query", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@yelp_search" | "yelp" => {
|
||||||
|
format!("https://www.yelp.com/search?find_desc={}", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@spotify_search" | "spotify" => {
|
||||||
|
format!("https://open.spotify.com/search/{}", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@linkedin_search" | "linkedin" => {
|
||||||
|
format!("https://www.linkedin.com/search/results/all/?keywords={}", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@instagram_search" | "instagram" => {
|
||||||
|
format!("https://www.instagram.com/explore/search/keyword/?q={}", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@tiktok_search" | "tiktok" => {
|
||||||
|
format!("https://www.tiktok.com/search?q={}", urlencoding(query))
|
||||||
|
}
|
||||||
|
"@twitch_search" | "twitch" => {
|
||||||
|
format!("https://www.twitch.tv/search?term={}", urlencoding(query))
|
||||||
|
}
|
||||||
|
// If it looks like a URL already, use it as-is
|
||||||
|
s if s.starts_with("http://") || s.starts_with("https://") => s.to_string(),
|
||||||
|
// Default: Google search
|
||||||
|
_ => {
|
||||||
|
warn!("Unknown macro '{}', falling back to Google search", macro_name);
|
||||||
|
format!("https://www.google.com/search?q={}&hl=en", urlencoding(query))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let resp = self.send_command("navigate", |cmd| {
|
let resp = self.send_command("navigate", |cmd| {
|
||||||
cmd.macro_name = Some(macro_name.to_string());
|
cmd.url = Some(url);
|
||||||
cmd.query = Some(query.to_string());
|
cmd.wait = Some(5000);
|
||||||
cmd.wait = Some(3000);
|
|
||||||
}).await?;
|
}).await?;
|
||||||
let data = self.check_response(resp)?;
|
let data = self.check_response(resp)?;
|
||||||
Ok(NavigationResult {
|
Ok(NavigationResult {
|
||||||
@@ -763,6 +813,24 @@ impl BrowserClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// URL-encode a string for use in a search query.
|
||||||
|
fn urlencoding(input: &str) -> String {
|
||||||
|
// Simple URL encoding — replaces spaces with + and encodes special chars
|
||||||
|
let mut result = String::with_capacity(input.len());
|
||||||
|
for byte in input.bytes() {
|
||||||
|
match byte {
|
||||||
|
b' ' => result.push('+'),
|
||||||
|
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
|
||||||
|
result.push(byte as char);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
result.push_str(&format!("%{:02X}", byte));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for BrowserClient {
|
impl Default for BrowserClient {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
|||||||
Reference in New Issue
Block a user