@florydev/linkedin-api-voyager 1.3.5
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/README.md +290 -0
- package/lib/browser.d.ts +9 -0
- package/lib/browser.js +48 -0
- package/lib/company.d.ts +1 -0
- package/lib/company.js +40 -0
- package/lib/config.d.ts +7 -0
- package/lib/config.js +36 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +22 -0
- package/lib/posts.d.ts +12 -0
- package/lib/posts.js +134 -0
- package/lib/search.d.ts +3 -0
- package/lib/search.js +184 -0
- package/lib/teste.d.ts +1 -0
- package/lib/teste.js +15 -0
- package/lib/types.d.ts +794 -0
- package/lib/types.js +2 -0
- package/lib/user.d.ts +38 -0
- package/lib/user.js +172 -0
- package/lib/utils.d.ts +45 -0
- package/lib/utils.js +606 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# LinkedIn API Voyager
|
|
2
|
+
|
|
3
|
+
Biblioteca TypeScript para interagir com endpoints internos do LinkedIn (Voyager). Esta não é uma API oficial.
|
|
4
|
+
|
|
5
|
+
## Instalação
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install linkedin-api-voyager
|
|
9
|
+
# ou
|
|
10
|
+
yarn add linkedin-api-voyager
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuração (Obrigatório)
|
|
14
|
+
|
|
15
|
+
**Atenção:** Esta biblioteca deve ser executada **exclusivamente no lado do servidor (Node.js)**. O uso direto no navegador (client-side) resultará em erros de CORS e restrições de segurança.
|
|
16
|
+
|
|
17
|
+
Se você estiver usando em uma aplicação web (React, Vue, etc.), você deve criar uma API ou função intermediária no seu backend para chamar esta biblioteca.
|
|
18
|
+
|
|
19
|
+
### 1. Inicialize o Client
|
|
20
|
+
|
|
21
|
+
No ponto de entrada da sua aplicação backend (ex: `index.ts`, `server.ts`):
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { Client } from "linkedin-api-voyager";
|
|
25
|
+
|
|
26
|
+
// Configure suas credenciais uma única vez
|
|
27
|
+
Client({
|
|
28
|
+
JSESSIONID: process.env.LINKEDIN_JSESSIONID, // ex: "ajax:123456789" (apenas os números se preferir, a lib trata)
|
|
29
|
+
li_at: process.env.LINKEDIN_LI_AT, // ex: "AQEDAR..."
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Onde pegar `li_at` e `JSESSIONID`
|
|
34
|
+
|
|
35
|
+
1. Faça login no LinkedIn pelo navegador.
|
|
36
|
+
2. Abra o DevTools do navegador.
|
|
37
|
+
3. Vá em:
|
|
38
|
+
- Chrome/Edge: `Application` -> `Storage` -> `Cookies` -> `https://www.linkedin.com`
|
|
39
|
+
- Firefox: `Storage` -> `Cookies` -> `https://www.linkedin.com`
|
|
40
|
+
4. Copie os valores:
|
|
41
|
+
- `li_at`: valor completo.
|
|
42
|
+
- `JSESSIONID`: valor completo (ex: `"ajax:123456789"`).
|
|
43
|
+
|
|
44
|
+
> **Nota:** Nunca comite suas credenciais reais no código. Use variáveis de ambiente (`.env`).
|
|
45
|
+
|
|
46
|
+
## Exemplos de Uso
|
|
47
|
+
|
|
48
|
+
Após inicializar o `Client`, você pode importar e usar qualquer função diretamente:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import {
|
|
52
|
+
getUserMiniProfile,
|
|
53
|
+
getProfissionalExperiences,
|
|
54
|
+
getCompany,
|
|
55
|
+
searchPeople,
|
|
56
|
+
getCommentsByPostUrl,
|
|
57
|
+
} from "linkedin-api-voyager";
|
|
58
|
+
|
|
59
|
+
// Exemplo: Buscar perfil
|
|
60
|
+
const profile = await getUserMiniProfile("florymignon");
|
|
61
|
+
console.log(profile);
|
|
62
|
+
|
|
63
|
+
// Exemplo: Buscar experiências
|
|
64
|
+
const experiences = await getProfissionalExperiences("florymignon");
|
|
65
|
+
|
|
66
|
+
// Exemplo: Buscar empresa
|
|
67
|
+
const company = await getCompany("microsoft");
|
|
68
|
+
|
|
69
|
+
// Exemplo: Pesquisar pessoas
|
|
70
|
+
const people = await searchPeople("software engineer");
|
|
71
|
+
|
|
72
|
+
// Exemplo: Buscar comentários
|
|
73
|
+
const comments = await getCommentsByPostUrl(
|
|
74
|
+
"https://www.linkedin.com/feed/update/urn:li:activity-1234567890/",
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API
|
|
79
|
+
|
|
80
|
+
### `src/config.ts`
|
|
81
|
+
|
|
82
|
+
- `Client(config: { JSESSIONID: string; li_at: string })`: Configura a instância global do axios. Deve ser chamado antes de qualquer outra função.
|
|
83
|
+
- `API_BASE_URL`: `https://www.linkedin.com/voyager/api`
|
|
84
|
+
|
|
85
|
+
### Módulos Disponíveis
|
|
86
|
+
|
|
87
|
+
A biblioteca exporta funções dos seguintes módulos:
|
|
88
|
+
|
|
89
|
+
- `user`: Perfis e dados de usuário.
|
|
90
|
+
- `company`: Dados de empresas.
|
|
91
|
+
- `posts`: Interações com posts e comentários.
|
|
92
|
+
- `search`: Busca de pessoas e empresas.
|
|
93
|
+
- `utils`: Utilitários gerais.
|
|
94
|
+
|
|
95
|
+
````
|
|
96
|
+
|
|
97
|
+
### `src/user.ts`
|
|
98
|
+
|
|
99
|
+
Tipos exportados:
|
|
100
|
+
|
|
101
|
+
- `MiniUserProfileLinkedin`
|
|
102
|
+
|
|
103
|
+
Funções exportadas:
|
|
104
|
+
|
|
105
|
+
- `getUserMiniProfile(identifier: string): Promise<MiniUserProfileLinkedin>`
|
|
106
|
+
- Busca dados básicos do perfil (nome, headline, imagens) e também o `about`.
|
|
107
|
+
- `identifier` é o `publicIdentifier` (parte final da URL `linkedin.com/in/<identifier>`).
|
|
108
|
+
|
|
109
|
+
- `extractProfileIdLinkedin(profileUrl: string): Promise<string | null>`
|
|
110
|
+
- Extrai o `publicIdentifier` de uma URL `linkedin.com/in/...`.
|
|
111
|
+
- Se você passar apenas o identificador, ele tenta usar diretamente.
|
|
112
|
+
- Retorna o ID numérico interno (sem o prefixo `urn:li:fsd_profile:`) quando encontra.
|
|
113
|
+
|
|
114
|
+
- `getProfileSectionAbout(identifier: string): Promise<string | null>`
|
|
115
|
+
- Retorna o texto de “Sobre” (about) do perfil.
|
|
116
|
+
|
|
117
|
+
- `getProfissionalExperiences(identifier: string): Promise<Array<any>>`
|
|
118
|
+
- Retorna a lista de experiências profissionais.
|
|
119
|
+
- Para cada experiência, tenta enriquecer com dados de empresa via `getCompany`.
|
|
120
|
+
|
|
121
|
+
- `getContactInfo(identifier: string): Promise<{ ... }>`
|
|
122
|
+
- Retorna informações de contato quando disponíveis (email, telefones, sites etc.).
|
|
123
|
+
|
|
124
|
+
- `getLinkedinSkills(identifier: string): Promise<Array<string | null>>`
|
|
125
|
+
- Retorna as skills (habilidades) listadas no perfil.
|
|
126
|
+
|
|
127
|
+
- `getLinkedinEducation(identifier: string): Promise<Array<any>>`
|
|
128
|
+
- Retorna a educação (escola, degree, datas, skills relacionadas quando houver).
|
|
129
|
+
|
|
130
|
+
- `getLinkedinCertifications(identifier: string): Promise<Array<any>>`
|
|
131
|
+
- Retorna certificações.
|
|
132
|
+
|
|
133
|
+
Exemplo:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import {
|
|
137
|
+
getUserMiniProfile,
|
|
138
|
+
getProfileSectionAbout,
|
|
139
|
+
getProfissionalExperiences,
|
|
140
|
+
getContactInfo,
|
|
141
|
+
getLinkedinSkills,
|
|
142
|
+
getLinkedinEducation,
|
|
143
|
+
getLinkedinCertifications,
|
|
144
|
+
} from "linkedin-api-voyager";
|
|
145
|
+
|
|
146
|
+
const identifier = "florymignon";
|
|
147
|
+
|
|
148
|
+
const mini = await getUserMiniProfile(identifier);
|
|
149
|
+
const about = await getProfileSectionAbout(identifier);
|
|
150
|
+
const experiences = await getProfissionalExperiences(identifier);
|
|
151
|
+
const contact = await getContactInfo(identifier);
|
|
152
|
+
const skills = await getLinkedinSkills(identifier);
|
|
153
|
+
const education = await getLinkedinEducation(identifier);
|
|
154
|
+
const certifications = await getLinkedinCertifications(identifier);
|
|
155
|
+
````
|
|
156
|
+
|
|
157
|
+
### `src/company.ts`
|
|
158
|
+
|
|
159
|
+
Funções exportadas:
|
|
160
|
+
|
|
161
|
+
- `getCompany(identifier: string): Promise<any>`
|
|
162
|
+
- Busca dados de uma empresa pelo `universalName` (slug da página).
|
|
163
|
+
- Exemplo de slug: `https://www.linkedin.com/company/microsoft/` -> `microsoft`.
|
|
164
|
+
|
|
165
|
+
Exemplo:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { getCompany } from "linkedin-api-voyager";
|
|
169
|
+
|
|
170
|
+
const company = await getCompany("microsoft");
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### `src/posts.ts`
|
|
174
|
+
|
|
175
|
+
Funções exportadas:
|
|
176
|
+
|
|
177
|
+
- `parseResponsePostLinkedin(response: any, key: string, accumulatedData: any): any`
|
|
178
|
+
- Helper para selecionar itens do `included` a partir de `*elements`.
|
|
179
|
+
|
|
180
|
+
- `getCommentsByPostUrl(url: string, start = 0, limit = 50, accumulatedComments: unknown[] = []): Promise<unknown[]>`
|
|
181
|
+
- Busca comentários de um post (paginando recursivamente até acabar).
|
|
182
|
+
|
|
183
|
+
- `getPosts(): Promise<unknown[]>`
|
|
184
|
+
- Atualmente retorna `[]` (placeholder).
|
|
185
|
+
|
|
186
|
+
- `getPostLinkedin(url: string, commentsCount = 10, likesCount = 10): Promise<any>`
|
|
187
|
+
- Busca um post pelo slug da URL e retorna os dados do post e do autor.
|
|
188
|
+
|
|
189
|
+
- `getUserPosts({ identifier, start = 0, count = 50, accumulatedPosts = [] }): Promise<any>`
|
|
190
|
+
- Busca posts do usuário por `identifier` (publicIdentifier).
|
|
191
|
+
|
|
192
|
+
- `helperGetPosts(response: any, key: string, accumulatedPosts?: any, addFields?: Record<string, string>): any`
|
|
193
|
+
- Helper para extrair posts e contagens (likes, comentários, shares).
|
|
194
|
+
|
|
195
|
+
- `helperGetImageUrl(item: any): string`
|
|
196
|
+
- Helper para montar a URL de imagem, priorizando o maior artifact.
|
|
197
|
+
|
|
198
|
+
Exemplo (comentários):
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { getCommentsByPostUrl } from "linkedin-api-voyager";
|
|
202
|
+
|
|
203
|
+
const comments = await getCommentsByPostUrl(
|
|
204
|
+
"https://www.linkedin.com/feed/update/urn:li:activity-1234567890/",
|
|
205
|
+
);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### `src/search.ts`
|
|
209
|
+
|
|
210
|
+
Constantes internas:
|
|
211
|
+
|
|
212
|
+
- `MAX_SEARCH_COUNT = 25` (limite máximo por chamada na busca geral)
|
|
213
|
+
|
|
214
|
+
Funções exportadas:
|
|
215
|
+
|
|
216
|
+
- `search(params: ISearchParams): Promise<SearchResponse>`
|
|
217
|
+
- Busca geral usando `query` e/ou `filters` (formato Voyager).
|
|
218
|
+
- Aceita paginação via `offset`.
|
|
219
|
+
|
|
220
|
+
- `searchPeople(queryOrParams: string | ISearchPeopleParams): Promise<ISearchPeopleResponse>`
|
|
221
|
+
- Busca pessoas com helpers para montar filtros (networkDepth, regiões, empresas etc.).
|
|
222
|
+
|
|
223
|
+
Exemplo:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { search, searchPeople } from "linkedin-api-voyager";
|
|
227
|
+
|
|
228
|
+
const res = await search({ query: "react developer" });
|
|
229
|
+
const people = await searchPeople({
|
|
230
|
+
query: "engenheiro de software",
|
|
231
|
+
regions: ["br:0"],
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### `src/utils.ts`
|
|
236
|
+
|
|
237
|
+
Funções exportadas (helpers usados em parsing e normalização):
|
|
238
|
+
|
|
239
|
+
- `filterKeys(obj: any, keysToKeep: string[]): any`
|
|
240
|
+
- `filterOutKeys(obj: any, keysToIgnore: string[]): any`
|
|
241
|
+
- `getNestedValue(obj: any, path: string): any`
|
|
242
|
+
- `extractFields(data: any[], fieldsMap: Record<string, string>): any[]`
|
|
243
|
+
- `debugObjectStructure(obj: any, maxDepth = 3, currentDepth = 0): void`
|
|
244
|
+
- `resolveReferences(data: any, included: any[]): any`
|
|
245
|
+
- `extractDataWithReferences(elements: string[], included: any[], fieldsMap?: Record<string, string>): any[]`
|
|
246
|
+
- `debugResolvedStructure(elements: string[], included: any[], maxDepth = 2): void`
|
|
247
|
+
- `extractFieldsFromIncluded(included: any[], fields: string[]): Array<Record<string, any>>`
|
|
248
|
+
- `mergeExtraFields(mainData: any[], extraData: Array<Record<string, any>>, matchKey = "companyUrn"): any[]`
|
|
249
|
+
- `getDataIncludedForEntity(jsonData: Record<string, any>, entityUrn: string): any`
|
|
250
|
+
- `extractExperiences(jsonData: Record<string, any>): Array<{ role: string | null; idCompany: string | null; company: string | null; ... }>`
|
|
251
|
+
- `assert(value: unknown, message?: string | Error): asserts value`
|
|
252
|
+
- `getIdFromUrn(urn?: string): string | undefined`
|
|
253
|
+
- `getUrnFromRawUpdate(update?: string): string | undefined`
|
|
254
|
+
- `isLinkedInUrn(urn?: string): boolean`
|
|
255
|
+
- `parseExperienceItem(item: any, opts: { isGroupItem?: boolean; included: any[] }): ExperienceItem`
|
|
256
|
+
- `getGroupedItemId(item: any): string | undefined`
|
|
257
|
+
- `omit(inputObj: object, ...keys: string[]): object`
|
|
258
|
+
- `resolveImageUrl(vectorImage?: VectorImage): string | undefined`
|
|
259
|
+
- `resolveLinkedVectorImageUrl(linkedVectorImage?: LinkedVectorImage): string | undefined`
|
|
260
|
+
- `stringifyLinkedInDate(date?: LIDate): string | undefined`
|
|
261
|
+
- `normalizeRawOrganization(o?: RawOrganization): Organization`
|
|
262
|
+
|
|
263
|
+
Exemplo (mapear campos com path aninhado):
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { extractFields } from "linkedin-api-voyager";
|
|
267
|
+
|
|
268
|
+
const fieldsMap = {
|
|
269
|
+
nome: "firstName",
|
|
270
|
+
headline: "headline",
|
|
271
|
+
foto: "profilePicture.displayImageReferenceResolutionResult.vectorImage.rootUrl",
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const mapped = extractFields([someObject], fieldsMap);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### `src/types.ts`
|
|
278
|
+
|
|
279
|
+
Este arquivo exporta tipos e interfaces TypeScript usados pela biblioteca (por exemplo: `ISearchParams`, `ISearchPeopleParams`, `SearchResponse`, `Organization`, `ExperienceItem`).
|
|
280
|
+
|
|
281
|
+
## Limitações e considerações
|
|
282
|
+
|
|
283
|
+
- Usa endpoints internos do LinkedIn (Voyager), que podem mudar sem aviso.
|
|
284
|
+
- Requer cookies válidos de uma sessão autenticada.
|
|
285
|
+
- Use com moderação para reduzir risco de bloqueio.
|
|
286
|
+
- Respeite os termos de uso do LinkedIn.
|
|
287
|
+
|
|
288
|
+
## Licença
|
|
289
|
+
|
|
290
|
+
MIT
|
package/lib/browser.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./utils";
|
|
3
|
+
export declare const COOKIE_FILE_PATH = "linkedin_cookies.json";
|
|
4
|
+
export declare const API_BASE_URL = "https://www.linkedin.com/voyager/api";
|
|
5
|
+
export declare const AUTH_BASE_URL = "https://www.linkedin.com";
|
|
6
|
+
export declare const saveCookies: () => Promise<never>;
|
|
7
|
+
export declare const loadCookies: () => Promise<never>;
|
|
8
|
+
export declare const Client: () => Promise<never>;
|
|
9
|
+
export declare const fetchData: () => Promise<never>;
|
package/lib/browser.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
17
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
18
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
19
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
20
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
21
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
22
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.fetchData = exports.Client = exports.loadCookies = exports.saveCookies = exports.AUTH_BASE_URL = exports.API_BASE_URL = exports.COOKIE_FILE_PATH = void 0;
|
|
27
|
+
const ERROR_BROWSER_ONLY = "linkedin-api-voyager: este pacote não roda no browser. Use um Route Handler (Next.js) no servidor.";
|
|
28
|
+
__exportStar(require("./types"), exports);
|
|
29
|
+
__exportStar(require("./utils"), exports);
|
|
30
|
+
exports.COOKIE_FILE_PATH = "linkedin_cookies.json";
|
|
31
|
+
exports.API_BASE_URL = "https://www.linkedin.com/voyager/api";
|
|
32
|
+
exports.AUTH_BASE_URL = "https://www.linkedin.com";
|
|
33
|
+
const saveCookies = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
throw new Error(ERROR_BROWSER_ONLY);
|
|
35
|
+
});
|
|
36
|
+
exports.saveCookies = saveCookies;
|
|
37
|
+
const loadCookies = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
+
throw new Error(ERROR_BROWSER_ONLY);
|
|
39
|
+
});
|
|
40
|
+
exports.loadCookies = loadCookies;
|
|
41
|
+
const Client = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
throw new Error(ERROR_BROWSER_ONLY);
|
|
43
|
+
});
|
|
44
|
+
exports.Client = Client;
|
|
45
|
+
const fetchData = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
46
|
+
throw new Error(ERROR_BROWSER_ONLY);
|
|
47
|
+
});
|
|
48
|
+
exports.fetchData = fetchData;
|
package/lib/company.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getCompany: (identifier: string) => Promise<any>;
|
package/lib/company.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getCompany = void 0;
|
|
13
|
+
const config_1 = require("./config");
|
|
14
|
+
const utils_1 = require("./utils");
|
|
15
|
+
const getCompany = (identifier) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16
|
+
const response = yield (0, config_1.fetchData)(`/organization/companies?decorationId=com.linkedin.voyager.deco.organization.web.WebFullCompanyMain-12&q=universalName&universalName=${identifier}`);
|
|
17
|
+
const data = (0, utils_1.extractDataWithReferences)(response.data["*elements"], response.included);
|
|
18
|
+
const fieldsMap = {
|
|
19
|
+
id: "entityUrn",
|
|
20
|
+
name: "name",
|
|
21
|
+
description: "description",
|
|
22
|
+
username: "universalName",
|
|
23
|
+
companyPageUrl: "companyPageUrl",
|
|
24
|
+
staffCount: "staffCount",
|
|
25
|
+
url: "url",
|
|
26
|
+
companyIndustries: "*companyIndustries[0].localizedName",
|
|
27
|
+
location: "locationName",
|
|
28
|
+
jobSearchPageUrl: "jobSearchPageUrl",
|
|
29
|
+
phone: "phone",
|
|
30
|
+
followerCount: "followingInfo.followerCount",
|
|
31
|
+
backgroundCoverImage: "backgroundCoverImage.image",
|
|
32
|
+
logo: "logo.image",
|
|
33
|
+
permissions: "permissions",
|
|
34
|
+
};
|
|
35
|
+
return (0, utils_1.extractFields)(data, fieldsMap).map((item) => {
|
|
36
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
37
|
+
return (Object.assign(Object.assign({}, item), { id: item.id.split(":")[3], backgroundCoverImage: `${(_a = item.backgroundCoverImage) === null || _a === void 0 ? void 0 : _a.rootUrl}${(_d = (_c = (_b = item.backgroundCoverImage) === null || _b === void 0 ? void 0 : _b.artifacts) === null || _c === void 0 ? void 0 : _c.at(-1)) === null || _d === void 0 ? void 0 : _d.fileIdentifyingUrlPathSegment}`, logo: `${(_e = item.logo) === null || _e === void 0 ? void 0 : _e.rootUrl}${(_h = (_g = (_f = item.logo) === null || _f === void 0 ? void 0 : _f.artifacts) === null || _g === void 0 ? void 0 : _g.at(-1)) === null || _h === void 0 ? void 0 : _h.fileIdentifyingUrlPathSegment}` }));
|
|
38
|
+
})[0];
|
|
39
|
+
});
|
|
40
|
+
exports.getCompany = getCompany;
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AxiosInstance } from "axios";
|
|
2
|
+
export declare const API_BASE_URL = "https://www.linkedin.com/voyager/api";
|
|
3
|
+
export declare const Client: (providedCookies: {
|
|
4
|
+
JSESSIONID: string;
|
|
5
|
+
li_at: string;
|
|
6
|
+
}) => AxiosInstance;
|
|
7
|
+
export declare const fetchData: (endpoint: string) => Promise<any>;
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.fetchData = exports.Client = exports.API_BASE_URL = void 0;
|
|
13
|
+
const axios_1 = require("axios");
|
|
14
|
+
exports.API_BASE_URL = "https://www.linkedin.com/voyager/api";
|
|
15
|
+
let apiInstance = null;
|
|
16
|
+
const Client = (providedCookies) => {
|
|
17
|
+
apiInstance = axios_1.default.create({
|
|
18
|
+
baseURL: exports.API_BASE_URL,
|
|
19
|
+
headers: {
|
|
20
|
+
"accept-language": "pt-BR,pt;q=0.9,fr-FR;q=0.8,fr;q=0.7,en-US;q=0.6,en;q=0.5",
|
|
21
|
+
accept: "application/vnd.linkedin.normalized+json+2.1",
|
|
22
|
+
cookie: `li_at=${providedCookies.li_at}; JSESSIONID="ajax:${providedCookies.JSESSIONID}"`,
|
|
23
|
+
"csrf-token": `ajax:${providedCookies.JSESSIONID}`,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
return apiInstance;
|
|
27
|
+
};
|
|
28
|
+
exports.Client = Client;
|
|
29
|
+
const fetchData = (endpoint) => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
|
+
if (!apiInstance) {
|
|
31
|
+
throw new Error("Client not initialized. Please call Client({ JSESSIONID, li_at }) first.");
|
|
32
|
+
}
|
|
33
|
+
const response = yield apiInstance.get(endpoint);
|
|
34
|
+
return response.data;
|
|
35
|
+
});
|
|
36
|
+
exports.fetchData = fetchData;
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./user"), exports);
|
|
18
|
+
__exportStar(require("./company"), exports);
|
|
19
|
+
__exportStar(require("./posts"), exports);
|
|
20
|
+
__exportStar(require("./search"), exports);
|
|
21
|
+
__exportStar(require("./utils"), exports);
|
|
22
|
+
__exportStar(require("./config"), exports);
|
package/lib/posts.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const parseResponsePostLinkedin: (response: any, key: string, accumulatedData: any) => any;
|
|
2
|
+
export declare const getCommentsByPostUrl: (url: string, start?: number, limit?: number, accumulatedComments?: unknown[]) => Promise<unknown[]>;
|
|
3
|
+
export declare const getPosts: () => Promise<never[]>;
|
|
4
|
+
export declare const getPostLinkedin: (url: string, commentsCount?: number, likesCount?: number) => Promise<any>;
|
|
5
|
+
export declare const getUserPosts: ({ identifier, start, count, accumulatedPosts, }: {
|
|
6
|
+
identifier: string;
|
|
7
|
+
start?: number;
|
|
8
|
+
count?: number;
|
|
9
|
+
accumulatedPosts?: unknown[];
|
|
10
|
+
}) => Promise<any[]>;
|
|
11
|
+
export declare const helperGetPosts: (response: any, key: string, accumulatedPosts?: any, addFields?: Record<string, string>) => any[];
|
|
12
|
+
export declare const helperGetImageUrl: (item: any) => any;
|
package/lib/posts.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.helperGetImageUrl = exports.helperGetPosts = exports.getUserPosts = exports.getPostLinkedin = exports.getPosts = exports.getCommentsByPostUrl = exports.parseResponsePostLinkedin = void 0;
|
|
13
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
14
|
+
const user_1 = require("./user");
|
|
15
|
+
const config_1 = require("./config");
|
|
16
|
+
const utils_1 = require("./utils");
|
|
17
|
+
const parseResponsePostLinkedin = (response, key, accumulatedData) => {
|
|
18
|
+
var _a, _b, _c, _d;
|
|
19
|
+
const elements = (_c = (_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b[key]) === null || _c === void 0 ? void 0 : _c["*elements"];
|
|
20
|
+
const data = ((_d = response.included) === null || _d === void 0 ? void 0 : _d.filter((item) => elements.includes(item.entityUrn))) || [];
|
|
21
|
+
if (!elements || elements.length === 0) {
|
|
22
|
+
return accumulatedData;
|
|
23
|
+
}
|
|
24
|
+
return data;
|
|
25
|
+
};
|
|
26
|
+
exports.parseResponsePostLinkedin = parseResponsePostLinkedin;
|
|
27
|
+
const getCommentsByPostUrl = (url_1, ...args_1) => __awaiter(void 0, [url_1, ...args_1], void 0, function* (url, start = 0, limit = 50, accumulatedComments = []) {
|
|
28
|
+
var _a, _b, _c, _d, _e;
|
|
29
|
+
const postID = (_a = url.match(/activity-(\d+)/)) === null || _a === void 0 ? void 0 : _a[1];
|
|
30
|
+
const response = yield (0, config_1.fetchData)(`/graphql?includeWebMetadata=false&queryId=voyagerSocialDashComments.95ed44bc87596acce7c460c70934d0ff&variables=(count:${limit},start:${start},numReplies:1,socialDetailUrn:urn%3Ali%3Afsd_socialDetail%3A%28urn%3Ali%3Aactivity%${postID}%2Curn%3Ali%3Aactivity%3A${postID}%2Curn%3Ali%3AhighlightedReply%3A-%29,sortOrder:RELEVANCE)`);
|
|
31
|
+
const elements = (_d = (_c = (_b = response.data) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.socialDashCommentsBySocialDetail) === null || _d === void 0 ? void 0 : _d["*elements"];
|
|
32
|
+
// Se não há elementos, retorna os comentários acumulados
|
|
33
|
+
if (!elements || elements.length === 0) {
|
|
34
|
+
return accumulatedComments;
|
|
35
|
+
}
|
|
36
|
+
const data = ((_e = response.included) === null || _e === void 0 ? void 0 : _e.filter((item) => elements.includes(item.entityUrn))) || [];
|
|
37
|
+
// Mapeamento melhorado dos campos
|
|
38
|
+
const fieldsMap = {
|
|
39
|
+
id: "entityUrn",
|
|
40
|
+
createdAt: "createdAt",
|
|
41
|
+
isAuthor: "commenter.author",
|
|
42
|
+
name: "commenter.title.text",
|
|
43
|
+
headline: "commenter.subtitle",
|
|
44
|
+
profileUrl: "commenter.navigationUrl",
|
|
45
|
+
comment: "commentary.text",
|
|
46
|
+
permalink: "permalink",
|
|
47
|
+
image: "commenter.image.attributes.0.detailData.nonEntityProfilePicture.vectorImage",
|
|
48
|
+
};
|
|
49
|
+
const currentComments = (0, utils_1.extractFields)(data, fieldsMap).map((comment) => {
|
|
50
|
+
var _a, _b, _c, _d;
|
|
51
|
+
return (Object.assign(Object.assign({}, comment), { image: `${(_a = comment.image) === null || _a === void 0 ? void 0 : _a.rootUrl}${(_d = (_c = (_b = comment.image) === null || _b === void 0 ? void 0 : _b.artifacts) === null || _c === void 0 ? void 0 : _c.at(-1)) === null || _d === void 0 ? void 0 : _d.fileIdentifyingUrlPathSegment}` }));
|
|
52
|
+
});
|
|
53
|
+
const allComments = [...accumulatedComments, ...currentComments];
|
|
54
|
+
// console.log(
|
|
55
|
+
// `🔍 Encontrados ${elements.length} comentários (Total: ${allComments.length})`
|
|
56
|
+
// );
|
|
57
|
+
// Continua a busca se há mais elementos
|
|
58
|
+
if (elements.length > 0) {
|
|
59
|
+
return yield (0, exports.getCommentsByPostUrl)(url, start + elements.length, limit, allComments);
|
|
60
|
+
}
|
|
61
|
+
// Se retornou menos que o limite, chegou ao fim
|
|
62
|
+
return allComments;
|
|
63
|
+
});
|
|
64
|
+
exports.getCommentsByPostUrl = getCommentsByPostUrl;
|
|
65
|
+
const getPosts = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
+
return [];
|
|
67
|
+
});
|
|
68
|
+
exports.getPosts = getPosts;
|
|
69
|
+
const getPostLinkedin = (url_1, ...args_1) => __awaiter(void 0, [url_1, ...args_1], void 0, function* (url, commentsCount = 10, likesCount = 10) {
|
|
70
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
|
|
71
|
+
const slugPost = (_a = url.match(/\/posts\/([^\/?]+)/)) === null || _a === void 0 ? void 0 : _a[1];
|
|
72
|
+
const response = yield (0, config_1.fetchData)(`/graphql?includeWebMetadata=false&queryId=voyagerFeedDashUpdates.5cf9b25c46b9d86c224647752f7d6bfd&variables=(commentsCount:${commentsCount},likesCount:${likesCount},includeCommentsFirstReply:true,includeReactions:false,moduleKey:feed-item%3Adesktop,slug:${slugPost})`);
|
|
73
|
+
const posts = (_b = (0, exports.helperGetPosts)(response, "feedDashUpdatesByPostSlug", undefined, { actor: "actor" })) === null || _b === void 0 ? void 0 : _b[0];
|
|
74
|
+
const actor = {
|
|
75
|
+
name: (_d = (_c = posts === null || posts === void 0 ? void 0 : posts.actor) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.text,
|
|
76
|
+
headline: (_f = (_e = posts === null || posts === void 0 ? void 0 : posts.actor) === null || _e === void 0 ? void 0 : _e.description) === null || _f === void 0 ? void 0 : _f.text,
|
|
77
|
+
profileUrl: ((_o = (_m = (_l = (_k = (_j = (_h = (_g = posts === null || posts === void 0 ? void 0 : posts.actor) === null || _g === void 0 ? void 0 : _g.image) === null || _h === void 0 ? void 0 : _h.attributes) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.detailData) === null || _l === void 0 ? void 0 : _l.nonEntityProfilePicture) === null || _m === void 0 ? void 0 : _m.vectorImage) === null || _o === void 0 ? void 0 : _o.rootUrl) +
|
|
78
|
+
((_x = (_w = (_v = (_u = (_t = (_s = (_r = (_q = (_p = posts === null || posts === void 0 ? void 0 : posts.actor) === null || _p === void 0 ? void 0 : _p.image) === null || _q === void 0 ? void 0 : _q.attributes) === null || _r === void 0 ? void 0 : _r[0]) === null || _s === void 0 ? void 0 : _s.detailData) === null || _t === void 0 ? void 0 : _t.nonEntityProfilePicture) === null || _u === void 0 ? void 0 : _u.vectorImage) === null || _v === void 0 ? void 0 : _v.artifacts) === null || _w === void 0 ? void 0 : _w.at(-1)) === null || _x === void 0 ? void 0 : _x.fileIdentifyingUrlPathSegment),
|
|
79
|
+
};
|
|
80
|
+
return Object.assign(Object.assign({}, posts), { actor });
|
|
81
|
+
});
|
|
82
|
+
exports.getPostLinkedin = getPostLinkedin;
|
|
83
|
+
const getUserPosts = (_a) => __awaiter(void 0, [_a], void 0, function* ({ identifier, start = 0, count = 50, accumulatedPosts = [], }) {
|
|
84
|
+
const profileId = yield (0, user_1.extractProfileIdLinkedin)(identifier);
|
|
85
|
+
const response = yield (0, config_1.fetchData)(`graphql?variables=(profileUrn:urn%3Ali%3Afsd_profile%3A${profileId},count:${count},start:${start})&queryId=voyagerFeedDashProfileUpdates.4af00b28d60ed0f1488018948daad822`);
|
|
86
|
+
const parsePosts = (0, exports.helperGetPosts)(response, "feedDashProfileUpdatesByMemberShareFeed", accumulatedPosts);
|
|
87
|
+
return parsePosts;
|
|
88
|
+
});
|
|
89
|
+
exports.getUserPosts = getUserPosts;
|
|
90
|
+
const helperGetPosts = (response, key, accumulatedPosts, addFields) => {
|
|
91
|
+
const data = (0, exports.parseResponsePostLinkedin)(response, key, accumulatedPosts);
|
|
92
|
+
const socialActivityData = response.included.filter((item) => (item === null || item === void 0 ? void 0 : item.$type) === "com.linkedin.voyager.dash.feed.SocialActivityCounts");
|
|
93
|
+
const fieldsMap = Object.assign({ urn: "metadata.backendUrn", postUrl: "socialContent.shareUrl", contentText: "commentary.text.text", tags: "commentary.text.attributesV2", media: "content", dateDescription: "actor.subDescription.text" }, addFields);
|
|
94
|
+
const fieldsSocialActivityCountMap = {
|
|
95
|
+
numLikes: "numLikes",
|
|
96
|
+
numComments: "numComments",
|
|
97
|
+
reactionCounts: "reactionTypeCounts",
|
|
98
|
+
numShares: "numShares",
|
|
99
|
+
urn: "urn",
|
|
100
|
+
};
|
|
101
|
+
const extractPosts = (0, utils_1.extractFields)(data, fieldsMap);
|
|
102
|
+
const extractSocialActivityCount = (0, utils_1.extractFields)(socialActivityData, fieldsSocialActivityCountMap);
|
|
103
|
+
const parsePosts = extractPosts === null || extractPosts === void 0 ? void 0 : extractPosts.map((post) => {
|
|
104
|
+
var _a, _b, _c;
|
|
105
|
+
const socialActivity = extractSocialActivityCount.find((item) => item.urn === post.urn) || {};
|
|
106
|
+
if (socialActivity) {
|
|
107
|
+
const mediaNonNullKeys = Object.fromEntries(Object.entries(post.media).filter(([_, value]) => value !== null));
|
|
108
|
+
let media = {};
|
|
109
|
+
if (mediaNonNullKeys === null || mediaNonNullKeys === void 0 ? void 0 : mediaNonNullKeys.imageComponent) {
|
|
110
|
+
media = Object.assign(Object.assign({}, media), { images: (_b = (_a = mediaNonNullKeys === null || mediaNonNullKeys === void 0 ? void 0 : mediaNonNullKeys.imageComponent) === null || _a === void 0 ? void 0 : _a.images) === null || _b === void 0 ? void 0 : _b.map((item) => (0, exports.helperGetImageUrl)(item)) });
|
|
111
|
+
}
|
|
112
|
+
if (mediaNonNullKeys === null || mediaNonNullKeys === void 0 ? void 0 : mediaNonNullKeys.linkedInVideoComponent) {
|
|
113
|
+
const videoentityUrn = (_c = mediaNonNullKeys === null || mediaNonNullKeys === void 0 ? void 0 : mediaNonNullKeys.linkedInVideoComponent) === null || _c === void 0 ? void 0 : _c["*videoPlayMetadata"];
|
|
114
|
+
const videoActivityData = response.included.filter((item) => (item === null || item === void 0 ? void 0 : item.entityUrn) === videoentityUrn);
|
|
115
|
+
media = Object.assign(Object.assign({}, media), { videoActivityData });
|
|
116
|
+
}
|
|
117
|
+
return Object.assign(Object.assign(Object.assign({}, post), { media }), socialActivity);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return parsePosts;
|
|
121
|
+
};
|
|
122
|
+
exports.helperGetPosts = helperGetPosts;
|
|
123
|
+
const helperGetImageUrl = (item) => {
|
|
124
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
125
|
+
const keyEntity = ((_c = (_b = (_a = item === null || item === void 0 ? void 0 : item.attributes) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.detailData) === null || _c === void 0 ? void 0 : _c.nonEntityProfilePicture)
|
|
126
|
+
? "nonEntityProfilePicture"
|
|
127
|
+
: "vectorImage";
|
|
128
|
+
const itemImage = (_f = (_e = (_d = item === null || item === void 0 ? void 0 : item.attributes) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.detailData) === null || _f === void 0 ? void 0 : _f[keyEntity];
|
|
129
|
+
const biggestWidth = (_g = itemImage === null || itemImage === void 0 ? void 0 : itemImage.artifacts) === null || _g === void 0 ? void 0 : _g.reduce((max, current) => {
|
|
130
|
+
return current.width > max.width ? current : max;
|
|
131
|
+
}, (_h = itemImage === null || itemImage === void 0 ? void 0 : itemImage.artifacts) === null || _h === void 0 ? void 0 : _h[0]);
|
|
132
|
+
return (itemImage === null || itemImage === void 0 ? void 0 : itemImage.rootUrl) + (biggestWidth === null || biggestWidth === void 0 ? void 0 : biggestWidth.fileIdentifyingUrlPathSegment);
|
|
133
|
+
};
|
|
134
|
+
exports.helperGetImageUrl = helperGetImageUrl;
|
package/lib/search.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ISearchParams, ISearchPeopleParams, ISearchPeopleResponse, SearchResponse } from "./types";
|
|
2
|
+
export declare const search: ({ offset, limit, ...opts }: ISearchParams) => Promise<SearchResponse>;
|
|
3
|
+
export declare function searchPeople(queryOrParams: string | ISearchPeopleParams): Promise<ISearchPeopleResponse>;
|