ipwhoami 1.0.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/LICENSE +21 -0
- package/README.md +207 -0
- package/bin/ipwhoami.js +5 -0
- package/package.json +49 -0
- package/src/cli.js +146 -0
- package/src/colors.js +11 -0
- package/src/config.js +7 -0
- package/src/formatter.js +33 -0
- package/src/ip.js +31 -0
- package/src/providers/base.js +19 -0
- package/src/providers/index.js +21 -0
- package/src/providers/ip-api.js +22 -0
- package/src/providers/ipapi.js +22 -0
- package/src/providers/ipinfo.js +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vineeth Krishnan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# ipwhoami
|
|
2
|
+
|
|
3
|
+
IP geolocation lookup from your terminal. Query multiple providers and compare results side-by-side.
|
|
4
|
+
|
|
5
|
+
Works on **macOS**, **Linux**, and **Windows**. Zero dependencies.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Look up geolocation for any IP address (or auto-detect yours)
|
|
10
|
+
- Choose from 3 providers: [ipinfo.io](https://ipinfo.io), [ipapi.co](https://ipapi.co), [ip-api.com](http://ip-api.com)
|
|
11
|
+
- Compare results across all providers at once
|
|
12
|
+
- Raw JSON output for scripting and piping
|
|
13
|
+
- No API keys required
|
|
14
|
+
- Zero dependencies
|
|
15
|
+
- Cross-platform: Node.js, Bash, and PowerShell
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
### npm (recommended)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g ipwhoami
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or run directly without installing:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx ipwhoami 8.8.8.8
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Homebrew (macOS / Linux)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
brew tap vineethkrishnan/ipwhoami
|
|
35
|
+
brew install ipwhoami
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Scoop (Windows)
|
|
39
|
+
|
|
40
|
+
```powershell
|
|
41
|
+
scoop bucket add ipwhoami https://github.com/vineethkrishnan/scoop-ipwhoami
|
|
42
|
+
scoop install ipwhoami
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Docker
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
docker run --rm vineethkrishnan/ipwhoami 8.8.8.8
|
|
49
|
+
docker run --rm vineethkrishnan/ipwhoami -c 1.1.1.1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Standalone Bash script (macOS / Linux)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
curl -fsSL https://raw.githubusercontent.com/vineethkrishnan/ipwhoami/main/install.sh | bash
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Standalone PowerShell script (Windows)
|
|
59
|
+
|
|
60
|
+
```powershell
|
|
61
|
+
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/vineethkrishnan/ipwhoami/main/scripts/ipwhoami.ps1" -OutFile "$HOME\ipwhoami.ps1"
|
|
62
|
+
Add-Content $PROFILE 'Set-Alias ipwhoami "$HOME\ipwhoami.ps1"'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
ipwhoami [options] [ip]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Options
|
|
72
|
+
|
|
73
|
+
| Flag | Description |
|
|
74
|
+
|------|-------------|
|
|
75
|
+
| `-p, --provider NAME` | Use a specific provider: `ipinfo`, `ipapi`, `ip-api` (default: `ipinfo`) |
|
|
76
|
+
| `-c, --compare` | Compare results from all providers |
|
|
77
|
+
| `-r, --raw` | Output raw JSON |
|
|
78
|
+
| `-h, --help` | Show help |
|
|
79
|
+
| `-v, --version` | Show version |
|
|
80
|
+
|
|
81
|
+
### Examples
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Look up your own public IP
|
|
85
|
+
ipwhoami
|
|
86
|
+
|
|
87
|
+
# Look up a specific IP
|
|
88
|
+
ipwhoami 8.8.8.8
|
|
89
|
+
|
|
90
|
+
# Compare across all providers
|
|
91
|
+
ipwhoami -c 1.1.1.1
|
|
92
|
+
|
|
93
|
+
# Use a specific provider
|
|
94
|
+
ipwhoami -p ipapi 8.8.8.8
|
|
95
|
+
|
|
96
|
+
# Get raw JSON (great for piping)
|
|
97
|
+
ipwhoami -r 8.8.8.8 | jq .city
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Sample Output
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
$ ipwhoami -c 8.8.8.8
|
|
104
|
+
Comparing geolocation for: 8.8.8.8
|
|
105
|
+
────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
[ipinfo]
|
|
108
|
+
IP: 8.8.8.8
|
|
109
|
+
City: Mountain View
|
|
110
|
+
Region: California
|
|
111
|
+
Country: US
|
|
112
|
+
Org: AS15169 Google LLC
|
|
113
|
+
Location: 37.4056,-122.0775
|
|
114
|
+
Timezone: America/Los_Angeles
|
|
115
|
+
|
|
116
|
+
[ipapi]
|
|
117
|
+
IP: 8.8.8.8
|
|
118
|
+
City: Mountain View
|
|
119
|
+
Region: California
|
|
120
|
+
Country: United States
|
|
121
|
+
Org: Google LLC
|
|
122
|
+
Location: 37.4223, -122.085
|
|
123
|
+
Timezone: America/Los_Angeles
|
|
124
|
+
|
|
125
|
+
[ip-api]
|
|
126
|
+
IP: 8.8.8.8
|
|
127
|
+
City: Mountain View
|
|
128
|
+
Region: California
|
|
129
|
+
Country: United States
|
|
130
|
+
ISP: Google LLC
|
|
131
|
+
Location: 37.4056, -122.0775
|
|
132
|
+
Timezone: America/Los_Angeles
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Providers
|
|
136
|
+
|
|
137
|
+
| Provider | HTTPS | Rate Limit | Notes |
|
|
138
|
+
|----------|-------|------------|-------|
|
|
139
|
+
| [ipinfo.io](https://ipinfo.io) | Yes | 50k/month free | Default provider |
|
|
140
|
+
| [ipapi.co](https://ipapi.co) | Yes | 1k/day free | Good detail |
|
|
141
|
+
| [ip-api.com](http://ip-api.com) | No (HTTP) | 45/min free | Includes ISP info |
|
|
142
|
+
|
|
143
|
+
## Adding a Provider
|
|
144
|
+
|
|
145
|
+
Providers live in `src/providers/`. To add a new one:
|
|
146
|
+
|
|
147
|
+
1. Create a new file in `src/providers/` (see existing ones for the pattern)
|
|
148
|
+
2. Implement the `lookup(ip)` method returning a normalized result object
|
|
149
|
+
3. Register it in `src/providers/index.js`
|
|
150
|
+
|
|
151
|
+
## Project Structure
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
ipwhoami/
|
|
155
|
+
├── bin/
|
|
156
|
+
│ └── ipwhoami.js # CLI entry point
|
|
157
|
+
├── src/
|
|
158
|
+
│ ├── cli.js # Argument parsing & command routing
|
|
159
|
+
│ ├── colors.js # Terminal color helpers
|
|
160
|
+
│ ├── config.js # Constants & defaults
|
|
161
|
+
│ ├── formatter.js # Output formatting
|
|
162
|
+
│ ├── ip.js # IP validation & public IP resolution
|
|
163
|
+
│ └── providers/
|
|
164
|
+
│ ├── base.js # Shared HTTP fetch logic
|
|
165
|
+
│ ├── index.js # Provider registry
|
|
166
|
+
│ ├── ipinfo.js # ipinfo.io
|
|
167
|
+
│ ├── ipapi.js # ipapi.co
|
|
168
|
+
│ └── ip-api.js # ip-api.com
|
|
169
|
+
├── scripts/
|
|
170
|
+
│ ├── ipwhoami.sh # Standalone Bash version
|
|
171
|
+
│ └── ipwhoami.ps1 # Standalone PowerShell version
|
|
172
|
+
├── test/ # Unit & integration tests
|
|
173
|
+
├── docs/ # Starlight documentation site
|
|
174
|
+
├── Dockerfile
|
|
175
|
+
├── .github/workflows/
|
|
176
|
+
│ ├── ci.yml # Lint & test on PRs
|
|
177
|
+
│ ├── release.yml # Release Please + npm + Docker + Homebrew + Scoop
|
|
178
|
+
│ └── deploy-docs.yml # Cloudflare Pages docs deploy
|
|
179
|
+
├── package.json
|
|
180
|
+
├── CHANGELOG.md
|
|
181
|
+
├── LICENSE
|
|
182
|
+
└── README.md
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Release
|
|
186
|
+
|
|
187
|
+
Releases are fully automated via [Release Please](https://github.com/googleapis/release-please):
|
|
188
|
+
|
|
189
|
+
1. Push commits to `main` using [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, etc.)
|
|
190
|
+
2. Release Please auto-creates a **Release PR** that bumps `package.json`, `src/config.js`, and `CHANGELOG.md`
|
|
191
|
+
3. Merge the PR to trigger the full release pipeline:
|
|
192
|
+
- Creates a GitHub Release with auto-generated notes
|
|
193
|
+
- Publishes to npm
|
|
194
|
+
- Builds and pushes Docker images (Docker Hub + GHCR)
|
|
195
|
+
- Updates the Homebrew formula
|
|
196
|
+
- Updates the Scoop manifest
|
|
197
|
+
|
|
198
|
+
## Requirements
|
|
199
|
+
|
|
200
|
+
- **npm / Homebrew / Scoop**: Node.js >= 18
|
|
201
|
+
- **Docker**: No requirements (Node.js included in image)
|
|
202
|
+
- **Bash script**: `curl` + `jq`
|
|
203
|
+
- **PowerShell script**: PowerShell 5.1+ or 7+
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
[MIT](LICENSE)
|
package/bin/ipwhoami.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ipwhoami",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "IP geolocation lookup from your terminal. Query multiple providers, compare results side-by-side.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ipwhoami": "./bin/ipwhoami.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node bin/ipwhoami.js",
|
|
17
|
+
"lint": "node --check src/**/*.js bin/**/*.js",
|
|
18
|
+
"test": "node --test test/config.test.js test/ip.test.js test/colors.test.js test/formatter.test.js test/providers.test.js test/cli.test.js",
|
|
19
|
+
"test:unit": "node --test test/config.test.js test/ip.test.js test/colors.test.js test/formatter.test.js test/providers.test.js",
|
|
20
|
+
"test:integration": "node --test test/cli.test.js"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"ip",
|
|
24
|
+
"geolocation",
|
|
25
|
+
"ipinfo",
|
|
26
|
+
"ip-lookup",
|
|
27
|
+
"cli",
|
|
28
|
+
"terminal",
|
|
29
|
+
"network",
|
|
30
|
+
"geo",
|
|
31
|
+
"ipapi",
|
|
32
|
+
"ip-api",
|
|
33
|
+
"whois",
|
|
34
|
+
"location"
|
|
35
|
+
],
|
|
36
|
+
"author": "Vineeth Krishnan",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/vineethkrishnan/ipwhoami.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/vineethkrishnan/ipwhoami#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/vineethkrishnan/ipwhoami/issues"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { VERSION, DEFAULT_PROVIDER, PROVIDER_NAMES } from './config.js';
|
|
2
|
+
import { bold, dim, green, red } from './colors.js';
|
|
3
|
+
import { validateIP, getPublicIP } from './ip.js';
|
|
4
|
+
import { getProvider, getAllProviders } from './providers/index.js';
|
|
5
|
+
import { formatResult, formatRaw, formatCompareHeader } from './formatter.js';
|
|
6
|
+
|
|
7
|
+
function printUsage() {
|
|
8
|
+
console.log(`${bold('ipwhoami')} - IP geolocation lookup from your terminal
|
|
9
|
+
|
|
10
|
+
${bold('USAGE')}
|
|
11
|
+
ipwhoami [options] [ip]
|
|
12
|
+
|
|
13
|
+
${bold('ARGUMENTS')}
|
|
14
|
+
ip IP address to look up (defaults to your public IP)
|
|
15
|
+
|
|
16
|
+
${bold('OPTIONS')}
|
|
17
|
+
-p, --provider NAME Use a specific provider: ${PROVIDER_NAMES.join(', ')} (default: ${DEFAULT_PROVIDER})
|
|
18
|
+
-c, --compare Compare results from all providers
|
|
19
|
+
-r, --raw Output raw JSON (no formatting)
|
|
20
|
+
-h, --help Show this help message
|
|
21
|
+
-v, --version Show version
|
|
22
|
+
|
|
23
|
+
${bold('EXAMPLES')}
|
|
24
|
+
ipwhoami Look up your own public IP
|
|
25
|
+
ipwhoami 8.8.8.8 Look up a specific IP
|
|
26
|
+
ipwhoami -c Compare your IP across all providers
|
|
27
|
+
ipwhoami -c 1.1.1.1 Compare a specific IP across all providers
|
|
28
|
+
ipwhoami -p ipapi 8.8.8.8 Use a specific provider
|
|
29
|
+
ipwhoami -r 8.8.8.8 Raw JSON output (pipe-friendly)`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const opts = {
|
|
34
|
+
ip: null,
|
|
35
|
+
provider: DEFAULT_PROVIDER,
|
|
36
|
+
compare: false,
|
|
37
|
+
raw: false,
|
|
38
|
+
help: false,
|
|
39
|
+
version: false,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < argv.length; i++) {
|
|
43
|
+
const arg = argv[i];
|
|
44
|
+
switch (arg) {
|
|
45
|
+
case '-h':
|
|
46
|
+
case '--help':
|
|
47
|
+
opts.help = true;
|
|
48
|
+
break;
|
|
49
|
+
case '-v':
|
|
50
|
+
case '--version':
|
|
51
|
+
opts.version = true;
|
|
52
|
+
break;
|
|
53
|
+
case '-c':
|
|
54
|
+
case '--compare':
|
|
55
|
+
opts.compare = true;
|
|
56
|
+
break;
|
|
57
|
+
case '-r':
|
|
58
|
+
case '--raw':
|
|
59
|
+
opts.raw = true;
|
|
60
|
+
break;
|
|
61
|
+
case '-p':
|
|
62
|
+
case '--provider':
|
|
63
|
+
i++;
|
|
64
|
+
opts.provider = argv[i];
|
|
65
|
+
if (!opts.provider) {
|
|
66
|
+
die('--provider requires a value.');
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
if (arg.startsWith('-')) {
|
|
71
|
+
die(`unknown option: ${arg}. Use --help for usage.`);
|
|
72
|
+
}
|
|
73
|
+
opts.ip = arg;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return opts;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function die(message) {
|
|
81
|
+
console.error(`${red('error:')} ${message}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function lookup(ip, providerName, raw) {
|
|
86
|
+
const provider = getProvider(providerName);
|
|
87
|
+
const result = await provider.lookup(ip);
|
|
88
|
+
|
|
89
|
+
if (raw) {
|
|
90
|
+
console.log(formatRaw(result));
|
|
91
|
+
} else {
|
|
92
|
+
console.log(formatResult(providerName, result));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function compare(ip, raw) {
|
|
97
|
+
console.log(formatCompareHeader(ip));
|
|
98
|
+
console.log();
|
|
99
|
+
|
|
100
|
+
for (const { name, provider } of getAllProviders()) {
|
|
101
|
+
const result = await provider.lookup(ip);
|
|
102
|
+
if (raw) {
|
|
103
|
+
console.log(`// ${name}`);
|
|
104
|
+
console.log(formatRaw(result));
|
|
105
|
+
} else {
|
|
106
|
+
console.log(formatResult(name, result));
|
|
107
|
+
}
|
|
108
|
+
console.log();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(dim('Done.'));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function run(argv) {
|
|
115
|
+
const opts = parseArgs(argv);
|
|
116
|
+
|
|
117
|
+
if (opts.help) {
|
|
118
|
+
printUsage();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (opts.version) {
|
|
123
|
+
console.log(`ipwhoami ${VERSION}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let ip = opts.ip;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
if (!ip) {
|
|
131
|
+
console.log(dim('Fetching your public IP...'));
|
|
132
|
+
ip = await getPublicIP();
|
|
133
|
+
console.log(`${bold('Your IP:')} ${green(ip)}\n`);
|
|
134
|
+
} else {
|
|
135
|
+
validateIP(ip);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (opts.compare) {
|
|
139
|
+
await compare(ip, opts.raw);
|
|
140
|
+
} else {
|
|
141
|
+
await lookup(ip, opts.provider, opts.raw);
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
die(err.message);
|
|
145
|
+
}
|
|
146
|
+
}
|
package/src/colors.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const isTTY = process.stdout.isTTY;
|
|
2
|
+
|
|
3
|
+
function wrap(code) {
|
|
4
|
+
return isTTY ? (s) => `\x1b[${code}m${s}\x1b[0m` : (s) => s;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const bold = wrap('1');
|
|
8
|
+
export const dim = wrap('2');
|
|
9
|
+
export const cyan = wrap('36');
|
|
10
|
+
export const green = wrap('32');
|
|
11
|
+
export const red = wrap('31');
|
package/src/config.js
ADDED
package/src/formatter.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { bold, cyan, dim } from './colors.js';
|
|
2
|
+
|
|
3
|
+
const FIELDS = [
|
|
4
|
+
['IP', 'ip'],
|
|
5
|
+
['City', 'city'],
|
|
6
|
+
['Region', 'region'],
|
|
7
|
+
['Country', 'country'],
|
|
8
|
+
['Org', 'org'],
|
|
9
|
+
['Location', 'location'],
|
|
10
|
+
['Timezone', 'timezone'],
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export function formatResult(providerName, result) {
|
|
14
|
+
const lines = [cyan(bold(`[${providerName}]`))];
|
|
15
|
+
|
|
16
|
+
for (const [label, key] of FIELDS) {
|
|
17
|
+
const value = result[key] || 'n/a';
|
|
18
|
+
lines.push(` ${label.padEnd(10)} ${value}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return lines.join('\n');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatRaw(result) {
|
|
25
|
+
return JSON.stringify(result, null, 2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function formatCompareHeader(ip) {
|
|
29
|
+
return [
|
|
30
|
+
`${bold('Comparing geolocation for:')} ${ip}`,
|
|
31
|
+
dim('\u2500'.repeat(40)),
|
|
32
|
+
].join('\n');
|
|
33
|
+
}
|
package/src/ip.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SELF_IP_URL } from './config.js';
|
|
2
|
+
|
|
3
|
+
const IPV4_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
4
|
+
|
|
5
|
+
export function validateIP(ip) {
|
|
6
|
+
if (IPV4_REGEX.test(ip) || ip.includes(':')) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
throw new Error(`invalid IP address: ${ip}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function getPublicIP() {
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(SELF_IP_URL, { signal: controller.signal });
|
|
18
|
+
const text = (await res.text()).trim();
|
|
19
|
+
if (!text) {
|
|
20
|
+
throw new Error('got empty response when fetching public IP.');
|
|
21
|
+
}
|
|
22
|
+
return text;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (err.name === 'AbortError') {
|
|
25
|
+
throw new Error('timed out fetching your public IP. Check your internet connection.');
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`failed to fetch your public IP: ${err.message}`);
|
|
28
|
+
} finally {
|
|
29
|
+
clearTimeout(timeout);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export async function fetchJSON(url) {
|
|
2
|
+
const controller = new AbortController();
|
|
3
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
7
|
+
if (!res.ok) {
|
|
8
|
+
throw new Error(`HTTP ${res.status}`);
|
|
9
|
+
}
|
|
10
|
+
return await res.json();
|
|
11
|
+
} catch (err) {
|
|
12
|
+
if (err.name === 'AbortError') {
|
|
13
|
+
throw new Error(`request to ${url} timed out.`);
|
|
14
|
+
}
|
|
15
|
+
throw new Error(`request to ${url} failed: ${err.message}`);
|
|
16
|
+
} finally {
|
|
17
|
+
clearTimeout(timeout);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ipinfo } from './ipinfo.js';
|
|
2
|
+
import { ipapi } from './ipapi.js';
|
|
3
|
+
import { ipApiCom } from './ip-api.js';
|
|
4
|
+
|
|
5
|
+
const providers = new Map([
|
|
6
|
+
['ipinfo', ipinfo],
|
|
7
|
+
['ipapi', ipapi],
|
|
8
|
+
['ip-api', ipApiCom],
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export function getProvider(name) {
|
|
12
|
+
const provider = providers.get(name);
|
|
13
|
+
if (!provider) {
|
|
14
|
+
throw new Error(`unknown provider: ${name}. Available: ${[...providers.keys()].join(', ')}`);
|
|
15
|
+
}
|
|
16
|
+
return provider;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getAllProviders() {
|
|
20
|
+
return [...providers.entries()].map(([name, provider]) => ({ name, provider }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fetchJSON } from './base.js';
|
|
2
|
+
|
|
3
|
+
export const ipApiCom = {
|
|
4
|
+
name: 'ip-api',
|
|
5
|
+
displayName: 'ip-api.com',
|
|
6
|
+
url: (ip) => `http://ip-api.com/json/${ip}`,
|
|
7
|
+
|
|
8
|
+
async lookup(ip) {
|
|
9
|
+
const data = await fetchJSON(this.url(ip));
|
|
10
|
+
return {
|
|
11
|
+
ip: data.query,
|
|
12
|
+
city: data.city,
|
|
13
|
+
region: data.regionName,
|
|
14
|
+
country: data.country,
|
|
15
|
+
org: data.isp,
|
|
16
|
+
location: data.lat && data.lon
|
|
17
|
+
? `${data.lat}, ${data.lon}`
|
|
18
|
+
: undefined,
|
|
19
|
+
timezone: data.timezone,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fetchJSON } from './base.js';
|
|
2
|
+
|
|
3
|
+
export const ipapi = {
|
|
4
|
+
name: 'ipapi',
|
|
5
|
+
displayName: 'ipapi.co',
|
|
6
|
+
url: (ip) => `https://ipapi.co/${ip}/json`,
|
|
7
|
+
|
|
8
|
+
async lookup(ip) {
|
|
9
|
+
const data = await fetchJSON(this.url(ip));
|
|
10
|
+
return {
|
|
11
|
+
ip: data.ip,
|
|
12
|
+
city: data.city,
|
|
13
|
+
region: data.region,
|
|
14
|
+
country: data.country_name,
|
|
15
|
+
org: data.org,
|
|
16
|
+
location: data.latitude && data.longitude
|
|
17
|
+
? `${data.latitude}, ${data.longitude}`
|
|
18
|
+
: undefined,
|
|
19
|
+
timezone: data.timezone,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { fetchJSON } from './base.js';
|
|
2
|
+
|
|
3
|
+
export const ipinfo = {
|
|
4
|
+
name: 'ipinfo',
|
|
5
|
+
displayName: 'ipinfo.io',
|
|
6
|
+
url: (ip) => `https://ipinfo.io/${ip}/json`,
|
|
7
|
+
|
|
8
|
+
async lookup(ip) {
|
|
9
|
+
const data = await fetchJSON(this.url(ip));
|
|
10
|
+
return {
|
|
11
|
+
ip: data.ip,
|
|
12
|
+
city: data.city,
|
|
13
|
+
region: data.region,
|
|
14
|
+
country: data.country,
|
|
15
|
+
org: data.org,
|
|
16
|
+
location: data.loc,
|
|
17
|
+
timezone: data.timezone,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
};
|