openvibers/openvibe.sources Commits

Sources: Porkbun's public domain price list (every TLD it sells), seeded disabled for OpenVibe.Host's domain comparison (#70)

OP OpenVibers committed 28 days ago · 4af6277fa5 · 4 files, +92 −1

Changes

ddiff --git a/STATUS.json b/STATUS.json
iindex 6aba0ac..b864b22 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -28,7 +28,7 @@
"api",
"manual"
],
-- "seededSources": 39,
++ "seededSources": 40,
"enabledSources": 35,
"deployed": true,
"deployment": "Deployed with `sudo ovhost deploy sources`: /opt/openvibe.sources, unit openvibe-sources, 127.0.0.1:4720, principal sources. On 2026-10-16, 35 of 39 sources were enabled in production (reviews 19, trade 4, news 4, hosting 3, coupons 2, and blog, crowdfunding and deals 1 each), with 17,965 items from 2,156 fetch runs; they feed OpenVibe.Deals, News, Reviews, Trade, Host, Coupons, Blog's desks and Deals' crowdfunding search. Restore drill passed 2026-09-23.",
ddiff --git a/seeds/sources.json b/seeds/sources.json
iindex d422cdd..7dbe2ce 100644
---- a/seeds/sources.json
++++ b/seeds/sources.json
@@@ -1140,6 +1140,33 @@
"sensitivity": "none",
"default_indexability": "noindex"
},
++ {
++ "key": "porkbun-tld-pricing",
++ "name": "Porkbun domain prices (every TLD it sells)",
++ "type": "api",
++ "category": "hosting",
++ "homepage_url": "https://porkbun.com/products/domains",
++ "endpoints": [{
++ "url": "https://api.porkbun.com/api/json/v3/pricing/get",
++ "format": "json",
++ "items_path": "pricing",
++ "items_shape": "map",
++ "item_kind": "tld_price",
++ "max_bytes": 1048576,
++ "fields": { "identity": "$key", "title": "$key" },
++ "extra": { "registration": "registration", "renewal": "renewal", "transfer": "transfer", "special_type": "specialType" }
++ }],
++ "robots_note": "robots.txt checked 2026-10-16: https://api.porkbun.com/robots.txt answers 404 (openresty), so the API host publishes no robots.txt rules; a 404 is treated as no restrictions (never guessed as a disallow). porkbun.com's own robots.txt disallows /api/* on porkbun.com, a different host that is not fetched. The fetcher re-checks robots.txt on every run.",
++ "terms_note": "Porkbun's API v3 documentation (https://porkbun.com/llms/pricing, https://porkbun.com/api/json/v3/documentation) names GET /api/json/v3/pricing/get 'Retrieve domain pricing (public)' and says it 'does not require authentication'; prices are in US dollars. The legal hub (https://porkbun.com/legal) lists no API agreement, and the Product Terms of Service (https://porkbun.com/legal/agreement/product_terms_of_service) has no clause on automated access, API use or republishing prices (read 2026-10-16). One request a day. Attribute Porkbun by linking https://porkbun.com/products/domains; show each price as an observation 'checked on <date>', never a current price, and imply no endorsement. Handshake names (specialType 'handshake') are not ICANN TLDs; OpenVibe.Host leaves them out.",
++ "license_note": "Domain prices are Porkbun's data (https://porkbun.com/products/domains). No licence to republish them was found; attribute Porkbun and link the price list; no endorsement implied.",
++ "min_interval_ms": 60000,
++ "poll_interval_sec": 86400,
++ "max_items": 1200,
++ "enabled": false,
++ "review_required": true,
++ "sensitivity": "none",
++ "default_indexability": "noindex"
++ },
{
"key": "lowendtalk-offers",
"name": "LowEndTalk: the Offers board",
ddiff --git a/test/fixtures/porkbun-pricing.json b/test/fixtures/porkbun-pricing.json
nnew file mode 100644
iindex 0000000..26249dc
---- /dev/null
++++ b/test/fixtures/porkbun-pricing.json
@@@ -0,0 +1,48 @@
++{
++ "status": "SUCCESS",
++ "pricing": {
++ "0zebra": {
++ "registration": "3.63",
++ "renewal": "8.20",
++ "transfer": "8.20",
++ "coupons": [],
++ "specialType": "handshake"
++ },
++ "com": {
++ "registration": "11.08",
++ "renewal": "11.08",
++ "transfer": "11.08",
++ "coupons": []
++ },
++ "com.mx": {
++ "registration": "11.33",
++ "renewal": "23.32",
++ "transfer": "23.32",
++ "coupons": []
++ },
++ "dev": {
++ "registration": "8.75",
++ "renewal": "12.87",
++ "transfer": "12.87",
++ "coupons": []
++ },
++ "protection": {
++ "registration": "2,060.25",
++ "renewal": "2,060.25",
++ "transfer": "2,060.25",
++ "coupons": []
++ },
++ "security": {
++ "registration": "2,060.25",
++ "renewal": "2,060.25",
++ "transfer": "2,060.25",
++ "coupons": []
++ },
++ "xn--q9jyb4c": {
++ "registration": "7.43",
++ "renewal": "7.43",
++ "transfer": "7.43",
++ "coupons": []
++ }
++ }
++}
ddiff --git a/test/hosting-sources.test.js b/test/hosting-sources.test.js
iindex 8bc1e8d..6479d52 100644
---- a/test/hosting-sources.test.js
++++ b/test/hosting-sources.test.js
@@@ -75,6 +75,22 @@ t('Linode /v4/linode/types: identity id, title label, dotted price.monthly/hourl
});
});
++t('Porkbun prices: one item per TLD from the keyed price list, the prices as Porkbun writes them (Host parses them)', () => {
++ const s = seed('porkbun-tld-pricing');
++ assert.deepStrictEqual([s.type, s.category, s.enabled, s.review_required], ['api', 'hosting', false, true]);
++ const ep = endpoint('porkbun-tld-pricing');
++ assert.strictEqual(api.checkMapping(ep), null);
++ assert.strictEqual(validateSource(s).max_items, 1200, 'Porkbun sells about 900 names; the whole list fits');
++ const r = api.parse(fx('porkbun-pricing.json'), { url: ep.url, endpoint: ep });
++ assert.strictEqual(r.skipped, 0);
++ assert.deepStrictEqual(r.items.map((i) => i.identity), ['0zebra', 'com', 'com.mx', 'dev', 'protection', 'security', 'xn--q9jyb4c']);
++ assert.ok(r.items.every((i) => i.kind === 'tld_price' && i.title === i.identity));
++ const by = Object.fromEntries(r.items.map((i) => [i.identity, i.fields]));
++ assert.deepStrictEqual(by.dev, { registration: '8.75', renewal: '12.87', transfer: '12.87', special_type: null });
++ assert.strictEqual(by.security.registration, '2,060.25', 'thousands separators are kept as given');
++ assert.strictEqual(by['0zebra'].special_type, 'handshake', 'Handshake names are marked, for Host to leave out');
++});
++
t('the registry accepts category hosting and refuses an unknown category', () => {
const rec = validateSource(seed('vultr-plans'));
assert.strictEqual(rec.category, 'hosting');