universal-mcp-applications 0.1.25__py3-none-any.whl → 0.1.32__py3-none-any.whl

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.
@@ -1,16 +1,14 @@
1
+ import os
1
2
  from dotenv import load_dotenv
2
3
 
3
4
  load_dotenv()
4
5
 
5
- from typing import Any
6
+ from typing import Any, Literal
6
7
 
7
8
  from loguru import logger
8
9
  from universal_mcp.applications.application import APIApplication
9
10
  from universal_mcp.integrations import Integration
10
11
 
11
- from universal_mcp.applications.unipile import UnipileApp
12
-
13
-
14
12
  class ScraperApp(APIApplication):
15
13
  """
16
14
  Application for interacting with LinkedIn API.
@@ -18,126 +16,151 @@ class ScraperApp(APIApplication):
18
16
  """
19
17
 
20
18
  def __init__(self, integration: Integration, **kwargs: Any) -> None:
21
- """
22
- Initialize the ScraperApp.
23
-
24
- Args:
25
- integration: The integration configuration containing credentials and other settings.
26
- It is expected that the integration provides the necessary credentials
27
- for LinkedIn API access.
28
- """
29
19
  super().__init__(name="scraper", integration=integration, **kwargs)
30
20
  if self.integration:
31
21
  credentials = self.integration.get_credentials()
32
22
  self.account_id = credentials.get("account_id")
33
- self._unipile_app = UnipileApp(integration=self.integration)
34
23
  else:
35
24
  logger.warning("Integration not found")
36
25
  self.account_id = None
37
- self._unipile_app = None
38
26
 
39
- def linkedin_post_search(
40
- self,
41
- category: str = "posts",
42
- api: str = "classic",
43
- cursor: str | None = None,
44
- limit: int | None = None,
45
- keywords: str | None = None,
46
- sort_by: str | None = None,
47
- date_posted: str | None = None,
48
- content_type: str | None = None,
49
- ) -> dict[str, Any]:
27
+ @property
28
+ def base_url(self) -> str:
29
+ if not self._base_url:
30
+ unipile_dsn = os.getenv("UNIPILE_DSN")
31
+ if not unipile_dsn:
32
+ logger.error(
33
+ "UnipileApp: UNIPILE_DSN environment variable is not set."
34
+ )
35
+ raise ValueError(
36
+ "UnipileApp: UNIPILE_DSN environment variable is required."
37
+ )
38
+ self._base_url = f"https://{unipile_dsn}"
39
+ return self._base_url
40
+
41
+ @base_url.setter
42
+ def base_url(self, base_url: str) -> None:
43
+ self._base_url = base_url
44
+ logger.info(f"UnipileApp: Base URL set to {self._base_url}")
45
+
46
+ def _get_headers(self) -> dict[str, str]:
47
+ """
48
+ Get the headers for Unipile API requests.
49
+ Overrides the base class method to use X-Api-Key.
50
50
  """
51
- Performs a general LinkedIn search for posts using keywords and filters like date and content type. It supports pagination and can utilize either the 'classic' or 'sales_navigator' API, searching broadly across the platform rather than fetching posts from a specific user's profile.
51
+ if not self.integration:
52
+ logger.warning(
53
+ "UnipileApp: No integration configured, returning empty headers."
54
+ )
55
+ return {}
56
+
57
+ api_key = os.getenv("UNIPILE_API_KEY")
58
+ if not api_key:
59
+ logger.error(
60
+ "UnipileApp: API key not found in integration credentials for Unipile."
61
+ )
62
+ return { # Or return minimal headers if some calls might not need auth (unlikely for Unipile)
63
+ "Content-Type": "application/json",
64
+ "Cache-Control": "no-cache",
65
+ }
66
+
67
+ logger.debug("UnipileApp: Using X-Api-Key for authentication.")
68
+ return {
69
+ "x-api-key": api_key,
70
+ "Content-Type": "application/json",
71
+ "Cache-Control": "no-cache", # Often good practice for APIs
72
+ }
73
+
74
+ def _get_search_parameter_id(self, param_type: str, keywords: str) -> str:
75
+ """
76
+ Retrieves the ID for a given LinkedIn search parameter by its name.
52
77
 
53
78
  Args:
54
- category: Type of search to perform (defaults to "posts").
55
- api: Which LinkedIn API to use - "classic" or "sales_navigator".
56
- cursor: Pagination cursor for the next page of entries.
57
- limit: Number of items to return (up to 50 for Classic search).
58
- keywords: Keywords to search for.
59
- sort_by: How to sort the results, e.g., "relevance" or "date".
60
- date_posted: Filter posts by when they were posted.
61
- content_type: Filter by the type of content in the post. Example: "videos", "images", "live_videos", "collaborative_articles", "documents"
79
+ param_type: The type of parameter to search for (e.g., "LOCATION", "COMPANY").
80
+ keywords: The name of the parameter to find (e.g., "United States").
62
81
 
63
82
  Returns:
64
- A dictionary containing search results and pagination details.
83
+ The corresponding ID for the search parameter.
65
84
 
66
85
  Raises:
86
+ ValueError: If no exact match for the keywords is found.
67
87
  httpx.HTTPError: If the API request fails.
68
-
69
- Tags:
70
- linkedin, search, posts, api, scrapper, important
71
88
  """
