duck-poacher 0.6.0

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.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Lars Dreier
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # duck-poacher
2
+
3
+ A Node.js library for image search via DuckDuckGo. Returns image and thumbnail
4
+ URLs for a query, with optional filters for size, color, type, and more.
5
+
6
+ It calls DuckDuckGo's image-search endpoints, which are not a public, versioned
7
+ API. Response formats can change on DuckDuckGo's side and break the library.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install duck-poacher
13
+ ```
14
+
15
+ Requires Node.js 18 or newer. The package ships dual ESM/CJS and exposes both an
16
+ `import` and a `require` entry through its `exports` map.
17
+
18
+ ## Usage
19
+
20
+ `DuckDuckGo` is the client. Construct one and call `imageSearch` — it returns a
21
+ parsed `ImageSearchResult[]` (objects with `imageUrl` / `thumbnailUrl`, not a raw
22
+ string) and manages the per-session `vqd` token for you, so there is no token to
23
+ pass.
24
+
25
+ ```ts
26
+ import { DuckDuckGo, type DdgSearchOptions, type ImageSearchResult } from 'duck-poacher';
27
+
28
+ const ddg = new DuckDuckGo();
29
+
30
+ const options: DdgSearchOptions = {
31
+ size: 'Large',
32
+ layout: 'Square',
33
+ safeSearch: true,
34
+ };
35
+ const results: ImageSearchResult[] = await ddg.imageSearch('mountain landscape', options);
36
+
37
+ for (const result of results) {
38
+ console.log(result.imageUrl, result.thumbnailUrl);
39
+ }
40
+ ```
41
+
42
+ Each call makes two live requests (mint the token, then search). There is no
43
+ built-in multi-query, dedupe, or cap.
44
+
45
+ ## API
46
+
47
+ | Export | Kind | Purpose |
48
+ |--------|------|---------|
49
+ | `DuckDuckGo` | class | The client: `imageSearch(query, options?)`, token managed internally |
50
+ | `ImageSearchResult` | class | Immutable value object `{ thumbnailUrl, imageUrl }` |
51
+ | `DdgSearchOptions` | type | Filter options for `imageSearch` |
52
+ | `DdgTime` `DdgSize` `DdgColor` `DdgType` `DdgLayout` `DdgLicense` | type | String-union option values |
53
+
54
+ ### `DuckDuckGo`
55
+
56
+ - **`imageSearch(query: string, options?: DdgSearchOptions): Promise<ImageSearchResult[]>`**
57
+ — generates a per-session `vqd` token, runs the image search, and returns the
58
+ parsed results. Throws `Error('Unable to read token from DuckDuckGo response.')`
59
+ if the token cannot be scraped; a malformed response body throws.
60
+
61
+ ### `DdgSearchOptions`
62
+
63
+ All fields are optional. `safeSearch` is a boolean; the rest are string unions:
64
+
65
+ | Option | Type | Values |
66
+ |--------|------|--------|
67
+ | `time` | `DdgTime` | `Day` `Week` `Month` |
68
+ | `size` | `DdgSize` | `Small` `Medium` `Large` `Wallpaper` |
69
+ | `color` | `DdgColor` | `color` `Monochrome` |
70
+ | `type` | `DdgType` | `photo` `clipart` `gif` `transparent` `line` |
71
+ | `layout` | `DdgLayout` | `Square` `Tall` `Wide` |
72
+ | `license` | `DdgLicense` | `Any` `Public` |
73
+ | `safeSearch` | `boolean` | safe search on / off |
74
+
75
+ ## Error handling
76
+
77
+ Errors are thrown rather than swallowed. `generateToken` throws if no token can
78
+ be read from the response. HTTP failures (status ≥ 400, network errors,
79
+ timeouts) reject from the underlying request and propagate to the caller.
80
+
81
+ ## Development
82
+
83
+ | Command | Does |
84
+ |---------|------|
85
+ | `npm run build` | tsdown → dual ESM/CJS in `dist/` |
86
+ | `npm test` | run all `test/**/*.test.ts` (hits the live DuckDuckGo API) |
87
+ | `npm run typecheck` | `tsc --noEmit` over `src/` |
88
+ | `npm run lint` / `lint:fix` | ESLint |
89
+ | `npm run format` / `format:check` | dprint |
90
+ | `npm run check:exports` | `attw` validates the published export map |
91
+
92
+ The only runtime dependency is
93
+ [`node-http-toolkit`](https://www.npmjs.com/package/node-http-toolkit), which
94
+ provides the HTTP layer.
95
+
96
+ The tests run against the live DuckDuckGo endpoints, so they require network
97
+ access.
98
+
99
+ ## License
100
+
101
+ ISC © Lars Dreier
@@ -0,0 +1,11 @@
1
+ const require_ImageSearchClient = require("./image/ImageSearchClient.cjs");
2
+ //#region src/DuckDuckGo.ts
3
+ var DuckDuckGo = class {
4
+ _imageSearch = new require_ImageSearchClient.default();
5
+ async imageSearch(query, options) {
6
+ const token = await this._imageSearch.generateToken(query);
7
+ return this._imageSearch.imageSearch(query, token, options);
8
+ }
9
+ };
10
+ //#endregion
11
+ exports.default = DuckDuckGo;
@@ -0,0 +1,10 @@
1
+ import { ImageSearchResult } from "./image/ImageSearchResult.cjs";
2
+ import { DdgSearchOptions } from "./image/ImageSearchClient.cjs";
3
+
4
+ //#region src/DuckDuckGo.d.ts
5
+ declare class DuckDuckGo {
6
+ private readonly _imageSearch;
7
+ imageSearch(query: string, options?: DdgSearchOptions): Promise<ImageSearchResult[]>;
8
+ }
9
+ //#endregion
10
+ export { DuckDuckGo };
@@ -0,0 +1,10 @@
1
+ import { ImageSearchResult } from "./image/ImageSearchResult.mjs";
2
+ import { DdgSearchOptions } from "./image/ImageSearchClient.mjs";
3
+
4
+ //#region src/DuckDuckGo.d.ts
5
+ declare class DuckDuckGo {
6
+ private readonly _imageSearch;
7
+ imageSearch(query: string, options?: DdgSearchOptions): Promise<ImageSearchResult[]>;
8
+ }
9
+ //#endregion
10
+ export { DuckDuckGo };
@@ -0,0 +1,11 @@
1
+ import ImageSearchClient from "./image/ImageSearchClient.mjs";
2
+ //#region src/DuckDuckGo.ts
3
+ var DuckDuckGo = class {
4
+ _imageSearch = new ImageSearchClient();
5
+ async imageSearch(query, options) {
6
+ const token = await this._imageSearch.generateToken(query);
7
+ return this._imageSearch.imageSearch(query, token, options);
8
+ }
9
+ };
10
+ //#endregion
11
+ export { DuckDuckGo as default };
@@ -0,0 +1,23 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+ //#endregion
23
+ exports.__toESM = __toESM;
@@ -0,0 +1,70 @@
1
+ require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_ImageSearchParser = require("./ImageSearchParser.cjs");
3
+ let node_http_toolkit = require("node-http-toolkit");
4
+ //#region src/image/ImageSearchClient.ts
5
+ var ImageSearchClient = class {
6
+ OPTION_NAMES = [
7
+ "time",
8
+ "size",
9
+ "color",
10
+ "type",
11
+ "layout",
12
+ "license"
13
+ ];
14
+ TOKEN_HEADERS = { dnt: "1" };
15
+ SEARCH_HEADERS = {
16
+ dnt: "1",
17
+ "accept-encoding": "gzip, deflate, sdch, br",
18
+ "x-requested-with": "XMLHttpRequest",
19
+ "accept-language": "en-GB,en-US;q=0.8,en;q=0.6,ms;q=0.4",
20
+ "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
21
+ accept: "application/json, text/javascript, */*; q=0.01",
22
+ referer: "https://duckduckgo.com/",
23
+ authority: "duckduckgo.com"
24
+ };
25
+ TOKEN_REGEX = /vqd=(?<vqd>[\d-]+)/;
26
+ _parser = new require_ImageSearchParser.default();
27
+ async generateToken(query) {
28
+ const params = new URLSearchParams();
29
+ params.append("q", query);
30
+ params.append("atb", "v299-1");
31
+ params.append("iar", "images");
32
+ params.append("iax", "images");
33
+ params.append("ia", "images");
34
+ const url = `https://duckduckgo.com/?${params.toString()}`;
35
+ const vqd = (await this.get(url, this.TOKEN_HEADERS)).match(this.TOKEN_REGEX)?.groups?.["vqd"];
36
+ if (vqd == null) throw new Error("Unable to read token from DuckDuckGo response.");
37
+ return vqd;
38
+ }
39
+ async imageSearch(query, token, options) {
40
+ const searchOptions = options ?? {};
41
+ const url = this.createSearchUrl(query, token, searchOptions);
42
+ const responseText = await this.get(url, this.SEARCH_HEADERS);
43
+ return this._parser.parse(responseText);
44
+ }
45
+ async get(url, headers) {
46
+ const response = await new node_http_toolkit.AsyncResolvingHttpRequest(url, node_http_toolkit.HttpMethod.GET, headers).resolve();
47
+ return new node_http_toolkit.HttpResponseReader().readData(response);
48
+ }
49
+ createSearchUrl(query, token, options) {
50
+ const params = new URLSearchParams();
51
+ params.append("l", "de-de");
52
+ params.append("o", "json");
53
+ params.append("q", query);
54
+ params.append("vqd", token);
55
+ params.append("f", this.createImageSearchOptionsHeader(options));
56
+ params.append("p", options.safeSearch == true ? "1" : "-1");
57
+ return `https://duckduckgo.com/i.js?${params.toString()}`;
58
+ }
59
+ createImageSearchOptionsHeader(options) {
60
+ const optionValues = [];
61
+ for (const optionName of this.OPTION_NAMES) {
62
+ const optionValue = options[optionName]?.toString();
63
+ if (optionValue == null) optionValues.push("");
64
+ else optionValues.push(`${optionName}:${optionValue}`);
65
+ }
66
+ return optionValues.join(",");
67
+ }
68
+ };
69
+ //#endregion
70
+ exports.default = ImageSearchClient;
@@ -0,0 +1,18 @@
1
+ //#region src/image/ImageSearchClient.d.ts
2
+ type DdgTime = 'Day' | 'Week' | 'Month';
3
+ type DdgSize = 'Small' | 'Medium' | 'Large' | 'Wallpaper';
4
+ type DdgColor = 'color' | 'Monochrome';
5
+ type DdgType = 'photo' | 'clipart' | 'gif' | 'transparent' | 'line';
6
+ type DdgLayout = 'Square' | 'Tall' | 'Wide';
7
+ type DdgLicense = 'Any' | 'Public';
8
+ interface DdgSearchOptions {
9
+ time?: DdgTime;
10
+ size?: DdgSize;
11
+ color?: DdgColor;
12
+ type?: DdgType;
13
+ layout?: DdgLayout;
14
+ license?: DdgLicense;
15
+ safeSearch?: boolean;
16
+ }
17
+ //#endregion
18
+ export { DdgColor, DdgLayout, DdgLicense, DdgSearchOptions, DdgSize, DdgTime, DdgType };
@@ -0,0 +1,18 @@
1
+ //#region src/image/ImageSearchClient.d.ts
2
+ type DdgTime = 'Day' | 'Week' | 'Month';
3
+ type DdgSize = 'Small' | 'Medium' | 'Large' | 'Wallpaper';
4
+ type DdgColor = 'color' | 'Monochrome';
5
+ type DdgType = 'photo' | 'clipart' | 'gif' | 'transparent' | 'line';
6
+ type DdgLayout = 'Square' | 'Tall' | 'Wide';
7
+ type DdgLicense = 'Any' | 'Public';
8
+ interface DdgSearchOptions {
9
+ time?: DdgTime;
10
+ size?: DdgSize;
11
+ color?: DdgColor;
12
+ type?: DdgType;
13
+ layout?: DdgLayout;
14
+ license?: DdgLicense;
15
+ safeSearch?: boolean;
16
+ }
17
+ //#endregion
18
+ export { DdgColor, DdgLayout, DdgLicense, DdgSearchOptions, DdgSize, DdgTime, DdgType };
@@ -0,0 +1,69 @@
1
+ import ImageSearchParser from "./ImageSearchParser.mjs";
2
+ import { AsyncResolvingHttpRequest, HttpMethod, HttpResponseReader } from "node-http-toolkit";
3
+ //#region src/image/ImageSearchClient.ts
4
+ var ImageSearchClient = class {
5
+ OPTION_NAMES = [
6
+ "time",
7
+ "size",
8
+ "color",
9
+ "type",
10
+ "layout",
11
+ "license"
12
+ ];
13
+ TOKEN_HEADERS = { dnt: "1" };
14
+ SEARCH_HEADERS = {
15
+ dnt: "1",
16
+ "accept-encoding": "gzip, deflate, sdch, br",
17
+ "x-requested-with": "XMLHttpRequest",
18
+ "accept-language": "en-GB,en-US;q=0.8,en;q=0.6,ms;q=0.4",
19
+ "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
20
+ accept: "application/json, text/javascript, */*; q=0.01",
21
+ referer: "https://duckduckgo.com/",
22
+ authority: "duckduckgo.com"
23
+ };
24
+ TOKEN_REGEX = /vqd=(?<vqd>[\d-]+)/;
25
+ _parser = new ImageSearchParser();
26
+ async generateToken(query) {
27
+ const params = new URLSearchParams();
28
+ params.append("q", query);
29
+ params.append("atb", "v299-1");
30
+ params.append("iar", "images");
31
+ params.append("iax", "images");
32
+ params.append("ia", "images");
33
+ const url = `https://duckduckgo.com/?${params.toString()}`;
34
+ const vqd = (await this.get(url, this.TOKEN_HEADERS)).match(this.TOKEN_REGEX)?.groups?.["vqd"];
35
+ if (vqd == null) throw new Error("Unable to read token from DuckDuckGo response.");
36
+ return vqd;
37
+ }
38
+ async imageSearch(query, token, options) {
39
+ const searchOptions = options ?? {};
40
+ const url = this.createSearchUrl(query, token, searchOptions);
41
+ const responseText = await this.get(url, this.SEARCH_HEADERS);
42
+ return this._parser.parse(responseText);
43
+ }
44
+ async get(url, headers) {
45
+ const response = await new AsyncResolvingHttpRequest(url, HttpMethod.GET, headers).resolve();
46
+ return new HttpResponseReader().readData(response);
47
+ }
48
+ createSearchUrl(query, token, options) {
49
+ const params = new URLSearchParams();
50
+ params.append("l", "de-de");
51
+ params.append("o", "json");
52
+ params.append("q", query);
53
+ params.append("vqd", token);
54
+ params.append("f", this.createImageSearchOptionsHeader(options));
55
+ params.append("p", options.safeSearch == true ? "1" : "-1");
56
+ return `https://duckduckgo.com/i.js?${params.toString()}`;
57
+ }
58
+ createImageSearchOptionsHeader(options) {
59
+ const optionValues = [];
60
+ for (const optionName of this.OPTION_NAMES) {
61
+ const optionValue = options[optionName]?.toString();
62
+ if (optionValue == null) optionValues.push("");
63
+ else optionValues.push(`${optionName}:${optionValue}`);
64
+ }
65
+ return optionValues.join(",");
66
+ }
67
+ };
68
+ //#endregion
69
+ export { ImageSearchClient as default };
@@ -0,0 +1,12 @@
1
+ const require_ImageSearchResult = require("./ImageSearchResult.cjs");
2
+ //#region src/image/ImageSearchParser.ts
3
+ var ImageSearchParser = class {
4
+ parse(responseText) {
5
+ const parsedResults = JSON.parse(responseText).results;
6
+ const searchResults = [];
7
+ for (const result of parsedResults) searchResults.push(new require_ImageSearchResult.default(result.thumbnail, result.image));
8
+ return searchResults;
9
+ }
10
+ };
11
+ //#endregion
12
+ exports.default = ImageSearchParser;
@@ -0,0 +1,12 @@
1
+ import ImageSearchResult from "./ImageSearchResult.mjs";
2
+ //#region src/image/ImageSearchParser.ts
3
+ var ImageSearchParser = class {
4
+ parse(responseText) {
5
+ const parsedResults = JSON.parse(responseText).results;
6
+ const searchResults = [];
7
+ for (const result of parsedResults) searchResults.push(new ImageSearchResult(result.thumbnail, result.image));
8
+ return searchResults;
9
+ }
10
+ };
11
+ //#endregion
12
+ export { ImageSearchParser as default };
@@ -0,0 +1,11 @@
1
+ //#region src/image/ImageSearchResult.ts
2
+ var ImageSearchResult = class {
3
+ thumbnailUrl;
4
+ imageUrl;
5
+ constructor(thumbnailUrl, imageUrl) {
6
+ this.thumbnailUrl = thumbnailUrl;
7
+ this.imageUrl = imageUrl;
8
+ }
9
+ };
10
+ //#endregion
11
+ exports.default = ImageSearchResult;
@@ -0,0 +1,8 @@
1
+ //#region src/image/ImageSearchResult.d.ts
2
+ declare class ImageSearchResult {
3
+ readonly thumbnailUrl: string;
4
+ readonly imageUrl: string;
5
+ constructor(thumbnailUrl: string, imageUrl: string);
6
+ }
7
+ //#endregion
8
+ export { ImageSearchResult };
@@ -0,0 +1,8 @@
1
+ //#region src/image/ImageSearchResult.d.ts
2
+ declare class ImageSearchResult {
3
+ readonly thumbnailUrl: string;
4
+ readonly imageUrl: string;
5
+ constructor(thumbnailUrl: string, imageUrl: string);
6
+ }
7
+ //#endregion
8
+ export { ImageSearchResult };
@@ -0,0 +1,11 @@
1
+ //#region src/image/ImageSearchResult.ts
2
+ var ImageSearchResult = class {
3
+ thumbnailUrl;
4
+ imageUrl;
5
+ constructor(thumbnailUrl, imageUrl) {
6
+ this.thumbnailUrl = thumbnailUrl;
7
+ this.imageUrl = imageUrl;
8
+ }
9
+ };
10
+ //#endregion
11
+ export { ImageSearchResult as default };
package/dist/index.cjs ADDED
@@ -0,0 +1,5 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_ImageSearchResult = require("./image/ImageSearchResult.cjs");
3
+ const require_DuckDuckGo = require("./DuckDuckGo.cjs");
4
+ exports.DuckDuckGo = require_DuckDuckGo.default;
5
+ exports.ImageSearchResult = require_ImageSearchResult.default;
@@ -0,0 +1,4 @@
1
+ import { ImageSearchResult } from "./image/ImageSearchResult.cjs";
2
+ import { DdgColor, DdgLayout, DdgLicense, DdgSearchOptions, DdgSize, DdgTime, DdgType } from "./image/ImageSearchClient.cjs";
3
+ import { DuckDuckGo } from "./DuckDuckGo.cjs";
4
+ export { type DdgColor, type DdgLayout, type DdgLicense, type DdgSearchOptions, type DdgSize, type DdgTime, type DdgType, DuckDuckGo, ImageSearchResult };
@@ -0,0 +1,4 @@
1
+ import { ImageSearchResult } from "./image/ImageSearchResult.mjs";
2
+ import { DdgColor, DdgLayout, DdgLicense, DdgSearchOptions, DdgSize, DdgTime, DdgType } from "./image/ImageSearchClient.mjs";
3
+ import { DuckDuckGo } from "./DuckDuckGo.mjs";
4
+ export { type DdgColor, type DdgLayout, type DdgLicense, type DdgSearchOptions, type DdgSize, type DdgTime, type DdgType, DuckDuckGo, ImageSearchResult };
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import ImageSearchResult from "./image/ImageSearchResult.mjs";
2
+ import DuckDuckGo from "./DuckDuckGo.mjs";
3
+ export { DuckDuckGo, ImageSearchResult };
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "duck-poacher",
3
+ "version": "0.6.0",
4
+ "description": "DuckDuckGo image search scraper for Node.js.",
5
+ "type": "module",
6
+ "types": "./dist/index.d.mts",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.mjs",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.mts",
13
+ "default": "./dist/index.mjs"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "sideEffects": false,
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsdown",
28
+ "typecheck": "tsc --noEmit",
29
+ "check:exports": "attw --pack .",
30
+ "prepublishOnly": "npm test && npm run typecheck && npm run build && npm run check:exports",
31
+ "dev": "tsdown --watch",
32
+ "test": "tsx --test \"test/**/*.test.ts\"",
33
+ "test:coverage": "tsx --test --experimental-test-coverage \"test/**/*.test.ts\"",
34
+ "test:watch": "tsx --test --watch \"test/**/*.test.ts\"",
35
+ "lint": "eslint .",
36
+ "lint:fix": "eslint . --fix",
37
+ "format": "dprint fmt",
38
+ "format:check": "dprint check"
39
+ },
40
+ "keywords": [
41
+ "duckduckgo",
42
+ "image-search",
43
+ "images",
44
+ "search",
45
+ "scraper",
46
+ "scraping"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/lars-dreier/duck-poacher.git"
51
+ },
52
+ "bugs": {
53
+ "url": "https://github.com/lars-dreier/duck-poacher/issues"
54
+ },
55
+ "homepage": "https://github.com/lars-dreier/duck-poacher#readme",
56
+ "author": {
57
+ "name": "Lars Dreier",
58
+ "email": "ldreier@posteo.de"
59
+ },
60
+ "license": "ISC",
61
+ "engines": {
62
+ "node": ">=18"
63
+ },
64
+ "devDependencies": {
65
+ "@arethetypeswrong/cli": "0.18.3",
66
+ "@eslint/js": "10.0.1",
67
+ "@types/node": "25.9.3",
68
+ "dprint": "0.54.0",
69
+ "eslint": "10.5.0",
70
+ "eslint-config-prettier": "10.1.8",
71
+ "globals": "17.6.0",
72
+ "tsdown": "0.22.2",
73
+ "tsx": "4.22.4",
74
+ "typescript": "6.0.3",
75
+ "typescript-eslint": "8.61.0"
76
+ },
77
+ "dependencies": {
78
+ "node-http-toolkit": "1.0.1"
79
+ }
80
+ }