use night_fury_core::BrowserSession;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let session = BrowserSession::builder()
.connect_to("ws://127.0.0.1:9222")
.build()
.await?;
session.navigate("https://x.com").await?;
tokio::time::sleep(Duration::from_secs(8)).await;
// Debug 1: List all script tags
let r = session
.eval(
r#"
(() => {
const scripts = Array.from(document.querySelectorAll('script[src]')).map(s => s.src);
return JSON.stringify(scripts.slice(0, 20));
})()
"#,
)
.await?;
println!("Scripts on page: {}", r);
// Debug 2: Try scanning scripts for SearchTimeline
let r2 = session
.eval(
r#"
(async () => {
const scripts = Array.from(document.querySelectorAll('script[src]')).map(s => s.src);
const results = [];
for (const url of scripts) {
try {
const text = await (await fetch(url)).text();
const re = /queryId:"([A-Za-z0-9_-]+)"[^{}]{0,200}operationName:"SearchTimeline"/;
const match = text.match(re);
if (match) {
results.push({ url: url.split('/').pop(), queryId: match[1] });
}
} catch(e) {
results.push({ url: url.split('/').pop(), error: e.message });
}
}
return JSON.stringify({ scriptCount: scripts.length, results });
})()
"#,
)
.await?;
println!("Bundle scan: {}", r2);
// Debug 3: Dump a search result structure
let dump = session.eval(r#"
(async () => {
const ct0 = document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1];
const bearer = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs=1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
const variables = {"rawQuery":"rust","count":2,"querySource":"typed_query","product":"Latest"};
const features = {"rweb_tipjar_consumption_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"communities_web_enable_tweet_community_results_fetch":true,"c9s_tweet_anatomy_moderator_badge_enabled":true,"articles_preview_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":true,"tweet_awards_web_tipping_enabled":false,"creator_subscriptions_quote_tweet_preview_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"rweb_video_timestamps_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_enhance_cards_enabled":false};
const scripts = Array.from(document.querySelectorAll('script[src]')).map(s => s.src);
let qid = null;
for (const url of scripts) {
try {
const text = await (await fetch(url)).text();
const re = /queryId:"([A-Za-z0-9_-]+)"[^{}]{0,200}operationName:"SearchTimeline"/;
const match = text.match(re);
if (match) { qid = match[1]; break; }
} catch {}
}
const resp = await fetch('/i/api/graphql/' + qid + '/SearchTimeline', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + bearer, 'X-Csrf-Token': ct0, 'X-Twitter-Auth-Type': 'OAuth2Session', 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ variables, features })
});
const data = await resp.json();
const entries = data?.data?.search_by_raw_query?.search_timeline?.timeline?.instructions?.[0]?.entries || [];
const first = entries.find(e => e.entryId?.startsWith('tweet-'));
if (!first) return 'no tweet entry found';
const result = first?.content?.itemContent?.tweet_results?.result;
return JSON.stringify({ keys: Object.keys(result || {}), core: result?.core, typename: result?.__typename }, null, 2);
})()
"#).await?;
println!("Tweet structure: {}", dump);
// Debug 3 original:
let ct0_js = r#"document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1] || null"#;
let ct0_val = session.eval(ct0_js).await?;
println!("ct0 from document.cookie: {}", ct0_val);
let cookies = session.get_cookies().await?;
let ct0_cookie = cookies
.iter()
.find(|c| c.get("name").and_then(|v| v.as_str()) == Some("ct0"));
println!(
"ct0 from get_cookies: {:?}",
ct0_cookie.map(|c| c.get("value"))
);
// Try the actual query with the discovered queryId
let qid = r2
.as_str()
.and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok())
.and_then(|v| v["results"][0]["queryId"].as_str().map(|s| s.to_string()));
if let Some(qid) = qid {
println!("Using queryId: {}", qid);
// Build proper variables and features
let test = session.eval(&format!(r#"
(async () => {{
const ct0 = document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1];
const variables = {{"rawQuery":"rust","count":5,"querySource":"typed_query","product":"Latest"}};
const features = {{"rweb_tipjar_consumption_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"communities_web_enable_tweet_community_results_fetch":true,"c9s_tweet_anatomy_moderator_badge_enabled":true,"articles_preview_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":true,"tweet_awards_web_tipping_enabled":false,"creator_subscriptions_quote_tweet_preview_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"rweb_video_timestamps_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_enhance_cards_enabled":false}};
const bearer = decodeURIComponent('AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA');
const headers = {{
'Authorization': 'Bearer ' + bearer,
'X-Csrf-Token': ct0,
'X-Twitter-Auth-Type': 'OAuth2Session',
'X-Twitter-Active-User': 'yes',
'Content-Type': 'application/json'
}};
// Try GET first
const urlGet = '/i/api/graphql/{}/SearchTimeline?variables=' + encodeURIComponent(JSON.stringify(variables)) + '&features=' + encodeURIComponent(JSON.stringify(features));
const respGet = await fetch(urlGet, {{ headers, credentials: 'include' }});
const bodyGet = await respGet.text();
// Try POST
const urlPost = '/i/api/graphql/{}/SearchTimeline';
const respPost = await fetch(urlPost, {{
method: 'POST',
headers,
credentials: 'include',
body: JSON.stringify({{ variables, features }})
}});
const bodyPost = await respPost.text();
return JSON.stringify({{
get: {{ status: respGet.status, body: bodyGet.substring(0, 300) }},
post: {{ status: respPost.status, body: bodyPost.substring(0, 300) }}
}});
}})()
"#, qid, qid)).await?;
println!("Search API test: {}", test);
}
Ok(())
}