proxychecker-dev 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 +113 -0
- package/bin/proxycheck.js +297 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Flash AI Solutions (Jaylon Malone)
|
|
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,113 @@
|
|
|
1
|
+
# proxycheck
|
|
2
|
+
|
|
3
|
+
Fast **proxy checker** for the command line. Test HTTP, HTTPS, SOCKS4, and SOCKS5 proxies for liveness, latency, anonymity, and datacenter-vs-residential type. No signup, no ads, no sketchy binary.
|
|
4
|
+
|
|
5
|
+
Powered by [proxychecker.dev](https://proxychecker.dev).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx proxychecker-dev proxies.txt
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
ALIVE 45.12.30.9:8080 http datacenter 142ms US Cloudflare
|
|
13
|
+
ALIVE 98.162.25.7:31654 socks5 residential 380ms DE Deutsche Telekom
|
|
14
|
+
DEAD 9.9.9.9:3128 http - - - timeout
|
|
15
|
+
|
|
16
|
+
2 alive · 1 dead · avg 261ms · 1 dc · 1 res
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Why
|
|
20
|
+
|
|
21
|
+
If you buy proxies for scraping, half the list is usually dead, slow, or a datacenter IP being sold as "residential." Checking them by hand or hacking together a `curl` loop is a waste of a morning. This does it in one command, tells you which are actually alive, how fast, and what type they really are.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
Run it with no install:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx proxychecker-dev proxies.txt
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or install globally for the short `proxycheck` command:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g proxychecker-dev
|
|
35
|
+
proxycheck proxies.txt
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires Node.js 18+.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# from a file (one proxy per line, # comments ignored)
|
|
44
|
+
proxycheck proxies.txt
|
|
45
|
+
|
|
46
|
+
# inline
|
|
47
|
+
proxycheck 1.2.3.4:8080 socks5://5.6.7.8:1080
|
|
48
|
+
|
|
49
|
+
# from stdin
|
|
50
|
+
cat proxies.txt | proxycheck
|
|
51
|
+
|
|
52
|
+
# only the live ones, as JSON, piped to jq
|
|
53
|
+
proxycheck proxies.txt --alive --json | jq -r '.results[].input'
|
|
54
|
+
|
|
55
|
+
# export a CSV
|
|
56
|
+
proxycheck proxies.txt --csv > results.csv
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Accepted formats
|
|
60
|
+
|
|
61
|
+
One per line. Lines starting with `#` are ignored.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
ip:port
|
|
65
|
+
ip:port:user:pass
|
|
66
|
+
user:pass@ip:port
|
|
67
|
+
socks5://ip:port
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Protocols: `http`, `https`, `socks4`, `socks5`.
|
|
71
|
+
|
|
72
|
+
### Options
|
|
73
|
+
|
|
74
|
+
| Flag | Description |
|
|
75
|
+
|------|-------------|
|
|
76
|
+
| `-k, --key <pck_...>` | API key for 5,000 proxies/call + auto-chunking (or set `PROXYCHECK_KEY`) |
|
|
77
|
+
| `-t, --timeout <ms>` | Per-proxy timeout, 3000-30000 (default 10000) |
|
|
78
|
+
| `-a, --alive` | Only output proxies that are alive |
|
|
79
|
+
| `--json` | Output raw JSON (pipe to `jq`) |
|
|
80
|
+
| `--csv` | Output CSV |
|
|
81
|
+
| `--fail-if-none` | Exit non-zero if zero proxies are alive (useful in CI) |
|
|
82
|
+
| `-h, --help` | Show help |
|
|
83
|
+
| `-v, --version` | Show version |
|
|
84
|
+
|
|
85
|
+
## Free vs. keyed
|
|
86
|
+
|
|
87
|
+
The free tier checks **50 proxies per call** with no key and no signup. That covers casual use forever.
|
|
88
|
+
|
|
89
|
+
Got a big list? A key unlocks **5,000 proxies per call**, and the CLI automatically splits larger jobs into chunks and merges the results, so `proxycheck huge-list.txt --key pck_...` just works.
|
|
90
|
+
|
|
91
|
+
Keys are a one-time **$5** (5,000 proxies) or **$15/mo** for ongoing bulk + API access: [proxychecker.dev/pricing](https://proxychecker.dev/pricing). Set it once:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
export PROXYCHECK_KEY=pck_live_xxxxx
|
|
95
|
+
proxycheck huge-list.txt
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## What you get per proxy
|
|
99
|
+
|
|
100
|
+
- **status** — alive or dead
|
|
101
|
+
- **protocol** — http / https / socks4 / socks5
|
|
102
|
+
- **type** — datacenter, residential, or mobile
|
|
103
|
+
- **latency** — round-trip in ms
|
|
104
|
+
- **country / city / ISP / ASN** — geo + network
|
|
105
|
+
- **suspected_fake_residential** — flags datacenter IPs being sold as residential
|
|
106
|
+
|
|
107
|
+
## API
|
|
108
|
+
|
|
109
|
+
The CLI is a thin wrapper over the [proxychecker.dev API](https://proxychecker.dev/docs/api). POST a JSON array of proxies to `/api/check` with `Authorization: Bearer pck_...` and get JSON or CSV back. Use whichever you like.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT © Flash AI Solutions
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// proxycheck - CLI for proxychecker.dev
|
|
3
|
+
// Zero runtime dependencies. Node >=18 (uses built-in fetch + util.parseArgs).
|
|
4
|
+
//
|
|
5
|
+
// Free tier: 50 proxies/call, no key needed.
|
|
6
|
+
// Paid tier: pass --key pck_... (or PROXYCHECK_KEY env) for 5,000/call + chunking.
|
|
7
|
+
|
|
8
|
+
import { parseArgs } from "node:util";
|
|
9
|
+
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
10
|
+
|
|
11
|
+
const API_BASE = process.env.PROXYCHECK_API || "https://proxychecker.dev";
|
|
12
|
+
const FREE_CAP = 50; // server truncates free requests above this
|
|
13
|
+
const KEYED_CHUNK = 5000; // pro tier cap; we split larger jobs into 5k calls
|
|
14
|
+
|
|
15
|
+
// ---------- tiny ANSI color helpers (respect NO_COLOR + non-TTY pipes) ----------
|
|
16
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
17
|
+
const paint = (code) => (s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : String(s));
|
|
18
|
+
const green = paint(32), red = paint(31), dim = paint(90), bold = paint(1),
|
|
19
|
+
cyan = paint(36), yellow = paint(33);
|
|
20
|
+
|
|
21
|
+
const HELP = `
|
|
22
|
+
${bold("proxycheck")} - test proxies for liveness, speed, and type ${dim("proxychecker.dev")}
|
|
23
|
+
|
|
24
|
+
${bold("USAGE")}
|
|
25
|
+
proxycheck <file> check proxies from a file (one per line)
|
|
26
|
+
proxycheck <proxy> [proxy...] check proxies passed as arguments
|
|
27
|
+
cat proxies.txt | proxycheck check proxies from stdin
|
|
28
|
+
|
|
29
|
+
${bold("OPTIONS")}
|
|
30
|
+
-k, --key <pck_...> API key for 5,000/call + auto-chunking (or PROXYCHECK_KEY)
|
|
31
|
+
-t, --timeout <ms> per-proxy timeout, 3000-30000 (default 10000)
|
|
32
|
+
-a, --alive only output proxies that are alive
|
|
33
|
+
--json output raw JSON (good for piping to jq)
|
|
34
|
+
--csv output CSV (input,status,protocol,type,latency,country,isp)
|
|
35
|
+
--fail-if-none exit non-zero if zero proxies are alive (for CI)
|
|
36
|
+
-h, --help show this help
|
|
37
|
+
-v, --version show version
|
|
38
|
+
|
|
39
|
+
${bold("FORMATS ACCEPTED")} (one per line, # comments ignored)
|
|
40
|
+
ip:port socks5://ip:port
|
|
41
|
+
ip:port:user:pass user:pass@ip:port
|
|
42
|
+
|
|
43
|
+
${bold("EXAMPLES")}
|
|
44
|
+
proxycheck proxies.txt
|
|
45
|
+
proxycheck 1.2.3.4:8080 socks5://5.6.7.8:1080 --alive
|
|
46
|
+
proxycheck big.txt --key pck_live_xxx --csv > results.csv
|
|
47
|
+
cat proxies.txt | proxycheck --alive --json | jq '.results[].input'
|
|
48
|
+
|
|
49
|
+
Free checks 50 proxies/call. Bulk + API access from $5: ${cyan("https://proxychecker.dev/pricing")}
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
function fail(msg, code = 1) {
|
|
53
|
+
process.stderr.write(red("error: ") + msg + "\n");
|
|
54
|
+
process.exit(code);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------- parse args ----------
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = parseArgs({
|
|
61
|
+
allowPositionals: true,
|
|
62
|
+
options: {
|
|
63
|
+
key: { type: "string", short: "k" },
|
|
64
|
+
timeout: { type: "string", short: "t" },
|
|
65
|
+
alive: { type: "boolean", short: "a" },
|
|
66
|
+
json: { type: "boolean" },
|
|
67
|
+
csv: { type: "boolean" },
|
|
68
|
+
"fail-if-none": { type: "boolean" },
|
|
69
|
+
help: { type: "boolean", short: "h" },
|
|
70
|
+
version: { type: "boolean", short: "v" },
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
} catch (e) {
|
|
74
|
+
fail(e.message + "\nRun 'proxycheck --help' for usage.");
|
|
75
|
+
}
|
|
76
|
+
const { values: opts, positionals } = parsed;
|
|
77
|
+
|
|
78
|
+
if (opts.help) { process.stdout.write(HELP); process.exit(0); }
|
|
79
|
+
if (opts.version) {
|
|
80
|
+
// read our own version without importing json (keeps it ESM-simple)
|
|
81
|
+
try {
|
|
82
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
83
|
+
process.stdout.write(pkg.version + "\n");
|
|
84
|
+
} catch { process.stdout.write("unknown\n"); }
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const key = opts.key || process.env.PROXYCHECK_KEY || null;
|
|
89
|
+
const timeout = opts.timeout ? parseInt(opts.timeout, 10) : undefined;
|
|
90
|
+
if (opts.timeout && (!Number.isFinite(timeout) || timeout < 1000)) {
|
|
91
|
+
fail("--timeout must be a number of milliseconds (>= 1000)");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------- gather proxy list from file / args / stdin ----------
|
|
95
|
+
function looksLikeProxy(s) {
|
|
96
|
+
return /[:@]/.test(s); // has a port or credentials separator
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function readStdin() {
|
|
100
|
+
const chunks = [];
|
|
101
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
102
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseList(text) {
|
|
106
|
+
return text
|
|
107
|
+
.split(/\r?\n/)
|
|
108
|
+
.map((l) => l.trim())
|
|
109
|
+
.filter((l) => l && !l.startsWith("#"));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isFile(p) {
|
|
113
|
+
try { return existsSync(p) && statSync(p).isFile(); }
|
|
114
|
+
catch { return false; }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function gatherProxies() {
|
|
118
|
+
if (positionals.length) {
|
|
119
|
+
// A real file on disk wins (a Windows path like C:\list.txt also contains
|
|
120
|
+
// ':' so we can't rely on looksLikeProxy alone -- check the filesystem).
|
|
121
|
+
if (isFile(positionals[0])) {
|
|
122
|
+
return parseList(readFileSync(positionals[0], "utf8"));
|
|
123
|
+
}
|
|
124
|
+
// Otherwise treat the positionals as inline proxies.
|
|
125
|
+
if (positionals.every(looksLikeProxy)) {
|
|
126
|
+
return positionals;
|
|
127
|
+
}
|
|
128
|
+
fail(`'${positionals[0]}' is not a readable file or a valid proxy.\n` +
|
|
129
|
+
`Run 'proxycheck --help' for usage.`);
|
|
130
|
+
}
|
|
131
|
+
// Piped stdin (cat proxies.txt | proxycheck)
|
|
132
|
+
if (!process.stdin.isTTY) {
|
|
133
|
+
return parseList(await readStdin());
|
|
134
|
+
}
|
|
135
|
+
// Nothing given
|
|
136
|
+
process.stdout.write(HELP);
|
|
137
|
+
process.exit(0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------- API call (one chunk), with a single 429 backoff/retry ----------
|
|
141
|
+
// Throws on failure (caught by main). We avoid process.exit() here because a
|
|
142
|
+
// hard exit while an undici keep-alive socket is still closing trips a libuv
|
|
143
|
+
// assertion on Windows. Setting exitCode + draining the loop is the safe path.
|
|
144
|
+
async function checkChunk(proxies, attempt = 0) {
|
|
145
|
+
const headers = { "Content-Type": "application/json" };
|
|
146
|
+
if (key) headers["Authorization"] = "Bearer " + key;
|
|
147
|
+
let res;
|
|
148
|
+
try {
|
|
149
|
+
res = await fetch(API_BASE + "/api/check", {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers,
|
|
152
|
+
body: JSON.stringify(timeout ? { proxies, timeout } : { proxies }),
|
|
153
|
+
});
|
|
154
|
+
} catch (e) {
|
|
155
|
+
throw new Error(`network error reaching ${API_BASE}: ${e.message}`);
|
|
156
|
+
}
|
|
157
|
+
if (res.status === 429 && attempt < 3) {
|
|
158
|
+
process.stderr.write(dim(`rate limited, waiting 6s...\n`));
|
|
159
|
+
await new Promise((r) => setTimeout(r, 6000));
|
|
160
|
+
return checkChunk(proxies, attempt + 1);
|
|
161
|
+
}
|
|
162
|
+
if (!res.ok) {
|
|
163
|
+
const body = await res.text().catch(() => "");
|
|
164
|
+
let msg = `API returned ${res.status}`;
|
|
165
|
+
try { const j = JSON.parse(body); if (j.error) msg = j.error; } catch {}
|
|
166
|
+
throw new Error(msg);
|
|
167
|
+
}
|
|
168
|
+
return res.json();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---------- rendering ----------
|
|
172
|
+
function statusCell(r) {
|
|
173
|
+
if (r.status === "alive") return green("ALIVE");
|
|
174
|
+
return red("DEAD ");
|
|
175
|
+
}
|
|
176
|
+
function pad(s, n) {
|
|
177
|
+
s = String(s ?? "");
|
|
178
|
+
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function printTable(results) {
|
|
182
|
+
const rows = opts.alive ? results.filter((r) => r.status === "alive") : results;
|
|
183
|
+
if (!rows.length) {
|
|
184
|
+
process.stdout.write(dim("no proxies to show\n"));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const wInput = Math.min(Math.max(...rows.map((r) => (r.input || "").length), 5), 32);
|
|
188
|
+
for (const r of rows) {
|
|
189
|
+
const type = r.status === "alive" ? (r.type || "unknown") : "-";
|
|
190
|
+
const lat = r.latency != null ? r.latency + "ms" : "-";
|
|
191
|
+
const geo = [r.country, r.isp].filter(Boolean).join(" ");
|
|
192
|
+
const fake = r.suspected_fake_residential ? yellow(" fake-res?") : "";
|
|
193
|
+
process.stdout.write(
|
|
194
|
+
` ${statusCell(r)} ${pad(r.input, wInput)} ${dim(pad(r.protocol || "-", 6))} ` +
|
|
195
|
+
`${pad(type, 11)} ${pad(lat, 7)} ${dim(geo)}${fake}\n`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function printSummary(summary, truncated) {
|
|
201
|
+
const s = summary;
|
|
202
|
+
const parts = [
|
|
203
|
+
green(s.alive + " alive"),
|
|
204
|
+
red(s.dead + " dead"),
|
|
205
|
+
dim(`avg ${s.avg_latency_ms}ms`),
|
|
206
|
+
dim(`${s.datacenter} dc`),
|
|
207
|
+
dim(`${s.residential} res`),
|
|
208
|
+
];
|
|
209
|
+
if (s.mobile) parts.push(dim(`${s.mobile} mobile`));
|
|
210
|
+
if (s.suspected_fake_residential) parts.push(yellow(`${s.suspected_fake_residential} fake-res`));
|
|
211
|
+
process.stdout.write("\n " + parts.join(dim(" · ")) + "\n");
|
|
212
|
+
if (truncated) {
|
|
213
|
+
process.stdout.write(
|
|
214
|
+
"\n " + yellow(`checked ${truncated.checked} of ${truncated.sent} (free tier cap).`) +
|
|
215
|
+
"\n " + `Unlock ${bold("5,000/call")} with a $5 one-time key -> ${cyan("https://proxychecker.dev/pricing")}\n`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function toCsv(results) {
|
|
221
|
+
const cols = ["input", "status", "protocol", "type", "latency", "country", "city", "isp", "asn", "suspected_fake_residential", "reason"];
|
|
222
|
+
const esc = (v) => {
|
|
223
|
+
if (v == null) return "";
|
|
224
|
+
const str = String(v);
|
|
225
|
+
return /[",\n]/.test(str) ? '"' + str.replace(/"/g, '""') + '"' : str;
|
|
226
|
+
};
|
|
227
|
+
const lines = [cols.join(",")];
|
|
228
|
+
for (const r of results) lines.push(cols.map((c) => esc(r[c])).join(","));
|
|
229
|
+
return lines.join("\n");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------- main ----------
|
|
233
|
+
(async () => {
|
|
234
|
+
const proxies = await gatherProxies();
|
|
235
|
+
if (!proxies.length) fail("no valid proxies found in input");
|
|
236
|
+
try {
|
|
237
|
+
|
|
238
|
+
let merged = [];
|
|
239
|
+
let summaryAcc = null;
|
|
240
|
+
let truncated = null;
|
|
241
|
+
|
|
242
|
+
if (!key) {
|
|
243
|
+
// Free path: send only the first FREE_CAP (server would truncate anyway),
|
|
244
|
+
// but tell the user the true total so the upsell is honest.
|
|
245
|
+
const send = proxies.slice(0, FREE_CAP);
|
|
246
|
+
if (!opts.json && !opts.csv) {
|
|
247
|
+
process.stderr.write(dim(`proxychecker.dev · checking ${send.length} ` +
|
|
248
|
+
`${proxies.length > FREE_CAP ? `of ${proxies.length} ` : ""}proxies...\n\n`));
|
|
249
|
+
}
|
|
250
|
+
const data = await checkChunk(send);
|
|
251
|
+
merged = data.results;
|
|
252
|
+
summaryAcc = data.summary;
|
|
253
|
+
if (proxies.length > FREE_CAP) truncated = { sent: proxies.length, checked: send.length };
|
|
254
|
+
} else {
|
|
255
|
+
// Keyed path: split into 5k chunks and merge.
|
|
256
|
+
const chunks = [];
|
|
257
|
+
for (let i = 0; i < proxies.length; i += KEYED_CHUNK) chunks.push(proxies.slice(i, i + KEYED_CHUNK));
|
|
258
|
+
let done = 0;
|
|
259
|
+
for (const chunk of chunks) {
|
|
260
|
+
if (!opts.json && !opts.csv) {
|
|
261
|
+
process.stderr.write(dim(`\rproxychecker.dev · checking ${done + chunk.length}/${proxies.length}...`));
|
|
262
|
+
}
|
|
263
|
+
const data = await checkChunk(chunk);
|
|
264
|
+
merged.push(...data.results);
|
|
265
|
+
done += chunk.length;
|
|
266
|
+
if (!summaryAcc) summaryAcc = { ...data.summary };
|
|
267
|
+
else for (const k of Object.keys(data.summary)) summaryAcc[k] += data.summary[k];
|
|
268
|
+
}
|
|
269
|
+
if (summaryAcc && merged.length) {
|
|
270
|
+
const alive = merged.filter((r) => r.status === "alive");
|
|
271
|
+
summaryAcc.avg_latency_ms = alive.length
|
|
272
|
+
? Math.round(alive.reduce((s, r) => s + (r.latency || 0), 0) / alive.length)
|
|
273
|
+
: 0;
|
|
274
|
+
}
|
|
275
|
+
if (!opts.json && !opts.csv) process.stderr.write("\n\n");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ---------- output ----------
|
|
279
|
+
if (opts.csv) {
|
|
280
|
+
const rows = opts.alive ? merged.filter((r) => r.status === "alive") : merged;
|
|
281
|
+
process.stdout.write(toCsv(rows) + "\n");
|
|
282
|
+
} else if (opts.json) {
|
|
283
|
+
const rows = opts.alive ? merged.filter((r) => r.status === "alive") : merged;
|
|
284
|
+
process.stdout.write(JSON.stringify({ summary: summaryAcc, truncated, results: rows }, null, 2) + "\n");
|
|
285
|
+
} else {
|
|
286
|
+
printTable(merged);
|
|
287
|
+
printSummary(summaryAcc, truncated);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (opts["fail-if-none"] && (!summaryAcc || summaryAcc.alive === 0)) process.exitCode = 2;
|
|
291
|
+
} catch (e) {
|
|
292
|
+
// Any post-network failure lands here: report and set a non-zero exit code,
|
|
293
|
+
// then let the event loop drain so we never hard-exit on a closing socket.
|
|
294
|
+
process.stderr.write(red("error: ") + e.message + "\n");
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
}
|
|
297
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "proxychecker-dev",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Fast proxy checker CLI. Test HTTP/HTTPS/SOCKS proxies for liveness, latency, anonymity, and datacenter-vs-residential type. Free, no signup.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"proxycheck": "bin/proxycheck.js",
|
|
8
|
+
"proxychecker-dev": "bin/proxycheck.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"proxy",
|
|
15
|
+
"proxy-checker",
|
|
16
|
+
"proxy-tester",
|
|
17
|
+
"proxy-validation",
|
|
18
|
+
"socks5",
|
|
19
|
+
"socks4",
|
|
20
|
+
"http-proxy",
|
|
21
|
+
"https-proxy",
|
|
22
|
+
"scraping",
|
|
23
|
+
"web-scraping",
|
|
24
|
+
"proxies",
|
|
25
|
+
"cli",
|
|
26
|
+
"residential-proxy",
|
|
27
|
+
"datacenter-proxy"
|
|
28
|
+
],
|
|
29
|
+
"author": "Flash AI Solutions",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"homepage": "https://proxychecker.dev",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/iFan6oy/proxychecker-cli.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/iFan6oy/proxychecker-cli/issues"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"bin",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {}
|
|
45
|
+
}
|