Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
node_modules/
dist/behaviors.js
dist
145 changes: 101 additions & 44 deletions src/autoscroll.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import { Behavior } from "./lib/behavior";
import { sleep, waitUnit, xpathNode, isInViewport, waitUntil, behaviorLog, addLink } from "./lib/utils";
import { type AutoFetcher } from "./autofetcher";

import {
sleep,
waitUnit,
xpathNode,
isInViewport,
waitUntil,
behaviorLog,
addLink,
currentlyFetching,
} from "./lib/utils";
import type { AbstractBehavior } from "./lib/behavior";
//import { type AutoFetcher } from "./autofetcher";

// ===========================================================================
export class AutoScroll extends Behavior {
autoFetcher: AutoFetcher;
export class AutoScroll implements AbstractBehavior {
showMoreQuery: string;
state: { segments: number } = { segments: 1};
state: { segments: number } = { segments: 1 };
lastScrollPos: number;
samePosCount: number;

origPath: string;
lastMsg = "";

constructor(autofetcher: AutoFetcher) {
super();

this.autoFetcher = autofetcher;
constructor() {
//super();

this.showMoreQuery = "//*[contains(text(), 'show more') or contains(text(), 'Show more')]";
this.showMoreQuery =
"//*[contains(text(), 'show more') or contains(text(), 'Show more')]";

this.lastScrollPos = -1;
this.samePosCount = 0;
Expand All @@ -29,20 +35,37 @@ export class AutoScroll extends Behavior {

static id = "Autoscroll";

static init() {
return {
state: {},
};
}

static isMatch() {
return true;
}

async awaitPageLoad(_: any) {
return;
}

currScrollPos() {
return Math.round(self.scrollY + self.innerHeight);
}

canScrollMore() {
const scrollElem = self.document.scrollingElement || self.document.body;
return this.currScrollPos() < Math.max(scrollElem.clientHeight, scrollElem.scrollHeight);
return (
this.currScrollPos() <
Math.max(scrollElem.clientHeight, scrollElem.scrollHeight)
);
}

debug(msg: string) {
if (this.lastMsg === msg) {
return;
}
super.debug(msg);
void behaviorLog(msg, "debug");
this.lastMsg = msg;
}

Expand All @@ -51,15 +74,17 @@ export class AutoScroll extends Behavior {
return !!self["getEventListeners"](obj).scroll;
} catch (_) {
// unknown, assume has listeners
this.debug("getEventListeners() not available");
void behaviorLog("getEventListeners() not available", "debug");
return true;
}
}

async shouldScroll() {
if (!this.hasScrollEL(self.window) &&
if (
!this.hasScrollEL(self.window) &&
!this.hasScrollEL(self.document) &&
!this.hasScrollEL(self.document.body)) {
!this.hasScrollEL(self.document.body)
) {
return false;
}

Expand All @@ -69,19 +94,22 @@ export class AutoScroll extends Behavior {
}

const lastScrollHeight = self.document.scrollingElement.scrollHeight;
const numFetching = this.autoFetcher.numFetching;
const numFetching = currentlyFetching();

// scroll to almost end of page
const scrollEnd = (document.scrollingElement.scrollHeight * 0.98) - self.innerHeight;
const scrollEnd =
document.scrollingElement.scrollHeight * 0.98 - self.innerHeight;

window.scrollTo({ top: scrollEnd, left: 0, behavior: "smooth" });

// wait for any updates
await sleep(500);

// scroll height changed, should scroll
if (lastScrollHeight !== self.document.scrollingElement.scrollHeight ||
numFetching < this.autoFetcher.numFetching) {
if (
lastScrollHeight !== self.document.scrollingElement.scrollHeight ||
numFetching < currentlyFetching()
) {
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
return true;
}
Expand All @@ -94,29 +122,42 @@ export class AutoScroll extends Behavior {
return false;
}

if ((self.window.scrollY + self["scrollHeight"]) / self.document.scrollingElement.scrollHeight < 0.90) {
if (
(self.window.scrollY + self["scrollHeight"]) /
self.document.scrollingElement.scrollHeight <
0.9
) {
return false;
}

return true;
}

async*[Symbol.asyncIterator]() {
async *run(ctx) {
const { getState } = ctx.Lib;

if (this.shouldScrollUp()) {
yield* this.scrollUp();
yield* this.scrollUp(ctx);
return;
}

if (await this.shouldScroll()) {
yield* this.scrollDown();
yield* this.scrollDown(ctx);
return;
}

yield this.getState("Skipping autoscroll, page seems to not be responsive to scrolling events");
yield getState(
ctx,
"Skipping autoscroll, page seems to not be responsive to scrolling events",
);
}

async* scrollDown() {
const scrollInc = Math.min(self.document.scrollingElement.clientHeight * 0.10, 30);
async *scrollDown(ctx) {
const { getState } = ctx.Lib;
const scrollInc = Math.min(
self.document.scrollingElement.clientHeight * 0.1,
30,
);
const interval = 75;
let elapsedWait = 0;

Expand All @@ -128,9 +169,12 @@ export class AutoScroll extends Behavior {

while (this.canScrollMore()) {
if (document.location.pathname !== this.origPath) {
behaviorLog("Location Changed, stopping scroll: " +
`${document.location.pathname} != ${this.origPath}`, "info");
addLink(document.location.href);
void behaviorLog(
"Location Changed, stopping scroll: " +
`${document.location.pathname} != ${this.origPath}`,
"info",
);
void addLink(document.location.href);
return;
}

Expand All @@ -146,14 +190,17 @@ export class AutoScroll extends Behavior {
}

if (showMoreElem && isInViewport(showMoreElem)) {
yield this.getState("Clicking 'Show More', awaiting more content");
yield getState(ctx, "Clicking 'Show More', awaiting more content");
showMoreElem["click"]();

await sleep(waitUnit);

await Promise.race([
waitUntil(() => self.document.scrollingElement.scrollHeight > scrollHeight, 500),
sleep(30000)
waitUntil(
() => self.document.scrollingElement.scrollHeight > scrollHeight,
500,
),
sleep(30000),
]);

if (self.document.scrollingElement.scrollHeight === scrollHeight) {
Expand All @@ -163,27 +210,31 @@ export class AutoScroll extends Behavior {
showMoreElem = null;
}

// eslint-disable-next-line
self.scrollBy(scrollOpts as ScrollToOptions);

await sleep(interval);

if (this.state.segments === 1) {
// only print this the first time
yield this.getState(`Scrolling down by ${scrollOpts.top} pixels every ${interval / 1000.0} seconds`);
yield getState(
ctx,
`Scrolling down by ${scrollOpts.top} pixels every ${interval / 1000.0} seconds`,
);
elapsedWait = 2.0;

} else {
const waitSecs = elapsedWait / (this.state.segments - 1);
// only add extra wait if actually changed height
// check for scrolling, but allow for more time for content to appear the longer have already scrolled
this.debug(`Waiting up to ${waitSecs} seconds for more scroll segments`);
void behaviorLog(
`Waiting up to ${waitSecs} seconds for more scroll segments`,
"debug",
);

const startTime = Date.now();

await Promise.race([
waitUntil(() => this.canScrollMore(), interval),
sleep(waitSecs)
sleep(waitSecs),
]);

elapsedWait += (Date.now() - startTime) * 2;
Expand All @@ -203,8 +254,12 @@ export class AutoScroll extends Behavior {
}
}

async* scrollUp() {
const scrollInc = Math.min(self.document.scrollingElement.clientHeight * 0.10, 30);
async *scrollUp(ctx) {
const { getState } = ctx.Lib;
const scrollInc = Math.min(
self.document.scrollingElement.clientHeight * 0.1,
30,
);
const interval = 75;

const scrollOpts = { top: -scrollInc, left: 0, behavior: "auto" };
Expand All @@ -219,20 +274,22 @@ export class AutoScroll extends Behavior {
lastScrollHeight = scrollHeight;
}

// eslint-disable-next-line
self.scrollBy(scrollOpts as ScrollToOptions);

await sleep(interval);

if (this.state.segments === 1) {
// only print this the first time
yield this.getState(`Scrolling up by ${scrollOpts.top} pixels every ${interval / 1000.0} seconds`);
yield getState(
ctx,
`Scrolling up by ${scrollOpts.top} pixels every ${interval / 1000.0} seconds`,
);
} else {
// only add extra wait if actually changed height
// check for scrolling, but allow for more time for content to appear the longer have already scrolled
await Promise.race([
waitUntil(() => self.scrollY > 0, interval),
sleep((this.state.segments - 1) * 2000)
sleep((this.state.segments - 1) * 2000),
]);
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Autoplay } from "./autoplay";
import { AutoScroll } from "./autoscroll";
import { AutoClick } from "./autoclick";
import { awaitLoad, sleep, behaviorLog, _setLogFunc, _setBehaviorManager, installBehaviors, addLink, checkToJsonOverride } from "./lib/utils";
import { type Behavior, BehaviorRunner } from "./lib/behavior";
import { BehaviorRunner } from "./lib/behavior";
import * as Lib from "./lib/utils";


Expand Down Expand Up @@ -44,7 +44,7 @@ export class BehaviorManager {
autofetch: AutoFetcher;
behaviors: any[];
loadedBehaviors: any;
mainBehavior: Behavior | BehaviorRunner | null;
mainBehavior: BehaviorRunner | null;
mainBehaviorClass: any;
inited: boolean;
started: boolean;
Expand Down Expand Up @@ -152,7 +152,7 @@ export class BehaviorManager {
if (!siteMatch && opts.autoscroll) {
behaviorLog("Using Autoscroll");
this.mainBehaviorClass = AutoScroll;
this.mainBehavior = new AutoScroll(this.autofetch);
this.mainBehavior = new BehaviorRunner(AutoScroll, {});
}

if (this.mainBehavior) {
Expand Down Expand Up @@ -213,7 +213,7 @@ export class BehaviorManager {
this.selectMainBehavior();
if (this.mainBehavior?.awaitPageLoad) {
behaviorLog("Waiting for custom page load via behavior");
await this.mainBehavior.awaitPageLoad({Lib});
await this.mainBehavior.awaitPageLoad();
} else {
behaviorLog("No custom wait behavior");
}
Expand Down
Loading
Loading