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
- const trending = result.trending || [];
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
- const categories = result.categories || {};
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.676",
3
+ "version": "1.0.678",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [