async_linkedinlib 0.2.0__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.
- async_linkedinlib/__init__.py +187 -0
- async_linkedinlib/__main__.py +3 -0
- async_linkedinlib/auth/__init__.py +5 -0
- async_linkedinlib/auth/service.py +77 -0
- async_linkedinlib/cli.py +37 -0
- async_linkedinlib/client.py +93 -0
- async_linkedinlib/config/__init__.py +6 -0
- async_linkedinlib/config/options.py +15 -0
- async_linkedinlib/config/profile.py +56 -0
- async_linkedinlib/exceptions.py +75 -0
- async_linkedinlib/models/__init__.py +155 -0
- async_linkedinlib/models/_json.py +65 -0
- async_linkedinlib/models/comments.py +145 -0
- async_linkedinlib/models/content.py +207 -0
- async_linkedinlib/models/feed.py +124 -0
- async_linkedinlib/models/jobs.py +215 -0
- async_linkedinlib/models/jobs_apply.py +424 -0
- async_linkedinlib/models/member.py +14 -0
- async_linkedinlib/models/pagination.py +52 -0
- async_linkedinlib/models/posts.py +141 -0
- async_linkedinlib/models/posts_results.py +120 -0
- async_linkedinlib/models/search.py +307 -0
- async_linkedinlib/models/search_filters.py +339 -0
- async_linkedinlib/models/search_jobs.py +213 -0
- async_linkedinlib/py.typed +0 -0
- async_linkedinlib/resources/__init__.py +12 -0
- async_linkedinlib/resources/account.py +35 -0
- async_linkedinlib/resources/comments/__init__.py +5 -0
- async_linkedinlib/resources/comments/resource.py +227 -0
- async_linkedinlib/resources/comments/urns.py +43 -0
- async_linkedinlib/resources/feed/__init__.py +5 -0
- async_linkedinlib/resources/feed/queries.py +57 -0
- async_linkedinlib/resources/feed/resource.py +225 -0
- async_linkedinlib/resources/feed/saved.py +49 -0
- async_linkedinlib/resources/feed/urns.py +68 -0
- async_linkedinlib/resources/jobs/__init__.py +5 -0
- async_linkedinlib/resources/jobs/answers.py +239 -0
- async_linkedinlib/resources/jobs/resource.py +212 -0
- async_linkedinlib/resources/jobs/upload.py +78 -0
- async_linkedinlib/resources/mentions.py +58 -0
- async_linkedinlib/resources/paging.py +88 -0
- async_linkedinlib/resources/posts/__init__.py +6 -0
- async_linkedinlib/resources/posts/media.py +173 -0
- async_linkedinlib/resources/posts/payloads.py +98 -0
- async_linkedinlib/resources/posts/resource.py +254 -0
- async_linkedinlib/resources/reactions.py +89 -0
- async_linkedinlib/resources/search/__init__.py +5 -0
- async_linkedinlib/resources/search/queries.py +79 -0
- async_linkedinlib/resources/search/results.py +85 -0
- async_linkedinlib/resources/search/search.py +284 -0
- async_linkedinlib/resources/threads.py +30 -0
- async_linkedinlib/session/__init__.py +5 -0
- async_linkedinlib/session/cookies.py +52 -0
- async_linkedinlib/session/lease.py +30 -0
- async_linkedinlib/session/state.py +129 -0
- async_linkedinlib/transport/__init__.py +6 -0
- async_linkedinlib/transport/http.py +183 -0
- async_linkedinlib/transport/restli.py +46 -0
- async_linkedinlib/transport/voyager.py +151 -0
- async_linkedinlib-0.2.0.dist-info/METADATA +478 -0
- async_linkedinlib-0.2.0.dist-info/RECORD +64 -0
- async_linkedinlib-0.2.0.dist-info/WHEEL +4 -0
- async_linkedinlib-0.2.0.dist-info/entry_points.txt +2 -0
- async_linkedinlib-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: async_linkedinlib
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: An async Python client for LinkedIn's Android API: sessions, feed, posts, comments, search and jobs.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Anandesh-Sharma/async_linkedinlib
|
|
6
|
+
Project-URL: Repository, https://github.com/Anandesh-Sharma/async_linkedinlib
|
|
7
|
+
Project-URL: Issues, https://github.com/Anandesh-Sharma/async_linkedinlib/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/Anandesh-Sharma/async_linkedinlib#readme
|
|
9
|
+
Author: Anandesh Sharma
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api-client,asyncio,httpx,linkedin,python
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Operating System :: POSIX
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: httpx[http2]<1,>=0.28
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<img src="https://raw.githubusercontent.com/Anandesh-Sharma/async_linkedinlib/main/assets/banner.png" alt="async_linkedinlib — Async Python for LinkedIn" width="100%">
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
# async_linkedinlib
|
|
27
|
+
|
|
28
|
+
[](https://github.com/Anandesh-Sharma/async_linkedinlib/actions/workflows/ci.yml)
|
|
29
|
+
[](https://pypi.org/project/async_linkedinlib/)
|
|
30
|
+

|
|
31
|
+
[](https://github.com/Anandesh-Sharma/async_linkedinlib/blob/main/LICENSE)
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
Async Python client for LinkedIn's private Android API (app 4.1.1267). One saved session
|
|
35
|
+
drives everything: sign-in, the feed, your posts, comments, search and jobs with Easy Apply.
|
|
36
|
+
Connections and messages are not built yet. Python 3.11+ is required.
|
|
37
|
+
|
|
38
|
+
## At a glance
|
|
39
|
+
|
|
40
|
+
| Area | What you can do |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| Sessions | Import your own cookies, persist sessions and verify your account |
|
|
43
|
+
| Feed | Home, profile and company feeds, reactions, saves and polls |
|
|
44
|
+
| Publishing | Posts, media uploads, comments and replies |
|
|
45
|
+
| Search | People, companies, content, jobs and other supported search categories |
|
|
46
|
+
| Jobs | Search, details, saved jobs and explicit Easy Apply steps |
|
|
47
|
+
|
|
48
|
+
Connections and messages are not implemented. Google sign-in exchanges a caller-supplied
|
|
49
|
+
fresh token; it does not acquire one. See [feature status](#feature-status) for the
|
|
50
|
+
boundary between offline tests and previously recorded live verification.
|
|
51
|
+
|
|
52
|
+
[Sessions](#saved-sessions) · [Feed](#feed-linkedinfeed) · [Posts](#posts-linkedinposts) ·
|
|
53
|
+
[Search](#search-linkedinsearch) · [Jobs](#jobs-and-easy-apply-linkedinjobs) ·
|
|
54
|
+
[Feature status](#feature-status) · [Development](#development)
|
|
55
|
+
|
|
56
|
+
One account, one async client, one shared connection pool. Pagination and writes
|
|
57
|
+
are explicit; session files preserve account state between runs.
|
|
58
|
+
|
|
59
|
+
This is an independent, unofficial client for LinkedIn's private API. It is not
|
|
60
|
+
affiliated with or endorsed by LinkedIn. Private endpoints can change without notice.
|
|
61
|
+
Saved-session ownership uses POSIX file locks; use macOS or Linux.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
Requires Python 3.11 or newer. Install from PyPI:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pip install async_linkedinlib
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For development and the `uv run` examples below:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
git clone https://github.com/Anandesh-Sharma/async_linkedinlib.git
|
|
75
|
+
cd async_linkedinlib
|
|
76
|
+
uv sync --locked
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For reproducible deployments, pin a PyPI version or install a reviewed Git commit: `git+https://github.com/Anandesh-Sharma/async_linkedinlib.git@<commit-sha>`.
|
|
80
|
+
|
|
81
|
+
## Saved sessions
|
|
82
|
+
|
|
83
|
+
`li_at` is the primary session credential. The client also keeps `JSESSIONID` and
|
|
84
|
+
sends its unquoted value as `csrf-token`. Import your own cookie pair through hidden
|
|
85
|
+
prompts, verify it with LinkedIn, and save it outside the repository:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
uv run async-linkedinlib import-session \
|
|
89
|
+
--session ~/.local/share/async_linkedinlib/account.session.json
|
|
90
|
+
uv run async-linkedinlib me \
|
|
91
|
+
--session ~/.local/share/async_linkedinlib/account.session.json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import asyncio
|
|
96
|
+
from pathlib import Path
|
|
97
|
+
from async_linkedinlib import LinkedIn
|
|
98
|
+
|
|
99
|
+
async def main():
|
|
100
|
+
path = Path.home() / ".local/share/async_linkedinlib/account.session.json"
|
|
101
|
+
async with LinkedIn.from_session_file(path) as linkedin:
|
|
102
|
+
me = await linkedin.account.me()
|
|
103
|
+
print(me.full_name, me.id)
|
|
104
|
+
|
|
105
|
+
asyncio.run(main())
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For credentials obtained through your own secure input, use
|
|
109
|
+
`Session.from_cookies(li_at, jsessionid)` with `LinkedIn(session)`. The explicit
|
|
110
|
+
constructor uses in-memory state; call `session.save(path)` when persistence is
|
|
111
|
+
needed. `Session.load(path)` restores state without checking server validity.
|
|
112
|
+
`session.authenticated` only checks local credential presence and expiration.
|
|
113
|
+
`account.me()` checks server acceptance and returns a `Member`.
|
|
114
|
+
|
|
115
|
+
Session files are written atomically with mode 0600. Cookies retain domain, path,
|
|
116
|
+
secure flag and expiry. Device identity and app profile persist with them.
|
|
117
|
+
The file-backed client checkpoints cookie changes and saves on close without
|
|
118
|
+
logging out. It owns a POSIX sidecar lock until closed; a second owner raises
|
|
119
|
+
`SessionInUse`. Do not remove the lock file while another process may own it.
|
|
120
|
+
One file per account; share its active client among tasks.
|
|
121
|
+
|
|
122
|
+
## Google sign-in
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
uv run async-linkedinlib login-google \
|
|
126
|
+
--session ~/.local/share/async_linkedinlib/google.session.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The hidden prompt expects a **fresh Google ID token issued for LinkedIn's Android
|
|
130
|
+
sign-in flow**. An arbitrary token issued for your own Google OAuth client is not
|
|
131
|
+
equivalent. The SDK does not sign into Google, obtain that token, or replay the
|
|
132
|
+
recorded token. In Python, call `await linkedin.auth.login_google(id_token)` on a
|
|
133
|
+
client without an existing authenticated session.
|
|
134
|
+
|
|
135
|
+
The captured sequence is:
|
|
136
|
+
|
|
137
|
+
1. `GET /mob/tracking` obtains fresh server cookies. The app uses a generated
|
|
138
|
+
`JSESSIONID` in `ajax:<19 digits>` form; the client generates a fresh value when
|
|
139
|
+
the bootstrap does not supply one.
|
|
140
|
+
2. `POST /uas/authenticate` submits a URL-encoded Google token with the recorded
|
|
141
|
+
`soogleLoginRequestType=ONE_TAP_AUTO_LOGIN` spelling, native captcha capability,
|
|
142
|
+
CSRF cookie and fresh bootstrap cookies.
|
|
143
|
+
3. A `login_result=PASS` response sets `li_at` and rotates `JSESSIONID`.
|
|
144
|
+
4. `GET /mob/sso/you` verifies the session and reads the member identity.
|
|
145
|
+
|
|
146
|
+
The login method returns the verified `Member`. It neither stores the Google token
|
|
147
|
+
nor retries the exchange. A challenge URL is available through
|
|
148
|
+
`ChallengeRequired.challenge_url` but is not followed or included in its message.
|
|
149
|
+
Complete challenges in the official app. Email/password login, Google token
|
|
150
|
+
acquisition, MFA continuation and remote logout are not implemented.
|
|
151
|
+
|
|
152
|
+
## How requests are made
|
|
153
|
+
|
|
154
|
+
The app draws feed, post, comment, search and job screens with server-driven protobuf. The
|
|
155
|
+
same APK still ships the classic Voyager operations, and Voyager answers plain JSON on the
|
|
156
|
+
same session, so every feature calls those: persisted GraphQL queries and mutations
|
|
157
|
+
(`/voyager/api/graphql`) and rest.li routes (`/voyager/api/<route>`), with variables encoded
|
|
158
|
+
exactly as the app encodes them ([transport/voyager.py](https://github.com/Anandesh-Sharma/async_linkedinlib/blob/main/src/async_linkedinlib/transport/voyager.py),
|
|
159
|
+
[transport/restli.py](https://github.com/Anandesh-Sharma/async_linkedinlib/blob/main/src/async_linkedinlib/transport/restli.py)). Query IDs, variable names,
|
|
160
|
+
enums and bodies come from the app's code; each was then checked against the live service.
|
|
161
|
+
|
|
162
|
+
Every resource shares the client's one session and connection pool. Lists return a
|
|
163
|
+
`Page` with `items`, `total` (when LinkedIn gives one) and `next`, a `Cursor` that continues
|
|
164
|
+
only the operation, query and session that made it; pass it back as `cursor=`. Paging
|
|
165
|
+
follows the app's pager (next start = `paging.start + paging.count`, until `paging.total`
|
|
166
|
+
or an empty page), and nothing fetches the next page by itself. `count` and `cursor` are
|
|
167
|
+
keyword arguments everywhere. Writes are sent once and never retried. URNs and IDs are
|
|
168
|
+
strings. Every public type is importable from `async_linkedinlib`. Models keep the provider
|
|
169
|
+
object in `raw` (left out of repr).
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from async_linkedinlib import FeedSort, LinkedIn, ReactionType
|
|
173
|
+
|
|
174
|
+
async with LinkedIn.from_session_file(path) as linkedin:
|
|
175
|
+
page = await linkedin.feed.home(count=10, sort=FeedSort.RECENT)
|
|
176
|
+
post = page.items[0]
|
|
177
|
+
await linkedin.feed.react(post.urn, ReactionType.CELEBRATE)
|
|
178
|
+
comments = await linkedin.comments.list(post.urn)
|
|
179
|
+
more = await linkedin.feed.home(count=10, sort=FeedSort.RECENT, cursor=page.next)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Feed (`linkedin.feed`)
|
|
183
|
+
|
|
184
|
+
| Operation | Method |
|
|
185
|
+
| --- | --- |
|
|
186
|
+
| Main feed, top or recent | `home(count, sort, cursor)` |
|
|
187
|
+
| One post by activity, ugcPost or share URN | `get(urn)` |
|
|
188
|
+
| A member's posts, or posts they commented on or reacted to | `profile(profile_urn, activity, count, cursor)` |
|
|
189
|
+
| A company page's posts (all, images, videos, documents, articles) | `company(company_urn, content, count, cursor)` |
|
|
190
|
+
| Who reacted, by type and order | `reactions(urn, reaction, sort, count, cursor)` |
|
|
191
|
+
| React, change, remove | `react(urn, reaction)`, `change_reaction`, `unreact` |
|
|
192
|
+
| Save, unsave, saved posts | `save(activity_urn)`, `unsave`, `saved(count, cursor)` |
|
|
193
|
+
| Hide a post and undo | `hide(post.update_urn)`, `undo_hide` |
|
|
194
|
+
| Vote on a poll and withdraw | `vote(option.urn)`, `unvote` |
|
|
195
|
+
|
|
196
|
+
`Post.urn` is the post's social thread (reactions and comments use it); `activity_urn` is the
|
|
197
|
+
feed activity, which differs for "X likes this" updates. Recommendation modules are not posts,
|
|
198
|
+
so `items` can be shorter than `count` (`page.raw` keeps them). `save` takes the post's own
|
|
199
|
+
activity URN. Profile feeds give no total. No read shows whether a post is hidden.
|
|
200
|
+
|
|
201
|
+
### Posts (`linkedin.posts`)
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from async_linkedinlib import CommentScope, Image, MediaKind, Mention, Visibility
|
|
205
|
+
|
|
206
|
+
text = "Hello from Ada Lovelace"
|
|
207
|
+
shared = await linkedin.posts.create(
|
|
208
|
+
text, visibility=Visibility.CONNECTIONS_ONLY, comments=CommentScope.ANYONE,
|
|
209
|
+
mentions=[Mention("urn:li:fsd_profile:<id>", text.index("Ada"), 12)],
|
|
210
|
+
)
|
|
211
|
+
photo = await linkedin.posts.media.upload("photo.png", MediaKind.IMAGE)
|
|
212
|
+
await linkedin.posts.create("With a photo", media=Image(photo))
|
|
213
|
+
await linkedin.posts.edit(shared.share_urn, "Hello again")
|
|
214
|
+
await linkedin.posts.delete(shared.share_urn)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
| Operation | Method | Verified |
|
|
218
|
+
| --- | --- | --- |
|
|
219
|
+
| Text post with visibility, comment control, member/company mentions | `create` | live |
|
|
220
|
+
| Schedule, list, reschedule, cancel | `create(publish_at=)`, `scheduled`, `reschedule`, `cancel_scheduled` | live |
|
|
221
|
+
| Edit text and mentions; delete | `edit`, `delete` | live |
|
|
222
|
+
| Who may comment on a published post | `set_comment_scope` | live |
|
|
223
|
+
| Instant repost and undo | `repost`, `undo_repost` | live |
|
|
224
|
+
| Upload image, video, document; processing status; link preview | `media.upload`, `media.status`, `media.link_preview` | live |
|
|
225
|
+
| Image, gallery, video, document, poll and link posts; repost with thoughts | `create(media=...)`, `repost_with_thoughts` | offline (see limits) |
|
|
226
|
+
|
|
227
|
+
Mention offsets count Unicode code points; the server silently drops a wrong offset.
|
|
228
|
+
Hashtags are plain `#text`. After publishing only text and mentions can be edited. A gallery
|
|
229
|
+
needs `session.member_id` (call `account.me()` once). Galleries and polls are created before
|
|
230
|
+
the post and remain if the post is refused. Uploads must come back as single-request
|
|
231
|
+
uploads. Uploaded media cannot be deleted. Unverified accounts get a small daily posting
|
|
232
|
+
limit, raised as `OperationRejected`; the test account hit it before the media post kinds
|
|
233
|
+
could be published, so those shapes are tested offline only.
|
|
234
|
+
|
|
235
|
+
### Comments (`linkedin.comments`)
|
|
236
|
+
|
|
237
|
+
| Operation | Method |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| Top-level comments, relevant or recent, with reply previews | `list(post_urn, sort, count, cursor)` |
|
|
240
|
+
| Replies, oldest first; one comment | `replies(comment_urn, count, cursor)`, `get(comment_urn)` |
|
|
241
|
+
| Comment, reply (with member mentions) | `create(post_urn, text, mentions)`, `reply(comment_urn, text, mentions)` |
|
|
242
|
+
| Edit, delete own comment | `edit(comment_urn, text, mentions)`, `delete(comment_urn)` |
|
|
243
|
+
| React, change, remove; who reacted (`Reactor`s, as for posts) | `react`, `change_reaction`, `unreact`, `reactions(comment_urn, reaction, sort, count, cursor)` |
|
|
244
|
+
| Hide another member's comment on your post | `hide(comment)`, `unhide(comment)` (offline only) |
|
|
245
|
+
|
|
246
|
+
Comment URNs may be `urn:li:comment:(activity:<post>,<id>)` or
|
|
247
|
+
`urn:li:fsd_comment:(<id>,urn:li:activity:<post>)`. A reply to a reply attaches to its
|
|
248
|
+
top-level comment. Text only: no image, GIF or company mentions; no pinning.
|
|
249
|
+
|
|
250
|
+
### Search (`linkedin.search`)
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from async_linkedinlib import JobFilters, PeopleFilters, TypeaheadKind
|
|
254
|
+
|
|
255
|
+
berlin = (await linkedin.search.typeahead(TypeaheadKind.GEO, "Berlin", count=1))[0]
|
|
256
|
+
people = await linkedin.search.people("python developer", PeopleFilters(locations=[berlin.id]))
|
|
257
|
+
jobs = await linkedin.search.jobs("python", JobFilters(location=berlin.id, easy_apply=True))
|
|
258
|
+
job = await linkedin.jobs.get(jobs.items[0].id)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
| Operation | Returns |
|
|
262
|
+
| --- | --- |
|
|
263
|
+
| `all(keywords)` | `Page[ResultGroup]`: People, Posts, Jobs, Companies blocks |
|
|
264
|
+
| `people`, `services` (with `PeopleFilters`, `ServiceFilters`) | `Page[PersonResult]` |
|
|
265
|
+
| `posts` (with `PostFilters`) | `Page[Post]` |
|
|
266
|
+
| `companies`, `groups`, `schools`, `events`, `courses`, `products` | `Page[SearchResult]` |
|
|
267
|
+
| `jobs` (with `JobFilters`) | `Page[JobResult]`; `id` goes to `linkedin.jobs` |
|
|
268
|
+
| `filters(vertical, keywords)`, `job_filters(keywords)` | available filters and suggested values |
|
|
269
|
+
| `typeahead(kind, text)`, `suggest(text)` | `Suggestion`s; `id` is the filter value |
|
|
270
|
+
|
|
271
|
+
Filters are typed dataclasses per vertical; ID fields take IDs or simple URNs, and `extra`
|
|
272
|
+
passes any other key that `filters()` lists. People filters cover connection degree,
|
|
273
|
+
locations, current and past companies, schools, industries, profile languages, connections
|
|
274
|
+
of, followers of, service categories, open to volunteering and name/title/company/school
|
|
275
|
+
keywords. Job filters cover location and distance, date posted, Easy Apply, under 10
|
|
276
|
+
applicants, in my network, verified, workplace, experience, job type, company, place,
|
|
277
|
+
salary, industry, function, title and commitments; `workplace` takes the same
|
|
278
|
+
`WorkplaceType` that `Job` reports. Searches never add to search history.
|
|
279
|
+
People, company, group and service results stop at 1,000; `total` is an estimate.
|
|
280
|
+
|
|
281
|
+
### Jobs and Easy Apply (`linkedin.jobs`)
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
from pathlib import Path
|
|
285
|
+
|
|
286
|
+
form = await linkedin.jobs.application(job.id) # pages, questions, offered resumes
|
|
287
|
+
for question in form.questions:
|
|
288
|
+
print(question.label, question.kind, [o.label for o in question.options])
|
|
289
|
+
resume = await linkedin.jobs.upload_file(
|
|
290
|
+
Path("resume.pdf"), max_size=form.file("Resume").max_size # refuse before storing
|
|
291
|
+
)
|
|
292
|
+
submission = linkedin.jobs.submission(
|
|
293
|
+
form, {"Mobile phone number": "5550100"}, files={"Resume": resume}, job=job
|
|
294
|
+
)
|
|
295
|
+
for label, values in submission.answers: # checked locally; nothing sent yet
|
|
296
|
+
print(label, values)
|
|
297
|
+
status = await linkedin.jobs.submit(submission) # applies; sent once
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
| Operation | Method | Verified |
|
|
301
|
+
| --- | --- | --- |
|
|
302
|
+
| Job details (title, company, place, workplace, type, description, applicants, Easy Apply, state) | `get(job)` | live |
|
|
303
|
+
| Save, unsave; My jobs lists (saved, in progress, applied, archived) | `save`, `unsave`, `tracked(status, count, cursor)` | live |
|
|
304
|
+
| Easy Apply form with typed questions and file slots | `application(job)` | live |
|
|
305
|
+
| Store a .pdf/.doc/.docx file | `upload_file(file)` | live |
|
|
306
|
+
| Check answers and build the app's submission | `submission(form, answers, files)` | live, not sent |
|
|
307
|
+
| Send the application | `submit(submission)` | offline only |
|
|
308
|
+
|
|
309
|
+
Answers are checked against question kinds, options and limits; unanswered questions keep
|
|
310
|
+
LinkedIn's prefill. Date, location and typeahead questions keep their prefill, conditional
|
|
311
|
+
rules are not evaluated, and forms that need a data-consent step or have repeatable sections
|
|
312
|
+
are refused. Following the company is off unless `follow_company=True`. Uploaded files cannot
|
|
313
|
+
be deleted, and the app has no withdraw. `submit` has not been sent to the live service:
|
|
314
|
+
the one real application is left to the account owner.
|
|
315
|
+
|
|
316
|
+
## Failures and transport
|
|
317
|
+
|
|
318
|
+
Expected failures inherit from `LinkedInError`. Authentication rejection, required
|
|
319
|
+
verification, HTTP failures, network failures, rate limits, unexpected protocol responses
|
|
320
|
+
and refused operations have separate exception classes. `OperationRejected.reasons` holds
|
|
321
|
+
LinkedIn's GraphQL messages (for example a validation error or the daily posting limit);
|
|
322
|
+
`HTTPError.reason` holds rest.li's one-line client-error message when there is one.
|
|
323
|
+
`UnconfirmedWrite` (a `ProtocolError`) means LinkedIn accepted a write but its reply could
|
|
324
|
+
not be read: the change may exist, so read the state back before sending it again.
|
|
325
|
+
Errors do not attach raw HTTP responses or include credentials in messages. A 403 is conservatively reported as requiring
|
|
326
|
+
review in the app; it does not prove which verification or restriction occurred.
|
|
327
|
+
|
|
328
|
+
HTTP 401 clears the local `li_at` credential. HTTP 429 records the `Retry-After`
|
|
329
|
+
cooldown and blocks further requests locally until it expires. Without a valid
|
|
330
|
+
server hint, the local fallback is 60 seconds. Cooldowns persist with the session.
|
|
331
|
+
No network operation is automatically retried, and redirects are not followed.
|
|
332
|
+
Requests are serialized per client so cookie rotation is applied in order.
|
|
333
|
+
|
|
334
|
+
`ClientOptions(timeout=30, max_connections=1)` configures the HTTPX pool.
|
|
335
|
+
TLS verification stays enabled and ambient proxy environment variables are ignored.
|
|
336
|
+
The SDK contacts only `https://www.linkedin.com` (media and resume uploads go to its
|
|
337
|
+
`/dms-uploads/` and `/ambry/` paths); it needs neither an emulator nor the capture proxy at
|
|
338
|
+
runtime. The low-level HTTPX transport can be injected
|
|
339
|
+
for tests. Do not enable HTTP wire logging when using real credentials.
|
|
340
|
+
|
|
341
|
+
## Files and verification
|
|
342
|
+
|
|
343
|
+
```text
|
|
344
|
+
src/async_linkedinlib/
|
|
345
|
+
client.py public client, lifecycle and resource construction
|
|
346
|
+
cli.py interactive login, import-session and me commands
|
|
347
|
+
exceptions.py public, credential-safe failures
|
|
348
|
+
auth/service.py Google login orchestration and fresh bootstrap
|
|
349
|
+
config/ connection options, versioned Android app metadata
|
|
350
|
+
session/ identity, scoped cookies, cooldowns, atomic persistence, lease
|
|
351
|
+
transport/
|
|
352
|
+
http.py shared HTTPX pool, headers, cookie rotation and errors
|
|
353
|
+
voyager.py GraphQL queries/mutations and rest.li calls, error decoding
|
|
354
|
+
restli.py the app's rest.li variable encoding
|
|
355
|
+
resources/
|
|
356
|
+
account.py current-member read and verification
|
|
357
|
+
paging.py the app's pager: collections, pages and session-bound cursors
|
|
358
|
+
threads.py post and comment thread URNs, social-detail URNs
|
|
359
|
+
mentions.py member/company mentions in post and comment text
|
|
360
|
+
reactions.py react, change, remove and list reactions on posts and comments
|
|
361
|
+
feed/ home, single post, profile/company feeds, saves, hides, polls
|
|
362
|
+
posts/ create, edit, delete, schedule, repost; media uploads
|
|
363
|
+
comments/ list, replies, create, reply, edit, delete, reactions
|
|
364
|
+
search/ verticals, job search, filters, typeahead
|
|
365
|
+
jobs/ job details, saved jobs, tracker, Easy Apply
|
|
366
|
+
models/ typed results and inputs per feature; content.py holds Post,
|
|
367
|
+
Author and ReactionType; pagination.py holds Page and Cursor
|
|
368
|
+
tests/ one folder per area, synthetic data, httpx.MockTransport;
|
|
369
|
+
restli_decode.py reads what a test request sent
|
|
370
|
+
examples/ account, feed, posts, comments, search, jobs
|
|
371
|
+
AGENTS.md library architecture and contribution rules
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The call path is `LinkedIn resource → Voyager → Transport → LinkedIn HTTP endpoint`.
|
|
375
|
+
All resources use the client's one session and transport. Models contain data;
|
|
376
|
+
resources own feature behavior; the client assembles them and manages lifetime.
|
|
377
|
+
See [AGENTS.md](https://github.com/Anandesh-Sharma/async_linkedinlib/blob/main/AGENTS.md) before adding functionality.
|
|
378
|
+
|
|
379
|
+
### Feature status
|
|
380
|
+
|
|
381
|
+
| Client group | Status |
|
|
382
|
+
| --- | --- |
|
|
383
|
+
| `auth` | Google-token exchange; tested locally, fresh exchange not live-verified |
|
|
384
|
+
| `account` | live verified |
|
|
385
|
+
| `feed`, `comments`, `search` | live verified, except comment hide/unhide (offline) |
|
|
386
|
+
| `posts` | live verified, except posts with media, polls, links and repost with thoughts (offline; daily posting limit) |
|
|
387
|
+
| `jobs` | live verified up to Easy Apply submit; `submit` offline only |
|
|
388
|
+
| `connections`, `messages` | not built |
|
|
389
|
+
|
|
390
|
+
```sh
|
|
391
|
+
uv run pytest -q
|
|
392
|
+
uv run ruff check src tests examples
|
|
393
|
+
uv run ruff format --check src tests examples
|
|
394
|
+
uv run mypy src
|
|
395
|
+
uv build
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Evidence comes from a private Android research lab (not distributed here): the
|
|
399
|
+
`login-20261031-055813` capture for sign-in; recordings of every feature flow in the app;
|
|
400
|
+
and the decompiled 4.1.1267 APK, whose Voyager GraphQL clients, routes and request builders
|
|
401
|
+
give each operation's query ID, variables and body. Each module names its query IDs.
|
|
402
|
+
The login capture contains a successful Google exchange and account verification and no password login. Raw captures,
|
|
403
|
+
Google tokens and real account cookies remain outside this package. The four
|
|
404
|
+
unavailable response-body exports were streaming/unrelated records; the login and
|
|
405
|
+
account-verification bodies were exported successfully.
|
|
406
|
+
|
|
407
|
+
Postman CLI 1.45.0 lacks the simulation command required by the installed skill.
|
|
408
|
+
Verification uses synthetic protocol tests and a real loopback HTTP server instead.
|
|
409
|
+
|
|
410
|
+
Verified on 2026-10-31:
|
|
411
|
+
|
|
412
|
+
- The official Android app completed Google sign-in and returned `li_at` and
|
|
413
|
+
`JSESSIONID` in the captured success response.
|
|
414
|
+
- A fresh Python bootstrap to `/mob/tracking` returned HTTP 200 and server cookies.
|
|
415
|
+
- The SDK imported only the captured `li_at`/`JSESSIONID` credential pair and called
|
|
416
|
+
`account.me()` successfully; the member matched the captured account response.
|
|
417
|
+
- Reopening the saved session and reading the account again succeeded without login.
|
|
418
|
+
- Synthetic protocol and real loopback HTTP tests cover the Google exchange, cookie
|
|
419
|
+
rotation/deletion, session persistence, malformed responses, challenge reporting,
|
|
420
|
+
rejected sessions, redirects, network failures and persisted rate limits.
|
|
421
|
+
|
|
422
|
+
Feature verification on 2026-10-31 used the test account through the SDK itself: each
|
|
423
|
+
operation in the tables above marked live was run once, every write was undone or deleted,
|
|
424
|
+
and the cleanup was confirmed with a read. Five test media uploads, one gallery container,
|
|
425
|
+
one poll container and one Easy Apply resume file remain stored on LinkedIn because the app
|
|
426
|
+
offers no way to delete them; none is attached to a post, profile or application.
|
|
427
|
+
|
|
428
|
+
A fresh programmatic Google ID-token exchange has not been live-verified. The
|
|
429
|
+
captured token was not replayed.
|
|
430
|
+
|
|
431
|
+
## Development
|
|
432
|
+
|
|
433
|
+
The normal suite uses synthetic data and local HTTP servers. It does not need a
|
|
434
|
+
LinkedIn account. Run these checks before opening a pull request:
|
|
435
|
+
|
|
436
|
+
```sh
|
|
437
|
+
uv sync --locked
|
|
438
|
+
uv run pytest -q
|
|
439
|
+
uv run ruff check src tests examples
|
|
440
|
+
uv run ruff format --check src tests examples
|
|
441
|
+
uv run mypy src
|
|
442
|
+
uv build
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
CI runs these checks on Linux and macOS with Python 3.11 and 3.13. Historical live
|
|
446
|
+
verification is documented above; CI does not verify current private API behavior.
|
|
447
|
+
|
|
448
|
+
Keep changes focused, include a synthetic regression test for behavior changes,
|
|
449
|
+
and update this README when a public method changes. Open a
|
|
450
|
+
[bug report or feature request](https://github.com/Anandesh-Sharma/async_linkedinlib/issues/new/choose) with a minimal example,
|
|
451
|
+
Python/package versions, and a redacted error. Keep cookies, passwords, tokens,
|
|
452
|
+
session files, raw captures and real account data out of issues and pull requests.
|
|
453
|
+
|
|
454
|
+
## Related library
|
|
455
|
+
|
|
456
|
+
[async_instalib](https://github.com/Anandesh-Sharma/async_instalib) is the companion async Python client for Instagram. Each package is independent.
|
|
457
|
+
|
|
458
|
+
## License
|
|
459
|
+
|
|
460
|
+
[MIT](https://github.com/Anandesh-Sharma/async_linkedinlib/blob/main/LICENSE) © 2026 Anandesh Sharma.
|
|
461
|
+
|
|
462
|
+
## Releases
|
|
463
|
+
|
|
464
|
+
Publishing a GitHub release runs the complete CI matrix, builds a wheel and source
|
|
465
|
+
archive, validates package metadata, and uploads both to PyPI with
|
|
466
|
+
[trusted publishing](https://docs.pypi.org/trusted-publishers/). No stored PyPI token
|
|
467
|
+
is needed. The publisher is bound to `Anandesh-Sharma/async_linkedinlib`, `release.yml`, and
|
|
468
|
+
the GitHub environment `pypi`.
|
|
469
|
+
|
|
470
|
+
1. Update `project.version` in `pyproject.toml`, run `uv lock`, and commit to `main`.
|
|
471
|
+
2. Publish a GitHub release with the matching tag `v<version>` from that commit.
|
|
472
|
+
3. Check the [publishing workflow](https://github.com/Anandesh-Sharma/async_linkedinlib/actions/workflows/release.yml) and
|
|
473
|
+
[PyPI project](https://pypi.org/project/async_linkedinlib/).
|
|
474
|
+
|
|
475
|
+
Tags must match the package version exactly and point to a commit on `main`.
|
|
476
|
+
A draft release does not publish. A published prerelease does; use a matching
|
|
477
|
+
PEP 440 prerelease version such as `0.3.0rc1`. Re-running a release skips files
|
|
478
|
+
already present only after checking their SHA-256 hashes match the build.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
async_linkedinlib/__init__.py,sha256=lz7R5w5EE1sbk7SCyYxUISunqYN86omXcTVJrqyspAk,7688
|
|
2
|
+
async_linkedinlib/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
|
|
3
|
+
async_linkedinlib/cli.py,sha256=BTXdjEtGGpS07fUXMfLOKnCamH0nhmhQ3hz45fu-MW4,1511
|
|
4
|
+
async_linkedinlib/client.py,sha256=l24__hsrTDKsCZAHdw7w9ZlkI3MGQE0zkAAmij9or-0,2955
|
|
5
|
+
async_linkedinlib/exceptions.py,sha256=vSavA34U8wnQgdFeNlPFp-2byodrqm2O3d2U_DbZzXc,2527
|
|
6
|
+
async_linkedinlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
async_linkedinlib/auth/__init__.py,sha256=phRgYDPZM7cheUmlr7d6zVTpoctmcL65dFk6RJ8u3Jk,121
|
|
8
|
+
async_linkedinlib/auth/service.py,sha256=-qPIMAz5r4YGFQp9Tvdii-fw3Bb9v8o7UKaDjQTMQyc,3348
|
|
9
|
+
async_linkedinlib/config/__init__.py,sha256=2cTrGAqF6EvldJ5zDMNfeCgGpz2habd3zTAoPeBrJsI,202
|
|
10
|
+
async_linkedinlib/config/options.py,sha256=sFaqdNY8ZbQRjhcGG4Bb0KvBgvz3VqjaFif9HN_3FvE,438
|
|
11
|
+
async_linkedinlib/config/profile.py,sha256=V0sQhpJshZDw42V_y_IYWUA2w7tRNyXYge9nLdQeGoU,1982
|
|
12
|
+
async_linkedinlib/models/__init__.py,sha256=WTPExSLDnOg_AULF71TBNgHjhOZiXZGoyJHbQPIDlZ8,6378
|
|
13
|
+
async_linkedinlib/models/_json.py,sha256=XnWBVYxD6pPdEE5R8qZVvd_QpDmnyC9UJZcb1V-d338,2113
|
|
14
|
+
async_linkedinlib/models/comments.py,sha256=lmHrUyfezsmE46ZuaTVJVV7WHakEC0iJP_ePMkn_4h0,5662
|
|
15
|
+
async_linkedinlib/models/content.py,sha256=Dp_NjVvAdxykTY5ZQTp3-1IYv7bJnEm-4HswoDbXJpw,7922
|
|
16
|
+
async_linkedinlib/models/feed.py,sha256=9BvYw2C7HbyAJRQZ1QBA3PZ1VcrI7oHNq89BXu2Bm68,4153
|
|
17
|
+
async_linkedinlib/models/jobs.py,sha256=jh9YQ4NnBlema4r63CsFhSt_KZkIDF5S3BBSxVtVStU,8460
|
|
18
|
+
async_linkedinlib/models/jobs_apply.py,sha256=A9zeehFmDNPBnb95uJD1coaZgJw5DRcSr0pq-jr9sLY,16188
|
|
19
|
+
async_linkedinlib/models/member.py,sha256=kf5j73ZIFWHM2WHXLPm_dJV2mGxidTawsifhxcjtSeI,312
|
|
20
|
+
async_linkedinlib/models/pagination.py,sha256=HvHo3M5OUrzQbkd7-Av8TdR6jwUrnmk7XA69cZm4Ttg,1905
|
|
21
|
+
async_linkedinlib/models/posts.py,sha256=t6OZzPlxZKcVeYcXeD8i0Ub9Y4atQm-4Dx25fe-DISA,4316
|
|
22
|
+
async_linkedinlib/models/posts_results.py,sha256=VzZ9QF9LG1b8WZ_dgh0mjotcAPdXEg_0grufTO8ItRc,4200
|
|
23
|
+
async_linkedinlib/models/search.py,sha256=Fu9Hlnk9NOpIG80cCAzqdfL8hOn6i0p_A9z-Gm9sjf0,10806
|
|
24
|
+
async_linkedinlib/models/search_filters.py,sha256=TZQu7IjsqlYxoMMBENcmB3VTdplOJH9py4KWmU-NZpE,12146
|
|
25
|
+
async_linkedinlib/models/search_jobs.py,sha256=gpBLxJF5awuY1S5ZDn5F8eT3-Lhll6qDwVRdjyy3m5E,7650
|
|
26
|
+
async_linkedinlib/resources/__init__.py,sha256=DIgzbR5JlP7qYVLacO6HC4Gvf7GUMaM1x5JEpan6V9U,710
|
|
27
|
+
async_linkedinlib/resources/account.py,sha256=fDLuue5Q9M4KQBkMXDXL8zP1h0qevrs7zPBQMRvWcTo,1666
|
|
28
|
+
async_linkedinlib/resources/mentions.py,sha256=0Hj7T4nJ0_0DuMKcS0btCb7L4BEoH1mIBtyaP1F4EVs,2346
|
|
29
|
+
async_linkedinlib/resources/paging.py,sha256=tz1uRyXi0m0QKLpKUj6v_y9_pz1TTOHcE2i5s0G0p7M,3162
|
|
30
|
+
async_linkedinlib/resources/reactions.py,sha256=4Zn8UCAb4GyGneb2MxP80L09YHNvFXexG62tykWoUkc,3431
|
|
31
|
+
async_linkedinlib/resources/threads.py,sha256=WSb2wmH3blYqYDkXtarY_751qbAkyLbAULGjFMB1rwI,1194
|
|
32
|
+
async_linkedinlib/resources/comments/__init__.py,sha256=IIKHMhJUbB_RwPyAcFk5eNvwfNhxdTpveJ2BeuRXL8M,129
|
|
33
|
+
async_linkedinlib/resources/comments/resource.py,sha256=PKNxVAEf41bvhmleLjt2Tngop0mnpn9jufAqzX07VP0,9439
|
|
34
|
+
async_linkedinlib/resources/comments/urns.py,sha256=jxTvLMhtjjQZmjVmVSWYidF_s0NtuolKLKB0jKtBXj4,1524
|
|
35
|
+
async_linkedinlib/resources/feed/__init__.py,sha256=zSEwt1Wpbzb-g5DmC1zdlv5wAhsp5NqEVUQ9BO_v0z4,118
|
|
36
|
+
async_linkedinlib/resources/feed/queries.py,sha256=SLW0RimtQGiccW6LvY4HAHMHCJ-W5EXltWgz9MTmxPQ,2358
|
|
37
|
+
async_linkedinlib/resources/feed/resource.py,sha256=i8eNK23WBuECmEvVFZoL0il-UxhKlBKM_LjLYOYIlL0,9032
|
|
38
|
+
async_linkedinlib/resources/feed/saved.py,sha256=SzvS7y5MT74jOcuNdZjb9nCC8i3c4t_T1Yd-ieHZpyE,1713
|
|
39
|
+
async_linkedinlib/resources/feed/urns.py,sha256=TrzSe5Ld8jgfrDjqcFIjqsWneIreSkxe1EIlzmaJjo0,2581
|
|
40
|
+
async_linkedinlib/resources/jobs/__init__.py,sha256=AuVTTooN3gSRpUaQBVSKm0Wyv9w-OLwnF7Tsct6HOBA,133
|
|
41
|
+
async_linkedinlib/resources/jobs/answers.py,sha256=ladpOypMTsY8LmsmyhRZewvrYDMGIvSkgcUMidEpUYo,10859
|
|
42
|
+
async_linkedinlib/resources/jobs/resource.py,sha256=pzaKQRJj13gF8n5DoTttMxw5YgLdjO7BSGIcfFK_R5Q,8750
|
|
43
|
+
async_linkedinlib/resources/jobs/upload.py,sha256=M7yYWPJvHTYfUzyjTeumFCkTdNcMX1jcNrlbZZtuB64,3145
|
|
44
|
+
async_linkedinlib/resources/posts/__init__.py,sha256=GPb5zfQU7Adp11r94PDzH74ezLIKGqvZksZVw_zd42I,237
|
|
45
|
+
async_linkedinlib/resources/posts/media.py,sha256=-v3GJqKkD_UiO6uJSDm6hUpqGx3dFPjA6eOMYyTV0Bg,7220
|
|
46
|
+
async_linkedinlib/resources/posts/payloads.py,sha256=ZNiEowsWx9Go0BiY4pJ3CqSfOSPGvObGCxRh7R6apCc,3179
|
|
47
|
+
async_linkedinlib/resources/posts/resource.py,sha256=TdctnLIS_4YgEQ9Pb8rPK2K8pgumRAah04tpDHHKo-E,10870
|
|
48
|
+
async_linkedinlib/resources/search/__init__.py,sha256=SALXeXqWrOvuI5A3aIjB_u5EFuRFs6uCpGQkqQePGq4,148
|
|
49
|
+
async_linkedinlib/resources/search/queries.py,sha256=6dn4_nIoGRd48ViR7naroZxVeGrDoKdETpJM0zCQo9Y,3209
|
|
50
|
+
async_linkedinlib/resources/search/results.py,sha256=k-EpFdrg0jZTyHEYDhVRAinU6MCsFUJAUxwYZY1FjBg,2849
|
|
51
|
+
async_linkedinlib/resources/search/search.py,sha256=Mdme6UcBjNF2Cz4x-6QzhyyemnWeR8bS6ZDAvFM9iPU,11163
|
|
52
|
+
async_linkedinlib/session/__init__.py,sha256=ZIDDsV1GKDOvTIlOxkZLpBRHcddi43mUV-aarQan5ZI,123
|
|
53
|
+
async_linkedinlib/session/cookies.py,sha256=lMDPwwBh10o9cPxy7kqWDMDns6EQobkzO2jBB82AhsM,1642
|
|
54
|
+
async_linkedinlib/session/lease.py,sha256=IV4q32FtdA2c8JOWvRD6jVveGDOOUOG9ZiVrcMEbsT0,927
|
|
55
|
+
async_linkedinlib/session/state.py,sha256=7EJ1tjnrYOzOOCUfa0K0hXQDeHtIZhQP7IotAYhSOA4,5043
|
|
56
|
+
async_linkedinlib/transport/__init__.py,sha256=AiNNLA66jHf6aUFHs5xTJ-6MsBnYNZaPQMLnXcacc8M,228
|
|
57
|
+
async_linkedinlib/transport/http.py,sha256=em4ZSxlv1yzMDP6GcxucKg2k7WlspGWfy-etf0v_Kow,6960
|
|
58
|
+
async_linkedinlib/transport/restli.py,sha256=vmf681z6W-o8xWVBEw28jdIwsuowFww9XuqlabABQ78,1994
|
|
59
|
+
async_linkedinlib/transport/voyager.py,sha256=HkR0dnqKLupBy0_BGmjYdH91PWXX-Zex_rFhJ5B12HM,6098
|
|
60
|
+
async_linkedinlib-0.2.0.dist-info/METADATA,sha256=y1OvhdxTtq6rwkAma7jhl7R219osQK5xRT_aHhCildM,25661
|
|
61
|
+
async_linkedinlib-0.2.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
62
|
+
async_linkedinlib-0.2.0.dist-info/entry_points.txt,sha256=wY1cHAT0kY-iH3lx8kIkZTcv40lgRNa1hn4bq4bfjQ8,65
|
|
63
|
+
async_linkedinlib-0.2.0.dist-info/licenses/LICENSE,sha256=61MZy4hjyfZS9m3-44VvnGBI2mi4iBM7KDrvG5ATKwU,1072
|
|
64
|
+
async_linkedinlib-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anandesh Sharma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|