bulltrackers-module 1.0.170 → 1.0.172
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/functions/computation-system/controllers/computation_controller.js +215 -0
- package/functions/computation-system/helpers/computation_pass_runner.js +10 -2
- package/functions/computation-system/helpers/orchestration_helpers.js +191 -652
- package/functions/computation-system/layers/math_primitives.js +346 -0
- package/functions/task-engine/helpers/discover_helpers.js +33 -51
- package/functions/task-engine/helpers/update_helpers.js +57 -159
- package/functions/task-engine/helpers/verify_helpers.js +27 -44
- package/package.json +1 -5
|
@@ -15,87 +15,40 @@ async function lookupUsernames(cids, { logger, headerManager, proxyManager }, co
|
|
|
15
15
|
if (!cids?.length) return [];
|
|
16
16
|
logger.log('INFO', `[lookupUsernames] Looking up usernames for ${cids.length} CIDs.`);
|
|
17
17
|
|
|
18
|
-
// ---
|
|
18
|
+
// --- Set concurrency to 1 because appscript gets really fucked up with undocumented rate limits if we try spam it concurrently, a shame but that's life. DO NOT CHANGE THIS
|
|
19
19
|
const limit = pLimit(1);
|
|
20
|
-
// --- END REFACTOR 1 ---
|
|
21
|
-
|
|
22
20
|
const { USERNAME_LOOKUP_BATCH_SIZE, ETORO_API_RANKINGS_URL } = config;
|
|
23
|
-
|
|
24
21
|
const batches = [];
|
|
25
|
-
for (let i = 0; i < cids.length; i += USERNAME_LOOKUP_BATCH_SIZE) {
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const batchPromises = batches.map((batch, index) => limit(async () => {
|
|
30
|
-
const batchId = `batch-${index + 1}`;
|
|
22
|
+
for (let i = 0; i < cids.length; i += USERNAME_LOOKUP_BATCH_SIZE) { batches.push(cids.slice(i, i + USERNAME_LOOKUP_BATCH_SIZE).map(Number)); }
|
|
23
|
+
const batchPromises = batches.map((batch, index) => limit(async () => { const batchId = `batch-${index + 1}`;
|
|
31
24
|
logger.log('INFO', `[lookupUsernames/${batchId}] Processing batch of ${batch.length} CIDs...`);
|
|
32
|
-
|
|
33
25
|
const header = await headerManager.selectHeader();
|
|
34
|
-
if (!header) {
|
|
35
|
-
logger.log('ERROR', `[lookupUsernames/${batchId}] Could not select a header.`);
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
26
|
+
if (!header) { logger.log('ERROR', `[lookupUsernames/${batchId}] Could not select a header.`); return null; }
|
|
39
27
|
let wasSuccess = false;
|
|
40
28
|
let proxyUsed = true;
|
|
41
29
|
let response;
|
|
42
30
|
const url = `${ETORO_API_RANKINGS_URL}?Period=LastTwoYears`;
|
|
43
31
|
const options = { method: 'POST', headers: { ...header.header, 'Content-Type': 'application/json' }, body: JSON.stringify(batch) };
|
|
44
|
-
|
|
45
32
|
try {
|
|
46
|
-
// --- REFACTOR 3: ADD FALLBACK ---
|
|
47
33
|
logger.log('TRACE', `[lookupUsernames/${batchId}] Attempting fetch via AppScript proxy...`);
|
|
48
34
|
response = await proxyManager.fetch(url, options);
|
|
49
35
|
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`);
|
|
50
|
-
|
|
51
|
-
wasSuccess = true;
|
|
36
|
+
wasSuccess = true; // Yay we win
|
|
52
37
|
logger.log('INFO', `[lookupUsernames/${batchId}] AppScript proxy fetch successful.`);
|
|
53
|
-
|
|
54
|
-
} catch (proxyError) {
|
|
55
|
-
logger.log('WARN', `[lookupUsernames/${batchId}] AppScript proxy fetch FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, {
|
|
56
|
-
error: proxyError.message,
|
|
57
|
-
source: 'AppScript'
|
|
58
|
-
});
|
|
59
|
-
|
|
38
|
+
} catch (proxyError) { logger.log('WARN', `[lookupUsernames/${batchId}] AppScript proxy fetch FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, { error: proxyError.message, source: 'AppScript' }); // SHIT we failed...
|
|
60
39
|
proxyUsed = false; // Don't penalize header for proxy failure
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const errorText = await response.text();
|
|
66
|
-
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`);
|
|
67
|
-
}
|
|
68
|
-
logger.log('INFO', `[lookupUsernames/${batchId}] Direct node-fetch fallback successful.`);
|
|
69
|
-
|
|
70
|
-
} catch (fallbackError) {
|
|
71
|
-
logger.log('ERROR', `[lookupUsernames/${batchId}] Direct node-fetch fallback FAILED. Giving up on this batch.`, {
|
|
72
|
-
error: fallbackError.message,
|
|
73
|
-
source: 'eToro/Network'
|
|
74
|
-
});
|
|
40
|
+
try { response = await fetch(url, options); // Ok let's try again with node, using GCP IP pools
|
|
41
|
+
if (!response.ok) { const errorText = await response.text(); throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`); }
|
|
42
|
+
logger.log('INFO', `[lookupUsernames/${batchId}] Direct node-fetch fallback successful.`); // Yay we win
|
|
43
|
+
} catch (fallbackError) { logger.log('ERROR', `[lookupUsernames/${batchId}] Direct node-fetch fallback FAILED. Giving up on this batch.`, { error: fallbackError.message, source: 'eToro/Network' }); // SHIT, we failed here too
|
|
75
44
|
return null; // Give up on this batch
|
|
76
45
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
headerManager.updatePerformance(header.id, wasSuccess);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const data = await response.json();
|
|
86
|
-
return data;
|
|
87
|
-
} catch (parseError) {
|
|
88
|
-
logger.log('ERROR', `[lookupUsernames/${batchId}] Failed to parse JSON response.`, { error: parseError.message });
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
}));
|
|
92
|
-
|
|
93
|
-
const results = await Promise.allSettled(batchPromises);
|
|
94
|
-
|
|
95
|
-
const allUsers = results
|
|
96
|
-
.filter(r => r.status === 'fulfilled' && r.value && Array.isArray(r.value))
|
|
97
|
-
.flatMap(r => r.value);
|
|
46
|
+
} finally { if (proxyUsed) { headerManager.updatePerformance(header.id, wasSuccess); } } // If we used Appscript IP Pool and not GCP IP Pool, record performance
|
|
47
|
+
try { const data = await response.json(); return data;
|
|
48
|
+
} catch (parseError) { logger.log('ERROR', `[lookupUsernames/${batchId}] Failed to parse JSON response.`, { error: parseError.message }); return null; } }));
|
|
98
49
|
|
|
50
|
+
const results = await Promise.allSettled(batchPromises);
|
|
51
|
+
const allUsers = results .filter(r => r.status === 'fulfilled' && r.value && Array.isArray(r.value)) .flatMap(r => r.value);
|
|
99
52
|
logger.log('INFO', `[lookupUsernames] Found ${allUsers.length} public users out of ${cids.length}.`);
|
|
100
53
|
return allUsers;
|
|
101
54
|
}
|
|
@@ -110,8 +63,6 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
110
63
|
const today = new Date().toISOString().slice(0, 10);
|
|
111
64
|
const portfolioBlockId = `${Math.floor(parseInt(userId) / 1000000)}M`;
|
|
112
65
|
let isPrivate = false;
|
|
113
|
-
|
|
114
|
-
// --- REFACTOR 2: ADD VERBOSE LOGGING (with User ID) ---
|
|
115
66
|
logger.log('INFO', `[handleUpdate/${userId}] Starting update task. Type: ${userType}. Instruments: ${instrumentsToProcess.join(', ')}`);
|
|
116
67
|
|
|
117
68
|
// --- 1. Process History Fetch (Sequentially) ---
|
|
@@ -123,81 +74,52 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
123
74
|
if (!batchManager.checkAndSetHistoryFetched(userId)) {
|
|
124
75
|
logger.log('INFO', `[handleUpdate/${userId}] Attempting history fetch.`);
|
|
125
76
|
historyHeader = await headerManager.selectHeader();
|
|
126
|
-
if (!historyHeader) {
|
|
127
|
-
logger.log('WARN', `[handleUpdate/${userId}] Could not select history header. Skipping history.`);
|
|
77
|
+
if (!historyHeader) { logger.log('WARN', `[handleUpdate/${userId}] Could not select history header. Skipping history.`);
|
|
128
78
|
} else {
|
|
129
79
|
const historyUrl = `${config.ETORO_API_USERSTATS_URL}${username}/trades/oneYearAgo?CopyAsAsset=true`;
|
|
130
80
|
const options = { headers: historyHeader.header };
|
|
131
81
|
let response;
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
// --- REFACTOR 3: ADD FALLBACK ---
|
|
135
|
-
logger.log('TRACE', `[handleUpdate/${userId}] Attempting history fetch via AppScript proxy...`);
|
|
82
|
+
try { logger.log('TRACE', `[handleUpdate/${userId}] Attempting history fetch via AppScript proxy...`);
|
|
136
83
|
response = await proxyManager.fetch(historyUrl, options);
|
|
137
|
-
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`);
|
|
138
|
-
wasHistorySuccess = true;
|
|
84
|
+
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`); // SHIT we failed here
|
|
85
|
+
wasHistorySuccess = true; // Appscript worked, we are very smart
|
|
139
86
|
|
|
140
87
|
} catch (proxyError) {
|
|
141
|
-
logger.log('WARN', `[handleUpdate/${userId}] History fetch via AppScript proxy FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, {
|
|
142
|
-
error: proxyError.message,
|
|
143
|
-
source: 'AppScript'
|
|
144
|
-
});
|
|
88
|
+
logger.log('WARN', `[handleUpdate/${userId}] History fetch via AppScript proxy FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, { error: proxyError.message, source: 'AppScript' }); // SHIT we failed here
|
|
145
89
|
proxyUsedForHistory = false;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const errorText = await response.text();
|
|
151
|
-
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`);
|
|
152
|
-
}
|
|
153
|
-
wasHistorySuccess = true; // Fallback succeeded
|
|
90
|
+
try { response = await fetch(historyUrl, options);
|
|
91
|
+
if (!response.ok) { const errorText = await response.text();
|
|
92
|
+
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`); } // SHIT we failed here too
|
|
93
|
+
wasHistorySuccess = true; // Fallback succeeded, we are so smart
|
|
154
94
|
|
|
155
|
-
} catch (fallbackError) {
|
|
156
|
-
|
|
157
|
-
error: fallbackError.message,
|
|
158
|
-
source: 'eToro/Network'
|
|
159
|
-
});
|
|
160
|
-
wasHistorySuccess = false;
|
|
95
|
+
} catch (fallbackError) { logger.log('ERROR', `[handleUpdate/${userId}] History fetch direct fallback FAILED.`, { error: fallbackError.message, source: 'eToro/Network' }); // We are dumb, everything failed
|
|
96
|
+
wasHistorySuccess = false; // Nope we are dumb....
|
|
161
97
|
}
|
|
162
|
-
// --- END REFACTOR 3 ---
|
|
163
98
|
}
|
|
164
99
|
|
|
165
|
-
if (wasHistorySuccess) {
|
|
166
|
-
logger.log('INFO', `[handleUpdate/${userId}] History fetch successful.`);
|
|
100
|
+
if (wasHistorySuccess) { logger.log('INFO', `[handleUpdate/${userId}] History fetch successful.`); // Some method worked, we are very smart
|
|
167
101
|
const data = await response.json();
|
|
168
|
-
await batchManager.addToTradingHistoryBatch(userId, portfolioBlockId, today, data, userType);
|
|
169
|
-
}
|
|
102
|
+
await batchManager.addToTradingHistoryBatch(userId, portfolioBlockId, today, data, userType); }
|
|
170
103
|
}
|
|
171
|
-
} else {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
} catch (err) {
|
|
175
|
-
logger.log('ERROR', `[handleUpdate/${userId}] Unhandled error during history processing.`, { error: err.message });
|
|
176
|
-
wasHistorySuccess = false;
|
|
177
|
-
} finally {
|
|
178
|
-
if (historyHeader && proxyUsedForHistory) {
|
|
179
|
-
headerManager.updatePerformance(historyHeader.id, wasHistorySuccess);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
104
|
+
} else { logger.log('TRACE', `[handleUpdate/${userId}] History fetch skipped (already fetched by this instance).`); }
|
|
105
|
+
} catch (err) { logger.log('ERROR', `[handleUpdate/${userId}] Unhandled error during history processing.`, { error: err.message }); wasHistorySuccess = false; // We fucked up.
|
|
106
|
+
} finally { if (historyHeader && proxyUsedForHistory) { headerManager.updatePerformance(historyHeader.id, wasHistorySuccess); } } // If we used appscript proxy, record performance, otherwise fuck off.
|
|
182
107
|
|
|
183
108
|
// --- 2. Process Portfolio Fetches (Sequentially) ---
|
|
184
109
|
logger.log('INFO', `[handleUpdate/${userId}] Starting ${instrumentsToProcess.length} sequential portfolio fetches.`);
|
|
185
110
|
|
|
186
|
-
for (const instId of instrumentsToProcess) {
|
|
187
|
-
if (isPrivate) {
|
|
188
|
-
|
|
189
|
-
|
|
111
|
+
for (const instId of instrumentsToProcess) {
|
|
112
|
+
if (isPrivate) {
|
|
113
|
+
logger.log('INFO', `[handleUpdate/${userId}] Skipping remaining instruments because user was marked as private.`);
|
|
114
|
+
break;
|
|
190
115
|
}
|
|
191
116
|
|
|
192
117
|
const portfolioHeader = await headerManager.selectHeader();
|
|
193
|
-
if (!portfolioHeader) {
|
|
194
|
-
logger.log('ERROR', `[handleUpdate/${userId}] Could not select portfolio header for instId ${instId}. Skipping this instrument.`);
|
|
118
|
+
if (!portfolioHeader) { logger.log('ERROR', `[handleUpdate/${userId}] Could not select portfolio header for instId ${instId}. Skipping this instrument.`);
|
|
195
119
|
continue;
|
|
196
120
|
}
|
|
197
121
|
|
|
198
|
-
const portfolioUrl = userType === 'speculator'
|
|
199
|
-
? `${config.ETORO_API_POSITIONS_URL}?cid=${userId}&InstrumentID=${instId}`
|
|
200
|
-
: `${config.ETORO_API_PORTFOLIO_URL}?cid=${userId}`;
|
|
122
|
+
const portfolioUrl = userType === 'speculator' ? `${config.ETORO_API_POSITIONS_URL}?cid=${userId}&InstrumentID=${instId}` : `${config.ETORO_API_PORTFOLIO_URL}?cid=${userId}`;
|
|
201
123
|
|
|
202
124
|
const options = { headers: portfolioHeader.header };
|
|
203
125
|
let response;
|
|
@@ -208,73 +130,53 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
208
130
|
// --- REFACTOR 3: ADD FALLBACK ---
|
|
209
131
|
logger.log('TRACE', `[handleUpdate/${userId}] Attempting portfolio fetch for instId ${instId} via AppScript proxy...`);
|
|
210
132
|
response = await proxyManager.fetch(portfolioUrl, options);
|
|
211
|
-
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`);
|
|
212
|
-
wasPortfolioSuccess = true;
|
|
133
|
+
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`); // SHIT we failed here
|
|
134
|
+
wasPortfolioSuccess = true; // Oh we are smart, worked first time.
|
|
213
135
|
|
|
214
|
-
} catch (proxyError) {
|
|
215
|
-
logger.log('WARN', `[handleUpdate/${userId}] Portfolio fetch for instId ${instId} via AppScript proxy FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, {
|
|
216
|
-
|
|
217
|
-
source: 'AppScript'
|
|
218
|
-
});
|
|
219
|
-
proxyUsedForPortfolio = false;
|
|
136
|
+
} catch (proxyError) { // try fallback with local node fetch using GCP IP Pools
|
|
137
|
+
logger.log('WARN', `[handleUpdate/${userId}] Portfolio fetch for instId ${instId} via AppScript proxy FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, { error: proxyError.message, source: 'AppScript' });
|
|
138
|
+
proxyUsedForPortfolio = false; // We are not using Appscript proxy here as fallback is GCP based, so false
|
|
220
139
|
|
|
221
140
|
try {
|
|
222
141
|
response = await fetch(portfolioUrl, options); // Direct node-fetch
|
|
223
142
|
if (!response.ok) {
|
|
224
143
|
const errorText = await response.text();
|
|
225
|
-
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`);
|
|
144
|
+
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`); // SHIT we failed here
|
|
226
145
|
}
|
|
227
|
-
wasPortfolioSuccess = true; // Fallback succeeded
|
|
146
|
+
wasPortfolioSuccess = true; // Fallback succeeded we are so smart
|
|
228
147
|
|
|
229
148
|
} catch (fallbackError) {
|
|
230
|
-
logger.log('ERROR', `[handleUpdate/${userId}] Portfolio fetch for instId ${instId} direct fallback FAILED.`, {
|
|
231
|
-
error: fallbackError.message,
|
|
232
|
-
source: 'eToro/Network'
|
|
233
|
-
});
|
|
149
|
+
logger.log('ERROR', `[handleUpdate/${userId}] Portfolio fetch for instId ${instId} direct fallback FAILED.`, { error: fallbackError.message, source: 'eToro/Network' });
|
|
234
150
|
wasPortfolioSuccess = false;
|
|
235
151
|
}
|
|
236
|
-
// --- END REFACTOR 3 ---
|
|
237
152
|
}
|
|
238
153
|
|
|
239
154
|
// --- 4. Process Portfolio Result (with verbose, raw logging) ---
|
|
240
155
|
if (wasPortfolioSuccess) {
|
|
241
156
|
const body = await response.text();
|
|
242
|
-
if (body.includes("user is PRIVATE")) {
|
|
243
|
-
isPrivate = true;
|
|
244
|
-
logger.log('WARN', `[handleUpdate/${userId}] User is PRIVATE. Marking for removal.`);
|
|
157
|
+
if (body.includes("user is PRIVATE")) { isPrivate = true; logger.log('WARN', `[handleUpdate/${userId}] User is PRIVATE. Marking for removal.`);
|
|
245
158
|
break; // Stop processing more portfolios for this private user
|
|
246
159
|
}
|
|
247
160
|
|
|
248
161
|
try {
|
|
249
162
|
const portfolioJson = JSON.parse(body);
|
|
250
163
|
await batchManager.addToPortfolioBatch(userId, portfolioBlockId, today, portfolioJson, userType, instId);
|
|
251
|
-
logger.log('INFO', `[handleUpdate/${userId}] Successfully processed portfolio for instId ${instId}.`);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
164
|
+
if (userType === 'speculator') { logger.log('INFO', `[handleUpdate/${userId}] Successfully processed portfolio for instId ${instId}.`); // Only speculators have an instid, so this is conditional
|
|
165
|
+
} else { logger.log('INFO', `[handleUpdate/${userId}] Successfully processed full portfolio (normal user).`); } // Normal users
|
|
166
|
+
|
|
167
|
+
} catch (parseError) { // Idk why this would happen, but if it does....log.
|
|
255
168
|
wasPortfolioSuccess = false; // Mark as failure
|
|
256
|
-
logger.log('ERROR', `[handleUpdate/${userId}] FAILED TO PARSE JSON RESPONSE. RAW BODY:`, {
|
|
257
|
-
url: portfolioUrl,
|
|
258
|
-
parseErrorMessage: parseError.message,
|
|
259
|
-
rawResponseText: body // <--- THIS LOGS THE FULL HTML/ERROR RESPONSE
|
|
260
|
-
});
|
|
261
|
-
// --- END REFACTOR 4 ---
|
|
169
|
+
logger.log('ERROR', `[handleUpdate/${userId}] FAILED TO PARSE JSON RESPONSE. RAW BODY:`, { url: portfolioUrl, parseErrorMessage: parseError.message, rawResponseText: body }); // Return full response
|
|
262
170
|
}
|
|
263
|
-
} else {
|
|
264
|
-
logger.log('WARN', `[handleUpdate/${userId}] Portfolio fetch failed for instId ${instId}. No response to process.`);
|
|
265
|
-
}
|
|
171
|
+
} else { logger.log('WARN', `[handleUpdate/${userId}] Portfolio fetch failed for instId ${instId}. No response to process.`); }
|
|
266
172
|
|
|
267
|
-
if (proxyUsedForPortfolio) {
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
} // --- End of sequential portfolio loop ---
|
|
173
|
+
if (proxyUsedForPortfolio) { headerManager.updatePerformance(portfolioHeader.id, wasPortfolioSuccess); }
|
|
174
|
+
}
|
|
271
175
|
|
|
272
176
|
// --- 5. Handle Private Users & Timestamps ---
|
|
273
177
|
if (isPrivate) {
|
|
274
178
|
logger.log('WARN', `[handleUpdate/${userId}] Removing private user from updates.`);
|
|
275
|
-
for (const instrumentId of instrumentsToProcess) {
|
|
276
|
-
await batchManager.deleteFromTimestampBatch(userId, userType, instId);
|
|
277
|
-
}
|
|
179
|
+
for (const instrumentId of instrumentsToProcess) { await batchManager.deleteFromTimestampBatch(userId, userType, instId); }
|
|
278
180
|
const blockCountsRef = db.doc(config.FIRESTORE_DOC_SPECULATOR_BLOCK_COUNTS);
|
|
279
181
|
for (const instrumentId of instrumentsToProcess) {
|
|
280
182
|
const incrementField = `counts.${instrumentId}_${Math.floor(userId/1e6)*1e6}`;
|
|
@@ -284,12 +186,8 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
284
186
|
}
|
|
285
187
|
|
|
286
188
|
// If not private, update all timestamps
|
|
287
|
-
for (const instrumentId of instrumentsToProcess) {
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
if (userType === 'speculator') {
|
|
291
|
-
await batchManager.addSpeculatorTimestampFix(userId, String(Math.floor(userId/1e6)*1e6));
|
|
292
|
-
}
|
|
189
|
+
for (const instrumentId of instrumentsToProcess) { await batchManager.updateUserTimestamp(userId, userType, instId); }
|
|
190
|
+
if (userType === 'speculator') { await batchManager.addSpeculatorTimestampFix(userId, String(Math.floor(userId/1e6)*1e6)); }
|
|
293
191
|
|
|
294
192
|
logger.log('INFO', `[handleUpdate/${userId}] Update task finished successfully.`);
|
|
295
193
|
// 'finally' block for header flushing is handled by the main handler_creator.js
|
|
@@ -14,13 +14,10 @@ async function fetchAndVerifyUser(user, { logger, headerManager, proxyManager },
|
|
|
14
14
|
const logPrefix = `[VERIFY/${taskId}/${cid}]`; // --- REFACTOR 2: VERBOSE LOGGING ---
|
|
15
15
|
|
|
16
16
|
const selectedHeader = await headerManager.selectHeader();
|
|
17
|
-
if (!selectedHeader) {
|
|
18
|
-
logger.log('WARN', `${logPrefix} Could not select a header. Skipping user.`);
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
17
|
+
if (!selectedHeader) { logger.log('WARN', `${logPrefix} Could not select a header. Skipping user.`); return null; }
|
|
21
18
|
|
|
22
19
|
let wasSuccess = false;
|
|
23
|
-
let proxyUsed
|
|
20
|
+
let proxyUsed = true;
|
|
24
21
|
|
|
25
22
|
try { // Outer try for the whole operation
|
|
26
23
|
let response;
|
|
@@ -30,30 +27,24 @@ async function fetchAndVerifyUser(user, { logger, headerManager, proxyManager },
|
|
|
30
27
|
try {
|
|
31
28
|
// --- REFACTOR 3: ADD FALLBACK ---
|
|
32
29
|
logger.log('TRACE', `${logPrefix} Attempting portfolio fetch via AppScript proxy...`);
|
|
33
|
-
response = await proxyManager.fetch(url, options);
|
|
34
|
-
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`);
|
|
30
|
+
response = await proxyManager.fetch(url, options); // Try....
|
|
31
|
+
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`); // Fail
|
|
35
32
|
|
|
36
|
-
} catch (proxyError) {
|
|
37
|
-
logger.log('WARN', `${logPrefix} AppScript proxy fetch FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, {
|
|
38
|
-
error: proxyError.message,
|
|
39
|
-
source: 'AppScript'
|
|
40
|
-
});
|
|
33
|
+
} catch (proxyError) { // We fucked up, appscript proxy failed, log error type
|
|
34
|
+
logger.log('WARN', `${logPrefix} AppScript proxy fetch FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, { error: proxyError.message, source: 'AppScript' });
|
|
41
35
|
proxyUsed = false;
|
|
42
36
|
|
|
43
37
|
try {
|
|
44
|
-
response = await fetch(url, options); // Direct node-fetch
|
|
45
|
-
if (!response.ok) {
|
|
38
|
+
response = await fetch(url, options); // Direct node-fetch as fallback
|
|
39
|
+
if (!response.ok) { // we fucked up here too, log error text
|
|
46
40
|
const errorText = await response.text();
|
|
47
41
|
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`);
|
|
48
42
|
}
|
|
49
43
|
// Fallback succeeded, but we don't set wasSuccess yet,
|
|
50
44
|
// as we still need to parse the body.
|
|
51
45
|
|
|
52
|
-
} catch (fallbackError) {
|
|
53
|
-
logger.log('ERROR', `${logPrefix} Direct node-fetch fallback FAILED.`, {
|
|
54
|
-
error: fallbackError.message,
|
|
55
|
-
source: 'eToro/Network'
|
|
56
|
-
});
|
|
46
|
+
} catch (fallbackError) { // log error type for fallback
|
|
47
|
+
logger.log('ERROR', `${logPrefix} Direct node-fetch fallback FAILED.`, { error: fallbackError.message, source: 'eToro/Network' });
|
|
57
48
|
throw fallbackError; // Throw to be caught by outer try
|
|
58
49
|
}
|
|
59
50
|
// --- END REFACTOR 3 ---
|
|
@@ -62,46 +53,38 @@ async function fetchAndVerifyUser(user, { logger, headerManager, proxyManager },
|
|
|
62
53
|
// --- If we are here, `response` is valid ---
|
|
63
54
|
let portfolioData;
|
|
64
55
|
const body = await response.text();
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} catch (parseError) {
|
|
70
|
-
logger.log('ERROR', `${logPrefix} FAILED TO PARSE JSON RESPONSE. RAW BODY:`, {
|
|
71
|
-
parseErrorMessage: parseError.message,
|
|
72
|
-
rawResponseText: body
|
|
73
|
-
});
|
|
56
|
+
// --- REFACTOR 4: LOG RAW RESPONSE ON PARSE FAILURE ---
|
|
57
|
+
try { portfolioData = JSON.parse(body);
|
|
58
|
+
} catch (parseError) { // If we can't parse JSON, try raw log, shouldn't happen but this world is weird....
|
|
59
|
+
logger.log('ERROR', `${logPrefix} FAILED TO PARSE JSON RESPONSE. RAW BODY:`, { parseErrorMessage: parseError.message, rawResponseText: body });
|
|
74
60
|
throw new Error(`Failed to parse JSON for user ${cid}.`);
|
|
75
61
|
}
|
|
76
62
|
// --- END REFACTOR 4 ---
|
|
77
63
|
|
|
78
64
|
// --- Original logic ---
|
|
79
65
|
if (userType === 'speculator') {
|
|
80
|
-
const instruments = portfolioData.AggregatedPositions.map(p => p.InstrumentID).filter(id => SPECULATOR_INSTRUMENTS_ARRAY.includes(id));
|
|
66
|
+
const instruments = portfolioData.AggregatedPositions.map(p => p.InstrumentID).filter(id => SPECULATOR_INSTRUMENTS_ARRAY.includes(id));
|
|
81
67
|
if (!instruments.length) {
|
|
82
68
|
logger.log('TRACE', `${logPrefix} Verified user, but not a speculator (no matching assets).`);
|
|
83
|
-
wasSuccess = true; // API call *worked*
|
|
69
|
+
wasSuccess = true; // API call *worked* we are so smart
|
|
84
70
|
return null;
|
|
85
71
|
}
|
|
86
|
-
logger.log('INFO', `${logPrefix} Verified as SPECULATOR.`);
|
|
87
|
-
wasSuccess = true;
|
|
88
|
-
return { type: 'speculator', userId: cid, isBronze: user.isBronze, username: user.username, updateData: { instruments, lastVerified: new Date(), lastHeldSpeculatorAsset: new Date() } };
|
|
72
|
+
logger.log('INFO', `${logPrefix} Verified as SPECULATOR.`);
|
|
73
|
+
wasSuccess = true; // Mark that we are very high IQ
|
|
74
|
+
return { type: 'speculator', userId: cid, isBronze: user.isBronze, username: user.username, updateData: { instruments, lastVerified: new Date(), lastHeldSpeculatorAsset: new Date() } }; // Return values
|
|
89
75
|
}
|
|
90
|
-
|
|
91
|
-
logger.log('INFO', `${logPrefix} Verified as NORMAL user.`);
|
|
92
|
-
wasSuccess = true;
|
|
93
|
-
return { type: 'normal', userId: cid, isBronze: user.isBronze, username: user.username, updateData: { lastVerified: new Date() } };
|
|
76
|
+
// Other user types are just normal users....
|
|
77
|
+
logger.log('INFO', `${logPrefix} Verified as NORMAL user.`);
|
|
78
|
+
wasSuccess = true; // We are smart, this worked
|
|
79
|
+
return { type: 'normal', userId: cid, isBronze: user.isBronze, username: user.username, updateData: { lastVerified: new Date() } }; // Return values
|
|
94
80
|
|
|
95
81
|
} catch (err) {
|
|
96
82
|
// This catches proxy, fallback, or parse errors
|
|
97
|
-
logger.log('WARN', `${logPrefix} Error processing user.`, { errorMessage: err.message });
|
|
83
|
+
logger.log('WARN', `${logPrefix} Error processing user.`, { errorMessage: err.message }); // Some shit broke
|
|
98
84
|
wasSuccess = false; // Ensure it's marked as failure
|
|
99
85
|
return null;
|
|
100
86
|
} finally {
|
|
101
|
-
if (selectedHeader && proxyUsed) {
|
|
102
|
-
// Only update performance if the proxy was used
|
|
103
|
-
headerManager.updatePerformance(selectedHeader.id, wasSuccess);
|
|
104
|
-
}
|
|
87
|
+
if (selectedHeader && proxyUsed) { headerManager.updatePerformance(selectedHeader.id, wasSuccess); } // Only update performance if the proxy was used
|
|
105
88
|
}
|
|
106
89
|
}
|
|
107
90
|
|
|
@@ -118,7 +101,7 @@ async function handleVerify(task, taskId, { db, logger, ...dependencies }, confi
|
|
|
118
101
|
logger.log('INFO', `[VERIFY/${taskId}] Starting sequential verification for ${users.length} users...`);
|
|
119
102
|
const results = [];
|
|
120
103
|
for (const user of users) {
|
|
121
|
-
// Await each user one by one
|
|
104
|
+
// Await each user one by one otherwise appscript starts to cry and internally throttle...then rate limit...then fuck everything up and break and then i want to die.
|
|
122
105
|
const result = await fetchAndVerifyUser(user, { db, logger, ...dependencies }, { ...config, userType }, taskId);
|
|
123
106
|
results.push(result); // Push the actual result (or null)
|
|
124
107
|
}
|
|
@@ -136,7 +119,7 @@ async function handleVerify(task, taskId, { db, logger, ...dependencies }, confi
|
|
|
136
119
|
else normalUpdates[`users.${d.userId}`] = d.updateData;
|
|
137
120
|
}
|
|
138
121
|
});
|
|
139
|
-
|
|
122
|
+
// Process responses in object keys
|
|
140
123
|
if (Object.keys(speculatorUpdates).length || Object.keys(normalUpdates).length) {
|
|
141
124
|
const blockRef = db.collection(userType === 'speculator' ? config.FIRESTORE_COLLECTION_SPECULATOR_BLOCKS : config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS).doc(String(blockId));
|
|
142
125
|
batch.set(blockRef, userType === 'speculator' ? speculatorUpdates : normalUpdates, { merge: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bulltrackers-module",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.172",
|
|
4
4
|
"description": "Helper Functions for Bulltrackers.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -27,10 +27,6 @@
|
|
|
27
27
|
"calculations",
|
|
28
28
|
"finance"
|
|
29
29
|
],
|
|
30
|
-
"scripts": {
|
|
31
|
-
"postpublish": "node ./auto-deploy.js",
|
|
32
|
-
"release": "node ./release.js"
|
|
33
|
-
},
|
|
34
30
|
"dependencies": {
|
|
35
31
|
"@google-cloud/firestore": "^7.11.3",
|
|
36
32
|
"@google-cloud/pubsub": "latest",
|