89
+ url = f"{self.base_url}/api/v1/linkedin/search/parameters"
90
+ params = {
91
+ "account_id": self.account_id,
92
+ "keywords": keywords,
93
+ "type": param_type,
94
+ }
95
+
96
+ response = self._get(url, params=params)
97
+ results = self._handle_response(response)
98
+
99
+ items = results.get("items", [])
100
+ if items:
101
+ # Return the ID of the first result, assuming it's the most relevant
102
+ return items[0]["id"]
103
+
104
+ raise ValueError(f'Could not find a matching ID for {param_type}: "{keywords}"')
72
105
 
73
- return self._unipile_app.search(
74
- account_id=self.account_id,
75
- category=category,
76
- api=api,
77
- cursor=cursor,
78
- limit=limit,
79
- keywords=keywords,
80
- sort_by=sort_by,
81
- date_posted=date_posted,
82
- content_type=content_type,
83
- )
84
106
 
85
107
  def linkedin_list_profile_posts(
86
108
  self,
87
- identifier: str,
109
+ identifier: str, # User or Company provider internal ID
88
110
  cursor: str | None = None,
89
- limit: int | None = None,
111
+ limit: int | None = None, # 1-100 (spec says max 250)
112
+ is_company: bool | None = None,
90
113
  ) -> dict[str, Any]:
91
114
  """
92
- Fetches a paginated list of all LinkedIn posts from a specific user or company profile using their unique identifier. This function retrieves content directly from a profile, unlike `linkedin_post_search` which finds posts across LinkedIn based on keywords and other filters.
93
-
115
+ Fetches a paginated list of posts from a specific user or company profile using its provider ID. The `is_company` flag must specify the entity type. Unlike `linkedin_search_posts`, this function directly retrieves content from a known profile's feed instead of performing a global keyword search.
116
+
94
117
  Args:
95
- identifier: The entity's provider internal ID (LinkedIn ID).starts with ACo for users, while for companies it's a series of numbers.
96
- cursor: Pagination cursor for the next page of entries.
97
- limit: Number of items to return (1-100, though spec allows up to 250).
98
-
118
+ identifier: The entity's provider internal ID (LinkedIn ID).
119
+ cursor: Pagination cursor.
120
+ limit: Number of items to return (1-100, as per Unipile example, though spec allows up to 250).
121
+ is_company: Boolean indicating if the identifier is for a company.
122
+
99
123
  Returns:
100
124
  A dictionary containing a list of post objects and pagination details.
101
-
125
+
102
126
  Raises:
103
127
  httpx.HTTPError: If the API request fails.
104
-
128
+
105
129
  Tags:
106
130
  linkedin, post, list, user_posts, company_posts, content, api, important
107
131
  """
