{"record":{"id":"ec26d24db1202f9e","repo":"DIYgod/RSSHub","slug":"unable-to-locate-obsidian-community-search-api-con","errorCode":null,"errorMessage":"Unable to locate Obsidian community search API config","messagePattern":"Unable to locate Obsidian community search API config","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/obsidian/utils.ts","lineNumber":76,"sourceCode":"export function getTitle(path: string): string {\n    const match = path.match(titleRegex);\n    return match ? match[1] : '';\n}\n\nfunction getSearchPageUrl(type: CommunityEntryType) {\n    const url = new URL(searchPageBaseUrl);\n    url.searchParams.set('type', type);\n    url.searchParams.set('sort', 'created');\n\n    return url.href;\n}\n\nasync function getSearchConfig(pageUrl: string): Promise<CommunitySearchConfig> {\n    const html = await ofetch<string>(pageUrl);\n    const match = html.match(/apiKey\\\\\":\\\\\"([^\"\\\\]+)\\\\\"[\\s\\S]*?url\\\\\":\\\\\"([^\"\\\\]+)\\\\\"/) ?? html.match(/\"apiKey\":\"([^\"]+)\"[\\s\\S]*?\"url\":\"([^\"]+)\"/);\n\n    if (!match) {\n        throw new Error('Unable to locate Obsidian community search API config');\n    }\n\n    return {\n        apiKey: match[1],\n        url: match[2].replaceAll(String.raw`\\/`, '/'),\n    };\n}\n\nfunction buildItem(document: CommunitySearchHit['document']): DataItem {\n    return {\n        title: document.name,\n        description: document.short_desc,\n        link: `https://community.obsidian.md/${document.type}s/${document.slug}`,\n        guid: `${document.type}:${document.id}`,\n        pubDate: document.github_created_at ? parseDate(document.github_created_at) : undefined,\n        updated: document.latest_release_at ? parseDate(document.latest_release_at) : document.github_updated_at ? parseDate(document.github_updated_at) : undefined,\n        author: document.authors?.join(', '),\n        category: document.tags,","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/obsidian/utils.ts#L58-L94","documentation":"Thrown when the regex extraction of the Obsidian community search API configuration fails. The `getSearchConfig` function fetches the HTML of `https://community.obsidian.md/search` and attempts to match two regex patterns: one for escaped JSON (`apiKey\\\":\\\"`) and one for unescaped JSON (`\"apiKey\":\"`). If neither matches, the apiKey and Typesense URL cannot be extracted and the route cannot proceed.","triggerScenarios":"The Obsidian community forum (Discourse) changed its HTML structure, removed the embedded search config, or changed the serialization format. The page loaded but the JSON blob containing apiKey/url was not present. A CDN or error page was returned instead of the real search page.","commonSituations":"Obsidian updated their Discourse instance and changed how the Typesense search config is embedded. The community page returned a Cloudflare challenge page instead of real HTML. The regex patterns are too rigid for the current JSON escaping variant.","solutions":["Open `https://community.obsidian.md/search?type=plugin&sort=created` in a browser and view the page source to find how the apiKey and search URL are currently embedded.","Update the regex patterns on line 73 of `lib/routes/obsidian/utils.ts` to match the new HTML format.","If the config is now loaded via a separate JS file or API call, refactor `getSearchConfig` to fetch from the new source.","Check if the page returns a Cloudflare challenge and consider whether Puppeteer or a different fetch strategy is needed."],"exampleFix":"// before\nconst match = html.match(/apiKey\\\\\":\\\\\"([^\"\\\\]+)\\\\\"[\\s\\S]*?url\\\\\":\\\\\"([^\"\\\\]+)\\\\\"/) ?? html.match(/\"apiKey\":\"([^\"]+)\"[\\s\\S]*?\"url\":\"([^\"]+)\"/);\n\n// after — broader, order-independent matching\nconst apiKeyMatch = html.match(/\"?apiKey\"?\\s*[:=]\\s*\"([^\"]+)\"/);\nconst urlMatch = html.match(/\"?url\"?\\s*[:=]\\s*\"(https?:\\\\/\\\\/[^\"]+)\"/);\nif (!apiKeyMatch || !urlMatch) {\n    throw new Error('Unable to locate Obsidian community search API config');\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check: fetch the page and verify config is extractable before proceeding\nconst html = await ofetch<string>(pageUrl);\nconst hasConfig = /apiKey/.test(html) && /url/.test(html);\nif (!hasConfig) {\n    throw new Error('Obsidian community page does not contain expected search config markers');\n}","typeGuard":null,"tryCatchPattern":"try {\n    const searchConfig = await getSearchConfig(pageUrl);\n    // proceed with searchConfig...\n} catch (e) {\n    if (e instanceof Error && e.message.includes('Unable to locate')) {\n        logger.error('Obsidian community HTML structure may have changed');\n        // Optionally fall back to a known Typesense endpoint\n    }\n    throw e;\n}","preventionTips":["Make regex patterns more flexible to handle different JSON escaping variants.","Test the regex against the live HTML periodically.","Consider extracting config from a script tag by parsing it as JSON rather than regex."],"tags":["scraper","obsidian","regex","html-parsing","fragile-selector"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}