linkedin-secret-sauce 0.3.22 → 0.3.24
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/dist/cookie-pool.d.ts +36 -0
- package/dist/cookie-pool.js +53 -0
- package/dist/linkedin-api.js +46 -1
- package/dist/utils/search-encoder.js +14 -3
- package/package.json +1 -1
package/dist/cookie-pool.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { LinkedInCookie } from './types';
|
|
2
|
+
interface AccountEntry {
|
|
3
|
+
accountId: string;
|
|
4
|
+
cookies: LinkedInCookie[];
|
|
5
|
+
expiresAt?: number;
|
|
6
|
+
failures: number;
|
|
7
|
+
cooldownUntil: number;
|
|
8
|
+
lastUsedAt?: number;
|
|
9
|
+
}
|
|
2
10
|
export declare function initializeCookiePool(): Promise<void>;
|
|
3
11
|
export declare function getCookiePoolHealth(): {
|
|
4
12
|
initialized: boolean;
|
|
@@ -30,3 +38,31 @@ export declare function buildCookieHeader(cookies: LinkedInCookie[]): string;
|
|
|
30
38
|
export declare function extractCsrfToken(cookies: LinkedInCookie[]): string;
|
|
31
39
|
export declare function adminSetCooldown(accountId: string, ms: number): void;
|
|
32
40
|
export declare function adminResetAccount(accountId: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* TEST-ONLY: Get all account IDs from the pool
|
|
43
|
+
* @returns Array of account IDs in the pool
|
|
44
|
+
*/
|
|
45
|
+
export declare function _testGetAllAccountIds(): string[];
|
|
46
|
+
/**
|
|
47
|
+
* TEST-ONLY: Get cookies for a specific account
|
|
48
|
+
* @param accountId - The account ID to get cookies for
|
|
49
|
+
* @returns Cookies array or undefined if account not found
|
|
50
|
+
*/
|
|
51
|
+
export declare function _testGetAccountCookies(accountId: string): LinkedInCookie[] | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* TEST-ONLY: Get full account entry for diagnostics
|
|
54
|
+
* @param accountId - The account ID to inspect
|
|
55
|
+
* @returns Account entry with all metadata
|
|
56
|
+
*/
|
|
57
|
+
export declare function _testGetAccountEntry(accountId: string): AccountEntry | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* TEST-ONLY: Get current pool state snapshot
|
|
60
|
+
* @returns Pool state for debugging
|
|
61
|
+
*/
|
|
62
|
+
export declare function _testGetPoolState(): {
|
|
63
|
+
initialized: boolean;
|
|
64
|
+
totalAccounts: number;
|
|
65
|
+
currentRRIndex: number;
|
|
66
|
+
accountOrder: string[];
|
|
67
|
+
};
|
|
68
|
+
export {};
|
package/dist/cookie-pool.js
CHANGED
|
@@ -47,6 +47,10 @@ exports.buildCookieHeader = buildCookieHeader;
|
|
|
47
47
|
exports.extractCsrfToken = extractCsrfToken;
|
|
48
48
|
exports.adminSetCooldown = adminSetCooldown;
|
|
49
49
|
exports.adminResetAccount = adminResetAccount;
|
|
50
|
+
exports._testGetAllAccountIds = _testGetAllAccountIds;
|
|
51
|
+
exports._testGetAccountCookies = _testGetAccountCookies;
|
|
52
|
+
exports._testGetAccountEntry = _testGetAccountEntry;
|
|
53
|
+
exports._testGetPoolState = _testGetPoolState;
|
|
50
54
|
const cosiall_client_1 = require("./cosiall-client");
|
|
51
55
|
const config_1 = require("./config");
|
|
52
56
|
const errors_1 = require("./utils/errors");
|
|
@@ -357,3 +361,52 @@ function adminResetAccount(accountId) {
|
|
|
357
361
|
entry.failures = 0;
|
|
358
362
|
entry.cooldownUntil = 0;
|
|
359
363
|
}
|
|
364
|
+
// ============================================================================
|
|
365
|
+
// TEST-ONLY EXPORTS - Not for production use
|
|
366
|
+
// ============================================================================
|
|
367
|
+
// These functions expose internal pool state for diagnostic/testing purposes.
|
|
368
|
+
// They bypass normal selection logic and should only be used in test scripts.
|
|
369
|
+
/**
|
|
370
|
+
* TEST-ONLY: Get all account IDs from the pool
|
|
371
|
+
* @returns Array of account IDs in the pool
|
|
372
|
+
*/
|
|
373
|
+
function _testGetAllAccountIds() {
|
|
374
|
+
if (!poolState.initialized) {
|
|
375
|
+
throw new Error('TEST: Cookie pool not initialized. Call initializeLinkedInClient() first.');
|
|
376
|
+
}
|
|
377
|
+
return Array.from(poolState.accounts.keys());
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* TEST-ONLY: Get cookies for a specific account
|
|
381
|
+
* @param accountId - The account ID to get cookies for
|
|
382
|
+
* @returns Cookies array or undefined if account not found
|
|
383
|
+
*/
|
|
384
|
+
function _testGetAccountCookies(accountId) {
|
|
385
|
+
if (!poolState.initialized) {
|
|
386
|
+
throw new Error('TEST: Cookie pool not initialized. Call initializeLinkedInClient() first.');
|
|
387
|
+
}
|
|
388
|
+
return poolState.accounts.get(accountId)?.cookies;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* TEST-ONLY: Get full account entry for diagnostics
|
|
392
|
+
* @param accountId - The account ID to inspect
|
|
393
|
+
* @returns Account entry with all metadata
|
|
394
|
+
*/
|
|
395
|
+
function _testGetAccountEntry(accountId) {
|
|
396
|
+
if (!poolState.initialized) {
|
|
397
|
+
throw new Error('TEST: Cookie pool not initialized. Call initializeLinkedInClient() first.');
|
|
398
|
+
}
|
|
399
|
+
return poolState.accounts.get(accountId);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* TEST-ONLY: Get current pool state snapshot
|
|
403
|
+
* @returns Pool state for debugging
|
|
404
|
+
*/
|
|
405
|
+
function _testGetPoolState() {
|
|
406
|
+
return {
|
|
407
|
+
initialized: poolState.initialized,
|
|
408
|
+
totalAccounts: poolState.accounts.size,
|
|
409
|
+
currentRRIndex: poolState.rrIndex,
|
|
410
|
+
accountOrder: [...poolState.order],
|
|
411
|
+
};
|
|
412
|
+
}
|
package/dist/linkedin-api.js
CHANGED
|
@@ -128,9 +128,17 @@ async function getProfileByUrn(fsdKey) {
|
|
|
128
128
|
return cachedUrn;
|
|
129
129
|
}
|
|
130
130
|
const url = `${LINKEDIN_API_BASE}/identity/dash/profiles?q=memberIdentity&memberIdentity=${encodeURIComponent(keyMatch)}&decorationId=com.linkedin.voyager.dash.deco.identity.profile.FullProfileWithEntities-35`;
|
|
131
|
+
try {
|
|
132
|
+
(0, logger_1.log)('debug', 'api.requestUrl', { operation: 'getProfileByUrn', url, fsdKey: keyMatch });
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
131
135
|
let raw;
|
|
132
136
|
try {
|
|
133
137
|
raw = await (0, http_client_1.executeLinkedInRequest)({ url }, 'getProfileByUrn');
|
|
138
|
+
try {
|
|
139
|
+
(0, logger_1.log)('debug', 'api.rawResponse', { operation: 'getProfileByUrn', responseSize: JSON.stringify(raw).length });
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
134
142
|
}
|
|
135
143
|
catch (e) {
|
|
136
144
|
const status = e?.status ?? 0;
|
|
@@ -143,9 +151,46 @@ async function getProfileByUrn(fsdKey) {
|
|
|
143
151
|
}
|
|
144
152
|
throw e;
|
|
145
153
|
}
|
|
154
|
+
// Extract publicIdentifier from response to validate correct profile selection
|
|
155
|
+
// BUG FIX: LinkedIn API returns multiple profiles (requested profile + connections/colleagues)
|
|
156
|
+
// The correct profile is referenced in data.*elements[0] - we must match against this URN
|
|
157
|
+
const rr = raw;
|
|
158
|
+
const included = Array.isArray(rr?.included) ? rr.included : [];
|
|
159
|
+
// Get the primary profile URN from data.*elements[0]
|
|
160
|
+
const dataObj = rr?.data;
|
|
161
|
+
const elementsArray = dataObj?.['*elements'];
|
|
162
|
+
const requestedProfileUrn = elementsArray?.[0];
|
|
163
|
+
// Find the profile in included[] that matches the requested URN
|
|
164
|
+
const identityObj = requestedProfileUrn
|
|
165
|
+
? included.find((it) => {
|
|
166
|
+
const rec = it;
|
|
167
|
+
const isProfile = String(rec?.$type || '').includes('identity.profile.Profile');
|
|
168
|
+
if (!isProfile)
|
|
169
|
+
return false;
|
|
170
|
+
// Match the entityUrn against the requested profile URN from data.*elements[0]
|
|
171
|
+
return rec.entityUrn === requestedProfileUrn;
|
|
172
|
+
})
|
|
173
|
+
: undefined;
|
|
174
|
+
const publicIdentifier = identityObj?.publicIdentifier || '';
|
|
175
|
+
try {
|
|
176
|
+
(0, logger_1.log)('debug', 'api.extractedIdentity', {
|
|
177
|
+
operation: 'getProfileByUrn',
|
|
178
|
+
publicIdentifier,
|
|
179
|
+
firstName: identityObj?.firstName,
|
|
180
|
+
lastName: identityObj?.lastName,
|
|
181
|
+
objectUrn: identityObj?.objectUrn,
|
|
182
|
+
profilesInResponse: included.filter(it => String(it?.$type || '').includes('identity.profile.Profile')).length
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch { }
|
|
146
186
|
let prof;
|
|
147
187
|
try {
|
|
148
|
-
|
|
188
|
+
// Pass publicIdentifier to parser so it can validate profile selection (same logic as getProfileByVanity)
|
|
189
|
+
prof = (0, profile_parser_1.parseFullProfile)(raw, publicIdentifier);
|
|
190
|
+
try {
|
|
191
|
+
(0, logger_1.log)('debug', 'api.parsedProfile', { operation: 'getProfileByUrn', firstName: prof.firstName, lastName: prof.lastName, vanity: prof.vanity });
|
|
192
|
+
}
|
|
193
|
+
catch { }
|
|
149
194
|
}
|
|
150
195
|
catch {
|
|
151
196
|
try {
|
|
@@ -135,7 +135,7 @@ function buildLeadSearchQuery(keywords, filters) {
|
|
|
135
135
|
// If not URN or numeric, assume it's already a URN (pass through)
|
|
136
136
|
urn = idStr;
|
|
137
137
|
}
|
|
138
|
-
return valObj([`id:${urn}`, 'selectionType:INCLUDED']);
|
|
138
|
+
return valObj([`id:${encodeURIComponent(urn)}`, 'selectionType:INCLUDED']);
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
141
|
const curCompanies = encodeCompanies(filters?.company?.current?.include);
|
|
@@ -171,6 +171,17 @@ function buildLeadSearchQuery(keywords, filters) {
|
|
|
171
171
|
if (yr?.years_experience_ids?.length) {
|
|
172
172
|
pushFilter(f, 'YEARS_OF_EXPERIENCE', yr.years_experience_ids.map((id) => valObj([`id:${id}`, 'selectionType:INCLUDED'])));
|
|
173
173
|
}
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
// Build query with minimal format for maximum compatibility
|
|
175
|
+
// LinkedIn rejects spellCorrectionEnabled:true,keywords: with 400 errors
|
|
176
|
+
const parts = [];
|
|
177
|
+
// Only include keywords if non-empty
|
|
178
|
+
if (encodedKw) {
|
|
179
|
+
parts.push(`keywords:${encodedKw}`);
|
|
180
|
+
}
|
|
181
|
+
// Add filters if present
|
|
182
|
+
if (f.length) {
|
|
183
|
+
parts.push(`filters:${list(f)}`);
|
|
184
|
+
}
|
|
185
|
+
// Return minimal format - no spellCorrectionEnabled wrapper
|
|
186
|
+
return parts.length > 0 ? `(${parts.join(',')})` : '()';
|
|
176
187
|
}
|