bulltrackers-module 1.0.676 → 1.0.678
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.
|
@@ -2061,7 +2061,12 @@ const fetchTrendingPopularInvestors = async (db) => {
|
|
|
2061
2061
|
console.log(`[Trending] Got result for ${dateKey}:`, JSON.stringify(result).substring(0, 500));
|
|
2062
2062
|
// If we got a result, return it (even if trending array is empty)
|
|
2063
2063
|
if (result) {
|
|
2064
|
-
|
|
2064
|
+
// Handle case where data is wrapped under "undefined" key
|
|
2065
|
+
let trending = result.trending;
|
|
2066
|
+
if (!trending && result.undefined && result.undefined.trending) {
|
|
2067
|
+
trending = result.undefined.trending;
|
|
2068
|
+
}
|
|
2069
|
+
trending = trending || [];
|
|
2065
2070
|
console.log(`[Trending] Returning trending array with ${trending.length} items`);
|
|
2066
2071
|
return trending;
|
|
2067
2072
|
}
|
|
@@ -2089,7 +2094,12 @@ const fetchPopularInvestorCategories = async (db) => {
|
|
|
2089
2094
|
console.log(`[Categories] Got result for ${dateKey}:`, JSON.stringify(result).substring(0, 500));
|
|
2090
2095
|
// If we got a result, return it (even if categories object is empty)
|
|
2091
2096
|
if (result) {
|
|
2092
|
-
|
|
2097
|
+
// Handle case where data is wrapped under "undefined" key
|
|
2098
|
+
let categories = result.categories;
|
|
2099
|
+
if (!categories && result.undefined && result.undefined.categories) {
|
|
2100
|
+
categories = result.undefined.categories;
|
|
2101
|
+
}
|
|
2102
|
+
categories = categories || {};
|
|
2093
2103
|
console.log(`[Categories] Returning categories object with ${Object.keys(categories).length} keys`);
|
|
2094
2104
|
return categories;
|
|
2095
2105
|
}
|
|
@@ -8,6 +8,64 @@ const { IntelligentProxyManager } = require('../../core/utils/intelligent_proxy_
|
|
|
8
8
|
const { IntelligentHeaderManager } = require('../../core/utils/intelligent_header_manager');
|
|
9
9
|
const zlib = require('zlib');
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Fetches individual user rankings data by CID
|
|
13
|
+
* @param {string} cid - Customer ID
|
|
14
|
+
* @param {object} headers - Request headers to use
|
|
15
|
+
* @param {object} proxyManager - ProxyManager instance
|
|
16
|
+
* @param {object} logger - Logger instance
|
|
17
|
+
* @returns {object|null} - User rankings data or null if failed
|
|
18
|
+
*/
|
|
19
|
+
async function fetchIndividualUserRankings(cid, headers, proxyManager, logger) {
|
|
20
|
+
const individualUrl = `https://www.etoro.com/sapi/rankings/cid/${cid}/rankings/?Period=OneYearAgo`;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
logger.log('INFO', `[PopularInvestorFetch] Fetching individual rankings for CID: ${cid}`);
|
|
24
|
+
|
|
25
|
+
// Try with proxy first
|
|
26
|
+
try {
|
|
27
|
+
const response = await proxyManager.fetch(individualUrl, {
|
|
28
|
+
method: 'GET',
|
|
29
|
+
headers
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (response.ok) {
|
|
33
|
+
const data = await response.json();
|
|
34
|
+
if (data && data.Data) {
|
|
35
|
+
logger.log('SUCCESS', `[PopularInvestorFetch] Successfully fetched individual rankings for CID: ${cid} via proxy`);
|
|
36
|
+
return data.Data; // Return the Data object which matches the Items schema
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (proxyError) {
|
|
40
|
+
logger.log('WARN', `[PopularInvestorFetch] Proxy fetch failed for CID ${cid}: ${proxyError.message}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fallback to direct fetch
|
|
44
|
+
try {
|
|
45
|
+
const directResponse = await fetch(individualUrl, {
|
|
46
|
+
method: 'GET',
|
|
47
|
+
headers
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (directResponse.ok) {
|
|
51
|
+
const data = await directResponse.json();
|
|
52
|
+
if (data && data.Data) {
|
|
53
|
+
logger.log('SUCCESS', `[PopularInvestorFetch] Successfully fetched individual rankings for CID: ${cid} via direct fetch`);
|
|
54
|
+
return data.Data;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (directError) {
|
|
58
|
+
logger.log('WARN', `[PopularInvestorFetch] Direct fetch failed for CID ${cid}: ${directError.message}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
logger.log('ERROR', `[PopularInvestorFetch] Failed to fetch individual rankings for CID: ${cid} from all sources`);
|
|
62
|
+
return null;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.log('ERROR', `[PopularInvestorFetch] Error fetching individual rankings for CID ${cid}`, { errorMessage: error.message });
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
11
69
|
/**
|
|
12
70
|
* Fetches the top Popular Investors and stores the raw result in Firestore.
|
|
13
71
|
* @param {object} dependencies - Contains db, logger.
|
|
@@ -117,6 +175,90 @@ async function fetchAndStorePopularInvestors(config, dependencies) {
|
|
|
117
175
|
await headerManager.flushPerformanceUpdates();
|
|
118
176
|
}
|
|
119
177
|
|
|
178
|
+
// 5.5. Check for missing users from master list and fetch them individually
|
|
179
|
+
if (data && data.Items && Array.isArray(data.Items)) {
|
|
180
|
+
try {
|
|
181
|
+
logger.log('INFO', '[PopularInvestorFetch] Checking for missing users from master list...');
|
|
182
|
+
|
|
183
|
+
// Get master list path
|
|
184
|
+
let masterListPath = 'system_state/popular_investor_master_list';
|
|
185
|
+
if (collectionRegistry && collectionRegistry.getCollectionPath) {
|
|
186
|
+
try {
|
|
187
|
+
const registryPath = collectionRegistry.getCollectionPath('system', 'popularInvestorMasterList', {});
|
|
188
|
+
masterListPath = registryPath;
|
|
189
|
+
} catch (e) {
|
|
190
|
+
logger.log('WARN', `[PopularInvestorFetch] Failed to get master list path from registry, using default: ${e.message}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const masterListRef = db.doc(masterListPath);
|
|
195
|
+
const masterListDoc = await masterListRef.get();
|
|
196
|
+
|
|
197
|
+
if (masterListDoc.exists) {
|
|
198
|
+
const masterListData = masterListDoc.data();
|
|
199
|
+
const masterInvestors = masterListData.investors || {};
|
|
200
|
+
|
|
201
|
+
// Build a Set of CIDs from the fetched data for fast lookup
|
|
202
|
+
const fetchedCids = new Set(data.Items.map(item => String(item.CustomerId)));
|
|
203
|
+
|
|
204
|
+
// Identify missing CIDs
|
|
205
|
+
const masterCids = Object.keys(masterInvestors);
|
|
206
|
+
const missingCids = masterCids.filter(cid => !fetchedCids.has(cid));
|
|
207
|
+
|
|
208
|
+
if (missingCids.length > 0) {
|
|
209
|
+
logger.log('INFO', `[PopularInvestorFetch] Found ${missingCids.length} missing users from master list. Fetching individually...`);
|
|
210
|
+
|
|
211
|
+
// Prepare headers for individual fetches
|
|
212
|
+
const requestHeaders = {
|
|
213
|
+
'Accept': 'application/json',
|
|
214
|
+
'Referer': 'https://www.etoro.com/',
|
|
215
|
+
...(await headerManager.selectHeader()).header
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Fetch missing users with rate limiting
|
|
219
|
+
const missingUserData = [];
|
|
220
|
+
let successCount = 0;
|
|
221
|
+
let failureCount = 0;
|
|
222
|
+
|
|
223
|
+
for (const cid of missingCids) {
|
|
224
|
+
const userData = await fetchIndividualUserRankings(cid, requestHeaders, proxyManager, logger);
|
|
225
|
+
|
|
226
|
+
if (userData) {
|
|
227
|
+
missingUserData.push(userData);
|
|
228
|
+
successCount++;
|
|
229
|
+
} else {
|
|
230
|
+
failureCount++;
|
|
231
|
+
logger.log('WARN', `[PopularInvestorFetch] Failed to fetch data for missing user CID: ${cid} (${masterInvestors[cid].username})`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Add small delay between requests to avoid rate limiting
|
|
235
|
+
if (missingCids.length > 10 && missingCids.indexOf(cid) < missingCids.length - 1) {
|
|
236
|
+
await new Promise(resolve => setTimeout(resolve, 200)); // 200ms delay
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Append successfully fetched missing users to the main data
|
|
241
|
+
if (missingUserData.length > 0) {
|
|
242
|
+
data.Items.push(...missingUserData);
|
|
243
|
+
data.TotalRows += missingUserData.length;
|
|
244
|
+
logger.log('SUCCESS', `[PopularInvestorFetch] Successfully fetched ${successCount}/${missingCids.length} missing users. Updated Items array from ${data.Items.length - missingUserData.length} to ${data.Items.length} users.`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (failureCount > 0) {
|
|
248
|
+
logger.log('WARN', `[PopularInvestorFetch] Failed to fetch ${failureCount}/${missingCids.length} missing users.`);
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
logger.log('INFO', '[PopularInvestorFetch] All users from master list are present in the main fetch. No missing users to fetch individually.');
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
logger.log('INFO', '[PopularInvestorFetch] Master list document does not exist yet. Skipping missing user check.');
|
|
255
|
+
}
|
|
256
|
+
} catch (missingUserError) {
|
|
257
|
+
logger.log('WARN', `[PopularInvestorFetch] Error while checking/fetching missing users: ${missingUserError.message}. Continuing with main fetch data.`);
|
|
258
|
+
// Non-critical error, continue with whatever data we have
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
120
262
|
// 6. Final Validation & Storage
|
|
121
263
|
if (data && data.Items && Array.isArray(data.Items)) {
|
|
122
264
|
try {
|