@profullstack/nichedb 0.31.0 → 0.33.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/package.json +1 -1
- package/src/index.js +212 -1
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { createInterface } from 'node:readline';
|
|
17
17
|
|
|
18
|
-
export const VERSION = '0.
|
|
18
|
+
export const VERSION = '0.33.0';
|
|
19
19
|
const GEO_FLAGS = ['lat', 'long', 'radius', 'unit', 'bbox', 'sort', 'offset'];
|
|
20
20
|
const DEFAULT_API = process.env.NICHEDB_API ?? 'https://nichedb.dev';
|
|
21
21
|
const CONFIG_DIR = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'nichedb');
|
|
@@ -182,6 +182,47 @@ export const COMMANDS = [
|
|
|
182
182
|
summary: 'Print the RSS URL (or the feed itself with --fetch).',
|
|
183
183
|
options: ['--fetch'],
|
|
184
184
|
},
|
|
185
|
+
{
|
|
186
|
+
name: 'population',
|
|
187
|
+
usage: 'population [<country> [<state> [<city>]]] | zip <zip> | find <name>',
|
|
188
|
+
summary:
|
|
189
|
+
'Population from the world to the ZIP code: an area, its measures and its largest children.',
|
|
190
|
+
options: ['--level country|state|city|zip', '--limit', '--offset', '--json'],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: 'tlds',
|
|
194
|
+
usage: 'tlds [<query>] [--registrar <slug>] [--type <t>] [--max-renew <n>] [--trap] [--sort …]',
|
|
195
|
+
summary:
|
|
196
|
+
"Top-level domains with each one's cheapest register, renew and transfer price, renewal first.",
|
|
197
|
+
options: [
|
|
198
|
+
'--registrar <porkbun|dynadot|cloudflare|ovh>',
|
|
199
|
+
'--type <generic|country-code|…>',
|
|
200
|
+
'--max-renew <n>',
|
|
201
|
+
'--max-register <n>',
|
|
202
|
+
'--trap (renews at 2x the first year or more)',
|
|
203
|
+
'--idn / --no-idn',
|
|
204
|
+
'--status <delegated|removed|all>',
|
|
205
|
+
'--sort <renew|register|transfer|ratio|tld|registrars|first_seen>',
|
|
206
|
+
'--order <asc|desc>',
|
|
207
|
+
'--limit',
|
|
208
|
+
'--csv',
|
|
209
|
+
'--json',
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'tld',
|
|
214
|
+
usage: 'tld <tld> | tld changes',
|
|
215
|
+
summary:
|
|
216
|
+
'One top-level domain: registry, RDAP server and every registrar price; or what changed in the root.',
|
|
217
|
+
options: ['--json'],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'check',
|
|
221
|
+
usage: 'check <name> [--tlds com,dev,io]',
|
|
222
|
+
summary:
|
|
223
|
+
'Is a domain name registered? Asks the registry over RDAP; a bare word is tried under each ending.',
|
|
224
|
+
options: ['--tlds <list>', '--json'],
|
|
225
|
+
},
|
|
185
226
|
{
|
|
186
227
|
name: 'profiles',
|
|
187
228
|
usage: 'profiles [<query>] [--since <iso>] [--mine]',
|
|
@@ -740,6 +781,176 @@ export async function run(
|
|
|
740
781
|
out(await res.text());
|
|
741
782
|
return 0;
|
|
742
783
|
}
|
|
784
|
+
case 'population': {
|
|
785
|
+
const qs = new URLSearchParams();
|
|
786
|
+
if (flags.level) qs.set('level', flags.level);
|
|
787
|
+
if (flags.limit) qs.set('limit', flags.limit);
|
|
788
|
+
if (flags.offset) qs.set('offset', flags.offset);
|
|
789
|
+
const people = (n) =>
|
|
790
|
+
n === null || n === undefined ? '—' : Math.round(n).toLocaleString('en-US');
|
|
791
|
+
const [sub, ...args] = rest;
|
|
792
|
+
|
|
793
|
+
if (sub === 'find') {
|
|
794
|
+
const name = args.join(' ');
|
|
795
|
+
if (!name) throw new Error('population find <name>');
|
|
796
|
+
const { areas } = await client.get(
|
|
797
|
+
`/api/v1/population/search?q=${encodeURIComponent(name)}`,
|
|
798
|
+
);
|
|
799
|
+
if (json) {
|
|
800
|
+
out(JSON.stringify(areas, null, 2));
|
|
801
|
+
return 0;
|
|
802
|
+
}
|
|
803
|
+
for (const a of areas)
|
|
804
|
+
out(`${pad(a.key, 34)} ${pad(a.level, 8)} ${pad(people(a.population), 14)} ${a.title}`);
|
|
805
|
+
if (areas.length === 0) out('(no place by that name)');
|
|
806
|
+
return 0;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
let path = '/api/v1/population';
|
|
810
|
+
if (sub === 'zip') {
|
|
811
|
+
if (!args[0]) throw new Error('population zip <zip>');
|
|
812
|
+
path = `/api/v1/population/zip/${encodeURIComponent(String(args[0]).trim())}`;
|
|
813
|
+
} else if (sub) {
|
|
814
|
+
path = `/api/v1/population/${encodeURIComponent(rest.join('-').toLowerCase())}`;
|
|
815
|
+
}
|
|
816
|
+
const body = await client.get(`${path}${qs.size ? `?${qs}` : ''}`);
|
|
817
|
+
if (json) {
|
|
818
|
+
out(JSON.stringify(body, null, 2));
|
|
819
|
+
return 0;
|
|
820
|
+
}
|
|
821
|
+
if (body.area) {
|
|
822
|
+
const a = body.area;
|
|
823
|
+
out(`${a.title} (${a.key}, ${a.level})`);
|
|
824
|
+
out(
|
|
825
|
+
` ${pad('population', 24)} ${people(a.population)}${a.year ? ` (${a.survey ?? a.year})` : ''}`,
|
|
826
|
+
);
|
|
827
|
+
for (const [k, v] of Object.entries(a.measures ?? {})) {
|
|
828
|
+
if (typeof v === 'number') out(` ${pad(k, 24)} ${v.toLocaleString('en-US')}`);
|
|
829
|
+
}
|
|
830
|
+
out(` ${a.page}`);
|
|
831
|
+
}
|
|
832
|
+
const kids = body.children?.areas ?? body.countries ?? [];
|
|
833
|
+
const total = body.children?.total ?? body.total ?? kids.length;
|
|
834
|
+
if (kids.length) {
|
|
835
|
+
out(
|
|
836
|
+
`\n${body.children?.level ?? 'country'}: ${kids.length} of ${Number(total).toLocaleString('en-US')}`,
|
|
837
|
+
);
|
|
838
|
+
for (const k of kids) out(` ${pad(k.key, 34)} ${pad(people(k.population), 14)} ${k.name}`);
|
|
839
|
+
}
|
|
840
|
+
return 0;
|
|
841
|
+
}
|
|
842
|
+
case 'tlds': {
|
|
843
|
+
const qs = new URLSearchParams();
|
|
844
|
+
const term = rest.join(' ');
|
|
845
|
+
if (term) qs.set('q', term);
|
|
846
|
+
for (const [flag, param] of [
|
|
847
|
+
['registrar', 'registrar'],
|
|
848
|
+
['type', 'type'],
|
|
849
|
+
['maxRenew', 'max_renew'],
|
|
850
|
+
['maxRegister', 'max_register'],
|
|
851
|
+
['status', 'status'],
|
|
852
|
+
['sort', 'sort'],
|
|
853
|
+
['order', 'order'],
|
|
854
|
+
['limit', 'limit'],
|
|
855
|
+
['offset', 'offset'],
|
|
856
|
+
['manager', 'manager'],
|
|
857
|
+
['band', 'band'],
|
|
858
|
+
])
|
|
859
|
+
if (flags[flag] !== undefined && flags[flag] !== true) qs.set(param, flags[flag]);
|
|
860
|
+
if (flags.trap) qs.set('trap', '1');
|
|
861
|
+
if (flags.idn) qs.set('idn', '1');
|
|
862
|
+
if (flags.noIdn) qs.set('idn', '0');
|
|
863
|
+
if (flags.csv) {
|
|
864
|
+
qs.set('format', 'csv');
|
|
865
|
+
const res = await fetchImpl(`${api}/api/v1/tlds?${qs}`);
|
|
866
|
+
out((await res.text()).trimEnd());
|
|
867
|
+
return 0;
|
|
868
|
+
}
|
|
869
|
+
const r = await client.get(`/api/v1/tlds?${qs}`);
|
|
870
|
+
if (json) {
|
|
871
|
+
out(JSON.stringify(r, null, 2));
|
|
872
|
+
return 0;
|
|
873
|
+
}
|
|
874
|
+
const m = (p) =>
|
|
875
|
+
p ? `${p.amount.toFixed(2)} ${p.currency === 'USD' ? '' : p.currency}`.trim() : '-';
|
|
876
|
+
out(
|
|
877
|
+
`${pad('TLD', 22)} ${pad('REGISTER', 10)} ${pad('RENEW', 10)} ${pad('x', 5)} ${pad('TRANSFER', 10)} ${pad('CHEAPEST RENEWAL AT', 22)} REGISTRY`,
|
|
878
|
+
);
|
|
879
|
+
for (const t of r.tlds) {
|
|
880
|
+
out(
|
|
881
|
+
`${pad(`.${t.unicode ?? t.tld}`, 22)} ${pad(m(t.best?.register), 10)} ${pad(m(t.best?.renew), 10)} ${pad(t.renewal_ratio ? `${t.renewal_ratio}x` : '', 5)} ${pad(m(t.best?.transfer), 10)} ${pad(t.best?.renew?.registrar_name ?? '', 22)} ${t.manager ?? ''}`,
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
out(`(${r.tlds.length} of ${r.total}${r.compare_currency ? '; cheapest in USD' : ''})`);
|
|
885
|
+
return 0;
|
|
886
|
+
}
|
|
887
|
+
case 'tld': {
|
|
888
|
+
const [arg] = rest;
|
|
889
|
+
if (!arg) throw new Error('tld <tld> | tld changes');
|
|
890
|
+
if (arg === 'changes') {
|
|
891
|
+
const r = await client.get('/api/v1/tlds/changes');
|
|
892
|
+
if (json) {
|
|
893
|
+
out(JSON.stringify(r.changes, null, 2));
|
|
894
|
+
return 0;
|
|
895
|
+
}
|
|
896
|
+
for (const c of r.changes)
|
|
897
|
+
out(
|
|
898
|
+
`${when(c.at)} ${pad(c.change, 8)} .${pad(c.unicode ?? c.tld, 24)} ${c.list_version ?? ''}`,
|
|
899
|
+
);
|
|
900
|
+
if (!r.changes.length) out('(no changes since tracking began)');
|
|
901
|
+
return 0;
|
|
902
|
+
}
|
|
903
|
+
const t = await client.get(`/api/v1/tlds/${encodeURIComponent(arg.replace(/^\./, ''))}`);
|
|
904
|
+
if (json) {
|
|
905
|
+
out(JSON.stringify(t, null, 2));
|
|
906
|
+
return 0;
|
|
907
|
+
}
|
|
908
|
+
out(`.${t.unicode ?? t.tld}${t.unicode ? ` (${t.tld})` : ''} ${t.status} ${t.type ?? ''}`);
|
|
909
|
+
out(`registry ${t.manager ?? '-'}`);
|
|
910
|
+
out(`rdap ${t.rdap ?? 'none published'}`);
|
|
911
|
+
out('');
|
|
912
|
+
out(
|
|
913
|
+
`${pad('REGISTRAR', 24)} ${pad('CUR', 4)} ${pad('REGISTER', 10)} ${pad('RENEW', 10)} ${pad('TRANSFER', 10)} RESTORE`,
|
|
914
|
+
);
|
|
915
|
+
const f = (v) => (v === null || v === undefined ? '-' : Number(v).toFixed(2));
|
|
916
|
+
for (const p of t.prices)
|
|
917
|
+
out(
|
|
918
|
+
`${pad(p.registrar_name, 24)} ${pad(p.currency, 4)} ${pad(f(p.register), 10)} ${pad(f(p.renew), 10)} ${pad(f(p.transfer), 10)} ${f(p.restore)}`,
|
|
919
|
+
);
|
|
920
|
+
if (!t.prices.length) out('(no registrar read here sells it)');
|
|
921
|
+
out(`\n${t.page}`);
|
|
922
|
+
return 0;
|
|
923
|
+
}
|
|
924
|
+
case 'check': {
|
|
925
|
+
const name = rest.join(',');
|
|
926
|
+
if (!name) throw new Error('check <name> [--tlds com,dev,io]');
|
|
927
|
+
const qs = new URLSearchParams({ name });
|
|
928
|
+
if (flags.tlds && flags.tlds !== true) qs.set('tlds', flags.tlds);
|
|
929
|
+
const r = await client.get(`/api/v1/tlds/check?${qs}`);
|
|
930
|
+
if (json) {
|
|
931
|
+
out(JSON.stringify(r, null, 2));
|
|
932
|
+
return 0;
|
|
933
|
+
}
|
|
934
|
+
const word = {
|
|
935
|
+
registered: 'taken',
|
|
936
|
+
not_registered: 'not registered',
|
|
937
|
+
unknown: 'unknown',
|
|
938
|
+
invalid: 'invalid',
|
|
939
|
+
};
|
|
940
|
+
for (const x of r.results) {
|
|
941
|
+
const detail =
|
|
942
|
+
x.status === 'registered'
|
|
943
|
+
? `${x.registrar ?? ''}${x.expires ? `, expires ${String(x.expires).slice(0, 10)}` : ''}`
|
|
944
|
+
: x.status === 'not_registered'
|
|
945
|
+
? x.cheapest?.register
|
|
946
|
+
? `from ${x.cheapest.register.amount.toFixed(2)} at ${x.cheapest.register.registrar_name}, renews ${x.cheapest.renew?.amount?.toFixed(2) ?? '?'}`
|
|
947
|
+
: ''
|
|
948
|
+
: (x.reason ?? '');
|
|
949
|
+
out(`${pad(x.name, 32)} ${pad(word[x.status] ?? x.status, 15)} ${detail}`);
|
|
950
|
+
}
|
|
951
|
+
if (r.truncated) out('(only the first names were checked)');
|
|
952
|
+
return 0;
|
|
953
|
+
}
|
|
743
954
|
case 'profiles': {
|
|
744
955
|
const term = rest.join(' ');
|
|
745
956
|
const qs = new URLSearchParams();
|