llm-search-tools 1.1.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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +244 -0
  3. package/dist/index.d.ts +18 -0
  4. package/dist/index.js +40 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/integration.test.d.ts +1 -0
  7. package/dist/integration.test.js +237 -0
  8. package/dist/modules/answerbox.test.d.ts +1 -0
  9. package/dist/modules/answerbox.test.js +105 -0
  10. package/dist/modules/autocomplete.d.ts +11 -0
  11. package/dist/modules/autocomplete.js +159 -0
  12. package/dist/modules/autocomplete.test.d.ts +1 -0
  13. package/dist/modules/autocomplete.test.js +188 -0
  14. package/dist/modules/common.d.ts +26 -0
  15. package/dist/modules/common.js +263 -0
  16. package/dist/modules/common.test.d.ts +1 -0
  17. package/dist/modules/common.test.js +87 -0
  18. package/dist/modules/crawl.d.ts +9 -0
  19. package/dist/modules/crawl.js +117 -0
  20. package/dist/modules/crawl.test.d.ts +1 -0
  21. package/dist/modules/crawl.test.js +48 -0
  22. package/dist/modules/events.d.ts +8 -0
  23. package/dist/modules/events.js +129 -0
  24. package/dist/modules/events.test.d.ts +1 -0
  25. package/dist/modules/events.test.js +104 -0
  26. package/dist/modules/finance.d.ts +10 -0
  27. package/dist/modules/finance.js +20 -0
  28. package/dist/modules/finance.test.d.ts +1 -0
  29. package/dist/modules/finance.test.js +77 -0
  30. package/dist/modules/flights.d.ts +8 -0
  31. package/dist/modules/flights.js +135 -0
  32. package/dist/modules/flights.test.d.ts +1 -0
  33. package/dist/modules/flights.test.js +128 -0
  34. package/dist/modules/hackernews.d.ts +8 -0
  35. package/dist/modules/hackernews.js +87 -0
  36. package/dist/modules/hackernews.js.map +1 -0
  37. package/dist/modules/images.test.d.ts +1 -0
  38. package/dist/modules/images.test.js +145 -0
  39. package/dist/modules/integrations.test.d.ts +1 -0
  40. package/dist/modules/integrations.test.js +93 -0
  41. package/dist/modules/media.d.ts +11 -0
  42. package/dist/modules/media.js +132 -0
  43. package/dist/modules/media.test.d.ts +1 -0
  44. package/dist/modules/media.test.js +186 -0
  45. package/dist/modules/news.d.ts +3 -0
  46. package/dist/modules/news.js +39 -0
  47. package/dist/modules/news.test.d.ts +1 -0
  48. package/dist/modules/news.test.js +88 -0
  49. package/dist/modules/parser.d.ts +19 -0
  50. package/dist/modules/parser.js +361 -0
  51. package/dist/modules/parser.test.d.ts +1 -0
  52. package/dist/modules/parser.test.js +151 -0
  53. package/dist/modules/reddit.d.ts +21 -0
  54. package/dist/modules/reddit.js +107 -0
  55. package/dist/modules/scrape.d.ts +16 -0
  56. package/dist/modules/scrape.js +272 -0
  57. package/dist/modules/scrape.test.d.ts +1 -0
  58. package/dist/modules/scrape.test.js +232 -0
  59. package/dist/modules/scraper.d.ts +12 -0
  60. package/dist/modules/scraper.js +640 -0
  61. package/dist/modules/scrapers/anidb.d.ts +8 -0
  62. package/dist/modules/scrapers/anidb.js +156 -0
  63. package/dist/modules/scrapers/duckduckgo.d.ts +6 -0
  64. package/dist/modules/scrapers/duckduckgo.js +284 -0
  65. package/dist/modules/scrapers/google-news.d.ts +2 -0
  66. package/dist/modules/scrapers/google-news.js +60 -0
  67. package/dist/modules/scrapers/google.d.ts +6 -0
  68. package/dist/modules/scrapers/google.js +211 -0
  69. package/dist/modules/scrapers/searxng.d.ts +2 -0
  70. package/dist/modules/scrapers/searxng.js +93 -0
  71. package/dist/modules/scrapers/thetvdb.d.ts +3 -0
  72. package/dist/modules/scrapers/thetvdb.js +147 -0
  73. package/dist/modules/scrapers/tmdb.d.ts +3 -0
  74. package/dist/modules/scrapers/tmdb.js +172 -0
  75. package/dist/modules/scrapers/yahoo-finance.d.ts +2 -0
  76. package/dist/modules/scrapers/yahoo-finance.js +33 -0
  77. package/dist/modules/search.d.ts +5 -0
  78. package/dist/modules/search.js +45 -0
  79. package/dist/modules/search.js.map +1 -0
  80. package/dist/modules/search.test.d.ts +1 -0
  81. package/dist/modules/search.test.js +219 -0
  82. package/dist/modules/urbandictionary.d.ts +12 -0
  83. package/dist/modules/urbandictionary.js +26 -0
  84. package/dist/modules/webpage.d.ts +4 -0
  85. package/dist/modules/webpage.js +150 -0
  86. package/dist/modules/webpage.js.map +1 -0
  87. package/dist/modules/wikipedia.d.ts +5 -0
  88. package/dist/modules/wikipedia.js +85 -0
  89. package/dist/modules/wikipedia.js.map +1 -0
  90. package/dist/scripts/interactive-search.d.ts +1 -0
  91. package/dist/scripts/interactive-search.js +98 -0
  92. package/dist/test.d.ts +1 -0
  93. package/dist/test.js +179 -0
  94. package/dist/test.js.map +1 -0
  95. package/dist/testBraveSearch.d.ts +1 -0
  96. package/dist/testBraveSearch.js +34 -0
  97. package/dist/testDuckDuckGo.d.ts +1 -0
  98. package/dist/testDuckDuckGo.js +52 -0
  99. package/dist/testEcosia.d.ts +1 -0
  100. package/dist/testEcosia.js +57 -0
  101. package/dist/testSearchModule.d.ts +1 -0
  102. package/dist/testSearchModule.js +95 -0
  103. package/dist/testwebpage.d.ts +1 -0
  104. package/dist/testwebpage.js +81 -0
  105. package/dist/types.d.ts +174 -0
  106. package/dist/types.js +3 -0
  107. package/dist/types.js.map +1 -0
  108. package/dist/utils/createTestDocx.d.ts +1 -0
  109. package/dist/utils/createTestDocx.js +58 -0
  110. package/dist/utils/htmlcleaner.d.ts +20 -0
  111. package/dist/utils/htmlcleaner.js +172 -0
  112. package/docs/README.md +275 -0
  113. package/docs/autocomplete.md +73 -0
  114. package/docs/crawling.md +88 -0
  115. package/docs/events.md +58 -0
  116. package/docs/examples.md +158 -0
  117. package/docs/finance.md +60 -0
  118. package/docs/flights.md +71 -0
  119. package/docs/hackernews.md +121 -0
  120. package/docs/media.md +87 -0
  121. package/docs/news.md +75 -0
  122. package/docs/parser.md +197 -0
  123. package/docs/scraper.md +347 -0
  124. package/docs/search.md +106 -0
  125. package/docs/wikipedia.md +91 -0
  126. package/package.json +97 -0
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.searchFlights = searchFlights;
4
+ const common_1 = require("./common");
5
+ /**
6
+ * Search for flights on Google Flights
7
+ * @param query Search query (e.g. "flights from JFK to LHR") or options object
8
+ * @param options Search options
9
+ * @returns Promise<FlightResult>
10
+ */
11
+ async function searchFlights(query, options = {}) {
12
+ let opts;
13
+ if (typeof query === "object") {
14
+ opts = { ...query, ...options };
15
+ }
16
+ else {
17
+ opts = options;
18
+ // Attempt to parse query if provided as string, but relying on explicit options is better
19
+ }
20
+ // Construct URL
21
+ // Basic format: https://www.google.com/travel/flights?q=Flights%20to%20LHR%20from%20JFK%20on%202023-05-01
22
+ // Or more specific parameters
23
+ let url = "https://www.google.com/travel/flights";
24
+ const params = [];
25
+ if (opts.from && opts.to) {
26
+ let q = `Flights to ${opts.to} from ${opts.from}`;
27
+ if (opts.departureDate) {
28
+ q += ` on ${opts.departureDate}`;
29
+ }
30
+ if (opts.returnDate) {
31
+ q += ` returning ${opts.returnDate}`;
32
+ }
33
+ params.push(`q=${encodeURIComponent(q)}`);
34
+ }
35
+ else if (typeof query === "string") {
36
+ params.push(`q=${encodeURIComponent(query)}`);
37
+ }
38
+ if (params.length > 0) {
39
+ url += `?${params.join("&")}`;
40
+ }
41
+ const proxy = (0, common_1.parseProxyConfig)(opts.proxy);
42
+ const browser = await (0, common_1.createStealthBrowser)(proxy || undefined);
43
+ try {
44
+ const page = await browser.newPage();
45
+ await page.setViewport({ width: 1920, height: 1080 });
46
+ await page.setExtraHTTPHeaders((0, common_1.createRealisticHeaders)());
47
+ // Navigate to Google Flights
48
+ await page.goto(url, { waitUntil: "networkidle2", timeout: opts.timeout || 30000 });
49
+ // Handle cookie consent if present (common in EU)
50
+ try {
51
+ const consentButton = await page.waitForSelector('button[aria-label="Accept all"]', { timeout: 5000 });
52
+ if (consentButton) {
53
+ await consentButton.click();
54
+ await page.waitForNavigation({ waitUntil: "networkidle2" }).catch(() => { }); // Wait a bit if nav happens
55
+ }
56
+ }
57
+ catch {
58
+ // No consent button found, proceed
59
+ }
60
+ // Wait for results to load
61
+ // The selector for flight lists often changes, but usually there are accessible roles
62
+ try {
63
+ await page.waitForSelector('li[class*="pIav2d"]', { timeout: 10000 });
64
+ }
65
+ catch {
66
+ // Fallback or retry
67
+ }
68
+ // Extract flights
69
+ const flights = await page.evaluate(() => {
70
+ const results = [];
71
+ // Select flight list items
72
+ // Note: CSS classes in Google Flights are obfuscated and change often.
73
+ // We'll try to use more stable structure selectors where possible, or partial class matches.
74
+ // Current observation of Google Flights DOM structure (approximate)
75
+ const listItems = document.querySelectorAll('li[class*="pIav2d"]'); // Common container class for list items
76
+ listItems.forEach((item) => {
77
+ try {
78
+ // Airline & Times often in specific aria-labels or text content
79
+ const text = item.textContent || "";
80
+ // Attempt to extract structured data
81
+ // This is brittle and might need constant updates
82
+ // Look for time pattern (e.g. 10:00 AM)
83
+ const timeRegex = /(\d{1,2}:\d{2}\s*[AP]M)/g;
84
+ const times = text.match(timeRegex);
85
+ // Look for price
86
+ const priceRegex = /([$€£]\d+(?:,\d+)?)/;
87
+ const priceMatch = text.match(priceRegex);
88
+ // Look for duration (e.g. 7 hr 30 min)
89
+ const durationRegex = /(\d+\s*hr\s*\d*\s*min)/;
90
+ const durationMatch = text.match(durationRegex);
91
+ // Look for stops
92
+ const stops = text.includes("Non-stop")
93
+ ? "Non-stop"
94
+ : text.includes("1 stop")
95
+ ? "1 stop"
96
+ : text.includes("stops")
97
+ ? "2+ stops"
98
+ : "Unknown";
99
+ // Extract Airline (usually first text or near image)
100
+ // Simple heuristic: Text lines before the time
101
+ const airline = text.split(timeRegex)[0]?.trim() || "Unknown Airline";
102
+ if (times && times.length >= 2 && priceMatch) {
103
+ results.push({
104
+ airline, // Heuristic
105
+ departureTime: times[0],
106
+ arrivalTime: times[1],
107
+ duration: durationMatch ? durationMatch[0] : "Unknown",
108
+ price: priceMatch[0],
109
+ stops,
110
+ });
111
+ }
112
+ }
113
+ catch {
114
+ // Skip malformed items
115
+ }
116
+ });
117
+ return results;
118
+ });
119
+ return {
120
+ flights: flights.slice(0, opts.limit || 10),
121
+ url,
122
+ source: "google-flights",
123
+ };
124
+ }
125
+ catch (error) {
126
+ throw {
127
+ message: `Failed to search flights: ${error.message}`,
128
+ code: "FLIGHT_SEARCH_ERROR",
129
+ originalError: error,
130
+ };
131
+ }
132
+ finally {
133
+ await browser.close();
134
+ }
135
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const vitest_1 = require("vitest");
37
+ const flights_1 = require("./flights");
38
+ const common = __importStar(require("./common"));
39
+ // Mock common module
40
+ vitest_1.vi.mock("./common", async () => {
41
+ const actual = await vitest_1.vi.importActual("./common");
42
+ return {
43
+ ...actual,
44
+ createStealthBrowser: vitest_1.vi.fn(),
45
+ };
46
+ });
47
+ (0, vitest_1.describe)("Flights Module", () => {
48
+ let mockPage;
49
+ let mockBrowser;
50
+ (0, vitest_1.beforeEach)(() => {
51
+ vitest_1.vi.clearAllMocks();
52
+ mockPage = {
53
+ setViewport: vitest_1.vi.fn(),
54
+ setExtraHTTPHeaders: vitest_1.vi.fn(),
55
+ goto: vitest_1.vi.fn(),
56
+ waitForSelector: vitest_1.vi.fn(),
57
+ evaluate: vitest_1.vi.fn(),
58
+ click: vitest_1.vi.fn(),
59
+ waitForNavigation: vitest_1.vi.fn(),
60
+ $: vitest_1.vi.fn(),
61
+ };
62
+ mockBrowser = {
63
+ newPage: vitest_1.vi.fn().mockResolvedValue(mockPage),
64
+ close: vitest_1.vi.fn(),
65
+ };
66
+ vitest_1.vi.spyOn(common, "createStealthBrowser").mockResolvedValue(mockBrowser);
67
+ });
68
+ (0, vitest_1.it)("should search for flights with query string", async () => {
69
+ const mockFlights = [
70
+ {
71
+ airline: "Test Air",
72
+ departureTime: "10:00 AM",
73
+ arrivalTime: "02:00 PM",
74
+ duration: "4 hr",
75
+ price: "$200",
76
+ stops: "Non-stop",
77
+ },
78
+ ];
79
+ mockPage.evaluate.mockResolvedValue(mockFlights);
80
+ const result = await (0, flights_1.searchFlights)("flights from JFK to LHR");
81
+ (0, vitest_1.expect)(common.createStealthBrowser).toHaveBeenCalled();
82
+ (0, vitest_1.expect)(mockPage.goto).toHaveBeenCalledWith(vitest_1.expect.stringContaining("google.com/travel/flights"), vitest_1.expect.any(Object));
83
+ (0, vitest_1.expect)(result.flights).toEqual(mockFlights);
84
+ (0, vitest_1.expect)(result.source).toBe("google-flights");
85
+ });
86
+ (0, vitest_1.it)("should search for flights with structured options", async () => {
87
+ const mockFlights = [
88
+ {
89
+ airline: "Test Air",
90
+ departureTime: "10:00 AM",
91
+ arrivalTime: "02:00 PM",
92
+ duration: "4 hr",
93
+ price: "$200",
94
+ stops: "Non-stop",
95
+ },
96
+ ];
97
+ mockPage.evaluate.mockResolvedValue(mockFlights);
98
+ const options = {
99
+ from: "JFK",
100
+ to: "LHR",
101
+ departureDate: "2025-05-01",
102
+ returnDate: "2025-05-10",
103
+ };
104
+ const result = await (0, flights_1.searchFlights)(options);
105
+ (0, vitest_1.expect)(mockPage.goto).toHaveBeenCalledWith(vitest_1.expect.stringMatching(/q=Flights%20to%20LHR%20from%20JFK%20on%202025-05-01%20returning%202025-05-10/), vitest_1.expect.any(Object));
106
+ (0, vitest_1.expect)(result.flights).toEqual(mockFlights);
107
+ });
108
+ (0, vitest_1.it)("should handle scraping errors gracefully", async () => {
109
+ mockPage.goto.mockRejectedValue(new Error("Navigation failed"));
110
+ await (0, vitest_1.expect)((0, flights_1.searchFlights)("query")).rejects.toThrow("Failed to search flights");
111
+ (0, vitest_1.expect)(mockBrowser.close).toHaveBeenCalled();
112
+ });
113
+ (0, vitest_1.it)("should attempt to click consent button if found", async () => {
114
+ // Mock waitForSelector implementation for consent button
115
+ mockPage.waitForSelector.mockImplementation((selector) => {
116
+ if (selector.includes("Accept all")) {
117
+ return Promise.resolve({ click: vitest_1.vi.fn() });
118
+ }
119
+ return Promise.resolve(null); // Return null for other selectors or throw depending on logic
120
+ });
121
+ // Mock evaluate to return empty list so it finishes
122
+ mockPage.evaluate.mockResolvedValue([]);
123
+ await (0, flights_1.searchFlights)("query");
124
+ // We can't easily check if the exact element method was called since we return a new object
125
+ // But we can check if the flow continued
126
+ (0, vitest_1.expect)(mockPage.goto).toHaveBeenCalled();
127
+ });
128
+ });
@@ -0,0 +1,8 @@
1
+ import { HackerNewsResult } from "../types";
2
+ export declare function getTopStories(limit?: number): Promise<HackerNewsResult[]>;
3
+ export declare function getNewStories(limit?: number): Promise<HackerNewsResult[]>;
4
+ export declare function getBestStories(limit?: number): Promise<HackerNewsResult[]>;
5
+ export declare function getAskStories(limit?: number): Promise<HackerNewsResult[]>;
6
+ export declare function getShowStories(limit?: number): Promise<HackerNewsResult[]>;
7
+ export declare function getJobStories(limit?: number): Promise<HackerNewsResult[]>;
8
+ export declare function getStoryById(id: number): Promise<HackerNewsResult>;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ // hackernews.ts - get them spicy tech stories n stuff
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.getTopStories = getTopStories;
5
+ exports.getNewStories = getNewStories;
6
+ exports.getBestStories = getBestStories;
7
+ exports.getAskStories = getAskStories;
8
+ exports.getShowStories = getShowStories;
9
+ exports.getJobStories = getJobStories;
10
+ exports.getStoryById = getStoryById;
11
+ const API_BASE = "https://hacker-news.firebaseio.com/v0";
12
+ // convert hn item to our format
13
+ function formatHNResult(item, requestedId) {
14
+ if (!item) {
15
+ return {
16
+ id: requestedId,
17
+ title: "Unknown Story",
18
+ url: requestedId ? `https://news.ycombinator.com/item?id=${requestedId}` : "https://news.ycombinator.com",
19
+ snippet: "",
20
+ source: "hackernews",
21
+ };
22
+ }
23
+ return {
24
+ id: item.id,
25
+ title: item.title || "Untitled",
26
+ url: item.url || `https://news.ycombinator.com/item?id=${item.id}`,
27
+ snippet: item.text || "",
28
+ source: "hackernews",
29
+ points: item.score || 0,
30
+ author: item.by || "anonymous",
31
+ comments: item.descendants || 0,
32
+ time: item.time ? new Date(item.time * 1000) : new Date(),
33
+ };
34
+ }
35
+ async function fetchItem(id) {
36
+ const response = await fetch(`${API_BASE}/item/${id}.json`);
37
+ return response.json();
38
+ }
39
+ async function fetchStories(type, limit = 10) {
40
+ try {
41
+ const response = await fetch(`${API_BASE}/${type}.json`);
42
+ const ids = (await response.json());
43
+ // get first 'limit' number of stories
44
+ const storyIds = ids.slice(0, limit);
45
+ // fetch all stories in parallel
46
+ const stories = await Promise.all(storyIds.map(fetchItem));
47
+ return stories.filter((story) => !!story).map(formatHNResult);
48
+ }
49
+ catch (err) {
50
+ throw {
51
+ message: `failed to get ${type} stories :(`,
52
+ code: `HN_${type.toUpperCase()}_ERROR`,
53
+ originalError: err,
54
+ };
55
+ }
56
+ }
57
+ async function getTopStories(limit = 10) {
58
+ return fetchStories("topstories", limit);
59
+ }
60
+ async function getNewStories(limit = 10) {
61
+ return fetchStories("newstories", limit);
62
+ }
63
+ async function getBestStories(limit = 10) {
64
+ return fetchStories("beststories", limit);
65
+ }
66
+ async function getAskStories(limit = 10) {
67
+ return fetchStories("askstories", limit);
68
+ }
69
+ async function getShowStories(limit = 10) {
70
+ return fetchStories("showstories", limit);
71
+ }
72
+ async function getJobStories(limit = 10) {
73
+ return fetchStories("jobstories", limit);
74
+ }
75
+ async function getStoryById(id) {
76
+ try {
77
+ const story = await fetchItem(id);
78
+ return formatHNResult(story, id);
79
+ }
80
+ catch (err) {
81
+ throw {
82
+ message: "failed to get story :(",
83
+ code: "HN_STORY_ERROR",
84
+ originalError: err,
85
+ };
86
+ }
87
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hackernews.js","sourceRoot":"","sources":["../../src/modules/hackernews.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const vitest_1 = require("vitest");
37
+ const search_1 = require("./search");
38
+ const duckduckgo_1 = require("./scrapers/duckduckgo");
39
+ const google_1 = require("./scrapers/google");
40
+ const searxng_1 = require("./scrapers/searxng");
41
+ const common = __importStar(require("./common"));
42
+ // Mock common module
43
+ vitest_1.vi.mock("./common", async () => {
44
+ const actual = await vitest_1.vi.importActual("./common");
45
+ return {
46
+ ...actual,
47
+ createStealthBrowser: vitest_1.vi.fn(),
48
+ fetchWithDetection: vitest_1.vi.fn(),
49
+ };
50
+ });
51
+ (0, vitest_1.describe)("Image Search Integration", () => {
52
+ let mockPage;
53
+ let mockBrowser;
54
+ (0, vitest_1.beforeEach)(() => {
55
+ vitest_1.vi.clearAllMocks();
56
+ mockPage = {
57
+ setViewport: vitest_1.vi.fn(),
58
+ setExtraHTTPHeaders: vitest_1.vi.fn(),
59
+ goto: vitest_1.vi.fn(),
60
+ waitForSelector: vitest_1.vi.fn(),
61
+ evaluate: vitest_1.vi.fn(),
62
+ close: vitest_1.vi.fn(),
63
+ };
64
+ mockBrowser = {
65
+ newPage: vitest_1.vi.fn().mockResolvedValue(mockPage),
66
+ close: vitest_1.vi.fn(),
67
+ };
68
+ vitest_1.vi.spyOn(common, "createStealthBrowser").mockResolvedValue(mockBrowser);
69
+ });
70
+ (0, vitest_1.it)("should search google images correctly", async () => {
71
+ const mockImages = [
72
+ {
73
+ title: "Test Image",
74
+ url: "https://example.com/page",
75
+ snippet: "Test Image",
76
+ imageUrl: "https://example.com/image.jpg",
77
+ thumbnailUrl: "https://example.com/thumb.jpg",
78
+ source: "google-images",
79
+ },
80
+ ];
81
+ mockPage.evaluate.mockResolvedValue(mockImages);
82
+ const results = await (0, google_1.searchGoogle)("cats", { category: "images" });
83
+ (0, vitest_1.expect)(common.createStealthBrowser).toHaveBeenCalled();
84
+ (0, vitest_1.expect)(mockPage.goto).toHaveBeenCalledWith(vitest_1.expect.stringContaining("google.com/search?q=cats&tbm=isch"), vitest_1.expect.any(Object));
85
+ (0, vitest_1.expect)(results).toEqual(mockImages);
86
+ });
87
+ (0, vitest_1.it)("should search duckduckgo images correctly", async () => {
88
+ const mockImages = [
89
+ {
90
+ title: "Test DDG Image",
91
+ url: "https://example.com/ddg-page",
92
+ snippet: "Test DDG Image",
93
+ imageUrl: "https://example.com/ddg-image.jpg",
94
+ thumbnailUrl: "https://example.com/ddg-image.jpg",
95
+ source: "duckduckgo-images",
96
+ },
97
+ ];
98
+ mockPage.evaluate.mockResolvedValue(mockImages);
99
+ const results = await (0, duckduckgo_1.searchDuckDuckGo)("dogs", { category: "images" });
100
+ (0, vitest_1.expect)(common.createStealthBrowser).toHaveBeenCalled();
101
+ // DDG image search url param
102
+ (0, vitest_1.expect)(mockPage.goto).toHaveBeenCalledWith(vitest_1.expect.stringContaining("duckduckgo.com/?q=dogs&iax=images&ia=images"), vitest_1.expect.any(Object));
103
+ (0, vitest_1.expect)(results).toEqual(mockImages);
104
+ });
105
+ (0, vitest_1.it)("should search searxng images correctly", async () => {
106
+ const mockResponse = {
107
+ results: [
108
+ {
109
+ title: "Test SearxNG Image",
110
+ url: "https://example.com/searx-page",
111
+ content: "Test SearxNG Image",
112
+ img_src: "https://example.com/searx-image.jpg",
113
+ thumbnail_src: "https://example.com/searx-thumb.jpg",
114
+ },
115
+ ],
116
+ };
117
+ common.fetchWithDetection.mockResolvedValue({
118
+ headers: {},
119
+ body: JSON.stringify(mockResponse),
120
+ });
121
+ const results = await (0, searxng_1.searchSearxNG)("lizards", { category: "images" });
122
+ (0, vitest_1.expect)(common.fetchWithDetection).toHaveBeenCalledWith(vitest_1.expect.stringContaining("categories=images"), vitest_1.expect.any(Object));
123
+ (0, vitest_1.expect)(results[0]).toMatchObject({
124
+ title: "Test SearxNG Image",
125
+ imageUrl: "https://example.com/searx-image.jpg",
126
+ source: "searxng-images",
127
+ });
128
+ });
129
+ (0, vitest_1.it)("should delegate to scrapers via main search function", async () => {
130
+ const mockImages = [
131
+ {
132
+ title: "Test DDG Image",
133
+ url: "https://example.com/ddg-page",
134
+ snippet: "Test DDG Image",
135
+ imageUrl: "https://example.com/ddg-image.jpg",
136
+ thumbnailUrl: "https://example.com/ddg-image.jpg",
137
+ source: "duckduckgo-images",
138
+ },
139
+ ];
140
+ mockPage.evaluate.mockResolvedValue(mockImages);
141
+ // Should default to DDG first
142
+ const results = await (0, search_1.search)("birds", { category: "images" });
143
+ (0, vitest_1.expect)(results[0].source).toContain("images");
144
+ });
145
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vitest_1 = require("vitest");
7
+ const hackernews_1 = require("./hackernews");
8
+ const wikipedia_1 = require("./wikipedia");
9
+ const wikipedia_2 = __importDefault(require("wikipedia"));
10
+ // Mock dependencies
11
+ vitest_1.vi.mock("wikipedia");
12
+ // Global fetch mock
13
+ global.fetch = vitest_1.vi.fn();
14
+ (0, vitest_1.describe)("Integrations Module", () => {
15
+ (0, vitest_1.beforeEach)(() => {
16
+ vitest_1.vi.resetAllMocks();
17
+ });
18
+ (0, vitest_1.describe)("HackerNews", () => {
19
+ (0, vitest_1.it)("should fetch top stories", async () => {
20
+ // Mock ID list response
21
+ global.fetch
22
+ .mockResolvedValueOnce({
23
+ json: () => Promise.resolve([1, 2]),
24
+ })
25
+ // Mock Item 1
26
+ .mockResolvedValueOnce({
27
+ json: () => Promise.resolve({ id: 1, title: "Story 1", url: "http://story1.com", score: 100 }),
28
+ })
29
+ // Mock Item 2
30
+ .mockResolvedValueOnce({
31
+ json: () => Promise.resolve({ id: 2, title: "Story 2", url: "http://story2.com", score: 200 }),
32
+ });
33
+ const stories = await (0, hackernews_1.getTopStories)(2);
34
+ (0, vitest_1.expect)(stories).toHaveLength(2);
35
+ (0, vitest_1.expect)(stories[0].title).toBe("Story 1");
36
+ (0, vitest_1.expect)(stories[1].title).toBe("Story 2");
37
+ (0, vitest_1.expect)(global.fetch).toHaveBeenCalledTimes(3);
38
+ });
39
+ (0, vitest_1.it)("should fetch story by ID", async () => {
40
+ global.fetch.mockResolvedValue({
41
+ json: () => Promise.resolve({ id: 123, title: "Specific Story", text: "Content" }),
42
+ });
43
+ const story = await (0, hackernews_1.getStoryById)(123);
44
+ (0, vitest_1.expect)(story.title).toBe("Specific Story");
45
+ (0, vitest_1.expect)(story.snippet).toBe("Content");
46
+ });
47
+ (0, vitest_1.it)("should handle HN errors", async () => {
48
+ global.fetch.mockRejectedValue(new Error("API Error"));
49
+ await (0, vitest_1.expect)((0, hackernews_1.getStoryById)(1)).rejects.toThrow("failed to get story");
50
+ });
51
+ });
52
+ (0, vitest_1.describe)("Wikipedia", () => {
53
+ (0, vitest_1.it)("should search wikipedia", async () => {
54
+ const mockResults = {
55
+ results: [{ title: "Result 1" }, { title: "Result 2" }],
56
+ };
57
+ const mockSummary = {
58
+ title: "Result 1",
59
+ extract: "Summary 1",
60
+ thumbnail: { source: "img.jpg" },
61
+ };
62
+ wikipedia_2.default.search.mockResolvedValue(mockResults);
63
+ wikipedia_2.default.summary.mockResolvedValue(mockSummary);
64
+ const results = await (0, wikipedia_1.wikiSearch)("query", 2);
65
+ (0, vitest_1.expect)(results).toHaveLength(2);
66
+ (0, vitest_1.expect)(results[0].title).toBe("Result 1");
67
+ (0, vitest_1.expect)(results[0].snippet).toBe("Summary 1");
68
+ (0, vitest_1.expect)(wikipedia_2.default.search).toHaveBeenCalledWith("query", { limit: 2 });
69
+ });
70
+ (0, vitest_1.it)("should get wikipedia content", async () => {
71
+ const mockPage = {
72
+ content: vitest_1.vi.fn().mockResolvedValue("Page Content"),
73
+ };
74
+ wikipedia_2.default.page.mockResolvedValue(mockPage);
75
+ const content = await (0, wikipedia_1.wikiGetContent)("Test Page");
76
+ (0, vitest_1.expect)(content).toBe("Page Content");
77
+ (0, vitest_1.expect)(wikipedia_2.default.page).toHaveBeenCalledWith("Test Page");
78
+ });
79
+ (0, vitest_1.it)("should get wikipedia summary", async () => {
80
+ wikipedia_2.default.summary.mockResolvedValue({
81
+ title: "Summary Title",
82
+ extract: "Summary Text",
83
+ });
84
+ const summary = await (0, wikipedia_1.wikiGetSummary)("Test Page");
85
+ (0, vitest_1.expect)(summary.title).toBe("Summary Title");
86
+ (0, vitest_1.expect)(summary.extract).toBe("Summary Text");
87
+ });
88
+ (0, vitest_1.it)("should handle Wikipedia errors", async () => {
89
+ wikipedia_2.default.page.mockRejectedValue(new Error("Wiki Error"));
90
+ await (0, vitest_1.expect)((0, wikipedia_1.wikiGetContent)("Error Page")).rejects.toThrow("failed to get wikipedia content");
91
+ });
92
+ });
93
+ });
@@ -0,0 +1,11 @@
1
+ import { MediaResult, MediaSearchOptions } from "../types";
2
+ /**
3
+ * Unified Media Search
4
+ * Coordinates between TMDB, TheTVDB, and AniDB with fallback logic.
5
+ */
6
+ export declare function searchMedia(query: string, options?: MediaSearchOptions): Promise<MediaResult[]>;
7
+ /**
8
+ * Get detailed information for a media result.
9
+ * Automatically determines the source based on the URL or accepts an explicit source.
10
+ */
11
+ export declare function getMediaDetails(url: string, source?: "tmdb" | "thetvdb" | "anidb", options?: MediaSearchOptions): Promise<Partial<MediaResult>>;