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 CHANGED
@@ -1 +1,261 @@
1
- WIP, coming soon
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
- "description": "coming soon!",
4
- "version": "0.0.0-security",
5
- "files": ["package.json", "README.md"]
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
+ }
@@ -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,9 @@
1
+ import util from 'node:util';
2
+
3
+ export function assignDBMethods(db) {
4
+ Object.assign(db, {
5
+ runQuery: util.promisify(db.run.bind(db)),
6
+ getOne: util.promisify(db.get.bind(db)),
7
+ getAll: util.promisify(db.all.bind(db)),
8
+ });
9
+ }
@@ -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
+ }