linkedapi 1.3.8__tar.gz → 1.3.11__tar.gz
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.
- {linkedapi-1.3.8 → linkedapi-1.3.11}/PKG-INFO +1 -1
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/__init__.py +5 -1
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/client.py +6 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/errors.py +4 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/__init__.py +4 -0
- linkedapi-1.3.11/linkedapi/operations/retrieve_profile_viewers.py +15 -0
- linkedapi-1.3.11/linkedapi/operations/search_posts.py +15 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/__init__.py +40 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/jobs.py +48 -1
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/message.py +3 -0
- linkedapi-1.3.11/linkedapi/types/profile_viewers.py +82 -0
- linkedapi-1.3.11/linkedapi/types/search_posts.py +103 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/webhooks.py +3 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/pyproject.toml +1 -1
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_errors.py +2 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_operations.py +158 -1
- {linkedapi-1.3.8 → linkedapi-1.3.11}/.github/workflows/publish.yaml +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/.gitignore +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/LICENSE +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/README.md +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/__init__.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/accounts.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/admin.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/http_client.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/limits.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/subscription.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/webhooks.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/config.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/core/__init__.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/core/operation.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/core/polling.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/http/__init__.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/http/base.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/http/linked_api_http_client.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/__init__.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/array.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/base.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/send_message.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/simple.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/then.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/void.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/accept_invitation.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/check_connection_status.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/comment_on_post.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/create_post.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/custom_workflow.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/fetch_company.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/fetch_job.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/fetch_person.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/fetch_post.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/ignore_invitation.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/manage_conversation.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_fetch_company.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_fetch_person.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_manage_conversation.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_search_companies.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_search_people.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_send_message.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_sync_conversation.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_sync_inbox.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/react_to_comment.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/react_to_post.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/remove_connection.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/reply_to_comment.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_connections.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_feed.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_invitations.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_pending_requests.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_performance.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_ssi.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/search_companies.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/search_jobs.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/search_people.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/send_connection_request.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/send_message.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/sync_conversation.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/sync_inbox.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/sync_network.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/withdraw_connection_request.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/py.typed +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/account.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/__init__.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/accounts.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/config.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/limits.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/subscription.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/base.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/company.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/connection.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/feed.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/network.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/params.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/person.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/post.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/responses.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/search_companies.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/search_people.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/statistics.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/workflow.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/webhooks/__init__.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/webhooks/parse.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/conftest.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_admin.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_direct_methods.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_http_client.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_mappers.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_models.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_polling.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_webhooks.py +0 -0
- {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_working_hours_contract.py +0 -0
|
@@ -63,10 +63,12 @@ from linkedapi.operations import (
|
|
|
63
63
|
RetrieveInvitations,
|
|
64
64
|
RetrievePendingRequests,
|
|
65
65
|
RetrievePerformance,
|
|
66
|
+
RetrieveProfileViewers,
|
|
66
67
|
RetrieveSSI,
|
|
67
68
|
SearchCompanies,
|
|
68
69
|
SearchJobs,
|
|
69
70
|
SearchPeople,
|
|
71
|
+
SearchPosts,
|
|
70
72
|
SendConnectionRequest,
|
|
71
73
|
SendMessage,
|
|
72
74
|
SyncConversation,
|
|
@@ -78,7 +80,7 @@ from linkedapi.types import * # noqa: F403
|
|
|
78
80
|
from linkedapi.types import __all__ as _types_all
|
|
79
81
|
from linkedapi.webhooks import parse_webhook_event
|
|
80
82
|
|
|
81
|
-
__version__ = "1.3.
|
|
83
|
+
__version__ = "1.3.11"
|
|
82
84
|
PredefinedOperation = Operation
|
|
83
85
|
|
|
84
86
|
__all__ = [
|
|
@@ -140,10 +142,12 @@ __all__ = [
|
|
|
140
142
|
"RetrieveFeed",
|
|
141
143
|
"RetrievePendingRequests",
|
|
142
144
|
"RetrievePerformance",
|
|
145
|
+
"RetrieveProfileViewers",
|
|
143
146
|
"RetrieveSSI",
|
|
144
147
|
"SearchCompanies",
|
|
145
148
|
"SearchJobs",
|
|
146
149
|
"SearchPeople",
|
|
150
|
+
"SearchPosts",
|
|
147
151
|
"SendConnectionRequest",
|
|
148
152
|
"SendMessage",
|
|
149
153
|
"SimpleWorkflowMapper",
|
|
@@ -36,10 +36,12 @@ from linkedapi.operations import (
|
|
|
36
36
|
RetrieveInvitations,
|
|
37
37
|
RetrievePendingRequests,
|
|
38
38
|
RetrievePerformance,
|
|
39
|
+
RetrieveProfileViewers,
|
|
39
40
|
RetrieveSSI,
|
|
40
41
|
SearchCompanies,
|
|
41
42
|
SearchJobs,
|
|
42
43
|
SearchPeople,
|
|
44
|
+
SearchPosts,
|
|
43
45
|
SendConnectionRequest,
|
|
44
46
|
SendMessage,
|
|
45
47
|
SyncConversation,
|
|
@@ -86,6 +88,7 @@ class LinkedApi:
|
|
|
86
88
|
self.search_companies = SearchCompanies(self.http_client)
|
|
87
89
|
self.search_people = SearchPeople(self.http_client)
|
|
88
90
|
self.search_jobs = SearchJobs(self.http_client)
|
|
91
|
+
self.search_posts = SearchPosts(self.http_client)
|
|
89
92
|
self.fetch_company = FetchCompany(self.http_client)
|
|
90
93
|
self.fetch_person = FetchPerson(self.http_client)
|
|
91
94
|
self.fetch_post = FetchPost(self.http_client)
|
|
@@ -98,6 +101,7 @@ class LinkedApi:
|
|
|
98
101
|
self.retrieve_feed = RetrieveFeed(self.http_client)
|
|
99
102
|
self.retrieve_ssi = RetrieveSSI(self.http_client)
|
|
100
103
|
self.retrieve_performance = RetrievePerformance(self.http_client)
|
|
104
|
+
self.retrieve_profile_viewers = RetrieveProfileViewers(self.http_client)
|
|
101
105
|
self.nv_send_message = NvSendMessage(self.http_client)
|
|
102
106
|
self.nv_sync_conversation = NvSyncConversation(self.http_client)
|
|
103
107
|
self.nv_sync_inbox = NvSyncInbox(self.http_client)
|
|
@@ -126,6 +130,7 @@ class LinkedApi:
|
|
|
126
130
|
self.search_companies,
|
|
127
131
|
self.search_people,
|
|
128
132
|
self.search_jobs,
|
|
133
|
+
self.search_posts,
|
|
129
134
|
self.fetch_company,
|
|
130
135
|
self.fetch_person,
|
|
131
136
|
self.fetch_post,
|
|
@@ -138,6 +143,7 @@ class LinkedApi:
|
|
|
138
143
|
self.retrieve_feed,
|
|
139
144
|
self.retrieve_ssi,
|
|
140
145
|
self.retrieve_performance,
|
|
146
|
+
self.retrieve_profile_viewers,
|
|
141
147
|
self.nv_send_message,
|
|
142
148
|
self.nv_sync_conversation,
|
|
143
149
|
self.nv_sync_inbox,
|
|
@@ -16,6 +16,8 @@ LinkedApiActionErrorType = Literal[
|
|
|
16
16
|
"retrievingNotAllowed",
|
|
17
17
|
"connectionNotFound",
|
|
18
18
|
"searchingNotAllowed",
|
|
19
|
+
"searchInterfaceMismatch",
|
|
20
|
+
"filterIdentityMismatch",
|
|
19
21
|
"companyNotFound",
|
|
20
22
|
"postNotFound",
|
|
21
23
|
"jobNotFound",
|
|
@@ -41,6 +43,8 @@ LINKED_API_ACTION_ERROR_TYPES: tuple[str, ...] = (
|
|
|
41
43
|
"retrievingNotAllowed",
|
|
42
44
|
"connectionNotFound",
|
|
43
45
|
"searchingNotAllowed",
|
|
46
|
+
"searchInterfaceMismatch",
|
|
47
|
+
"filterIdentityMismatch",
|
|
44
48
|
"companyNotFound",
|
|
45
49
|
"postNotFound",
|
|
46
50
|
"jobNotFound",
|
|
@@ -26,10 +26,12 @@ from linkedapi.operations.retrieve_feed import RetrieveFeed
|
|
|
26
26
|
from linkedapi.operations.retrieve_invitations import RetrieveInvitations
|
|
27
27
|
from linkedapi.operations.retrieve_pending_requests import RetrievePendingRequests
|
|
28
28
|
from linkedapi.operations.retrieve_performance import RetrievePerformance
|
|
29
|
+
from linkedapi.operations.retrieve_profile_viewers import RetrieveProfileViewers
|
|
29
30
|
from linkedapi.operations.retrieve_ssi import RetrieveSSI
|
|
30
31
|
from linkedapi.operations.search_companies import SearchCompanies
|
|
31
32
|
from linkedapi.operations.search_jobs import SearchJobs
|
|
32
33
|
from linkedapi.operations.search_people import SearchPeople
|
|
34
|
+
from linkedapi.operations.search_posts import SearchPosts
|
|
33
35
|
from linkedapi.operations.send_connection_request import SendConnectionRequest
|
|
34
36
|
from linkedapi.operations.send_message import SendMessage
|
|
35
37
|
from linkedapi.operations.sync_conversation import SyncConversation
|
|
@@ -72,10 +74,12 @@ __all__ = [
|
|
|
72
74
|
"RetrieveInvitations",
|
|
73
75
|
"RetrievePendingRequests",
|
|
74
76
|
"RetrievePerformance",
|
|
77
|
+
"RetrieveProfileViewers",
|
|
75
78
|
"RetrieveSSI",
|
|
76
79
|
"SearchCompanies",
|
|
77
80
|
"SearchJobs",
|
|
78
81
|
"SearchPeople",
|
|
82
|
+
"SearchPosts",
|
|
79
83
|
"SendConnectionRequest",
|
|
80
84
|
"SendMessage",
|
|
81
85
|
"SyncConversation",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from linkedapi.core import Operation
|
|
4
|
+
from linkedapi.mappers import ArrayWorkflowMapper
|
|
5
|
+
from linkedapi.types import ProfileViewer, RetrieveProfileViewersParams
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RetrieveProfileViewers(Operation[RetrieveProfileViewersParams, list[ProfileViewer]]):
|
|
9
|
+
"""Retrieve the viewers visible on the current account's LinkedIn profile."""
|
|
10
|
+
|
|
11
|
+
operation_name = "retrieveProfileViewers"
|
|
12
|
+
mapper = ArrayWorkflowMapper[RetrieveProfileViewersParams, ProfileViewer](
|
|
13
|
+
"st.retrieveProfileViewers",
|
|
14
|
+
ProfileViewer,
|
|
15
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from linkedapi.core import Operation
|
|
4
|
+
from linkedapi.mappers import ArrayWorkflowMapper
|
|
5
|
+
from linkedapi.types import SearchPostResult, SearchPostsParams
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SearchPosts(Operation[SearchPostsParams, list[SearchPostResult]]):
|
|
9
|
+
"""Search posts on standard LinkedIn."""
|
|
10
|
+
|
|
11
|
+
operation_name = "searchPosts"
|
|
12
|
+
mapper = ArrayWorkflowMapper[SearchPostsParams, SearchPostResult](
|
|
13
|
+
"st.searchPosts",
|
|
14
|
+
SearchPostResult,
|
|
15
|
+
)
|
|
@@ -96,12 +96,15 @@ from linkedapi.types.jobs import (
|
|
|
96
96
|
JobDatePosted,
|
|
97
97
|
JobEmploymentType,
|
|
98
98
|
JobExperienceLevel,
|
|
99
|
+
JobPreferenceEmploymentType,
|
|
100
|
+
JobPreferenceExperienceLevel,
|
|
99
101
|
JobSalary,
|
|
100
102
|
JobWorkplaceType,
|
|
101
103
|
SalaryPeriod,
|
|
102
104
|
SearchJobResult,
|
|
103
105
|
SearchJobsFilter,
|
|
104
106
|
SearchJobsParams,
|
|
107
|
+
SearchJobsPreferences,
|
|
105
108
|
)
|
|
106
109
|
from linkedapi.types.message import (
|
|
107
110
|
ConversationPollRequest,
|
|
@@ -177,6 +180,11 @@ from linkedapi.types.post import (
|
|
|
177
180
|
ReplyToCommentParams,
|
|
178
181
|
ReplyToCommentResult,
|
|
179
182
|
)
|
|
183
|
+
from linkedapi.types.profile_viewers import (
|
|
184
|
+
ProfileViewer,
|
|
185
|
+
ProfileViewerType,
|
|
186
|
+
RetrieveProfileViewersParams,
|
|
187
|
+
)
|
|
180
188
|
from linkedapi.types.responses import LinkedApiRequestError, LinkedApiResponse
|
|
181
189
|
from linkedapi.types.search_companies import (
|
|
182
190
|
AnnualRevenueFilter,
|
|
@@ -198,6 +206,20 @@ from linkedapi.types.search_people import (
|
|
|
198
206
|
SearchPeopleParams,
|
|
199
207
|
SearchPeopleResult,
|
|
200
208
|
)
|
|
209
|
+
from linkedapi.types.search_posts import (
|
|
210
|
+
PostSearchContentType,
|
|
211
|
+
PostSearchDatePosted,
|
|
212
|
+
PostSearchPostedBy,
|
|
213
|
+
PostSearchSort,
|
|
214
|
+
SearchPostActor,
|
|
215
|
+
SearchPostResult,
|
|
216
|
+
SearchPostsCompanyFilter,
|
|
217
|
+
SearchPostsCompanyFilterEntry,
|
|
218
|
+
SearchPostsFilter,
|
|
219
|
+
SearchPostsParams,
|
|
220
|
+
SearchPostsPersonFilter,
|
|
221
|
+
SearchPostsPersonFilterEntry,
|
|
222
|
+
)
|
|
201
223
|
from linkedapi.types.statistics import (
|
|
202
224
|
ApiUsageAction,
|
|
203
225
|
ApiUsageParams,
|
|
@@ -315,6 +337,8 @@ __all__ = [
|
|
|
315
337
|
"JobDatePosted",
|
|
316
338
|
"JobEmploymentType",
|
|
317
339
|
"JobExperienceLevel",
|
|
340
|
+
"JobPreferenceEmploymentType",
|
|
341
|
+
"JobPreferenceExperienceLevel",
|
|
318
342
|
"JobSalary",
|
|
319
343
|
"JobWorkplaceType",
|
|
320
344
|
"LanguageProficiency",
|
|
@@ -382,7 +406,13 @@ __all__ = [
|
|
|
382
406
|
"PostReaction",
|
|
383
407
|
"PostReactionsRetrievalConfig",
|
|
384
408
|
"PostReposter",
|
|
409
|
+
"PostSearchContentType",
|
|
410
|
+
"PostSearchDatePosted",
|
|
411
|
+
"PostSearchPostedBy",
|
|
412
|
+
"PostSearchSort",
|
|
385
413
|
"PostType",
|
|
414
|
+
"ProfileViewer",
|
|
415
|
+
"ProfileViewerType",
|
|
386
416
|
"ReactToCommentParams",
|
|
387
417
|
"ReactToPostParams",
|
|
388
418
|
"Reaction",
|
|
@@ -402,6 +432,7 @@ __all__ = [
|
|
|
402
432
|
"RetrieveFeedParams",
|
|
403
433
|
"RetrievePendingRequestsResult",
|
|
404
434
|
"RetrievePerformanceResult",
|
|
435
|
+
"RetrieveProfileViewersParams",
|
|
405
436
|
"RetrieveSSIResult",
|
|
406
437
|
"SalaryPeriod",
|
|
407
438
|
"SearchCompaniesFilter",
|
|
@@ -411,9 +442,18 @@ __all__ = [
|
|
|
411
442
|
"SearchJobResult",
|
|
412
443
|
"SearchJobsFilter",
|
|
413
444
|
"SearchJobsParams",
|
|
445
|
+
"SearchJobsPreferences",
|
|
414
446
|
"SearchPeopleFilter",
|
|
415
447
|
"SearchPeopleParams",
|
|
416
448
|
"SearchPeopleResult",
|
|
449
|
+
"SearchPostActor",
|
|
450
|
+
"SearchPostResult",
|
|
451
|
+
"SearchPostsCompanyFilter",
|
|
452
|
+
"SearchPostsCompanyFilterEntry",
|
|
453
|
+
"SearchPostsFilter",
|
|
454
|
+
"SearchPostsParams",
|
|
455
|
+
"SearchPostsPersonFilter",
|
|
456
|
+
"SearchPostsPersonFilterEntry",
|
|
417
457
|
"SeatType",
|
|
418
458
|
"SendConnectionRequestParams",
|
|
419
459
|
"SendMessageManageConversation",
|
|
@@ -24,6 +24,24 @@ JobEmploymentType = Literal[
|
|
|
24
24
|
"other",
|
|
25
25
|
]
|
|
26
26
|
JobWorkplaceType = Literal["onSite", "remote", "hybrid"]
|
|
27
|
+
# Experience levels of LinkedIn's AI-powered jobs search. Not a subset of JobExperienceLevel:
|
|
28
|
+
# "senior" corresponds to "midSeniorLevel", "manager" has no classic equivalent, and "internship"
|
|
29
|
+
# and "associate" are absent here.
|
|
30
|
+
JobPreferenceExperienceLevel = Literal[
|
|
31
|
+
"entryLevel",
|
|
32
|
+
"senior",
|
|
33
|
+
"manager",
|
|
34
|
+
"director",
|
|
35
|
+
"executive",
|
|
36
|
+
]
|
|
37
|
+
# Same names as JobEmploymentType, minus "temporary" and "other".
|
|
38
|
+
JobPreferenceEmploymentType = Literal[
|
|
39
|
+
"fullTime",
|
|
40
|
+
"partTime",
|
|
41
|
+
"contract",
|
|
42
|
+
"internship",
|
|
43
|
+
"volunteer",
|
|
44
|
+
]
|
|
27
45
|
SalaryPeriod = Literal["yearly", "monthly", "hourly"]
|
|
28
46
|
JobCurrency = Literal[
|
|
29
47
|
"usd",
|
|
@@ -74,6 +92,12 @@ class JobSalary(LinkedApiModel):
|
|
|
74
92
|
|
|
75
93
|
|
|
76
94
|
class SearchJobsFilter(LinkedApiModel):
|
|
95
|
+
"""Filtering criteria for the classic LinkedIn jobs search.
|
|
96
|
+
|
|
97
|
+
Every specified field is applied, or the action fails.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
# Deprecated: use the top-level "location" of SearchJobsParams instead.
|
|
77
101
|
location: str | None = None
|
|
78
102
|
date_posted: JobDatePosted | None = None
|
|
79
103
|
experience_levels: list[JobExperienceLevel] | None = None
|
|
@@ -89,9 +113,32 @@ class SearchJobsFilter(LinkedApiModel):
|
|
|
89
113
|
fair_chance_employer: bool | None = None
|
|
90
114
|
|
|
91
115
|
|
|
116
|
+
class SearchJobsPreferences(LinkedApiModel):
|
|
117
|
+
"""Filtering criteria for LinkedIn's AI-powered jobs search.
|
|
118
|
+
|
|
119
|
+
LinkedIn decides which of them it offers for a given search, and the ones it does not offer are
|
|
120
|
+
skipped instead of failing the action.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
date_posted: JobDatePosted | None = None
|
|
124
|
+
experience_levels: list[JobPreferenceExperienceLevel] | None = None
|
|
125
|
+
employment_types: list[JobPreferenceEmploymentType] | None = None
|
|
126
|
+
companies: list[str] | None = None
|
|
127
|
+
remote: bool | None = None
|
|
128
|
+
easy_apply: bool | None = None
|
|
129
|
+
under_10_applicants: bool | None = None
|
|
130
|
+
in_your_network: bool | None = None
|
|
131
|
+
keywords: list[str] | None = None
|
|
132
|
+
|
|
133
|
+
|
|
92
134
|
class SearchJobsParams(BaseActionParams, LimitParams):
|
|
93
135
|
term: str | None = None
|
|
136
|
+
location: str | None = None
|
|
137
|
+
allow_similar_results: bool | None = None
|
|
138
|
+
# "filter" and "preferences" are mutually exclusive: "filter" targets the classic LinkedIn jobs
|
|
139
|
+
# search, "preferences" the AI-powered one.
|
|
94
140
|
filter: SearchJobsFilter | None = None
|
|
141
|
+
preferences: SearchJobsPreferences | None = None
|
|
95
142
|
custom_search_url: str | None = None
|
|
96
143
|
|
|
97
144
|
|
|
@@ -106,6 +153,7 @@ class SearchJobResult(LinkedApiModel):
|
|
|
106
153
|
salary: JobSalary | None = None
|
|
107
154
|
easy_apply: bool | None = None
|
|
108
155
|
is_promoted: bool | None = None
|
|
156
|
+
is_similar_match: bool | None = None
|
|
109
157
|
|
|
110
158
|
|
|
111
159
|
class FetchJobParams(BaseActionParams):
|
|
@@ -118,7 +166,6 @@ class Job(LinkedApiModel):
|
|
|
118
166
|
job_url: str | None = None
|
|
119
167
|
title: str | None = None
|
|
120
168
|
company_name: str | None = None
|
|
121
|
-
company_urn: str | None = None
|
|
122
169
|
company_url: str | None = None
|
|
123
170
|
location: str | None = None
|
|
124
171
|
posted_date: str | None = None
|
|
@@ -97,6 +97,9 @@ class InboxMessage(LinkedApiModel):
|
|
|
97
97
|
type: ConversationType | None = None
|
|
98
98
|
thread_id: str | None = None
|
|
99
99
|
person_urn: str | None = None
|
|
100
|
+
person_hashed_url: str | None = None
|
|
101
|
+
person_public_url: str | None = None
|
|
102
|
+
# Deprecated: use person_hashed_url. Still returned by the API for compatibility.
|
|
100
103
|
person_url: str | None = None
|
|
101
104
|
sender: MessageSender | None = None
|
|
102
105
|
text: str | None = None
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, model_validator
|
|
6
|
+
|
|
7
|
+
from linkedapi.types.base import LinkedApiModel
|
|
8
|
+
from linkedapi.types.params import BaseActionParams
|
|
9
|
+
|
|
10
|
+
ProfileViewerType = Literal["identified", "anonymous"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RetrieveProfileViewersParams(BaseActionParams):
|
|
14
|
+
limit: int | None = Field(default=None, ge=1, le=300)
|
|
15
|
+
since: str | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProfileViewer(LinkedApiModel):
|
|
19
|
+
viewer_type: ProfileViewerType
|
|
20
|
+
viewed_at: str | None
|
|
21
|
+
viewed_ago: str | None
|
|
22
|
+
name: str | None = None
|
|
23
|
+
public_url: str | None = None
|
|
24
|
+
urn: str | None = None
|
|
25
|
+
headline: str | None = None
|
|
26
|
+
connection_degree: Literal[1, 2, 3] | None = None
|
|
27
|
+
avatar_url: str | None = None
|
|
28
|
+
description: str | None = None
|
|
29
|
+
search_url: str | None = None
|
|
30
|
+
|
|
31
|
+
@model_validator(mode="after")
|
|
32
|
+
def validate_type_fields(self) -> ProfileViewer:
|
|
33
|
+
type_fields = {
|
|
34
|
+
"name",
|
|
35
|
+
"public_url",
|
|
36
|
+
"urn",
|
|
37
|
+
"headline",
|
|
38
|
+
"connection_degree",
|
|
39
|
+
"avatar_url",
|
|
40
|
+
"description",
|
|
41
|
+
"search_url",
|
|
42
|
+
}
|
|
43
|
+
required_fields = {
|
|
44
|
+
"identified": {
|
|
45
|
+
"name",
|
|
46
|
+
"public_url",
|
|
47
|
+
"urn",
|
|
48
|
+
"headline",
|
|
49
|
+
"connection_degree",
|
|
50
|
+
"avatar_url",
|
|
51
|
+
},
|
|
52
|
+
"anonymous": {"description", "search_url"},
|
|
53
|
+
}[self.viewer_type]
|
|
54
|
+
missing_fields = required_fields - self.model_fields_set
|
|
55
|
+
if missing_fields:
|
|
56
|
+
msg = (
|
|
57
|
+
f"{self.viewer_type} profile viewer is missing required fields: "
|
|
58
|
+
f"{', '.join(sorted(missing_fields))}"
|
|
59
|
+
)
|
|
60
|
+
raise ValueError(msg)
|
|
61
|
+
|
|
62
|
+
unexpected_fields = (self.model_fields_set & type_fields) - required_fields
|
|
63
|
+
if unexpected_fields:
|
|
64
|
+
msg = (
|
|
65
|
+
f"{self.viewer_type} profile viewer contains fields for another type: "
|
|
66
|
+
f"{', '.join(sorted(unexpected_fields))}"
|
|
67
|
+
)
|
|
68
|
+
raise ValueError(msg)
|
|
69
|
+
|
|
70
|
+
required_values = {
|
|
71
|
+
"identified": {"name": self.name, "public_url": self.public_url},
|
|
72
|
+
"anonymous": {"description": self.description, "search_url": self.search_url},
|
|
73
|
+
}[self.viewer_type]
|
|
74
|
+
null_fields = [name for name, value in required_values.items() if value is None]
|
|
75
|
+
if null_fields:
|
|
76
|
+
msg = (
|
|
77
|
+
f"{self.viewer_type} profile viewer requires non-null fields: "
|
|
78
|
+
f"{', '.join(sorted(null_fields))}"
|
|
79
|
+
)
|
|
80
|
+
raise ValueError(msg)
|
|
81
|
+
|
|
82
|
+
return self
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal, TypeAlias
|
|
4
|
+
|
|
5
|
+
from linkedapi.types.base import LinkedApiModel
|
|
6
|
+
from linkedapi.types.params import BaseActionParams, LimitParams
|
|
7
|
+
from linkedapi.types.post import PostActorType, PostType
|
|
8
|
+
|
|
9
|
+
PostSearchSort = Literal["topMatch", "latest"]
|
|
10
|
+
PostSearchDatePosted = Literal["past24Hours", "pastWeek", "pastMonth"]
|
|
11
|
+
PostSearchContentType = Literal["videos", "images", "jobPosts", "liveVideos", "documents"]
|
|
12
|
+
PostSearchPostedBy = Literal["me", "firstConnections", "peopleYouFollow"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SearchPostsPersonFilter(LinkedApiModel):
|
|
16
|
+
"""One person to narrow a post search by.
|
|
17
|
+
|
|
18
|
+
"name" is always required because LinkedIn's filter panel accepts nothing but typed text. The
|
|
19
|
+
optional identifier decides which of the offered namesakes is taken; with "name" alone the pick
|
|
20
|
+
is whichever suggestion LinkedIn ranked first.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
name: str
|
|
24
|
+
urn: str | None = None
|
|
25
|
+
person_hashed_url: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SearchPostsCompanyFilter(LinkedApiModel):
|
|
29
|
+
"""One company to narrow a post search by. Same contract as SearchPostsPersonFilter."""
|
|
30
|
+
|
|
31
|
+
name: str
|
|
32
|
+
urn: str | None = None
|
|
33
|
+
company_hashed_url: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# A plain string is shorthand for the name alone: "Bill Gates" means {"name": "Bill Gates"}.
|
|
37
|
+
# Both forms may be mixed in one list.
|
|
38
|
+
SearchPostsPersonFilterEntry: TypeAlias = SearchPostsPersonFilter | str
|
|
39
|
+
SearchPostsCompanyFilterEntry: TypeAlias = SearchPostsCompanyFilter | str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SearchPostsFilter(LinkedApiModel):
|
|
43
|
+
"""Filtering criteria for the LinkedIn content search.
|
|
44
|
+
|
|
45
|
+
Every specified field is applied, or the action fails. Ignored entirely when
|
|
46
|
+
"custom_search_url" is specified.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
sort: PostSearchSort | None = None
|
|
50
|
+
date_posted: PostSearchDatePosted | None = None
|
|
51
|
+
content_type: PostSearchContentType | None = None
|
|
52
|
+
posted_by: list[PostSearchPostedBy] | None = None
|
|
53
|
+
from_members: list[SearchPostsPersonFilterEntry] | None = None
|
|
54
|
+
from_companies: list[SearchPostsCompanyFilterEntry] | None = None
|
|
55
|
+
mentioning_members: list[SearchPostsPersonFilterEntry] | None = None
|
|
56
|
+
mentioning_companies: list[SearchPostsCompanyFilterEntry] | None = None
|
|
57
|
+
author_companies: list[SearchPostsCompanyFilterEntry] | None = None
|
|
58
|
+
author_industries: list[str] | None = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SearchPostsParams(BaseActionParams, LimitParams):
|
|
62
|
+
# Either "term" or "custom_search_url" must be provided. "limit" defaults to 10 and may go up
|
|
63
|
+
# to 100, or up to 20 when child actions are attached.
|
|
64
|
+
term: str | None = None
|
|
65
|
+
filter: SearchPostsFilter | None = None
|
|
66
|
+
custom_search_url: str | None = None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SearchPostActor(LinkedApiModel):
|
|
70
|
+
"""Whoever a search result attributes content to: its author, or the actor that reshared it.
|
|
71
|
+
|
|
72
|
+
Unlike the actors of a post returned by fetch_post, these carry no "urn": the URN is read from
|
|
73
|
+
identity anchors on the post's own page, and a search result list has none of them.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
type: PostActorType | None = None
|
|
77
|
+
name: str | None = None
|
|
78
|
+
profile_url: str | None = None
|
|
79
|
+
headline: str | None = None
|
|
80
|
+
company_url: str | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class SearchPostResult(LinkedApiModel):
|
|
84
|
+
url: str | None = None
|
|
85
|
+
activity_urn: str | None = None
|
|
86
|
+
time: str | None = None
|
|
87
|
+
type: PostType | None = None
|
|
88
|
+
author: SearchPostActor | None = None
|
|
89
|
+
# Non-null only when "type" is "repost".
|
|
90
|
+
reposter: SearchPostActor | None = None
|
|
91
|
+
text: str | None = None
|
|
92
|
+
repost_text: str | None = None
|
|
93
|
+
hashtags: list[str] | None = None
|
|
94
|
+
mentions: list[str] | None = None
|
|
95
|
+
external_links: list[str] | None = None
|
|
96
|
+
images: list[str] | None = None
|
|
97
|
+
document_slides: list[str] | None = None
|
|
98
|
+
has_video: bool | None = None
|
|
99
|
+
video_thumbnail: str | None = None
|
|
100
|
+
has_poll: bool | None = None
|
|
101
|
+
reactions_count: int | None = None
|
|
102
|
+
comments_count: int | None = None
|
|
103
|
+
reposts_count: int | None = None
|
|
@@ -102,6 +102,9 @@ class MessageWebhookEventData(LinkedApiModel):
|
|
|
102
102
|
type: ConversationType | None = None
|
|
103
103
|
thread_id: str | None = None
|
|
104
104
|
person_urn: str | None = None
|
|
105
|
+
person_hashed_url: str | None = None
|
|
106
|
+
person_public_url: str | None = None
|
|
107
|
+
# Deprecated: use person_hashed_url. Still returned by the API for compatibility.
|
|
105
108
|
person_url: str | None = None
|
|
106
109
|
message_id: str | None = None
|
|
107
110
|
sender: MessageSender | None = None
|
|
@@ -13,6 +13,12 @@ from linkedapi import (
|
|
|
13
13
|
LinkedApi,
|
|
14
14
|
LinkedApiConfig,
|
|
15
15
|
RetrieveFeedParams,
|
|
16
|
+
SearchPostActor,
|
|
17
|
+
SearchPostResult,
|
|
18
|
+
SearchPostsCompanyFilter,
|
|
19
|
+
SearchPostsFilter,
|
|
20
|
+
SearchPostsParams,
|
|
21
|
+
SearchPostsPersonFilter,
|
|
16
22
|
SimpleWorkflowMapper,
|
|
17
23
|
VoidWorkflowMapper,
|
|
18
24
|
)
|
|
@@ -33,6 +39,7 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
|
|
|
33
39
|
"search_companies",
|
|
34
40
|
"nv_search_companies",
|
|
35
41
|
"search_jobs",
|
|
42
|
+
"search_posts",
|
|
36
43
|
"send_connection_request",
|
|
37
44
|
"check_connection_status",
|
|
38
45
|
"withdraw_connection_request",
|
|
@@ -59,6 +66,7 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
|
|
|
59
66
|
"create_post",
|
|
60
67
|
"retrieve_ssi",
|
|
61
68
|
"retrieve_performance",
|
|
69
|
+
"retrieve_profile_viewers",
|
|
62
70
|
]
|
|
63
71
|
|
|
64
72
|
for name in operation_names:
|
|
@@ -66,7 +74,7 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
|
|
|
66
74
|
assert hasattr(operation, "execute")
|
|
67
75
|
assert hasattr(operation, "result")
|
|
68
76
|
assert hasattr(operation, "cancel")
|
|
69
|
-
assert len(linkedapi.operations) ==
|
|
77
|
+
assert len(linkedapi.operations) == 40
|
|
70
78
|
|
|
71
79
|
|
|
72
80
|
def test_operation_mappers_match_node_contract() -> None:
|
|
@@ -78,6 +86,8 @@ def test_operation_mappers_match_node_contract() -> None:
|
|
|
78
86
|
assert linkedapi.search_people.mapper.base_action_type == "st.searchPeople"
|
|
79
87
|
assert isinstance(linkedapi.search_jobs.mapper, ArrayWorkflowMapper)
|
|
80
88
|
assert linkedapi.search_jobs.mapper.base_action_type == "st.searchJobs"
|
|
89
|
+
assert isinstance(linkedapi.search_posts.mapper, ArrayWorkflowMapper)
|
|
90
|
+
assert linkedapi.search_posts.mapper.base_action_type == "st.searchPosts"
|
|
81
91
|
assert linkedapi.fetch_job.mapper.base_action_type == "st.openJob"
|
|
82
92
|
assert linkedapi.fetch_job.mapper.default_params == {"basicInfo": True}
|
|
83
93
|
assert isinstance(linkedapi.send_connection_request.mapper, VoidWorkflowMapper)
|
|
@@ -133,6 +143,153 @@ def test_retrieve_feed_maps_params_and_feed_context() -> None:
|
|
|
133
143
|
RetrieveFeedParams(limit=invalid_limit)
|
|
134
144
|
|
|
135
145
|
|
|
146
|
+
def test_search_posts_maps_filter_params_and_actors_without_urn() -> None:
|
|
147
|
+
linkedapi = LinkedApi(LinkedApiConfig(linked_api_token="x", identification_token="y"))
|
|
148
|
+
|
|
149
|
+
request = linkedapi.search_posts.mapper.map_request(
|
|
150
|
+
SearchPostsParams(
|
|
151
|
+
term="climate tech",
|
|
152
|
+
limit=20,
|
|
153
|
+
filter={
|
|
154
|
+
"sort": "latest",
|
|
155
|
+
"date_posted": "pastWeek",
|
|
156
|
+
"content_type": "images",
|
|
157
|
+
"posted_by": ["firstConnections", "peopleYouFollow"],
|
|
158
|
+
"from_members": [{"name": "Bill Gates", "urn": "urn:li:member:251749025"}],
|
|
159
|
+
"from_companies": ["Example Company"],
|
|
160
|
+
"mentioning_members": [],
|
|
161
|
+
"mentioning_companies": [],
|
|
162
|
+
"author_companies": [
|
|
163
|
+
{"name": "Example Company", "urn": "urn:li:organization:1234567"}
|
|
164
|
+
],
|
|
165
|
+
"author_industries": ["Software Development"],
|
|
166
|
+
},
|
|
167
|
+
custom_search_url=(
|
|
168
|
+
"https://www.linkedin.com/search/results/content/?keywords=climate%20tech"
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
assert request == {
|
|
174
|
+
"actionType": "st.searchPosts",
|
|
175
|
+
"term": "climate tech",
|
|
176
|
+
"limit": 20,
|
|
177
|
+
"filter": {
|
|
178
|
+
"sort": "latest",
|
|
179
|
+
"datePosted": "pastWeek",
|
|
180
|
+
"contentType": "images",
|
|
181
|
+
"postedBy": ["firstConnections", "peopleYouFollow"],
|
|
182
|
+
"fromMembers": [{"name": "Bill Gates", "urn": "urn:li:member:251749025"}],
|
|
183
|
+
"fromCompanies": ["Example Company"],
|
|
184
|
+
"mentioningMembers": [],
|
|
185
|
+
"mentioningCompanies": [],
|
|
186
|
+
"authorCompanies": [{"name": "Example Company", "urn": "urn:li:organization:1234567"}],
|
|
187
|
+
"authorIndustries": ["Software Development"],
|
|
188
|
+
},
|
|
189
|
+
"customSearchUrl": "https://www.linkedin.com/search/results/content/?keywords=climate%20tech",
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
response = linkedapi.search_posts.mapper.map_response(
|
|
193
|
+
{
|
|
194
|
+
"actionType": "st.searchPosts",
|
|
195
|
+
"success": True,
|
|
196
|
+
"data": [
|
|
197
|
+
{
|
|
198
|
+
"url": "https://www.linkedin.com/feed/update/urn:li:activity:1",
|
|
199
|
+
"activityUrn": "urn:li:activity:1",
|
|
200
|
+
"time": "2023-01-01T09:15:00Z",
|
|
201
|
+
"type": "repost",
|
|
202
|
+
"author": {
|
|
203
|
+
"type": "company",
|
|
204
|
+
"name": "Example Company",
|
|
205
|
+
"companyUrl": "https://www.linkedin.com/company/example-company",
|
|
206
|
+
},
|
|
207
|
+
"reposter": {
|
|
208
|
+
"type": "person",
|
|
209
|
+
"name": "Example Reposter",
|
|
210
|
+
"profileUrl": "https://www.linkedin.com/in/example-reposter",
|
|
211
|
+
"headline": None,
|
|
212
|
+
},
|
|
213
|
+
"text": "Original post content about the webinar.",
|
|
214
|
+
"repostText": "A useful summary for anyone planning a launch.",
|
|
215
|
+
"hashtags": [],
|
|
216
|
+
"mentions": [],
|
|
217
|
+
"externalLinks": [],
|
|
218
|
+
"images": [],
|
|
219
|
+
"documentSlides": [],
|
|
220
|
+
"hasVideo": True,
|
|
221
|
+
"videoThumbnail": "https://media.licdn.com/dms/image/video-cover.jpg",
|
|
222
|
+
"hasPoll": False,
|
|
223
|
+
"reactionsCount": 6,
|
|
224
|
+
"commentsCount": 0,
|
|
225
|
+
"repostsCount": 1,
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
assert response.data is not None
|
|
232
|
+
post = response.data[0]
|
|
233
|
+
assert isinstance(post, SearchPostResult)
|
|
234
|
+
assert post.activity_urn == "urn:li:activity:1"
|
|
235
|
+
assert post.author is not None
|
|
236
|
+
assert post.author.company_url == "https://www.linkedin.com/company/example-company"
|
|
237
|
+
assert post.reposter is not None
|
|
238
|
+
assert post.reposter.profile_url == "https://www.linkedin.com/in/example-reposter"
|
|
239
|
+
assert post.video_thumbnail == "https://media.licdn.com/dms/image/video-cover.jpg"
|
|
240
|
+
assert post.reposts_count == 1
|
|
241
|
+
# A search result never resolves an actor urn, so the field must not exist on this surface.
|
|
242
|
+
assert "urn" not in SearchPostActor.model_fields
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def test_search_posts_actor_filters_accept_bare_strings_and_objects_mixed() -> None:
|
|
246
|
+
params = SearchPostsParams(
|
|
247
|
+
term="ai",
|
|
248
|
+
filter=SearchPostsFilter(
|
|
249
|
+
from_members=[
|
|
250
|
+
"Bill Gates",
|
|
251
|
+
SearchPostsPersonFilter(
|
|
252
|
+
name="Satya Nadella",
|
|
253
|
+
person_hashed_url="https://www.linkedin.com/in/ACoAAABC",
|
|
254
|
+
),
|
|
255
|
+
],
|
|
256
|
+
from_companies=[
|
|
257
|
+
"Acme",
|
|
258
|
+
SearchPostsCompanyFilter(
|
|
259
|
+
name="Globex",
|
|
260
|
+
company_hashed_url="https://www.linkedin.com/company/1035",
|
|
261
|
+
),
|
|
262
|
+
],
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
assert params.model_dump(by_alias=True, exclude_none=True) == {
|
|
267
|
+
"term": "ai",
|
|
268
|
+
"filter": {
|
|
269
|
+
"fromMembers": [
|
|
270
|
+
"Bill Gates",
|
|
271
|
+
{
|
|
272
|
+
"name": "Satya Nadella",
|
|
273
|
+
"personHashedUrl": "https://www.linkedin.com/in/ACoAAABC",
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
"fromCompanies": [
|
|
277
|
+
"Acme",
|
|
278
|
+
{
|
|
279
|
+
"name": "Globex",
|
|
280
|
+
"companyHashedUrl": "https://www.linkedin.com/company/1035",
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
with pytest.raises(ValidationError):
|
|
287
|
+
SearchPostsFilter(sort="mostRecent")
|
|
288
|
+
|
|
289
|
+
with pytest.raises(ValidationError):
|
|
290
|
+
SearchPostsFilter(from_members=[{"urn": "urn:li:member:1"}])
|
|
291
|
+
|
|
292
|
+
|
|
136
293
|
def test_invitation_params_require_the_matching_target_url() -> None:
|
|
137
294
|
params = AcceptInvitationParams(
|
|
138
295
|
invitation_type="companyFollow",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|