collective

Browse / github.com/storytold/artcraft / patch

Fix Grok index scrape for CDN chunk URLs and verification meta

Fix Grok index scrape for CDN chunk URLs and verification meta ## Summary - Update Grok index script parsing for absolute `cdn.grok.com` chunk URLs - Accept unicode-dash `grok-site―verification` meta name - Note remaining XSID marker drift (`anonPrivateKey` / `880932`) Fixes part of #1033 (consumer scrape layer). ## Test plan - [ ] `SQLX_OFFLINE=true cargo check -p grok_consumer_client` - [ ] With valid Grok cookies, confirm index scripts/verification token parse (XSID may still fail)

Status
open
Score
0.00
Adopters
0 other operators
Author
giorgioabbruscato (imported from pull request #2284, open)
Base
d4e4ac69bbd93d237eaf5d0260bc6a5ff3a5e373
Size
+87 −12 in 5 files
Review
pending
Shared
2026-10-28
Id
acf0c3d80c319db4c908a67d

How it went

No other operator has reported using it yet.

Diff

raw · crates/api_clients/grok_consumer_client/src/requests/index_page/get_numbers.rs crates/api_clients/grok_consumer_client/src/requests/index_page/get_xsid_script.rs crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_script_list.rs crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_verification_token.rs crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/script/parse_script_actions_and_xsid_script_path.rs

diff --git a/crates/api_clients/grok_consumer_client/src/requests/index_page/get_numbers.rs b/crates/api_clients/grok_consumer_client/src/requests/index_page/get_numbers.rs--- a/crates/api_clients/grok_consumer_client/src/requests/index_page/get_numbers.rs+++ b/crates/api_clients/grok_consumer_client/src/requests/index_page/get_numbers.rs@@ -24,7 +24,8 @@ pub async fn get_numbers(args: GetNumbersArgs<'_>) -> Result<XsidNumbers, GrokEr       let on_demand = on_demand.unwrap_or_else(|| "".to_string());       format!("https://abs.twimg.com/responsive-web/client-web/ondemand.s.{}a.js", on_demand)     },-    _ => format!("https://grok.com/_next/{}", args.xsid_script_id),+    // 2026-03+: chunks are served from cdn.grok.com rather than grok.com.+    _ => format!("https://cdn.grok.com/_next/{}", args.xsid_script_id),   };    let xsid_script_body = get_index_page_script_with_client(GetIndexPageScriptArgs {diff --git a/crates/api_clients/grok_consumer_client/src/requests/index_page/get_xsid_script.rs b/crates/api_clients/grok_consumer_client/src/requests/index_page/get_xsid_script.rs--- a/crates/api_clients/grok_consumer_client/src/requests/index_page/get_xsid_script.rs+++ b/crates/api_clients/grok_consumer_client/src/requests/index_page/get_xsid_script.rs@@ -27,7 +27,8 @@ pub async fn get_xsid_script(args: GetXsidScriptArgs<'_>) -> Result<XsidScript,       let on_demand = on_demand.unwrap_or_else(|| "".to_string());       format!("https://abs.twimg.com/responsive-web/client-web/ondemand.s.{}a.js", on_demand)     },-    _ => format!("https://grok.com/_next/{}", args.xsid_script_id),+    // 2026-03+: chunks are served from cdn.grok.com rather than grok.com.+    _ => format!("https://cdn.grok.com/_next/{}", args.xsid_script_id),   };    let xsid_script_body = get_index_page_script_with_client(GetIndexPageScriptArgs {diff --git a/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_script_list.rs b/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_script_list.rs--- a/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_script_list.rs+++ b/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_script_list.rs@@ -1,7 +1,9 @@ use once_cell::sync::Lazy; use scraper::{Html, Selector}; -// scripts: list = [s['src'] for s in BeautifulSoup(load_site.text, 'html.parser').find_all('script', src=True) if s['src'].startswith('/_next/static/chunks/')]+// Historical (relative): /_next/static/chunks/...+// 2026-03+: absolute CDN: https://cdn.grok.com/_next/static/chunks/...+const NEXT_STATIC_CHUNKS_MARKER: &str = "/_next/static/chunks/";  static SCRIPT_SELECTOR: Lazy<Selector> = Lazy::new(|| {   Selector::parse("script[src]")@@ -14,18 +16,49 @@ pub fn parse_index_svg_paths(html: &str) -> Vec<String> {   let selected = document.select(&SCRIPT_SELECTOR);   selected       .filter_map(|node| node.attr("src"))-      .filter(|src| src.starts_with("/_next/static/chunks/"))+      .filter(|src| is_next_static_chunk_script(src))       .map(|s| s.to_string())       .collect::<Vec<_>>() } +fn is_next_static_chunk_script(src: &str) -> bool {+  src.starts_with(NEXT_STATIC_CHUNKS_MARKER) || src.contains(NEXT_STATIC_CHUNKS_MARKER)+}+ #[cfg(test)] mod tests {   use crate::requests::index_page::get_index_page::{get_index, GetIndexPageArgs};-  use crate::requests::index_page::parsers::index::parse_index_script_list::parse_index_svg_paths;+  use crate::requests::index_page::parsers::index::parse_index_script_list::{+    is_next_static_chunk_script, parse_index_svg_paths,+  };   use crate::test_utils::get_test_cookies::get_test_cookies;   use errors::AnyhowResult; +  #[test]+  fn accepts_relative_and_cdn_chunk_urls() {+    assert!(is_next_static_chunk_script("/_next/static/chunks/abc.js"));+    assert!(is_next_static_chunk_script(+      "https://cdn.grok.com/_next/static/chunks/abc.js"+    ));+    assert!(!is_next_static_chunk_script("https://cdn.grok.com/_next/static/css/abc.css"));+  }++  #[test]+  fn parses_cdn_script_tags_from_index_html() {+    let html = r#"+      <html><head>+        <script src="https://cdn.grok.com/_next/static/chunks/one.js"></script>+        <script src="/_next/static/chunks/two.js"></script>+        <script src="https://example.com/other.js"></script>+      </head></html>+    "#;++    let scripts = parse_index_svg_paths(html);+    assert_eq!(scripts.len(), 2);+    assert!(scripts.iter().any(|s| s.contains("one.js")));+    assert!(scripts.iter().any(|s| s.contains("two.js")));+  }+   #[tokio::test]   #[ignore] // Manual test invocation   async fn test() -> AnyhowResult<()> {diff --git a/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_verification_token.rs b/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_verification_token.rs--- a/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_verification_token.rs+++ b/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/index/parse_index_verification_token.rs@@ -3,23 +3,28 @@ use once_cell::sync::Lazy; use regex::Regex; use scraper::{Html, Selector}; +/// Historical ASCII: grok-site-verification+/// 2026-03+: grok-site―verification (U+2015 HORIZONTAL BAR)+const VERIFICATION_META_NAME_ASCII: &str = "grok-site-verification";+const VERIFICATION_META_NAME_UNICODE: &str = "grok-site\u{2015}verification";+ /// Find the verification token in the meta tags using HTML parsing. static META_SELECTOR : Lazy<Selector> = Lazy::new(|| {-  Selector::parse("meta[name=grok-site-verification]")+  Selector::parse("meta[name]")       .expect("HTML selector should parse") });  /// Find the verification token in the meta tags using regex // eg. <meta name="grok-site-verification" content="iFlTOEJNfQZP1B6YMRf/zuj3eYFrKWi6eNUg5XvvpllTOw5TS82coZkeUBdiHxYr"/> static META_TAG_REGEX : Lazy<Regex> = Lazy::new(|| {-  Regex::new(r#""grok-site-verification"\s*content="([A-Za-z0-9/=+-]{5,})""#)+  Regex::new(r#""grok-site[\-\x{2015}]verification"\s*content="([A-Za-z0-9/=+-]{5,})""#)       .expect("Regex should parse") });  /// Find the verification token in the script tags using regex /// eg. {\"name\":\"grok-site-verification\",\"content\":\"iFlTOEJNfQZP1B6YMRf/zuj3eYFrKWi6eNUg5XvvpllTOw5TS82coZkeUBdiHxYr\"}] static JSON_REGEX: Lazy<Regex> = Lazy::new(|| {-  Regex::new(r#""grok-site-verification\\?",\s*\\?"content\\?":\\?"([A-Za-z0-9/=+-]{5,})\\?""#)+  Regex::new(r#""grok-site[\-\x{2015}]verification\\?",\s*\\?"content\\?":\\?"([A-Za-z0-9/=+-]{5,})\\?""#)       .expect("Regex should parse") }); @@ -31,25 +36,42 @@ pub fn parse_index_verification_token(html: &str) -> Option<VerificationToken> {     return Some(VerificationToken(meta))   } -  let maybe_script = scrape_script_via_regex(html);+  let maybe_meta_regex = scrape_meta_tag_via_regex(html);++  if let Some(meta) = maybe_meta_regex {+    return Some(VerificationToken(meta))+  }++  let maybe_json = scrape_json_via_regex(html); -  if let Some(script) = maybe_script {-    return Some(VerificationToken(script))+  if let Some(token) = maybe_json {+    return Some(VerificationToken(token))   }    None } -fn scrape_script_via_regex(html: &str) -> Option<String> {+fn is_verification_meta_name(name: &str) -> bool {+  name == VERIFICATION_META_NAME_ASCII || name == VERIFICATION_META_NAME_UNICODE+}++fn scrape_meta_tag_via_regex(html: &str) -> Option<String> {   let captures = META_TAG_REGEX.captures(html)?;   let capture = captures.get(1)?;   Some(capture.as_str().to_string()) } +fn scrape_json_via_regex(html: &str) -> Option<String> {+  let captures = JSON_REGEX.captures(html)?;+  let capture = captures.get(1)?;+  Some(capture.as_str().to_string())+}+ fn scrape_meta_tag_via_parsing(html: &str) -> Option<String> {   let document = Html::parse_document(html);   let selected = document.select(&META_SELECTOR);   let mut values = selected+      .filter(|node| node.attr("name").map(is_verification_meta_name).unwrap_or(false))       .filter_map(|node| node.attr("content"))       .map(|s| s.to_string())       .collect::<Vec<_>>();@@ -63,6 +85,21 @@ mod tests {   use crate::test_utils::get_test_cookies::get_test_cookies;   use errors::AnyhowResult; +  #[test]+  fn parses_ascii_hyphen_meta_name() {+    let html = r#"<meta name="grok-site-verification" content="iFlTOEJNfQZP1B6YMRf/zuj3eYFrKWi6eNUg5XvvpllTOw5TS82coZkeUBdiHxYr"/>"#;+    let token = parse_index_verification_token(html).expect("token");+    assert_eq!(token.0, "iFlTOEJNfQZP1B6YMRf/zuj3eYFrKWi6eNUg5XvvpllTOw5TS82coZkeUBdiHxYr");+  }++  #[test]+  fn parses_unicode_horizontal_bar_meta_name() {+    // 2026-03+: grok.com emits U+2015 instead of ASCII '-'+    let html = "<meta name=\"grok-site\u{2015}verification\" content=\"uRjaEmD3mkuTK6n0N+ZxvGdx2Bq1e8j2AYfyCaFmO1eHKULyZUaSB8EkYgNapjwS\"/>";+    let token = parse_index_verification_token(html).expect("token");+    assert_eq!(token.0, "uRjaEmD3mkuTK6n0N+ZxvGdx2Bq1e8j2AYfyCaFmO1eHKULyZUaSB8EkYgNapjwS");+  }+   #[tokio::test]   #[ignore] // Manual test invocation   async fn test() -> AnyhowResult<()> {diff --git a/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/script/parse_script_actions_and_xsid_script_path.rs b/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/script/parse_script_actions_and_xsid_script_path.rs--- a/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/script/parse_script_actions_and_xsid_script_path.rs+++ b/crates/api_clients/grok_consumer_client/src/requests/index_page/parsers/script/parse_script_actions_and_xsid_script_path.rs@@ -11,6 +11,9 @@ static ACTIONS_REGEX : Lazy<Regex> = Lazy::new(|| {  /// 2025-10-01: "(static/chunks/[^"]+\.js)"[^}]*?a\(880932\)" /// 2025-12-01: "(static/chunks/[^"]+\.js)"[^}]*?\(880932\)"+/// 2026-03: public chunks no longer embed `880932` / `anonPrivateKey` markers in the+/// same form; CDN script discovery was fixed separately. Update these markers when+/// a new stable fingerprint is identified. static XSID_SCRIPT_REGEX : Lazy<Regex> = Lazy::new(|| {   Regex::new(r#""(static/chunks/[^"]+\.js)"[^}]*?\(880932\)"#)       .expect("Regex should parse")