@opencraw/core 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +4 -2
  2. package/dist/index.esm.js +2313 -632
  3. package/dist/src/api-steps/extract-from-document.use-case.d.ts +16 -3
  4. package/dist/src/api-steps/index.d.ts +1 -1
  5. package/dist/src/api-steps/send-request.use-case.d.ts +2 -2
  6. package/dist/src/captcha/captcha-budget.model.d.ts +21 -0
  7. package/dist/src/captcha/captcha-detection.client.d.ts +28 -0
  8. package/dist/src/captcha/captcha-guard.use-case.d.ts +64 -0
  9. package/dist/src/captcha/captcha-solver-registry.store.d.ts +19 -0
  10. package/dist/src/captcha/captcha-solver.contract.d.ts +47 -0
  11. package/dist/src/captcha/captcha.error.d.ts +13 -0
  12. package/dist/src/captcha/index.d.ts +10 -0
  13. package/dist/src/captcha/resolve-captcha.use-case.d.ts +40 -0
  14. package/dist/src/crawl-events/crawl-event.contract.d.ts +40 -0
  15. package/dist/src/crawl-execution/bootstrap-session.use-case.d.ts +5 -2
  16. package/dist/src/crawl-execution/crawl-options.config.d.ts +3 -0
  17. package/dist/src/crawl-execution/crawl-report.model.d.ts +6 -0
  18. package/dist/src/crawl-execution/create-crawler.use-case.d.ts +2 -1
  19. package/dist/src/crawl-execution/run-input-recipe.use-case.d.ts +3 -0
  20. package/dist/src/deck-document/deck-document.model.d.ts +58 -0
  21. package/dist/src/deck-document/deck-table.algorithm.d.ts +35 -0
  22. package/dist/src/deck-document/index.d.ts +6 -0
  23. package/dist/src/deck-document/read-pptx.client.d.ts +16 -0
  24. package/dist/src/extraction-scope/extraction-scope.model.d.ts +3 -1
  25. package/dist/src/http-session/http-response.contract.d.ts +13 -1
  26. package/dist/src/http-session/text-decoding.algorithm.d.ts +35 -0
  27. package/dist/src/index.d.ts +9 -1
  28. package/dist/src/markdown-document/index.d.ts +3 -0
  29. package/dist/src/markdown-document/read-markdown.client.d.ts +29 -0
  30. package/dist/src/pdf-document/index.d.ts +1 -1
  31. package/dist/src/pdf-document/row-assembly.algorithm.d.ts +10 -1
  32. package/dist/src/recipe-loading/recipe-binding.validator.d.ts +3 -1
  33. package/dist/src/recipe-schema/index.d.ts +2 -2
  34. package/dist/src/recipe-schema/input-recipe.contract.d.ts +33 -3
  35. package/dist/src/recipe-schema/recipe-kind.enum.d.ts +3 -2
  36. package/dist/src/recipe-schema/step.contract.d.ts +46 -2
  37. package/dist/src/selection/index.d.ts +1 -1
  38. package/dist/src/selection/json-text.algorithm.d.ts +30 -3
  39. package/dist/src/web-steps/run-web-step.use-case.d.ts +10 -2
  40. package/dist/src/workbook-document/csv-parser.algorithm.d.ts +26 -0
  41. package/dist/src/workbook-document/csv-workbook.mapper.d.ts +24 -0
  42. package/dist/src/workbook-document/grid-table.algorithm.d.ts +53 -0
  43. package/dist/src/workbook-document/html-tables.mapper.d.ts +14 -0
  44. package/dist/src/workbook-document/index.d.ts +9 -0
  45. package/dist/src/workbook-document/read-xlsx.client.d.ts +18 -0
  46. package/dist/src/workbook-document/workbook-document.model.d.ts +51 -0
  47. package/dist/src/yaml-document/index.d.ts +3 -0
  48. package/dist/src/yaml-document/read-yaml.client.d.ts +25 -0
  49. package/package.json +16 -2
@@ -1,10 +1,12 @@
1
- import type { ExtractionScope } from '../extraction-scope/index.js';
1
+ import type { ExtractionScope, ScopeDocument } from '../extraction-scope/index.js';
2
2
  import type { ExtractStep } from '../recipe-schema/index.js';
