customrp-core 0.1.2__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.
- customrp_core-0.1.2/LICENSE +22 -0
- customrp_core-0.1.2/PKG-INFO +141 -0
- customrp_core-0.1.2/README.md +118 -0
- customrp_core-0.1.2/pyproject.toml +41 -0
- customrp_core-0.1.2/setup.cfg +4 -0
- customrp_core-0.1.2/src/customrp_core/__init__.py +35 -0
- customrp_core-0.1.2/src/customrp_core/assets.py +64 -0
- customrp_core-0.1.2/src/customrp_core/errors.py +29 -0
- customrp_core-0.1.2/src/customrp_core/ipc.py +337 -0
- customrp_core-0.1.2/src/customrp_core/presence.py +423 -0
- customrp_core-0.1.2/src/customrp_core/preset.py +168 -0
- customrp_core-0.1.2/src/customrp_core/session.py +246 -0
- customrp_core-0.1.2/src/customrp_core/testing.py +259 -0
- customrp_core-0.1.2/src/customrp_core.egg-info/PKG-INFO +141 -0
- customrp_core-0.1.2/src/customrp_core.egg-info/SOURCES.txt +21 -0
- customrp_core-0.1.2/src/customrp_core.egg-info/dependency_links.txt +1 -0
- customrp_core-0.1.2/src/customrp_core.egg-info/requires.txt +3 -0
- customrp_core-0.1.2/src/customrp_core.egg-info/top_level.txt +1 -0
- customrp_core-0.1.2/tests/test_assets.py +64 -0
- customrp_core-0.1.2/tests/test_ipc.py +161 -0
- customrp_core-0.1.2/tests/test_presence.py +192 -0
- customrp_core-0.1.2/tests/test_preset.py +91 -0
- customrp_core-0.1.2/tests/test_session.py +144 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KeiraOMG0
|
|
4
|
+
Copyright (c) 2020-2026 maximmax42 (CustomRP, from which the presence rules and preset format are derived)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: customrp-core
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Discord Rich Presence library implementing CustomRP's behaviour, for Linux, macOS and Windows
|
|
5
|
+
Author: KeiraOMG0
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/KeiraOMG0/customrp-core
|
|
8
|
+
Project-URL: Documentation, https://github.com/KeiraOMG0/customrp-core/tree/main/docs
|
|
9
|
+
Project-URL: Issues, https://github.com/KeiraOMG0/customrp-core/issues
|
|
10
|
+
Keywords: discord,rich presence,rpc,customrp
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Communications :: Chat
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# customrp-core
|
|
25
|
+
|
|
26
|
+
A small Python library for setting a custom Discord Rich Presence, and the engine behind
|
|
27
|
+
[customrp-cli](https://github.com/KeiraOMG0/customrp-cli). It follows the rules of
|
|
28
|
+
[CustomRP](https://www.customrp.xyz), the Windows app by maximmax42, but has no GUI and no Windows-only parts.
|
|
29
|
+
|
|
30
|
+
> **Independent project.** customrp-core is not part of CustomRP and is not maintained by its developer.
|
|
31
|
+
> Report problems here, not on the CustomRP repository.
|
|
32
|
+
|
|
33
|
+
## What it does
|
|
34
|
+
|
|
35
|
+
- **Talks to the Discord desktop client** over its local IPC socket (Linux, macOS) or named pipe (Windows).
|
|
36
|
+
No extra libraries are needed.
|
|
37
|
+
- **Models everything CustomRP can set:** activity type, name, details and state (with links), party size,
|
|
38
|
+
five timestamp modes, large and small images (asset keys or URLs) with tooltips and links, two buttons,
|
|
39
|
+
and which field shows in the member list.
|
|
40
|
+
- **Validates a presence the way Discord will.** It checks length limits (in characters or UTF-8 bytes),
|
|
41
|
+
single-character fields, bad URLs, and image URLs Discord would truncate, so you get a readable error
|
|
42
|
+
instead of a rejection.
|
|
43
|
+
- **Cleans up the input the same way CustomRP does:** it adds `https://`, converts hosts to punycode,
|
|
44
|
+
percent-encodes characters URLs can't contain, keeps a leading no-break space, and routes images from
|
|
45
|
+
Discord's own CDN through CustomRP's proxy.
|
|
46
|
+
- **Keeps a presence up** with `PresenceSession`. It retries every 10 seconds when Discord isn't running,
|
|
47
|
+
reconnects when Discord restarts, refreshes the local time display at midnight, and resends when the
|
|
48
|
+
presence changes.
|
|
49
|
+
- **Reads and writes `.crp` presets** that open in the Windows app and the other way round.
|
|
50
|
+
- **Lists an application's art assets**, using the public endpoint CustomRP uses.
|
|
51
|
+
- **Ships a fake Discord server** (`customrp_core.testing`) for testing your own code without Discord.
|
|
52
|
+
|
|
53
|
+
It has no dependencies outside the Python standard library.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
Python 3.9 or newer. Install from [PyPI](https://pypi.org/project/customrp-core/), ideally inside a
|
|
58
|
+
virtual environment:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
python3 -m venv .venv
|
|
62
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
63
|
+
pip install customrp-core
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or straight from Git, at a release tag:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
pip install "customrp-core @ git+https://github.com/KeiraOMG0/customrp-core.git@v0.1.2"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
In a `requirements.txt`, either works:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
customrp-core>=0.1.2,<0.2
|
|
76
|
+
customrp-core @ git+https://github.com/KeiraOMG0/customrp-core.git@v0.1.2
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Example
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import logging
|
|
83
|
+
from customrp_core import Button, Presence, PresenceSession, validate
|
|
84
|
+
|
|
85
|
+
logging.basicConfig(level=logging.INFO)
|
|
86
|
+
|
|
87
|
+
presence = Presence(
|
|
88
|
+
application_id="", # empty: CustomRP's default application
|
|
89
|
+
details="Writing a library",
|
|
90
|
+
state="in Python",
|
|
91
|
+
large_image="https://example.com/logo.png",
|
|
92
|
+
button1=Button("Source", "github.com/KeiraOMG0/customrp-core"),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
for issue in validate(presence):
|
|
96
|
+
print(issue) # e.g. "details: must be at least 2 characters long ..."
|
|
97
|
+
|
|
98
|
+
session = PresenceSession(presence) # retries 30 times, every 10 s, like CustomRP
|
|
99
|
+
try:
|
|
100
|
+
session.run() # blocks; Ctrl+C to stop
|
|
101
|
+
except KeyboardInterrupt:
|
|
102
|
+
pass
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
More in [docs/getting-started.md](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/getting-started.md).
|
|
106
|
+
|
|
107
|
+
## Documentation
|
|
108
|
+
|
|
109
|
+
| Page | Contents |
|
|
110
|
+
|---|---|
|
|
111
|
+
| [Getting started](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/getting-started.md) | Installing, a first presence, common tasks |
|
|
112
|
+
| [Presence model](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/presence.md) | Every field, its limits, validation, and how the payload is built |
|
|
113
|
+
| [Sessions](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/sessions.md) | `PresenceSession`: connecting, retries, updates, threads |
|
|
114
|
+
| [Low-level IPC](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/ipc.md) | `DiscordIPC`, where Discord's socket is looked for, the wire protocol |
|
|
115
|
+
| [Presets](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/presets.md) | The `.crp` format and how it maps to `Presence` |
|
|
116
|
+
| [Assets](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/assets.md) | Listing an application's uploaded images |
|
|
117
|
+
| [Testing with FakeDiscord](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/testing.md) | Testing code that uses this library |
|
|
118
|
+
| [Compatibility with CustomRP](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/compatibility.md) | What matches the Windows app, what differs, what isn't verified |
|
|
119
|
+
| [Development](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/development.md) | Running the tests, project layout, releasing |
|
|
120
|
+
|
|
121
|
+
## Platform support
|
|
122
|
+
|
|
123
|
+
| Platform | Status |
|
|
124
|
+
|---|---|
|
|
125
|
+
| Windows 10/11 | Unit tests pass. A handshake with a real Discord client over its named pipe works. |
|
|
126
|
+
| Linux | Unit tests run in CI on Ubuntu, including a fake Discord over a real Unix socket. Not yet tried against a real Discord client. |
|
|
127
|
+
| macOS | Unit tests run in CI, as on Linux. Not yet tried against a real Discord client. |
|
|
128
|
+
|
|
129
|
+
The Discord client has to run on the same machine and user account, since Rich Presence only works
|
|
130
|
+
through its local IPC endpoint. Flatpak, Snap and Vesktop socket locations are searched too (see
|
|
131
|
+
[docs/ipc.md](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/ipc.md)); those paths come from other Rich Presence projects, and none has been tested here.
|
|
132
|
+
|
|
133
|
+
## Credits
|
|
134
|
+
|
|
135
|
+
The presence rules, preset format and default application come from
|
|
136
|
+
[CustomRP](https://github.com/maximmax42/Discord-CustomRP) by maximmax42 (MIT license). Thanks to them for
|
|
137
|
+
the original app and for being open to an independent alternative.
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT. See [LICENSE](https://github.com/KeiraOMG0/customrp-core/blob/main/LICENSE).
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# customrp-core
|
|
2
|
+
|
|
3
|
+
A small Python library for setting a custom Discord Rich Presence, and the engine behind
|
|
4
|
+
[customrp-cli](https://github.com/KeiraOMG0/customrp-cli). It follows the rules of
|
|
5
|
+
[CustomRP](https://www.customrp.xyz), the Windows app by maximmax42, but has no GUI and no Windows-only parts.
|
|
6
|
+
|
|
7
|
+
> **Independent project.** customrp-core is not part of CustomRP and is not maintained by its developer.
|
|
8
|
+
> Report problems here, not on the CustomRP repository.
|
|
9
|
+
|
|
10
|
+
## What it does
|
|
11
|
+
|
|
12
|
+
- **Talks to the Discord desktop client** over its local IPC socket (Linux, macOS) or named pipe (Windows).
|
|
13
|
+
No extra libraries are needed.
|
|
14
|
+
- **Models everything CustomRP can set:** activity type, name, details and state (with links), party size,
|
|
15
|
+
five timestamp modes, large and small images (asset keys or URLs) with tooltips and links, two buttons,
|
|
16
|
+
and which field shows in the member list.
|
|
17
|
+
- **Validates a presence the way Discord will.** It checks length limits (in characters or UTF-8 bytes),
|
|
18
|
+
single-character fields, bad URLs, and image URLs Discord would truncate, so you get a readable error
|
|
19
|
+
instead of a rejection.
|
|
20
|
+
- **Cleans up the input the same way CustomRP does:** it adds `https://`, converts hosts to punycode,
|
|
21
|
+
percent-encodes characters URLs can't contain, keeps a leading no-break space, and routes images from
|
|
22
|
+
Discord's own CDN through CustomRP's proxy.
|
|
23
|
+
- **Keeps a presence up** with `PresenceSession`. It retries every 10 seconds when Discord isn't running,
|
|
24
|
+
reconnects when Discord restarts, refreshes the local time display at midnight, and resends when the
|
|
25
|
+
presence changes.
|
|
26
|
+
- **Reads and writes `.crp` presets** that open in the Windows app and the other way round.
|
|
27
|
+
- **Lists an application's art assets**, using the public endpoint CustomRP uses.
|
|
28
|
+
- **Ships a fake Discord server** (`customrp_core.testing`) for testing your own code without Discord.
|
|
29
|
+
|
|
30
|
+
It has no dependencies outside the Python standard library.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
Python 3.9 or newer. Install from [PyPI](https://pypi.org/project/customrp-core/), ideally inside a
|
|
35
|
+
virtual environment:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
python3 -m venv .venv
|
|
39
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
40
|
+
pip install customrp-core
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or straight from Git, at a release tag:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pip install "customrp-core @ git+https://github.com/KeiraOMG0/customrp-core.git@v0.1.2"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
In a `requirements.txt`, either works:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
customrp-core>=0.1.2,<0.2
|
|
53
|
+
customrp-core @ git+https://github.com/KeiraOMG0/customrp-core.git@v0.1.2
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Example
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import logging
|
|
60
|
+
from customrp_core import Button, Presence, PresenceSession, validate
|
|
61
|
+
|
|
62
|
+
logging.basicConfig(level=logging.INFO)
|
|
63
|
+
|
|
64
|
+
presence = Presence(
|
|
65
|
+
application_id="", # empty: CustomRP's default application
|
|
66
|
+
details="Writing a library",
|
|
67
|
+
state="in Python",
|
|
68
|
+
large_image="https://example.com/logo.png",
|
|
69
|
+
button1=Button("Source", "github.com/KeiraOMG0/customrp-core"),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
for issue in validate(presence):
|
|
73
|
+
print(issue) # e.g. "details: must be at least 2 characters long ..."
|
|
74
|
+
|
|
75
|
+
session = PresenceSession(presence) # retries 30 times, every 10 s, like CustomRP
|
|
76
|
+
try:
|
|
77
|
+
session.run() # blocks; Ctrl+C to stop
|
|
78
|
+
except KeyboardInterrupt:
|
|
79
|
+
pass
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
More in [docs/getting-started.md](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/getting-started.md).
|
|
83
|
+
|
|
84
|
+
## Documentation
|
|
85
|
+
|
|
86
|
+
| Page | Contents |
|
|
87
|
+
|---|---|
|
|
88
|
+
| [Getting started](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/getting-started.md) | Installing, a first presence, common tasks |
|
|
89
|
+
| [Presence model](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/presence.md) | Every field, its limits, validation, and how the payload is built |
|
|
90
|
+
| [Sessions](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/sessions.md) | `PresenceSession`: connecting, retries, updates, threads |
|
|
91
|
+
| [Low-level IPC](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/ipc.md) | `DiscordIPC`, where Discord's socket is looked for, the wire protocol |
|
|
92
|
+
| [Presets](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/presets.md) | The `.crp` format and how it maps to `Presence` |
|
|
93
|
+
| [Assets](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/assets.md) | Listing an application's uploaded images |
|
|
94
|
+
| [Testing with FakeDiscord](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/testing.md) | Testing code that uses this library |
|
|
95
|
+
| [Compatibility with CustomRP](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/compatibility.md) | What matches the Windows app, what differs, what isn't verified |
|
|
96
|
+
| [Development](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/development.md) | Running the tests, project layout, releasing |
|
|
97
|
+
|
|
98
|
+
## Platform support
|
|
99
|
+
|
|
100
|
+
| Platform | Status |
|
|
101
|
+
|---|---|
|
|
102
|
+
| Windows 10/11 | Unit tests pass. A handshake with a real Discord client over its named pipe works. |
|
|
103
|
+
| Linux | Unit tests run in CI on Ubuntu, including a fake Discord over a real Unix socket. Not yet tried against a real Discord client. |
|
|
104
|
+
| macOS | Unit tests run in CI, as on Linux. Not yet tried against a real Discord client. |
|
|
105
|
+
|
|
106
|
+
The Discord client has to run on the same machine and user account, since Rich Presence only works
|
|
107
|
+
through its local IPC endpoint. Flatpak, Snap and Vesktop socket locations are searched too (see
|
|
108
|
+
[docs/ipc.md](https://github.com/KeiraOMG0/customrp-core/blob/main/docs/ipc.md)); those paths come from other Rich Presence projects, and none has been tested here.
|
|
109
|
+
|
|
110
|
+
## Credits
|
|
111
|
+
|
|
112
|
+
The presence rules, preset format and default application come from
|
|
113
|
+
[CustomRP](https://github.com/maximmax42/Discord-CustomRP) by maximmax42 (MIT license). Thanks to them for
|
|
114
|
+
the original app and for being open to an independent alternative.
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT. See [LICENSE](https://github.com/KeiraOMG0/customrp-core/blob/main/LICENSE).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "customrp-core"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Discord Rich Presence library implementing CustomRP's behaviour, for Linux, macOS and Windows"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "KeiraOMG0" }]
|
|
13
|
+
keywords = ["discord", "rich presence", "rpc", "customrp"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Communications :: Chat",
|
|
21
|
+
]
|
|
22
|
+
dependencies = []
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
test = ["pytest>=7"]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/KeiraOMG0/customrp-core"
|
|
29
|
+
Documentation = "https://github.com/KeiraOMG0/customrp-core/tree/main/docs"
|
|
30
|
+
Issues = "https://github.com/KeiraOMG0/customrp-core/issues"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.dynamic]
|
|
33
|
+
version = { attr = "customrp_core.__version__" }
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
testpaths = ["tests"]
|
|
40
|
+
addopts = "-m 'not integration'"
|
|
41
|
+
markers = ["integration: needs a real Discord client running (see docs/development.md)"]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""customrp-core: the platform-independent parts of CustomRP, as a library.
|
|
2
|
+
|
|
3
|
+
Build a :class:`Presence`, then either turn it into a payload with :func:`build_activity` and send it
|
|
4
|
+
with :class:`DiscordIPC`, or let :class:`PresenceSession` handle connecting, retrying and resending.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.2"
|
|
8
|
+
|
|
9
|
+
from .errors import AssetsError, CustomRPError, PresenceError, PresetError # noqa: E402
|
|
10
|
+
from .ipc import DiscordIPC, display_name, ipc_paths, open_transport # noqa: E402
|
|
11
|
+
from .presence import ( # noqa: E402
|
|
12
|
+
DEFAULT_APPLICATION_ID,
|
|
13
|
+
MAX_LENGTH,
|
|
14
|
+
Activity,
|
|
15
|
+
ActivityType,
|
|
16
|
+
Button,
|
|
17
|
+
Issue,
|
|
18
|
+
Presence,
|
|
19
|
+
StatusDisplay,
|
|
20
|
+
TimestampMode,
|
|
21
|
+
build_activity,
|
|
22
|
+
check,
|
|
23
|
+
normalize,
|
|
24
|
+
validate,
|
|
25
|
+
)
|
|
26
|
+
from .preset import dump_preset, load_preset, parse_preset, save_preset # noqa: E402
|
|
27
|
+
from .session import PresenceSession, SessionState # noqa: E402
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"Activity", "ActivityType", "AssetsError", "Button", "CustomRPError", "DEFAULT_APPLICATION_ID",
|
|
31
|
+
"DiscordIPC", "Issue", "MAX_LENGTH", "Presence", "PresenceError", "PresenceSession", "PresetError",
|
|
32
|
+
"SessionState", "StatusDisplay", "TimestampMode", "build_activity", "check", "display_name",
|
|
33
|
+
"dump_preset", "ipc_paths", "load_preset", "normalize", "open_transport", "parse_preset",
|
|
34
|
+
"save_preset", "validate",
|
|
35
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Looking up the art assets uploaded to a Discord application.
|
|
2
|
+
|
|
3
|
+
CustomRP fills its image dropdowns from the same public endpoint; it needs no token.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import urllib.error
|
|
10
|
+
import urllib.request
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from . import __version__
|
|
14
|
+
from .errors import AssetsError
|
|
15
|
+
|
|
16
|
+
ASSETS_URL = "https://discord.com/api/oauth2/applications/{}/assets"
|
|
17
|
+
ASSETS_PAGE_URL = "https://discord.com/developers/applications/{}/rich-presence/assets"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class Asset:
|
|
22
|
+
id: str
|
|
23
|
+
name: str
|
|
24
|
+
#: Discord's asset type number, passed through as is (CustomRP doesn't use it)
|
|
25
|
+
type: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _check_id(application_id: str):
|
|
29
|
+
if not (application_id.isdigit() and application_id.isascii()):
|
|
30
|
+
raise AssetsError(f"{application_id!r} isn't a valid application ID")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def assets_page_url(application_id: str) -> str:
|
|
34
|
+
"""The Developer Portal page where the application's assets are managed."""
|
|
35
|
+
_check_id(application_id)
|
|
36
|
+
return ASSETS_PAGE_URL.format(application_id)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def fetch_assets(application_id: str, *, timeout: float = 10.0) -> list[Asset]:
|
|
40
|
+
"""Fetches the list of assets uploaded to an application. Raises :class:`AssetsError` on failure."""
|
|
41
|
+
_check_id(application_id)
|
|
42
|
+
request = urllib.request.Request(ASSETS_URL.format(application_id),
|
|
43
|
+
headers={"User-Agent": f"customrp-core/{__version__}"})
|
|
44
|
+
try:
|
|
45
|
+
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
46
|
+
data = json.load(response)
|
|
47
|
+
except urllib.error.HTTPError as e:
|
|
48
|
+
if e.code == 404:
|
|
49
|
+
raise AssetsError(f"There's no application with the ID {application_id}") from e
|
|
50
|
+
raise AssetsError(f"Discord answered with HTTP {e.code} {e.reason}") from e
|
|
51
|
+
except (urllib.error.URLError, OSError) as e:
|
|
52
|
+
reason = getattr(e, "reason", e)
|
|
53
|
+
raise AssetsError(f"Couldn't reach Discord: {reason}") from e
|
|
54
|
+
except ValueError as e:
|
|
55
|
+
raise AssetsError("Discord sent something that isn't JSON") from e
|
|
56
|
+
|
|
57
|
+
return parse_assets(data)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def parse_assets(data) -> list[Asset]:
|
|
61
|
+
if not isinstance(data, list):
|
|
62
|
+
raise AssetsError("Unexpected answer from Discord")
|
|
63
|
+
return [Asset(str(item.get("id", "")), str(item.get("name", "")), int(item.get("type", 0) or 0))
|
|
64
|
+
for item in data if isinstance(item, dict)]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Exceptions raised by customrp-core."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CustomRPError(Exception):
|
|
7
|
+
"""Base class for every error this library raises on purpose."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PresenceError(CustomRPError, ValueError):
|
|
11
|
+
"""A presence can't be sent as it is.
|
|
12
|
+
|
|
13
|
+
``field`` names the :class:`~customrp_core.presence.Presence` attribute at fault
|
|
14
|
+
(for example ``"large_image"`` or ``"button1.url"``), or is ``None`` when the problem
|
|
15
|
+
isn't tied to one field.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, message: str, field: str | None = None):
|
|
19
|
+
super().__init__(f"{field}: {message}" if field else message)
|
|
20
|
+
self.message = message
|
|
21
|
+
self.field = field
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PresetError(CustomRPError):
|
|
25
|
+
"""A ``.crp`` preset file can't be read or written."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AssetsError(CustomRPError):
|
|
29
|
+
"""The list of an application's art assets couldn't be fetched."""
|