ogu-api 0.1.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.
- ogu_api-0.1.0/.gitignore +27 -0
- ogu_api-0.1.0/LICENSE +21 -0
- ogu_api-0.1.0/PKG-INFO +237 -0
- ogu_api-0.1.0/README.md +179 -0
- ogu_api-0.1.0/ogu_api/__init__.py +59 -0
- ogu_api-0.1.0/ogu_api/_http.py +338 -0
- ogu_api-0.1.0/ogu_api/client.py +112 -0
- ogu_api-0.1.0/ogu_api/errors.py +129 -0
- ogu_api-0.1.0/ogu_api/models.py +48 -0
- ogu_api-0.1.0/ogu_api/proxy.py +40 -0
- ogu_api-0.1.0/ogu_api/py.typed +0 -0
- ogu_api-0.1.0/ogu_api/resources/__init__.py +27 -0
- ogu_api-0.1.0/ogu_api/resources/_base.py +56 -0
- ogu_api-0.1.0/ogu_api/resources/credits.py +86 -0
- ogu_api-0.1.0/ogu_api/resources/feed.py +81 -0
- ogu_api-0.1.0/ogu_api/resources/members.py +55 -0
- ogu_api-0.1.0/ogu_api/resources/messages.py +127 -0
- ogu_api-0.1.0/ogu_api/resources/notifications.py +37 -0
- ogu_api-0.1.0/ogu_api/resources/reputation.py +50 -0
- ogu_api-0.1.0/ogu_api/resources/search.py +60 -0
- ogu_api-0.1.0/ogu_api/resources/session.py +45 -0
- ogu_api-0.1.0/ogu_api/resources/threads.py +103 -0
- ogu_api-0.1.0/ogu_api/resources/usercp.py +147 -0
- ogu_api-0.1.0/ogu_api/resources/users.py +61 -0
- ogu_api-0.1.0/pyproject.toml +64 -0
ogu_api-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.so
|
|
5
|
+
.Python
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
*.egg
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
.env
|
|
16
|
+
.envrc
|
|
17
|
+
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.mypy_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
.coverage
|
|
22
|
+
htmlcov/
|
|
23
|
+
coverage.xml
|
|
24
|
+
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
.DS_Store
|
ogu_api-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 forgivenforget
|
|
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.
|
ogu_api-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ogu-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async Python wrapper for the OGUsers (oguser.com) forum.
|
|
5
|
+
Project-URL: Homepage, https://github.com/forgivenforget/ogu-api
|
|
6
|
+
Project-URL: Repository, https://github.com/forgivenforget/ogu-api
|
|
7
|
+
Project-URL: Issues, https://github.com/forgivenforget/ogu-api/issues
|
|
8
|
+
Author: forgivenforget
|
|
9
|
+
Maintainer: forgivenforget
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 forgivenforget
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: forum,ogu,oguser,ogusers,sdk,wrapper
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
43
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
44
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
45
|
+
Classifier: Typing :: Typed
|
|
46
|
+
Requires-Python: >=3.11
|
|
47
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
48
|
+
Requires-Dist: tls-client>=1.0
|
|
49
|
+
Provides-Extra: dev
|
|
50
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
51
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
52
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
53
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
54
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
55
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
56
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
57
|
+
Description-Content-Type: text/markdown
|
|
58
|
+
|
|
59
|
+
# ogu-api
|
|
60
|
+
|
|
61
|
+
An async Python wrapper for the [OGUsers](https://oguser.com) forum.
|
|
62
|
+
|
|
63
|
+
Wraps the public-facing pages and forms; login, profile lookup, reputation, credits, private messages, feed, threads, notifications, member search, account control panel, with a typed, async-first client.
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install ogu-api
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires Python 3.11+.
|
|
72
|
+
|
|
73
|
+
## Quick start
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
|
|
78
|
+
from ogu_api import OguClient
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def main() -> None:
|
|
82
|
+
async with OguClient() as client:
|
|
83
|
+
await login(client, 'username', 'password')
|
|
84
|
+
|
|
85
|
+
profile = await client.users.get_by_username('forgivenforget')
|
|
86
|
+
print(client.users.extract_username(profile.text))
|
|
87
|
+
print(client.users.extract_credits(profile.text))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def login(client: OguClient, username: str, password: str, two_factor: str = '') -> None:
|
|
91
|
+
page = await client.session.get_login_page()
|
|
92
|
+
hidden = client.session.extract_login_hidden(page.text)
|
|
93
|
+
|
|
94
|
+
await client.session.login(username, password, two_factor, **hidden)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
asyncio.run(main())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Resources
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
| Resource | What it covers |
|
|
104
|
+
| ---------------------- | ------------------------------------------------------------------------------------- |
|
|
105
|
+
| `client.session` | login page, login, logout |
|
|
106
|
+
| `client.users` | profile lookup by username or uid; extract id, username, reputation, vouches, credits |
|
|
107
|
+
| `client.usercp` | notepad, signature, options, profile, change username/password/email |
|
|
108
|
+
| `client.reputation` | reputation page, send reputation |
|
|
109
|
+
| `client.credits` | donate page, stats page, send credits, parse recently-sent transactions |
|
|
110
|
+
| `client.messages` | PM inbox, conversation, compose, send, tracking, delete |
|
|
111
|
+
| `client.notifications` | notifications page, alerts, mark alerts read |
|
|
112
|
+
| `client.feed` | explore feed, home feed, thread-id extraction |
|
|
113
|
+
| `client.threads` | view thread, view forum, reply, create thread |
|
|
114
|
+
| `client.search` | full-text search form + post |
|
|
115
|
+
| `client.members` | member list, top, statistics, team, groups, vouches, awards, reputation history |
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## Send a PM
|
|
119
|
+
|
|
120
|
+
Most authenticated POSTs need a `my_post_key`. Pull it off whatever page you're already loading — here, the compose page:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
compose = await client.messages.get_compose_page()
|
|
124
|
+
|
|
125
|
+
await client.messages.send(
|
|
126
|
+
to = 'recipient',
|
|
127
|
+
message = 'hello',
|
|
128
|
+
my_post_key = client.messages.extract_my_post_key(compose.text),
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Read the feed
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
explore = await client.feed.get_explore()
|
|
136
|
+
|
|
137
|
+
for link in client.feed.extract_thread_links(explore.text)[:5]:
|
|
138
|
+
thread = await client.threads.get_by_link(link)
|
|
139
|
+
print(thread.text[:200])
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`extract_thread_links` returns paths for both numeric (`/showthread.php?tid=1234`) and slug-rewritten (`/Thread-Some-Slug`) thread URLs. If you specifically need numeric thread IDs, use `extract_thread_ids` — it skips slug-only links.
|
|
143
|
+
|
|
144
|
+
## Reply to a thread
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
reply_page = await client.threads.get_reply_page(tid)
|
|
148
|
+
hidden = client.threads.extract_reply_hidden(reply_page.text)
|
|
149
|
+
|
|
150
|
+
await client.threads.reply(
|
|
151
|
+
tid,
|
|
152
|
+
message = 'great post',
|
|
153
|
+
my_post_key = hidden['my_post_key'],
|
|
154
|
+
post_hash = hidden.get('posthash', ''),
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Persist a session
|
|
159
|
+
|
|
160
|
+
`ogu-api` doesn't store credentials. After login, the session lives on the client's cookie jar — serialize it however you like:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
cookies = {cookie.name: cookie.value for cookie in client.cookies}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
To resume a session, pre-seed the cookies on a fresh client:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
client = OguClient()
|
|
170
|
+
|
|
171
|
+
for name, value in cookies.items():
|
|
172
|
+
client.cookies.set(name, value)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Configuration
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
client = OguClient(
|
|
179
|
+
proxy = 'user:pass@host:8080',
|
|
180
|
+
timeout_seconds = 30.0,
|
|
181
|
+
max_retries = 3,
|
|
182
|
+
retry_backoff_seconds = 0.5,
|
|
183
|
+
client_identifier = 'chrome131',
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Proxy strings accept `user:pass@host:port`, `host:port:user:pass`, or plain `host:port` and are normalized to `http://...` for the underlying `tls_client` session.
|
|
188
|
+
|
|
189
|
+
## Errors
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from ogu_api import OguNotFoundError, OguRateLimitError
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
await client.users.get_by_username('does-not-exist')
|
|
196
|
+
|
|
197
|
+
except OguNotFoundError as E:
|
|
198
|
+
print('not found:', E.url)
|
|
199
|
+
|
|
200
|
+
except OguRateLimitError as E:
|
|
201
|
+
print('rate limited, retry after', E.retry_after_seconds)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Full hierarchy:
|
|
205
|
+
|
|
206
|
+
- `OguError`
|
|
207
|
+
- `OguAPIError` — `OguAuthenticationError`, `OguAuthorizationError`, `OguNotFoundError`, `OguValidationError`, `OguRateLimitError`, `OguServerError`
|
|
208
|
+
- `OguNetworkError` — `OguTimeoutError`
|
|
209
|
+
- `OguParseError`, `OguSessionError`, `OguLoginError`, `OguReputationError`, `OguCreditsError`
|
|
210
|
+
|
|
211
|
+
## Captcha
|
|
212
|
+
|
|
213
|
+
Sending credits requires an hCaptcha token. `ogu-api` doesn't ship a solver — pass a pre-solved token in:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
page = await client.credits.get_donate_page()
|
|
217
|
+
|
|
218
|
+
await client.credits.send(
|
|
219
|
+
username = 'recipient',
|
|
220
|
+
amount = 100,
|
|
221
|
+
captcha_token = solved_token,
|
|
222
|
+
**client.credits.extract_hidden(page.text),
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Features
|
|
227
|
+
|
|
228
|
+
- **Async-first** — every request is `async`, backed by `tls_client` on a worker thread for Cloudflare-friendly TLS fingerprints
|
|
229
|
+
- **Typed** — `from __future__ import annotations` everywhere, `py.typed` marker, dataclass models for parsed payloads
|
|
230
|
+
- **Resource-oriented** — calls grouped under `client.session`, `client.users`, `client.messages`, etc.
|
|
231
|
+
- **Typed errors** — full exception hierarchy mapped from HTTP status codes
|
|
232
|
+
- **Retries** — exponential backoff on `429` and `5xx`, honors `Retry-After`
|
|
233
|
+
- **Proxies** — string-form proxies auto-normalized
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
MIT
|
ogu_api-0.1.0/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# ogu-api
|
|
2
|
+
|
|
3
|
+
An async Python wrapper for the [OGUsers](https://oguser.com) forum.
|
|
4
|
+
|
|
5
|
+
Wraps the public-facing pages and forms; login, profile lookup, reputation, credits, private messages, feed, threads, notifications, member search, account control panel, with a typed, async-first client.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install ogu-api
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Python 3.11+.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import asyncio
|
|
19
|
+
|
|
20
|
+
from ogu_api import OguClient
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def main() -> None:
|
|
24
|
+
async with OguClient() as client:
|
|
25
|
+
await login(client, 'username', 'password')
|
|
26
|
+
|
|
27
|
+
profile = await client.users.get_by_username('forgivenforget')
|
|
28
|
+
print(client.users.extract_username(profile.text))
|
|
29
|
+
print(client.users.extract_credits(profile.text))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def login(client: OguClient, username: str, password: str, two_factor: str = '') -> None:
|
|
33
|
+
page = await client.session.get_login_page()
|
|
34
|
+
hidden = client.session.extract_login_hidden(page.text)
|
|
35
|
+
|
|
36
|
+
await client.session.login(username, password, two_factor, **hidden)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
asyncio.run(main())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Resources
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
| Resource | What it covers |
|
|
46
|
+
| ---------------------- | ------------------------------------------------------------------------------------- |
|
|
47
|
+
| `client.session` | login page, login, logout |
|
|
48
|
+
| `client.users` | profile lookup by username or uid; extract id, username, reputation, vouches, credits |
|
|
49
|
+
| `client.usercp` | notepad, signature, options, profile, change username/password/email |
|
|
50
|
+
| `client.reputation` | reputation page, send reputation |
|
|
51
|
+
| `client.credits` | donate page, stats page, send credits, parse recently-sent transactions |
|
|
52
|
+
| `client.messages` | PM inbox, conversation, compose, send, tracking, delete |
|
|
53
|
+
| `client.notifications` | notifications page, alerts, mark alerts read |
|
|
54
|
+
| `client.feed` | explore feed, home feed, thread-id extraction |
|
|
55
|
+
| `client.threads` | view thread, view forum, reply, create thread |
|
|
56
|
+
| `client.search` | full-text search form + post |
|
|
57
|
+
| `client.members` | member list, top, statistics, team, groups, vouches, awards, reputation history |
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Send a PM
|
|
61
|
+
|
|
62
|
+
Most authenticated POSTs need a `my_post_key`. Pull it off whatever page you're already loading — here, the compose page:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
compose = await client.messages.get_compose_page()
|
|
66
|
+
|
|
67
|
+
await client.messages.send(
|
|
68
|
+
to = 'recipient',
|
|
69
|
+
message = 'hello',
|
|
70
|
+
my_post_key = client.messages.extract_my_post_key(compose.text),
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Read the feed
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
explore = await client.feed.get_explore()
|
|
78
|
+
|
|
79
|
+
for link in client.feed.extract_thread_links(explore.text)[:5]:
|
|
80
|
+
thread = await client.threads.get_by_link(link)
|
|
81
|
+
print(thread.text[:200])
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`extract_thread_links` returns paths for both numeric (`/showthread.php?tid=1234`) and slug-rewritten (`/Thread-Some-Slug`) thread URLs. If you specifically need numeric thread IDs, use `extract_thread_ids` — it skips slug-only links.
|
|
85
|
+
|
|
86
|
+
## Reply to a thread
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
reply_page = await client.threads.get_reply_page(tid)
|
|
90
|
+
hidden = client.threads.extract_reply_hidden(reply_page.text)
|
|
91
|
+
|
|
92
|
+
await client.threads.reply(
|
|
93
|
+
tid,
|
|
94
|
+
message = 'great post',
|
|
95
|
+
my_post_key = hidden['my_post_key'],
|
|
96
|
+
post_hash = hidden.get('posthash', ''),
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Persist a session
|
|
101
|
+
|
|
102
|
+
`ogu-api` doesn't store credentials. After login, the session lives on the client's cookie jar — serialize it however you like:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
cookies = {cookie.name: cookie.value for cookie in client.cookies}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
To resume a session, pre-seed the cookies on a fresh client:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
client = OguClient()
|
|
112
|
+
|
|
113
|
+
for name, value in cookies.items():
|
|
114
|
+
client.cookies.set(name, value)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
client = OguClient(
|
|
121
|
+
proxy = 'user:pass@host:8080',
|
|
122
|
+
timeout_seconds = 30.0,
|
|
123
|
+
max_retries = 3,
|
|
124
|
+
retry_backoff_seconds = 0.5,
|
|
125
|
+
client_identifier = 'chrome131',
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Proxy strings accept `user:pass@host:port`, `host:port:user:pass`, or plain `host:port` and are normalized to `http://...` for the underlying `tls_client` session.
|
|
130
|
+
|
|
131
|
+
## Errors
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from ogu_api import OguNotFoundError, OguRateLimitError
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
await client.users.get_by_username('does-not-exist')
|
|
138
|
+
|
|
139
|
+
except OguNotFoundError as E:
|
|
140
|
+
print('not found:', E.url)
|
|
141
|
+
|
|
142
|
+
except OguRateLimitError as E:
|
|
143
|
+
print('rate limited, retry after', E.retry_after_seconds)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Full hierarchy:
|
|
147
|
+
|
|
148
|
+
- `OguError`
|
|
149
|
+
- `OguAPIError` — `OguAuthenticationError`, `OguAuthorizationError`, `OguNotFoundError`, `OguValidationError`, `OguRateLimitError`, `OguServerError`
|
|
150
|
+
- `OguNetworkError` — `OguTimeoutError`
|
|
151
|
+
- `OguParseError`, `OguSessionError`, `OguLoginError`, `OguReputationError`, `OguCreditsError`
|
|
152
|
+
|
|
153
|
+
## Captcha
|
|
154
|
+
|
|
155
|
+
Sending credits requires an hCaptcha token. `ogu-api` doesn't ship a solver — pass a pre-solved token in:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
page = await client.credits.get_donate_page()
|
|
159
|
+
|
|
160
|
+
await client.credits.send(
|
|
161
|
+
username = 'recipient',
|
|
162
|
+
amount = 100,
|
|
163
|
+
captcha_token = solved_token,
|
|
164
|
+
**client.credits.extract_hidden(page.text),
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Features
|
|
169
|
+
|
|
170
|
+
- **Async-first** — every request is `async`, backed by `tls_client` on a worker thread for Cloudflare-friendly TLS fingerprints
|
|
171
|
+
- **Typed** — `from __future__ import annotations` everywhere, `py.typed` marker, dataclass models for parsed payloads
|
|
172
|
+
- **Resource-oriented** — calls grouped under `client.session`, `client.users`, `client.messages`, etc.
|
|
173
|
+
- **Typed errors** — full exception hierarchy mapped from HTTP status codes
|
|
174
|
+
- **Retries** — exponential backoff on `429` and `5xx`, honors `Retry-After`
|
|
175
|
+
- **Proxies** — string-form proxies auto-normalized
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version as _package_version
|
|
4
|
+
|
|
5
|
+
from ._http import HttpClient, HttpClientConfig
|
|
6
|
+
from .client import OguClient
|
|
7
|
+
from .errors import (
|
|
8
|
+
OguAPIError,
|
|
9
|
+
OguAuthenticationError,
|
|
10
|
+
OguAuthorizationError,
|
|
11
|
+
OguCreditsError,
|
|
12
|
+
OguError,
|
|
13
|
+
OguLoginError,
|
|
14
|
+
OguNetworkError,
|
|
15
|
+
OguNotFoundError,
|
|
16
|
+
OguParseError,
|
|
17
|
+
OguRateLimitError,
|
|
18
|
+
OguReputationError,
|
|
19
|
+
OguServerError,
|
|
20
|
+
OguSessionError,
|
|
21
|
+
OguTimeoutError,
|
|
22
|
+
OguValidationError,
|
|
23
|
+
)
|
|
24
|
+
from .models import ActionResult, Message, RecentTransaction, ReputationPage, UserProfile
|
|
25
|
+
from .proxy import Proxy
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
__version__ = _package_version('ogu-api')
|
|
29
|
+
|
|
30
|
+
except PackageNotFoundError:
|
|
31
|
+
__version__ = '0.0.0+unknown'
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
'__version__',
|
|
35
|
+
'OguClient',
|
|
36
|
+
'HttpClient',
|
|
37
|
+
'HttpClientConfig',
|
|
38
|
+
'Proxy',
|
|
39
|
+
'ActionResult',
|
|
40
|
+
'Message',
|
|
41
|
+
'RecentTransaction',
|
|
42
|
+
'ReputationPage',
|
|
43
|
+
'UserProfile',
|
|
44
|
+
'OguError',
|
|
45
|
+
'OguAPIError',
|
|
46
|
+
'OguAuthenticationError',
|
|
47
|
+
'OguAuthorizationError',
|
|
48
|
+
'OguNotFoundError',
|
|
49
|
+
'OguValidationError',
|
|
50
|
+
'OguRateLimitError',
|
|
51
|
+
'OguServerError',
|
|
52
|
+
'OguNetworkError',
|
|
53
|
+
'OguTimeoutError',
|
|
54
|
+
'OguParseError',
|
|
55
|
+
'OguSessionError',
|
|
56
|
+
'OguLoginError',
|
|
57
|
+
'OguReputationError',
|
|
58
|
+
'OguCreditsError',
|
|
59
|
+
]
|