search-web-api 1.0.13

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 (111) hide show
  1. package/README.md +307 -0
  2. package/demo/index.ts +38 -0
  3. package/demo/openapi.ts +402 -0
  4. package/demo/routes/autocomplete.ts +124 -0
  5. package/demo/routes/search.ts +61 -0
  6. package/docs/AUTOCOMPLETE.md +296 -0
  7. package/docs/CATEGORY_SEARCH.md +265 -0
  8. package/package.json +32 -0
  9. package/src/autocomplete/autocomplete-ai-next-word-predictor.ts +47 -0
  10. package/src/autocomplete/autocomplete-search-engine-backends.ts +231 -0
  11. package/src/category-registry.ts +6 -0
  12. package/src/config/search-engine-constants.ts +216 -0
  13. package/src/constants.ts +6 -0
  14. package/src/engine-descriptions.ts +7 -0
  15. package/src/engine-status.ts +7 -0
  16. package/src/engine.ts +6 -0
  17. package/src/registry/search-engine-category-registry.ts +200 -0
  18. package/src/registry/search-engine-descriptions.ts +121 -0
  19. package/src/registry/search-engine-status-tracker.ts +240 -0
  20. package/src/result-container.ts +8 -0
  21. package/src/search/search-engines-registry-list.ts +205 -0
  22. package/src/search/search-query-executor.ts +168 -0
  23. package/src/search/search-result-container.ts +421 -0
  24. package/src/search-web-types.ts +7 -0
  25. package/src/search.ts +10 -0
  26. package/src/sources/academic/arxiv.ts +72 -0
  27. package/src/sources/academic/core.ts +65 -0
  28. package/src/sources/academic/crossref.ts +75 -0
  29. package/src/sources/academic/doaj.ts +63 -0
  30. package/src/sources/academic/google_scholar.ts +53 -0
  31. package/src/sources/academic/openalex.ts +75 -0
  32. package/src/sources/academic/pubmed.ts +96 -0
  33. package/src/sources/academic/semantic_scholar.ts +77 -0
  34. package/src/sources/academic/wikidata.ts +44 -0
  35. package/src/sources/general/baidu.ts +59 -0
  36. package/src/sources/general/bing.ts +30 -0
  37. package/src/sources/general/brave.ts +54 -0
  38. package/src/sources/general/duckduckgo.ts +49 -0
  39. package/src/sources/general/google.ts +68 -0
  40. package/src/sources/general/mojeek.ts +56 -0
  41. package/src/sources/general/qwant.ts +37 -0
  42. package/src/sources/general/startpage.ts +44 -0
  43. package/src/sources/general/yahoo.ts +41 -0
  44. package/src/sources/general/yandex.ts +56 -0
  45. package/src/sources/images/bing_images.ts +68 -0
  46. package/src/sources/images/deviantart.ts +71 -0
  47. package/src/sources/images/flickr.ts +132 -0
  48. package/src/sources/images/google_images.ts +101 -0
  49. package/src/sources/images/imgur.ts +58 -0
  50. package/src/sources/images/openclipart.ts +52 -0
  51. package/src/sources/images/pixabay.ts +56 -0
  52. package/src/sources/images/unsplash.ts +38 -0
  53. package/src/sources/images/wallhaven.ts +50 -0
  54. package/src/sources/it/crates.ts +41 -0
  55. package/src/sources/it/dockerhub.ts +47 -0
  56. package/src/sources/it/github.ts +37 -0
  57. package/src/sources/it/gitlab.ts +55 -0
  58. package/src/sources/it/npm.ts +36 -0
  59. package/src/sources/it/packagist.ts +43 -0
  60. package/src/sources/it/pypi.ts +30 -0
  61. package/src/sources/it/rubygems.ts +43 -0
  62. package/src/sources/it/stackoverflow.ts +39 -0
  63. package/src/sources/maps/apple_maps.ts +105 -0
  64. package/src/sources/maps/openstreetmap.ts +34 -0
  65. package/src/sources/maps/photon.ts +77 -0
  66. package/src/sources/news/bing_news.ts +95 -0
  67. package/src/sources/news/google_news.ts +80 -0
  68. package/src/sources/news/hackernews.ts +94 -0
  69. package/src/sources/news/yahoo_news.ts +77 -0
  70. package/src/sources/shopping/ebay.ts +96 -0
  71. package/src/sources/social/mastodon.ts +46 -0
  72. package/src/sources/social/medium.ts +52 -0
  73. package/src/sources/social/reddit.ts +48 -0
  74. package/src/sources/social/soundcloud.ts +64 -0
  75. package/src/sources/social/twitter.ts +56 -0
  76. package/src/sources/specialized/annas_archive.ts +97 -0
  77. package/src/sources/specialized/archive.ts +48 -0
  78. package/src/sources/specialized/genius.ts +43 -0
  79. package/src/sources/specialized/goodreads.ts +62 -0
  80. package/src/sources/specialized/imdb.ts +55 -0
  81. package/src/sources/specialized/openlibrary.ts +59 -0
  82. package/src/sources/specialized/wikipedia.ts +37 -0
  83. package/src/sources/specialized/wttr.ts +98 -0
  84. package/src/sources/torrents/1337x.ts +43 -0
  85. package/src/sources/torrents/eztv.ts +47 -0
  86. package/src/sources/torrents/kickass.ts +63 -0
  87. package/src/sources/torrents/nyaa.ts +53 -0
  88. package/src/sources/torrents/solidtorrents.ts +68 -0
  89. package/src/sources/torrents/thepiratebay.ts +57 -0
  90. package/src/sources/torrents/yts.ts +54 -0
  91. package/src/sources/videos/bing_videos.ts +91 -0
  92. package/src/sources/videos/dailymotion.ts +101 -0
  93. package/src/sources/videos/invidious.ts +86 -0
  94. package/src/sources/videos/peertube.ts +76 -0
  95. package/src/sources/videos/vimeo.ts +67 -0
  96. package/src/sources/videos/youtube.ts +78 -0
  97. package/src/suggest-next-words/autocomplete-ai.ts +38 -0
  98. package/src/suggest-next-words/autocomplete-search-engines.ts +384 -0
  99. package/src/suggest-next-words/misspelled-typos-8k.json +1 -0
  100. package/src/types/search-engine-interface.ts +27 -0
  101. package/src/types/search-result-types.ts +405 -0
  102. package/test/api.test.ts +128 -0
  103. package/test/autocomplete-ai.test.ts +20 -0
  104. package/test/autocomplete-engines.test.ts +131 -0
  105. package/test/engine-health-suite.test.ts +350 -0
  106. package/test/search.test.ts +69 -0
  107. package/test/sources-unit.test.ts +1152 -0
  108. package/test/sources.test.ts +182 -0
  109. package/test/test-utils.ts +81 -0
  110. package/tsconfig.json +16 -0
  111. package/vitest.config.ts +20 -0
