Fix old Reddit comment discovery
This commit is contained in:
parent
1a9ab6ebdf
commit
803df7feee
5 changed files with 50 additions and 12 deletions
|
|
@ -78,7 +78,7 @@ export class DefinitionEngine {
|
||||||
const matches = Array.from(post.querySelectorAll(selector));
|
const matches = Array.from(post.querySelectorAll(selector));
|
||||||
const contents = post.matches(selector) ? [post, ...matches] : matches;
|
const contents = post.matches(selector) ? [post, ...matches] : matches;
|
||||||
for (const content of contents) {
|
for (const content of contents) {
|
||||||
const text = visibleText(content, this.definition.post.excludedSelectors ?? []);
|
const text = visibleText(content, this.definition.post.excludedSelectors ?? [], post);
|
||||||
if (text) return text;
|
if (text) return text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -140,13 +140,19 @@ function wildcardMatches(pattern: string, value: string): boolean {
|
||||||
|
|
||||||
function escapeRegex(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }
|
function escapeRegex(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }
|
||||||
|
|
||||||
function visibleText(element: Element, excludedSelectors: string[]): string {
|
function visibleText(element: Element, excludedSelectors: string[], postBoundary: Element): string {
|
||||||
const chunks: string[] = [];
|
const chunks: string[] = [];
|
||||||
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
||||||
let node: Node | null;
|
let node: Node | null;
|
||||||
while ((node = walker.nextNode())) {
|
while ((node = walker.nextNode())) {
|
||||||
const parent = node.parentElement;
|
const parent = node.parentElement;
|
||||||
if (!parent || excludedSelectors.some((selector) => parent.closest(selector))) continue;
|
// An excluded container only applies when it is inside this post's
|
||||||
|
// boundary. Nested Reddit comments are themselves inside an ancestor
|
||||||
|
// `.child` container, which must not exclude the comment's own text.
|
||||||
|
if (!parent || excludedSelectors.some((selector) => {
|
||||||
|
const excluded = parent.closest(selector);
|
||||||
|
return excluded !== null && postBoundary.contains(excluded);
|
||||||
|
})) continue;
|
||||||
if (isVisible(parent, element)) chunks.push(node.textContent ?? "");
|
if (isVisible(parent, element)) chunks.push(node.textContent ?? "");
|
||||||
}
|
}
|
||||||
return normalizeText(chunks.join(" "));
|
return normalizeText(chunks.join(" "));
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,12 @@
|
||||||
"https://old.reddit.com/*"
|
"https://old.reddit.com/*"
|
||||||
],
|
],
|
||||||
"requiredSelectors": [
|
"requiredSelectors": [
|
||||||
"#siteTable .thing.link, #siteTable .thing.comment"
|
"#siteTable .thing.link, .commentarea .thing.comment > .entry"
|
||||||
],
|
],
|
||||||
"post": {
|
"post": {
|
||||||
"rootSelectors": [
|
"rootSelectors": [
|
||||||
"#siteTable > .thing.link",
|
"#siteTable > .thing.link",
|
||||||
"#siteTable .thing.comment"
|
".commentarea .thing.comment > .entry"
|
||||||
],
|
],
|
||||||
"textSelectors": [
|
"textSelectors": [
|
||||||
".md",
|
".md",
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
"id": "reddit-old",
|
"id": "reddit-old",
|
||||||
"name": "Reddit (old interface)",
|
"name": "Reddit (old interface)",
|
||||||
"urlPatterns": ["https://old.reddit.com/*"],
|
"urlPatterns": ["https://old.reddit.com/*"],
|
||||||
"requiredSelectors": ["#siteTable .thing.link, #siteTable .thing.comment"],
|
"requiredSelectors": ["#siteTable .thing.link, .commentarea .thing.comment > .entry"],
|
||||||
"post": {
|
"post": {
|
||||||
"rootSelectors": ["#siteTable > .thing.link", "#siteTable .thing.comment"],
|
"rootSelectors": ["#siteTable > .thing.link", ".commentarea .thing.comment > .entry"],
|
||||||
"textSelectors": [".md", ".title"],
|
"textSelectors": [".md", ".title"],
|
||||||
"excludedSelectors": [".expando", ".child"],
|
"excludedSelectors": [".expando", ".child"],
|
||||||
"idAttributes": ["data-fullname"],
|
"idAttributes": ["data-fullname"],
|
||||||
|
|
|
||||||
|
|
@ -95,12 +95,35 @@ describe("DefinitionEngine", () => {
|
||||||
expect(posts.map((post) => post.text)).toEqual(["the main post on the threads detail page", "a reply on the threads detail page"]);
|
expect(posts.map((post) => post.text)).toEqual(["the main post on the threads detail page", "a reply on the threads detail page"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("discovers posts from the old Reddit interface using fullname IDs", () => {
|
it("discovers old Reddit posts and comment entries from their separate page containers", () => {
|
||||||
document.body.innerHTML = readFileSync("tests/fixtures/reddit-old-feed.html", "utf8");
|
document.body.innerHTML = readFileSync("tests/fixtures/reddit-old-feed.html", "utf8");
|
||||||
const reddit = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "reddit-old")!;
|
const reddit = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "reddit-old")!;
|
||||||
const posts = new DefinitionEngine(reddit).discover(document);
|
const posts = new DefinitionEngine(reddit).discover(document);
|
||||||
expect(posts.map((post) => post.id)).toEqual(["reddit-old:t3_abc123", "reddit-old:t1_def456"]);
|
expect(posts.map((post) => post.id)).toEqual([
|
||||||
expect(posts.map((post) => post.text)).toEqual(["the post body with visible text.", "a useful comment with visible text."]);
|
"reddit-old:t3_abc123",
|
||||||
|
"reddit-old:https://old.reddit.com/r/example/comments/abc123/example_post/def456/",
|
||||||
|
"reddit-old:https://old.reddit.com/r/example/comments/abc123/example_post/ghi789/"
|
||||||
|
]);
|
||||||
|
expect(posts.map((post) => post.text)).toEqual([
|
||||||
|
"the post body with visible text.",
|
||||||
|
"a useful comment with visible text.",
|
||||||
|
"a nested reply with its own visible text."
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("discovers old Reddit comment entries appended after observation starts", async () => {
|
||||||
|
document.body.innerHTML = readFileSync("tests/fixtures/reddit-old-feed.html", "utf8");
|
||||||
|
const reddit = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "reddit-old")!;
|
||||||
|
const engine = new DefinitionEngine(reddit);
|
||||||
|
const observed: string[] = [];
|
||||||
|
const stop = engine.observe((posts) => observed.push(...posts.map((post) => post.text)));
|
||||||
|
const comment = document.createElement("div");
|
||||||
|
comment.className = "thing comment";
|
||||||
|
comment.innerHTML = '<div class="entry"><div class="md"><p>A newly loaded reply.</p></div><a class="bylink" href="/r/example/comments/abc123/example_post/jkl012/">permalink</a></div>';
|
||||||
|
document.querySelector(".commentarea .sitetable")?.append(comment);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
stop();
|
||||||
|
expect(observed).toContain("a newly loaded reply.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("discovers X posts using semantic tweet markers and permalink IDs", () => {
|
it("discovers X posts using semantic tweet markers and permalink IDs", () => {
|
||||||
|
|
|
||||||
13
tests/fixtures/reddit-old-feed.html
vendored
13
tests/fixtures/reddit-old-feed.html
vendored
|
|
@ -3,7 +3,16 @@
|
||||||
<p class="title"><a class="title" href="https://old.reddit.com/r/example/comments/abc123/example_post/">A useful Reddit post</a></p>
|
<p class="title"><a class="title" href="https://old.reddit.com/r/example/comments/abc123/example_post/">A useful Reddit post</a></p>
|
||||||
<div class="entry"><div class="expando"><div class="md">Hidden expanded media controls</div></div><div class="md"><p>The post body with visible text.</p></div></div>
|
<div class="entry"><div class="expando"><div class="md">Hidden expanded media controls</div></div><div class="md"><p>The post body with visible text.</p></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="thing comment" data-fullname="t1_def456">
|
</div>
|
||||||
<div class="entry"><div class="md"><p>A useful comment with visible text.</p></div><a class="bylink" href="https://old.reddit.com/r/example/comments/abc123/example_post/def456/">permalink</a></div>
|
<div class="commentarea">
|
||||||
|
<div class="sitetable nestedlisting">
|
||||||
|
<div class="thing comment" data-fullname="t1_def456">
|
||||||
|
<div class="entry"><div class="md"><p>A useful comment with visible text.</p></div><a class="bylink" href="https://old.reddit.com/r/example/comments/abc123/example_post/def456/">permalink</a></div>
|
||||||
|
<div class="child">
|
||||||
|
<div class="thing comment" data-fullname="t1_ghi789">
|
||||||
|
<div class="entry"><div class="md"><p>A nested reply with its own visible text.</p></div><a class="bylink" href="https://old.reddit.com/r/example/comments/abc123/example_post/ghi789/">permalink</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue