universal-mcp-applications 0.1.22__py3-none-any.whl → 0.1.39rc8__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.
Potentially problematic release.
This version of universal-mcp-applications might be problematic. Click here for more details.
- universal_mcp/applications/ahrefs/app.py +92 -238
- universal_mcp/applications/airtable/app.py +23 -122
- universal_mcp/applications/apollo/app.py +122 -475
- universal_mcp/applications/asana/app.py +605 -1755
- universal_mcp/applications/aws_s3/app.py +36 -103
- universal_mcp/applications/bill/app.py +644 -2055
- universal_mcp/applications/box/app.py +1246 -4159
- universal_mcp/applications/braze/app.py +410 -1476
- universal_mcp/applications/browser_use/README.md +15 -1
- universal_mcp/applications/browser_use/__init__.py +1 -0
- universal_mcp/applications/browser_use/app.py +94 -37
- universal_mcp/applications/cal_com_v2/app.py +207 -625
- universal_mcp/applications/calendly/app.py +103 -242
- universal_mcp/applications/canva/app.py +75 -140
- universal_mcp/applications/clickup/app.py +331 -798
- universal_mcp/applications/coda/app.py +240 -520
- universal_mcp/applications/confluence/app.py +497 -1285
- universal_mcp/applications/contentful/app.py +36 -151
- universal_mcp/applications/crustdata/app.py +42 -121
- universal_mcp/applications/dialpad/app.py +451 -924
- universal_mcp/applications/digitalocean/app.py +2071 -6082
- universal_mcp/applications/domain_checker/app.py +3 -54
- universal_mcp/applications/e2b/app.py +14 -64
- universal_mcp/applications/elevenlabs/app.py +9 -47
- universal_mcp/applications/exa/README.md +8 -4
- universal_mcp/applications/exa/app.py +408 -186
- universal_mcp/applications/falai/app.py +24 -101
- universal_mcp/applications/figma/app.py +91 -175
- universal_mcp/applications/file_system/app.py +2 -13
- universal_mcp/applications/firecrawl/app.py +186 -163
- universal_mcp/applications/fireflies/app.py +59 -281
- universal_mcp/applications/fpl/app.py +92 -529
- universal_mcp/applications/fpl/utils/fixtures.py +15 -49
- universal_mcp/applications/fpl/utils/helper.py +25 -89
- universal_mcp/applications/fpl/utils/league_utils.py +20 -64
- universal_mcp/applications/ghost_content/app.py +66 -175
- universal_mcp/applications/github/app.py +28 -65
- universal_mcp/applications/gong/app.py +140 -300
- universal_mcp/applications/google_calendar/app.py +26 -78
- universal_mcp/applications/google_docs/app.py +324 -354
- universal_mcp/applications/google_drive/app.py +194 -793
- universal_mcp/applications/google_gemini/app.py +29 -64
- universal_mcp/applications/google_mail/README.md +1 -0
- universal_mcp/applications/google_mail/app.py +93 -214
- universal_mcp/applications/google_searchconsole/app.py +25 -58
- universal_mcp/applications/google_sheet/app.py +174 -623
- universal_mcp/applications/google_sheet/helper.py +26 -53
- universal_mcp/applications/hashnode/app.py +57 -269
- universal_mcp/applications/heygen/app.py +77 -155
- universal_mcp/applications/http_tools/app.py +10 -32
- universal_mcp/applications/hubspot/README.md +1 -1
- universal_mcp/applications/hubspot/app.py +7508 -99
- universal_mcp/applications/jira/app.py +2419 -8334
- universal_mcp/applications/klaviyo/app.py +737 -1619
- universal_mcp/applications/linkedin/README.md +23 -4
- universal_mcp/applications/linkedin/app.py +861 -155
- universal_mcp/applications/mailchimp/app.py +696 -1851
- universal_mcp/applications/markitdown/app.py +8 -20
- universal_mcp/applications/miro/app.py +333 -815
- universal_mcp/applications/ms_teams/app.py +85 -207
- universal_mcp/applications/neon/app.py +144 -250
- universal_mcp/applications/notion/app.py +36 -51
- universal_mcp/applications/onedrive/README.md +24 -0
- universal_mcp/applications/onedrive/__init__.py +1 -0
- universal_mcp/applications/onedrive/app.py +316 -0
- universal_mcp/applications/openai/app.py +42 -165
- universal_mcp/applications/outlook/README.md +22 -9
- universal_mcp/applications/outlook/app.py +606 -262
- universal_mcp/applications/perplexity/README.md +2 -1
- universal_mcp/applications/perplexity/app.py +162 -20
- universal_mcp/applications/pipedrive/app.py +1021 -3331
- universal_mcp/applications/posthog/app.py +272 -541
- universal_mcp/applications/reddit/app.py +88 -204
- universal_mcp/applications/resend/app.py +41 -107
- universal_mcp/applications/retell/app.py +23 -50
- universal_mcp/applications/rocketlane/app.py +250 -963
- universal_mcp/applications/scraper/README.md +7 -4
- universal_mcp/applications/scraper/app.py +245 -283
- universal_mcp/applications/semanticscholar/app.py +36 -78
- universal_mcp/applications/semrush/app.py +43 -77
- universal_mcp/applications/sendgrid/app.py +826 -1576
- universal_mcp/applications/sentry/app.py +444 -1079
- universal_mcp/applications/serpapi/app.py +40 -143
- universal_mcp/applications/sharepoint/README.md +16 -14
- universal_mcp/applications/sharepoint/app.py +245 -154
- universal_mcp/applications/shopify/app.py +1743 -4479
- universal_mcp/applications/shortcut/app.py +272 -534
- universal_mcp/applications/slack/app.py +58 -109
- universal_mcp/applications/spotify/app.py +206 -405
- universal_mcp/applications/supabase/app.py +174 -283
- universal_mcp/applications/tavily/app.py +2 -2
- universal_mcp/applications/trello/app.py +853 -2816
- universal_mcp/applications/twilio/app.py +14 -50
- universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
- universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
- universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
- universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
- universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
- universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
- universal_mcp/applications/whatsapp/app.py +35 -186
- universal_mcp/applications/whatsapp/audio.py +2 -6
- universal_mcp/applications/whatsapp/whatsapp.py +17 -51
- universal_mcp/applications/whatsapp_business/app.py +86 -299
- universal_mcp/applications/wrike/app.py +80 -153
- universal_mcp/applications/yahoo_finance/app.py +19 -65
- universal_mcp/applications/youtube/app.py +120 -306
- universal_mcp/applications/zenquotes/app.py +4 -4
- {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/METADATA +4 -2
- {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/RECORD +113 -117
- {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/WHEEL +1 -1
- universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
- universal_mcp/applications/hubspot/api_segments/api_segment_base.py +0 -54
- universal_mcp/applications/hubspot/api_segments/crm_api.py +0 -7337
- universal_mcp/applications/hubspot/api_segments/marketing_api.py +0 -1467
- universal_mcp/applications/unipile/README.md +0 -28
- universal_mcp/applications/unipile/__init__.py +0 -1
- universal_mcp/applications/unipile/app.py +0 -1077
- {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,15 +1,12 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from dotenv import load_dotenv
|
|
2
3
|
|
|
3
4
|
load_dotenv()
|
|
4
|
-
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
5
|
+
from typing import Any, Literal
|
|
7
6
|
from loguru import logger
|
|
8
7
|
from universal_mcp.applications.application import APIApplication
|
|
9
8
|
from universal_mcp.integrations import Integration
|
|
10
9
|
|
|
11
|
-
from universal_mcp.applications.unipile import UnipileApp
|
|
12
|
-
|
|
13
10
|
|
|
14
11
|
class ScraperApp(APIApplication):
|
|
15
12
|
"""
|
|
@@ -18,83 +15,86 @@ class ScraperApp(APIApplication):
|
|
|
18
15
|
"""
|
|
19
16
|
|
|
20
17
|
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
18
|
super().__init__(name="scraper", integration=integration, **kwargs)
|
|
30
19
|
if self.integration:
|
|
31
20
|
credentials = self.integration.get_credentials()
|
|
32
21
|
self.account_id = credentials.get("account_id")
|
|
33
|
-
self._unipile_app = UnipileApp(integration=self.integration)
|
|
34
22
|
else:
|
|
35
23
|
logger.warning("Integration not found")
|
|
36
24
|
self.account_id = None
|
|
37
|
-
self._unipile_app = None
|
|
38
25
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
26
|
+
@property
|
|
27
|
+
def base_url(self) -> str:
|
|
28
|
+
if not self._base_url:
|
|
29
|
+
unipile_dsn = os.getenv("UNIPILE_DSN")
|
|
30
|
+
if not unipile_dsn:
|
|
31
|
+
logger.error("UnipileApp: UNIPILE_DSN environment variable is not set.")
|
|
32
|
+
raise ValueError("UnipileApp: UNIPILE_DSN environment variable is required.")
|
|
33
|
+
self._base_url = f"https://{unipile_dsn}"
|
|
34
|
+
return self._base_url
|
|
35
|
+
|
|
36
|
+
@base_url.setter
|
|
37
|
+
def base_url(self, base_url: str) -> None:
|
|
38
|
+
self._base_url = base_url
|
|
39
|
+
logger.info(f"UnipileApp: Base URL set to {self._base_url}")
|
|
40
|
+
|
|
41
|
+
def _get_headers(self) -> dict[str, str]:
|
|
42
|
+
"""
|
|
43
|
+
Get the headers for Unipile API requests.
|
|
44
|
+
Overrides the base class method to use X-Api-Key.
|
|
45
|
+
"""
|
|
46
|
+
if not self.integration:
|
|
47
|
+
logger.warning("UnipileApp: No integration configured, returning empty headers.")
|
|
48
|
+
return {}
|
|
49
|
+
api_key = os.getenv("UNIPILE_API_KEY")
|
|
50
|
+
if not api_key:
|
|
51
|
+
logger.error("UnipileApp: API key not found in integration credentials for Unipile.")
|
|
52
|
+
return {"Content-Type": "application/json", "Cache-Control": "no-cache"}
|
|
53
|
+
logger.debug("UnipileApp: Using X-Api-Key for authentication.")
|
|
54
|
+
return {"x-api-key": api_key, "Content-Type": "application/json", "Cache-Control": "no-cache"}
|
|
55
|
+
|
|
56
|
+
async def _aget_headers(self) -> dict[str, str]:
|
|
57
|
+
"""
|
|
58
|
+
Get the headers for Unipile API requests asynchronously.
|
|
59
|
+
Overrides the base class method to use X-Api-Key.
|
|
50
60
|
"""
|
|
51
|
-
|
|
61
|
+
return self._get_headers()
|
|
62
|
+
|
|
63
|
+
async def _aget_search_parameter_id(self, param_type: str, keywords: str) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Retrieves the ID for a given LinkedIn search parameter by its name.
|
|
52
66
|
|
|
53
67
|
Args:
|
|
54
|
-
|
|
55
|
-
|
|
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"
|
|
68
|
+
param_type: The type of parameter to search for (e.g., "LOCATION", "COMPANY").
|
|
69
|
+
keywords: The name of the parameter to find (e.g., "United States").
|
|
62
70
|
|
|
63
71
|
Returns:
|
|
64
|
-
|
|
72
|
+
The corresponding ID for the search parameter.
|
|
65
73
|
|
|
66
74
|
Raises:
|
|
75
|
+
ValueError: If no exact match for the keywords is found.
|
|
67
76
|
httpx.HTTPError: If the API request fails.
|
|
68
|
-
|
|
69
|
-
Tags:
|
|
70
|
-
linkedin, search, posts, api, scrapper, important
|
|
71
77
|
"""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
def linkedin_list_profile_posts(
|
|
86
|
-
self,
|
|
87
|
-
identifier: str,
|
|
88
|
-
cursor: str | None = None,
|
|
89
|
-
limit: int | None = None,
|
|
78
|
+
url = f"{self.base_url}/api/v1/linkedin/search/parameters"
|
|
79
|
+
params = {"account_id": self.account_id, "keywords": keywords, "type": param_type}
|
|
80
|
+
response = await self._aget(url, params=params)
|
|
81
|
+
results = self._handle_response(response)
|
|
82
|
+
items = results.get("items", [])
|
|
83
|
+
if items:
|
|
84
|
+
return items[0]["id"]
|
|
85
|
+
raise ValueError(f'Could not find a matching ID for {param_type}: "{keywords}"')
|
|
86
|
+
|
|
87
|
+
async def linkedin_list_profile_posts(
|
|
88
|
+
self, identifier: str, cursor: str | None = None, limit: int | None = None, is_company: bool | None = None
|
|
90
89
|
) -> dict[str, Any]:
|
|
91
90
|
"""
|
|
92
|
-
Fetches a paginated list of
|
|
91
|
+
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.
|
|
93
92
|
|
|
94
93
|
Args:
|
|
95
|
-
identifier: The entity's provider internal ID (LinkedIn ID).
|
|
96
|
-
cursor: Pagination cursor
|
|
97
|
-
limit: Number of items to return (1-100, though spec allows up to 250).
|
|
94
|
+
identifier: The entity's provider internal ID (LinkedIn ID).
|
|
95
|
+
cursor: Pagination cursor.
|
|
96
|
+
limit: Number of items to return (1-100, as per Unipile example, though spec allows up to 250).
|
|
97
|
+
is_company: Boolean indicating if the identifier is for a company.
|
|
98
98
|
|
|
99
99
|
Returns:
|
|
100
100
|
A dictionary containing a list of post objects and pagination details.
|
|
@@ -105,24 +105,23 @@ class ScraperApp(APIApplication):
|
|
|
105
105
|
Tags:
|
|
106
106
|
linkedin, post, list, user_posts, company_posts, content, api, important
|
|
107
107
|
"""
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
limit=limit
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
) -> dict[str, Any]:
|
|
108
|
+
url = f"{self.base_url}/api/v1/users/{identifier}/posts"
|
|
109
|
+
params: dict[str, Any] = {"account_id": self.account_id}
|
|
110
|
+
if cursor:
|
|
111
|
+
params["cursor"] = cursor
|
|
112
|
+
if limit:
|
|
113
|
+
params["limit"] = limit
|
|
114
|
+
if is_company is not None:
|
|
115
|
+
params["is_company"] = is_company
|
|
116
|
+
response = await self._aget(url, params=params)
|
|
117
|
+
return response.json()
|
|
118
|
+
|
|
119
|
+
async def linkedin_retrieve_profile(self, identifier: str) -> dict[str, Any]:
|
|
120
120
|
"""
|
|
121
|
-
|
|
121
|
+
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.
|
|
122
122
|
|
|
123
123
|
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".
|
|
124
|
+
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".
|
|
126
125
|
|
|
127
126
|
Returns:
|
|
128
127
|
A dictionary containing the user's profile details.
|
|
@@ -133,27 +132,22 @@ class ScraperApp(APIApplication):
|
|
|
133
132
|
Tags:
|
|
134
133
|
linkedin, user, profile, retrieve, get, api, important
|
|
135
134
|
"""
|
|
135
|
+
url = f"{self.base_url}/api/v1/users/{identifier}"
|
|
136
|
+
params: dict[str, Any] = {"account_id": self.account_id}
|
|
137
|
+
response = await self._aget(url, params=params)
|
|
138
|
+
return self._handle_response(response)
|
|
136
139
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
account_id=self.account_id,
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
def linkedin_list_post_comments(
|
|
143
|
-
self,
|
|
144
|
-
post_id: str,
|
|
145
|
-
comment_id: str | None = None,
|
|
146
|
-
cursor: str | None = None,
|
|
147
|
-
limit: int | None = None,
|
|
140
|
+
async def linkedin_list_post_comments(
|
|
141
|
+
self, post_id: str, comment_id: str | None = None, cursor: str | None = None, limit: int | None = None
|
|
148
142
|
) -> dict[str, Any]:
|
|
149
143
|
"""
|
|
150
|
-
Fetches comments for a specified LinkedIn post.
|
|
144
|
+
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.
|
|
151
145
|
|
|
152
146
|
Args:
|
|
153
|
-
post_id: The social ID of the post.
|
|
147
|
+
post_id: The social ID of the post.
|
|
154
148
|
comment_id: If provided, retrieves replies to this comment ID instead of top-level comments.
|
|
155
149
|
cursor: Pagination cursor.
|
|
156
|
-
limit: Number of comments to return.
|
|
150
|
+
limit: Number of comments to return. (OpenAPI spec shows type string, passed as string if provided).
|
|
157
151
|
|
|
158
152
|
Returns:
|
|
159
153
|
A dictionary containing a list of comment objects and pagination details.
|
|
@@ -164,231 +158,198 @@ class ScraperApp(APIApplication):
|
|
|
164
158
|
Tags:
|
|
165
159
|
linkedin, post, comment, list, content, api, important
|
|
166
160
|
"""
|
|
161
|
+
url = f"{self.base_url}/api/v1/posts/{post_id}/comments"
|
|
162
|
+
params: dict[str, Any] = {"account_id": self.account_id}
|
|
163
|
+
if cursor:
|
|
164
|
+
params["cursor"] = cursor
|
|
165
|
+
if limit is not None:
|
|
166
|
+
params["limit"] = str(limit)
|
|
167
|
+
if comment_id:
|
|
168
|
+
params["comment_id"] = comment_id
|
|
169
|
+
response = await self._aget(url, params=params)
|
|
170
|
+
return response.json()
|
|
171
|
+
|
|
172
|
+
async def linkedin_search_people(
|
|
173
|
+
self,
|
|
174
|
+
cursor: str | None = None,
|
|
175
|
+
limit: int | None = None,
|
|
176
|
+
keywords: str | None = None,
|
|
177
|
+
location: str | None = None,
|
|
178
|
+
industry: str | None = None,
|
|
179
|
+
company: str | None = None,
|
|
180
|
+
) -> dict[str, Any]:
|
|
181
|
+
"""
|
|
182
|
+
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.
|
|
167
183
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
Args:
|
|
185
|
+
cursor: Pagination cursor for the next page of entries.
|
|
186
|
+
limit: Number of items to return (up to 50 for Classic search).
|
|
187
|
+
keywords: Keywords to search for.
|
|
188
|
+
location: The geographical location to filter people by (e.g., "United States").
|
|
189
|
+
industry: The industry to filter people by.(e.g., "Software Development".)
|
|
190
|
+
company: The company to filter people by.(e.g., "Google".)
|
|
175
191
|
|
|
176
|
-
|
|
192
|
+
Returns:
|
|
193
|
+
A dictionary containing search results and pagination details.
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
httpx.HTTPError: If the API request fails.
|
|
197
|
+
"""
|
|
198
|
+
url = f"{self.base_url}/api/v1/linkedin/search"
|
|
199
|
+
params: dict[str, Any] = {"account_id": self.account_id}
|
|
200
|
+
if cursor:
|
|
201
|
+
params["cursor"] = cursor
|
|
202
|
+
if limit is not None:
|
|
203
|
+
params["limit"] = limit
|
|
204
|
+
payload: dict[str, Any] = {"api": "classic", "category": "people"}
|
|
205
|
+
if keywords:
|
|
206
|
+
payload["keywords"] = keywords
|
|
207
|
+
if location:
|
|
208
|
+
location_id = await self._aget_search_parameter_id("LOCATION", location)
|
|
209
|
+
payload["location"] = [location_id]
|
|
210
|
+
if industry:
|
|
211
|
+
industry_id = await self._aget_search_parameter_id("INDUSTRY", industry)
|
|
212
|
+
payload["industry"] = [industry_id]
|
|
213
|
+
if company:
|
|
214
|
+
company_id = await self._aget_search_parameter_id("COMPANY", company)
|
|
215
|
+
payload["company"] = [company_id]
|
|
216
|
+
response = await self._apost(url, params=params, data=payload)
|
|
217
|
+
return self._handle_response(response)
|
|
218
|
+
|
|
219
|
+
async def linkedin_search_companies(
|
|
177
220
|
self,
|
|
178
221
|
cursor: str | None = None,
|
|
179
222
|
limit: int | None = None,
|
|
180
223
|
keywords: str | None = None,
|
|
181
|
-
|
|
182
|
-
|
|
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,
|
|
224
|
+
location: str | None = None,
|
|
225
|
+
industry: str | None = None,
|
|
220
226
|
) -> dict[str, Any]:
|
|
221
227
|
"""
|
|
222
|
-
|
|
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.
|
|
228
|
+
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.
|
|
225
229
|
|
|
226
230
|
Args:
|
|
227
231
|
cursor: Pagination cursor for the next page of entries.
|
|
228
|
-
limit: Number of items to return.
|
|
229
|
-
keywords:
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
232
|
+
limit: Number of items to return (up to 50 for Classic search).
|
|
233
|
+
keywords: Keywords to search for.
|
|
234
|
+
location: The geographical location to filter companies by (e.g., "United States").
|
|
235
|
+
industry: The industry to filter companies by.(e.g., "Software Development".)
|
|
269
236
|
|
|
270
237
|
Returns:
|
|
271
238
|
A dictionary containing search results and pagination details.
|
|
272
239
|
|
|
273
240
|
Raises:
|
|
274
241
|
httpx.HTTPError: If the API request fails.
|
|
275
|
-
|
|
276
|
-
Tags:
|
|
277
|
-
linkedin, sales_navigator, people, search, advanced, scraper, api, important
|
|
278
242
|
"""
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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(
|
|
243
|
+
url = f"{self.base_url}/api/v1/linkedin/search"
|
|
244
|
+
params: dict[str, Any] = {"account_id": self.account_id}
|
|
245
|
+
if cursor:
|
|
246
|
+
params["cursor"] = cursor
|
|
247
|
+
if limit is not None:
|
|
248
|
+
params["limit"] = limit
|
|
249
|
+
payload: dict[str, Any] = {"api": "classic", "category": "companies"}
|
|
250
|
+
if keywords:
|
|
251
|
+
payload["keywords"] = keywords
|
|
252
|
+
if location:
|
|
253
|
+
location_id = await self._aget_search_parameter_id("LOCATION", location)
|
|
254
|
+
payload["location"] = [location_id]
|
|
255
|
+
if industry:
|
|
256
|
+
industry_id = await self._aget_search_parameter_id("INDUSTRY", industry)
|
|
257
|
+
payload["industry"] = [industry_id]
|
|
258
|
+
response = await self._apost(url, params=params, data=payload)
|
|
259
|
+
return self._handle_response(response)
|
|
260
|
+
|
|
261
|
+
async def linkedin_search_posts(
|
|
326
262
|
self,
|
|
327
263
|
cursor: str | None = None,
|
|
328
264
|
limit: int | None = None,
|
|
329
265
|
keywords: str | None = None,
|
|
330
|
-
|
|
331
|
-
|
|
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,
|
|
266
|
+
date_posted: Literal["past_day", "past_week", "past_month"] | None = None,
|
|
267
|
+
sort_by: Literal["relevance", "date"] = "relevance",
|
|
342
268
|
) -> dict[str, Any]:
|
|
343
269
|
"""
|
|
344
|
-
Performs a
|
|
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.
|
|
270
|
+
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.
|
|
347
271
|
|
|
348
272
|
Args:
|
|
349
273
|
cursor: Pagination cursor for the next page of entries.
|
|
350
|
-
limit: Number of items to return.
|
|
351
|
-
keywords:
|
|
352
|
-
|
|
353
|
-
|
|
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
|
|
274
|
+
limit: Number of items to return (up to 50 for Classic search).
|
|
275
|
+
keywords: Keywords to search for.
|
|
276
|
+
date_posted: Filter by when the post was posted.
|
|
277
|
+
sort_by: How to sort the results.
|
|
364
278
|
|
|
365
279
|
Returns:
|
|
366
280
|
A dictionary containing search results and pagination details.
|
|
367
281
|
|
|
368
282
|
Raises:
|
|
369
283
|
httpx.HTTPError: If the API request fails.
|
|
284
|
+
"""
|
|
285
|
+
url = f"{self.base_url}/api/v1/linkedin/search"
|
|
286
|
+
params: dict[str, Any] = {"account_id": self.account_id}
|
|
287
|
+
if cursor:
|
|
288
|
+
params["cursor"] = cursor
|
|
289
|
+
if limit is not None:
|
|
290
|
+
params["limit"] = limit
|
|
291
|
+
payload: dict[str, Any] = {"api": "classic", "category": "posts"}
|
|
292
|
+
if keywords:
|
|
293
|
+
payload["keywords"] = keywords
|
|
294
|
+
if date_posted:
|
|
295
|
+
payload["date_posted"] = date_posted
|
|
296
|
+
if sort_by:
|
|
297
|
+
payload["sort_by"] = sort_by
|
|
298
|
+
response = await self._apost(url, params=params, data=payload)
|
|
299
|
+
return self._handle_response(response)
|
|
300
|
+
|
|
301
|
+
async def linkedin_search_jobs(
|
|
302
|
+
self,
|
|
303
|
+
cursor: str | None = None,
|
|
304
|
+
limit: int | None = None,
|
|
305
|
+
keywords: str | None = None,
|
|
306
|
+
region: str | None = None,
|
|
307
|
+
sort_by: Literal["relevance", "date"] = "relevance",
|
|
308
|
+
minimum_salary_value: Literal[40, 60, 80, 100, 120, 140, 160, 180, 200] = 40,
|
|
309
|
+
industry: str | None = None,
|
|
310
|
+
) -> dict[str, Any]:
|
|
311
|
+
"""
|
|
312
|
+
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.
|
|
370
313
|
|
|
371
|
-
|
|
372
|
-
|
|
314
|
+
Args:
|
|
315
|
+
cursor: Pagination cursor for the next page of entries.
|
|
316
|
+
limit: Number of items to return (up to 50 for Classic search).
|
|
317
|
+
keywords: Keywords to search for.
|
|
318
|
+
region: The geographical region to filter jobs by (e.g., "United States").
|
|
319
|
+
sort_by: How to sort the results.(e.g., "relevance" or "date".)
|
|
320
|
+
minimum_salary_value: The minimum salary to filter for. Allowed values are 40, 60, 80, 100, 120, 140, 160, 180, 200.
|
|
321
|
+
industry: The industry to filter jobs by. (e.g., "Software Development".)
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
A dictionary containing search results and pagination details.
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
httpx.HTTPError: If the API request fails.
|
|
328
|
+
ValueError: If the specified location is not found.
|
|
373
329
|
"""
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
330
|
+
url = f"{self.base_url}/api/v1/linkedin/search"
|
|
331
|
+
params: dict[str, Any] = {"account_id": self.account_id}
|
|
332
|
+
if cursor:
|
|
333
|
+
params["cursor"] = cursor
|
|
334
|
+
if limit is not None:
|
|
335
|
+
params["limit"] = limit
|
|
336
|
+
payload: dict[str, Any] = {
|
|
337
|
+
"api": "classic",
|
|
338
|
+
"category": "jobs",
|
|
339
|
+
"minimum_salary": {"currency": "USD", "value": minimum_salary_value},
|
|
340
|
+
}
|
|
341
|
+
if keywords:
|
|
342
|
+
payload["keywords"] = keywords
|
|
343
|
+
if sort_by:
|
|
344
|
+
payload["sort_by"] = sort_by
|
|
345
|
+
if region:
|
|
346
|
+
location_id = await self._aget_search_parameter_id("LOCATION", region)
|
|
347
|
+
payload["region"] = location_id
|
|
348
|
+
if industry:
|
|
349
|
+
industry_id = await self._aget_search_parameter_id("INDUSTRY", industry)
|
|
350
|
+
payload["industry"] = [industry_id]
|
|
351
|
+
response = await self._apost(url, params=params, data=payload)
|
|
352
|
+
return self._handle_response(response)
|
|
392
353
|
|
|
393
354
|
def list_tools(self):
|
|
394
355
|
"""
|
|
@@ -398,10 +359,11 @@ class ScraperApp(APIApplication):
|
|
|
398
359
|
A list of functions that can be used as tools.
|
|
399
360
|
"""
|
|
400
361
|
return [
|
|
401
|
-
self.linkedin_post_search,
|
|
402
362
|
self.linkedin_list_profile_posts,
|
|
403
363
|
self.linkedin_retrieve_profile,
|
|
404
364
|
self.linkedin_list_post_comments,
|
|
405
|
-
self.
|
|
406
|
-
self.
|
|
365
|
+
self.linkedin_search_people,
|
|
366
|
+
self.linkedin_search_companies,
|
|
367
|
+
self.linkedin_search_posts,
|
|
368
|
+
self.linkedin_search_jobs,
|
|
407
369
|
]
|