3
3
  /**
4
4
  * Runs an `extract` step against a static document: the value bound under
5
5
  * `from`, else the scope's current document. `css` reads HTML, `jsonpath`
6
- * reads JSON (or a read PDF's rows), `table` reads a PDF's tables, `regex`
7
- * reads any document as text; `xpath` needs a live page and is refused here.
6
+ * reads JSON (or a read PDF, workbook or deck as data), `table` reads the
7
+ * tables of a PDF, a workbook (a spreadsheet, a CSV), a deck (a presentation)
8
+ * or HTML (its `<table>`s), `regex` reads any document as text; `xpath` needs
9
+ * a live page and is refused here.
8
10
  *
9
11
  * A `jsonpath` extract whose `from` is text parses that text as JSON, and a
10
12
  * list of texts (every `<script type="application/ld+json">` of a page) becomes
@@ -25,4 +27,15 @@ export declare function extractFromDocument(step: ExtractStep, scope: Extraction
25
27
  * @returns The selector to run.
26
28
  */
27
29
  export declare function renderSelector(selector: string, scope: ExtractionScope): string;
30
+ /**
31
+ * The tables a `table` extract finds in a document: a PDF's, a workbook's, a
32
+ * deck's, or an HTML document's `<table>`s (a fetched page, rendered Markdown,
33
+ * a live page's content).
34
+ *
35
+ * @param document - The document.
36
+ * @param step - The extract step.
37
+ * @param scope - Where its selector renders.
38
+ * @returns The tables.
39
+ */
40
+ export declare function tablesIn(document: ScopeDocument, step: ExtractStep, scope: ExtractionScope): unknown[];
28
41
  //# sourceMappingURL=extract-from-document.use-case.d.ts.map
@@ -1,4 +1,4 @@
1
1
  export { ApiStepRunner } from './run-api-step.use-case.js';
2
- export { extractFromDocument, renderSelector } from './extract-from-document.use-case.js';
2
+ export { extractFromDocument, renderSelector, tablesIn } from './extract-from-document.use-case.js';
3
3
  export { sendRequest, documentValue } from './send-request.use-case.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -14,9 +14,9 @@ import type { RunGate } from '../step-flow/index.js';
14
14
  * @param recipe - The recipe: its limits, block rule and id.
15
15
  * @param gate - Spaces request starts by `delayMs`.
16
16
  * @param events - Where to report the visit.
17
- * @throws BlockedError when the response is a block; HttpError for any other 4xx/5xx.
17
+ * @throws BlockedError when the response is a block, or a captcha page under `session.captcha`; HttpError for any other 4xx/5xx.
18
18
  */
19
19
  export declare function sendRequest(step: RequestStep, scope: ExtractionScope, client: HttpSender, recipe: InputRecipe, gate: RunGate, events: EventBus): Promise<void>;
20
- /** What a step id holds for a document: parsed JSON, the read PDF, or the markup / text. */
20
+ /** What a step id holds for a document: parsed JSON, the read PDF, workbook or deck, or the markup / text. */
21
21
  export declare function documentValue(body: HttpBody): unknown;
22
22
  //# sourceMappingURL=send-request.use-case.d.ts.map
