puppyproxy 0.0.0-security → 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/README.md +261 -1
- package/package.json +29 -6
- package/src/PuppyProxy.js +131 -0
- package/src/constants.js +82 -0
- package/src/database/DatabaseUtils.js +9 -0
- package/src/database/ProxyDatabase.js +77 -0
- package/src/index.d.ts +186 -0
- package/src/index.js +2 -0
- package/src/network/RequestClient.js +284 -0
- package/src/providers/IPVanish.js +36 -0
- package/src/providers/Scraper.js +144 -0
- package/src/utils.js +12 -0
- package/src/workers/collector.js +311 -0
package/README.md
CHANGED
|
@@ -1 +1,261 @@
|
|
|
1
|
-
|
|
1
|
+
# puppyproxy
|
|
2
|
+
|
|
3
|
+
[On npm @ https://www.npmjs.com/package/puppyproxy](https://www.npmjs.com/package/puppyproxy)
|
|
4
|
+
|
|
5
|
+
[And GitHub @ https://github.com/onlypuppy7/puppyproxy](https://github.com/onlypuppy7/puppyproxy)
|
|
6
|
+
|
|
7
|
+
Robust and reliable free proxy scraper and request wrapper, ported to npm. Another useless file taken out of my projects!
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
`npm install puppyproxy`
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
```js
|
|
14
|
+
import PuppyProxy from 'puppyproxy';
|
|
15
|
+
import log from 'puppylog'; //another one of my packages
|
|
16
|
+
import { wait } from 'puppymisc'; //another one of my packages!
|
|
17
|
+
|
|
18
|
+
const ipUrl = 'http://ifconfig.me/ip';
|
|
19
|
+
const wsUrl = 'wss://ws.postman-echo.com/raw';
|
|
20
|
+
|
|
21
|
+
const config = {
|
|
22
|
+
// storeDir: './store', // optional, defaults to platform specific data dir
|
|
23
|
+
ipvanish: {
|
|
24
|
+
// use: true,
|
|
25
|
+
// creds: { username: 'abc', password: '123' },
|
|
26
|
+
},
|
|
27
|
+
scraper: {
|
|
28
|
+
use: true,
|
|
29
|
+
timeBetweenScrapes: 45 * 60e3,
|
|
30
|
+
maxProxiesToCheck: 5000, // the higher this is, the longer it takes, but the more proxies you get
|
|
31
|
+
limitProxies: 500000,
|
|
32
|
+
timeoutPerProxy: 5,
|
|
33
|
+
timeoutMax: 5,
|
|
34
|
+
verbose: true, // for logging purposes
|
|
35
|
+
logStatus: true, // shows a convenient summary every so often
|
|
36
|
+
proxyTypes: ['socks4', 'socks5'], //['socks4', 'socks5', 'http', 'https']
|
|
37
|
+
|
|
38
|
+
timeoutFetch: 10000, // ms
|
|
39
|
+
timeoutWs: 7000,
|
|
40
|
+
maxRetries: 10,
|
|
41
|
+
},
|
|
42
|
+
LOG: { connect: true, error: true } // for logging purposes
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const pp = new PuppyProxy(config);
|
|
46
|
+
|
|
47
|
+
log.highlight("TESTING: runCollector");
|
|
48
|
+
|
|
49
|
+
await pp.init(true); // true forces run the collector
|
|
50
|
+
|
|
51
|
+
log.highlight("TESTING: RequestClient");
|
|
52
|
+
|
|
53
|
+
log.bold("----- pp.request.fetch")
|
|
54
|
+
|
|
55
|
+
//fetch with inbuilt resilient retrying in case of error. uses a new proxy from the pool each time
|
|
56
|
+
const resRequestFetch = await pp.request.fetch(ipUrl);
|
|
57
|
+
log.success("res from request fetch", await resRequestFetch.text());
|
|
58
|
+
|
|
59
|
+
log.bold("----- pp.request.fetchBasic")
|
|
60
|
+
|
|
61
|
+
//more basic fetch that just forwards to you the req object with a proxy agent set
|
|
62
|
+
try {
|
|
63
|
+
const resFetchBasic = await (pp.request.fetchBasic(ipUrl));
|
|
64
|
+
if (resFetchBasic?.ok) {
|
|
65
|
+
log.success("res from basic fetch", await resFetchBasic.text());
|
|
66
|
+
} else {
|
|
67
|
+
//this may fail because free proxies are unreliable, and it only tries once
|
|
68
|
+
log.error("basic fetch failed", resFetchBasic.status);
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
log.error("basic fetch error, which is sort of expected. create your own catching logic.", error.message);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
log.bold("----- pp.request.ws")
|
|
75
|
+
|
|
76
|
+
//ws with inbuilt resilient retrying in case of error. uses a new proxy from the pool each time
|
|
77
|
+
const testWs = async (wsHandler) => {
|
|
78
|
+
return new Promise(async (resolve, reject) => {
|
|
79
|
+
await wsHandler(
|
|
80
|
+
wsUrl,
|
|
81
|
+
{
|
|
82
|
+
// preferredAgent: 'none' // 'ipvanish', 'scraper', or 'none'
|
|
83
|
+
},
|
|
84
|
+
// open
|
|
85
|
+
(ws) => {
|
|
86
|
+
const testMessage = 'echo, test message';
|
|
87
|
+
log.success(`Connected to echo server, sending test message ${testMessage}`);
|
|
88
|
+
ws.send(JSON.stringify(testMessage));
|
|
89
|
+
},
|
|
90
|
+
// message
|
|
91
|
+
(ws, event) => {
|
|
92
|
+
try {
|
|
93
|
+
const data = JSON.parse(event.data);
|
|
94
|
+
log.success('Received JSON:', data);
|
|
95
|
+
} catch {
|
|
96
|
+
log.success('Received text:', event.data);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// close after we get responses
|
|
100
|
+
ws.close();
|
|
101
|
+
wait(500).then(resolve);
|
|
102
|
+
},
|
|
103
|
+
// close
|
|
104
|
+
(ws, event) => {
|
|
105
|
+
log.italic(`WebSocket closed with code: ${event.code}, reason: ${event.reason}`);
|
|
106
|
+
},
|
|
107
|
+
// error
|
|
108
|
+
(ws, error) => {
|
|
109
|
+
// reject(error);
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
resolve();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await testWs(pp.request.ws.bind(pp.request));
|
|
118
|
+
} catch (error) {
|
|
119
|
+
log.error("WebSocket connection failed:", error.message);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
log.bold("----- pp.request.wsBasic");
|
|
123
|
+
|
|
124
|
+
const testWsBasic = async (wsBasicHandler) => {
|
|
125
|
+
try {
|
|
126
|
+
await new Promise(async (resolve, reject) => {
|
|
127
|
+
const ws = wsBasicHandler(wsUrl, {});
|
|
128
|
+
|
|
129
|
+
ws.onopen = (event) => {
|
|
130
|
+
const testMessage = 'echo, test message via wsBasic';
|
|
131
|
+
log.success(`Connected to echo server via wsBasic, sending test message ${testMessage}`);
|
|
132
|
+
ws.send(JSON.stringify(testMessage));
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
ws.onmessage = (event) => {
|
|
136
|
+
try {
|
|
137
|
+
const data = JSON.parse(event.data);
|
|
138
|
+
log.success('wsBasic Received JSON:', data);
|
|
139
|
+
} catch {
|
|
140
|
+
log.success('wsBasic Received text:', event.data);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
ws.close();
|
|
144
|
+
wait(500).then(resolve);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
ws.onclose = (event) => {
|
|
148
|
+
log.italic(`wsBasic WebSocket closed with code: ${event.code}, reason: ${event.reason}`);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
ws.onerror = (error) => {
|
|
152
|
+
log.error("wsBasic WebSocket error, which is sort of expected. create your own catching logic.", error.message);
|
|
153
|
+
reject(error);
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// log.error("proxyRequest wsBasic WebSocket connection failed:", error.message);
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
//similar basic method like pp.request.fetchBasic exists for pp.request.wsBasic if you want less resilience and more control.
|
|
162
|
+
|
|
163
|
+
await testWsBasic(pp.request.wsBasic.bind(pp.request));
|
|
164
|
+
|
|
165
|
+
log.highlight("TESTING: ProxyRequest");
|
|
166
|
+
|
|
167
|
+
//proxyRequest is a wrapper that allows you to use a more consistent proxy for multiple requests, with auto-rotation when it fails
|
|
168
|
+
const proxyRequest = pp.createProxyRequest(ipUrl, {
|
|
169
|
+
autoNewAgent: true, // auto rotate the proxy when a request fails. if this is false if you really need just one proxy you can manage yourself
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
log.bold("----- proxyRequest.fetch");
|
|
173
|
+
|
|
174
|
+
//all methods are similar to pp.request but use the same proxy until it fails
|
|
175
|
+
const resProxyRequestFetch = await proxyRequest.fetch(); //url can be passed here, but it already was set in constructor
|
|
176
|
+
log.success("res from proxyRequest fetch", await resProxyRequestFetch.text());
|
|
177
|
+
|
|
178
|
+
log.bold("----- proxyRequest.fetchBasic");
|
|
179
|
+
|
|
180
|
+
const testProxyRequestFetchBasic = async () => {
|
|
181
|
+
try {
|
|
182
|
+
const resProxyRequestFetchBasic = await (proxyRequest.fetchBasic());
|
|
183
|
+
if (resProxyRequestFetchBasic?.ok) {
|
|
184
|
+
log.success("res from proxyRequest basic fetch", await resProxyRequestFetchBasic.text());
|
|
185
|
+
} else {
|
|
186
|
+
log.error("proxyRequest basic fetch failed", resProxyRequestFetchBasic.status);
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
log.error("proxyRequest basic fetch error, which is sort of expected. create your own catching logic.", error.message);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
await testProxyRequestFetchBasic();
|
|
194
|
+
//try again to see it use the same proxy
|
|
195
|
+
await testProxyRequestFetchBasic();
|
|
196
|
+
|
|
197
|
+
log.bold("----- proxyRequest.newAgent");
|
|
198
|
+
|
|
199
|
+
//you can manually rotate the proxy too
|
|
200
|
+
proxyRequest.newAgent();
|
|
201
|
+
|
|
202
|
+
await testProxyRequestFetchBasic();
|
|
203
|
+
|
|
204
|
+
log.bold("----- proxyRequest.ws");
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
await testWs(proxyRequest.ws.bind(proxyRequest));
|
|
208
|
+
} catch (error) {
|
|
209
|
+
log.error("proxyRequest WebSocket connection failed:", error.message);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
log.bold("----- proxyRequest.wsBasic");
|
|
213
|
+
|
|
214
|
+
await testWsBasic(proxyRequest.wsBasic.bind(proxyRequest));
|
|
215
|
+
|
|
216
|
+
log.highlight("TESTING: createGlobalPatch");
|
|
217
|
+
|
|
218
|
+
pp.createGlobalPatch();
|
|
219
|
+
|
|
220
|
+
log.bold("----- global fetch")
|
|
221
|
+
|
|
222
|
+
const resGlobalFetch = await fetch(ipUrl);
|
|
223
|
+
log.success("res from global fetch", await resGlobalFetch.text());
|
|
224
|
+
|
|
225
|
+
//note: i dont think you can override node-fetch. sad!
|
|
226
|
+
|
|
227
|
+
log.bold("----- global WebSocket")
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
await new Promise((resolve, reject) => {
|
|
231
|
+
const ws = new WebSocket(wsUrl);
|
|
232
|
+
ws.onopen = (event) => {
|
|
233
|
+
const testMessage = 'echo, test message via global WebSocket';
|
|
234
|
+
log.success(`Connected to echo server via global WebSocket, sending test message ${testMessage}`);
|
|
235
|
+
ws.send(JSON.stringify(testMessage));
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
ws.onmessage = (event) => {
|
|
239
|
+
try {
|
|
240
|
+
const data = JSON.parse(event.data);
|
|
241
|
+
log.success('global WebSocket Received JSON:', data);
|
|
242
|
+
} catch {
|
|
243
|
+
log.success('global WebSocket Received text:', event.data);
|
|
244
|
+
}
|
|
245
|
+
ws.close();
|
|
246
|
+
wait(500).then(resolve);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
ws.onclose = (event) => {
|
|
250
|
+
log.italic(`global WebSocket closed with code: ${event.code}, reason: ${event.reason}`);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
ws.onerror = (error) => {
|
|
254
|
+
log.error("global WebSocket error, which is sort of expected. create your own catching logic.", error.message);
|
|
255
|
+
reject(error);
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
} catch (error) {
|
|
259
|
+
log.error("global WebSocket connection failed:", error.message);
|
|
260
|
+
}
|
|
261
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "puppyproxy",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "puppyproxy",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./src/index.d.ts",
|
|
7
|
+
"main": "./src/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node src/tests/test.js",
|
|
14
|
+
"build": "node src/tests/build.js"
|
|
15
|
+
},
|
|
16
|
+
"author": "onlypuppy7",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"http-proxy-agent": "^7.0.2",
|
|
20
|
+
"https-proxy-agent": "^7.0.6",
|
|
21
|
+
"p-limit": "^7.2.0",
|
|
22
|
+
"puppyid": "^1.1.0",
|
|
23
|
+
"puppylog": "^1.2.0",
|
|
24
|
+
"puppymisc": "^1.1.1",
|
|
25
|
+
"puppyscrambled": "^1.1.0",
|
|
26
|
+
"socks-proxy-agent": "^8.0.5",
|
|
27
|
+
"sqlite3": "^5.1.7"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import IPVanish from './providers/IPVanish.js';
|
|
2
|
+
import Scraper from './providers/Scraper.js';
|
|
3
|
+
import ProxyDatabase from './database/ProxyDatabase.js';
|
|
4
|
+
import RequestClient from './network/RequestClient.js';
|
|
5
|
+
import log from 'puppylog';
|
|
6
|
+
import id from 'puppyid';
|
|
7
|
+
import { originals } from './constants.js';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
export default class PuppyProxy {
|
|
12
|
+
/**
|
|
13
|
+
* @param {Object} config - The configuration object
|
|
14
|
+
*/
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.config.storeDir = this.config.storeDir || this.resolveDataDir();
|
|
18
|
+
console.log("Using data store directory:", this.config.storeDir);
|
|
19
|
+
|
|
20
|
+
// Initialize Modules
|
|
21
|
+
this.proxyDB = new ProxyDatabase(this.config);
|
|
22
|
+
|
|
23
|
+
this.ipvanish = new IPVanish(this.config);
|
|
24
|
+
this.scraper = new Scraper(this.config, this.proxyDB);
|
|
25
|
+
|
|
26
|
+
// Request Client needs access to this instance to get agents
|
|
27
|
+
this.request = new RequestClient(this, this.config);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
resolveDataDir() {
|
|
31
|
+
const appName = 'puppyproxy-data';
|
|
32
|
+
switch (process.platform) {
|
|
33
|
+
case 'win32':
|
|
34
|
+
return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), appName);
|
|
35
|
+
case 'darwin':
|
|
36
|
+
return path.join(os.homedir(), 'Library', 'Application Support', appName);
|
|
37
|
+
default: // Linux and others
|
|
38
|
+
return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), appName);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Initialize the PuppyProxy system
|
|
44
|
+
* @param {boolean} forceCollector - Whether to force run the proxy collector
|
|
45
|
+
*/
|
|
46
|
+
async init(forceCollector = false) {
|
|
47
|
+
await this.proxyDB.init();
|
|
48
|
+
|
|
49
|
+
// Load scraped proxies into memory
|
|
50
|
+
await this.scraper.runCollector(forceCollector);
|
|
51
|
+
await this.scraper.loadScrapedProxies();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Centralized method to get an agent based on preference
|
|
56
|
+
* @param {string} url
|
|
57
|
+
* @param {string} preferredAgent - 'ipvanish', 'scraper', or 'none'
|
|
58
|
+
*/
|
|
59
|
+
getAgent(url, preferredAgent = null) {
|
|
60
|
+
const agents = {
|
|
61
|
+
ipvanish: this.ipvanish.getAgent(url),
|
|
62
|
+
scraper: this.scraper.getAgent(url),
|
|
63
|
+
none: null
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Determine which agent to use based on config and preference
|
|
67
|
+
let use = null;
|
|
68
|
+
|
|
69
|
+
if (preferredAgent && agents[preferredAgent]) {
|
|
70
|
+
use = preferredAgent;
|
|
71
|
+
} else if (preferredAgent !== "none") {
|
|
72
|
+
if (this.config.ipvanish?.use) use = "ipvanish";
|
|
73
|
+
else if (this.config.scraper?.use) use = "scraper";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const agent = use ? agents[use] : null;
|
|
77
|
+
|
|
78
|
+
// Logging
|
|
79
|
+
if (agent) {
|
|
80
|
+
if (this.config.LOG?.connect) {
|
|
81
|
+
log.magenta(id, `Attempting to connect to ${url} via ${use}: ${agent.USINGSCRAPED || 'internal'}`);
|
|
82
|
+
}
|
|
83
|
+
} else if (preferredAgent !== "none" && (this.config.ipvanish?.use || this.config.scraper?.use)) {
|
|
84
|
+
const errorMsg = `Error!! Wanted proxy but none returned for connect to ${url} (Pref: ${preferredAgent})`;
|
|
85
|
+
log.error(id, errorMsg);
|
|
86
|
+
throw new Error(errorMsg);
|
|
87
|
+
} else {
|
|
88
|
+
if (this.config.LOG?.connectNoProxy) {
|
|
89
|
+
log.muted(id, `Attempting to connect to ${url} with no proxy`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return agent;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
createProxyRequest(url, options = {}) {
|
|
97
|
+
return this.request.createProxyRequest(url, options);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Trigger the proxy collection worker
|
|
102
|
+
*/
|
|
103
|
+
async collectProxies(force = false) {
|
|
104
|
+
return this.scraper.runCollector(force);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
createGlobalPatch(fetchOverride = this.request.fetch.bind(this.request), wsOverride = this.request.wsBasic.bind(this.request), fetchRule = () => true, wsRule = () => true) {
|
|
108
|
+
console.log(!!originals.fetch, !!originals.WebSocket, "Creating global fetch/WebSocket patch for PuppyProxy");
|
|
109
|
+
|
|
110
|
+
//override global fetch and WebSocket to use PuppyProxy
|
|
111
|
+
|
|
112
|
+
if (fetchOverride) {
|
|
113
|
+
const newFetch = (url, options = {}) => {
|
|
114
|
+
if (!fetchRule(url, options)) {
|
|
115
|
+
return originals.fetch(url, options);
|
|
116
|
+
} else return fetchOverride(url, options);
|
|
117
|
+
};
|
|
118
|
+
//global fetch
|
|
119
|
+
global.fetch = newFetch;
|
|
120
|
+
//i couldnt work out node-fetch
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
wsOverride && (global.WebSocket = class extends originals.WebSocket {
|
|
124
|
+
constructor(...args) {
|
|
125
|
+
if (args.length > 0 && !wsRule(args[0], args[1])) {
|
|
126
|
+
return new originals.WebSocket(...args);
|
|
127
|
+
} else return wsOverride(...args);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import { WebSocket } from "ws";
|
|
3
|
+
|
|
4
|
+
export const IPVANISH_HOSTS = [
|
|
5
|
+
"syd.socks.ipvanish.com",
|
|
6
|
+
"tor.socks.ipvanish.com",
|
|
7
|
+
"par.socks.ipvanish.com",
|
|
8
|
+
"fra.socks.ipvanish.com",
|
|
9
|
+
"lin.socks.ipvanish.com",
|
|
10
|
+
"nrt.socks.ipvanish.com",
|
|
11
|
+
"ams.socks.ipvanish.com",
|
|
12
|
+
"waw.socks.ipvanish.com",
|
|
13
|
+
"lis.socks.ipvanish.com",
|
|
14
|
+
"sin.socks.ipvanish.com",
|
|
15
|
+
"mad.socks.ipvanish.com",
|
|
16
|
+
"sto.socks.ipvanish.com",
|
|
17
|
+
"lon.socks.ipvanish.com",
|
|
18
|
+
"iad.socks.ipvanish.com",
|
|
19
|
+
"atl.socks.ipvanish.com",
|
|
20
|
+
"chi.socks.ipvanish.com",
|
|
21
|
+
"dal.socks.ipvanish.com",
|
|
22
|
+
"den.socks.ipvanish.com",
|
|
23
|
+
"lax.socks.ipvanish.com",
|
|
24
|
+
"mia.socks.ipvanish.com",
|
|
25
|
+
"nyc.socks.ipvanish.com",
|
|
26
|
+
"phx.socks.ipvanish.com",
|
|
27
|
+
"sea.socks.ipvanish.com"
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const PROXY_SOURCES = {
|
|
31
|
+
socks5: [
|
|
32
|
+
"https://raw.githubusercontent.com/TheSpeedX/PROXY-List/refs/heads/master/socks5.txt",
|
|
33
|
+
"https://raw.githubusercontent.com/vakhov/fresh-proxy-list/refs/heads/master/socks5.txt",
|
|
34
|
+
"https://github.com/hookzof/socks5_list/raw/refs/heads/master/proxy.txt",
|
|
35
|
+
"https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks5.txt",
|
|
36
|
+
"https://api.openproxylist.xyz/socks5.txt",
|
|
37
|
+
"https://raw.githubusercontent.com/B4RC0DE-TM/proxy-list/main/SOCKS5.txt",
|
|
38
|
+
"https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/socks5.txt",
|
|
39
|
+
// "https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/txt/proxies-socks5.txt", //not updated
|
|
40
|
+
"https://raw.githubusercontent.com/roosterkid/openproxylist/main/SOCKS5_RAW.txt",
|
|
41
|
+
"https://proxyspace.pro/socks5.txt",
|
|
42
|
+
// "https://www.proxy-list.download/api/v1/get?type=socks5", //takes ages
|
|
43
|
+
// "https://raw.githubusercontent.com/proxifly/free-proxy-list/refs/heads/main/proxies/protocols/socks5/data.txt", //27% //dead
|
|
44
|
+
"https://raw.githubusercontent.com/ErcinDedeoglu/proxies/refs/heads/main/proxies/socks5.txt",
|
|
45
|
+
"https://github.com/vmheaven/VMHeaven-Free-Proxy-Updated/raw/refs/heads/main/socks5.txt",
|
|
46
|
+
"https://github.com/Vann-Dev/proxy-list/raw/refs/heads/main/proxies/socks5.txt",
|
|
47
|
+
"https://github.com/ebrasha/abdal-proxy-hub/raw/refs/heads/main/socks5-proxy-list-by-EbraSha.txt",
|
|
48
|
+
"https://raw.githubusercontent.com/iplocate/free-proxy-list/refs/heads/main/protocols/socks5.txt",
|
|
49
|
+
"https://github.com/Anonym0usWork1221/Free-Proxies/raw/refs/heads/main/proxy_files/socks5_proxies.txt",
|
|
50
|
+
"https://github.com/murtaja89/public-proxies/raw/refs/heads/main/proxies_socks5.txt",
|
|
51
|
+
"https://github.com/Konorze/public-proxies/raw/refs/heads/main/proxies_socks5.txt",
|
|
52
|
+
// "https://raw.githubusercontent.com/fyvri/fresh-proxy-list/archive/storage/classic/socks5.txt", //like 50k
|
|
53
|
+
],
|
|
54
|
+
socks4: [
|
|
55
|
+
"https://github.com/TheSpeedX/PROXY-List/raw/refs/heads/master/socks4.txt",
|
|
56
|
+
"https://github.com/proxifly/free-proxy-list/raw/refs/heads/main/proxies/protocols/socks4/data.txt",
|
|
57
|
+
"https://github.com/MuRongPIG/Proxy-Master/raw/refs/heads/main/socks4_checked.txt",
|
|
58
|
+
"https://github.com/vakhov/fresh-proxy-list/raw/refs/heads/master/socks4.txt",
|
|
59
|
+
"https://github.com/dpangestuw/Free-Proxy/raw/refs/heads/main/socks4_proxies.txt",
|
|
60
|
+
"https://github.com/ALIILAPRO/Proxy/raw/refs/heads/main/socks4.txt",
|
|
61
|
+
"https://github.com/vmheaven/VMHeaven-Free-Proxy-Updated/raw/refs/heads/main/socks4.txt",
|
|
62
|
+
"https://github.com/Vann-Dev/proxy-list/raw/refs/heads/main/proxies/socks4.txt",
|
|
63
|
+
"https://github.com/ebrasha/abdal-proxy-hub/raw/refs/heads/main/socks4-proxy-list-by-EbraSha.txt",
|
|
64
|
+
"https://raw.githubusercontent.com/iplocate/free-proxy-list/refs/heads/main/protocols/socks4.txt",
|
|
65
|
+
"https://github.com/Anonym0usWork1221/Free-Proxies/raw/refs/heads/main/proxy_files/socks4_proxies.txt",
|
|
66
|
+
// "https://raw.githubusercontent.com/fyvri/fresh-proxy-list/archive/storage/classic/socks4.txt", //too many
|
|
67
|
+
"https://api.proxyscrape.com/v2/?request=displayproxies&protocol=socks4"
|
|
68
|
+
],
|
|
69
|
+
http: [
|
|
70
|
+
"https://github.com/dpangestuw/Free-Proxy/raw/refs/heads/main/http_proxies.txt",
|
|
71
|
+
"https://github.com/vakhov/fresh-proxy-list/raw/refs/heads/master/http.txt",
|
|
72
|
+
],
|
|
73
|
+
https: [
|
|
74
|
+
"https://github.com/vakhov/fresh-proxy-list/raw/refs/heads/master/https.txt",
|
|
75
|
+
"https://github.com/javadbazokar/PROXY-List/raw/refs/heads/main/https.txt",
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const originals = {
|
|
80
|
+
fetch,
|
|
81
|
+
WebSocket,
|
|
82
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import sqlite3 from 'sqlite3';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { assignDBMethods } from './DatabaseUtils.js';
|
|
5
|
+
|
|
6
|
+
export default class ProxyDatabase {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.storeDir = config.storeDir || './store';
|
|
10
|
+
this.dbPath = path.join(this.storeDir, 'proxies.db');
|
|
11
|
+
this.db = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async init() {
|
|
15
|
+
if (!fs.existsSync(this.storeDir)) fs.mkdirSync(this.storeDir, { recursive: true });
|
|
16
|
+
this.db = new sqlite3.Database(this.dbPath);
|
|
17
|
+
this.db.configure("busyTimeout", 30000);
|
|
18
|
+
assignDBMethods(this.db);
|
|
19
|
+
await this.setupTables();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async setupTables() {
|
|
23
|
+
await this.db.runQuery(`CREATE TABLE IF NOT EXISTS proxies (
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
url TEXT UNIQUE NOT NULL,
|
|
26
|
+
last_worked_at INTEGER DEFAULT 0,
|
|
27
|
+
time_added INTEGER DEFAULT (strftime('%s', 'now'))
|
|
28
|
+
)`);
|
|
29
|
+
|
|
30
|
+
await this.db.runQuery(`CREATE TABLE IF NOT EXISTS flags (
|
|
31
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
32
|
+
key TEXT UNIQUE NOT NULL,
|
|
33
|
+
value TEXT NOT NULL
|
|
34
|
+
)`);
|
|
35
|
+
|
|
36
|
+
await this.db.runQuery(`INSERT OR IGNORE INTO flags (key, value) VALUES (?, ?)`, ["current_iteration", 0]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async dealWithProxies(newProxies, outputPath) {
|
|
40
|
+
console.log("Dealing with proxies:", newProxies.length);
|
|
41
|
+
|
|
42
|
+
let currentIteration = Number((await this.db.getOne(`SELECT value FROM flags WHERE key = ?`, "current_iteration")).value);
|
|
43
|
+
|
|
44
|
+
let existingProxies = await this.db.getAll(`SELECT url FROM proxies`);
|
|
45
|
+
|
|
46
|
+
for (const proxy of newProxies) {
|
|
47
|
+
let existingProxy = existingProxies.find(p => p.url === proxy);
|
|
48
|
+
if (!existingProxy) {
|
|
49
|
+
await this.db.runQuery(`INSERT OR IGNORE INTO proxies (url, last_worked_at) VALUES (?, ?)`, [proxy, currentIteration]);
|
|
50
|
+
} else {
|
|
51
|
+
await this.db.runQuery(`UPDATE proxies SET last_worked_at = ? WHERE url = ?`, [currentIteration, proxy]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await this.db.runQuery(`UPDATE flags SET value = ? WHERE key = ?`, [currentIteration + 1, "current_iteration"]);
|
|
56
|
+
|
|
57
|
+
// Select working proxies (worked in last 3 iterations)
|
|
58
|
+
let decentProxies = await this.db.getAll(`SELECT url FROM proxies WHERE last_worked_at >= ?`, [currentIteration - 3]);
|
|
59
|
+
let decentProxiesArray = decentProxies.map(p => p.url);
|
|
60
|
+
|
|
61
|
+
// Merge extra proxies
|
|
62
|
+
const extraProxiesPath = path.join(this.storeDir, 'extra_proxies.json');
|
|
63
|
+
if (!fs.existsSync(extraProxiesPath)) {
|
|
64
|
+
fs.writeFileSync(extraProxiesPath, JSON.stringify([]), 'utf8');
|
|
65
|
+
}
|
|
66
|
+
let extraProxies = JSON.parse(fs.readFileSync(extraProxiesPath, 'utf8'));
|
|
67
|
+
decentProxiesArray = [...new Set([...decentProxiesArray, ...extraProxies])];
|
|
68
|
+
|
|
69
|
+
console.log(`Writing ${decentProxiesArray.length} proxies to ${outputPath}`);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
fs.writeFileSync(outputPath, JSON.stringify(decentProxiesArray), 'utf8');
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error("Error writing proxies to json:", err.message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|