108
-
109
- return self._unipile_app.list_profile_posts(
110
- identifier=identifier,
111
- account_id=self.account_id,
112
- cursor=cursor,
113
- limit=limit,
114
- )
115
-
116
- def linkedin_retrieve_profile(
117
- self,
118
- identifier: str,
119
- ) -> dict[str, Any]:
132
+ url = f"{self.base_url}/api/v1/users/{identifier}/posts"
133
+ params: dict[str, Any] = {"account_id": self.account_id}
134
+ if cursor:
135
+ params["cursor"] = cursor
136
+ if limit:
137
+ params["limit"] = limit
138
+ if is_company is not None:
139
+ params["is_company"] = is_company
140
+
141
+ response = self._get(url, params=params)
142
+ return response.json()
143
+
144
+ def linkedin_retrieve_profile(self, identifier: str) -> dict[str, Any]:
120
145
  """
121
- Retrieves a specific LinkedIn user's profile by their unique identifier, which can be an internal provider ID or a public username. This function simplifies data access by delegating the actual profile retrieval request to the integrated Unipile application, distinct from functions that list posts or comments.
122
-
146
+ Fetches a specific LinkedIn user's profile using their public or internal ID. Unlike `linkedin_search_people`, which discovers multiple users via keywords, this function targets and retrieves detailed data for a single, known individual based on a direct identifier.
147
+
123
148
  Args:
124
- identifier: Can be the provider's internal id OR the provider's public id of the requested user.
125
- For example, for https://www.linkedin.com/in/manojbajaj95/, the identifier is "manojbajaj95".
126
-
149
+ identifier: Can be the provider's internal id OR the provider's public id of the requested user.For example, for https://www.linkedin.com/in/manojbajaj95/, the identifier is "manojbajaj95".
150
+
127
151
  Returns:
128
152
  A dictionary containing the user's profile details.
129
-
153
+
130
154
  Raises:
131
155
  httpx.HTTPError: If the API request fails.
132
-
156
+
133
157
  Tags:
134
158
  linkedin, user, profile, retrieve, get, api, important
135
159
  """
136
-
137
- return self._unipile_app.retrieve_user_profile(
138
- identifier=identifier,
139
- account_id=self.account_id,
140
- )
160
+ url = f"{self.base_url}/api/v1/users/{identifier}"
161
+ params: dict[str, Any] = {"account_id": self.account_id}
162
+ response = self._get(url, params=params)
163
+ return self._handle_response(response)
141
164
 
142
165
  def linkedin_list_post_comments(
143
166
  self,
@@ -147,248 +170,238 @@ class ScraperApp(APIApplication):
147
170
  limit: int | None = None,
148
171
  ) -> dict[str, Any]:
149
172
  """
150
- Fetches comments for a specified LinkedIn post. If a `comment_id` is provided, it retrieves replies to that comment instead of top-level comments. This function supports pagination and specifically targets comments, unlike others in the class that search for or list entire posts.
151
-
173
+ Fetches a paginated list of comments for a specified LinkedIn post. It can retrieve either top-level comments or threaded replies if an optional `comment_id` is provided. This is a read-only operation, distinct from functions that search for posts or list user-specific content.
174
+
152
175
  Args:
153
- post_id: The social ID of the post. Example rn:li:activity:7342082869034393600
176
+ post_id: The social ID of the post.
154
177
  comment_id: If provided, retrieves replies to this comment ID instead of top-level comments.
155
178
  cursor: Pagination cursor.
156
- limit: Number of comments to return.
157
-
179
+ limit: Number of comments to return. (OpenAPI spec shows type string, passed as string if provided).
180
+
158
181
  Returns:
159
182
  A dictionary containing a list of comment objects and pagination details.
160
-
183
+
161
184
  Raises:
162
185
  httpx.HTTPError: If the API request fails.
163
-
186
+
164
187
  Tags:
165
188
  linkedin, post, comment, list, content, api, important
166
189
  """