@@ -0,0 +1,21 @@
1
+ /** Default solves a recipe run may spend. */
2
+ export declare const DEFAULT_MAX_SOLVES = 10;
3
+ /**
4
+ * The solves a recipe run may still spend. Every solve costs money: a detector
5
+ * that matches the wrong element would drain a balance without it. Shared by
6
+ * every runner of the run, rotations included.
7
+ */
8
+ export declare class CaptchaBudget {
9
+ readonly max: number;
10
+ private used;
11
+ constructor(max?: number);
12
+ /** Solves spent so far. */
13
+ get spent(): number;
14
+ /**
15
+ * Spends one solve.
16
+ *
17
+ * @returns Whether one was left.
18
+ */
19
+ take(): boolean;
20
+ }
21
+ //# sourceMappingURL=captcha-budget.model.d.ts.map
@@ -0,0 +1,28 @@
1
+ import type { Page } from 'playwright';
2
+ import type { CaptchaChallenge } from './captcha-solver.contract.js';
3
+ /** The widgets detection looks for when a recipe names none: reCAPTCHA v2, hCaptcha and Turnstile, as a container or as their iframe. */
4
+ export declare const DEFAULT_CAPTCHA_SELECTOR: string;
5
+ /**
6
+ * The first visible challenge on the page, if any. An element counts only
7
+ * when visible: sites keep hidden widgets around after a solve, and an
8
+ * invisible reCAPTCHA shows nothing until it challenges.
9
+ *
10
+ * @param page - The live page.
11
+ * @param selector - Where challenges are; `DEFAULT_CAPTCHA_SELECTOR` when omitted.
12
+ * @param options - `v3`: also report a reCAPTCHA v3 script (a `captcha` step asks for it; the automatic checks do not, since v3 never blocks a page by itself).
13
+ * @returns The challenge, or `undefined`.
14
+ */
15
+ export declare function detectChallenge(page: Page, selector?: string, options?: {
16
+ v3?: boolean;
17
+ }): Promise<CaptchaChallenge | undefined>;
18
+ /**
19
+ * Whether the page is clear of challenges, tolerating a page that is
20
+ * navigating (a solve often submits a form): an evaluation cut short by the
21
+ * navigation counts as not clear yet.
22
+ *
23
+ * @param page - The live page.
24
+ * @param selector - Where challenges are.
25
+ * @returns Whether no challenge is visible.
26
+ */
27
+ export declare function isClear(page: Page, selector: string): Promise<boolean>;
28
+ //# sourceMappingURL=captcha-detection.client.d.ts.map
@@ -0,0 +1,64 @@
1
+ import type { Page } from 'playwright';
2
+ import type { AccessLease } from '../access/index.js';
3
+ import type { EventBus } from '../crawl-events/index.js';
4
+ import type { CaptchaStep, InputRecipe } from '../recipe-schema/index.js';
5
+ import type { BlockedError } from '../step-flow/index.js';
6
+ import type { CaptchaBudget } from './captcha-budget.model.js';
7
+ import type { CaptchaSolverRegistry } from './captcha-solver-registry.store.js';
8
+ export interface CaptchaGuardOptions {
9
+ recipe: InputRecipe;
10
+ events: EventBus;
11
+ solvers: CaptchaSolverRegistry;
12
+ /** Shared by every runner of the recipe run, rotations and the bootstrap included. */
13
+ budget: CaptchaBudget;
14
+ /** The access the page goes through, handed to solvers. */
15
+ lease?: AccessLease;
16
+ }
17
+ /**
18
+ * Where a web runner meets captchas: after each navigation, click and key
19
+ * press (`session.captcha`), on a block page (`onBlock.solve`), and at a
20
+ * `captcha` step.
21
+ */
22
+ export declare class CaptchaGuard {
23
+ private readonly options;
24
+ constructor(options: CaptchaGuardOptions);
25
+ private settings;
26
+ private solve;
27
+ /** Whether a block page is searched for a challenge before the block counts. */
28
+ get solvesBlocks(): boolean;
29
+ /**
30
+ * The automatic check: with `session.captcha`, solves the challenge the page
31
+ * shows, if any. Without it, nothing is looked for.
32
+ *
33
+ * @param page - The live page.
34
+ * @throws CaptchaError when the challenge could not be solved.
35
+ */
36
+ check(page: Page): Promise<void>;
37
+ /**
38
+ * A block page under `onBlock.solve`: solves the challenge it shows. A block
39
+ * without a challenge stays a block.
40
+ *
41
+ * @param page - The page showing the block.
42
+ * @param blocked - The block.
43
+ * @throws BlockedError (`blocked`) when the page shows no challenge; CaptchaError when it could not be solved.
44
+ */
45
+ solveBlock(page: Page, blocked: BlockedError): Promise<void>;
46
+ /**
47
+ * A `captcha` step: solves the challenge the page shows, reCAPTCHA v3
48
+ * included; a page without one is fine.
49
+ *
50
+ * @param page - The live page.
51
+ * @param step - The step.
52
+ * @throws CaptchaError when the challenge could not be solved.
53
+ */
54
+ step(page: Page, step: CaptchaStep): Promise<void>;
55
+ }
56
+ /**
57
+ * Every solver name a recipe uses (`session.captcha` and its `captcha`
58
+ * steps, the bootstrap's included), to check them before the run starts.
59
+ *
60
+ * @param recipe - The input recipe.
61
+ * @returns The names, without repeats.
62
+ */
63
+ export declare function captchaSolverNames(recipe: InputRecipe): string[];
64
+ //# sourceMappingURL=captcha-guard.use-case.d.ts.map
@@ -0,0 +1,19 @@
1
+ import type { CaptchaSolver } from './captcha-solver.contract.js';
2
+ /** The captcha solvers a crawler was given, by name. */
3
+ export declare class CaptchaSolverRegistry {
4
+ private readonly solvers;
5
+ private readonly order;
6
+ constructor(solvers?: readonly CaptchaSolver[]);
7
+ /** The registered names. */
8
+ get names(): string[];
9
+ has(name: string): boolean;
10
+ /**
11
+ * The solver of that name.
12
+ *
13
+ * @param name - As a recipe names it.
14
+ * @returns The solver.
15
+ * @throws Error naming what is registered when it is not.
16
+ */
17
+ resolve(name: string): CaptchaSolver;
18
+ }
19
+ //# sourceMappingURL=captcha-solver-registry.store.d.ts.map
@@ -0,0 +1,47 @@
1
+ import type { Page } from 'playwright';
2
+ import type { AccessLease } from '../access/index.js';
3
+ /** The kinds of challenge detection tells apart. */
4
+ export type CaptchaKind = 'recaptcha-v2' | 'recaptcha-v3' | 'hcaptcha' | 'turnstile' | 'image' | 'unknown';
5
+ /** What the engine found on the page: enough for a token service, or for a solver that works on the page itself. */
6
+ export interface CaptchaChallenge {
7
+ kind: CaptchaKind;
8
+ /** The page the challenge is on. */
9
+ url: string;
10
+ /** The widget's site key (`data-sitekey`, or the `k` / `sitekey` of its iframe), when it has one. */
11
+ siteKey?: string;
12
+ /** The widget's action (`data-action`), when it declares one. */
13
+ action?: string;
14
+ /** A Playwright selector for the widget, or for the image of an image captcha. */
15
+ selector?: string;
16
+ }
17
+ export type CaptchaLog = (level: 'debug' | 'info' | 'warn' | 'error', message: string, meta?: Record<string, unknown>) => void;
18
+ /** What a solver gets besides the challenge. */
19
+ export interface CaptchaContext {
20
+ /** The live page: the solver may inject a token, fill a field, click. */
21
+ page: Page;
22
+ /** The access in use: token services often need the same IP (proxy) as the browser. */
23
+ lease?: AccessLease;
24
+ /** 1, 2, … within one solve loop. */
25
+ attempt: number;
26
+ /** Aborted when the engine's timeout for this attempt runs out. */
27
+ signal: AbortSignal;
28
+ log: CaptchaLog;
29
+ }
30
+ /** A solver's report. The engine checks it: `solved` is a claim until the page confirms it. */
31
+ export type CaptchaOutcome = {
32
+ status: 'solved';
33
+ } | {
34
+ status: 'failed';
35
+ reason: string;
36
+ };
37
+ /**
38
+ * A captcha solver: solves a challenge and applies the answer on the page
39
+ * (injects a token and calls the widget's callback, types an image's text,
40
+ * submits). Detection, verification, retries, rotation and the budget are the
41
+ * engine's.
42
+ */
43
+ export interface CaptchaSolver {
44
+ name: string;
45
+ solve: (challenge: CaptchaChallenge, context: CaptchaContext) => Promise<CaptchaOutcome> | CaptchaOutcome;
46
+ }
47
+ //# sourceMappingURL=captcha-solver.contract.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { BlockedError } from '../step-flow/index.js';
2
+ /**
3
+ * A challenge the engine could not get past: the solver failed, the page did
4
+ * not confirm it, or the budget ran out. It is a block, so a recipe with
5
+ * `onBlock.rotate` retries the step on a new access lease (a new IP often
6
+ * means an easier challenge, or none), then the step's error policy applies.
7
+ */
8
+ export declare class CaptchaError extends BlockedError {
9
+ readonly kind: string;
10
+ readonly attempts: number;
11
+ constructor(url: string, kind: string, attempts: number, reason: string);
12
+ }
13
+ //# sourceMappingURL=captcha.error.d.ts.map
@@ -0,0 +1,10 @@
1
+ export type { CaptchaSolver, CaptchaChallenge, CaptchaContext, CaptchaOutcome, CaptchaKind, CaptchaLog } from './captcha-solver.contract.js';
2
+ export { CaptchaError } from './captcha.error.js';
3
+ export { CaptchaSolverRegistry } from './captcha-solver-registry.store.js';
4
+ export { CaptchaBudget, DEFAULT_MAX_SOLVES } from './captcha-budget.model.js';
5
+ export { DEFAULT_CAPTCHA_SELECTOR, detectChallenge } from './captcha-detection.client.js';
6
+ export { resolveCaptcha, DEFAULT_CAPTCHA_ATTEMPTS, DEFAULT_CAPTCHA_TIMEOUT_MS } from './resolve-captcha.use-case.js';
7
+ export type { CaptchaAttemptPlan } from './resolve-captcha.use-case.js';
8
+ export { CaptchaGuard, captchaSolverNames } from './captcha-guard.use-case.js';
9
+ export type { CaptchaGuardOptions } from './captcha-guard.use-case.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,40 @@
1
+ import type { Page } from 'playwright';
2
+ import type { AccessLease } from '../access/index.js';
3
+ import type { EventBus } from '../crawl-events/index.js';
4
+ import type { CaptchaCheck } from '../recipe-schema/index.js';
5
+ import type { CaptchaBudget } from './captcha-budget.model.js';
6
+ import type { CaptchaChallenge, CaptchaSolver } from './captcha-solver.contract.js';
7
+ /** Default solves tried per challenge. */
8
+ export declare const DEFAULT_CAPTCHA_ATTEMPTS = 3;
9
+ /** Default time one solve may take: token services take 10 to 60 seconds. */
10
+ export declare const DEFAULT_CAPTCHA_TIMEOUT_MS = 120000;
11
+ /** One challenge to get past, and everything the loop needs for it. */
12
+ export interface CaptchaAttemptPlan {
13
+ recipeId: string;
14
+ page: Page;
15
+ challenge: CaptchaChallenge;
16
+ solver: CaptchaSolver;
17
+ /** Where challenges are, to re-detect and to confirm one is gone. */
18
+ selector: string;
19
+ verify?: CaptchaCheck;
20
+ attempts: number;
21
+ timeoutMs: number;
22
+ budget: CaptchaBudget;
23
+ events: EventBus;
24
+ lease?: AccessLease;
25
+ }
26
+ /**
27
+ * Gets past one challenge: asks the solver, then checks the page (a solver's
28
+ * `solved` is a claim; the challenge must be gone and/or the `verify` element
29
+ * must appear), and tries again with what the page shows next, up to
30
+ * `attempts`. Each try spends one solve of the run's budget.
31
+ *
32
+ * A failed try re-detects the challenge (a widget re-renders after a wrong
33
+ * answer). When it is gone without the page confirming, the page is reloaded
34
+ * for a fresh one; when a reload shows none, there is nothing left to solve.
35
+ *
36
+ * @param plan - The challenge, the solver and the limits.
37
+ * @throws CaptchaError when every try failed, or the budget is spent.
38
+ */
39
+ export declare function resolveCaptcha(plan: CaptchaAttemptPlan): Promise<void>;
40
+ //# sourceMappingURL=resolve-captcha.use-case.d.ts.map
@@ -48,6 +48,46 @@ export type CrawlEvent = (Base & {
48
48
  type: 'access:rotate';
49
49
  attempt: number;
50
50
  reason: string;
51
+ }) |
52
+ /** A captcha challenge is on the page. */
53
+ (Base & {
54
+ type: 'captcha:detected';
55
+ url: string;
56
+ kind: string;
57
+ siteKey?: string;
58
+ }) |
59
+ /** A solver is trying (`attempt` counts from 1 per challenge). */
60
+ (Base & {
61
+ type: 'captcha:solve';
62
+ url: string;
63
+ kind: string;
64
+ solver: string;
65
+ attempt: number;
66
+ }) |
67
+ /** The page confirmed the solve. */
68
+ (Base & {
69
+ type: 'captcha:solved';
70
+ url: string;
71
+ kind: string;
72
+ solver: string;
73
+ attempt: number;
74
+ durationMs: number;
75
+ }) |
76
+ /** The solver failed, timed out, or the page still shows the challenge. */
77
+ (Base & {
78
+ type: 'captcha:failed';
79
+ url: string;
80
+ kind: string;
81
+ solver: string;
82
+ attempt: number;
83
+ reason: string;
84
+ }) |
85
+ /** A challenge was left unsolved: the run spent its `maxSolves`. */
86
+ (Base & {
87
+ type: 'captcha:budget';
88
+ url: string;
89
+ kind: string;
90
+ max: number;
51
91
  }) | (Base & {
52
92
  type: 'step:start';
53
93
  stepType: string;
@@ -1,5 +1,6 @@
1
1
  import type { AccessLease } from '../access/index.js';
2
2
  import type { BrowserClient, BrowserSession, SessionOptions, StorageState } from '../browser-session/index.js';
3
+ import type { CaptchaGuard } from '../captcha/index.js';
3
4
  import type { EventBus } from '../crawl-events/index.js';
4
5
  import type { HookRegistry } from '../hooks/index.js';
5
6
  import type { InputRecipe } from '../recipe-schema/index.js';
@@ -30,9 +31,10 @@ export declare function accessOptions(lease: AccessLease | undefined, headers: R
30
31
  * @param recipe - The input recipe.
31
32
  * @param deps - Browser, hooks, events.
32
33
  * @param lease - The recipe run's access; direct when omitted.
34
+ * @param captcha - Solves the bootstrap's captchas (a login form's).
33
35
  * @returns The state, or `undefined` when the recipe declares none.
34
36
  */
35
- export declare function resolveStorageState(recipe: InputRecipe, deps: BootstrapDependencies, lease?: AccessLease): Promise<StorageState | undefined>;
37
+ export declare function resolveStorageState(recipe: InputRecipe, deps: BootstrapDependencies, lease?: AccessLease, captcha?: CaptchaGuard): Promise<StorageState | undefined>;
36
38
  /**
37
39
  * The storage state saved by an earlier bootstrap (`session.storageStatePath`), if the recipe names one.
38
40
  *
@@ -49,7 +51,8 @@ export declare function readSavedState(recipe: InputRecipe, deps: Pick<Bootstrap
49
51
  * @param recipe - An input recipe with `session.bootstrap`.
50
52
  * @param browserSession - Where the steps run.
51
53
  * @param deps - Hooks, events, `storageStateDir`.
54
+ * @param captcha - Solves the bootstrap's captchas.
52
55
  * @returns The kept state.
53
56
  */
54
- export declare function runBootstrap(recipe: InputRecipe, browserSession: BrowserSession, deps: Omit<BootstrapDependencies, 'browser'>): Promise<StorageState>;
57
+ export declare function runBootstrap(recipe: InputRecipe, browserSession: BrowserSession, deps: Omit<BootstrapDependencies, 'browser'>, captcha?: CaptchaGuard): Promise<StorageState>;
55
58
  //# sourceMappingURL=bootstrap-session.use-case.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import type { AccessConfig, AccessPlugin } from '../access/index.js';
2
2
  import type { BrowserSessionConfig } from '../browser-session/index.js';
3
+ import type { CaptchaSolver } from '../captcha/index.js';
3
4
  import type { CrawlListener } from '../crawl-events/index.js';
4
5
  import type { HookMap } from '../hooks/index.js';
5
6
  import type { DedupeScope, RecordSink } from '../record-sink/index.js';
@@ -33,5 +34,7 @@ export interface CrawlOptions {
33
34
  access?: AccessConfig;
34
35
  /** Plugins `{ kind: 'plugin', name }` profiles refer to. */
35
36
  accessPlugins?: AccessPlugin[];
37
+ /** Solvers recipes name in `session.captcha.solver` and `captcha` steps. */
38
+ captchaSolvers?: CaptchaSolver[];
36
39
  }
37
40
  //# sourceMappingURL=crawl-options.config.d.ts.map
@@ -12,6 +12,12 @@ export interface RecipeReport {
12
12
  stepsSkipped: number;
13
13
  pages: number;
14
14
  durationMs: number;
15
+ /** Captcha challenges met, solved, and solve attempts that failed; present when any was met. */
16
+ captchas?: {
17
+ detected: number;
18
+ solved: number;
19
+ failed: number;
20
+ };
15
21
  /** Set when the recipe stopped on a failure. */
16
22
  error?: string;
17
23
  }
@@ -11,9 +11,10 @@ export interface Crawler {
11
11
  * Creates a crawler. The browser is launched lazily, on the first recipe or
12
12
  * bootstrap that needs it, and shared by every run until `close`.
13
13
  *
14
- * @param options - Hooks, sink, events, browser settings, access, policies.
14
+ * @param options - Hooks, sink, events, browser settings, access, captcha solvers, policies.
15
15
  * @returns The crawler.
16
16
  * @throws AccessConfigError when the access config cannot work.
17
+ * @throws Error when two captcha solvers share a name.
17
18
  */
18
19
  export declare function createCrawler(options?: CrawlOptions): Crawler;
19
20
  //# sourceMappingURL=create-crawler.use-case.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import type { AccessBroker } from '../access/index.js';
2
2
  import { BrowserClient } from '../browser-session/index.js';
3
+ import { CaptchaSolverRegistry } from '../captcha/index.js';
3
4
  import type { EventBus } from '../crawl-events/index.js';
4
5
  import type { HookRegistry } from '../hooks/index.js';
5
6
  import type { InputRecipe, OutputRecipe } from '../recipe-schema/index.js';
@@ -20,6 +21,8 @@ export interface RecipeRunDependencies {
20
21
  debug?: boolean;
21
22
  /** Leases each recipe run its network access. */
22
23
  access: AccessBroker;
24
+ /** The solvers recipes name; none when omitted. */
25
+ captchaSolvers?: CaptchaSolverRegistry;
23
26
  }
24
27
  /**
25
28
  * Runs one input recipe end to end: session, runner, the step walk, and for
@@ -0,0 +1,58 @@
1
+ import type { Sheet } from '../workbook-document/index.js';
2
+ /** A text box on a slide, in points from the slide's top-left corner. */
3
+ export interface DeckShape {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ text: string;
9
+ /** The placeholder it fills (`title`, `body`…). */
10
+ placeholder?: string;
11
+ }
12
+ /** A chart's data, from the values the chart caches. */
13
+ export interface DeckChart {
14
+ type: string;
15
+ title?: string;
16
+ series: {
17
+ name: string;
18
+ categories: string[];
19
+ values: (number | null)[];
20
+ }[];
21
+ }
22
+ /** One slide. */
23
+ export interface DeckSlide {
24
+ number: number;
25
+ title?: string;
26
+ hidden: boolean;
27
+ /** Text boxes in reading order. */
28
+ shapes: DeckShape[];
29
+ /** Native tables, as sheets (`table 1`…) with their merged cells. */
30
+ tables: Sheet[];
31
+ charts: DeckChart[];
32
+ notes: string;
33
+ }
34
+ /** A presentation read into slides: what `extract` works on. */
35
+ export interface DeckDocument {
36
+ kind: 'deck';
37
+ /** The slide size, in points. */
38
+ width: number;
39
+ height: number;
40
+ slides: DeckSlide[];
41
+ }
42
+ /**
43
+ * The text a `regex` extract reads: per visible slide, its title, its text
44
+ * boxes in reading order, its tables' rows (cells separated by a tab) and its
45
+ * notes after `Notes:`; slides separated by a blank line.
46
+ *
47
+ * @param document - The deck.
48
+ * @returns The text.
49
+ */
50
+ export declare function deckText(document: DeckDocument): string;
51
+ /**
52
+ * Whether a value bound in scope is a read deck (so `extract … from` can take it).
53
+ *
54
+ * @param value - Anything.
55
+ * @returns Whether it is a {@link DeckDocument}.
56
+ */
57
+ export declare function isDeckDocument(value: unknown): value is DeckDocument;
58
+ //# sourceMappingURL=deck-document.model.d.ts.map
@@ -0,0 +1,35 @@
1
+ import type { TableAlign } from '../pdf-document/index.js';
2
+ import type { GridTableQuery, WorkbookCell } from '../workbook-document/index.js';
3
+ import type { DeckDocument } from './deck-document.model.js';
4
+ /** What a table extract looks for in a deck. */
5
+ export interface DeckTableQuery extends Omit<GridTableQuery, 'sheet'> {
6
+ /** Matches the titles of the slides to read; default every slide. */
7
+ slide?: RegExp;
8
+ /** Read text boxes laid out as a table instead of native tables. */
9
+ shapes?: boolean;
10
+ /** With `shapes`: how a row's values sit against a box wrapped over several lines. */
11
+ align?: TableAlign;
12
+ }
13
+ /** One table found in a deck. */
14
+ export interface DeckTable {
15
+ /** The slide's number, from 1. */
16
+ slide: number;
17
+ slideTitle: string;
18
+ /** The first header cell. */
19
+ title: string;
20
+ header: string[];
21
+ rows: Record<string, WorkbookCell>[];
22
+ }
23
+ /**
24
+ * Finds tables in a deck: native tables through the workbook table reader
25
+ * (merged cells filled, a header over several rows joined), or, with
26
+ * `shapes`, text boxes laid out as a table through the PDF table reader (a box
27
+ * is a cell, boxes whose heights overlap a row, columns from where the body's
28
+ * boxes start). Hidden slides are skipped unless `includeHidden`.
29
+ *
30
+ * @param document - The deck.
31
+ * @param query - Which tables, on which slides, and how to name their columns.
32
+ * @returns The tables, slide by slide.
33
+ */
34
+ export declare function findDeckTables(document: DeckDocument, query: DeckTableQuery): DeckTable[];
35
+ //# sourceMappingURL=deck-table.algorithm.d.ts.map
@@ -0,0 +1,6 @@
1
+ export { deckText, isDeckDocument } from './deck-document.model.js';
2
+ export type { DeckDocument, DeckSlide, DeckShape, DeckChart } from './deck-document.model.js';
3
+ export { readPptxDeck } from './read-pptx.client.js';
4
+ export { findDeckTables } from './deck-table.algorithm.js';
5
+ export type { DeckTable, DeckTableQuery } from './deck-table.algorithm.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ import type { DeckDocument } from './deck-document.model.js';
2
+ /**
3
+ * Reads a `.pptx` presentation into a deck document, through
4
+ * `@opencraw/office-reader`: every slide's text boxes with their positions,
5
+ * its tables with their merged cells, its charts' cached data and its notes.
6
+ * The reader is imported on first use, so recipes that never read a
7
+ * presentation never load it.
8
+ *
9
+ * @param bytes - The file.
10
+ * @param source - Where it came from, for messages.
11
+ * @returns The deck.
12
+ * @throws Error naming the source, and saying what to do, for a file that is
13
+ * not a readable presentation (a legacy `.ppt`, a password-protected file, an `.odp`…).
14
+ */
15
+ export declare function readPptxDeck(bytes: Uint8Array, source: string): Promise<DeckDocument>;
16
+ //# sourceMappingURL=read-pptx.client.d.ts.map
@@ -7,9 +7,11 @@
7
7
  * Page state (the current URL, page number and the document `extract` reads by
8
8
  * default) is scope state too, bound in the innermost scope that navigated.
9
9
  */
10
+ import type { DeckDocument } from '../deck-document/index.js';
10
11
  import type { PdfDocument } from '../pdf-document/index.js';
12
+ import type { WorkbookDocument } from '../workbook-document/index.js';
11
13
  /** A fetched or rendered document a later `extract` can read. */
12
- export type ScopeDocument = PdfDocument | {
14
+ export type ScopeDocument = PdfDocument | WorkbookDocument | DeckDocument | {
13
15
  kind: 'json';
14
16
  data: unknown;
15
17
  } | {
@@ -1,4 +1,6 @@
1
+ import type { DeckDocument } from '../deck-document/index.js';
1
2
  import type { PdfDocument } from '../pdf-document/index.js';
3
+ import type { WorkbookDocument } from '../workbook-document/index.js';
2
4
  import type { BodyKind, HttpMethod } from '../recipe-schema/index.js';
3
5
  /** One HTTP request as the api runner sends it, templates already rendered. */
4
6
  export interface HttpRequest {
@@ -9,10 +11,16 @@ export interface HttpRequest {
9
11
  body?: unknown;
10
12
  /** How to read the body; default: from the response content type. */
11
13
  as?: BodyKind;
14
+ /** The encoding of a text body (a WHATWG label); default: the BOM, the declared charset, UTF-8, else Windows-1252. */
15
+ encoding?: string;
16
+ /** A CSV body's delimiter (one character); default: detected. */
17
+ delimiter?: string;
18
+ /** A YAML body's scalars: `typed` (default) or `text`. */
19
+ scalars?: 'typed' | 'text';
12
20
  timeoutMs?: number;
13
21
  }
14
22
  /** A parsed response body. Structurally the same as a scope document, on purpose. */
15
- export type HttpBody = PdfDocument | {
23
+ export type HttpBody = PdfDocument | WorkbookDocument | DeckDocument | {
16
24
  kind: 'json';
17
25
  data: unknown;
18
26
  } | {
@@ -28,6 +36,10 @@ export interface HttpResponse {
28
36
  url: string;
29
37
  headers: Record<string, string>;
30
38
  body: HttpBody;
39
+ /** What reading the body noticed but read anyway (an unknown YAML tag…). */
40
+ warnings?: string[];
41
+ /** The format the body was read as (`yaml` and `jsonl` both give JSON data). */
42
+ format?: BodyKind;
31
43
  }
32
44
  /** The part of the client the api runner needs; tests fake it. */
33
45
  export interface HttpSender {
@@ -0,0 +1,35 @@
1
+ /** Decoded text and the encoding it was read as. */
2
+ export interface DecodedText {
3
+ text: string;
4
+ encoding: string;
5
+ }
6
+ /**
7
+ * Decodes a body, in this order: a byte-order mark (UTF-8, UTF-16 LE/BE; Excel's
8
+ * "Unicode text" export is UTF-16 LE), the encoding a recipe asks for, the
9
+ * charset the server declares, strict UTF-8, and Windows-1252 (a superset of
10
+ * Latin-1) for text that is not UTF-8.
11
+ *
12
+ * The Windows-1252 fallback is only taken when the text holds no valid UTF-8
13
+ * beyond ASCII: a UTF-8 page with one stray byte keeps its accents, with a
14
+ * replacement character for the stray byte, instead of turning every accent
15
+ * into mojibake.
16
+ *
17
+ * @param bytes - The body.
18
+ * @param options - `encoding`: the recipe's choice, a WHATWG label (wins over
19
+ * the charset, not over a BOM); `charset`: from the content type (ignored when
20
+ * not a known label).
21
+ * @returns The text, without its BOM, and the encoding used.
22
+ * @throws Error when `encoding` is not a known label.
23
+ */
24
+ export declare function decodeText(bytes: Uint8Array, options?: {
25
+ encoding?: string;
26
+ charset?: string;
27
+ }): DecodedText;
28
+ /**
29
+ * The charset a content type declares (`text/csv; charset=ISO-8859-1`).
30
+ *
31
+ * @param contentType - The header value.
32
+ * @returns The charset, or `undefined`.
33
+ */
34
+ export declare function charsetOf(contentType: string): string | undefined;
35
+ //# sourceMappingURL=text-decoding.algorithm.d.ts.map