{"record":{"id":"cb7808f3114821fa","repo":"iawia002/lux","slug":"unable-to-get-video-id","errorCode":null,"errorMessage":"unable to get video ID","messagePattern":"unable to get video ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"extractors/douyin/douyin.go","lineNumber":59,"sourceCode":"\t\tif err != nil {\n\t\t\treturn nil, errors.WithStack(err)\n\t\t}\n\t\tc := http.Client{\n\t\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\t\treturn http.ErrUseLastResponse\n\t\t\t},\n\t\t}\n\t\tresp, err := c.Do(req)\n\t\tif err != nil {\n\t\t\treturn nil, errors.WithStack(err)\n\t\t}\n\t\tdefer resp.Body.Close() // nolint\n\t\turl = resp.Header.Get(\"location\")\n\t}\n\n\titemIds := utils.MatchOneOf(url, `/video/(\\d+)`)\n\tif len(itemIds) == 0 {\n\t\treturn nil, errors.New(\"unable to get video ID\")\n\t}\n\tif itemIds == nil || len(itemIds) < 2 {\n\t\treturn nil, errors.WithStack(extractors.ErrURLParseFailed)\n\t}\n\titemId := itemIds[len(itemIds)-1]\n\n\t// dynamic generate cookie\n\tcookie, err := createCookie()\n\tif err != nil {\n\t\treturn nil, errors.WithStack(err)\n\t}\n\n\tapi := \"https://www.douyin.com/aweme/v1/web/aweme/detail/?aweme_id=\" + itemId\n\t// parse api query params string\n\tquery, err := netURL.Parse(api)\n\tif err != nil {\n\t\treturn nil, errors.WithStack(extractors.ErrURLQueryParamsParseFailed)\n\t}","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/iawia002/lux/blob/dd00f6d258d80b6684a0b9402d7124e5c18ef42f/extractors/douyin/douyin.go#L41-L77","documentation":"douyin.Extract resolves v.douyin.com share redirects manually, then requires /video/(\\d+) in the resulting URL. This error means the resolved URL has no video path: the link points at something other than a standard video page. The subsequent ErrURLParseFailed check in the same block is effectively dead code — only this earlier branch fires.","triggerScenarios":"Share links that resolve to /note/ (image slideshows), user profiles or live pages; redirect chains where the location header was empty so the URL never gained the /video/ segment; douyin changing path formats.","commonSituations":"Copying app share text for photo posts; proxies altering or stripping redirects; douyin URL-scheme evolution.","solutions":["Open the share link in a browser and pass the final www.douyin.com/video/<digits> URL instead.","For image/note posts this extractor does not apply — use a tool that supports douyin images.","Log the resolved URL after the redirect loop; if it is still v.douyin.com, the manual redirect following needs another hop.","If douyin introduced new path shapes, extend the /video/(\\d+) pattern."],"exampleFix":"// before\nitemIds := utils.MatchOneOf(url, `/video/(\\d+)`)\n// after — surface what the redirect produced before failing\nfmt.Fprintln(os.Stderr, \"douyin: resolved to\", url)\nitemIds := utils.MatchOneOf(url, `/(video|note)/(\\d+)`)","handlingStrategy":"validation","validationCode":"u := strings.TrimSpace(rawURL)\nif strings.Contains(u, \"v.douyin.com\") {\n\tu = resolveShareLink(u) // follow the redirect yourself first\n}\nif utils.MatchOneOf(u, `/video/(\\d+)`) == nil {\n\treturn fmt.Errorf(\"not a douyin video link (resolved to %s)\", u)\n}","typeGuard":"func isDouyinVideoURL(u string) bool {\n\treturn utils.MatchOneOf(u, `/video/(\\d+)`) != nil\n}","tryCatchPattern":"If caught, log the resolved URL alongside the error — it shows which non-video page (note/profile/live) the share link actually pointed to, which is the actionable datum.","preventionTips":["Expand share links to their final www.douyin.com form before extraction","Keep image/note posts out of video-extraction pipelines","Log the post-redirect URL for every douyin job so misrouted links are visible"],"tags":["douyin","redirect","url-validation","share-link"],"backgroundTag":null,"analyzedSha":"dd00f6d258d80b6684a0b9402d7124e5c18ef42f","analyzedAt":"2026-08-15T16:57:31.080Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}