167
-
168
- return self._unipile_app.list_post_comments(
169
- post_id=post_id,
170
- account_id=self.account_id,
171
- comment_id=comment_id,
172
- cursor=cursor,
173
- limit=limit,
174
- )
175
-
176
- def linkedin_people_search(
190
+ url = f"{self.base_url}/api/v1/posts/{post_id}/comments"
191
+ params: dict[str, Any] = {"account_id": self.account_id}
192
+ if cursor:
193
+ params["cursor"] = cursor
194
+ if limit is not None:
195
+ params["limit"] = str(limit)
196
+ if comment_id:
197
+ params["comment_id"] = comment_id
198
+
199
+ response = self._get(url, params=params)
200
+ return response.json()
201
+
202
+ def linkedin_search_people(
177
203
  self,
178
204
  cursor: str | None = None,
179
205
  limit: int | None = None,
180
206
  keywords: str | None = None,
181
- last_viewed_at: int | None = None,
182
- saved_search_id: str | None = None,
183
- recent_search_id: str | None = None,
184
- location: dict[str, Any] | None = None,
185
- location_by_postal_code: dict[str, Any] | None = None,
186
- industry: dict[str, Any] | None = None,
187
- first_name: str | None = None,
188
- last_name: str | None = None,
189
- tenure: list[dict[str, Any]] | None = None,
190
- groups: list[str] | None = None,
191
- school: dict[str, Any] | None = None,
192
- profile_language: list[str] | None = None,
193
- company: dict[str, Any] | None = None,
194
- company_headcount: list[dict[str, Any]] | None = None,
195
- company_type: list[str] | None = None,
196
- company_location: dict[str, Any] | None = None,
197
- tenure_at_company: list[dict[str, Any]] | None = None,
198
- past_company: dict[str, Any] | None = None,
199
- function: dict[str, Any] | None = None,
200
- role: dict[str, Any] | None = None,
201
- tenure_at_role: list[dict[str, Any]] | None = None,
202
- seniority: dict[str, Any] | None = None,
203
- past_role: dict[str, Any] | None = None,
204
- following_your_company: bool | None = None,
205
- viewed_your_profile_recently: bool | None = None,
206
- network_distance: list[str] | None = None,
207
- connections_of: list[str] | None = None,
208
- past_colleague: bool | None = None,
209
- shared_experiences: bool | None = None,
210
- changed_jobs: bool | None = None,
211
- posted_on_linkedin: bool | None = None,
212
- mentionned_in_news: bool | None = None,
213
- persona: list[str] | None = None,
214
- account_lists: dict[str, Any] | None = None,
215
- lead_lists: dict[str, Any] | None = None,
216
- viewed_profile_recently: bool | None = None,
217
- messaged_recently: bool | None = None,
218
- include_saved_leads: bool | None = None,
219
- include_saved_accounts: bool | None = None,
207
+ location: str | None = None,
208
+ industry: str | None = None,
209
+ company: str | None = None,
220
210
  ) -> dict[str, Any]:
221
211
  """
222
- Performs a comprehensive LinkedIn Sales Navigator people search with advanced targeting options.
223
- This function provides access to LinkedIn's Sales Navigator search capabilities for finding people
224
- with precise filters including experience, company details, education, and relationship criteria.
225
-
212
+ Performs a paginated search for people on LinkedIn, distinct from searches for companies or jobs. It filters results using keywords, location, industry, and company, internally converting filter names like 'United States' into their required API IDs before making the request.
213
+
226
214
  Args:
227
215
  cursor: Pagination cursor for the next page of entries.
228
- limit: Number of items to return.
229
- keywords: LinkedIn native filter: KEYWORDS.
230
- last_viewed_at: Unix timestamp for saved search filtering.
231
- saved_search_id: ID of saved search (overrides other parameters).
232
- recent_search_id: ID of recent search (overrides other parameters).
233
- location: LinkedIn native filter: GEOGRAPHY. Example: {"include": ["San Francisco Bay Area", "New York City Area"]}
234
- location_by_postal_code: Location filter by postal code. Example: {"postal_code": "94105", "radius": "25"}
235
- industry: LinkedIn native filter: INDUSTRY. Example: {"include": ["Information Technology and Services", "Financial Services"]}
236
- first_name: LinkedIn native filter: FIRST NAME. Example: "John"
237
- last_name: LinkedIn native filter: LAST NAME. Example: "Smith"
238
- tenure: LinkedIn native filter: YEARS OF EXPERIENCE. Example: [{"min": 5, "max": 10}]
239
- groups: LinkedIn native filter: GROUPS. Example: ["group_id_1", "group_id_2"]
240
- school: LinkedIn native filter: SCHOOL. Example: {"include": ["Stanford University", "Harvard University"]}
241
- profile_language: ISO 639-1 language codes, LinkedIn native filter: PROFILE LANGUAGE. Example: ["en", "es"]
242
- company: LinkedIn native filter: CURRENT COMPANY. Example: {"include": ["Google", "Microsoft", "Apple"]}
243
- company_headcount: LinkedIn native filter: COMPANY HEADCOUNT. Example: [{"min": 100, "max": 1000}]
244
- company_type: LinkedIn native filter: COMPANY TYPE. Example: ["Public Company", "Privately Held"]
245
- company_location: LinkedIn native filter: COMPANY HEADQUARTERS LOCATION. Example: {"include": ["San Francisco", "Seattle"]}
246
- tenure_at_company: LinkedIn native filter: YEARS IN CURRENT COMPANY. Example: [{"min": 2, "max": 5}]
247
- past_company: LinkedIn native filter: PAST COMPANY. Example: {"include": ["Facebook", "Amazon"]}
248
- function: LinkedIn native filter: FUNCTION. Example: {"include": ["Engineering", "Sales", "Marketing"]}
249
- role: LinkedIn native filter: CURRENT JOB TITLE. Example: {"include": ["Software Engineer", "Product Manager"]}
250
- tenure_at_role: LinkedIn native filter: YEARS IN CURRENT POSITION. Example: [{"min": 1, "max": 3}]
251
- seniority: LinkedIn native filter: SENIORITY LEVEL. Example: {"include": ["Senior", "Director", "VP"]}
252
- past_role: LinkedIn native filter: PAST JOB TITLE. Example: {"include": ["Senior Developer", "Team Lead"]}
253
- following_your_company: LinkedIn native filter: FOLLOWING YOUR COMPANY. Example: True
254
- viewed_your_profile_recently: LinkedIn native filter: VIEWED YOUR PROFILE RECENTLY. Example: True
255
- network_distance: First, second, third+ degree or GROUP, LinkedIn native filter: CONNECTION. Example: ["1st", "2nd"]
256
- connections_of: LinkedIn native filter: CONNECTIONS OF. Example: ["person_id_1", "person_id_2"]
257
- past_colleague: LinkedIn native filter: PAST COLLEAGUE. Example: True
258
- shared_experiences: LinkedIn native filter: SHARED EXPERIENCES. Example: True
259
- changed_jobs: LinkedIn native filter: CHANGED JOBS. Example: True
260
- posted_on_linkedin: LinkedIn native filter: POSTED ON LINKEDIN. Example: True
261
- mentionned_in_news: LinkedIn native filter: MENTIONNED IN NEWS. Example: True
262
- persona: LinkedIn native filter: PERSONA. Example: ["persona_id_1", "persona_id_2"]
263
- account_lists: LinkedIn native filter: ACCOUNT LISTS. Example: {"include": ["list_id_1"]}
264
- lead_lists: LinkedIn native filter: LEAD LISTS. Example: {"include": ["lead_list_id_1"]}
265
- viewed_profile_recently: LinkedIn native filter: PEOPLE YOU INTERACTED WITH / VIEWED PROFILE. Example: True
266
- messaged_recently: LinkedIn native filter: PEOPLE YOU INTERACTED WITH / MESSAGED. Example: True
267
- include_saved_leads: LinkedIn native filter: SAVED LEADS AND ACCOUNTS / ALL MY SAVED LEADS. Example: True
268
- include_saved_accounts: LinkedIn native filter: SAVED LEADS AND ACCOUNTS / ALL MY SAVED ACCOUNTS. Example: True
269
-
216
+ limit: Number of items to return (up to 50 for Classic search).
217
+ keywords: Keywords to search for.
218
+
270
219
  Returns:
271
220
  A dictionary containing search results and pagination details.
272
-
221
+
273
222
  Raises:
274
223
  httpx.HTTPError: If the API request fails.
275
-
276
- Tags:
277
- linkedin, sales_navigator, people, search, advanced, scraper, api, important
278
224
  """
279
- return self._unipile_app.people_search(
280
- account_id=self.account_id,
281
- cursor=cursor,
282
- limit=limit,
283
- keywords=keywords,
284
- last_viewed_at=last_viewed_at,
285
- saved_search_id=saved_search_id,
286
- recent_search_id=recent_search_id,
287
- location=location,
288
- location_by_postal_code=location_by_postal_code,
289
- industry=industry,
290
- first_name=first_name,
291
- last_name=last_name,
292
- tenure=tenure,
293
- groups=groups,
294
- school=school,
295
- profile_language=profile_language,
296
- company=company,
297
- company_headcount=company_headcount,
298
- company_type=company_type,
299
- company_location=company_location,
300
- tenure_at_company=tenure_at_company,
301
- past_company=past_company,
302
- function=function,
303
- role=role,
304
- tenure_at_role=tenure_at_role,
305
- seniority=seniority,
306
- past_role=past_role,
307
- following_your_company=following_your_company,
308
- viewed_your_profile_recently=viewed_your_profile_recently,
309
- network_distance=network_distance,
310
- connections_of=connections_of,
311
- past_colleague=past_colleague,
312
- shared_experiences=shared_experiences,
313
- changed_jobs=changed_jobs,
314
- posted_on_linkedin=posted_on_linkedin,
315
- mentionned_in_news=mentionned_in_news,
316
- persona=persona,
317
- account_lists=account_lists,
318
- lead_lists=lead_lists,
319
- viewed_profile_recently=viewed_profile_recently,
320
- messaged_recently=messaged_recently,
321
- include_saved_leads=include_saved_leads,
322
- include_saved_accounts=include_saved_accounts,
323
- )
324
-
325
- def linkedin_company_search(
225
+ url = f"{self.base_url}/api/v1/linkedin/search"
226
+
227
+ params: dict[str, Any] = {"account_id": self.account_id}
228
+ if cursor:
229
+ params["cursor"] = cursor
230
+ if limit is not None:
231
+ params["limit"] = limit
232
+
233
+ payload: dict[str, Any] = {"api": "classic", "category": "people"}
234
+
235
+ if keywords:
236
+ payload["keywords"] = keywords
237
+
238
+ if location:
239
+ location_id = self._get_search_parameter_id("LOCATION", location)
240
+ payload["location"] = [location_id]
241
+
242
+ if industry:
243
+ industry_id = self._get_search_parameter_id("INDUSTRY", industry)
244
+ payload["industry"] = [industry_id]
245
+
246
+ if company:
247
+ company_id = self._get_search_parameter_id("COMPANY", company)
248
+ payload["company"] = [company_id]
249
+
250
+ response = self._post(url, params=params, data=payload)
251
+ return self._handle_response(response)
252
+
253
+ def linkedin_search_companies(
326
254
  self,
327
255
  cursor: str | None = None,
328
256
  limit: int | None = None,
329
257
  keywords: str | None = None,
330
- last_viewed_at: int | None = None,
331
- saved_search_id: str | None = None,
332
- recent_search_id: str | None = None,
333
- location: dict[str, Any] | None = None,
334
- location_by_postal_code: dict[str, Any] | None = None,
335
- industry: dict[str, Any] | None = None,
336
- company_headcount: list[dict[str, Any]] | None = None,
337
- company_type: list[str] | None = None,
338
- company_location: dict[str, Any] | None = None,
339
- following_your_company: bool | None = None,
340
- account_lists: dict[str, Any] | None = None,
341
- include_saved_accounts: bool | None = None,
258
+ location: str | None = None,
259
+ industry: str | None = None,
342
260
  ) -> dict[str, Any]:
343
261
  """
344
- Performs a comprehensive LinkedIn Sales Navigator company search with advanced targeting options.
345
- This function provides access to LinkedIn's Sales Navigator search capabilities for finding companies
346
- with precise filters including size, location, industry, and relationship criteria.
347
-
262
+ Executes a paginated LinkedIn search for companies, filtering by optional keywords, location, and industry. Unlike `linkedin_search_people` or `linkedin_search_jobs`, this function specifically sets the API search category to 'companies' to ensure that only company profiles are returned in the search results.
263
+
348
264
  Args:
349
265
  cursor: Pagination cursor for the next page of entries.
350
- limit: Number of items to return.
351
- keywords: LinkedIn native filter: KEYWORDS. Example: "fintech startup"
352
- last_viewed_at: Unix timestamp for saved search filtering.
353
- saved_search_id: ID of saved search (overrides other parameters).
354
- recent_search_id: ID of recent search (overrides other parameters).
355
- location: LinkedIn native filter: GEOGRAPHY. Example: {"include": ["San Francisco Bay Area", "New York City Area"]}
356
- location_by_postal_code: Location filter by postal code. Example: {"postal_code": "94105", "radius": "25"}
357
- industry: LinkedIn native filter: INDUSTRY. Example: {"include": ["Information Technology and Services", "Financial Services"]}
358
- company_headcount: LinkedIn native filter: COMPANY HEADCOUNT. Example: [{"min": 10, "max": 100}]
359
- company_type: LinkedIn native filter: COMPANY TYPE. Example: ["Public Company", "Privately Held", "Startup"]
360
- company_location: LinkedIn native filter: COMPANY HEADQUARTERS LOCATION. Example: {"include": ["San Francisco", "Seattle", "Austin"]}
361
- following_your_company: LinkedIn native filter: FOLLOWING YOUR COMPANY. Example: True
362
- account_lists: LinkedIn native filter: ACCOUNT LISTS. Example: {"include": ["account_list_id_1"]}
363
- include_saved_accounts: LinkedIn native filter: SAVED LEADS AND ACCOUNTS / ALL MY SAVED ACCOUNTS. Example: True
364
-
266
+ limit: Number of items to return (up to 50 for Classic search).
267
+ keywords: Keywords to search for.
268
+
365
269
  Returns:
366
270
  A dictionary containing search results and pagination details.
271
+
272
+ Raises:
273
+ httpx.HTTPError: If the API request fails.
274
+ """
275
+ url = f"{self.base_url}/api/v1/linkedin/search"
276
+
277
+ params: dict[str, Any] = {"account_id": self.account_id}
278
+ if cursor:
279
+ params["cursor"] = cursor
280
+ if limit is not None:
281
+ params["limit"] = limit
282
+
283
+ payload: dict[str, Any] = {"api": "classic", "category": "companies"}
367
284
 
285
+ if keywords:
286
+ payload["keywords"] = keywords
287
+
288
+ if location:
289
+ location_id = self._get_search_parameter_id("LOCATION", location)
290
+ payload["location"] = [location_id]
291
+
292
+ if industry:
293
+ industry_id = self._get_search_parameter_id("INDUSTRY", industry)
294
+ payload["industry"] = [industry_id]
295
+
296
+ response = self._post(url, params=params, data=payload)
297
+ return self._handle_response(response)
298
+
299
+ def linkedin_search_posts(
300
+ self,
301
+ cursor: str | None = None,
302
+ limit: int | None = None,
303
+ keywords: str | None = None,
304
+ date_posted: Literal["past_day", "past_week", "past_month"] | None = None,
305
+ sort_by: Literal["relevance", "date"] = "relevance",
306
+ ) -> dict[str, Any]:
307
+ """
308
+ Performs a keyword-based search for LinkedIn posts, allowing results to be filtered by date and sorted by relevance. This function specifically queries the 'posts' category, distinguishing it from other search methods in the class that target people, companies, or jobs, and returns relevant content.
309
+
310
+ Args:
311
+ cursor: Pagination cursor for the next page of entries.
312
+ limit: Number of items to return (up to 50 for Classic search).
313
+ keywords: Keywords to search for.
314
+ date_posted: Filter by when the post was posted.
315
+ sort_by: How to sort the results.
316
+
317
+ Returns:
318
+ A dictionary containing search results and pagination details.
319
+
368
320
  Raises:
369
321
  httpx.HTTPError: If the API request fails.
322
+ """
323
+ url = f"{self.base_url}/api/v1/linkedin/search"
370
324
 
371
- Tags:
372
- linkedin, sales_navigator, companies, search, advanced, scraper, api, important
325
+ params: dict[str, Any] = {"account_id": self.account_id}
326
+ if cursor:
327
+ params["cursor"] = cursor
328
+ if limit is not None:
329
+ params["limit"] = limit
330
+
331
+ payload: dict[str, Any] = {"api": "classic", "category": "posts"}
332
+
333
+ if keywords:
334
+ payload["keywords"] = keywords
335
+ if date_posted:
336
+ payload["date_posted"] = date_posted
337
+ if sort_by:
338
+ payload["sort_by"] = sort_by
339
+
340
+ response = self._post(url, params=params, data=payload)
341
+ return self._handle_response(response)
342
+
343
+ def linkedin_search_jobs(
344
+ self,
345
+ cursor: str | None = None,
346
+ limit: int | None = None,
347
+ keywords: str | None = None,
348
+ region: str | None = None,
349
+ sort_by: Literal["relevance", "date"] = "relevance",
350
+ minimum_salary_value: int = 40,
351
+ industry: str | None = None,
352
+ ) -> dict[str, Any]:
353
+ """
354
+ Executes a LinkedIn search specifically for job listings using keywords and filters like region, industry, and minimum salary. Unlike other search functions targeting people or companies, this is specialized for job listings and converts friendly filter names (e.g., "United States") into their required API IDs.
355
+
356
+ Args:
357
+ cursor: Pagination cursor for the next page of entries.
358
+ limit: Number of items to return (up to 50 for Classic search).
359
+ keywords: Keywords to search for.
360
+ location: The geographical location to filter jobs by (e.g., "United States").
361
+ sort_by: How to sort the results.
362
+ minimum_salary_value: The minimum salary to filter for.
363
+
364
+ Returns:
365
+ A dictionary containing search results and pagination details.
366
+
367
+ Raises:
368
+ httpx.HTTPError: If the API request fails.
369
+ ValueError: If the specified location is not found.
373
370
  """
374
- return self._unipile_app.company_search(
375
- account_id=self.account_id,
376
- cursor=cursor,
377
- limit=limit,
378
- keywords=keywords,
379
- last_viewed_at=last_viewed_at,
380
- saved_search_id=saved_search_id,
381
- recent_search_id=recent_search_id,
382
- location=location,
383
- location_by_postal_code=location_by_postal_code,
384
- industry=industry,
385
- company_headcount=company_headcount,
386
- company_type=company_type,
387
- company_location=company_location,
388
- following_your_company=following_your_company,
389
- account_lists=account_lists,
390
- include_saved_accounts=include_saved_accounts,
391
- )
371
+ url = f"{self.base_url}/api/v1/linkedin/search"
372
+
373
+ params: dict[str, Any] = {"account_id": self.account_id}
374
+ if cursor:
375
+ params["cursor"] = cursor
376
+ if limit is not None:
377
+ params["limit"] = limit
378
+
379
+ payload: dict[str, Any] = {
380
+ "api": "classic",
381
+ "category": "jobs",
382
+ "minimum_salary": {
383
+ "currency": "USD",
384
+ "value": minimum_salary_value,
385
+ },
386
+ }
387
+
388
+ if keywords:
389
+ payload["keywords"] = keywords
390
+ if sort_by:
391
+ payload["sort_by"] = sort_by
392
+
393
+ # If location is provided, get its ID and add it to the payload
394
+ if region:
395
+ location_id = self._get_search_parameter_id("LOCATION", region)
396
+ payload["region"] = location_id
397
+
398
+ if industry:
399
+ industry_id = self._get_search_parameter_id("INDUSTRY", industry)
400
+ payload["industry"] = [industry_id]
401
+
402
+ response = self._post(url, params=params, data=payload)
403
+ return self._handle_response(response)
404
+
392
405
 
393
406
  def list_tools(self):
394
407
  """
@@ -398,10 +411,11 @@ class ScraperApp(APIApplication):
398
411
  A list of functions that can be used as tools.
399
412
  """
400
413
  return [
401
- self.linkedin_post_search,
402
414
  self.linkedin_list_profile_posts,
403
415
  self.linkedin_retrieve_profile,
404
416
  self.linkedin_list_post_comments,
405
- self.linkedin_people_search,
406
- self.linkedin_company_search,
417
+ self.linkedin_search_people,
418
+ self.linkedin_search_companies,
419
+ self.linkedin_search_posts,
420
+ self.linkedin_search_jobs,
407
421
  ]