mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-28 19:31:12 +00:00
fix(web-readability): resolve relative article links
This commit is contained in:
@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Web Readability relative links:** seed parsed documents with the request URL so article links resolve correctly while removing the plugin's duplicate lazy-loader facade. (#106860)
|
||||
- **Browser auto-routing:** fall back to the Gateway host when an implicitly selected browser node reports that its control host is unreachable, while preserving explicit node pins and ambiguous action failures.
|
||||
- **Discord voice participant context:** maintain the live Gateway voice-state roster and include current channel participants in authorized voice agent turns so agents can answer who is present.
|
||||
- **OC Path JSONC insertion:** patch object and array insertions through `jsonc-parser` so comments, trailing commas, and CRLF formatting survive. (#106847)
|
||||
|
||||
@@ -20,6 +20,7 @@ const SAMPLE_HTML = `<!doctype html>
|
||||
<h1>Example Article</h1>
|
||||
<p>Main content starts here with enough words to satisfy readability.</p>
|
||||
<p>Second paragraph for a bit more signal.</p>
|
||||
<p><a href="../next">Continue reading</a></p>
|
||||
</article>
|
||||
</main>
|
||||
<footer>Footer text</footer>
|
||||
@@ -59,6 +60,7 @@ describe("web readability extractor", () => {
|
||||
});
|
||||
const extracted = requireReadabilityResult(result);
|
||||
expect(extracted.text).toContain("Main content starts here");
|
||||
expect(extracted.text).toContain("[Continue reading](https://example.com/next)");
|
||||
expect(extracted.title).toBe("Example Article");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Web Readability plugin module implements web content extractor behavior.
|
||||
import { createLazyRuntimeSurface } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import type {
|
||||
WebContentExtractionRequest,
|
||||
WebContentExtractionResult,
|
||||
@@ -30,65 +31,20 @@ const HTML_VOID_TAGS = new Set([
|
||||
"wbr",
|
||||
]);
|
||||
|
||||
type ParsedHtml = {
|
||||
document: Document;
|
||||
};
|
||||
|
||||
type ParseHtml = (html: string) => ParsedHtml;
|
||||
|
||||
type ReadabilityResult = {
|
||||
content?: string;
|
||||
textContent?: string | null;
|
||||
title?: string | null;
|
||||
};
|
||||
|
||||
type ReadabilityInstance = {
|
||||
parse(): ReadabilityResult | null;
|
||||
};
|
||||
|
||||
type ReadabilityConstructor = new (
|
||||
document: Document,
|
||||
options: { charThreshold: number },
|
||||
) => ReadabilityInstance;
|
||||
|
||||
type ReadabilityModule = {
|
||||
Readability: ReadabilityConstructor;
|
||||
};
|
||||
|
||||
type LinkedomModule = {
|
||||
parseHTML: ParseHtml;
|
||||
};
|
||||
|
||||
const READABILITY_MODULE = "@mozilla/readability";
|
||||
const LINKEDOM_MODULE = "linkedom";
|
||||
|
||||
let readabilityDepsPromise:
|
||||
| Promise<{
|
||||
Readability: ReadabilityConstructor;
|
||||
parseHTML: ParseHtml;
|
||||
}>
|
||||
| undefined;
|
||||
|
||||
async function loadReadabilityDeps(): Promise<{
|
||||
Readability: ReadabilityConstructor;
|
||||
parseHTML: ParseHtml;
|
||||
}> {
|
||||
if (!readabilityDepsPromise) {
|
||||
readabilityDepsPromise = Promise.all([
|
||||
import(READABILITY_MODULE) as Promise<ReadabilityModule>,
|
||||
import(LINKEDOM_MODULE) as Promise<LinkedomModule>,
|
||||
]).then(([readability, linkedom]) => ({
|
||||
Readability: readability.Readability,
|
||||
parseHTML: linkedom.parseHTML,
|
||||
}));
|
||||
}
|
||||
try {
|
||||
return await readabilityDepsPromise;
|
||||
} catch (error) {
|
||||
readabilityDepsPromise = undefined;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const loadReadabilityDeps = createLazyRuntimeSurface(
|
||||
() =>
|
||||
Promise.all([
|
||||
import(READABILITY_MODULE) as Promise<typeof import("@mozilla/readability")>,
|
||||
import(LINKEDOM_MODULE) as Promise<typeof import("linkedom")>,
|
||||
]),
|
||||
([readability, linkedom]) => ({
|
||||
Readability: readability.Readability,
|
||||
parseHTML: linkedom.parseHTML,
|
||||
}),
|
||||
);
|
||||
|
||||
function exceedsEstimatedHtmlNestingDepth(html: string, maxDepth: number): boolean {
|
||||
let depth = 0;
|
||||
@@ -175,12 +131,7 @@ async function extractWithReadability(
|
||||
}
|
||||
try {
|
||||
const { Readability, parseHTML } = await loadReadabilityDeps();
|
||||
const { document } = parseHTML(cleanHtml);
|
||||
try {
|
||||
(document as { baseURI?: string }).baseURI = request.url;
|
||||
} catch {
|
||||
// Best-effort base URI for relative links.
|
||||
}
|
||||
const { document } = parseHTML(cleanHtml, { location: { href: request.url } });
|
||||
const reader = new Readability(document, { charThreshold: 0 });
|
||||
const parsed = reader.parse();
|
||||
if (!parsed?.content) {
|
||||
|
||||
Reference in New Issue
Block a user