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.
@@ -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
@@ -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
+ ]