@builtbyecho/public-api-finder 0.5.5 → 0.5.7
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/scripts/eval-hardening.mjs +78 -0
- package/src/cli.js +90 -3
package/package.json
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
const cases = [
|
|
5
|
+
{ q: 'crypto prices', args: ['--no-auth','--https','--cors','Yes'], topCategory: /cryptocurrency/i, top3Category: /cryptocurrency/i, mustName: /dexscreener|dexpaprika|defillama|geckoterminal|coinpaprika/i, forbid: /weather|joke|tvmaze|jobs/i },
|
|
6
|
+
{ q: 'stock prices', args: ['--no-auth','--https','--cors','Yes'], topCategory: /finance/i, top3Category: /finance/i, forbid: /weather|joke|tvmaze|jobs/i },
|
|
7
|
+
{ q: 'free stock quote API no auth', args: ['--no-auth','--https'], topCategory: /finance/i, mustName: /stooq|portfolio|alpha|finnhub|twelve/i, forbid: /weather|joke|anime|tv/i },
|
|
8
|
+
{ q: 'solana token price market cap dex no key', args: ['--no-auth','--https'], topCategory: /cryptocurrency/i, top3Category: /cryptocurrency/i, mustName: /dexscreener|dexpaprika|geckoterminal|defillama/i },
|
|
9
|
+
{ q: 'wallet balance transactions erc20 transfers api', args: [], topCategory: /cryptocurrency|openapi/i, mustName: /etherscan|alchemy|moralis|covalent|block/i },
|
|
10
|
+
{ q: 'weather forecast no auth cors', args: ['--no-auth','--https','--cors','Yes'], topCategory: /weather/i, top3Category: /weather/i, mustName: /open-meteo|national weather|pirate/i, forbid: /joke|crypto|stocks/i },
|
|
11
|
+
{ q: 'historical weather api', args: [], topCategory: /weather/i, mustName: /open-meteo|weather/i },
|
|
12
|
+
{ q: 'weather alerts us api', args: ['--no-auth'], topCategory: /weather/i, mustName: /national weather|weather/i },
|
|
13
|
+
{ q: 'geocoding api no auth', args: ['--no-auth','--https'], topCategory: /geocoding|location/i, mustName: /nominatim|geocod|zippopotam|ipinfo/i },
|
|
14
|
+
{ q: 'reverse geocoding api', args: [], topCategory: /geocoding|location/i, mustName: /nominatim|geocod|mapbox|google/i },
|
|
15
|
+
{ q: 'maps routing api', args: [], topCategory: /geocoding|location/i, mustName: /graphhopper|mapbox|google|here|tomtom/i, forbid: /linkedin/i },
|
|
16
|
+
{ q: 'public transit api', args: [], topCategory: /transport|transit/i, mustName: /transitland|transport|mbta/i },
|
|
17
|
+
{ q: 'jobs api remote', args: [], topCategory: /jobs|agents/i, mustName: /graphql jobs|adzuna|usajobs|search.gov|linkedin jobs/i },
|
|
18
|
+
{ q: 'government jobs api', args: [], topCategory: /jobs|government/i, mustName: /usajobs|search.gov/i },
|
|
19
|
+
{ q: 'company enrichment api', args: [], topCategory: /business|marketing|openapi/i, mustName: /clearbit|brandfetch|opencorporates|company/i },
|
|
20
|
+
{ q: 'email validation api', args: [], topCategory: /email/i, mustName: /abstract|email|mail/i },
|
|
21
|
+
{ q: 'phone number lookup api', args: [], topCategory: /communication|telecom|security|openapi/i, mustName: /numverify|twilio|phone|abstract/i },
|
|
22
|
+
{ q: 'sms api', args: [], topCategory: /communication|messaging|telecom|openapi/i, mustName: /twilio|telnyx|vonage|sms|message/i },
|
|
23
|
+
{ q: 'webhook testing api', args: [], mustName: /webhook|requestbin|pipedream|beeceptor|alchemy/i },
|
|
24
|
+
{ q: 'payment processing api', args: [], topCategory: /payments|financial|openapi/i, mustName: /stripe|paypal|checkout|payment/i },
|
|
25
|
+
{ q: 'subscriptions billing api', args: [], topCategory: /payments|openapi/i, mustName: /stripe|billing|subscription|chargebee/i },
|
|
26
|
+
{ q: 'oauth identity api', args: [], topCategory: /authentication|security|development|openapi/i, mustName: /auth0|clerk|okta|oauth|openid/i },
|
|
27
|
+
{ q: 'passwordless auth api', args: [], topCategory: /authentication|security|openapi/i, mustName: /auth0|clerk|magic|stytch|passwordless/i },
|
|
28
|
+
{ q: 'screenshot api', args: [], topCategory: /development|media|documents|openapi/i, mustName: /urlbox|microlink|screenshot/i },
|
|
29
|
+
{ q: 'link preview api', args: [], topCategory: /development|media|utility|openapi/i, mustName: /microlink|linkpreview|metadata|open graph/i },
|
|
30
|
+
{ q: 'website metadata api', args: [], topCategory: /development|media|utility|openapi/i, mustName: /microlink|metadata|open graph|urlbox/i },
|
|
31
|
+
{ q: 'pdf generation api', args: [], topCategory: /documents|development|utility|openapi/i, mustName: /pdf|document/i },
|
|
32
|
+
{ q: 'ocr api', args: [], topCategory: /machine learning|ai|documents|openapi/i, mustName: /ocr|vision|mindee/i },
|
|
33
|
+
{ q: 'image generation api', args: [], topCategory: /ai|machine learning|media|openapi/i, mustName: /stability|openai|replicate|image/i },
|
|
34
|
+
{ q: 'text to speech api', args: [], topCategory: /ai|audio|machine learning|openapi/i, mustName: /elevenlabs|speech|voice|tts/i },
|
|
35
|
+
{ q: 'speech to text api', args: [], topCategory: /ai|audio|machine learning|openapi/i, mustName: /assemblyai|deepgram|whisper|speech/i },
|
|
36
|
+
{ q: 'vulnerability database api', args: [], topCategory: /security|development|open data/i, mustName: /osv|nvd|cve|vulnerab/i },
|
|
37
|
+
{ q: 'cve lookup api', args: [], topCategory: /security|open data/i, mustName: /nvd|cve|osv/i },
|
|
38
|
+
{ q: 'npm package metadata api', args: ['--no-auth'], topCategory: /development|open data/i, mustName: /npm|libraries.io|package/i },
|
|
39
|
+
{ q: 'github repo stats api', args: [], topCategory: /development|open data/i, mustName: /github|gitlab/i },
|
|
40
|
+
{ q: 'domain dns lookup api', args: [], topCategory: /security|development|openapi/i, mustName: /dns|google dns|whois|ssl/i },
|
|
41
|
+
{ q: 'open data demographics api', args: [], topCategory: /government|open data/i, mustName: /census|data.gov|demographics/i },
|
|
42
|
+
{ q: 'fake ecommerce api for testing', args: ['--no-auth','--https'], topCategory: /test data|shopping/i, mustName: /fake store|dummyjson|jsonplaceholder/i },
|
|
43
|
+
{ q: 'joke api', args: ['--no-auth'], topCategory: /entertainment|jokes/i, mustName: /joke/i, forbid: /weather|crypto|stock/i },
|
|
44
|
+
{ q: 'cat images random dog facts no auth', args: ['--no-auth'], mustName: /cat|dog|thecatapi|dog ceo/i, forbid: /weather|stock|crypto/i },
|
|
45
|
+
{ q: 'food barcode ingredients allergens nutrition no auth cors', args: ['--no-auth','--https'], topCategory: /food/i, mustName: /open food facts/i },
|
|
46
|
+
{ q: 'recipe from pantry ingredients avoid allergens api', args: [], topCategory: /food/i, mustName: /spoonacular|edamam|recipe|meal/i },
|
|
47
|
+
{ q: 'air quality by coordinates pollutant measurements', args: [], topCategory: /environment|science|weather/i, mustName: /openaq|air quality|aqicn/i },
|
|
48
|
+
{ q: 'historical currency exchange rates no auth cors', args: ['--no-auth','--https'], topCategory: /currency exchange/i, mustName: /frankfurter|currency/i, forbid: /crypto|bitcoin|dex/i },
|
|
49
|
+
{ q: 'public holidays by country no auth', args: ['--no-auth','--https'], topCategory: /calendar|date/i, mustName: /nager|holiday|calendarific/i },
|
|
50
|
+
{ q: 'vehicle vin decode recall safety data no auth', args: ['--no-auth'], topCategory: /transportation|government|open data/i, mustName: /nhtsa|vin|vehicle/i },
|
|
51
|
+
{ q: 'real estate property value rent estimate api', args: [], topCategory: /real estate|property|openapi|finance/i, mustName: /rentcast|attom|zillow|real estate|property/i },
|
|
52
|
+
{ q: 'flight status airport arrivals departures api', args: [], topCategory: /transportation|travel|openapi/i, mustName: /aviation|flight|airport|amadeus/i },
|
|
53
|
+
{ q: 'hotel search booking availability api', args: [], topCategory: /travel|commerce|openapi/i, mustName: /hotel|booking|amadeus/i },
|
|
54
|
+
{ q: 'brand colors fonts logo company domain enrichment', args: [], topCategory: /business|marketing|development|openapi/i, mustName: /brandfetch|clearbit|logo|brand/i },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
let failures = 0;
|
|
58
|
+
for (const [i, c] of cases.entries()) {
|
|
59
|
+
const res = spawnSync(process.execPath, ['src/cli.js', c.q, ...c.args, '--limit', '5', '--json'], { encoding: 'utf8' });
|
|
60
|
+
const rows = res.status === 0 ? JSON.parse(res.stdout || '[]') : [];
|
|
61
|
+
const top = rows[0];
|
|
62
|
+
const checks = [];
|
|
63
|
+
if (!top) checks.push('no results');
|
|
64
|
+
if (top && c.topCategory && !c.topCategory.test(top.category || '')) checks.push(`top category ${top.category} !~ ${c.topCategory}`);
|
|
65
|
+
if (top && c.top3Category) {
|
|
66
|
+
const bad = rows.slice(0, 3).filter(r => !c.top3Category.test(r.category || '')).map(r => `${r.name} (${r.category})`);
|
|
67
|
+
if (bad.length) checks.push(`top3 off-domain: ${bad.join(', ')}`);
|
|
68
|
+
}
|
|
69
|
+
if (c.mustName && !rows.slice(0, 3).some(r => c.mustName.test(`${r.name} ${r.description} ${r.url}`))) checks.push(`top3 missing ${c.mustName}`);
|
|
70
|
+
if (c.forbid && rows.slice(0, 5).some(r => c.forbid.test(`${r.name} ${r.category} ${r.description}`))) checks.push(`forbidden result ${c.forbid}`);
|
|
71
|
+
const ok = checks.length === 0;
|
|
72
|
+
if (!ok) failures++;
|
|
73
|
+
console.log(`${ok ? 'PASS' : 'FAIL'} ${i + 1}. ${c.q}`);
|
|
74
|
+
rows.slice(0, 3).forEach((r, idx) => console.log(` ${idx + 1}. ${r.name} | ${r.category} | auth=${r.auth} | cors=${r.cors} | score=${r.score}`));
|
|
75
|
+
if (!ok) console.log(` Reasons: ${checks.join('; ')}`);
|
|
76
|
+
}
|
|
77
|
+
console.log(`\n${cases.length - failures}/${cases.length} hardening cases passed`);
|
|
78
|
+
process.exitCode = failures ? 1 : 0;
|
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 = 16;
|
|
16
17
|
|
|
17
18
|
const ENRICHMENT_FIELDS = [
|
|
18
19
|
'tags',
|
|
@@ -53,6 +54,15 @@ const TARGETED_BOOSTS = [
|
|
|
53
54
|
[/\b(sanctions|ofac|pep|kyc|aml)\b/, /\b(opensanctions|ofac|sanctions|pep|kyc|aml|chainalysis)\b/i, 135],
|
|
54
55
|
[/\b(wallet risk|crypto sanctions|blockchain wallet risk)\b/, /\b(chainalysis|trm|elliptic|sanctions|wallet risk)\b/i, 95],
|
|
55
56
|
[/\b(crypto token metadata|token metadata|token logos|coin logos|logos contract addresses)\b/, /\b(coinmarketcap|coingecko|coinpaprika|coinbase|token logos|coin metadata|coin images)\b/i, 90],
|
|
57
|
+
[/\b(stock|stocks|stock quote|stock prices|equity|equities|ticker|tickers)\b/, /\b(stooq|portfolio optimizer|alpha vantage|polygon|finnhub|twelve data|stock|stocks|equity|ticker|quote)\b/i, 90],
|
|
58
|
+
[/\b(reverse geocoding|geocoding|maps routing|routing api|distance matrix|places)\b/, /\b(nominatim|geocod\.io|graphhopper|mapbox|foursquare|geocoding|routing|distance matrix|places)\b/i, 120],
|
|
59
|
+
[/\b(webhook testing|webhook debug|request bin|mock api)\b/, /\b(webhook\.site|beeceptor|requestbin|webhook|mock api|request bin)\b/i, 180],
|
|
60
|
+
[/\b(oauth identity|passwordless auth|openid|social auth|login oauth)\b/, /\b(auth0|clerk|stytch|magic|openid|oauth|passwordless|authentication|identity)\b/i, 190],
|
|
61
|
+
[/\b(cve lookup|vulnerability database|vulnerability data|package security)\b/, /\b(osv|nvd|cve|cvss|vulnerability|security advisories)\b/i, 190],
|
|
62
|
+
[/\b(cve lookup|cve api|cve search)\b/, /\b(osv|nvd|cve)\b/i, 260],
|
|
63
|
+
[/\b(open data demographics|demographics|census data)\b/, /\b(census|data\.gov|demographics|open data|government data)\b/i, 145],
|
|
64
|
+
[/\b(joke|jokes|meme|memes|random quote)\b/, /\b(official joke|joke api|jokes|quotable|quote)\b/i, 110],
|
|
65
|
+
[/\b(github repo|repo stats|stars issues commits)\b/, /\b(github rest|github|gitlab|repositories|stars|issues|commits)\b/i, 100],
|
|
56
66
|
[/\b(zipcode|zip code|postal code)\b/, /\b(zippopotam|zip|postal|census)\b/i, 80],
|
|
57
67
|
[/\b(real estate|property value|rent estimate)\b/, /\b(rentcast|attom|zillow|real estate|property|rent estimate)\b/i, 135],
|
|
58
68
|
[/\b(mortgage|mortgage rates|loan calculator|loan rate|home loan)\b/, /\b(mortgage|home loan|loan rate)\b/i, 230],
|
|
@@ -387,9 +397,13 @@ const CURATED_APIS = [
|
|
|
387
397
|
{ name: 'GitHub REST API', url: 'https://docs.github.com/en/rest', description: 'GitHub repositories, stars, issues, commits, pull requests, releases, users, and org data API.', auth: 'No', https: true, cors: 'Yes', category: 'Development', source: 'curated', sourceWeight: 5 },
|
|
388
398
|
{ name: 'npm Registry API', url: 'https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md', description: 'No-auth npm package metadata, versions, downloads, dist-tags, registry documents, and package data.', auth: 'No', https: true, cors: 'Yes', category: 'Development', source: 'curated', sourceWeight: 5 },
|
|
389
399
|
{ name: 'Docker Hub', url: 'https://docs.docker.com/docker-hub/api/latest/', description: 'Docker image repositories, tags, registry metadata, namespaces, vulnerabilities, and container image data API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Development', source: 'curated', sourceWeight: 5 },
|
|
400
|
+
{ name: 'Webhook.site', url: 'https://webhook.site/', description: 'Webhook testing, inspect HTTP requests, debug callbacks, request bins, and temporary webhook endpoints.', auth: 'No', https: true, cors: 'Yes', category: 'Development', source: 'curated', sourceWeight: 5 },
|
|
401
|
+
{ name: 'Beeceptor', url: 'https://beeceptor.com/docs/', description: 'Webhook testing, mock APIs, request bin inspection, HTTP debugging, and endpoint simulation.', auth: 'No', https: true, cors: 'Unknown', category: 'Development', source: 'curated', sourceWeight: 5 },
|
|
390
402
|
|
|
391
403
|
{ name: 'Auth0', url: 'https://auth0.com/docs/api', description: 'OAuth, OpenID Connect, login, user profiles, social auth, authentication, and identity APIs.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Authentication', source: 'curated', sourceWeight: 5 },
|
|
392
404
|
{ name: 'Clerk', url: 'https://clerk.com/docs/reference/backend-api', description: 'Authentication, user profiles, organizations, sessions, OAuth, social login, and identity APIs.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Authentication', source: 'curated', sourceWeight: 5 },
|
|
405
|
+
{ name: 'Stytch', url: 'https://stytch.com/docs/api', description: 'Passwordless login, magic links, OTP, OAuth, passkeys, sessions, and user authentication API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Authentication', source: 'curated', sourceWeight: 5 },
|
|
406
|
+
{ name: 'Magic', url: 'https://magic.link/docs/api', description: 'Passwordless authentication, magic links, wallets, login, users, and identity API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Authentication', source: 'curated', sourceWeight: 5 },
|
|
393
407
|
{ name: 'Mail.tm', url: 'https://docs.mail.tm/', description: 'No-auth temporary email inboxes, disposable mail accounts, receive messages, and testing email API.', auth: 'No', https: true, cors: 'Yes', category: 'Email', source: 'curated', sourceWeight: 5 },
|
|
394
408
|
{ name: 'Twilio Verify', url: 'https://www.twilio.com/docs/verify/api', description: 'SMS OTP verification, phone verification, one-time passcodes, factors, and verification checks.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Telecom', source: 'curated', sourceWeight: 5 },
|
|
395
409
|
{ name: 'numverify', url: 'https://numverify.com/documentation', description: 'Phone number validation, carrier, country, location, line type, and international number lookup API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Telecom', source: 'curated', sourceWeight: 5 },
|
|
@@ -683,7 +697,7 @@ Usage:
|
|
|
683
697
|
|
|
684
698
|
Options:
|
|
685
699
|
--category <name> Filter by category substring
|
|
686
|
-
--source <name> Filter by source: public-api-lists, public-apis, apis-guru, curated
|
|
700
|
+
--source <name> Filter by source: public-api-lists, public-apis, apis-guru, api-mega-list, curated
|
|
687
701
|
--no-auth Only APIs with Auth = No
|
|
688
702
|
--https Only HTTPS APIs
|
|
689
703
|
--cors <value> Filter by CORS: Yes, No, Unknown
|
|
@@ -835,6 +849,37 @@ function intentPenalty(entry, queryText) {
|
|
|
835
849
|
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;
|
|
836
850
|
}
|
|
837
851
|
|
|
852
|
+
if (/\b(school district|school boundary|district boundary|reverse geocoding|maps routing|routing api|open data demographics|demographics|census data)\b/.test(queryText) && /\b(linkedin|jobs scraper|lead|sales|recruiting|amazon product scraper|tiktok profile scraper)\b/.test(text)) {
|
|
853
|
+
penalty += 120;
|
|
854
|
+
}
|
|
855
|
+
if (/\b(stock|stocks|stock quote|stock prices|equity|equities|ticker|tickers)\b/.test(queryText) && !/finance|financial|stock|stocks|equity|ticker|market data|portfolio|stooq|finnhub|polygon|alpha vantage|twelve data/.test(text)) {
|
|
856
|
+
penalty += 80;
|
|
857
|
+
}
|
|
858
|
+
if (/\b(stock|stocks|stock quote|stock prices|equity|equities|ticker|tickers)\b/.test(queryText) && /\b(quotable|joke|entertainment|food|weather|test data|placeholder)\b/.test(text)) {
|
|
859
|
+
penalty += 140;
|
|
860
|
+
}
|
|
861
|
+
if (/\bcrypto\b/.test(queryText) && /\bnot stocks?\b/.test(queryText) && /\b(finance|financial|stock|stocks|equity|ticker|portfolio|stooq|finnhub|polygon|sec edgar|predscope|valueray|econdb)\b/.test(text)) {
|
|
862
|
+
penalty += 240;
|
|
863
|
+
}
|
|
864
|
+
if (/\b(oauth identity|passwordless auth|openid|social auth|login oauth)\b/.test(queryText) && !/authentication|auth0|clerk|stytch|magic|openid|identity|passwordless|social auth/.test(text)) {
|
|
865
|
+
penalty += 160;
|
|
866
|
+
}
|
|
867
|
+
if (/\b(oauth identity|passwordless auth|openid|social auth|login oauth)\b/.test(queryText) && /\b(calendar|nager|holiday|open food|plaid|clearbit logo)\b/.test(text)) {
|
|
868
|
+
penalty += 180;
|
|
869
|
+
}
|
|
870
|
+
if (/\b(webhook testing|webhook debug|request bin|mock api)\b/.test(queryText) && !/webhook|beeceptor|requestbin|mock api|pipedream/.test(text)) {
|
|
871
|
+
penalty += 80;
|
|
872
|
+
}
|
|
873
|
+
if (/\b(cve lookup|vulnerability database|vulnerability data|package security)\b/.test(queryText) && !/osv|nvd|cve|cvss|vulnerability|security advisories/.test(text)) {
|
|
874
|
+
penalty += 95;
|
|
875
|
+
}
|
|
876
|
+
if (/\b(cve lookup|cve api|cve search)\b/.test(queryText) && !/osv|nvd|cve/.test(text)) {
|
|
877
|
+
penalty += 180;
|
|
878
|
+
}
|
|
879
|
+
if (/\b(joke api|jokes?|memes?)\b/.test(queryText) && !/joke|meme|quote/.test(text)) {
|
|
880
|
+
penalty += 70;
|
|
881
|
+
}
|
|
882
|
+
|
|
838
883
|
if (!cat.includes('cryptocurrency') && /\b(wallet address|identicon|avatar|profile picture)\b/.test(queryText) && /\b(avatar|identicon|profile picture)\b/.test(text)) {
|
|
839
884
|
penalty -= 20;
|
|
840
885
|
}
|
|
@@ -926,6 +971,42 @@ function parsePublicApisReadme(readme) {
|
|
|
926
971
|
return entries;
|
|
927
972
|
}
|
|
928
973
|
|
|
974
|
+
|
|
975
|
+
function parseApiMegaList(readme) {
|
|
976
|
+
const entries = [];
|
|
977
|
+
let category = '';
|
|
978
|
+
for (const raw of readme.split('\n')) {
|
|
979
|
+
const heading = raw.match(/^##\s+(.+?)\s*$/) || raw.match(/^###\s+(.+?)\s*$/);
|
|
980
|
+
if (heading) {
|
|
981
|
+
const text = heading[1].replace(/[#*_`]/g, '').trim();
|
|
982
|
+
if (text && !/table of contents|repository stats|star this|join my|contributing|license/i.test(text)) category = normalizeCategory(text.replace(/^\d+\.\s*/, ''));
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
if (!raw.startsWith('| [')) continue;
|
|
986
|
+
const cells = raw.split('|').slice(1, -1).map(c => c.trim());
|
|
987
|
+
if (cells.length < 2) continue;
|
|
988
|
+
if (/^-+$/.test(cells[0]) || /^api name$/i.test(cells[0])) continue;
|
|
989
|
+
const link = cells[0].match(/\[([^\]]+)\]\(([^)]+)\)/);
|
|
990
|
+
if (!link) continue;
|
|
991
|
+
const name = link[1].replace(/<[^>]+>/g, '').trim();
|
|
992
|
+
const url = link[2].trim();
|
|
993
|
+
const description = cleanDescription(cells[1] || `${name} API`);
|
|
994
|
+
if (!name || !/^https?:\/\//i.test(url)) continue;
|
|
995
|
+
entries.push({
|
|
996
|
+
name,
|
|
997
|
+
url,
|
|
998
|
+
description,
|
|
999
|
+
auth: 'Unknown',
|
|
1000
|
+
https: /^https:/i.test(url),
|
|
1001
|
+
cors: 'Unknown',
|
|
1002
|
+
category: category || 'Unknown',
|
|
1003
|
+
source: 'api-mega-list',
|
|
1004
|
+
sourceWeight: 1,
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
return entries;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
929
1010
|
function parseApisGuru(data) {
|
|
930
1011
|
const entries = [];
|
|
931
1012
|
for (const [providerName, item] of Object.entries(data || {})) {
|
|
@@ -950,10 +1031,11 @@ function parseApisGuru(data) {
|
|
|
950
1031
|
}
|
|
951
1032
|
|
|
952
1033
|
async function buildData() {
|
|
953
|
-
const [pal, publicApisReadme, guru] = await Promise.allSettled([
|
|
1034
|
+
const [pal, publicApisReadme, guru, megaList] = await Promise.allSettled([
|
|
954
1035
|
fetchJson(SOURCES.publicApiLists),
|
|
955
1036
|
fetchText(SOURCES.publicApisReadme),
|
|
956
1037
|
fetchJson(SOURCES.apisGuru),
|
|
1038
|
+
fetchText(SOURCES.apiMegaList),
|
|
957
1039
|
]);
|
|
958
1040
|
const entries = [];
|
|
959
1041
|
const sourceStatus = {};
|
|
@@ -971,6 +1053,11 @@ async function buildData() {
|
|
|
971
1053
|
sourceStatus['apis-guru'] = rows.length;
|
|
972
1054
|
entries.push(...rows);
|
|
973
1055
|
} else sourceStatus['apis-guru'] = `error: ${guru.reason.message}`;
|
|
1056
|
+
if (megaList.status === 'fulfilled') {
|
|
1057
|
+
const rows = parseApiMegaList(megaList.value);
|
|
1058
|
+
sourceStatus['api-mega-list'] = rows.length;
|
|
1059
|
+
entries.push(...rows);
|
|
1060
|
+
} else sourceStatus['api-mega-list'] = `error: ${megaList.reason.message}`;
|
|
974
1061
|
sourceStatus.curated = ENRICHED_CURATED_APIS.length;
|
|
975
1062
|
entries.push(...ENRICHED_CURATED_APIS);
|
|
976
1063
|
const deduped = dedupe(entries).map(enrichApiMetadata);
|