{"record":{"id":"4763bc30e09359c2","repo":"tonhowtf/omniget","slug":"could-not-extract-tweet-id","errorCode":null,"errorMessage":"Could not extract tweet ID","messagePattern":"Could not extract tweet ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/platforms/twitter.rs","lineNumber":900,"sourceCode":"    async fn fallback_ytdlp(&self, url: &str) -> anyhow::Result<MediaInfo> {\n        let ytdlp_path = crate::core::ytdlp::ensure_ytdlp().await?;\n        let mut extra_flags = vec![\n            \"--referer\".to_string(),\n            \"https://x.com/\".to_string(),\n            \"--add-headers\".to_string(),\n            \"Referer:https://x.com/\".to_string(),\n        ];\n        if let Some(cookie) = Self::auth_cookie_string() {\n            extra_flags.push(\"--add-headers\".to_string());\n            extra_flags.push(format!(\"Cookie:{}\", cookie));\n        }\n        let json = crate::core::ytdlp::get_video_info(&ytdlp_path, url, &extra_flags).await?;\n        crate::platforms::generic_ytdlp::GenericYtdlpDownloader::parse_video_info(&json)\n    }\n\n    async fn native_get_media_info(&self, url: &str) -> anyhow::Result<MediaInfo> {\n        let tweet_id =\n            Self::extract_tweet_id(url).ok_or_else(|| anyhow!(\"Could not extract tweet ID\"))?;\n        tracing::debug!(\n            \"[twitter] native_get_media_info tweet_id={} url={}\",\n            tweet_id,\n            url\n        );\n\n        let filename_base = format!(\"twitter_{}\", tweet_id);\n\n        let media_items = match self.try_graphql(&tweet_id).await {\n            Ok(items) => items,\n            Err(graphql_err) => {\n                tracing::warn!(\n                    \"[twitter] graphql lookup failed for tweet_id={}: {}\",\n                    tweet_id,\n                    graphql_err\n                );\n                match self.request_syndication(&tweet_id).await {\n                    Ok(syndication) => match Self::extract_syndication_media(&syndication) {","sourceCodeStart":882,"sourceCodeEnd":918,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/platforms/twitter.rs#L882-L918","documentation":"native_get_media_info calls Self::extract_tweet_id(url), which parses the tweet ID out of the URL; it returns Option and this error is raised (via ok_or_else) when the result is None. It means the URL was accepted by can_handle (a twitter.com/x.com/vxtwitter.com/fixvx.com host) but its path does not contain a recognizable numeric tweet ID.","triggerScenarios":"Calling get_media_info with a Twitter-domain URL that has no /status/<digits> segment — e.g. profile URLs (https://x.com/user), status with non-numeric or truncated ID, URLs like https://x.com/user/status (missing ID), localized/redirect paths, or alternate front-end hosts (vxtwitter/fixvx) with rewritten paths.","commonSituations":"Users pasting profile or home-timeline links instead of a tweet link; URL shortener output that hasn't been resolved; malformed copy-paste that truncates the ID; code feeding can_handle-approved URLs without validating the status path; regex in extract_tweet_id lagging behind new URL shapes.","solutions":["Ensure the URL contains /status/<numeric-id> (e.g. https://x.com/<user>/status/1234567890123456789).","Resolve any shortener/redirect links to the final x.com or twitter.com URL before calling get_media_info.","Validate the URL with a regex like /status/(\\d+) before invoking the downloader to give users a clearer message.","If the URL uses vxtwitter/fixvx rewriting, confirm the path still preserves the status/ID segment.","Update extract_tweet_id if Twitter introduced a new valid URL format it no longer matches."],"exampleFix":"// before\nlet url = \"https://x.com/someuser\"; // no status path\ndownloader.get_media_info(url).await?;\n// after\nlet re = regex::Regex::new(r\"/status/(\\d+)\").unwrap();\nif !re.is_match(&url) {\n    anyhow::bail!(\"not a tweet permalink: {}\", url);\n}\nlet info = downloader.get_media_info(url).await?;","handlingStrategy":"validation","validationCode":"static TWEET_ID_RE: once_cell::sync::Lazy<regex::Regex> =\n    once_cell::sync::Lazy::new(|| regex::Regex::new(r\"/status/(\\d+)\").unwrap());\n\nfn is_tweet_permalink(url: &str) -> bool {\n    TWEET_ID_RE.is_match(url)\n}\n// call is_tweet_permalink(url) before get_media_info","typeGuard":"fn is_tweet_permalink(url: &str) -> bool {\n    regex::Regex::new(r\"https?://(www\\.)?(x|twitter|vxtwitter|fixvx)\\.com/\\S+/status/\\d+\")\n        .map(|re| re.is_match(url))\n        .unwrap_or(false)\n}","tryCatchPattern":"match downloader.get_media_info(url).await {\n    Err(e) if e.to_string().contains(\"Could not extract tweet ID\") => {\n        eprintln!(\"'{url}' is not a tweet permalink; expected x.com/<user>/status/<id>\");\n    }\n    other => other?,\n}","preventionTips":["Always resolve shortener URLs to their final destination first.","Reject non-permalink Twitter URLs (profiles, search, home) at input validation time.","Require the /status/<numeric-id> segment before passing URLs to the downloader.","Handle vxtwitter/fixvx links by normalizing them to x.com permalinks."],"tags":["twitter","url-parsing","invalid-input"],"backgroundTag":"invalid-url-format","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}