{"record":{"id":"c919d34798837c98","repo":"Jackett/Jackett","slug":"download-links-not-found-make-sure-you-can-downlo","errorCode":null,"errorMessage":"Download links not found. Make sure you can download from the website.","messagePattern":"Download links not found\\. Make sure you can download from the website\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/Jackett.Common/Indexers/Definitions/FunFile.cs","lineNumber":209,"sourceCode":"            if (results.IsRedirect) // re-login\n            {\n                await ApplyConfiguration(null);\n                results = await RequestWithCookiesAndRetryAsync(searchUrl);\n            }\n\n            char[] delimiters = { ',', ' ', '/', ')', '(', '.', ';', '[', ']', '\"', '|', ':' };\n\n            try\n            {\n                var parser = new HtmlParser();\n                using var dom = parser.ParseDocument(results.ContentString);\n\n                var rows = dom.QuerySelectorAll(\"table.mainframe table[cellpadding=\\\"2\\\"] > tbody > tr:has(td.row3)\");\n                foreach (var row in rows)\n                {\n                    var qDownloadLink = row.QuerySelector(\"a[href^=\\\"download.php\\\"]\");\n                    if (qDownloadLink == null)\n                        throw new Exception(\"Download links not found. Make sure you can download from the website.\");\n\n                    var link = new Uri(SiteLink + qDownloadLink.GetAttribute(\"href\"));\n\n                    var qDetailsLink = row.QuerySelector(\"a[href^=\\\"details.php?id=\\\"]\");\n                    var title = qDetailsLink?.GetAttribute(\"title\")?.Trim();\n                    var details = new Uri(SiteLink + qDetailsLink?.GetAttribute(\"href\")?.Replace(\"&hit=1\", \"\"));\n\n                    var categoryLink = row.QuerySelector(\"a[href^=\\\"browse.php?cat=\\\"]\")?.GetAttribute(\"href\");\n                    var cat = ParseUtil.GetArgumentFromQueryString(categoryLink, \"cat\");\n\n                    var seeders = ParseUtil.CoerceInt(row.Children[9].TextContent);\n                    var leechers = ParseUtil.CoerceInt(row.Children[10].TextContent);\n\n                    var release = new ReleaseInfo\n                    {\n                        Guid = link,\n                        Link = link,\n                        Details = details,","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/Jackett/Jackett/blob/adff1941476d8057a430cf55360356c1d23e971e/src/Jackett.Common/Indexers/Definitions/FunFile.cs#L191-L227","documentation":"Thrown during FunFile.PerformQuery while iterating search-result rows. Each row (matched by the selector 'table.mainframe table[cellpadding=\"2\"] > tbody > tr:has(td.row3)') is expected to contain an anchor with an href starting with 'download.php'. If that anchor is missing on any row, the indexer aborts the entire parse with this message. This is a scraper-fragility error: it fires when the site's HTML markup does not match the indexer's assumptions.","triggerScenarios":"A matched table row has no download link anchor — because the torrent was deleted, the user lacks download permission for that torrent, or FunFile changed its table/column HTML layout so rows match but links moved or were renamed.","commonSituations":"FunFile pushes a UI redesign that renames download.php links or wraps them in JavaScript; a low-seed/dead torrent row renders without a download button; the user's account class lacks download rights for certain categories.","solutions":["Open the FunFile search page in a browser and confirm download links are present on result rows.","Check if the FunFile site was recently redesigned; if so, the indexer selector needs updating.","Ensure the configured account has download permissions for all categories being searched.","Skip rows missing the link instead of throwing, so a single bad row does not kill the whole query.","Report the HTML change to the Jackett maintainers with a sample of the page source."],"exampleFix":"// before\nvar qDownloadLink = row.QuerySelector(\"a[href^=\\\"download.php\\\"]\");\nif (qDownloadLink == null)\n    throw new Exception(\"Download links not found. Make sure you can download from the website.\");\n// after - skip rows without a link\nvar qDownloadLink = row.QuerySelector(\"a[href^=\\\"download.php\\\"]\");\nif (qDownloadLink == null)\n    continue;","handlingStrategy":"try-catch","validationCode":"// Before parsing, confirm results page actually has download rows\nvar rows = dom.QuerySelectorAll(\"table.mainframe table[cellpadding=\\\"2\\\"] > tbody > tr:has(td.row3)\");\nif (!rows.Any(r => r.QuerySelector(\"a[href^=\\\"download.php\\\"]\") != null))\n    logger.Warn(\"No downloadable rows found — site layout may have changed.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    var releases = await indexer.PerformQuery(query);\n}\ncatch (Exception ex) when (ex.Message.Contains(\"Download links not found\"))\n{\n    // Scraper mismatch — FunFile HTML changed or account lacks download rights\n    logger.Error(\"FunFile parse failure: {Message}\", ex.Message);\n    // Do not retry blindly; the selector needs updating\n}","preventionTips":["Confirm the FunFile account has download permissions for searched categories.","Periodically verify FunFile has not redesigned its torrent table.","Report persistent parse failures to Jackett maintainers with page source.","Do not retry on this error — it indicates a structural mismatch, not a transient issue."],"tags":["parsing","html-scraping","funfile","scraper-fragility"],"backgroundTag":null,"analyzedSha":"adff1941476d8057a430cf55360356c1d23e971e","analyzedAt":"2026-08-13T15:06:32.872Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}