linkedapi 1.1.0__tar.gz → 1.1.1__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.
Files changed (93) hide show
  1. {linkedapi-1.1.0 → linkedapi-1.1.1}/PKG-INFO +1 -1
  2. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/__init__.py +5 -1
  3. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/client.py +36 -1
  4. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/__init__.py +4 -0
  5. linkedapi-1.1.1/linkedapi/operations/nv_sync_inbox.py +12 -0
  6. linkedapi-1.1.1/linkedapi/operations/sync_inbox.py +12 -0
  7. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/__init__.py +12 -0
  8. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/message.py +30 -3
  9. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/webhooks.py +24 -1
  10. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/webhooks/parse.py +3 -0
  11. {linkedapi-1.1.0 → linkedapi-1.1.1}/pyproject.toml +1 -1
  12. {linkedapi-1.1.0 → linkedapi-1.1.1}/tests/test_direct_methods.py +58 -0
  13. {linkedapi-1.1.0 → linkedapi-1.1.1}/tests/test_operations.py +7 -1
  14. linkedapi-1.1.1/tests/test_webhooks.py +84 -0
  15. {linkedapi-1.1.0 → linkedapi-1.1.1}/.github/workflows/publish.yaml +0 -0
  16. {linkedapi-1.1.0 → linkedapi-1.1.1}/.gitignore +0 -0
  17. {linkedapi-1.1.0 → linkedapi-1.1.1}/LICENSE +0 -0
  18. {linkedapi-1.1.0 → linkedapi-1.1.1}/README.md +0 -0
  19. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/admin/__init__.py +0 -0
  20. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/admin/accounts.py +0 -0
  21. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/admin/admin.py +0 -0
  22. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/admin/http_client.py +0 -0
  23. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/admin/limits.py +0 -0
  24. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/admin/subscription.py +0 -0
  25. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/admin/webhooks.py +0 -0
  26. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/config.py +0 -0
  27. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/core/__init__.py +0 -0
  28. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/core/operation.py +0 -0
  29. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/core/polling.py +0 -0
  30. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/errors.py +0 -0
  31. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/http/__init__.py +0 -0
  32. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/http/base.py +0 -0
  33. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/http/linked_api_http_client.py +0 -0
  34. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/mappers/__init__.py +0 -0
  35. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/mappers/array.py +0 -0
  36. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/mappers/base.py +0 -0
  37. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/mappers/simple.py +0 -0
  38. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/mappers/then.py +0 -0
  39. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/mappers/void.py +0 -0
  40. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/check_connection_status.py +0 -0
  41. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/comment_on_post.py +0 -0
  42. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/create_post.py +0 -0
  43. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/custom_workflow.py +0 -0
  44. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/fetch_company.py +0 -0
  45. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/fetch_job.py +0 -0
  46. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/fetch_person.py +0 -0
  47. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/fetch_post.py +0 -0
  48. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/nv_fetch_company.py +0 -0
  49. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/nv_fetch_person.py +0 -0
  50. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/nv_search_companies.py +0 -0
  51. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/nv_search_people.py +0 -0
  52. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/nv_send_message.py +0 -0
  53. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/nv_sync_conversation.py +0 -0
  54. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/react_to_post.py +0 -0
  55. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/remove_connection.py +0 -0
  56. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/retrieve_connections.py +0 -0
  57. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/retrieve_pending_requests.py +0 -0
  58. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/retrieve_performance.py +0 -0
  59. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/retrieve_ssi.py +0 -0
  60. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/search_companies.py +0 -0
  61. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/search_jobs.py +0 -0
  62. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/search_people.py +0 -0
  63. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/send_connection_request.py +0 -0
  64. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/send_message.py +0 -0
  65. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/sync_conversation.py +0 -0
  66. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/operations/withdraw_connection_request.py +0 -0
  67. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/py.typed +0 -0
  68. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/account.py +0 -0
  69. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/admin/__init__.py +0 -0
  70. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/admin/accounts.py +0 -0
  71. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/admin/config.py +0 -0
  72. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/admin/limits.py +0 -0
  73. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/admin/subscription.py +0 -0
  74. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/base.py +0 -0
  75. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/company.py +0 -0
  76. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/connection.py +0 -0
  77. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/jobs.py +0 -0
  78. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/params.py +0 -0
  79. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/person.py +0 -0
  80. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/post.py +0 -0
  81. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/responses.py +0 -0
  82. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/search_companies.py +0 -0
  83. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/search_people.py +0 -0
  84. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/statistics.py +0 -0
  85. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/types/workflow.py +0 -0
  86. {linkedapi-1.1.0 → linkedapi-1.1.1}/linkedapi/webhooks/__init__.py +0 -0
  87. {linkedapi-1.1.0 → linkedapi-1.1.1}/tests/conftest.py +0 -0
  88. {linkedapi-1.1.0 → linkedapi-1.1.1}/tests/test_admin.py +0 -0
  89. {linkedapi-1.1.0 → linkedapi-1.1.1}/tests/test_errors.py +0 -0
  90. {linkedapi-1.1.0 → linkedapi-1.1.1}/tests/test_http_client.py +0 -0
  91. {linkedapi-1.1.0 → linkedapi-1.1.1}/tests/test_mappers.py +0 -0
  92. {linkedapi-1.1.0 → linkedapi-1.1.1}/tests/test_models.py +0 -0
  93. {linkedapi-1.1.0 → linkedapi-1.1.1}/tests/test_polling.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: linkedapi
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: Official synchronous Python SDK for Linked API.
5
5
  Project-URL: Homepage, https://linkedapi.io