@@ -0,0 +1,101 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const google_images: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ q: query,
9
+ tbm: "isch",
10
+ asearch: "isch",
11
+ hl: "en",
12
+ gl: "US",
13
+ async: `_fmt:json,p:1,ijn:${(page || 1) - 1}`,
14
+ });
15
+ const response = await fetch(
16
+ `https://www.google.com/search?${params}`,
17
+ {
18
+ headers: {
19
+ "User-Agent":
20
+ "NSTN/3.60.474802233.release Dalvik/2.1.0 (Linux; U; Android 12; US) gzip",
21
+ Accept:
22
+ "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
23
+ "Accept-Language": "en-US,en;q=0.5",
24
+ Cookie: "CONSENT=YES+; SOCS=CAESBQgYEgAgAA==",
25
+ },
26
+ }
27
+ );
28
+ if (!response.ok) return [];
29
+ const html = await response.text();
30
+
31
+ if (!html || typeof html !== "string") {
32
+ return [];
33
+ }
34
+
35
+ try {
36
+ const jsonStart = html.indexOf('{"ischj":');
37
+ if (jsonStart === -1) {
38
+ return [];
39
+ }
40
+
41
+ const jsonData = JSON.parse(html.substring(jsonStart));
42
+ const metadata = jsonData?.ischj?.metadata || [];
43
+
44
+ return metadata
45
+ .map((item: any) => {
46
+ if (!item?.result || !item?.original_image) {
47
+ return null;
48
+ }
49
+
50
+ const result = item.result;
51
+ const originalImage = item.original_image;
52
+ const thumbnail = item.thumbnail;
53
+
54
+ const sourceParts: string[] = [];
55
+ if (result.site_title) {
56
+ sourceParts.push(result.site_title);
57
+ }
58
+
59
+ const author = result.iptc?.creator;
60
+ if (author && Array.isArray(author)) {
61
+ sourceParts.push(`Author: ${author.join(", ")}`);
62
+ }
63
+
64
+ const copyright = result.iptc?.copyright_notice;
65
+ if (copyright) {
66
+ sourceParts.push(copyright);
67
+ }
68
+
69
+ if (result.freshness_date) {
70
+ sourceParts.push(result.freshness_date);
71
+ }
72
+
73
+ const fileSize = item.gsa?.file_size;
74
+ if (fileSize) {
75
+ sourceParts.push(`(${fileSize})`);
76
+ }
77
+
78
+ const resolution = `${originalImage.width} x ${originalImage.height}`;
79
+
80
+ const content = [
81
+ item.text_in_grid?.snippet || "",
82
+ sourceParts.join(" | "),
83
+ `Resolution: ${resolution}`,
84
+ ]
85
+ .filter(Boolean)
86
+ .join("\n");
87
+
88
+ return {
89
+ url: result.referrer_url,
90
+ title: result.page_title || "",
91
+ content,
92
+ thumbnail: thumbnail?.url,
93
+ engine: "google_images",
94
+ };
95
+ })
96
+ .filter((r: any) => r !== null);
97
+ } catch (e) {
98
+ console.error("Error parsing Google Images response:", e);
99
+ return [];
100
+ }
101
+ };
@@ -0,0 +1,58 @@
1
+ import { parseHTML } from "linkedom";
2
+ import { EngineFunction } from "../../types/search-engine-interface.js";
3
+
4
+ export const imgur: EngineFunction = async (
5
+ query: string,
6
+ page: number | undefined
7
+ ) => {
8
+ const params = new URLSearchParams({
9
+ q: query,
10
+ qs: "thumbs",
11
+ p: String((page || 1) - 1),
12
+ });
13
+ const response = await fetch(
14
+ `https://imgur.com/search/score/all?${params}`,
15
+ {
16
+ headers: {
17
+ "User-Agent":
18
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
19
+ Accept:
20
+ "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
21
+ "Accept-Language": "en-US,en;q=0.5",
22
+ },
23
+ }
24
+ );
25
+ if (!response.ok) return [];
26
+ const html = await response.text();
27
+
28
+ if (!html || typeof html !== "string") {
29
+ return [];
30
+ }
31
+
32
+ const { document } = parseHTML(html);
33
+
34
+ return Array.from(
35
+ document.querySelectorAll('div.cards div.post, div[class*="post"]')
36
+ )
37
+ .map((element) => {
38
+ const link = element.querySelector("a");
39
+ const url = link?.getAttribute("href");
40
+ const title = link?.querySelector("img")?.getAttribute("alt") || "";
41
+ let thumbnail = link?.querySelector("img")?.getAttribute("src") || "";
42
+
43
+ if (!url || !thumbnail || thumbnail.length < 25) {
44
+ return null;
45
+ }
46
+
47
+ const imgSrc = thumbnail.replace("b.", ".");
48
+
49
+ return {
50
+ url: `https://imgur.com${url}`,
51
+ title,
52
+ content: "",
53
+ thumbnail,
54
+ engine: "imgur",
55
+ };
56
+ })
57
+ .filter((r) => r !== null);
58
+ };
@@ -0,0 +1,52 @@
1
+ import { parseHTML } from "linkedom";
2
+ import { EngineFunction } from "../../types/search-engine-interface.js";
3
+
4
+ export const openclipart: EngineFunction = async (
5
+ query: string,
6
+ page: number | undefined
7
+ ) => {
8
+ const params = new URLSearchParams({
9
+ query: query,
10
+ p: String(page || 1),
11
+ });
12
+ const response = await fetch(
13
+ `https://openclipart.org/search/?${params}`,
14
+ {
15
+ headers: {
16
+ "User-Agent":
17
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
18
+ },
19
+ }
20
+ );
21
+ if (!response.ok) return [];
22
+ const html = await response.text();
23
+
24
+ if (!html || typeof html !== "string") {
25
+ return [];
26
+ }
27
+
28
+ const { document } = parseHTML(html);
29
+
30
+ return Array.from(
31
+ document.querySelectorAll("div.gallery div.artwork")
32
+ )
33
+ .map((element) => {
34
+ const link = element.querySelector("a");
35
+ const href = link?.getAttribute("href");
36
+ const title = link?.querySelector("img")?.getAttribute("alt");
37
+ const imgSrc = link?.querySelector("img")?.getAttribute("src");
38
+
39
+ if (!href || !title || !imgSrc) {
40
+ return null;
41
+ }
42
+
43
+ return {
44
+ url: `https://openclipart.org${href}`,
45
+ title,
46
+ content: "",
47
+ engine: "openclipart",
48
+ thumbnail: `https://openclipart.org${imgSrc}`,
49
+ };
50
+ })
51
+ .filter((r) => r !== null);
52
+ };
@@ -0,0 +1,56 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const pixabay: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ pagi: String(page || 1),
9
+ });
10
+ const response = await fetch(
11
+ `https://pixabay.com/images/search/${query}/?${params}`,
12
+ {
13
+ headers: {
14
+ "User-Agent":
15
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Pixabay",
16
+ "x-bootstrap-cache-miss": "1",
17
+ "x-fetch-bootstrap": "1",
18
+ },
19
+ redirect: "manual",
20
+ }
21
+ );
22
+ if (!response.ok) return [];
23
+ const json = await response.json();
24
+
25
+ if (!json || !json.page || !json.page.results) {
26
+ return [];
27
+ }
28
+
29
+ return json.page.results
30
+ .map((result: any) => {
31
+ if (
32
+ !result.mediaType ||
33
+ !["photo", "illustration", "vector"].includes(result.mediaType)
34
+ ) {
35
+ return null;
36
+ }
37
+
38
+ const sources = result.sources || {};
39
+ const thumbnail =
40
+ (Object.values(sources)[0] as string) || "";
41
+ const img_src =
42
+ (Object.values(sources)[
43
+ Object.values(sources).length - 1
44
+ ] as string) || "";
45
+
46
+ return {
47
+ url: `https://pixabay.com${result.href}`,
48
+ title: result.name || "",
49
+ content: result.description || "",
50
+ engine: "pixabay",
51
+ thumbnail,
52
+ img_src,
53
+ };
54
+ })
55
+ .filter((r: any) => r !== null);
56
+ };
@@ -0,0 +1,38 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const unsplash: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ query: query,
9
+ per_page: "20",
10
+ page: String(page || 1),
11
+ });
12
+ const response = await fetch(
13
+ `https://unsplash.com/napi/search/photos?${params}`,
14
+ {
15
+ headers: {
16
+ "User-Agent":
17
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
18
+ },
19
+ }
20
+ );
21
+ if (!response.ok) return [];
22
+ const json = await response.json();
23
+
24
+ if (!json || !json.results) {
25
+ return [];
26
+ }
27
+
28
+ return json.results.map((item: any) => ({
29
+ url: item.links.html,
30
+ title:
31
+ item.description ||
32
+ item.alt_description ||
33
+ "Unsplash Image",
34
+ content: `By ${item.user.name}`,
35
+ thumbnail: item.urls.small,
36
+ engine: "unsplash",
37
+ }));
38
+ };
@@ -0,0 +1,50 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const wallhaven: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ q: query,
9
+ page: String(page || 1),
10
+ purity: "100",
11
+ });
12
+ const response = await fetch(
13
+ `https://wallhaven.cc/api/v1/search?${params}`,
14
+ {
15
+ headers: {
16
+ "User-Agent":
17
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
18
+ },
19
+ }
20
+ );
21
+ if (!response.ok) return [];
22
+ const json = await response.json();
23
+
24
+ if (!json || !json.data) {
25
+ return [];
26
+ }
27
+
28
+ return json.data.map((result: any) => {
29
+ const fileSize = result.file_size
30
+ ? `${(result.file_size / 1024 / 1024).toFixed(2)} MB`
31
+ : "";
32
+ const content = [
33
+ `${result.category} / ${result.purity}`,
34
+ result.resolution ? `Resolution: ${result.resolution}` : "",
35
+ fileSize ? `Size: ${fileSize}` : "",
36
+ result.file_type ? `Format: ${result.file_type}` : "",
37
+ ]
38
+ .filter(Boolean)
39
+ .join(" | ");
40
+
41
+ return {
42
+ url: result.url,
43
+ title: result.resolution || "Wallpaper",
44
+ content,
45
+ engine: "wallhaven",
46
+ img_src: result.path,
47
+ thumbnail: result.thumbs?.small || result.thumbs?.original,
48
+ };
49
+ });
50
+ };
@@ -0,0 +1,41 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const crates: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ q: query,
9
+ page: String(page || 1),
10
+ per_page: "10",
11
+ });
12
+ const response = await fetch(`https://crates.io/api/v1/crates?${params}`, {
13
+ headers: {
14
+ "User-Agent":
15
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
16
+ Accept: "application/json",
17
+ },
18
+ });
19
+ if (!response.ok) return [];
20
+ const data = await response.json();
21
+ const results: any[] = [];
22
+
23
+ if (data && data.crates) {
24
+ data.crates.forEach((crate: any) => {
25
+ const name = crate.name;
26
+ const version = crate.max_version || crate.newest_version;
27
+ const description = crate.description || "No description";
28
+ const downloads = crate.downloads || 0;
29
+ const recent_downloads = crate.recent_downloads || 0;
30
+
31
+ results.push({
32
+ url: `https://crates.io/crates/${name}`,
33
+ title: `${name} ${version}`,
34
+ content: `${description} | 📥 ${downloads.toLocaleString()} total, ${recent_downloads.toLocaleString()} recent`,
35
+ engine: "crates",
36
+ });
37
+ });
38
+ }
39
+
40
+ return results;
41
+ };
@@ -0,0 +1,47 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const dockerhub: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ q: query,
9
+ page: String(page || 1),
10
+ page_size: "25",
11
+ });
12
+ const response = await fetch(
13
+ `https://hub.docker.com/api/search/v3/catalog/search?${params}`,
14
+ {
15
+ headers: {
16
+ "User-Agent":
17
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
18
+ Accept: "application/json",
19
+ },
20
+ }
21
+ );
22
+ if (!response.ok) return [];
23
+ const data = await response.json();
24
+ const results: any[] = [];
25
+
26
+ if (data && data.results) {
27
+ data.results.forEach((item: any) => {
28
+ const name = item.name || item.slug;
29
+ const namespace = item.namespace || "";
30
+ const fullName = namespace ? `${namespace}/${name}` : name;
31
+ const description =
32
+ item.description || item.short_description || "No description";
33
+ const stars = item.star_count || 0;
34
+ const pulls = item.pull_count || 0;
35
+ const isOfficial = item.is_official || false;
36
+
37
+ results.push({
38
+ url: `https://hub.docker.com/r/${fullName}`,
39
+ title: `${fullName}${isOfficial ? " [OFFICIAL]" : ""}`,
40
+ content: `${description} | ⭐ ${stars} | 📥 ${pulls.toLocaleString()} pulls`,
41
+ engine: "dockerhub",
42
+ });
43
+ });
44
+ }
45
+
46
+ return results;
47
+ };
@@ -0,0 +1,37 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const github: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ q: query,
9
+ page: String(page || 1),
10
+ per_page: "10",
11
+ });
12
+ const response = await fetch(
13
+ `https://api.github.com/search/repositories?${params}`,
14
+ {
15
+ headers: {
16
+ "User-Agent": "HonoxSearX/1.0",
17
+ Accept: "application/vnd.github.v3+json",
18
+ },
19
+ }
20
+ );
21
+ if (!response.ok) return [];
22
+ const data = await response.json();
23
+ const results: any[] = [];
24
+
25
+ if (data && data.items) {
26
+ data.items.forEach((item: any) => {
27
+ results.push({
28
+ url: item.html_url,
29
+ title: item.full_name,
30
+ content: item.description || "No description",
31
+ engine: "github",
32
+ });
33
+ });
34
+ }
35
+
36
+ return results;
37
+ };
@@ -0,0 +1,55 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const gitlab: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ search: query,
9
+ page: String(page || 1),
10
+ });
11
+ const response = await fetch(
12
+ `https://gitlab.com/api/v4/projects?${params}`,
13
+ {
14
+ headers: {
15
+ Accept: "application/json",
16
+ },
17
+ }
18
+ );
19
+ if (!response.ok) return [];
20
+ const data = await response.json();
21
+ const results = [];
22
+
23
+ if (Array.isArray(data)) {
24
+ for (const item of data) {
25
+ try {
26
+ const result: any = {
27
+ url: item.web_url || "",
28
+ title: item.name || "",
29
+ content: item.description || "",
30
+ thumbnail: item.avatar_url,
31
+ publishedDate: new Date(
32
+ item.last_activity_at || item.created_at
33
+ ),
34
+ engine: "gitlab",
35
+ };
36
+
37
+ // Add extra fields
38
+ result.template = "packages";
39
+ result.package_name = item.name;
40
+ result.maintainer = item.namespace?.name;
41
+ result.tags = item.tag_list || [];
42
+ result.popularity = item.star_count;
43
+ result.homepage = item.readme_url;
44
+ result.source_code_url = item.http_url_to_repo;
45
+
46
+ results.push(result);
47
+ } catch (error) {
48
+ // Skip malformed results
49
+ continue;
50
+ }
51
+ }
52
+ }
53
+
54
+ return results;
55
+ };
@@ -0,0 +1,36 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const npm: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ text: query,
9
+ size: "10",
10
+ from: String(((page || 1) - 1) * 10),
11
+ });
12
+ const response = await fetch(
13
+ `https://registry.npmjs.org/-/v1/search?${params}`,
14
+ {
15
+ headers: {
16
+ "User-Agent": "HonoxSearX/1.0",
17
+ },
18
+ }
19
+ );
20
+ if (!response.ok) return [];
21
+ const data = await response.json();
22
+ const results: any[] = [];
23
+
24
+ if (data && data.objects) {
25
+ data.objects.forEach((item: any) => {
26
+ results.push({
27
+ url: item.package.links.npm,
28
+ title: item.package.name,
29
+ content: item.package.description || "",
30
+ engine: "npm",
31
+ });
32
+ });
33
+ }
34
+
35
+ return results;
36
+ };
@@ -0,0 +1,43 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const packagist: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ q: query,
9
+ page: String(page || 1),
10
+ per_page: "15",
11
+ });
12
+ const response = await fetch(
13
+ `https://packagist.org/search.json?${params}`,
14
+ {
15
+ headers: {
16
+ "User-Agent":
17
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
18
+ Accept: "application/json",
19
+ },
20
+ }
21
+ );
22
+ if (!response.ok) return [];
23
+ const data = await response.json();
24
+ const results: any[] = [];
25
+
26
+ if (data && data.results) {
27
+ data.results.forEach((pkg: any) => {
28
+ const name = pkg.name;
29
+ const description = pkg.description || "No description";
30
+ const downloads = pkg.downloads || 0;
31
+ const favers = pkg.favers || 0;
32
+
33
+ results.push({
34
+ url: pkg.url || `https://packagist.org/packages/${name}`,
35
+ title: name,
36
+ content: `${description} | 📥 ${downloads.toLocaleString()} downloads | ⭐ ${favers} favorites`,
37
+ engine: "packagist",
38
+ });
39
+ });
40
+ }
41
+
42
+ return results;
43
+ };
@@ -0,0 +1,30 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const pypi: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({ q: query });
8
+ const response = await fetch(`https://pypi.org/search/?${params}`);
9
+ if (!response.ok) return [];
10
+ const html = await response.text();
11
+ const results = [];
12
+
13
+ // PyPI search uses a simple structure
14
+ const packageRegex =
15
+ /<a class="package-snippet"[^>]*href="([^"]+)"[^>]*>[\s\S]*?<span class="package-snippet__name">([^<]+)<\/span>[\s\S]*?<span class="package-snippet__version">([^<]+)<\/span>[\s\S]*?<p class="package-snippet__description">([^<]*)<\/p>/g;
16
+
17
+ let match;
18
+ while ((match = packageRegex.exec(html)) !== null) {
19
+ const [, url, name, version, description] = match;
20
+
21
+ results.push({
22
+ url: `https://pypi.org${url}`,
23
+ title: `${name.trim()} ${version.trim()}`,
24
+ content: description.trim() || "No description available",
25
+ engine: "pypi",
26
+ });
27
+ }
28
+
29
+ return results;
30
+ };
@@ -0,0 +1,43 @@
1
+ import { EngineFunction } from "../../types/search-engine-interface.js";
2
+
3
+ export const rubygems: EngineFunction = async (
4
+ query: string,
5
+ page: number | undefined
6
+ ) => {
7
+ const params = new URLSearchParams({
8
+ query: query,
9
+ page: String(page || 1),
10
+ });
11
+ const response = await fetch(
12
+ `https://rubygems.org/api/v1/search.json?${params}`,
13
+ {
14
+ headers: {
15
+ "User-Agent":
16
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
17
+ Accept: "application/json",
18
+ },
19
+ }
20
+ );
21
+ if (!response.ok) return [];
22
+ const data = await response.json();
23
+ const results: any[] = [];
24
+
25
+ if (Array.isArray(data)) {
26
+ data.forEach((gem: any) => {
27
+ const name = gem.name;
28
+ const version = gem.version;
29
+ const info = gem.info || "No description";
30
+ const downloads = gem.downloads || 0;
31
+ const authors = gem.authors || "";
32
+
33
+ results.push({
34
+ url: `https://rubygems.org/gems/${name}`,
35
+ title: `${name} ${version}`,
36
+ content: `${info} | By: ${authors} | 📥 ${downloads.toLocaleString()} downloads`,
37
+ engine: "rubygems",
38
+ });
39
+ });
40
+ }
41
+
42
+ return results;
43
+ };