tonhowtf/omniget · error · anyhow::Error
No photo URLs found in HTML
Error message
No photo URLs found in HTML
What it means
Thrown by TwitterDownloader::request_html_media when the tweet page was fetched successfully (2xx) but extract_html_photo_items found zero pbs.twimg.com/media URLs matching its regex. The HTML response simply did not contain recognizable photo media.
Solutions
- Confirm the tweet actually contains photos (this fallback only extracts photo URLs, not videos).
- Check the saved HTML (log a snippet) for pbs.twimg.com/media occurrences; if the markup changed, update the regex in extract_html_photo_items.
- Use the GraphQL or syndication strategies instead, or a yt-dlp fallback for video tweets.
- Ensure the auth cookie is set so the page is not a login wall.
Example fix
// before
let items = Self::extract_html_photo_items(&html);
if items.is_empty() {
return Err(anyhow!("No photo URLs found in HTML"));
}
// after: include diagnostics in the error
let items = Self::extract_html_photo_items(&html);
if items.is_empty() {
if html.contains("pbs.twimg.com") {
return Err(anyhow!("No photo URLs found in HTML (found twimg URLs but regex missed them)"));
}
return Err(anyhow!("No photo URLs found in HTML (page likely login-walled or video-only)"));
} Defensive patterns
Strategy: validation
Validate before calling
// cheap pre-check that the page actually contains photo media markup
let html = fetch_page(&url).await?;
if !html.contains("pbs.twimg.com/media") {
eprintln!("tweet page has no photo media markup — photos-only fallback will fail");
} Type guard
fn has_photo_markup(html: &str) -> bool {
html.contains("pbs.twimg.com/media")
} Try / catch
match request_html_media(&url).await {
Err(e) if e.to_string().contains("No photo URLs found") => {
// tweet is likely video-only or login-walled; switch strategy
let info = ytdlp_fallback(&url).await?;
Ok(info)
}
other => other,
} Prevention
- Only use the HTML-photo fallback for tweets known to contain photos.
- Check for a login wall (sign-in markers in HTML) before concluding there is no media.
- Log a snippet of the HTML when extraction fails to spot markup changes early.
- Keep the pbs.twimg.com regex updated against X markup changes.
- Prefer GraphQL/syndication strategies; treat HTML as last resort only.
When it happens
Trigger: Calling get_media_info on a tweet whose page HTML contains no pbs.twimg.com/media URLs: the tweet has video/GIF only (no photos), the tweet is a login wall rendering no media markup, or X changed its HTML structure so the regex no longer matches.
Common situations: Trying to download video-only tweets via the photo-oriented HTML scraper; X A/B-testing new markup; heavily JavaScript-rendered pages returning an app shell without media; login-required responses returning a 200 page with a sign-in wall.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
- animacao do X nao encontrada na pagina
- No photo URLs found in HTML
- indices de assinatura nao encontrados
- frame da animacao incompleto
- chave de verificacao do X nao encontrada
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/43238454623ab531.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/platforms/twitter/mod.rs:1053
async fn request_html_media(&self, url: &str) -> anyhow::Result<Vec<serde_json::Value>> {
let mut request = self
.client
.get(url)
.header("User-Agent", USER_AGENT)
.header("Accept-Language", "en")
.header("Referer", "https://x.com/");
if let Some(cookie) = Self::auth_cookie_string() {
request = request.header("Cookie", cookie);
}
let response = request.send().await?;
if !response.status().is_success() {
return Err(anyhow!("HTML request returned HTTP {}", response.status()));
}
let html = response.text().await?;
let items = Self::extract_html_photo_items(&html);
if items.is_empty() {
return Err(anyhow!("No photo URLs found in HTML"));
}
tracing::debug!("[twitter] html extracted {} photo entries", items.len());
Ok(items
.into_iter()
.map(|item| {
serde_json::json!({
"type": "photo",
"media_url_https": item.url,
})
})
.collect())
}
async fn try_graphql(&self, tweet_id: &str) -> anyhow::Result<Vec<serde_json::Value>> {
let token = self.get_guest_token(false).await?;
match self.request_tweet(tweet_id, &token).await {
Ok(json) => Self::extract_graphql_media(&json, tweet_id),View on GitHub (pinned to 8600b91f42)