6
6
  Project-URL: Repository, https://github.com/Linked-API/linkedapi-python
@@ -49,6 +49,7 @@ from linkedapi.operations import (
49
49
  NvSearchPeople,
50
50
  NvSendMessage,
51
51
  NvSyncConversation,
52
+ NvSyncInbox,
52
53
  ReactToPost,
53
54
  RemoveConnection,
54
55
  RetrieveConnections,
@@ -61,13 +62,14 @@ from linkedapi.operations import (
61
62
  SendConnectionRequest,
62
63
  SendMessage,
63
64
  SyncConversation,
65
+ SyncInbox,
64
66
  WithdrawConnectionRequest,
65
67
  )
66
68
  from linkedapi.types import * # noqa: F403
67
69
  from linkedapi.types import __all__ as _types_all
68
70
  from linkedapi.webhooks import parse_webhook_event
69
71
 
70
- __version__ = "1.1.0"
72
+ __version__ = "1.1.1"
71
73
  PredefinedOperation = Operation
72
74
 
73
75
  __all__ = [
@@ -112,6 +114,7 @@ __all__ = [
112
114
  "NvSearchPeople",
113
115
  "NvSendMessage",
114
116
  "NvSyncConversation",
117
+ "NvSyncInbox",
115
118
  "Operation",
116
119
  "PredefinedOperation",
117
120
  "ReactToPost",
@@ -128,6 +131,7 @@ __all__ = [
128
131
  "SendMessage",
129
132
  "SimpleWorkflowMapper",
130
133
  "SyncConversation",
134
+ "SyncInbox",
131
135
  "ThenWorkflowMapper",
132
136
  "VoidWorkflowMapper",
133
137
  "WithdrawConnectionRequest",
@@ -22,6 +22,7 @@ from linkedapi.operations import (
22
22
  NvSearchPeople,
23
23
  NvSendMessage,
24
24
  NvSyncConversation,
25
+ NvSyncInbox,
25
26
  ReactToPost,
26
27
  RemoveConnection,
27
28
  RetrieveConnections,
@@ -34,10 +35,16 @@ from linkedapi.operations import (
34
35
  SendConnectionRequest,
35
36
  SendMessage,
36
37
  SyncConversation,
38
+ SyncInbox,
37
39
  WithdrawConnectionRequest,
38
40
  )
39
41
  from linkedapi.types import AccountInfo, LinkedApiActionError, serialize_value
40
- from linkedapi.types.message import ConversationPollRequest, ConversationPollResult
42
+ from linkedapi.types.message import (
43
+ ConversationPollRequest,
44
+ ConversationPollResult,
45
+ InboxMessage,
46
+ InboxPollRequest,
47
+ )
41
48
  from linkedapi.types.statistics import ApiUsageAction, ApiUsageParams
42
49
 
43
50
 
@@ -54,6 +61,7 @@ class LinkedApi:
54
61
  self.custom_workflow = CustomWorkflow(self.http_client)
55
62
  self.send_message = SendMessage(self.http_client)
56
63
  self.sync_conversation = SyncConversation(self.http_client)
64
+ self.sync_inbox = SyncInbox(self.http_client)
57
65
  self.check_connection_status = CheckConnectionStatus(self.http_client)
58
66
  self.send_connection_request = SendConnectionRequest(self.http_client)
59
67
  self.withdraw_connection_request = WithdrawConnectionRequest(self.http_client)
@@ -74,6 +82,7 @@ class LinkedApi:
74
82
  self.retrieve_performance = RetrievePerformance(self.http_client)
75
83
  self.nv_send_message = NvSendMessage(self.http_client)
76
84
  self.nv_sync_conversation = NvSyncConversation(self.http_client)
85
+ self.nv_sync_inbox = NvSyncInbox(self.http_client)
77
86
  self.nv_search_companies = NvSearchCompanies(self.http_client)
78
87
  self.nv_search_people = NvSearchPeople(self.http_client)
79
88
  self.nv_fetch_company = NvFetchCompany(self.http_client)
@@ -83,6 +92,7 @@ class LinkedApi:
83
92
  self.custom_workflow,
84
93
  self.send_message,
85
94
  self.sync_conversation,
95
+ self.sync_inbox,
86
96
  self.check_connection_status,
87
97
  self.send_connection_request,
88
98
  self.withdraw_connection_request,
@@ -103,6 +113,7 @@ class LinkedApi:
103
113
  self.retrieve_performance,
104
114
  self.nv_send_message,
105
115
  self.nv_sync_conversation,
116
+ self.nv_sync_inbox,
106
117
  self.nv_search_companies,
107
118
  self.nv_search_people,
108
119
  self.nv_fetch_company,
@@ -139,6 +150,30 @@ class LinkedApi:
139
150
  )
140
151
  raise
141
152
 
153
+ def poll_inbox(
154
+ self,
155
+ request: InboxPollRequest | None = None,
156
+ ) -> MappedResponse[list[InboxMessage]]:
157
+ """Read the monitored standard or Sales Navigator inbox, newest messages first."""
158
+
159
+ payload = serialize_value(request) if request is not None else {}
160
+ response = self.http_client.post("/inbox/poll", payload)
161
+ if response.success and response.result is not None:
162
+ messages = response.result.get("messages", [])
163
+ return MappedResponse(
164
+ data=[InboxMessage.model_validate(item) for item in messages],
165
+ errors=[],
166
+ )
167
+ return MappedResponse(
168
+ data=None,
169
+ errors=[
170
+ LinkedApiActionError(
171
+ type=response.error.type if response.error else "",
172
+ message=response.error.message if response.error else "",
173
+ ),
174
+ ],
175
+ )
176
+
142
177
  def get_account_info(self) -> MappedResponse[AccountInfo]:
143
178
  """Retrieve basic information about the current LinkedIn account."""
144
179
 
@@ -12,6 +12,7 @@ from linkedapi.operations.nv_search_companies import NvSearchCompanies
12
12
  from linkedapi.operations.nv_search_people import NvSearchPeople
13
13
  from linkedapi.operations.nv_send_message import NvSendMessage
14
14
  from linkedapi.operations.nv_sync_conversation import NvSyncConversation
15
+ from linkedapi.operations.nv_sync_inbox import NvSyncInbox
15
16
  from linkedapi.operations.react_to_post import ReactToPost
16
17
  from linkedapi.operations.remove_connection import RemoveConnection
17
18
  from linkedapi.operations.retrieve_connections import RetrieveConnections
@@ -24,6 +25,7 @@ from linkedapi.operations.search_people import SearchPeople
24
25
  from linkedapi.operations.send_connection_request import SendConnectionRequest
25
26
  from linkedapi.operations.send_message import SendMessage
26
27
  from linkedapi.operations.sync_conversation import SyncConversation
28
+ from linkedapi.operations.sync_inbox import SyncInbox
27
29
  from linkedapi.operations.withdraw_connection_request import WithdrawConnectionRequest
28
30
 
29
31
  __all__ = [
@@ -47,6 +49,7 @@ __all__ = [
47
49
  "NvSearchPeople",
48
50
  "NvSendMessage",
49
51
  "NvSyncConversation",
52
+ "NvSyncInbox",
50
53
  "ReactToPost",
51
54
  "RemoveConnection",
52
55
  "RetrieveConnections",
@@ -59,5 +62,6 @@ __all__ = [
59
62
  "SendConnectionRequest",
60
63
  "SendMessage",
61
64
  "SyncConversation",
65
+ "SyncInbox",
62
66
  "WithdrawConnectionRequest",
63
67
  ]
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from linkedapi.core import Operation
4
+ from linkedapi.mappers import VoidWorkflowMapper
5
+ from linkedapi.types import NvSyncInboxParams
6
+
7
+
8
+ class NvSyncInbox(Operation[NvSyncInboxParams, None]):
9
+ """Enable whole-inbox monitoring for Sales Navigator conversations."""
10
+
11
+ operation_name = "nvSyncInbox"
12
+ mapper = VoidWorkflowMapper[NvSyncInboxParams]("nv.syncInbox")
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from linkedapi.core import Operation
4
+ from linkedapi.mappers import VoidWorkflowMapper
5
+ from linkedapi.types import SyncInboxParams
6
+
7
+
8
+ class SyncInbox(Operation[SyncInboxParams, None]):
9
+ """Enable whole-inbox monitoring for standard LinkedIn conversations."""
10
+
11
+ operation_name = "syncInbox"
12
+ mapper = VoidWorkflowMapper[SyncInboxParams]("st.syncInbox")
@@ -101,12 +101,16 @@ from linkedapi.types.message import (
101
101
  ConversationPollRequest,
102
102
  ConversationPollResult,
103
103
  ConversationType,
104
+ InboxMessage,
105
+ InboxPollRequest,
104
106
  Message,
105
107
  MessageSender,
106
108
  NvSendMessageParams,
107
109
  NvSyncConversationParams,
110
+ NvSyncInboxParams,
108
111
  SendMessageParams,
109
112
  SyncConversationParams,
113
+ SyncInboxParams,
110
114
  )
111
115
  from linkedapi.types.params import BaseActionParams, LimitParams, LimitSinceParams
112
116
  from linkedapi.types.person import (
@@ -183,6 +187,8 @@ from linkedapi.types.webhooks import (
183
187
  AccountWebhookEventData,
184
188
  AccountWebhookStatus,
185
189
  DeleteWebhookParams,
190
+ MessageWebhookEvent,
191
+ MessageWebhookEventData,
186
192
  ReplayWebhookDeliveryParams,
187
193
  SetWebhookParams,
188
194
  SetWebhookPayloadModeParams,
@@ -269,6 +275,8 @@ __all__ = [
269
275
  "GetConnectionSessionParams",
270
276
  "GetLimitsParams",
271
277
  "GetLimitsUsageParams",
278
+ "InboxMessage",
279
+ "InboxPollRequest",
272
280
  "Job",
273
281
  "JobCurrency",
274
282
  "JobDatePosted",
@@ -293,6 +301,8 @@ __all__ = [
293
301
  "MaxAnnualRevenue",
294
302
  "Message",
295
303
  "MessageSender",
304
+ "MessageWebhookEvent",
305
+ "MessageWebhookEventData",
296
306
  "MinAnnualRevenue",
297
307
  "NvBaseFetchCompanyParams",
298
308
  "NvBaseFetchCompanyParamsWide",
@@ -313,6 +323,7 @@ __all__ = [
313
323
  "NvSearchPeopleResult",
314
324
  "NvSendMessageParams",
315
325
  "NvSyncConversationParams",
326
+ "NvSyncInboxParams",
316
327
  "PendingConnectionSession",
317
328
  "Person",
318
329
  "PersonEducation",
@@ -377,6 +388,7 @@ __all__ = [
377
388
  "SubscriptionStatus",
378
389
  "SubscriptionStatusValue",
379
390
  "SyncConversationParams",
391
+ "SyncInboxParams",
380
392
  "WebhookDelivery",
381
393
  "WebhookDeliveryStatus",
382
394
  "WebhookEvent",
@@ -10,24 +10,34 @@ MessageSender = Literal["us", "them"]
10
10
 
11
11
 
12
12
  class SendMessageParams(BaseActionParams):
13
- person_url: str
14
13
  text: str
14
+ person_url: str | None = None
15
+ thread_id: str | None = None
15
16
 
16
17
 
17
18
  class SyncConversationParams(BaseActionParams):
18
19
  person_url: str
19
20
 
20
21
 
22
+ class SyncInboxParams(BaseActionParams):
23
+ pass
24
+
25
+
21
26
  class NvSendMessageParams(BaseActionParams):
22
- person_url: str
23
27
  text: str
24
- subject: str
28
+ person_url: str | None = None
29
+ subject: str | None = None
30
+ thread_id: str | None = None
25
31
 
26
32
 
27
33
  class NvSyncConversationParams(BaseActionParams):
28
34
  person_url: str
29
35
 
30
36
 
37
+ class NvSyncInboxParams(BaseActionParams):
38
+ pass
39
+
40
+
31
41
  class ConversationPollRequest(LinkedApiModel):
32
42
  person_url: str
33
43
  type: ConversationType
@@ -39,6 +49,7 @@ class Message(LinkedApiModel):
39
49
  sender: MessageSender | None = None
40
50
  text: str | None = None
41
51
  time: str | None = None
52
+ thread_id: str | None = None
42
53
 
43
54
 
44
55
  class ConversationPollResult(LinkedApiModel):
@@ -46,3 +57,19 @@ class ConversationPollResult(LinkedApiModel):
46
57
  type: ConversationType | None = None
47
58
  messages: list[Message] | None = None
48
59
  since: str | None = None
60
+
61
+
62
+ class InboxPollRequest(LinkedApiModel):
63
+ since: str | None = None
64
+ type: ConversationType | None = None
65
+ thread_id: str | None = None
66
+
67
+
68
+ class InboxMessage(LinkedApiModel):
69
+ id: str | None = None
70
+ type: ConversationType | None = None
71
+ thread_id: str | None = None
72
+ person_url: str | None = None
73
+ sender: MessageSender | None = None
74
+ text: str | None = None
75
+ time: str | None = None
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from typing import Any, Literal
4
4
 
5
5
  from linkedapi.types.base import LinkedApiModel
6
+ from linkedapi.types.message import ConversationType, MessageSender
6
7
 
7
8
  WebhookPayloadMode = Literal["thin", "fat"]
8
9
  WebhookEventType = Literal[
@@ -13,6 +14,8 @@ WebhookEventType = Literal[
13
14
  "account.reconnectionRequired",
14
15
  "account.frozen",
15
16
  "account.deleted",
17
+ "linkedin.messageReceived",
18
+ "linkedin.messageSent",
16
19
  "webhook.test",
17
20
  ]
18
21
  WebhookDeliveryStatus = Literal["pending", "delivering", "success", "failed"]
@@ -91,6 +94,24 @@ class AccountWebhookEvent(LinkedApiModel):
91
94
  data: AccountWebhookEventData
92
95
 
93
96
 
97
+ class MessageWebhookEventData(LinkedApiModel):
98
+ account_id: str | None = None
99
+ type: ConversationType | None = None
100
+ thread_id: str | None = None
101
+ person_url: str | None = None
102
+ message_id: str | None = None
103
+ sender: MessageSender | None = None
104
+ text: str | None = None
105
+ time: str | None = None
106
+
107
+
108
+ class MessageWebhookEvent(LinkedApiModel):
109
+ id: str
110
+ type: Literal["linkedin.messageReceived", "linkedin.messageSent"]
111
+ created_at: str | None = None
112
+ data: MessageWebhookEventData
113
+
114
+
94
115
  class WebhookTestEventData(LinkedApiModel):
95
116
  message: str | None = None
96
117
 
@@ -102,4 +123,6 @@ class WebhookTestEvent(LinkedApiModel):
102
123
  data: WebhookTestEventData
103
124
 
104
125
 
105
- WebhookEvent = WorkflowWebhookEvent | AccountWebhookEvent | WebhookTestEvent
126
+ WebhookEvent = (
127
+ WorkflowWebhookEvent | AccountWebhookEvent | MessageWebhookEvent | WebhookTestEvent
128
+ )
@@ -4,6 +4,7 @@ import json
4
4
 
5
5
  from linkedapi.types.webhooks import (
6
6
  AccountWebhookEvent,
7
+ MessageWebhookEvent,
7
8
  WebhookEvent,
8
9
  WebhookTestEvent,
9
10
  WorkflowWebhookEvent,
@@ -41,6 +42,8 @@ def parse_webhook_event(raw_body: str | bytes) -> WebhookEvent:
41
42
  return WorkflowWebhookEvent.model_validate(parsed)
42
43
  if event_type.startswith("account."):
43
44
  return AccountWebhookEvent.model_validate(parsed)
45
+ if event_type.startswith("linkedin."):
46
+ return MessageWebhookEvent.model_validate(parsed)
44
47
  if event_type == "webhook.test":
45
48
  return WebhookTestEvent.model_validate(parsed)
46
49
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "linkedapi"
7
- version = "1.1.0"
7
+ version = "1.1.1"
8
8
  description = "Official synchronous Python SDK for Linked API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -6,6 +6,7 @@ from conftest import FakeHttpClient
6
6
  from linkedapi import (
7
7
  ApiUsageParams,
8
8
  ConversationPollRequest,
9
+ InboxPollRequest,
9
10
  LinkedApi,
10
11
  LinkedApiActionError,
11
12
  LinkedApiError,
@@ -76,6 +77,63 @@ def test_poll_conversations_other_thrown_error_is_raised(fake_http_client: FakeH
76
77
  assert error.value.type == "httpError"
77
78
 
78
79
 
80
+ def test_poll_inbox_success(fake_http_client: FakeHttpClient) -> None:
81
+ linkedapi = LinkedApi(fake_http_client)
82
+ fake_http_client.queue_response(
83
+ result={
84
+ "messages": [
85
+ {
86
+ "id": "m1",
87
+ "type": "st",
88
+ "threadId": "t1",
89
+ "personUrl": "u",
90
+ "sender": "them",
91
+ "text": "hi",
92
+ "time": "now",
93
+ }
94
+ ]
95
+ }
96
+ )
97
+
98
+ result = linkedapi.poll_inbox(InboxPollRequest(type="st", thread_id="t1"))
99
+
100
+ assert fake_http_client.calls[0] == (
101
+ "POST",
102
+ "/inbox/poll",
103
+ {"type": "st", "threadId": "t1"},
104
+ )
105
+ assert result.data is not None
106
+ assert result.data[0].thread_id == "t1"
107
+ assert result.data[0].sender == "them"
108
+ assert result.errors == []
109
+
110
+
111
+ def test_poll_inbox_without_request_sends_empty_body(fake_http_client: FakeHttpClient) -> None:
112
+ linkedapi = LinkedApi(fake_http_client)
113
+ fake_http_client.queue_response(result={"messages": []})
114
+
115
+ result = linkedapi.poll_inbox()
116
+
117
+ assert fake_http_client.calls[0] == ("POST", "/inbox/poll", {})
118
+ assert result.data == []
119
+ assert result.errors == []
120
+
121
+
122
+ def test_poll_inbox_response_error_maps_to_action_errors(
123
+ fake_http_client: FakeHttpClient,
124
+ ) -> None:
125
+ linkedapi = LinkedApi(fake_http_client)
126
+ fake_http_client.queue_response(
127
+ success=False,
128
+ error=LinkedApiRequestError(type="invalidRequestPayload", message="bad"),
129
+ )
130
+
131
+ result = linkedapi.poll_inbox(InboxPollRequest(type="st"))
132
+
133
+ assert result.data is None
134
+ assert result.errors == [LinkedApiActionError(type="invalidRequestPayload", message="bad")]
135
+
136
+
79
137
  def test_get_account_info_and_api_usage_paths(fake_http_client: FakeHttpClient) -> None:
80
138
  linkedapi = LinkedApi(fake_http_client)
81
139
  fake_http_client.queue_response(result={"name": "Jane", "url": "https://linkedin.com/in/jane"})
@@ -34,8 +34,10 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
34
34
  "remove_connection",
35
35
  "send_message",
36
36
  "sync_conversation",
37
+ "sync_inbox",
37
38
  "nv_send_message",
38
39
  "nv_sync_conversation",
40
+ "nv_sync_inbox",
39
41
  "react_to_post",
40
42
  "comment_on_post",
41
43
  "create_post",
@@ -48,7 +50,7 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
48
50
  assert hasattr(operation, "execute")
49
51
  assert hasattr(operation, "result")
50
52
  assert hasattr(operation, "cancel")
51
- assert len(linkedapi.operations) == 27
53
+ assert len(linkedapi.operations) == 29
52
54
 
53
55
 
54
56
  def test_operation_mappers_match_node_contract() -> None:
@@ -64,6 +66,10 @@ def test_operation_mappers_match_node_contract() -> None:
64
66
  assert linkedapi.fetch_job.mapper.default_params == {"basicInfo": True}
65
67
  assert isinstance(linkedapi.send_connection_request.mapper, VoidWorkflowMapper)
66
68
  assert linkedapi.send_connection_request.mapper.action_type == "st.sendConnectionRequest"
69
+ assert isinstance(linkedapi.sync_inbox.mapper, VoidWorkflowMapper)
70
+ assert linkedapi.sync_inbox.mapper.action_type == "st.syncInbox"
71
+ assert isinstance(linkedapi.nv_sync_inbox.mapper, VoidWorkflowMapper)
72
+ assert linkedapi.nv_sync_inbox.mapper.action_type == "nv.syncInbox"
67
73
 
68
74
 
69
75
  def test_execute_and_result_flow_returns_pydantic_data(
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import pytest
6
+
7
+ from linkedapi import (
8
+ AccountWebhookEvent,
9
+ MessageWebhookEvent,
10
+ WorkflowWebhookEvent,
11
+ parse_webhook_event,
12
+ )
13
+
14
+
15
+ def test_parse_workflow_event() -> None:
16
+ event = parse_webhook_event(
17
+ json.dumps(
18
+ {
19
+ "id": "e1",
20
+ "type": "workflow.completed",
21
+ "data": {"workflowId": "wf1", "accountId": "a1", "status": "completed"},
22
+ }
23
+ )
24
+ )
25
+
26
+ assert isinstance(event, WorkflowWebhookEvent)
27
+ assert event.data.workflow_id == "wf1"
28
+
29
+
30
+ def test_parse_account_event() -> None:
31
+ event = parse_webhook_event(
32
+ json.dumps({"id": "e2", "type": "account.frozen", "data": {"accountId": "a1"}})
33
+ )
34
+
35
+ assert isinstance(event, AccountWebhookEvent)
36
+ assert event.data.account_id == "a1"
37
+
38
+
39
+ def test_parse_message_received_event() -> None:
40
+ event = parse_webhook_event(
41
+ json.dumps(
42
+ {
43
+ "id": "e3",
44
+ "type": "linkedin.messageReceived",
45
+ "data": {
46
+ "accountId": "a1",
47
+ "type": "st",
48
+ "threadId": "t1",
49
+ "personUrl": "u",
50
+ "messageId": "m1",
51
+ "sender": "them",
52
+ "text": "hello",
53
+ "time": "now",
54
+ },
55
+ }
56
+ )
57
+ )
58
+
59
+ assert isinstance(event, MessageWebhookEvent)
60
+ assert event.type == "linkedin.messageReceived"
61
+ assert event.data.thread_id == "t1"
62
+ assert event.data.message_id == "m1"
63
+ assert event.data.sender == "them"
64
+
65
+
66
+ def test_parse_message_sent_event() -> None:
67
+ event = parse_webhook_event(
68
+ json.dumps(
69
+ {
70
+ "id": "e4",
71
+ "type": "linkedin.messageSent",
72
+ "data": {"accountId": "a1", "type": "nv", "sender": "us", "text": "hi"},
73
+ }
74
+ )
75
+ )
76
+
77
+ assert isinstance(event, MessageWebhookEvent)
78
+ assert event.type == "linkedin.messageSent"
79
+ assert event.data.type == "nv"
80
+
81
+
82
+ def test_parse_unknown_event_raises() -> None:
83
+ with pytest.raises(ValueError):
84
+ parse_webhook_event(json.dumps({"id": "e5", "type": "unknown.thing", "data": {}}))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes