diff --git a/src/handlers/google.ts b/src/handlers/google.ts index 7dd6be0..06568cc 100644 --- a/src/handlers/google.ts +++ b/src/handlers/google.ts @@ -1,10 +1,12 @@ -import { DOMWindow } from "jsdom"; +import { HandlerInput } from "./handler-input"; import { IHandlerOutput } from "./handler.interface"; import { EngineParseError } from "../errors/main"; export default async function google( - window: DOMWindow, + input: HandlerInput, ): Promise { + const window = input.parseDom().window; + const googleAnchors = [ ...window.document.querySelectorAll("a[jsname=ACyKwe]"), ] as HTMLAnchorElement[]; diff --git a/src/handlers/handler-input.ts b/src/handlers/handler-input.ts new file mode 100644 index 0000000..1f2d260 --- /dev/null +++ b/src/handlers/handler-input.ts @@ -0,0 +1,48 @@ +import { JSDOM } from "jsdom"; +import { generateProxyUrl } from "../utils/generate"; + +export class HandlerInput { + private data: string; + private url: string; + private requestUrl: URL; + private engine?: string; + private redirectPath: string; + + constructor( + data: string, + url: string, + requestUrl: URL, + engine?: string, + redirectPath: string = "get", + ) { + this.data = data; + this.url = url; + this.requestUrl = requestUrl; + this.engine = engine; + this.redirectPath = redirectPath; + } + + parseDom(): JSDOM { + const dom = new JSDOM(this.data, { url: this.url }); + + const links = dom.window.document.getElementsByTagName("a"); + for (const link of links) { + try { + link.href = generateProxyUrl( + this.requestUrl, + link.href, + this.engine, + this.redirectPath, + ); + } catch (_err) { + // ignore TypeError: Invalid URL + } + } + + return dom; + } + + getUrl(): string { + return this.url; + } +} diff --git a/src/handlers/main.ts b/src/handlers/main.ts index 61e0bb3..8ff4ab9 100644 --- a/src/handlers/main.ts +++ b/src/handlers/main.ts @@ -1,25 +1,23 @@ import { IHandlerOutput } from "./handler.interface"; +import { Engines, EngineFunction, EnginesMatch } from "../types/handlers"; import axios from "../types/axios"; -import { JSDOM } from "jsdom"; -import { DOMWindow } from "jsdom"; +import micromatch from "micromatch"; import readability from "./readability"; import google, { GoogleDomains } from "./google"; import stackoverflow, { StackOverflowDomains } from "./stackoverflow/main"; -import { generateProxyUrl } from "../utils/generate"; import isLocalResource from "../utils/islocal"; -import micromatch from "micromatch"; - import { LocalResourceError, NotHtmlMimetypeError } from "../errors/main"; +import { HandlerInput } from "./handler-input"; export default async function handlePage( url: string, // remote URL requestUrl: URL, // proxy URL engine?: string, - redirect_path: string = "get", + redirectPath: string = "get", ): Promise { const urlObj = new URL(url); @@ -34,39 +32,27 @@ export default async function handlePage( throw new NotHtmlMimetypeError(url); } - const window = new JSDOM(response.data, { url }).window; - - [...window.document.getElementsByTagName("a")].forEach((link) => { - try { - link.href = generateProxyUrl( - requestUrl, - link.href, - engine, - redirect_path, - ); - } catch (_err) { - // ignore TypeError: Invalid URL - } - }); - - if (engine) { - return engines[engine](window); - } - - const title = window.document.title; - const lang = window.document.documentElement.lang; - - for (const match of fallback) { - if (micromatch.isMatch(urlObj.hostname, match.pattern)) { - return { title, lang, ...match.engine(window) }; - } - } - - return engines.readability(window); + return getFallbackEngine(urlObj.hostname, engine)( + new HandlerInput( + response.data, + url, + requestUrl, + engine, + redirectPath, + ) + ); } -interface Engines { - [key: string]: EngineFunction; +function getFallbackEngine(host: string, specified?: string): EngineFunction { + if (specified) { + return engines[specified]; + } + for (const engine of fallback) { + if (micromatch.isMatch(host, engine.pattern)) { + return engine.engine; + } + } + return engines.readability; } export const engines: Engines = { @@ -75,13 +61,6 @@ export const engines: Engines = { stackoverflow, }; -type EngineFunction = (window: DOMWindow) => Promise; -export type EngineMatch = { - pattern: string | string[]; - engine: EngineFunction; -}; -export type EnginesMatch = EngineMatch[]; - export const engineList: string[] = Object.keys(engines); export const fallback: EnginesMatch = [ diff --git a/src/handlers/readability.ts b/src/handlers/readability.ts index f629aa2..a22cc66 100644 --- a/src/handlers/readability.ts +++ b/src/handlers/readability.ts @@ -1,12 +1,12 @@ import { Readability } from "@mozilla/readability"; +import { HandlerInput } from "./handler-input"; import { IHandlerOutput } from "./handler.interface"; -import { DOMWindow } from "jsdom"; import { EngineParseError } from "../errors/main"; export default async function readability( - window: DOMWindow + input: HandlerInput, ): Promise { - const reader = new Readability(window.document); + const reader = new Readability(input.parseDom().window.document); const parsed = reader.parse(); if (!parsed) { diff --git a/src/handlers/stackoverflow/main.ts b/src/handlers/stackoverflow/main.ts index 9730bc8..f1b9c6c 100644 --- a/src/handlers/stackoverflow/main.ts +++ b/src/handlers/stackoverflow/main.ts @@ -1,13 +1,14 @@ +import { HandlerInput } from "../handler-input"; import { IHandlerOutput } from "../handler.interface"; -import { DOMWindow } from "jsdom"; import { EngineParseError } from "../../errors/main"; import qPostsHandler from "./questions-posts"; export default async function stackoverflow( - window: DOMWindow, + input: HandlerInput, ): Promise { - const url = new URL(window.location.href); + const window = input.parseDom().window; + const url = new URL(window.location.href); const path = url.pathname.split("/").filter((p) => p !== ""); let result: IHandlerOutput = { diff --git a/src/types/handlers.ts b/src/types/handlers.ts new file mode 100644 index 0000000..846eeda --- /dev/null +++ b/src/types/handlers.ts @@ -0,0 +1,14 @@ +import { HandlerInput } from "../handlers/handler-input"; +import { IHandlerOutput } from "../handlers/handler.interface"; + +export interface Engines { + [key: string]: EngineFunction; +} + +export type EngineMatch = { + pattern: string | string[]; + engine: EngineFunction; +}; + +export type EngineFunction = (input: HandlerInput) => Promise; +export type EnginesMatch = EngineMatch[];