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.
@@ -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
+ [![Repo](https://img.shields.io/badge/repo-Codeberg.org-blue)](https://codeberg.org/MarvinsMastodonTools/longwei "Repo at Codeberg.org")
31
+ [![CI](https://ci.codeberg.org/api/badges/MarvinsMastodonTools/longwei/status.svg)](https://ci.codeberg.org/MarvinsMastodonTools/longwei "CI / Woodpecker")
32
+ [![Downloads](https://pepy.tech/badge/longwei)](https://pepy.tech/project/longwei "Download count")
33
+ [![uv_secure](https://img.shields.io/badge/uv--secure-checked-green)](https://docs.astral.sh/uv/guides/audit/ "Checked with uv-secure")
34
+ [![gitleaks](https://img.shields.io/badge/gitleaks-checked-green)](https://github.com/gitleaks/gitleaks "Checked with gitleaks")
35
+ [![pysentry](https://img.shields.io/badge/pysentry-checked-green)](https://github.com/astral-sh/pysentry "Checked with pysentry")
36
+ [![complexipy](https://img.shields.io/badge/complexipy-checked-green.svg)](https://github.com/rohaquinlop/complexipy "Checked with complexipy")
37
+ [![Codestyle](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff "Code style: ruff")
38
+ [![Version](https://img.shields.io/pypi/pyversions/longwei)](https://pypi.org/project/longwei "PyPI – Python Version")
39
+ [![Wheel](https://img.shields.io/pypi/wheel/longwei)](https://pypi.org/project/longwei "PyPI – Wheel")
40
+ [![License: AGPL-3.0-or-later](https://img.shields.io/badge/License-AGPL--3.0--or--later-blue.svg)](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
+ [![Repo](https://img.shields.io/badge/repo-Codeberg.org-blue)](https://codeberg.org/MarvinsMastodonTools/longwei "Repo at Codeberg.org")
4
+ [![CI](https://ci.codeberg.org/api/badges/MarvinsMastodonTools/longwei/status.svg)](https://ci.codeberg.org/MarvinsMastodonTools/longwei "CI / Woodpecker")
5
+ [![Downloads](https://pepy.tech/badge/longwei)](https://pepy.tech/project/longwei "Download count")
6
+ [![uv_secure](https://img.shields.io/badge/uv--secure-checked-green)](https://docs.astral.sh/uv/guides/audit/ "Checked with uv-secure")
7
+ [![gitleaks](https://img.shields.io/badge/gitleaks-checked-green)](https://github.com/gitleaks/gitleaks "Checked with gitleaks")
8
+ [![pysentry](https://img.shields.io/badge/pysentry-checked-green)](https://github.com/astral-sh/pysentry "Checked with pysentry")
9
+ [![complexipy](https://img.shields.io/badge/complexipy-checked-green.svg)](https://github.com/rohaquinlop/complexipy "Checked with complexipy")
10
+ [![Codestyle](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff "Code style: ruff")
11
+ [![Version](https://img.shields.io/pypi/pyversions/longwei)](https://pypi.org/project/longwei "PyPI – Python Version")
12
+ [![Wheel](https://img.shields.io/pypi/wheel/longwei)](https://pypi.org/project/longwei "PyPI – Wheel")
13
+ [![License: AGPL-3.0-or-later](https://img.shields.io/badge/License-AGPL--3.0--or--later-blue.svg)](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"])