jugs 1.0.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,40 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ environment:
15
+ name: pypi
16
+ url: https://pypi.org/p/jugs
17
+
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.12"
26
+
27
+ - name: Install build tools
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install build twine
31
+
32
+ - name: Build package
33
+ run: python -m build
34
+
35
+ - name: Publish to PyPI
36
+ uses: pypa/gh-action-pypi-publish@release/v1
37
+ with:
38
+ user: __token__
39
+ password: ${{ secrets.pypi }}
40
+ verbose: true
jugs-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.5
2
+ Name: jugs
3
+ Version: 1.0.0
4
+ Summary: High-performance Python client for jugs.lol
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: httpx[http2]>=0.27.0
7
+ Requires-Dist: orjson>=3.10.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest; extra == 'dev'
10
+ Requires-Dist: pytest-asyncio; extra == 'dev'
jugs-1.0.0/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # jugs
2
+
3
+ High-performance Python client for [jugs.lol](https://jugs.lol).
4
+
5
+ Blazing fast. Minimal overhead. Fully typed. Proxy rotation. Session/auth support.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install -e .
11
+ ```
12
+
13
+ Requires Python 3.10+.
14
+
15
+ Dependencies: `httpx[http2]`, `orjson`.
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from jugs import JugsClient
21
+
22
+ with JugsClient("c") as client:
23
+ print(client.view())
24
+ print(client.status())
25
+ print(client.comments())
26
+ print(client.presence("1274550388594311188"))
27
+ ```
28
+
29
+ ## Features
30
+
31
+ - All public `/api/sys/*` endpoints
32
+ - Discord presence
33
+ - Link click tracking
34
+ - Next.js server actions (tips, drops, crates)
35
+ - Email login / signup via Supabase Auth
36
+ - Discord OAuth URL helper
37
+ - Session load / refresh / logout
38
+ - Proxy rotation (round-robin + random)
39
+ - HTTP/2 + orjson for max speed
40
+ - Zero comments / docstrings in source (clean, dense code)
41
+ - Strict type hints + `slots=True` dataclasses
42
+ - Context manager support
43
+
44
+ ## Proxy Rotation
45
+
46
+ ```python
47
+ proxies = [
48
+ "http://user:pass@ip1:port",
49
+ "http://user:pass@ip2:port",
50
+ "socks5://ip3:port",
51
+ ]
52
+
53
+ client = JugsClient("c", proxies=proxies)
54
+
55
+ # Auto-rotate on view
56
+ client.view(rotate=True)
57
+ ```
58
+
59
+ ## Authentication
60
+
61
+ ```python
62
+ client = JugsClient("c")
63
+
64
+ # Email
65
+ client.login("you@example.com", "password")
66
+ client.signup("you@example.com", "password")
67
+
68
+ # Load existing tokens
69
+ client.load_session(access_token="...", refresh_token="...")
70
+
71
+ # Refresh
72
+ client.refresh_session()
73
+
74
+ # Discord OAuth (open in browser)
75
+ print(client.discord_oauth_url())
76
+
77
+ # Logout
78
+ client.logout()
79
+ ```
80
+
81
+ ## Full API Surface
82
+
83
+ ### Profile / Social
84
+
85
+ | Method | Description |
86
+ |--------|-------------|
87
+ | `view(username=None, rotate=False)` | Increment view counter |
88
+ | `status(username=None)` | Bookmarks + likes counts |
89
+ | `likes(username=None)` | Alias of status |
90
+ | `click(url, username=None)` | Track link click |
91
+ | `comments(username=None)` | Fetch comments |
92
+ | `post_comment(content, username=None)` | Post comment (auth) |
93
+
94
+ ### Discord
95
+
96
+ | Method | Description |
97
+ |--------|-------------|
98
+ | `presence(discord_id)` | Live status, activities, roles |
99
+
100
+ ### Server Actions
101
+
102
+ | Method | Description |
103
+ |--------|-------------|
104
+ | `tip(payload=None)` | Send credits tip (auth) |
105
+ | `claim_drop(drop_id)` | Claim Discord credit drop |
106
+ | `claim_crate(crate_id)` | Claim crate drop |
107
+ | `get_unseen_drops()` | Unseen credit drops |
108
+ | `get_unseen_crates()` | Unseen crate drops |
109
+ | `mark_drops_seen()` | Mark drops seen |
110
+ | `mark_crates_seen()` | Mark crates seen |
111
+
112
+ ### Feedback
113
+
114
+ | Method | Description |
115
+ |--------|-------------|
116
+ | `bug_check()` | Check bug report eligibility |
117
+ | `bug_report(...)` | Submit bug report (auth) |
118
+ | `suggest_check()` | Check suggest eligibility |
119
+ | `suggest(text, context="")` | Submit suggestion (auth) |
120
+
121
+ ### Auth
122
+
123
+ | Method | Description |
124
+ |--------|-------------|
125
+ | `login(email, password)` | Email/password login |
126
+ | `signup(email, password)` | Email signup |
127
+ | `load_session(access, refresh=None)` | Inject tokens |
128
+ | `refresh_session()` | Refresh tokens |
129
+ | `logout()` | Clear session |
130
+ | `discord_oauth_url(redirect=None)` | Get Discord OAuth URL |
131
+
132
+ ## Models
133
+
134
+ All responses are typed dataclasses (`slots=True`, frozen where possible):
135
+
136
+ - `ViewResult`
137
+ - `StatusResult`
138
+ - `CommentsResult` / `Comment`
139
+ - `PresenceResult` / `DiscordUser` / `DiscordMember`
140
+ - `ClickResult`
141
+ - `ActionResult`
142
+
143
+ ## Architecture
144
+
145
+ ```
146
+ jugs/
147
+ ├── __init__.py
148
+ ├── client.py # JugsClient facade
149
+ ├── http.py # HTTPSession + ProxyRotator
150
+ ├── auth.py # AuthManager (Supabase)
151
+ ├── constants.py
152
+ ├── exceptions.py
153
+ ├── models.py
154
+ └── endpoints/
155
+ ├── sys.py # /api/sys/*
156
+ ├── discord.py
157
+ └── actions.py # Next.js server actions
158
+ ```
159
+
160
+ ## Performance Notes
161
+
162
+ - `orjson` for all serialization (significantly faster than stdlib json)
163
+ - `httpx` with HTTP/2
164
+ - `__slots__` everywhere to cut memory + attribute lookup cost
165
+ - Frozen dataclasses for response objects
166
+ - No unnecessary abstractions
167
+ - Connection reuse via persistent session
168
+ - Optional per-request proxy rotation
169
+
170
+ ## Reserved Usernames
171
+
172
+ These cannot be used as profile handles:
173
+
174
+ `login`, `signup`, `dashboard`, `api`, `admin`, `owner`, `auth`, `about`, `terms`, `privacy`, `leaderboard`, `directory`, `discord`, `shop`, `addons`, `claim`, `premium`, `crates`, `reset-password`, `account-restricted`, `_next`, `static`
175
+
176
+ ## License
177
+
178
+ Private. All rights reserved.
@@ -0,0 +1,17 @@
1
+ from jugs import JugsClient
2
+
3
+
4
+ def main() -> None:
5
+ client = JugsClient("c")
6
+
7
+ print(client.discord_oauth_url())
8
+
9
+ # client.login("email@example.com", "password")
10
+ # client.signup("email@example.com", "password")
11
+ # client.load_session("access_token_here", "refresh_token_here")
12
+
13
+ client.close()
14
+
15
+
16
+ if __name__ == "__main__":
17
+ main()
@@ -0,0 +1,13 @@
1
+ from jugs import JugsClient
2
+
3
+
4
+ def main() -> None:
5
+ with JugsClient("c") as client:
6
+ print(client.view())
7
+ print(client.status())
8
+ print(client.comments())
9
+ print(client.presence("1274550388594311188"))
10
+
11
+
12
+ if __name__ == "__main__":
13
+ main()
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import random
5
+ import string
6
+ import sys
7
+ import time
8
+ from dataclasses import dataclass
9
+ from typing import Any
10
+
11
+ from rich import box
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.table import Table
15
+ from rich.text import Text
16
+ from rich.align import Align
17
+ from rich.rule import Rule
18
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
19
+
20
+ from jugs import JugsClient
21
+ from jugs.exceptions import AuthError, JugsError
22
+
23
+
24
+ console = Console()
25
+
26
+
27
+ @dataclass(slots=True)
28
+ class AccountResult:
29
+ email: str
30
+ password: str
31
+ ok: bool
32
+ user_id: str | None = None
33
+ username_hint: str | None = None
34
+ access_token: str | None = None
35
+ error: str | None = None
36
+ unseen_drops: Any = None
37
+ unseen_crates: Any = None
38
+ session_user: dict[str, Any] | None = None
39
+
40
+
41
+ def random_string(length: int = 10) -> str:
42
+ alphabet = string.ascii_lowercase + string.digits
43
+ return "".join(random.choices(alphabet, k=length))
44
+
45
+
46
+ def generate_account() -> tuple[str, str]:
47
+ user = random_string(8)
48
+ domain = random.choice(["gmail.com", "outlook.com", "proton.me", "yahoo.com"])
49
+ email = f"{user}.{random_string(4)}@{domain}"
50
+ password = random_string(12) + random.choice("!@#$%") + str(random.randint(10, 99))
51
+ return email, password
52
+
53
+
54
+ def create_account(client: JugsClient, email: str, password: str) -> AccountResult:
55
+ result = AccountResult(email=email, password=password, ok=False)
56
+
57
+ try:
58
+ data = client.signup(email, password)
59
+ user = data.get("user") or data
60
+ result.ok = True
61
+ result.user_id = user.get("id")
62
+ result.access_token = data.get("access_token")
63
+ result.session_user = user if isinstance(user, dict) else data
64
+
65
+ try:
66
+ drops = client.get_unseen_drops()
67
+ result.unseen_drops = drops.data
68
+ except Exception:
69
+ result.unseen_drops = None
70
+
71
+ try:
72
+ crates = client.get_unseen_crates()
73
+ result.unseen_crates = crates.data
74
+ except Exception:
75
+ result.unseen_crates = None
76
+
77
+ try:
78
+ session = client.auth.get_session()
79
+ if session:
80
+ result.session_user = session
81
+ except Exception:
82
+ pass
83
+
84
+ except AuthError as e:
85
+ result.error = e.message
86
+ except JugsError as e:
87
+ result.error = e.message
88
+ except Exception as e:
89
+ result.error = str(e)
90
+
91
+ return result
92
+
93
+
94
+ def render_header() -> None:
95
+ title = Text()
96
+ title.append("jugs", style="bold magenta")
97
+ title.append(" multi-account forge", style="bold white")
98
+
99
+ console.print()
100
+ console.print(Panel(
101
+ Align.center(title),
102
+ subtitle="[dim]create · inspect · display[/dim]",
103
+ border_style="magenta",
104
+ box=box.DOUBLE,
105
+ ))
106
+ console.print()
107
+
108
+
109
+ def render_account(idx: int, acc: AccountResult) -> None:
110
+ status = Text("SUCCESS", style="bold green") if acc.ok else Text("FAILED", style="bold red")
111
+
112
+ table = Table(
113
+ show_header=False,
114
+ box=box.ROUNDED,
115
+ border_style="cyan" if acc.ok else "red",
116
+ padding=(0, 1),
117
+ expand=True,
118
+ )
119
+ table.add_column("key", style="bold dim", width=16)
120
+ table.add_column("value", overflow="fold")
121
+
122
+ table.add_row("status", status)
123
+ table.add_row("email", f"[bold]{acc.email}[/bold]")
124
+ table.add_row("password", f"[yellow]{acc.password}[/yellow]")
125
+
126
+ if acc.user_id:
127
+ table.add_row("user id", f"[cyan]{acc.user_id}[/cyan]")
128
+
129
+ if acc.access_token:
130
+ token_preview = acc.access_token[:28] + "…" if len(acc.access_token) > 28 else acc.access_token
131
+ table.add_row("access token", f"[dim]{token_preview}[/dim]")
132
+
133
+ if acc.session_user:
134
+ email_confirmed = acc.session_user.get("email_confirmed_at")
135
+ created = acc.session_user.get("created_at", "?")
136
+ table.add_row("created", str(created))
137
+ table.add_row("email confirmed", "yes" if email_confirmed else "[yellow]pending[/yellow]")
138
+
139
+ meta = acc.session_user.get("user_metadata") or {}
140
+ if meta:
141
+ table.add_row("metadata", str(meta))
142
+
143
+ if acc.unseen_drops is not None:
144
+ table.add_row("unseen drops", f"[green]{acc.unseen_drops}[/green]")
145
+
146
+ if acc.unseen_crates is not None:
147
+ table.add_row("unseen crates", f"[green]{acc.unseen_crates}[/green]")
148
+
149
+ if acc.error:
150
+ table.add_row("error", f"[red]{acc.error}[/red]")
151
+
152
+ console.print(Panel(
153
+ table,
154
+ title=f"[bold]Account {idx}[/bold]",
155
+ border_style="cyan" if acc.ok else "red",
156
+ box=box.ROUNDED,
157
+ ))
158
+ console.print()
159
+
160
+
161
+ def render_summary(results: list[AccountResult]) -> None:
162
+ ok_count = sum(1 for r in results if r.ok)
163
+ fail_count = len(results) - ok_count
164
+
165
+ summary = Table(box=box.SIMPLE_HEAVY, show_header=True, border_style="magenta")
166
+ summary.add_column("metric", style="bold")
167
+ summary.add_column("value", justify="right")
168
+
169
+ summary.add_row("total", str(len(results)))
170
+ summary.add_row("success", f"[green]{ok_count}[/green]")
171
+ summary.add_row("failed", f"[red]{fail_count}[/red]")
172
+
173
+ console.print(Rule("[bold magenta]summary[/bold magenta]"))
174
+ console.print(summary)
175
+ console.print()
176
+
177
+ if ok_count:
178
+ creds = Table(title="credentials", box=box.MINIMAL_DOUBLE_HEAD, border_style="green")
179
+ creds.add_column("#", style="dim")
180
+ creds.add_column("email", style="bold")
181
+ creds.add_column("password", style="yellow")
182
+ creds.add_column("user id", style="cyan")
183
+
184
+ for i, r in enumerate(results, 1):
185
+ if r.ok:
186
+ creds.add_row(str(i), r.email, r.password, r.user_id or "—")
187
+
188
+ console.print(creds)
189
+ console.print()
190
+
191
+
192
+ def main() -> None:
193
+ count = 3
194
+ if len(sys.argv) > 1:
195
+ try:
196
+ count = max(1, int(sys.argv[1]))
197
+ except ValueError:
198
+ pass
199
+
200
+ render_header()
201
+
202
+ results: list[AccountResult] = []
203
+
204
+ with Progress(
205
+ SpinnerColumn(style="magenta"),
206
+ TextColumn("[progress.description]{task.description}"),
207
+ BarColumn(bar_width=24, style="cyan", complete_style="green"),
208
+ console=console,
209
+ transient=True,
210
+ ) as progress:
211
+ task = progress.add_task("forging accounts…", total=count)
212
+
213
+ for i in range(count):
214
+ email, password = generate_account()
215
+ progress.update(task, description=f"creating [cyan]{email}[/cyan]")
216
+
217
+ with JugsClient("c") as client:
218
+ result = create_account(client, email, password)
219
+
220
+ results.append(result)
221
+ progress.advance(task)
222
+ time.sleep(0.4)
223
+
224
+ console.print()
225
+ console.print(Rule("[bold]results[/bold]"))
226
+ console.print()
227
+
228
+ for i, acc in enumerate(results, 1):
229
+ render_account(i, acc)
230
+
231
+ render_summary(results)
232
+
233
+ console.print(Panel(
234
+ Align.center("[dim]done — accounts created via Supabase Auth on jugs.lol[/dim]"),
235
+ border_style="dim",
236
+ box=box.SIMPLE,
237
+ ))
238
+
239
+
240
+ if __name__ == "__main__":
241
+ main()
@@ -0,0 +1,18 @@
1
+ from jugs import JugsClient
2
+
3
+
4
+ PROXIES = [
5
+ "http://user:pass@ip1:port",
6
+ "http://user:pass@ip2:port",
7
+ ]
8
+
9
+
10
+ def main() -> None:
11
+ with JugsClient("c", proxies=PROXIES) as client:
12
+ for _ in range(5):
13
+ result = client.view(rotate=True)
14
+ print(result)
15
+
16
+
17
+ if __name__ == "__main__":
18
+ main()
@@ -0,0 +1,2 @@
1
+ rich>=13.7.0
2
+ jugs @ git+https://github.com/lmaohi7181828/jugs.git
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from jugs.client import JugsClient
4
+ from jugs.exceptions import (
5
+ AuthError,
6
+ JugsError,
7
+ NotFoundError,
8
+ ProxyError,
9
+ RateLimitError,
10
+ ValidationError,
11
+ )
12
+ from jugs.models import (
13
+ ActionResult,
14
+ ClickResult,
15
+ Comment,
16
+ CommentsResult,
17
+ DiscordMember,
18
+ DiscordUser,
19
+ PresenceResult,
20
+ StatusResult,
21
+ ViewResult,
22
+ )
23
+
24
+ __all__ = (
25
+ "JugsClient",
26
+ "JugsError",
27
+ "AuthError",
28
+ "RateLimitError",
29
+ "NotFoundError",
30
+ "ValidationError",
31
+ "ProxyError",
32
+ "ViewResult",
33
+ "StatusResult",
34
+ "Comment",
35
+ "CommentsResult",
36
+ "DiscordUser",
37
+ "DiscordMember",
38
+ "PresenceResult",
39
+ "ClickResult",
40
+ "ActionResult",
41
+ )
42
+
43
+ __version__ = "1.0.0"