justob 1.0.0__py3-none-any.whl
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.
- justob/__init__.py +4 -0
- justob/__main__.py +6 -0
- justob/cli.py +114 -0
- justob-1.0.0.dist-info/METADATA +56 -0
- justob-1.0.0.dist-info/RECORD +8 -0
- justob-1.0.0.dist-info/WHEEL +5 -0
- justob-1.0.0.dist-info/entry_points.txt +2 -0
- justob-1.0.0.dist-info/top_level.txt +1 -0
justob/__init__.py
ADDED
justob/__main__.py
ADDED
justob/cli.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
justob.cli — argparse front-end for the Obfuscate API.
|
|
3
|
+
|
|
4
|
+
Identical logic to the standalone /justob.py script, but installed as a
|
|
5
|
+
proper package so users get a `justob` command on $PATH after
|
|
6
|
+
`pip install justob`.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import mimetypes
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import uuid
|
|
15
|
+
from urllib import request as _req
|
|
16
|
+
from urllib.error import HTTPError, URLError
|
|
17
|
+
|
|
18
|
+
__version__ = "1.0.0"
|
|
19
|
+
DEFAULT_ENDPOINT = os.environ.get(
|
|
20
|
+
"JUSTOB_ENDPOINT",
|
|
21
|
+
"https://project--36cd11b2-d68a-4f5c-b183-df77d18fac1d.lovable.app",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _encode_multipart(fields: dict, file_path: str):
|
|
26
|
+
"""Build a multipart/form-data body using only the stdlib."""
|
|
27
|
+
boundary = uuid.uuid4().hex
|
|
28
|
+
crlf = b"\r\n"
|
|
29
|
+
body = bytearray()
|
|
30
|
+
for name, value in fields.items():
|
|
31
|
+
body += f"--{boundary}\r\n".encode()
|
|
32
|
+
body += f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode()
|
|
33
|
+
body += str(value).encode() + crlf
|
|
34
|
+
fname = os.path.basename(file_path)
|
|
35
|
+
ctype = mimetypes.guess_type(fname)[0] or "application/zip"
|
|
36
|
+
with open(file_path, "rb") as fh:
|
|
37
|
+
data = fh.read()
|
|
38
|
+
body += f"--{boundary}\r\n".encode()
|
|
39
|
+
body += (
|
|
40
|
+
f'Content-Disposition: form-data; name="file"; filename="{fname}"\r\n'
|
|
41
|
+
f"Content-Type: {ctype}\r\n\r\n"
|
|
42
|
+
).encode()
|
|
43
|
+
body += data + crlf
|
|
44
|
+
body += f"--{boundary}--\r\n".encode()
|
|
45
|
+
return bytes(body), f"multipart/form-data; boundary={boundary}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def run(argv=None) -> int:
|
|
49
|
+
p = argparse.ArgumentParser(
|
|
50
|
+
prog="justob",
|
|
51
|
+
description="Obfuscate a ZIP archive via the Obfuscate API.",
|
|
52
|
+
)
|
|
53
|
+
p.add_argument("zip", nargs="?", help="Path to the .zip file to obfuscate.")
|
|
54
|
+
p.add_argument("--profile", default="maximum",
|
|
55
|
+
choices=["light", "standard", "maximum"])
|
|
56
|
+
p.add_argument("--strip-console", default="true")
|
|
57
|
+
p.add_argument("--encrypt-strings", default="true")
|
|
58
|
+
p.add_argument("--include-skipped", default="false")
|
|
59
|
+
p.add_argument("--inject-header", default="false")
|
|
60
|
+
p.add_argument("--header", default="Sealed by Obfuscate")
|
|
61
|
+
p.add_argument("--out", default=None)
|
|
62
|
+
p.add_argument("--endpoint", default=DEFAULT_ENDPOINT)
|
|
63
|
+
p.add_argument("--version", action="version", version=f"justob {__version__}")
|
|
64
|
+
args = p.parse_args(argv)
|
|
65
|
+
|
|
66
|
+
if not args.zip:
|
|
67
|
+
p.print_help()
|
|
68
|
+
return 1
|
|
69
|
+
if not os.path.isfile(args.zip):
|
|
70
|
+
print(f"justob: file not found: {args.zip}", file=sys.stderr); return 2
|
|
71
|
+
if not args.zip.lower().endswith(".zip"):
|
|
72
|
+
print("justob: input must be a .zip archive", file=sys.stderr); return 2
|
|
73
|
+
|
|
74
|
+
out_path = args.out or args.zip.rsplit(".zip", 1)[0] + ".obfuscated.zip"
|
|
75
|
+
url = args.endpoint.rstrip("/") + "/api/public/obfuscate"
|
|
76
|
+
fields = {
|
|
77
|
+
"profile": args.profile,
|
|
78
|
+
"stripConsole": args.strip_console,
|
|
79
|
+
"encryptStrings": args.encrypt_strings,
|
|
80
|
+
"includeSkipped": args.include_skipped,
|
|
81
|
+
"injectHeader": args.inject_header,
|
|
82
|
+
"headerText": args.header,
|
|
83
|
+
}
|
|
84
|
+
body, content_type = _encode_multipart(fields, args.zip)
|
|
85
|
+
print(f"→ POST {url} ({len(body)/(1024*1024):.2f} MB, profile={args.profile})")
|
|
86
|
+
req = _req.Request(url, data=body, method="POST",
|
|
87
|
+
headers={"Content-Type": content_type,
|
|
88
|
+
"User-Agent": f"justob/{__version__}"})
|
|
89
|
+
try:
|
|
90
|
+
with _req.urlopen(req, timeout=300) as resp:
|
|
91
|
+
payload = resp.read()
|
|
92
|
+
total = resp.headers.get("x-files-total", "?")
|
|
93
|
+
obf = resp.headers.get("x-files-obfuscated", "?")
|
|
94
|
+
dur = resp.headers.get("x-duration-ms", "?")
|
|
95
|
+
except HTTPError as e:
|
|
96
|
+
print(f"justob: server returned {e.code}: {e.read().decode(errors='replace')}",
|
|
97
|
+
file=sys.stderr); return 3
|
|
98
|
+
except URLError as e:
|
|
99
|
+
print(f"justob: network error: {e}", file=sys.stderr); return 3
|
|
100
|
+
|
|
101
|
+
with open(out_path, "wb") as fh:
|
|
102
|
+
fh.write(payload)
|
|
103
|
+
print(f"✓ wrote {out_path} ({len(payload)/1024:.1f} KB)")
|
|
104
|
+
print(f" files: {obf}/{total} obfuscated · {dur}ms")
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def main() -> int:
|
|
109
|
+
"""Entry point referenced by [project.scripts] in pyproject.toml."""
|
|
110
|
+
return run()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
sys.exit(main())
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: justob
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CLI for the Obfuscate API — seal a ZIP of JS/TS source from your terminal.
|
|
5
|
+
Author: Obfuscate
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://project--36cd11b2-d68a-4f5c-b183-df77d18fac1d.lovable.app
|
|
8
|
+
Project-URL: API, https://project--36cd11b2-d68a-4f5c-b183-df77d18fac1d.lovable.app/api/public/obfuscate
|
|
9
|
+
Keywords: obfuscate,javascript,minify,cli,zip
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
16
|
+
Requires-Python: >=3.8
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# justob
|
|
20
|
+
|
|
21
|
+
CLI for the Obfuscate API — seal a ZIP of JS/TS source from your terminal.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install justob # any system with Python 3.8+
|
|
27
|
+
pkg install python && pip install justob # Termux (Android)
|
|
28
|
+
sudo apt install pipx && pipx install justob # Debian / Ubuntu
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Use
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
justob ./my-project.zip # default: profile=maximum
|
|
35
|
+
justob ./app.zip --profile standard --out sealed.zip
|
|
36
|
+
justob --help
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Flags
|
|
40
|
+
|
|
41
|
+
| Flag | Default | Notes |
|
|
42
|
+
|------|---------|-------|
|
|
43
|
+
| `--profile` | `maximum` | `light` / `standard` / `maximum` |
|
|
44
|
+
| `--strip-console` | `true` | Remove `console.*` calls |
|
|
45
|
+
| `--encrypt-strings` | `true` | Base64-encode string literals |
|
|
46
|
+
| `--include-skipped` | `false` | Repack `node_modules` / `.git` untouched |
|
|
47
|
+
| `--inject-header` | `false` | Prepend a license header |
|
|
48
|
+
| `--header` | `Sealed by Obfuscate` | Header text |
|
|
49
|
+
| `--out` | `<name>.obfuscated.zip` | Output path |
|
|
50
|
+
| `--endpoint` | the hosted site | Override with `$JUSTOB_ENDPOINT` |
|
|
51
|
+
|
|
52
|
+
## Zero dependencies
|
|
53
|
+
|
|
54
|
+
`justob` uses only the Python stdlib (`urllib`, `argparse`). Works on
|
|
55
|
+
Termux, Alpine, minimal Docker images, CI runners — anywhere Python 3.8+
|
|
56
|
+
runs.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
justob/__init__.py,sha256=cMVprk7D0VeZyZfgXwoYY84BnlVGRlIwMkUdPo3IoGc,108
|
|
2
|
+
justob/__main__.py,sha256=x7kVdomkteoKLRuaK4XVXlq3FgD-3nfM_Rp96y0g9Zo,163
|
|
3
|
+
justob/cli.py,sha256=6muNzAfym0-1P82pKgN5bBSOwi_z6vZ1_ZIMSciBRe8,4253
|
|
4
|
+
justob-1.0.0.dist-info/METADATA,sha256=tRIPWxl2_Pg1qGzroAREDrBgeU5xEBNhv7qgnKBlcog,1999
|
|
5
|
+
justob-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
justob-1.0.0.dist-info/entry_points.txt,sha256=QZlSlARLQZDKKiz5XGQNAWfPD2_8qp10gZr4qeFfEss,43
|
|
7
|
+
justob-1.0.0.dist-info/top_level.txt,sha256=50VDY2XHjn2o9GC2q25QAejll1ajJDXwfnJb3OxzQV4,7
|
|
8
|
+
justob-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
justob
|