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.
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.test.d.ts +1 -0
- package/dist/integration.test.js +237 -0
- package/dist/modules/answerbox.test.d.ts +1 -0
- package/dist/modules/answerbox.test.js +105 -0
- package/dist/modules/autocomplete.d.ts +11 -0
- package/dist/modules/autocomplete.js +159 -0
- package/dist/modules/autocomplete.test.d.ts +1 -0
- package/dist/modules/autocomplete.test.js +188 -0
- package/dist/modules/common.d.ts +26 -0
- package/dist/modules/common.js +263 -0
- package/dist/modules/common.test.d.ts +1 -0
- package/dist/modules/common.test.js +87 -0
- package/dist/modules/crawl.d.ts +9 -0
- package/dist/modules/crawl.js +117 -0
- package/dist/modules/crawl.test.d.ts +1 -0
- package/dist/modules/crawl.test.js +48 -0
- package/dist/modules/events.d.ts +8 -0
- package/dist/modules/events.js +129 -0
- package/dist/modules/events.test.d.ts +1 -0
- package/dist/modules/events.test.js +104 -0
- package/dist/modules/finance.d.ts +10 -0
- package/dist/modules/finance.js +20 -0
- package/dist/modules/finance.test.d.ts +1 -0
- package/dist/modules/finance.test.js +77 -0
- package/dist/modules/flights.d.ts +8 -0
- package/dist/modules/flights.js +135 -0
- package/dist/modules/flights.test.d.ts +1 -0
- package/dist/modules/flights.test.js +128 -0
- package/dist/modules/hackernews.d.ts +8 -0
- package/dist/modules/hackernews.js +87 -0
- package/dist/modules/hackernews.js.map +1 -0
- package/dist/modules/images.test.d.ts +1 -0
- package/dist/modules/images.test.js +145 -0
- package/dist/modules/integrations.test.d.ts +1 -0
- package/dist/modules/integrations.test.js +93 -0
- package/dist/modules/media.d.ts +11 -0
- package/dist/modules/media.js +132 -0
- package/dist/modules/media.test.d.ts +1 -0
- package/dist/modules/media.test.js +186 -0
- package/dist/modules/news.d.ts +3 -0
- package/dist/modules/news.js +39 -0
- package/dist/modules/news.test.d.ts +1 -0
- package/dist/modules/news.test.js +88 -0
- package/dist/modules/parser.d.ts +19 -0
- package/dist/modules/parser.js +361 -0
- package/dist/modules/parser.test.d.ts +1 -0
- package/dist/modules/parser.test.js +151 -0
- package/dist/modules/reddit.d.ts +21 -0
- package/dist/modules/reddit.js +107 -0
- package/dist/modules/scrape.d.ts +16 -0
- package/dist/modules/scrape.js +272 -0
- package/dist/modules/scrape.test.d.ts +1 -0
- package/dist/modules/scrape.test.js +232 -0
- package/dist/modules/scraper.d.ts +12 -0
- package/dist/modules/scraper.js +640 -0
- package/dist/modules/scrapers/anidb.d.ts +8 -0
- package/dist/modules/scrapers/anidb.js +156 -0
- package/dist/modules/scrapers/duckduckgo.d.ts +6 -0
- package/dist/modules/scrapers/duckduckgo.js +284 -0
- package/dist/modules/scrapers/google-news.d.ts +2 -0
- package/dist/modules/scrapers/google-news.js +60 -0
- package/dist/modules/scrapers/google.d.ts +6 -0
- package/dist/modules/scrapers/google.js +211 -0
- package/dist/modules/scrapers/searxng.d.ts +2 -0
- package/dist/modules/scrapers/searxng.js +93 -0
- package/dist/modules/scrapers/thetvdb.d.ts +3 -0
- package/dist/modules/scrapers/thetvdb.js +147 -0
- package/dist/modules/scrapers/tmdb.d.ts +3 -0
- package/dist/modules/scrapers/tmdb.js +172 -0
- package/dist/modules/scrapers/yahoo-finance.d.ts +2 -0
- package/dist/modules/scrapers/yahoo-finance.js +33 -0
- package/dist/modules/search.d.ts +5 -0
- package/dist/modules/search.js +45 -0
- package/dist/modules/search.js.map +1 -0
- package/dist/modules/search.test.d.ts +1 -0
- package/dist/modules/search.test.js +219 -0
- package/dist/modules/urbandictionary.d.ts +12 -0
- package/dist/modules/urbandictionary.js +26 -0
- package/dist/modules/webpage.d.ts +4 -0
- package/dist/modules/webpage.js +150 -0
- package/dist/modules/webpage.js.map +1 -0
- package/dist/modules/wikipedia.d.ts +5 -0
- package/dist/modules/wikipedia.js +85 -0
- package/dist/modules/wikipedia.js.map +1 -0
- package/dist/scripts/interactive-search.d.ts +1 -0
- package/dist/scripts/interactive-search.js +98 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +179 -0
- package/dist/test.js.map +1 -0
- package/dist/testBraveSearch.d.ts +1 -0
- package/dist/testBraveSearch.js +34 -0
- package/dist/testDuckDuckGo.d.ts +1 -0
- package/dist/testDuckDuckGo.js +52 -0
- package/dist/testEcosia.d.ts +1 -0
- package/dist/testEcosia.js +57 -0
- package/dist/testSearchModule.d.ts +1 -0
- package/dist/testSearchModule.js +95 -0
- package/dist/testwebpage.d.ts +1 -0
- package/dist/testwebpage.js +81 -0
- package/dist/types.d.ts +174 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/createTestDocx.d.ts +1 -0
- package/dist/utils/createTestDocx.js +58 -0
- package/dist/utils/htmlcleaner.d.ts +20 -0
- package/dist/utils/htmlcleaner.js +172 -0
- package/docs/README.md +275 -0
- package/docs/autocomplete.md +73 -0
- package/docs/crawling.md +88 -0
- package/docs/events.md +58 -0
- package/docs/examples.md +158 -0
- package/docs/finance.md +60 -0
- package/docs/flights.md +71 -0
- package/docs/hackernews.md +121 -0
- package/docs/media.md +87 -0
- package/docs/news.md +75 -0
- package/docs/parser.md +197 -0
- package/docs/scraper.md +347 -0
- package/docs/search.md +106 -0
- package/docs/wikipedia.md +91 -0
- package/package.json +97 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchMedia = searchMedia;
|
|
4
|
+
exports.getMediaDetails = getMediaDetails;
|
|
5
|
+
const tmdb_1 = require("./scrapers/tmdb");
|
|
6
|
+
const thetvdb_1 = require("./scrapers/thetvdb");
|
|
7
|
+
const anidb_1 = require("./scrapers/anidb");
|
|
8
|
+
/**
|
|
9
|
+
* Unified Media Search
|
|
10
|
+
* Coordinates between TMDB, TheTVDB, and AniDB with fallback logic.
|
|
11
|
+
*/
|
|
12
|
+
async function searchMedia(query, options = {}) {
|
|
13
|
+
const { type } = options;
|
|
14
|
+
let results = [];
|
|
15
|
+
const errors = [];
|
|
16
|
+
// Strategy Pattern based on media type
|
|
17
|
+
// 1. ANIME Specific Strategy
|
|
18
|
+
if (type === "anime") {
|
|
19
|
+
try {
|
|
20
|
+
// Try AniDB first for Anime
|
|
21
|
+
results = await (0, anidb_1.searchAniDB)(query, options);
|
|
22
|
+
if (results.length > 0)
|
|
23
|
+
return results;
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
errors.push(e);
|
|
27
|
+
}
|
|
28
|
+
// Fallback to TMDB with anime/tv type
|
|
29
|
+
try {
|
|
30
|
+
results = await (0, tmdb_1.searchTMDB)(query, { ...options, type: "tv" }); // Anime is often under TV in TMDB
|
|
31
|
+
// Filter for animation genre if possible, but for now just return results
|
|
32
|
+
if (results.length > 0)
|
|
33
|
+
return results;
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
errors.push(e);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// 2. TV SHOW Strategy
|
|
40
|
+
else if (type === "tv") {
|
|
41
|
+
try {
|
|
42
|
+
// TMDB is generally faster and better structured for general TV
|
|
43
|
+
results = await (0, tmdb_1.searchTMDB)(query, options);
|
|
44
|
+
if (results.length > 0)
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
errors.push(e);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
// Fallback to TheTVDB
|
|
52
|
+
results = await (0, thetvdb_1.searchTheTVDB)(query, options);
|
|
53
|
+
if (results.length > 0)
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
errors.push(e);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 3. MOVIE or GENERAL Strategy
|
|
61
|
+
else {
|
|
62
|
+
try {
|
|
63
|
+
// TMDB is the best all-rounder
|
|
64
|
+
results = await (0, tmdb_1.searchTMDB)(query, options);
|
|
65
|
+
if (results.length > 0)
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
errors.push(e);
|
|
70
|
+
}
|
|
71
|
+
// If generic search and TMDB failed or found nothing, maybe try TheTVDB?
|
|
72
|
+
// Only if type wasn't specified as "movie" (TheTVDB is mostly TV)
|
|
73
|
+
if (type !== "movie" && results.length === 0) {
|
|
74
|
+
try {
|
|
75
|
+
const tvdbResults = await (0, thetvdb_1.searchTheTVDB)(query, options);
|
|
76
|
+
if (tvdbResults.length > 0)
|
|
77
|
+
return tvdbResults;
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
errors.push(e);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (results.length === 0 && errors.length > 0) {
|
|
85
|
+
throw {
|
|
86
|
+
message: "Media search failed on all providers",
|
|
87
|
+
code: "MEDIA_SEARCH_FAILED",
|
|
88
|
+
originalError: errors[0], // Return the first error for context
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get detailed information for a media result.
|
|
95
|
+
* Automatically determines the source based on the URL or accepts an explicit source.
|
|
96
|
+
*/
|
|
97
|
+
async function getMediaDetails(url, source, options = {}) {
|
|
98
|
+
// Infer source from URL if not provided
|
|
99
|
+
if (!source) {
|
|
100
|
+
if (url.includes("themoviedb.org"))
|
|
101
|
+
source = "tmdb";
|
|
102
|
+
else if (url.includes("thetvdb.com"))
|
|
103
|
+
source = "thetvdb";
|
|
104
|
+
else if (url.includes("anidb.net"))
|
|
105
|
+
source = "anidb";
|
|
106
|
+
else {
|
|
107
|
+
throw {
|
|
108
|
+
message: "Could not determine media source from URL",
|
|
109
|
+
code: "UNKNOWN_MEDIA_SOURCE",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
switch (source) {
|
|
115
|
+
case "tmdb":
|
|
116
|
+
return await (0, tmdb_1.getTMDBDetails)(url, options);
|
|
117
|
+
case "thetvdb":
|
|
118
|
+
return await (0, thetvdb_1.getTheTVDBDetails)(url, options);
|
|
119
|
+
case "anidb":
|
|
120
|
+
return await (0, anidb_1.getAniDBDetails)(url, options);
|
|
121
|
+
default:
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
throw {
|
|
127
|
+
message: `Failed to get details from ${source}`,
|
|
128
|
+
code: "MEDIA_DETAILS_FAILED",
|
|
129
|
+
originalError: error,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
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 media_1 = require("./media");
|
|
38
|
+
const aniDB = __importStar(require("./scrapers/anidb"));
|
|
39
|
+
const tmdb = __importStar(require("./scrapers/tmdb"));
|
|
40
|
+
const theTVDB = __importStar(require("./scrapers/thetvdb"));
|
|
41
|
+
// Mock the scrapers
|
|
42
|
+
vitest_1.vi.mock("./scrapers/anidb");
|
|
43
|
+
vitest_1.vi.mock("./scrapers/tmdb");
|
|
44
|
+
vitest_1.vi.mock("./scrapers/thetvdb");
|
|
45
|
+
(0, vitest_1.describe)("Media Search Coordinator", () => {
|
|
46
|
+
const mockAnimeResult = {
|
|
47
|
+
title: "Test Anime",
|
|
48
|
+
url: "https://anidb.net/anime/1",
|
|
49
|
+
source: "anidb",
|
|
50
|
+
mediaType: "anime",
|
|
51
|
+
};
|
|
52
|
+
const mockTVResult = {
|
|
53
|
+
title: "Test TV Show",
|
|
54
|
+
url: "https://themoviedb.org/tv/1",
|
|
55
|
+
source: "tmdb",
|
|
56
|
+
mediaType: "tv",
|
|
57
|
+
};
|
|
58
|
+
const mockMovieResult = {
|
|
59
|
+
title: "Test Movie",
|
|
60
|
+
url: "https://themoviedb.org/movie/1",
|
|
61
|
+
source: "tmdb",
|
|
62
|
+
mediaType: "movie",
|
|
63
|
+
};
|
|
64
|
+
(0, vitest_1.beforeEach)(() => {
|
|
65
|
+
vitest_1.vi.resetAllMocks();
|
|
66
|
+
});
|
|
67
|
+
(0, vitest_1.describe)("searchMedia", () => {
|
|
68
|
+
(0, vitest_1.describe)("Anime Strategy", () => {
|
|
69
|
+
(0, vitest_1.it)("should try AniDB first for anime", async () => {
|
|
70
|
+
vitest_1.vi.mocked(aniDB.searchAniDB).mockResolvedValue([mockAnimeResult]);
|
|
71
|
+
const results = await (0, media_1.searchMedia)("Naruto", { type: "anime" });
|
|
72
|
+
(0, vitest_1.expect)(aniDB.searchAniDB).toHaveBeenCalledWith("Naruto", vitest_1.expect.objectContaining({ type: "anime" }));
|
|
73
|
+
(0, vitest_1.expect)(tmdb.searchTMDB).not.toHaveBeenCalled();
|
|
74
|
+
(0, vitest_1.expect)(results).toEqual([mockAnimeResult]);
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)("should fall back to TMDB if AniDB fails", async () => {
|
|
77
|
+
vitest_1.vi.mocked(aniDB.searchAniDB).mockRejectedValue(new Error("AniDB down"));
|
|
78
|
+
vitest_1.vi.mocked(tmdb.searchTMDB).mockResolvedValue([mockTVResult]);
|
|
79
|
+
const results = await (0, media_1.searchMedia)("Naruto", { type: "anime" });
|
|
80
|
+
(0, vitest_1.expect)(aniDB.searchAniDB).toHaveBeenCalled();
|
|
81
|
+
(0, vitest_1.expect)(tmdb.searchTMDB).toHaveBeenCalledWith("Naruto", vitest_1.expect.objectContaining({ type: "tv" }));
|
|
82
|
+
(0, vitest_1.expect)(results).toEqual([mockTVResult]);
|
|
83
|
+
});
|
|
84
|
+
(0, vitest_1.it)("should fall back to TMDB if AniDB returns empty", async () => {
|
|
85
|
+
vitest_1.vi.mocked(aniDB.searchAniDB).mockResolvedValue([]);
|
|
86
|
+
vitest_1.vi.mocked(tmdb.searchTMDB).mockResolvedValue([mockTVResult]);
|
|
87
|
+
const results = await (0, media_1.searchMedia)("Naruto", { type: "anime" });
|
|
88
|
+
(0, vitest_1.expect)(aniDB.searchAniDB).toHaveBeenCalled();
|
|
89
|
+
(0, vitest_1.expect)(tmdb.searchTMDB).toHaveBeenCalled();
|
|
90
|
+
(0, vitest_1.expect)(results).toEqual([mockTVResult]);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.describe)("TV Strategy", () => {
|
|
94
|
+
(0, vitest_1.it)("should try TMDB first for TV", async () => {
|
|
95
|
+
vitest_1.vi.mocked(tmdb.searchTMDB).mockResolvedValue([mockTVResult]);
|
|
96
|
+
const results = await (0, media_1.searchMedia)("Breaking Bad", { type: "tv" });
|
|
97
|
+
(0, vitest_1.expect)(tmdb.searchTMDB).toHaveBeenCalledWith("Breaking Bad", vitest_1.expect.objectContaining({ type: "tv" }));
|
|
98
|
+
(0, vitest_1.expect)(theTVDB.searchTheTVDB).not.toHaveBeenCalled();
|
|
99
|
+
(0, vitest_1.expect)(results).toEqual([mockTVResult]);
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.it)("should fall back to TheTVDB if TMDB fails", async () => {
|
|
102
|
+
vitest_1.vi.mocked(tmdb.searchTMDB).mockRejectedValue(new Error("TMDB down"));
|
|
103
|
+
vitest_1.vi.mocked(theTVDB.searchTheTVDB).mockResolvedValue([mockTVResult]);
|
|
104
|
+
const results = await (0, media_1.searchMedia)("Breaking Bad", { type: "tv" });
|
|
105
|
+
(0, vitest_1.expect)(tmdb.searchTMDB).toHaveBeenCalled();
|
|
106
|
+
(0, vitest_1.expect)(theTVDB.searchTheTVDB).toHaveBeenCalledWith("Breaking Bad", vitest_1.expect.objectContaining({ type: "tv" }));
|
|
107
|
+
(0, vitest_1.expect)(results).toEqual([mockTVResult]);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
(0, vitest_1.describe)("General/Movie Strategy", () => {
|
|
111
|
+
(0, vitest_1.it)("should default to TMDB for unspecified type", async () => {
|
|
112
|
+
vitest_1.vi.mocked(tmdb.searchTMDB).mockResolvedValue([mockMovieResult]);
|
|
113
|
+
const results = await (0, media_1.searchMedia)("Inception", {});
|
|
114
|
+
(0, vitest_1.expect)(tmdb.searchTMDB).toHaveBeenCalledWith("Inception", {});
|
|
115
|
+
(0, vitest_1.expect)(results).toEqual([mockMovieResult]);
|
|
116
|
+
});
|
|
117
|
+
(0, vitest_1.it)("should try TheTVDB if general TMDB search fails and type is not movie", async () => {
|
|
118
|
+
// TMDB fails/returns empty
|
|
119
|
+
vitest_1.vi.mocked(tmdb.searchTMDB).mockResolvedValue([]);
|
|
120
|
+
vitest_1.vi.mocked(theTVDB.searchTheTVDB).mockResolvedValue([mockTVResult]);
|
|
121
|
+
// No type specified, so it could be a TV show
|
|
122
|
+
const results = await (0, media_1.searchMedia)("Unknown Show", {});
|
|
123
|
+
(0, vitest_1.expect)(tmdb.searchTMDB).toHaveBeenCalled();
|
|
124
|
+
(0, vitest_1.expect)(theTVDB.searchTheTVDB).toHaveBeenCalled();
|
|
125
|
+
(0, vitest_1.expect)(results).toEqual([mockTVResult]);
|
|
126
|
+
});
|
|
127
|
+
(0, vitest_1.it)("should NOT try TheTVDB if type is explicitly movie", async () => {
|
|
128
|
+
vitest_1.vi.mocked(tmdb.searchTMDB).mockResolvedValue([]);
|
|
129
|
+
try {
|
|
130
|
+
await (0, media_1.searchMedia)("Unknown Movie", { type: "movie" });
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Expected to throw
|
|
134
|
+
}
|
|
135
|
+
(0, vitest_1.expect)(tmdb.searchTMDB).toHaveBeenCalled();
|
|
136
|
+
(0, vitest_1.expect)(theTVDB.searchTheTVDB).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
(0, vitest_1.describe)("Error Handling", () => {
|
|
140
|
+
(0, vitest_1.it)("should throw SearchError if all providers fail", async () => {
|
|
141
|
+
vitest_1.vi.mocked(aniDB.searchAniDB).mockRejectedValue(new Error("Fail 1"));
|
|
142
|
+
vitest_1.vi.mocked(tmdb.searchTMDB).mockRejectedValue(new Error("Fail 2"));
|
|
143
|
+
await (0, vitest_1.expect)((0, media_1.searchMedia)("Nothing", { type: "anime" })).rejects.toMatchObject({
|
|
144
|
+
code: "MEDIA_SEARCH_FAILED",
|
|
145
|
+
message: "Media search failed on all providers",
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
(0, vitest_1.describe)("getMediaDetails", () => {
|
|
151
|
+
const mockDetails = { genres: ["Action"], cast: ["Actor A"] };
|
|
152
|
+
(0, vitest_1.it)("should call getTMDBDetails for TMDB URLs", async () => {
|
|
153
|
+
vitest_1.vi.mocked(tmdb.getTMDBDetails).mockResolvedValue(mockDetails);
|
|
154
|
+
const url = "https://www.themoviedb.org/movie/123";
|
|
155
|
+
const result = await (0, media_1.getMediaDetails)(url);
|
|
156
|
+
(0, vitest_1.expect)(tmdb.getTMDBDetails).toHaveBeenCalledWith(url, vitest_1.expect.anything());
|
|
157
|
+
(0, vitest_1.expect)(result).toEqual(mockDetails);
|
|
158
|
+
});
|
|
159
|
+
(0, vitest_1.it)("should call getTheTVDBDetails for TheTVDB URLs", async () => {
|
|
160
|
+
vitest_1.vi.mocked(theTVDB.getTheTVDBDetails).mockResolvedValue(mockDetails);
|
|
161
|
+
const url = "https://thetvdb.com/series/breaking-bad";
|
|
162
|
+
const result = await (0, media_1.getMediaDetails)(url);
|
|
163
|
+
(0, vitest_1.expect)(theTVDB.getTheTVDBDetails).toHaveBeenCalledWith(url, vitest_1.expect.anything());
|
|
164
|
+
(0, vitest_1.expect)(result).toEqual(mockDetails);
|
|
165
|
+
});
|
|
166
|
+
(0, vitest_1.it)("should call getAniDBDetails for AniDB URLs", async () => {
|
|
167
|
+
vitest_1.vi.mocked(aniDB.getAniDBDetails).mockResolvedValue(mockDetails);
|
|
168
|
+
const url = "https://anidb.net/anime/123";
|
|
169
|
+
const result = await (0, media_1.getMediaDetails)(url);
|
|
170
|
+
(0, vitest_1.expect)(aniDB.getAniDBDetails).toHaveBeenCalledWith(url, vitest_1.expect.anything());
|
|
171
|
+
(0, vitest_1.expect)(result).toEqual(mockDetails);
|
|
172
|
+
});
|
|
173
|
+
(0, vitest_1.it)("should use explicit source if provided", async () => {
|
|
174
|
+
vitest_1.vi.mocked(tmdb.getTMDBDetails).mockResolvedValue(mockDetails);
|
|
175
|
+
const url = "https://some-mirror.com/movie/123";
|
|
176
|
+
await (0, media_1.getMediaDetails)(url, "tmdb");
|
|
177
|
+
(0, vitest_1.expect)(tmdb.getTMDBDetails).toHaveBeenCalledWith(url, vitest_1.expect.anything());
|
|
178
|
+
});
|
|
179
|
+
(0, vitest_1.it)("should throw error for unknown sources", async () => {
|
|
180
|
+
const url = "https://unknown.com/movie/123";
|
|
181
|
+
await (0, vitest_1.expect)((0, media_1.getMediaDetails)(url)).rejects.toMatchObject({
|
|
182
|
+
code: "UNKNOWN_MEDIA_SOURCE",
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchGoogleNews = void 0;
|
|
4
|
+
exports.searchNews = searchNews;
|
|
5
|
+
const google_news_1 = require("./scrapers/google-news");
|
|
6
|
+
const duckduckgo_1 = require("./scrapers/duckduckgo");
|
|
7
|
+
// Re-export specific news search functions
|
|
8
|
+
var google_news_2 = require("./scrapers/google-news");
|
|
9
|
+
Object.defineProperty(exports, "searchGoogleNews", { enumerable: true, get: function () { return google_news_2.searchGoogleNews; } });
|
|
10
|
+
// Unified news search that tries engines in sequence: Google News -> DuckDuckGo News
|
|
11
|
+
async function searchNews(query, options = {}) {
|
|
12
|
+
const errors = [];
|
|
13
|
+
// 1. Try Google News first (specialized news scraper)
|
|
14
|
+
try {
|
|
15
|
+
return await (0, google_news_1.searchGoogleNews)(query, options);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
errors.push(err);
|
|
19
|
+
}
|
|
20
|
+
// 2. Try DuckDuckGo with news category
|
|
21
|
+
try {
|
|
22
|
+
const ddgOptions = { ...options, category: "news" };
|
|
23
|
+
const results = await (0, duckduckgo_1.searchDuckDuckGo)(query, ddgOptions);
|
|
24
|
+
// Convert generic SearchResult to NewsResult
|
|
25
|
+
return results.map((result) => ({
|
|
26
|
+
...result,
|
|
27
|
+
source: "duckduckgo-news",
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
errors.push(err);
|
|
32
|
+
}
|
|
33
|
+
// If all failed, throw error with details
|
|
34
|
+
throw {
|
|
35
|
+
message: "All news search engines failed",
|
|
36
|
+
code: "ALL_NEWS_ENGINES_FAILED",
|
|
37
|
+
errors,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
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 news_1 = require("./news");
|
|
8
|
+
const google_news_1 = require("./scrapers/google-news");
|
|
9
|
+
const duckduckgo_1 = require("./scrapers/duckduckgo");
|
|
10
|
+
const google_news_scraper_1 = __importDefault(require("google-news-scraper"));
|
|
11
|
+
// Mock dependencies
|
|
12
|
+
vitest_1.vi.mock("google-news-scraper");
|
|
13
|
+
vitest_1.vi.mock("./scrapers/duckduckgo");
|
|
14
|
+
vitest_1.vi.mock("./scrapers/google-news", async () => {
|
|
15
|
+
const actual = await vitest_1.vi.importActual("./scrapers/google-news");
|
|
16
|
+
return {
|
|
17
|
+
...actual,
|
|
18
|
+
// We want to test the real implementation of searchGoogleNews sometimes,
|
|
19
|
+
// but for searchNews orchestrator tests we might want to mock it.
|
|
20
|
+
// However, since searchGoogleNews is simple, we can mock the underlying library instead.
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
(0, vitest_1.describe)("News Module", () => {
|
|
24
|
+
(0, vitest_1.beforeEach)(() => {
|
|
25
|
+
vitest_1.vi.resetAllMocks();
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.describe)("searchGoogleNews", () => {
|
|
28
|
+
(0, vitest_1.it)("should return formatted news results", async () => {
|
|
29
|
+
const mockArticles = [
|
|
30
|
+
{
|
|
31
|
+
title: "Test News",
|
|
32
|
+
link: "https://news.com/article",
|
|
33
|
+
image: "https://news.com/image.jpg",
|
|
34
|
+
source: "News Source",
|
|
35
|
+
time: "2 hours ago",
|
|
36
|
+
subtitle: "Snippet text",
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
google_news_scraper_1.default.mockResolvedValue(mockArticles);
|
|
40
|
+
const results = await (0, google_news_1.searchGoogleNews)("test google news");
|
|
41
|
+
(0, vitest_1.expect)(results).toHaveLength(1);
|
|
42
|
+
(0, vitest_1.expect)(results[0]).toEqual({
|
|
43
|
+
title: "Test News",
|
|
44
|
+
url: "https://news.com/article",
|
|
45
|
+
imageUrl: "https://news.com/image.jpg",
|
|
46
|
+
source: "google-news",
|
|
47
|
+
sourceName: "News Source",
|
|
48
|
+
publishedAt: "2 hours ago",
|
|
49
|
+
snippet: "Snippet text",
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.it)("should handle errors", async () => {
|
|
53
|
+
google_news_scraper_1.default.mockRejectedValue(new Error("Scrape failed"));
|
|
54
|
+
await (0, vitest_1.expect)((0, google_news_1.searchGoogleNews)("test error")).rejects.toMatchObject({
|
|
55
|
+
code: "GOOGLE_NEWS_SEARCH_ERROR",
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.describe)("searchNews (Orchestrator)", () => {
|
|
60
|
+
(0, vitest_1.it)("should try Google News first", async () => {
|
|
61
|
+
const mockArticles = [{ title: "GNews", link: "url", source: "G" }];
|
|
62
|
+
google_news_scraper_1.default.mockResolvedValue(mockArticles);
|
|
63
|
+
const results = await (0, news_1.searchNews)("test orchestrator");
|
|
64
|
+
(0, vitest_1.expect)(results[0].source).toBe("google-news");
|
|
65
|
+
(0, vitest_1.expect)(google_news_scraper_1.default).toHaveBeenCalled();
|
|
66
|
+
(0, vitest_1.expect)(duckduckgo_1.searchDuckDuckGo).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
(0, vitest_1.it)("should fallback to DuckDuckGo if Google News fails", async () => {
|
|
69
|
+
// Mock Google News failure
|
|
70
|
+
google_news_scraper_1.default.mockRejectedValue(new Error("Fail"));
|
|
71
|
+
// Mock DDG success
|
|
72
|
+
duckduckgo_1.searchDuckDuckGo.mockResolvedValue([
|
|
73
|
+
{ title: "DDG News", url: "url", snippet: "desc", source: "duckduckgo" },
|
|
74
|
+
]);
|
|
75
|
+
const results = await (0, news_1.searchNews)("test fallback");
|
|
76
|
+
(0, vitest_1.expect)(results[0].source).toBe("duckduckgo-news");
|
|
77
|
+
(0, vitest_1.expect)(google_news_scraper_1.default).toHaveBeenCalled();
|
|
78
|
+
(0, vitest_1.expect)(duckduckgo_1.searchDuckDuckGo).toHaveBeenCalledWith("test fallback", vitest_1.expect.objectContaining({ category: "news" }));
|
|
79
|
+
});
|
|
80
|
+
(0, vitest_1.it)("should throw if all providers fail", async () => {
|
|
81
|
+
google_news_scraper_1.default.mockRejectedValue(new Error("Fail 1"));
|
|
82
|
+
duckduckgo_1.searchDuckDuckGo.mockRejectedValue(new Error("Fail 2"));
|
|
83
|
+
await (0, vitest_1.expect)((0, news_1.searchNews)("test all fail")).rejects.toMatchObject({
|
|
84
|
+
code: "ALL_NEWS_ENGINES_FAILED",
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type FileType = "pdf" | "docx" | "csv" | "image" | "text" | "xml" | "json" | "unknown";
|
|
2
|
+
export interface ParseOptions {
|
|
3
|
+
language?: string;
|
|
4
|
+
csv?: {
|
|
5
|
+
delimiter?: string;
|
|
6
|
+
columns?: boolean;
|
|
7
|
+
};
|
|
8
|
+
xml?: {
|
|
9
|
+
ignoreAttributes?: boolean;
|
|
10
|
+
parseAttributeValue?: boolean;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface ParseResult {
|
|
14
|
+
type: FileType;
|
|
15
|
+
text: string;
|
|
16
|
+
metadata?: Record<string, unknown>;
|
|
17
|
+
data?: unknown;
|
|
18
|
+
}
|
|
19
|
+
export declare function parse(pathOrBuffer: string | Buffer, options?: ParseOptions, filename?: string): Promise<ParseResult>;
|