figranium 0.12.1 → 0.12.2

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/proxy-rotation.js CHANGED
@@ -1,261 +1,261 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const crypto = require('crypto');
4
- const {
5
- normalizeServer,
6
- createProxyId,
7
- normalizeProxy,
8
- normalizeRotationMode
9
- } = require('./proxy-utils');
10
-
11
- const DATA_PROXY_FILE = path.join(__dirname, 'data', 'proxies.json');
12
- const PROXY_FILES = [
13
- DATA_PROXY_FILE,
14
- path.join(__dirname, 'proxies.json')
15
- ];
16
-
17
- let cached = {
18
- file: null,
19
- mtimeMs: 0,
20
- config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' }
21
- };
22
- let rotationIndex = 0;
23
-
24
- const loadProxyFile = (filePath) => {
25
- try {
26
- const raw = fs.readFileSync(filePath, 'utf8');
27
- const parsed = JSON.parse(raw);
28
- if (Array.isArray(parsed)) {
29
- return { proxies: parsed, defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' };
30
- }
31
- const proxies = Array.isArray(parsed.proxies) ? parsed.proxies : [];
32
- const defaultProxyId = parsed.defaultProxyId || null;
33
- const includeDefaultInRotation = !!parsed.includeDefaultInRotation;
34
- const rotationMode = normalizeRotationMode(parsed.rotationMode);
35
- return { proxies, defaultProxyId, includeDefaultInRotation, rotationMode };
36
- } catch {
37
- return { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' };
38
- }
39
- };
40
-
41
- const loadProxyConfig = () => {
42
- const filePath = PROXY_FILES.find((candidate) => {
43
- try {
44
- return fs.existsSync(candidate);
45
- } catch {
46
- return false;
47
- }
48
- });
49
-
50
- if (!filePath) {
51
- cached = { file: null, mtimeMs: 0, config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' } };
52
- return cached.config;
53
- }
54
-
55
- try {
56
- const stat = fs.statSync(filePath);
57
- const mtimeMs = stat.mtimeMs || 0;
58
- if (cached.file === filePath && cached.mtimeMs === mtimeMs) {
59
- return cached.config;
60
- }
61
- const rawConfig = loadProxyFile(filePath);
62
- const proxies = rawConfig.proxies.map(normalizeProxy).filter(Boolean);
63
- const defaultProxyId = rawConfig.defaultProxyId && proxies.some((proxy) => proxy.id === rawConfig.defaultProxyId)
64
- ? rawConfig.defaultProxyId
65
- : null;
66
- const config = {
67
- proxies,
68
- defaultProxyId,
69
- includeDefaultInRotation: !!rawConfig.includeDefaultInRotation,
70
- rotationMode: normalizeRotationMode(rawConfig.rotationMode)
71
- };
72
- cached = { file: filePath, mtimeMs, config };
73
- return config;
74
- } catch {
75
- cached = { file: filePath, mtimeMs: 0, config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' } };
76
- return cached.config;
77
- }
78
- };
79
-
80
- const saveProxyConfig = (config) => {
81
- const target = DATA_PROXY_FILE;
82
- const payload = {
83
- defaultProxyId: config.defaultProxyId || null,
84
- proxies: Array.isArray(config.proxies) ? config.proxies : [],
85
- includeDefaultInRotation: !!config.includeDefaultInRotation,
86
- rotationMode: normalizeRotationMode(config.rotationMode)
87
- };
88
- fs.writeFileSync(target, JSON.stringify(payload, null, 2));
89
- try {
90
- const stat = fs.statSync(target);
91
- cached = { file: target, mtimeMs: stat.mtimeMs || 0, config: payload };
92
- } catch {
93
- cached = { file: target, mtimeMs: 0, config: payload };
94
- }
95
- return payload;
96
- };
97
-
98
- const listProxies = () => {
99
- const config = loadProxyConfig();
100
- const hostEntry = {
101
- id: 'host',
102
- server: 'host_ip',
103
- label: 'Host IP (no proxy)'
104
- };
105
- return {
106
- proxies: [hostEntry, ...(config.proxies || [])],
107
- defaultProxyId: config.defaultProxyId || 'host',
108
- includeDefaultInRotation: !!config.includeDefaultInRotation,
109
- rotationMode: normalizeRotationMode(config.rotationMode)
110
- };
111
- };
112
-
113
- const addProxy = (entry) => {
114
- const normalized = normalizeProxy(entry);
115
- if (!normalized) return null;
116
- const config = loadProxyConfig();
117
- const proxies = [...config.proxies, { ...normalized, id: `proxy_${crypto.randomBytes(6).toString('hex')}` }];
118
- const next = { ...config, proxies };
119
- return saveProxyConfig(next);
120
- };
121
-
122
- const addProxies = (entries) => {
123
- if (!Array.isArray(entries)) return null;
124
- const normalizedEntries = entries.map(normalizeProxy).filter(Boolean);
125
- if (normalizedEntries.length === 0) return null;
126
- const config = loadProxyConfig();
127
- const existingByServer = new Map(
128
- config.proxies.map((proxy) => [String(proxy.server || '').toLowerCase(), proxy])
129
- );
130
- const seenServers = new Set();
131
- const updates = [];
132
- const additions = [];
133
-
134
- normalizedEntries.forEach((proxy) => {
135
- const serverKey = String(proxy.server || '').toLowerCase();
136
- if (!serverKey || seenServers.has(serverKey)) return;
137
- seenServers.add(serverKey);
138
- const existing = existingByServer.get(serverKey);
139
- if (existing) {
140
- updates.push({ ...existing, ...proxy, id: existing.id });
141
- } else {
142
- additions.push({ ...proxy, id: `proxy_${crypto.randomBytes(6).toString('hex')}` });
143
- }
144
- });
145
-
146
- const merged = config.proxies.map((proxy) => {
147
- const serverKey = String(proxy.server || '').toLowerCase();
148
- const replacement = updates.find((item) => String(item.server || '').toLowerCase() === serverKey);
149
- return replacement || proxy;
150
- });
151
-
152
- const proxies = [...merged, ...additions];
153
- const next = { ...config, proxies };
154
- return saveProxyConfig(next);
155
- };
156
-
157
- const updateProxy = (id, entry) => {
158
- if (!id) return null;
159
- const normalized = normalizeProxy(entry);
160
- if (!normalized) return null;
161
- const config = loadProxyConfig();
162
- const proxies = config.proxies.map((proxy) => {
163
- if (proxy.id !== id) return proxy;
164
- return { ...proxy, ...normalized, id };
165
- });
166
- if (!proxies.some((proxy) => proxy.id === id)) return null;
167
- return saveProxyConfig({ ...config, proxies });
168
- };
169
-
170
- const deleteProxy = (id) => {
171
- if (!id) return null;
172
- const config = loadProxyConfig();
173
- const proxies = config.proxies.filter((proxy) => proxy.id !== id);
174
- const defaultProxyId = config.defaultProxyId === id ? null : config.defaultProxyId;
175
- return saveProxyConfig({ ...config, proxies, defaultProxyId });
176
- };
177
-
178
- const deleteProxies = (ids) => {
179
- if (!Array.isArray(ids) || ids.length === 0) return null;
180
- const config = loadProxyConfig();
181
- const idSet = new Set(ids);
182
- const proxies = config.proxies.filter((proxy) => !idSet.has(proxy.id));
183
- const defaultProxyId = idSet.has(config.defaultProxyId) ? null : config.defaultProxyId;
184
- return saveProxyConfig({ ...config, proxies, defaultProxyId });
185
- };
186
-
187
- const setDefaultProxy = (id) => {
188
- const config = loadProxyConfig();
189
- if (!id) {
190
- return saveProxyConfig({ ...config, defaultProxyId: null });
191
- }
192
- if (!config.proxies.some((proxy) => proxy.id === id)) return null;
193
- return saveProxyConfig({ ...config, defaultProxyId: id });
194
- };
195
-
196
- const setIncludeDefaultInRotation = (enabled) => {
197
- const config = loadProxyConfig();
198
- return saveProxyConfig({ ...config, includeDefaultInRotation: !!enabled });
199
- };
200
-
201
- const setRotationMode = (mode) => {
202
- const config = loadProxyConfig();
203
- return saveProxyConfig({ ...config, rotationMode: normalizeRotationMode(mode) });
204
- };
205
-
206
- const getNextProxy = (proxies, mode) => {
207
- if (!proxies.length) return null;
208
- if (mode === 'random') {
209
- const index = Math.floor(Math.random() * proxies.length);
210
- return proxies[index];
211
- }
212
- const selected = proxies[rotationIndex % proxies.length];
213
- rotationIndex += 1;
214
- return selected;
215
- };
216
-
217
- const getProxySelection = (rotateProxies) => {
218
- const config = loadProxyConfig();
219
- const proxies = config.proxies || [];
220
- const hostEntry = { id: 'host', server: 'host_ip', label: 'Host IP (no proxy)' };
221
- const pool = [hostEntry, ...proxies];
222
- const defaultProxy = config.defaultProxyId
223
- ? proxies.find((proxy) => proxy.id === config.defaultProxyId) || null
224
- : null;
225
- const defaultIsHost = !config.defaultProxyId;
226
- const includeDefaultInRotation = !!config.includeDefaultInRotation;
227
- const rotationMode = normalizeRotationMode(config.rotationMode);
228
-
229
- if (rotateProxies) {
230
- let rotationPool = pool;
231
- if (!includeDefaultInRotation) {
232
- if (defaultIsHost) {
233
- rotationPool = pool.filter((proxy) => proxy.id !== 'host');
234
- } else {
235
- rotationPool = pool.filter((proxy) => proxy.id !== config.defaultProxyId);
236
- }
237
- }
238
- if (rotationPool.length > 0) {
239
- const picked = getNextProxy(rotationPool, rotationMode);
240
- return { proxy: picked && picked.id !== 'host' ? picked : null, mode: 'rotate' };
241
- }
242
- if (defaultProxy) return { proxy: defaultProxy, mode: 'default' };
243
- return { proxy: null, mode: 'host' };
244
- }
245
-
246
- if (defaultProxy) return { proxy: defaultProxy, mode: 'default' };
247
- return { proxy: null, mode: 'host' };
248
- };
249
-
250
- module.exports = {
251
- getProxySelection,
252
- listProxies,
253
- addProxy,
254
- addProxies,
255
- updateProxy,
256
- deleteProxy,
257
- deleteProxies,
258
- setDefaultProxy,
259
- setIncludeDefaultInRotation,
260
- setRotationMode
261
- };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const {
5
+ normalizeServer,
6
+ createProxyId,
7
+ normalizeProxy,
8
+ normalizeRotationMode
9
+ } = require('./proxy-utils');
10
+
11
+ const DATA_PROXY_FILE = path.join(__dirname, 'data', 'proxies.json');
12
+ const PROXY_FILES = [
13
+ DATA_PROXY_FILE,
14
+ path.join(__dirname, 'proxies.json')
15
+ ];
16
+
17
+ let cached = {
18
+ file: null,
19
+ mtimeMs: 0,
20
+ config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' }
21
+ };
22
+ let rotationIndex = 0;
23
+
24
+ const loadProxyFile = (filePath) => {
25
+ try {
26
+ const raw = fs.readFileSync(filePath, 'utf8');
27
+ const parsed = JSON.parse(raw);
28
+ if (Array.isArray(parsed)) {
29
+ return { proxies: parsed, defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' };
30
+ }
31
+ const proxies = Array.isArray(parsed.proxies) ? parsed.proxies : [];
32
+ const defaultProxyId = parsed.defaultProxyId || null;
33
+ const includeDefaultInRotation = !!parsed.includeDefaultInRotation;
34
+ const rotationMode = normalizeRotationMode(parsed.rotationMode);
35
+ return { proxies, defaultProxyId, includeDefaultInRotation, rotationMode };
36
+ } catch {
37
+ return { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' };
38
+ }
39
+ };
40
+
41
+ const loadProxyConfig = () => {
42
+ const filePath = PROXY_FILES.find((candidate) => {
43
+ try {
44
+ return fs.existsSync(candidate);
45
+ } catch {
46
+ return false;
47
+ }
48
+ });
49
+
50
+ if (!filePath) {
51
+ cached = { file: null, mtimeMs: 0, config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' } };
52
+ return cached.config;
53
+ }
54
+
55
+ try {
56
+ const stat = fs.statSync(filePath);
57
+ const mtimeMs = stat.mtimeMs || 0;
58
+ if (cached.file === filePath && cached.mtimeMs === mtimeMs) {
59
+ return cached.config;
60
+ }
61
+ const rawConfig = loadProxyFile(filePath);
62
+ const proxies = rawConfig.proxies.map(normalizeProxy).filter(Boolean);
63
+ const defaultProxyId = rawConfig.defaultProxyId && proxies.some((proxy) => proxy.id === rawConfig.defaultProxyId)
64
+ ? rawConfig.defaultProxyId
65
+ : null;
66
+ const config = {
67
+ proxies,
68
+ defaultProxyId,
69
+ includeDefaultInRotation: !!rawConfig.includeDefaultInRotation,
70
+ rotationMode: normalizeRotationMode(rawConfig.rotationMode)
71
+ };
72
+ cached = { file: filePath, mtimeMs, config };
73
+ return config;
74
+ } catch {
75
+ cached = { file: filePath, mtimeMs: 0, config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' } };
76
+ return cached.config;
77
+ }
78
+ };
79
+
80
+ const saveProxyConfig = (config) => {
81
+ const target = DATA_PROXY_FILE;
82
+ const payload = {
83
+ defaultProxyId: config.defaultProxyId || null,
84
+ proxies: Array.isArray(config.proxies) ? config.proxies : [],
85
+ includeDefaultInRotation: !!config.includeDefaultInRotation,
86
+ rotationMode: normalizeRotationMode(config.rotationMode)
87
+ };
88
+ fs.writeFileSync(target, JSON.stringify(payload, null, 2));
89
+ try {
90
+ const stat = fs.statSync(target);
91
+ cached = { file: target, mtimeMs: stat.mtimeMs || 0, config: payload };
92
+ } catch {
93
+ cached = { file: target, mtimeMs: 0, config: payload };
94
+ }
95
+ return payload;
96
+ };
97
+
98
+ const listProxies = () => {
99
+ const config = loadProxyConfig();
100
+ const hostEntry = {
101
+ id: 'host',
102
+ server: 'host_ip',
103
+ label: 'Host IP (no proxy)'
104
+ };
105
+ return {
106
+ proxies: [hostEntry, ...(config.proxies || [])],
107
+ defaultProxyId: config.defaultProxyId || 'host',
108
+ includeDefaultInRotation: !!config.includeDefaultInRotation,
109
+ rotationMode: normalizeRotationMode(config.rotationMode)
110
+ };
111
+ };
112
+
113
+ const addProxy = (entry) => {
114
+ const normalized = normalizeProxy(entry);
115
+ if (!normalized) return null;
116
+ const config = loadProxyConfig();
117
+ const proxies = [...config.proxies, { ...normalized, id: `proxy_${crypto.randomBytes(6).toString('hex')}` }];
118
+ const next = { ...config, proxies };
119
+ return saveProxyConfig(next);
120
+ };
121
+
122
+ const addProxies = (entries) => {
123
+ if (!Array.isArray(entries)) return null;
124
+ const normalizedEntries = entries.map(normalizeProxy).filter(Boolean);
125
+ if (normalizedEntries.length === 0) return null;
126
+ const config = loadProxyConfig();
127
+ const existingByServer = new Map(
128
+ config.proxies.map((proxy) => [String(proxy.server || '').toLowerCase(), proxy])
129
+ );
130
+ const seenServers = new Set();
131
+ const updates = [];
132
+ const additions = [];
133
+
134
+ normalizedEntries.forEach((proxy) => {
135
+ const serverKey = String(proxy.server || '').toLowerCase();
136
+ if (!serverKey || seenServers.has(serverKey)) return;
137
+ seenServers.add(serverKey);
138
+ const existing = existingByServer.get(serverKey);
139
+ if (existing) {
140
+ updates.push({ ...existing, ...proxy, id: existing.id });
141
+ } else {
142
+ additions.push({ ...proxy, id: `proxy_${crypto.randomBytes(6).toString('hex')}` });
143
+ }
144
+ });
145
+
146
+ const merged = config.proxies.map((proxy) => {
147
+ const serverKey = String(proxy.server || '').toLowerCase();
148
+ const replacement = updates.find((item) => String(item.server || '').toLowerCase() === serverKey);
149
+ return replacement || proxy;
150
+ });
151
+
152
+ const proxies = [...merged, ...additions];
153
+ const next = { ...config, proxies };
154
+ return saveProxyConfig(next);
155
+ };
156
+
157
+ const updateProxy = (id, entry) => {
158
+ if (!id) return null;
159
+ const normalized = normalizeProxy(entry);
160
+ if (!normalized) return null;
161
+ const config = loadProxyConfig();
162
+ const proxies = config.proxies.map((proxy) => {
163
+ if (proxy.id !== id) return proxy;
164
+ return { ...proxy, ...normalized, id };
165
+ });
166
+ if (!proxies.some((proxy) => proxy.id === id)) return null;
167
+ return saveProxyConfig({ ...config, proxies });
168
+ };
169
+
170
+ const deleteProxy = (id) => {
171
+ if (!id) return null;
172
+ const config = loadProxyConfig();
173
+ const proxies = config.proxies.filter((proxy) => proxy.id !== id);
174
+ const defaultProxyId = config.defaultProxyId === id ? null : config.defaultProxyId;
175
+ return saveProxyConfig({ ...config, proxies, defaultProxyId });
176
+ };
177
+
178
+ const deleteProxies = (ids) => {
179
+ if (!Array.isArray(ids) || ids.length === 0) return null;
180
+ const config = loadProxyConfig();
181
+ const idSet = new Set(ids);
182
+ const proxies = config.proxies.filter((proxy) => !idSet.has(proxy.id));
183
+ const defaultProxyId = idSet.has(config.defaultProxyId) ? null : config.defaultProxyId;
184
+ return saveProxyConfig({ ...config, proxies, defaultProxyId });
185
+ };
186
+
187
+ const setDefaultProxy = (id) => {
188
+ const config = loadProxyConfig();
189
+ if (!id) {
190
+ return saveProxyConfig({ ...config, defaultProxyId: null });
191
+ }
192
+ if (!config.proxies.some((proxy) => proxy.id === id)) return null;
193
+ return saveProxyConfig({ ...config, defaultProxyId: id });
194
+ };
195
+
196
+ const setIncludeDefaultInRotation = (enabled) => {
197
+ const config = loadProxyConfig();
198
+ return saveProxyConfig({ ...config, includeDefaultInRotation: !!enabled });
199
+ };
200
+
201
+ const setRotationMode = (mode) => {
202
+ const config = loadProxyConfig();
203
+ return saveProxyConfig({ ...config, rotationMode: normalizeRotationMode(mode) });
204
+ };
205
+
206
+ const getNextProxy = (proxies, mode) => {
207
+ if (!proxies.length) return null;
208
+ if (mode === 'random') {
209
+ const index = Math.floor(Math.random() * proxies.length);
210
+ return proxies[index];
211
+ }
212
+ const selected = proxies[rotationIndex % proxies.length];
213
+ rotationIndex += 1;
214
+ return selected;
215
+ };
216
+
217
+ const getProxySelection = (rotateProxies) => {
218
+ const config = loadProxyConfig();
219
+ const proxies = config.proxies || [];
220
+ const hostEntry = { id: 'host', server: 'host_ip', label: 'Host IP (no proxy)' };
221
+ const pool = [hostEntry, ...proxies];
222
+ const defaultProxy = config.defaultProxyId
223
+ ? proxies.find((proxy) => proxy.id === config.defaultProxyId) || null
224
+ : null;
225
+ const defaultIsHost = !config.defaultProxyId;
226
+ const includeDefaultInRotation = !!config.includeDefaultInRotation;
227
+ const rotationMode = normalizeRotationMode(config.rotationMode);
228
+
229
+ if (rotateProxies) {
230
+ let rotationPool = pool;
231
+ if (!includeDefaultInRotation) {
232
+ if (defaultIsHost) {
233
+ rotationPool = pool.filter((proxy) => proxy.id !== 'host');
234
+ } else {
235
+ rotationPool = pool.filter((proxy) => proxy.id !== config.defaultProxyId);
236
+ }
237
+ }
238
+ if (rotationPool.length > 0) {
239
+ const picked = getNextProxy(rotationPool, rotationMode);
240
+ return { proxy: picked && picked.id !== 'host' ? picked : null, mode: 'rotate' };
241
+ }
242
+ if (defaultProxy) return { proxy: defaultProxy, mode: 'default' };
243
+ return { proxy: null, mode: 'host' };
244
+ }
245
+
246
+ if (defaultProxy) return { proxy: defaultProxy, mode: 'default' };
247
+ return { proxy: null, mode: 'host' };
248
+ };
249
+
250
+ module.exports = {
251
+ getProxySelection,
252
+ listProxies,
253
+ addProxy,
254
+ addProxies,
255
+ updateProxy,
256
+ deleteProxy,
257
+ deleteProxies,
258
+ setDefaultProxy,
259
+ setIncludeDefaultInRotation,
260
+ setRotationMode
261
+ };