longwei 1.0.0.dev0__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.
- longwei-1.0.0.dev0/PKG-INFO +121 -0
- longwei-1.0.0.dev0/README.md +94 -0
- longwei-1.0.0.dev0/pyproject.toml +84 -0
- longwei-1.0.0.dev0/src/longwei/__init__.py +74 -0
- longwei-1.0.0.dev0/src/longwei/_mixin_auth.py +218 -0
- longwei-1.0.0.dev0/src/longwei/_mixin_credentials.py +103 -0
- longwei-1.0.0.dev0/src/longwei/_mixin_request_helpers.py +115 -0
- longwei-1.0.0.dev0/src/longwei/_mixin_statuses.py +386 -0
- longwei-1.0.0.dev0/src/longwei/_mixin_timelines.py +265 -0
- longwei-1.0.0.dev0/src/longwei/client_2_server.py +117 -0
- longwei-1.0.0.dev0/src/longwei/constants.py +36 -0
- longwei-1.0.0.dev0/src/longwei/exceptions.py +176 -0
- longwei-1.0.0.dev0/src/longwei/models.py +296 -0
- longwei-1.0.0.dev0/src/longwei/types.py +9 -0
- longwei-1.0.0.dev0/src/longwei/utils.py +139 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: longwei
|
|
3
|
+
Version: 1.0.0.dev0
|
|
4
|
+
Summary: Python client library for ActivityPub-compatible servers
|
|
5
|
+
Author: marvin8
|
|
6
|
+
Author-email: marvin8 <marvin8@tuta.io>
|
|
7
|
+
License: AGPL-3.0-or-later
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Dist: httpx[http2,zstd]~=0.28.1
|
|
18
|
+
Requires-Dist: pydantic~=2.12.5
|
|
19
|
+
Requires-Dist: pytz~=2026.1.post1
|
|
20
|
+
Requires-Dist: whenever~=0.9.5
|
|
21
|
+
Requires-Python: >=3.11, <3.15
|
|
22
|
+
Project-URL: Changelog, https://codeberg.org/MarvinsMastodonTools/longwei/src/branch/main/CHANGELOG.md
|
|
23
|
+
Project-URL: Documentation, https://marvinsmastodontools.codeberg.page/longwei/
|
|
24
|
+
Project-URL: Issues, https://codeberg.org/MarvinsMastodonTools/longwei/issues
|
|
25
|
+
Project-URL: Source, https://codeberg.org/MarvinsMastodonTools/longwei
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# longwei
|
|
29
|
+
|
|
30
|
+
[](https://codeberg.org/MarvinsMastodonTools/longwei "Repo at Codeberg.org")
|
|
31
|
+
[](https://ci.codeberg.org/MarvinsMastodonTools/longwei "CI / Woodpecker")
|
|
32
|
+
[](https://pepy.tech/project/longwei "Download count")
|
|
33
|
+
[](https://docs.astral.sh/uv/guides/audit/ "Checked with uv-secure")
|
|
34
|
+
[](https://github.com/gitleaks/gitleaks "Checked with gitleaks")
|
|
35
|
+
[](https://github.com/astral-sh/pysentry "Checked with pysentry")
|
|
36
|
+
[](https://github.com/rohaquinlop/complexipy "Checked with complexipy")
|
|
37
|
+
[](https://github.com/astral-sh/ruff "Code style: ruff")
|
|
38
|
+
[](https://pypi.org/project/longwei "PyPI – Python Version")
|
|
39
|
+
[](https://pypi.org/project/longwei "PyPI – Wheel")
|
|
40
|
+
[](https://spdx.org/licenses/AGPL-3.0-or-later.html "AGPL 3 or later")
|
|
41
|
+
|
|
42
|
+
Longwei is a minimal Python implementation of the ActivityPub REST API used by [Mastodon](https://joinmastodon.org/), [Pleroma](https://pleroma.social/), [GotoSocial](https://gotosocial.org/), and [Takahe](https://jointakahe.org/). This implementation makes use of asyncio where appropriate. It is intended to be used as a library by other applications. No standalone functionality is provided.
|
|
43
|
+
|
|
44
|
+
So far Longwei only implements the activtiy pub API calls I need for my other projects [Fedinesia](https://codeberg.org/MarvinsMastodonTools/fedinesia), [Feed2Fedi](https://codeberg.org/marvinsmastodontools/feed2fedi), [FenLiu and Zhongli](https://codeberg.org/marvinsmastodontools/dujiangyan).
|
|
45
|
+
|
|
46
|
+
**DO NOT** expect a full or complete implementation of all [ActivityPub API](https://activitypub.rocks/) functionality.
|
|
47
|
+
|
|
48
|
+
## API References
|
|
49
|
+
|
|
50
|
+
- [Mastodon API](https://docs.joinmastodon.org/api/)
|
|
51
|
+
- [Pleroma API](https://api.pleroma.social)
|
|
52
|
+
- [GotoSocial API](https://docs.gotosocial.org/en/stable/api/swagger/)
|
|
53
|
+
|
|
54
|
+
## Heavy Development / Breaking Changes
|
|
55
|
+
|
|
56
|
+
I have only just forked this from minimal_activitypub and am still working on it. Expect some major changes still and some breaking changes.
|
|
57
|
+
It is **not** a drop in replacement and will need some adjustments, even when I am done with the rework.
|
|
58
|
+
|
|
59
|
+
I advise not to use this at until at least version 1.0.0 (non dev)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## Contributing
|
|
63
|
+
|
|
64
|
+
Issues and pull requests are welcome.
|
|
65
|
+
|
|
66
|
+
longwei is using [pre-commit](https://pre-commit.com/) for code quality checks and [uv](https://docs.astral.sh/uv/) for dependency management. Please install and use both pre-commit and uv if you'd like to contribute.
|
|
67
|
+
|
|
68
|
+
## Documentation
|
|
69
|
+
|
|
70
|
+
The documentation is built using [MkDocs](https://www.mkdocs.org/) with the [Material theme](https://squidfunk.github.io/mkdocs-material/).
|
|
71
|
+
|
|
72
|
+
### Building Documentation Locally
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Install documentation dependencies (using the docs group from pyproject.toml)
|
|
76
|
+
uv sync --group docs
|
|
77
|
+
|
|
78
|
+
# Build documentation
|
|
79
|
+
mkdocs build
|
|
80
|
+
|
|
81
|
+
# Serve documentation locally
|
|
82
|
+
mkdocs serve
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Documentation Structure
|
|
86
|
+
|
|
87
|
+
- `docs/` - Markdown source files
|
|
88
|
+
- `mkdocs.yml` - MkDocs configuration
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
This project uses [uv](https://docs.astral.sh/uv/) for dependency management and virtual environments. To set up the development environment:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Create and activate virtual environment
|
|
96
|
+
uv venv
|
|
97
|
+
source .venv/bin/activate
|
|
98
|
+
|
|
99
|
+
# Install all dependencies including development and documentation groups
|
|
100
|
+
uv sync --all-groups
|
|
101
|
+
|
|
102
|
+
# Run tests
|
|
103
|
+
uv run nox
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For more details on the development workflow, check the `noxfile.py` and `.woodpecker/` configuration files.
|
|
107
|
+
|
|
108
|
+
## Licensing
|
|
109
|
+
|
|
110
|
+
longwei is licenced with the [GNU Affero General Public License v3.0](http://www.gnu.org/licenses/agpl-3.0.html)
|
|
111
|
+
|
|
112
|
+
## Supporting longwei
|
|
113
|
+
|
|
114
|
+
There are a number of ways you can support longwei:
|
|
115
|
+
|
|
116
|
+
- Create an issue with problems or ideas you have with/for longwei
|
|
117
|
+
- You can [buy me a coffee](https://www.buymeacoffee.com/marvin8).
|
|
118
|
+
- You can send me small change in Monero to the address below:
|
|
119
|
+
|
|
120
|
+
### Monero donation address
|
|
121
|
+
`8ADQkCya3orL178dADn4bnKuF1JuVGEG97HPRgmXgmZ2cZFSkWU9M2v7BssEGeTRNN2V5p6bSyHa83nrdu1XffDX3cnjKVu`
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# longwei
|
|
2
|
+
|
|
3
|
+
[](https://codeberg.org/MarvinsMastodonTools/longwei "Repo at Codeberg.org")
|
|
4
|
+
[](https://ci.codeberg.org/MarvinsMastodonTools/longwei "CI / Woodpecker")
|
|
5
|
+
[](https://pepy.tech/project/longwei "Download count")
|
|
6
|
+
[](https://docs.astral.sh/uv/guides/audit/ "Checked with uv-secure")
|
|
7
|
+
[](https://github.com/gitleaks/gitleaks "Checked with gitleaks")
|
|
8
|
+
[](https://github.com/astral-sh/pysentry "Checked with pysentry")
|
|
9
|
+
[](https://github.com/rohaquinlop/complexipy "Checked with complexipy")
|
|
10
|
+
[](https://github.com/astral-sh/ruff "Code style: ruff")
|
|
11
|
+
[](https://pypi.org/project/longwei "PyPI – Python Version")
|
|
12
|
+
[](https://pypi.org/project/longwei "PyPI – Wheel")
|
|
13
|
+
[](https://spdx.org/licenses/AGPL-3.0-or-later.html "AGPL 3 or later")
|
|
14
|
+
|
|
15
|
+
Longwei is a minimal Python implementation of the ActivityPub REST API used by [Mastodon](https://joinmastodon.org/), [Pleroma](https://pleroma.social/), [GotoSocial](https://gotosocial.org/), and [Takahe](https://jointakahe.org/). This implementation makes use of asyncio where appropriate. It is intended to be used as a library by other applications. No standalone functionality is provided.
|
|
16
|
+
|
|
17
|
+
So far Longwei only implements the activtiy pub API calls I need for my other projects [Fedinesia](https://codeberg.org/MarvinsMastodonTools/fedinesia), [Feed2Fedi](https://codeberg.org/marvinsmastodontools/feed2fedi), [FenLiu and Zhongli](https://codeberg.org/marvinsmastodontools/dujiangyan).
|
|
18
|
+
|
|
19
|
+
**DO NOT** expect a full or complete implementation of all [ActivityPub API](https://activitypub.rocks/) functionality.
|
|
20
|
+
|
|
21
|
+
## API References
|
|
22
|
+
|
|
23
|
+
- [Mastodon API](https://docs.joinmastodon.org/api/)
|
|
24
|
+
- [Pleroma API](https://api.pleroma.social)
|
|
25
|
+
- [GotoSocial API](https://docs.gotosocial.org/en/stable/api/swagger/)
|
|
26
|
+
|
|
27
|
+
## Heavy Development / Breaking Changes
|
|
28
|
+
|
|
29
|
+
I have only just forked this from minimal_activitypub and am still working on it. Expect some major changes still and some breaking changes.
|
|
30
|
+
It is **not** a drop in replacement and will need some adjustments, even when I am done with the rework.
|
|
31
|
+
|
|
32
|
+
I advise not to use this at until at least version 1.0.0 (non dev)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Contributing
|
|
36
|
+
|
|
37
|
+
Issues and pull requests are welcome.
|
|
38
|
+
|
|
39
|
+
longwei is using [pre-commit](https://pre-commit.com/) for code quality checks and [uv](https://docs.astral.sh/uv/) for dependency management. Please install and use both pre-commit and uv if you'd like to contribute.
|
|
40
|
+
|
|
41
|
+
## Documentation
|
|
42
|
+
|
|
43
|
+
The documentation is built using [MkDocs](https://www.mkdocs.org/) with the [Material theme](https://squidfunk.github.io/mkdocs-material/).
|
|
44
|
+
|
|
45
|
+
### Building Documentation Locally
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Install documentation dependencies (using the docs group from pyproject.toml)
|
|
49
|
+
uv sync --group docs
|
|
50
|
+
|
|
51
|
+
# Build documentation
|
|
52
|
+
mkdocs build
|
|
53
|
+
|
|
54
|
+
# Serve documentation locally
|
|
55
|
+
mkdocs serve
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Documentation Structure
|
|
59
|
+
|
|
60
|
+
- `docs/` - Markdown source files
|
|
61
|
+
- `mkdocs.yml` - MkDocs configuration
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
This project uses [uv](https://docs.astral.sh/uv/) for dependency management and virtual environments. To set up the development environment:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Create and activate virtual environment
|
|
69
|
+
uv venv
|
|
70
|
+
source .venv/bin/activate
|
|
71
|
+
|
|
72
|
+
# Install all dependencies including development and documentation groups
|
|
73
|
+
uv sync --all-groups
|
|
74
|
+
|
|
75
|
+
# Run tests
|
|
76
|
+
uv run nox
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For more details on the development workflow, check the `noxfile.py` and `.woodpecker/` configuration files.
|
|
80
|
+
|
|
81
|
+
## Licensing
|
|
82
|
+
|
|
83
|
+
longwei is licenced with the [GNU Affero General Public License v3.0](http://www.gnu.org/licenses/agpl-3.0.html)
|
|
84
|
+
|
|
85
|
+
## Supporting longwei
|
|
86
|
+
|
|
87
|
+
There are a number of ways you can support longwei:
|
|
88
|
+
|
|
89
|
+
- Create an issue with problems or ideas you have with/for longwei
|
|
90
|
+
- You can [buy me a coffee](https://www.buymeacoffee.com/marvin8).
|
|
91
|
+
- You can send me small change in Monero to the address below:
|
|
92
|
+
|
|
93
|
+
### Monero donation address
|
|
94
|
+
`8ADQkCya3orL178dADn4bnKuF1JuVGEG97HPRgmXgmZ2cZFSkWU9M2v7BssEGeTRNN2V5p6bSyHa83nrdu1XffDX3cnjKVu`
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.8.7,<0.9.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "longwei"
|
|
7
|
+
version = "1.0.0dev0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "marvin8", email = "marvin8@tuta.io" },
|
|
10
|
+
]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 5 - Production/Stable",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Programming Language :: Python :: 3.14",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
21
|
+
]
|
|
22
|
+
description = "Python client library for ActivityPub-compatible servers"
|
|
23
|
+
readme = "README.md"
|
|
24
|
+
license = { text = "AGPL-3.0-or-later" }
|
|
25
|
+
requires-python = ">=3.11, <3.15"
|
|
26
|
+
dependencies = [
|
|
27
|
+
"httpx[http2,zstd]~=0.28.1",
|
|
28
|
+
"pydantic~=2.12.5",
|
|
29
|
+
"pytz~=2026.1.post1",
|
|
30
|
+
"whenever~=0.9.5",
|
|
31
|
+
]
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"bump-my-version~=1.3.0",
|
|
35
|
+
"complexipy~=5.2.0",
|
|
36
|
+
"git-cliff~=2.12.0",
|
|
37
|
+
"prek~=0.3.8",
|
|
38
|
+
"ruff~=0.15.8",
|
|
39
|
+
"ty~=0.0.27",
|
|
40
|
+
"uv~=0.11.2",
|
|
41
|
+
]
|
|
42
|
+
docs = [
|
|
43
|
+
"mike~=2.1.4",
|
|
44
|
+
"mkdocs~=1.6.1",
|
|
45
|
+
"mkdocs-material~=9.7.6",
|
|
46
|
+
"mkdocstrings-python~=2.0.3",
|
|
47
|
+
]
|
|
48
|
+
nox = [
|
|
49
|
+
"nox-uv~=0.7.1",
|
|
50
|
+
]
|
|
51
|
+
pytest = [
|
|
52
|
+
"pytest~=9.0.2",
|
|
53
|
+
"pytest-asyncio~=1.3.0",
|
|
54
|
+
"pytest-cov~=7.1.0",
|
|
55
|
+
"pytest-httpx~=0.36.0",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.uv]
|
|
59
|
+
constraint-dependencies = [
|
|
60
|
+
"requests>=2.33.0", # Addresses vulnerability GHSA-gc5v-m9x4-r6x2
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[project.urls]
|
|
64
|
+
Documentation = "https://marvinsmastodontools.codeberg.page/longwei/"
|
|
65
|
+
Issues = "https://codeberg.org/MarvinsMastodonTools/longwei/issues"
|
|
66
|
+
Source = "https://codeberg.org/MarvinsMastodonTools/longwei"
|
|
67
|
+
Changelog = "https://codeberg.org/MarvinsMastodonTools/longwei/src/branch/main/CHANGELOG.md"
|
|
68
|
+
|
|
69
|
+
[tool.bumpversion]
|
|
70
|
+
commit = true
|
|
71
|
+
tag = true
|
|
72
|
+
tag_name = "{new_version}"
|
|
73
|
+
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
74
|
+
serialize = ["{major}.{minor}.{patch}"]
|
|
75
|
+
message = "bump: version {current_version} → {new_version}"
|
|
76
|
+
pre_commit_hooks = ["uv sync", "git add uv.lock"]
|
|
77
|
+
|
|
78
|
+
[[tool.bumpversion.files]]
|
|
79
|
+
filename = "pyproject.toml"
|
|
80
|
+
search = 'version = "{current_version}"'
|
|
81
|
+
replace = 'version = "{new_version}"'
|
|
82
|
+
|
|
83
|
+
[tool.pytest.ini_options]
|
|
84
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Package 'longwei' level definitions."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from importlib.metadata import version
|
|
6
|
+
from typing import Final
|
|
7
|
+
|
|
8
|
+
from longwei.constants import INSTANCE_TYPE_MASTODON
|
|
9
|
+
from longwei.constants import INSTANCE_TYPE_PLEROMA
|
|
10
|
+
from longwei.constants import INSTANCE_TYPE_TAKAHE
|
|
11
|
+
from longwei.exceptions import ActivityPubError
|
|
12
|
+
from longwei.exceptions import ApiError
|
|
13
|
+
from longwei.exceptions import ClientError
|
|
14
|
+
from longwei.exceptions import ConflictError
|
|
15
|
+
from longwei.exceptions import ForbiddenError
|
|
16
|
+
from longwei.exceptions import GoneError
|
|
17
|
+
from longwei.exceptions import NetworkError
|
|
18
|
+
from longwei.exceptions import NotFoundError
|
|
19
|
+
from longwei.exceptions import RatelimitError
|
|
20
|
+
from longwei.exceptions import ServerError
|
|
21
|
+
from longwei.exceptions import UnauthorizedError
|
|
22
|
+
from longwei.exceptions import UnprocessedError
|
|
23
|
+
from longwei.models import Account
|
|
24
|
+
from longwei.types import ActivityPubClass
|
|
25
|
+
from longwei.types import Status
|
|
26
|
+
|
|
27
|
+
__display_name__: Final[str] = "Longwei"
|
|
28
|
+
__version__: Final[str] = version(str(__package__))
|
|
29
|
+
|
|
30
|
+
USER_AGENT: Final[str] = f"{__display_name__}_v{__version__}_Python_{sys.version.split()[0]}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Visibility(str, Enum):
|
|
34
|
+
"""Enumerating possible Visibility values for statuses."""
|
|
35
|
+
|
|
36
|
+
PUBLIC = "public"
|
|
37
|
+
UNLISTED = "unlisted"
|
|
38
|
+
PRIVATE = "private"
|
|
39
|
+
DIRECT = "direct"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SearchType(str, Enum):
|
|
43
|
+
"""Enumerating possible type values for status searches."""
|
|
44
|
+
|
|
45
|
+
ACCOUNTS = "accounts"
|
|
46
|
+
HASHTAGS = "hashtags"
|
|
47
|
+
STATUSES = "statuses"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = [ # noqa: RUF022
|
|
51
|
+
"Account",
|
|
52
|
+
"ActivityPubClass",
|
|
53
|
+
"ActivityPubError",
|
|
54
|
+
"ApiError",
|
|
55
|
+
"ClientError",
|
|
56
|
+
"ConflictError",
|
|
57
|
+
"__display_name__",
|
|
58
|
+
"ForbiddenError",
|
|
59
|
+
"GoneError",
|
|
60
|
+
"INSTANCE_TYPE_MASTODON",
|
|
61
|
+
"INSTANCE_TYPE_PLEROMA",
|
|
62
|
+
"INSTANCE_TYPE_TAKAHE",
|
|
63
|
+
"NetworkError",
|
|
64
|
+
"NotFoundError",
|
|
65
|
+
"RatelimitError",
|
|
66
|
+
"SearchType",
|
|
67
|
+
"ServerError",
|
|
68
|
+
"Status",
|
|
69
|
+
"UnauthorizedError",
|
|
70
|
+
"UnprocessedError",
|
|
71
|
+
"USER_AGENT",
|
|
72
|
+
"__version__",
|
|
73
|
+
"Visibility",
|
|
74
|
+
]
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Auth mixin: OAuth app creation, token acquisition, authorization URL generation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from urllib.parse import urlencode
|
|
6
|
+
|
|
7
|
+
from httpx import AsyncClient
|
|
8
|
+
from httpx import HTTPError
|
|
9
|
+
|
|
10
|
+
from longwei import USER_AGENT
|
|
11
|
+
from longwei import __display_name__
|
|
12
|
+
from longwei.constants import REDIRECT_URI
|
|
13
|
+
from longwei.exceptions import NetworkError
|
|
14
|
+
from longwei.utils import _check_exception
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__display_name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _AuthMixin:
|
|
20
|
+
"""Mixin providing static OAuth/authentication methods."""
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
async def generate_authorization_url(
|
|
24
|
+
instance_url: str,
|
|
25
|
+
client_id: str,
|
|
26
|
+
user_agent: str = USER_AGENT,
|
|
27
|
+
) -> str:
|
|
28
|
+
"""Create URL to get access token interactively from website.
|
|
29
|
+
|
|
30
|
+
:param instance_url: The URL of the Mastodon instance you want to connect to
|
|
31
|
+
:param client_id: Client id of app as generated by create_app method
|
|
32
|
+
:param user_agent: User agent identifier to use. Defaults to longwei related one.
|
|
33
|
+
|
|
34
|
+
:returns: String containing URL to visit to get access token interactively from instance.
|
|
35
|
+
"""
|
|
36
|
+
logger.debug(
|
|
37
|
+
"ActivityPub.get_auth_token_interactive(instance_url=%s, client=...,client_id=%s, user_agent=%s)",
|
|
38
|
+
instance_url,
|
|
39
|
+
client_id,
|
|
40
|
+
user_agent,
|
|
41
|
+
)
|
|
42
|
+
if "http" not in instance_url:
|
|
43
|
+
instance_url = f"https://{instance_url}"
|
|
44
|
+
|
|
45
|
+
url_params = urlencode(
|
|
46
|
+
{
|
|
47
|
+
"response_type": "code",
|
|
48
|
+
"client_id": client_id,
|
|
49
|
+
"redirect_uri": REDIRECT_URI,
|
|
50
|
+
"scope": "read write",
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
auth_url = f"{instance_url}/oauth/authorize?{url_params}"
|
|
54
|
+
logger.debug(
|
|
55
|
+
"ActivityPub.get_auth_token_interactive(...) -> %s",
|
|
56
|
+
auth_url,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return auth_url
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
async def validate_authorization_code(
|
|
63
|
+
client: AsyncClient,
|
|
64
|
+
instance_url: str,
|
|
65
|
+
authorization_code: str,
|
|
66
|
+
client_id: str,
|
|
67
|
+
client_secret: str,
|
|
68
|
+
) -> str:
|
|
69
|
+
"""Validate an authorization code and get access token needed for API access.
|
|
70
|
+
|
|
71
|
+
:param client: httpx.AsyncClient
|
|
72
|
+
:param instance_url: The URL of the Mastodon instance you want to connect to
|
|
73
|
+
:param authorization_code: authorization code
|
|
74
|
+
:param client_id: client id as returned by create_app method
|
|
75
|
+
:param client_secret: client secret as returned by create_app method
|
|
76
|
+
|
|
77
|
+
:returns: access token
|
|
78
|
+
"""
|
|
79
|
+
logger.debug(
|
|
80
|
+
"ActivityPub.validate_authorization_code(authorization_code=%s, client_id=%s, client_secret=<redacted>)",
|
|
81
|
+
authorization_code,
|
|
82
|
+
client_id,
|
|
83
|
+
)
|
|
84
|
+
if "http" not in instance_url:
|
|
85
|
+
instance_url = f"https://{instance_url}"
|
|
86
|
+
|
|
87
|
+
data = {
|
|
88
|
+
"client_id": client_id,
|
|
89
|
+
"client_secret": client_secret,
|
|
90
|
+
"scope": "read write",
|
|
91
|
+
"redirect_uri": REDIRECT_URI,
|
|
92
|
+
"grant_type": "authorization_code",
|
|
93
|
+
"code": authorization_code,
|
|
94
|
+
}
|
|
95
|
+
try:
|
|
96
|
+
response = await client.post(url=f"{instance_url}/oauth/token", data=data)
|
|
97
|
+
logger.debug(
|
|
98
|
+
"ActivityPub.validate_authorization_code - response:\n%s",
|
|
99
|
+
response,
|
|
100
|
+
)
|
|
101
|
+
await _check_exception(response)
|
|
102
|
+
response_dict = response.json()
|
|
103
|
+
except HTTPError as error:
|
|
104
|
+
raise NetworkError from error
|
|
105
|
+
|
|
106
|
+
logger.debug(
|
|
107
|
+
"ActivityPub.validate_authorization_code - response.json: \n%s",
|
|
108
|
+
json.dumps(response_dict, indent=4),
|
|
109
|
+
)
|
|
110
|
+
return str(response_dict["access_token"])
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
async def get_auth_token( # noqa: PLR0913 - No way around needing all this parameters
|
|
114
|
+
instance_url: str,
|
|
115
|
+
username: str,
|
|
116
|
+
password: str,
|
|
117
|
+
client: AsyncClient,
|
|
118
|
+
user_agent: str = USER_AGENT,
|
|
119
|
+
client_website: str = "https://pypi.org/project/longwei/",
|
|
120
|
+
) -> str:
|
|
121
|
+
"""Create an app and use it to get an access token.
|
|
122
|
+
|
|
123
|
+
:param instance_url: The URL of the Mastodon instance you want to connect to
|
|
124
|
+
:param username: The username of the account you want to get an auth_token for
|
|
125
|
+
:param password: The password of the account you want to get an auth_token for
|
|
126
|
+
:param client: httpx.AsyncClient
|
|
127
|
+
:param user_agent: User agent identifier to use. Defaults to longwei related one.
|
|
128
|
+
:param client_website: Link to site for user_agent. Defaults to link to longwei on Pypi.org
|
|
129
|
+
|
|
130
|
+
:returns: The access token is being returned.
|
|
131
|
+
"""
|
|
132
|
+
logger.debug(
|
|
133
|
+
"ActivityPub.get_auth_token(instance_url=%s, username=%s, password=<redacted>, client=..., "
|
|
134
|
+
"user_agent=%s, client_website=%s)",
|
|
135
|
+
instance_url,
|
|
136
|
+
username,
|
|
137
|
+
user_agent,
|
|
138
|
+
client_website,
|
|
139
|
+
)
|
|
140
|
+
if "http" not in instance_url:
|
|
141
|
+
instance_url = f"https://{instance_url}"
|
|
142
|
+
|
|
143
|
+
client_id, client_secret = await _AuthMixin.create_app(
|
|
144
|
+
client_website=client_website,
|
|
145
|
+
instance_url=instance_url,
|
|
146
|
+
client=client,
|
|
147
|
+
user_agent=user_agent,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
data = {
|
|
151
|
+
"client_id": client_id,
|
|
152
|
+
"client_secret": client_secret,
|
|
153
|
+
"scope": "read write",
|
|
154
|
+
"redirect_uris": REDIRECT_URI,
|
|
155
|
+
"grant_type": "password",
|
|
156
|
+
"username": username,
|
|
157
|
+
"password": password,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
response = await client.post(url=f"{instance_url}/oauth/token", data=data)
|
|
162
|
+
logger.debug("ActivityPub.get_auth_token - response:\n%s", response)
|
|
163
|
+
await _check_exception(response)
|
|
164
|
+
response_dict = response.json()
|
|
165
|
+
except HTTPError as error:
|
|
166
|
+
raise NetworkError from error
|
|
167
|
+
|
|
168
|
+
logger.debug(
|
|
169
|
+
"ActivityPub.get_auth_token - response.json: \n%s",
|
|
170
|
+
json.dumps(response_dict, indent=4),
|
|
171
|
+
)
|
|
172
|
+
return str(response_dict["access_token"])
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
async def create_app(
|
|
176
|
+
instance_url: str,
|
|
177
|
+
client: AsyncClient,
|
|
178
|
+
user_agent: str = USER_AGENT,
|
|
179
|
+
client_website: str = "https://pypi.org/project/longwei/",
|
|
180
|
+
) -> tuple[str, str]:
|
|
181
|
+
"""Create an app.
|
|
182
|
+
|
|
183
|
+
:param instance_url: The URL of the Mastodon instance you want to connect to
|
|
184
|
+
:param client: httpx.AsyncClient
|
|
185
|
+
:param user_agent: User agent identifier to use. Defaults to longwei related one.
|
|
186
|
+
:param client_website: Link to site for user_agent. Defaults to link to longwei on Pypi.org
|
|
187
|
+
|
|
188
|
+
:returns: tuple(client_id, client_secret)
|
|
189
|
+
"""
|
|
190
|
+
logger.debug(
|
|
191
|
+
"ActivityPub.create_app(instance_url=%s, client=..., user_agent=%s, client_website=%s)",
|
|
192
|
+
instance_url,
|
|
193
|
+
user_agent,
|
|
194
|
+
client_website,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if "http" not in instance_url:
|
|
198
|
+
instance_url = f"https://{instance_url}"
|
|
199
|
+
|
|
200
|
+
data = {
|
|
201
|
+
"client_name": user_agent,
|
|
202
|
+
"client_website": client_website,
|
|
203
|
+
"scopes": "read write",
|
|
204
|
+
"redirect_uris": REDIRECT_URI,
|
|
205
|
+
}
|
|
206
|
+
try:
|
|
207
|
+
response = await client.post(url=f"{instance_url}/api/v1/apps", data=data)
|
|
208
|
+
logger.debug("ActivityPub.create_app response: \n%s", response)
|
|
209
|
+
await _check_exception(response)
|
|
210
|
+
response_dict = response.json()
|
|
211
|
+
except HTTPError as error:
|
|
212
|
+
raise NetworkError from error
|
|
213
|
+
|
|
214
|
+
logger.debug(
|
|
215
|
+
"ActivityPub.create_app response.json: \n%s",
|
|
216
|
+
json.dumps(response_dict, indent=4),
|
|
217
|
+
)
|
|
218
|
+
return (response_dict["client_id"]), (response_dict["client_secret"])
|