@builtbyecho/public-api-finder 0.5.4 → 0.5.6
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/package.json +1 -1
- package/src/cli.js +94 -12
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -9,10 +9,11 @@ const SOURCES = {
|
|
|
9
9
|
publicApiLists: 'https://public-api-lists.github.io/public-api-lists/api/all.json',
|
|
10
10
|
publicApisReadme: 'https://raw.githubusercontent.com/public-apis/public-apis/master/README.md',
|
|
11
11
|
apisGuru: 'https://api.apis.guru/v2/list.json',
|
|
12
|
+
apiMegaList: 'https://raw.githubusercontent.com/cporter202/API-mega-list/main/README.md',
|
|
12
13
|
};
|
|
13
14
|
const CACHE_PATH = process.env.PUBLIC_API_FINDER_CACHE || join(homedir(), '.cache', 'public-api-finder', 'all.json');
|
|
14
15
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
15
|
-
const DATA_VERSION =
|
|
16
|
+
const DATA_VERSION = 15;
|
|
16
17
|
|
|
17
18
|
const ENRICHMENT_FIELDS = [
|
|
18
19
|
'tags',
|
|
@@ -574,23 +575,51 @@ function normalizeAuthType(auth) {
|
|
|
574
575
|
return value && value !== 'unknown' ? value : 'unknown';
|
|
575
576
|
}
|
|
576
577
|
|
|
577
|
-
function
|
|
578
|
+
function wordsFrom(value) {
|
|
579
|
+
return String(value || '')
|
|
580
|
+
.replace(/&/g, ' and ')
|
|
581
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
582
|
+
.toLowerCase()
|
|
583
|
+
.match(/[a-z0-9]+/g) || [];
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function categoryTags(category) {
|
|
587
|
+
const words = wordsFrom(category).filter(word => !['and', 'api', 'apis', 'openapi', 'unknown'].includes(word));
|
|
588
|
+
const tags = [];
|
|
589
|
+
const categoryText = String(category || '').toLowerCase();
|
|
590
|
+
if (categoryText && !['unknown', 'openapi'].includes(categoryText)) tags.push(categoryText.replace(/\s+/g, ' '));
|
|
591
|
+
for (const word of words) tags.push(word);
|
|
592
|
+
return tags;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function inferTags(api, specific = {}) {
|
|
596
|
+
const text = `${api.name || ''} ${api.category || ''} ${api.description || ''} ${api.url || ''} ${api.provider || ''} ${(specific.tags || []).join(' ')}`;
|
|
597
|
+
return INTENT_TAG_RULES.filter(([pattern]) => pattern.test(text)).map(([, tag]) => tag);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function enrichApiMetadata(api) {
|
|
578
601
|
const categoryBase = CURATED_CATEGORY_ENRICHMENTS[api.category] || {};
|
|
579
602
|
const specific = CURATED_API_ENRICHMENTS[api.name] || {};
|
|
580
|
-
const text = `${api.name || ''} ${api.description || ''} ${(specific.tags || []).join(' ')}`;
|
|
581
|
-
const inferredTags = INTENT_TAG_RULES.filter(([pattern]) => pattern.test(text)).map(([, tag]) => tag);
|
|
582
603
|
const merged = { ...categoryBase, ...specific, ...api };
|
|
604
|
+
const tags = uniqueStrings([
|
|
605
|
+
...(categoryBase.tags || []),
|
|
606
|
+
...(specific.tags || []),
|
|
607
|
+
...inferTags(api, specific),
|
|
608
|
+
...(api.tags || []),
|
|
609
|
+
...categoryTags(api.category),
|
|
610
|
+
]);
|
|
611
|
+
|
|
583
612
|
merged.authType = api.authType || specific.authType || categoryBase.authType || normalizeAuthType(api.auth);
|
|
584
613
|
merged.providerType = api.providerType || specific.providerType || categoryBase.providerType || 'public-api';
|
|
585
|
-
merged.tags =
|
|
586
|
-
merged.domains = uniqueStrings([...(categoryBase.domains || []), ...(specific.domains || []), ...(api.domains || [])]);
|
|
614
|
+
merged.tags = tags.length ? tags : ['public api'];
|
|
615
|
+
merged.domains = uniqueStrings([...(categoryBase.domains || []), ...(specific.domains || []), ...(api.domains || []), ...categoryTags(api.category).slice(0, 2)]);
|
|
587
616
|
merged.useCases = uniqueStrings([...(categoryBase.useCases || []), ...(specific.useCases || []), ...(api.useCases || [])]);
|
|
588
617
|
merged.caveats = uniqueStrings([...(categoryBase.caveats || []), ...(specific.caveats || []), ...(api.caveats || [])]);
|
|
589
618
|
merged.bestFor = api.bestFor || specific.bestFor || categoryBase.bestFor;
|
|
590
619
|
return merged;
|
|
591
620
|
}
|
|
592
621
|
|
|
593
|
-
const ENRICHED_CURATED_APIS = CURATED_APIS.map(
|
|
622
|
+
const ENRICHED_CURATED_APIS = CURATED_APIS.map(enrichApiMetadata);
|
|
594
623
|
|
|
595
624
|
export function getCuratedApis() {
|
|
596
625
|
return ENRICHED_CURATED_APIS.map(api => ({ ...api, tags: [...(api.tags || [])], domains: [...(api.domains || [])], useCases: [...(api.useCases || [])] }));
|
|
@@ -655,7 +684,7 @@ Usage:
|
|
|
655
684
|
|
|
656
685
|
Options:
|
|
657
686
|
--category <name> Filter by category substring
|
|
658
|
-
--source <name> Filter by source: public-api-lists, public-apis, apis-guru, curated
|
|
687
|
+
--source <name> Filter by source: public-api-lists, public-apis, apis-guru, api-mega-list, curated
|
|
659
688
|
--no-auth Only APIs with Auth = No
|
|
660
689
|
--https Only HTTPS APIs
|
|
661
690
|
--cors <value> Filter by CORS: Yes, No, Unknown
|
|
@@ -705,8 +734,10 @@ function applyQueryHints(args) {
|
|
|
705
734
|
if (!args.cors && /\b(cors|frontend-safe|browser-safe|frontend safe|browser safe)\b/.test(query)) args.cors = 'Yes';
|
|
706
735
|
}
|
|
707
736
|
|
|
737
|
+
const SEARCH_STOPWORDS = new Set(['a', 'an', 'and', 'api', 'apis', 'for', 'from', 'in', 'no', 'of', 'on', 'or', 'the', 'to', 'with']);
|
|
738
|
+
|
|
708
739
|
function tokenSet(text) {
|
|
709
|
-
return new Set(String(text).toLowerCase().match(/[a-z0-9]+/g)?.filter(t => t.length > 1) || []);
|
|
740
|
+
return new Set(String(text).toLowerCase().match(/[a-z0-9]+/g)?.filter(t => t.length > 1 && !SEARCH_STOPWORDS.has(t)) || []);
|
|
710
741
|
}
|
|
711
742
|
|
|
712
743
|
function intersectionCount(a, b) {
|
|
@@ -805,6 +836,10 @@ function intentPenalty(entry, queryText) {
|
|
|
805
836
|
if (/\b(favicon|website preview|open graph|link preview|screenshot)\b/.test(queryText) && !/\b(microlink|urlbox|favicon|website metadata|open graph|link preview|screenshot)\b/.test(text)) penalty += 120;
|
|
806
837
|
}
|
|
807
838
|
|
|
839
|
+
if (/\b(school district|school boundary|district boundary)\b/.test(queryText) && /\b(linkedin|jobs scraper|lead|sales|recruiting)\b/.test(text)) {
|
|
840
|
+
penalty += 95;
|
|
841
|
+
}
|
|
842
|
+
|
|
808
843
|
if (!cat.includes('cryptocurrency') && /\b(wallet address|identicon|avatar|profile picture)\b/.test(queryText) && /\b(avatar|identicon|profile picture)\b/.test(text)) {
|
|
809
844
|
penalty -= 20;
|
|
810
845
|
}
|
|
@@ -896,6 +931,42 @@ function parsePublicApisReadme(readme) {
|
|
|
896
931
|
return entries;
|
|
897
932
|
}
|
|
898
933
|
|
|
934
|
+
|
|
935
|
+
function parseApiMegaList(readme) {
|
|
936
|
+
const entries = [];
|
|
937
|
+
let category = '';
|
|
938
|
+
for (const raw of readme.split('\n')) {
|
|
939
|
+
const heading = raw.match(/^##\s+(.+?)\s*$/) || raw.match(/^###\s+(.+?)\s*$/);
|
|
940
|
+
if (heading) {
|
|
941
|
+
const text = heading[1].replace(/[#*_`]/g, '').trim();
|
|
942
|
+
if (text && !/table of contents|repository stats|star this|join my|contributing|license/i.test(text)) category = normalizeCategory(text.replace(/^\d+\.\s*/, ''));
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
if (!raw.startsWith('| [')) continue;
|
|
946
|
+
const cells = raw.split('|').slice(1, -1).map(c => c.trim());
|
|
947
|
+
if (cells.length < 2) continue;
|
|
948
|
+
if (/^-+$/.test(cells[0]) || /^api name$/i.test(cells[0])) continue;
|
|
949
|
+
const link = cells[0].match(/\[([^\]]+)\]\(([^)]+)\)/);
|
|
950
|
+
if (!link) continue;
|
|
951
|
+
const name = link[1].replace(/<[^>]+>/g, '').trim();
|
|
952
|
+
const url = link[2].trim();
|
|
953
|
+
const description = cleanDescription(cells[1] || `${name} API`);
|
|
954
|
+
if (!name || !/^https?:\/\//i.test(url)) continue;
|
|
955
|
+
entries.push({
|
|
956
|
+
name,
|
|
957
|
+
url,
|
|
958
|
+
description,
|
|
959
|
+
auth: 'Unknown',
|
|
960
|
+
https: /^https:/i.test(url),
|
|
961
|
+
cors: 'Unknown',
|
|
962
|
+
category: category || 'Unknown',
|
|
963
|
+
source: 'api-mega-list',
|
|
964
|
+
sourceWeight: 1,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
return entries;
|
|
968
|
+
}
|
|
969
|
+
|
|
899
970
|
function parseApisGuru(data) {
|
|
900
971
|
const entries = [];
|
|
901
972
|
for (const [providerName, item] of Object.entries(data || {})) {
|
|
@@ -920,10 +991,11 @@ function parseApisGuru(data) {
|
|
|
920
991
|
}
|
|
921
992
|
|
|
922
993
|
async function buildData() {
|
|
923
|
-
const [pal, publicApisReadme, guru] = await Promise.allSettled([
|
|
994
|
+
const [pal, publicApisReadme, guru, megaList] = await Promise.allSettled([
|
|
924
995
|
fetchJson(SOURCES.publicApiLists),
|
|
925
996
|
fetchText(SOURCES.publicApisReadme),
|
|
926
997
|
fetchJson(SOURCES.apisGuru),
|
|
998
|
+
fetchText(SOURCES.apiMegaList),
|
|
927
999
|
]);
|
|
928
1000
|
const entries = [];
|
|
929
1001
|
const sourceStatus = {};
|
|
@@ -941,9 +1013,15 @@ async function buildData() {
|
|
|
941
1013
|
sourceStatus['apis-guru'] = rows.length;
|
|
942
1014
|
entries.push(...rows);
|
|
943
1015
|
} else sourceStatus['apis-guru'] = `error: ${guru.reason.message}`;
|
|
1016
|
+
if (megaList.status === 'fulfilled') {
|
|
1017
|
+
const rows = parseApiMegaList(megaList.value);
|
|
1018
|
+
sourceStatus['api-mega-list'] = rows.length;
|
|
1019
|
+
entries.push(...rows);
|
|
1020
|
+
} else sourceStatus['api-mega-list'] = `error: ${megaList.reason.message}`;
|
|
944
1021
|
sourceStatus.curated = ENRICHED_CURATED_APIS.length;
|
|
945
1022
|
entries.push(...ENRICHED_CURATED_APIS);
|
|
946
|
-
|
|
1023
|
+
const deduped = dedupe(entries).map(enrichApiMetadata);
|
|
1024
|
+
return { dataVersion: DATA_VERSION, generatedAt: new Date().toISOString(), sourceStatus, entries: deduped };
|
|
947
1025
|
}
|
|
948
1026
|
|
|
949
1027
|
function keyFor(entry) {
|
|
@@ -1006,7 +1084,7 @@ function dedupe(entries) {
|
|
|
1006
1084
|
async function loadData(refresh = false) {
|
|
1007
1085
|
if (!refresh && await cacheIsFresh()) {
|
|
1008
1086
|
const cached = JSON.parse(await readFile(CACHE_PATH, 'utf8'));
|
|
1009
|
-
if (cached.dataVersion === DATA_VERSION) return cached.entries || [];
|
|
1087
|
+
if (cached.dataVersion === DATA_VERSION) return (cached.entries || []).map(enrichApiMetadata);
|
|
1010
1088
|
}
|
|
1011
1089
|
const data = await buildData();
|
|
1012
1090
|
await mkdir(dirname(CACHE_PATH), { recursive: true });
|
|
@@ -1084,6 +1162,10 @@ async function checkRows(rows) {
|
|
|
1084
1162
|
return checked;
|
|
1085
1163
|
}
|
|
1086
1164
|
|
|
1165
|
+
export async function buildSearchIndex(options = {}) {
|
|
1166
|
+
return loadData(Boolean(options.refresh));
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1087
1169
|
export async function searchApis(options = {}) {
|
|
1088
1170
|
const args = {
|
|
1089
1171
|
query: options.query || '',
|