async_linkedinlib 0.2.0__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.
- async_linkedinlib-0.2.0/.github/ISSUE_TEMPLATE/bug_report.md +22 -0
- async_linkedinlib-0.2.0/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- async_linkedinlib-0.2.0/.github/pull_request_template.md +10 -0
- async_linkedinlib-0.2.0/.github/workflows/ci.yml +33 -0
- async_linkedinlib-0.2.0/.github/workflows/release.yml +83 -0
- async_linkedinlib-0.2.0/.gitignore +20 -0
- async_linkedinlib-0.2.0/AGENTS.md +123 -0
- async_linkedinlib-0.2.0/CLAUDE.md +4 -0
- async_linkedinlib-0.2.0/LICENSE +21 -0
- async_linkedinlib-0.2.0/PKG-INFO +478 -0
- async_linkedinlib-0.2.0/README.md +457 -0
- async_linkedinlib-0.2.0/assets/banner.png +0 -0
- async_linkedinlib-0.2.0/examples/account.py +20 -0
- async_linkedinlib-0.2.0/examples/comments.py +42 -0
- async_linkedinlib-0.2.0/examples/feed.py +48 -0
- async_linkedinlib-0.2.0/examples/jobs.py +60 -0
- async_linkedinlib-0.2.0/examples/posts.py +42 -0
- async_linkedinlib-0.2.0/examples/search.py +59 -0
- async_linkedinlib-0.2.0/pyproject.toml +61 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/__init__.py +187 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/__main__.py +3 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/auth/__init__.py +5 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/auth/service.py +77 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/cli.py +37 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/client.py +93 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/config/__init__.py +6 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/config/options.py +15 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/config/profile.py +56 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/exceptions.py +75 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/__init__.py +155 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/_json.py +65 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/comments.py +145 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/content.py +207 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/feed.py +124 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/jobs.py +215 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/jobs_apply.py +424 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/member.py +14 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/pagination.py +52 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/posts.py +141 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/posts_results.py +120 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/search.py +307 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/search_filters.py +339 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/models/search_jobs.py +213 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/py.typed +0 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/__init__.py +12 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/account.py +35 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/comments/__init__.py +5 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/comments/resource.py +227 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/comments/urns.py +43 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/__init__.py +5 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/queries.py +57 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/resource.py +225 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/saved.py +49 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/urns.py +68 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/jobs/__init__.py +5 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/jobs/answers.py +239 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/jobs/resource.py +212 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/jobs/upload.py +78 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/mentions.py +58 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/paging.py +88 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/posts/__init__.py +6 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/posts/media.py +173 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/posts/payloads.py +98 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/posts/resource.py +254 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/reactions.py +89 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/search/__init__.py +5 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/search/queries.py +79 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/search/results.py +85 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/search/search.py +284 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/resources/threads.py +30 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/session/__init__.py +5 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/session/cookies.py +52 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/session/lease.py +30 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/session/state.py +129 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/transport/__init__.py +6 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/transport/http.py +183 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/transport/restli.py +46 -0
- async_linkedinlib-0.2.0/src/async_linkedinlib/transport/voyager.py +151 -0
- async_linkedinlib-0.2.0/tests/auth/test_google.py +145 -0
- async_linkedinlib-0.2.0/tests/comments/conftest.py +114 -0
- async_linkedinlib-0.2.0/tests/comments/test_models.py +55 -0
- async_linkedinlib-0.2.0/tests/comments/test_reads.py +213 -0
- async_linkedinlib-0.2.0/tests/comments/test_writes.py +209 -0
- async_linkedinlib-0.2.0/tests/conftest.py +20 -0
- async_linkedinlib-0.2.0/tests/feed/conftest.py +141 -0
- async_linkedinlib-0.2.0/tests/feed/test_models.py +143 -0
- async_linkedinlib-0.2.0/tests/feed/test_reads.py +219 -0
- async_linkedinlib-0.2.0/tests/feed/test_writes.py +179 -0
- async_linkedinlib-0.2.0/tests/integration/test_loopback.py +100 -0
- async_linkedinlib-0.2.0/tests/jobs/conftest.py +242 -0
- async_linkedinlib-0.2.0/tests/jobs/test_application.py +224 -0
- async_linkedinlib-0.2.0/tests/jobs/test_details.py +296 -0
- async_linkedinlib-0.2.0/tests/jobs/test_submission.py +248 -0
- async_linkedinlib-0.2.0/tests/posts/conftest.py +92 -0
- async_linkedinlib-0.2.0/tests/posts/test_attachments.py +200 -0
- async_linkedinlib-0.2.0/tests/posts/test_create.py +235 -0
- async_linkedinlib-0.2.0/tests/posts/test_manage.py +154 -0
- async_linkedinlib-0.2.0/tests/posts/test_media.py +153 -0
- async_linkedinlib-0.2.0/tests/posts/test_models.py +35 -0
- async_linkedinlib-0.2.0/tests/restli_decode.py +48 -0
- async_linkedinlib-0.2.0/tests/search/conftest.py +146 -0
- async_linkedinlib-0.2.0/tests/search/test_filters.py +206 -0
- async_linkedinlib-0.2.0/tests/search/test_jobs.py +234 -0
- async_linkedinlib-0.2.0/tests/search/test_typeahead.py +125 -0
- async_linkedinlib-0.2.0/tests/search/test_verticals.py +296 -0
- async_linkedinlib-0.2.0/tests/session/test_state.py +88 -0
- async_linkedinlib-0.2.0/tests/transport/test_http.py +158 -0
- async_linkedinlib-0.2.0/tests/voyager/test_paging.py +88 -0
- async_linkedinlib-0.2.0/tests/voyager/test_restli.py +39 -0
- async_linkedinlib-0.2.0/tests/voyager/test_voyager.py +271 -0
- async_linkedinlib-0.2.0/uv.lock +504 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Report a reproducible problem with the library
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
### What happened?
|
|
7
|
+
|
|
8
|
+
Describe the actual and expected behavior.
|
|
9
|
+
|
|
10
|
+
### Minimal reproduction
|
|
11
|
+
|
|
12
|
+
Use synthetic inputs. Remove credentials, session data and private account details.
|
|
13
|
+
|
|
14
|
+
### Environment
|
|
15
|
+
|
|
16
|
+
- Package version or commit:
|
|
17
|
+
- Python version:
|
|
18
|
+
- Operating system:
|
|
19
|
+
|
|
20
|
+
### Redacted error
|
|
21
|
+
|
|
22
|
+
Include the exception type and relevant traceback, without raw response bodies.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Describe a missing capability or improvement
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
### Use case
|
|
7
|
+
|
|
8
|
+
What are you trying to accomplish?
|
|
9
|
+
|
|
10
|
+
### Proposed behavior
|
|
11
|
+
|
|
12
|
+
Describe the public method or workflow you would like.
|
|
13
|
+
|
|
14
|
+
### Evidence and constraints
|
|
15
|
+
|
|
16
|
+
Link public documentation or describe observed behavior. Do not attach credentials,
|
|
17
|
+
private captures or account data.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## Change
|
|
2
|
+
|
|
3
|
+
Describe the problem and resulting behavior.
|
|
4
|
+
|
|
5
|
+
## Verification
|
|
6
|
+
|
|
7
|
+
List checks run and distinguish synthetic tests from live verification.
|
|
8
|
+
|
|
9
|
+
- [ ] Public behavior changes are documented in README.md.
|
|
10
|
+
- [ ] Tests and examples contain only synthetic data.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
workflow_call:
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
jobs:
|
|
13
|
+
check:
|
|
14
|
+
name: ${{ matrix.os }} / Python ${{ matrix.python }}
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
timeout-minutes: 15
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
os: [ubuntu-latest, macos-latest]
|
|
21
|
+
python: ['3.11', '3.13']
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
24
|
+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python }}
|
|
27
|
+
enable-cache: true
|
|
28
|
+
- run: uv sync --locked
|
|
29
|
+
- run: uv run ruff check src tests examples
|
|
30
|
+
- run: uv run ruff format --check src tests examples
|
|
31
|
+
- run: uv run mypy src
|
|
32
|
+
- run: uv run pytest -q
|
|
33
|
+
- run: uv build
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
permissions:
|
|
6
|
+
contents: read
|
|
7
|
+
concurrency:
|
|
8
|
+
group: pypi-${{ github.repository }}
|
|
9
|
+
cancel-in-progress: false
|
|
10
|
+
jobs:
|
|
11
|
+
checks:
|
|
12
|
+
uses: ./.github/workflows/ci.yml
|
|
13
|
+
build:
|
|
14
|
+
needs: checks
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 10
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.11'
|
|
24
|
+
- name: Validate release tag and source
|
|
25
|
+
env:
|
|
26
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
27
|
+
run: |
|
|
28
|
+
git merge-base --is-ancestor HEAD origin/main
|
|
29
|
+
uv run --no-project python - <<'PYTHON'
|
|
30
|
+
import os, tomllib
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
|
|
33
|
+
if os.environ["RELEASE_TAG"] != f"v{version}":
|
|
34
|
+
raise SystemExit(f"Release tag must be v{version}")
|
|
35
|
+
PYTHON
|
|
36
|
+
- run: uv build
|
|
37
|
+
- run: uvx twine check --strict dist/*
|
|
38
|
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
39
|
+
with:
|
|
40
|
+
name: distributions
|
|
41
|
+
path: dist/*
|
|
42
|
+
if-no-files-found: error
|
|
43
|
+
retention-days: 7
|
|
44
|
+
publish:
|
|
45
|
+
needs: build
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
timeout-minutes: 10
|
|
48
|
+
environment:
|
|
49
|
+
name: pypi
|
|
50
|
+
url: https://pypi.org/p/async_linkedinlib
|
|
51
|
+
permissions:
|
|
52
|
+
id-token: write
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
55
|
+
with:
|
|
56
|
+
name: distributions
|
|
57
|
+
path: dist/
|
|
58
|
+
- name: Verify any previously published files
|
|
59
|
+
run: |
|
|
60
|
+
python - <<'PYTHON'
|
|
61
|
+
import hashlib, json, urllib.error, urllib.request
|
|
62
|
+
from pathlib import Path
|
|
63
|
+
try:
|
|
64
|
+
with urllib.request.urlopen("https://pypi.org/pypi/async_linkedinlib/json") as response:
|
|
65
|
+
project = json.load(response)
|
|
66
|
+
except urllib.error.HTTPError as error:
|
|
67
|
+
if error.code != 404:
|
|
68
|
+
raise
|
|
69
|
+
project = {"releases": {}}
|
|
70
|
+
published = {
|
|
71
|
+
item["filename"]: item["digests"]["sha256"]
|
|
72
|
+
for release in project["releases"].values() for item in release
|
|
73
|
+
}
|
|
74
|
+
for artifact in Path("dist").iterdir():
|
|
75
|
+
expected = published.get(artifact.name)
|
|
76
|
+
actual = hashlib.sha256(artifact.read_bytes()).hexdigest()
|
|
77
|
+
if expected is not None and expected != actual:
|
|
78
|
+
raise SystemExit(f"Existing PyPI file differs: {artifact.name}")
|
|
79
|
+
PYTHON
|
|
80
|
+
- name: Publish distributions
|
|
81
|
+
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
|
|
82
|
+
with:
|
|
83
|
+
skip-existing: true
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
.pytest_cache/
|
|
4
|
+
.mypy_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
dist/
|
|
7
|
+
*.session.json
|
|
8
|
+
*.session.json.lock
|
|
9
|
+
.DS_Store
|
|
10
|
+
*.py[cod]
|
|
11
|
+
build/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
.env
|
|
14
|
+
.env.*
|
|
15
|
+
*.har
|
|
16
|
+
*.mitm
|
|
17
|
+
*.pem
|
|
18
|
+
*.key
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Rules for async_linkedinlib
|
|
2
|
+
|
|
3
|
+
Follow these standalone library rules.
|
|
4
|
+
Read this package's [README.md](README.md), the relevant source, and its tests before editing.
|
|
5
|
+
Keep this a standalone Python package; do not import the backend or `async_instalib`.
|
|
6
|
+
|
|
7
|
+
## Structure and ownership
|
|
8
|
+
|
|
9
|
+
- `client.py` owns `LinkedIn`, the shared session/transport, resource construction,
|
|
10
|
+
async lifecycle and session-file ownership. Add an explicit attribute for each
|
|
11
|
+
implemented resource; keep endpoint logic out of this class.
|
|
12
|
+
- `auth/` owns login orchestration, fresh login context and supported challenge
|
|
13
|
+
handling. It may reuse the account resource to verify a completed login.
|
|
14
|
+
- `session/` owns account/device state, scoped cookies, persistence and file locks.
|
|
15
|
+
It performs no HTTP requests. Do not store passwords or Google ID tokens here.
|
|
16
|
+
- `transport/` is the single network boundary. `http.py` owns the HTTPX pool,
|
|
17
|
+
authentication headers, cookie updates, timeouts, HTTP error translation and cooldowns.
|
|
18
|
+
`voyager.py` runs persisted GraphQL operations and rest.li calls over it, and `restli.py`
|
|
19
|
+
encodes variables exactly as the app's `DataEncoder` does.
|
|
20
|
+
- `resources/` owns feature operations and provider-response decoding. Every
|
|
21
|
+
resource receives the existing transport; never create a separate session or
|
|
22
|
+
HTTP client per feature.
|
|
23
|
+
- `models/` owns typed public results. Models perform no network or persistence work.
|
|
24
|
+
- `config/` owns caller options and versioned app metadata, separate from credentials.
|
|
25
|
+
- `exceptions.py` owns public errors; `cli.py` owns interactive commands and credential
|
|
26
|
+
input. Put runnable usage examples in `examples/`.
|
|
27
|
+
|
|
28
|
+
Group methods around a user's feature: account, feed, posts, comments, search, jobs, and
|
|
29
|
+
later connections and messages. Shared by several features: `resources/reactions.py`
|
|
30
|
+
(react and list reactions), `resources/paging.py` (every list's pages and cursors; follow the
|
|
31
|
+
app's pager, never write another), `resources/threads.py` (thread URNs) and
|
|
32
|
+
`resources/mentions.py` (text mentions). `models/content.py` holds the shared `Post`, `Author` and
|
|
33
|
+
`SocialCounts`; `models/pagination.py` holds `Page` and `Cursor`.
|
|
34
|
+
A small feature can start as `resources/<feature>.py`. Split a substantial feature
|
|
35
|
+
into `resources/<feature>/` when its real responsibilities justify that split.
|
|
36
|
+
Matching result models belong in `models/<feature>.py`; tests belong under that
|
|
37
|
+
feature in `tests/`. Do not pre-create empty resources, generic CRUD methods, plugin
|
|
38
|
+
registries, base resource classes or speculative interfaces.
|
|
39
|
+
|
|
40
|
+
Use cohesive classes and explicit dependencies. Keep public imports stable:
|
|
41
|
+
`from async_linkedinlib import LinkedIn, Session, Member` must continue working.
|
|
42
|
+
Keep `__init__.py` files declarative, with `__all__` and a one-line purpose comment
|
|
43
|
+
on each re-export. Annotate public APIs, use familiar names, follow Ruff's 100-column
|
|
44
|
+
format and a 500-line source file limit.
|
|
45
|
+
|
|
46
|
+
## Adding a LinkedIn feature
|
|
47
|
+
|
|
48
|
+
1. Establish the operation from a captured exchange or from the shipped app's own request
|
|
49
|
+
code (decompiled Voyager GraphQL clients, routes and builders). App 4.1.1267 draws these
|
|
50
|
+
screens with server-driven protobuf (SDUI); the SDK does not parse SDUI UI trees and uses
|
|
51
|
+
the Voyager operations the same APK ships, over JSON. Take variable names, enums and
|
|
52
|
+
bodies from app code: the server silently ignores unknown variables. Record the app
|
|
53
|
+
version and evidence in the README. Distinguish observed behavior, locally tested
|
|
54
|
+
behavior and live SDK verification.
|
|
55
|
+
2. Define the smallest useful public method, typed result and expected failures.
|
|
56
|
+
Keep IDs as strings. Preserve unknown versus absent fields rather than guessing.
|
|
57
|
+
3. Implement request construction and decoding inside the feature, using the shared
|
|
58
|
+
transport. Do not spread provider payloads and URL construction through `client.py`.
|
|
59
|
+
4. Test the request contract and meaningful failures with synthetic data. Use a real
|
|
60
|
+
loopback server when serialization, cookies, streaming or lifecycle matters.
|
|
61
|
+
5. Update the README's feature coverage, usage and limits. Add root exports only for
|
|
62
|
+
supported public types. An observed endpoint does not establish every capability
|
|
63
|
+
or variation of that feature.
|
|
64
|
+
|
|
65
|
+
Intended future groups are `connections` and `messages`. These names describe extension
|
|
66
|
+
boundaries, not implemented functionality. Implement only the feature requested.
|
|
67
|
+
|
|
68
|
+
## Protocol and account state
|
|
69
|
+
|
|
70
|
+
- `li_at` is the primary session credential; preserve `JSESSIONID` and its CSRF role.
|
|
71
|
+
Retain cookie domain, path, security and expiry when available. Never flatten the
|
|
72
|
+
entire jar into a global credential header or send it to arbitrary hosts.
|
|
73
|
+
- Each client owns one account and one connection pool. New features must use the
|
|
74
|
+
same cookie rotation and cooldown behavior. Preserve saved-session compatibility;
|
|
75
|
+
version persisted schema changes explicitly.
|
|
76
|
+
- Google sign-in currently means exchanging a caller-supplied fresh ID token for
|
|
77
|
+
LinkedIn's Android flow. Do not imply that the SDK acquires Google tokens or accepts
|
|
78
|
+
tokens issued for unrelated OAuth clients. Do not replay a captured login token.
|
|
79
|
+
- Do not invent endpoints, GraphQL IDs, token refresh, OTP, password login, challenge
|
|
80
|
+
continuation or anti-abuse telemetry. Unsupported server flows fail explicitly.
|
|
81
|
+
- Keep pagination explicit. Preserve opaque server cursors and their account/query
|
|
82
|
+
context (`Cursor` carries the operation, query scope and owning session); do not
|
|
83
|
+
silently fetch every page or invent continuation values.
|
|
84
|
+
- A write whose 2xx reply cannot be read raises `UnconfirmedWrite`, never a plain
|
|
85
|
+
`ProtocolError`, so callers know the change may exist.
|
|
86
|
+
- Reads must not implicitly send messages, mark conversations read, follow, connect,
|
|
87
|
+
apply for jobs, publish, delete or acknowledge content. Expose those actions as
|
|
88
|
+
separate, clearly named operations when supported by evidence.
|
|
89
|
+
- Never silently retry a write or credential exchange. A timeout can occur after the
|
|
90
|
+
server accepted it. Do not assume an idempotency mechanism exists.
|
|
91
|
+
- Keep TLS verification enabled, redirects explicit, and destination hosts constrained
|
|
92
|
+
to the documented operation. Uploads observed so far go to www.linkedin.com; validate an
|
|
93
|
+
upload URL's host before sending. Add another host only when its flow requires it, with
|
|
94
|
+
credentials scoped to that host.
|
|
95
|
+
- Accept GraphQL replies with errors only through `Voyager.query(...,
|
|
96
|
+
ignore_null_field_errors=True)`, and only for finders known to report harmless nulls.
|
|
97
|
+
|
|
98
|
+
## Credentials and verification
|
|
99
|
+
|
|
100
|
+
Keep raw captures, cookies, passwords, ID tokens and real account data outside Git.
|
|
101
|
+
Use hidden prompts or caller-provided secret inputs, never credential arguments.
|
|
102
|
+
Session files remain atomic and mode 0600; preserve exclusive file ownership.
|
|
103
|
+
Logs, exception messages and object reprs must not expose secrets or raw responses.
|
|
104
|
+
Challenge URLs can carry private state and must not appear in ordinary error text.
|
|
105
|
+
|
|
106
|
+
Use synthetic credentials in tests. Live reads must use the user's authorized
|
|
107
|
+
session. Live writes require an authorized action and target; preserve that
|
|
108
|
+
authorization across turns rather than asking repeatedly. Delete test-created remote data, verify cleanup, and report any effects that cannot be undone.
|
|
109
|
+
|
|
110
|
+
Run checks from this directory:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
uv run pytest -q
|
|
114
|
+
uv run ruff check src tests examples
|
|
115
|
+
uv run ruff format --check src tests examples
|
|
116
|
+
uv run mypy src
|
|
117
|
+
uv build
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Use focused tests while developing, then the full package suite for changes to
|
|
121
|
+
auth, transport, sessions or public imports. Do not contact LinkedIn in the normal
|
|
122
|
+
test suite. Report live and simulated verification separately. Keep lasting
|
|
123
|
+
documentation in this README and these rules; plans belong in `tasks/`. Do not create a separate documentation site.
|
|
@@ -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.
|