lithium-web 1.0b1__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.
Files changed (38) hide show
  1. lithium_web-1.0b1/LICENSE +22 -0
  2. lithium_web-1.0b1/MANIFEST.in +4 -0
  3. lithium_web-1.0b1/PKG-INFO +418 -0
  4. lithium_web-1.0b1/README.md +397 -0
  5. lithium_web-1.0b1/lithium/__init__.py +8 -0
  6. lithium_web-1.0b1/lithium/bin/.lithium/fingerprints/canvas-noise-a.json +25 -0
  7. lithium_web-1.0b1/lithium/bin/.lithium/fingerprints/canvas-noise-b.json +26 -0
  8. lithium_web-1.0b1/lithium/bin/.lithium/fingerprints/conc-tab-924755-0.json +26 -0
  9. lithium_web-1.0b1/lithium/bin/.lithium/fingerprints/conc-tab-924755-1.json +25 -0
  10. lithium_web-1.0b1/lithium/bin/.lithium/fingerprints/conc-tab-924755-2.json +26 -0
  11. lithium_web-1.0b1/lithium/bin/.lithium/fingerprints/conc-tab-924755-3.json +25 -0
  12. lithium_web-1.0b1/lithium/bin/.lithium/fingerprints/one-off-override.json +26 -0
  13. lithium_web-1.0b1/lithium/bin/.lithium/fingerprints/persist-check-924755.json +26 -0
  14. lithium_web-1.0b1/lithium/bin/.lithium/fingerprints/separation-924755.json +26 -0
  15. lithium_web-1.0b1/lithium/bin/lithium-runtime-darwin-amd64 +0 -0
  16. lithium_web-1.0b1/lithium/bin/lithium-runtime-darwin-arm64 +0 -0
  17. lithium_web-1.0b1/lithium/bin/lithium-runtime-linux-amd64 +0 -0
  18. lithium_web-1.0b1/lithium/bin/lithium-runtime-linux-arm64 +0 -0
  19. lithium_web-1.0b1/lithium/bin/lithium-runtime-windows-amd64.exe +0 -0
  20. lithium_web-1.0b1/lithium/client.py +380 -0
  21. lithium_web-1.0b1/lithium/diagnostics.py +214 -0
  22. lithium_web-1.0b1/lithium/dom.py +38 -0
  23. lithium_web-1.0b1/lithium/element.py +116 -0
  24. lithium_web-1.0b1/lithium/response.py +237 -0
  25. lithium_web-1.0b1/lithium/session.py +13 -0
  26. lithium_web-1.0b1/lithium/transport/__init__.py +3 -0
  27. lithium_web-1.0b1/lithium/transport/connection.py +74 -0
  28. lithium_web-1.0b1/lithium/transport/process.py +112 -0
  29. lithium_web-1.0b1/lithium/transport/protocol.py +33 -0
  30. lithium_web-1.0b1/lithium/utils/__init__.py +3 -0
  31. lithium_web-1.0b1/lithium/utils/errors.py +13 -0
  32. lithium_web-1.0b1/lithium_web.egg-info/PKG-INFO +418 -0
  33. lithium_web-1.0b1/lithium_web.egg-info/SOURCES.txt +36 -0
  34. lithium_web-1.0b1/lithium_web.egg-info/dependency_links.txt +1 -0
  35. lithium_web-1.0b1/lithium_web.egg-info/requires.txt +1 -0
  36. lithium_web-1.0b1/lithium_web.egg-info/top_level.txt +1 -0
  37. lithium_web-1.0b1/pyproject.toml +53 -0
  38. lithium_web-1.0b1/setup.cfg +4 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OfficialDex
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.
22
+
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include lithium *.py
4
+ recursive-include lithium/bin *
@@ -0,0 +1,418 @@
1
+ Metadata-Version: 2.4
2
+ Name: lithium-web
3
+ Version: 1.0b1
4
+ Summary: An HTTP-first, stealth web runtime for Python — the simplicity of requests + the interaction model of Selenium.
5
+ Author: lithium-web contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/OfficialDex/lithium-web
8
+ Project-URL: Documentation, https://github.com/OfficialDex/lithium-web#readme
9
+ Keywords: http,http-client,web-scraping,undetected,browser-runtime,requests,selenium,fingerprint,curl,primp
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Go
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: msgpack>=1.0.0
20
+ Dynamic: license-file
21
+
22
+ # Lithium
23
+ # Version – 1.0b1
24
+
25
+ **An HTTP-first, high stealth web runtime for Python.**
26
+ The simplicity of `requests` + the interaction model of Selenium, backed by a tiny native Go runtime — no browser to install, no drivers, no config to get going.
27
+
28
+ ```python
29
+ from lithium import Client
30
+
31
+ client = Client() # zero-config, undetected out of the box
32
+ page = client.get("https://example.com/login")
33
+
34
+ page.fill("#username", "alice")
35
+ page.fill("#password", "secret")
36
+ page.click("button[type=submit]")
37
+ page.wait_for("#dashboard")
38
+
39
+ print(page.text)
40
+ page.screenshot("dashboard.png")
41
+ ```
42
+
43
+ > Fun fact #1: the name comes from lithium — the lightest solid element — because the whole runtime binary is only ~19 MB.
44
+ > Fun fact #2: Python talks to the Go runtime over a Unix socket using MessagePack. No browser process, no WebDriver, no Chromium. Just a runtime.
45
+ > Fun fact #3: by default the runtime speaks a Chrome TLS (JA3) fingerprint over uTLS, so `Client()` is already impersonating a real browser — you don't have to turn anything on.
46
+
47
+ ---
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install lithium-web
53
+ ```
54
+
55
+ That's it.
56
+
57
+ ---
58
+
59
+ ## Quickstart — major features, individually
60
+
61
+ ### Requests-style HTTP
62
+
63
+ ```python
64
+ from lithium import Client
65
+
66
+ client = Client()
67
+ r = client.get("https://api.example.com/users?limit=10")
68
+ print(r.status_code) # 200
69
+ print(r.ok) # True
70
+ print(r.json()) # parsed body
71
+
72
+ # POST with JSON
73
+ r = client.post("https://api.example.com/users", json={"name": "Ada"})
74
+
75
+ # POST with a form body (auto urlencoded)
76
+ r = client.post("https://httpbin.org/post", data={"q": "lithium"})
77
+
78
+ # POST multipart upload
79
+ r = client.post("https://httpbin.org/post",
80
+ data={"note": "hi"},
81
+ files={"file": ("report.pdf", b"%PDF-1.4 ...")})
82
+ ```
83
+
84
+ ### Selenium-style page interaction
85
+
86
+ ```python
87
+ from lithium import Client
88
+
89
+ page = client.get("https://example.com/login")
90
+ page.fill("#username", "alice")
91
+ page.fill("#password", "secret")
92
+ page.click("button[type=submit]") # navigates; `page` updates in place
93
+ page.wait_for("#dashboard") # waits for the element to appear
94
+ print(page.text) # text of the current page
95
+ ```
96
+
97
+ ### Execute JavaScript
98
+
99
+ ```python
100
+ page = client.get("https://example.com")
101
+ page.execute_script("document.body.dataset.loaded = 'true'")
102
+ print(page.dom.query("body").get_attr("data-loaded")) # 'true'
103
+ ```
104
+
105
+ ### Query the DOM
106
+
107
+ ```python
108
+ page = client.get("https://example.com/products")
109
+ items = page.dom.query_all(".product")
110
+ for el in items:
111
+ print(el.text, el.get_attr("data-price"))
112
+ ```
113
+
114
+ ### Screenshots
115
+
116
+ ```python
117
+ page = client.get("https://example.com")
118
+ page.screenshot("page.png") # writes a PNG, returns the path
119
+ png_bytes = page.screenshot() # or get raw bytes
120
+ ```
121
+
122
+ ### Async client
123
+
124
+ ```python
125
+ import asyncio
126
+ from lithium import AsyncClient
127
+
128
+ async def main():
129
+ async with AsyncClient() as client:
130
+ r = await client.get("https://example.com")
131
+ print(r.status_code)
132
+
133
+ asyncio.run(main())
134
+ ```
135
+
136
+ ### Streaming downloads
137
+
138
+ ```python
139
+ client = Client()
140
+ client.stream_to_file("https://example.com/bigfile.bin", "bigfile.bin")
141
+ ```
142
+
143
+ ### Proxies & rotating proxies
144
+
145
+ ```python
146
+ # single proxy
147
+ client = Client(proxy="socks5://user:pass@host:1080")
148
+
149
+ # rotating proxy pool (round-robin per request)
150
+ client = Client(proxies=[
151
+ "socks5://u:p@proxy1:1080",
152
+ "socks5://u:p@proxy2:1080",
153
+ ])
154
+ ```
155
+
156
+ ### Custom CA & mutual TLS
157
+
158
+ ```python
159
+ client = Client(
160
+ ca_cert="/path/to/ca.pem",
161
+ client_cert="/path/to/client.crt",
162
+ client_key="/path/to/client.key",
163
+ )
164
+ ```
165
+
166
+ ### Geo / locale auto-detection
167
+
168
+ ```python
169
+ client = Client(auto_location=True) # timezone + currency + country
170
+ r = client.get("https://example.com")
171
+ print(r.timezone, r.currency, r.country_code)
172
+ ```
173
+
174
+ ### Network inspection
175
+
176
+ ```python
177
+ page = client.get("https://example.com")
178
+ for req in page.network.requests: # every HTTP + in-page fetch/XHR
179
+ print(req.method, req.status, req.url)
180
+ ```
181
+
182
+ ### Custom browser profile / fingerprint
183
+
184
+ ```python
185
+ # pick a coherent identity (UA + JA3 + navigator + GPU all agree)
186
+ client = Client(platform="windows", browser="chrome", fingerprint="my-identity")
187
+ ```
188
+
189
+ ---
190
+
191
+ ## One script that uses everything
192
+
193
+ ```python
194
+ import asyncio, json
195
+ from lithium import Client, AsyncClient
196
+
197
+ # 1. Requests-style client, undetected by default
198
+ client = Client(proxies=["socks5://u:p@h1:1080", "socks5://u:p@h2:1080"])
199
+ client.get("https://example.com/set-session")
200
+
201
+ # 2. A page we interact with, like Selenium
202
+ page = client.get("https://example.com/login")
203
+ page.fill("#username", "alice")
204
+ page.fill("#password", "secret")
205
+ page.check("#terms")
206
+ page.click("button[type=submit]")
207
+ page.wait_for("#dashboard")
208
+ print("landed:", page.status_code)
209
+
210
+ # 3. DOM + JS
211
+ rows = page.dom.query_all("table tbody tr")
212
+ page.execute_script("document.title = 'hello from js'")
213
+
214
+ # 4. Run JS that does real network (in-page fetch)
215
+ page.execute_script(
216
+ "fetch('/api/data').then(r=>r.json())"
217
+ ".then(d=>document.body.setAttribute('data-h', d.hello));"
218
+ )
219
+
220
+ # 5. Rich media + screenshot
221
+ print("media:", page.media) # discovered video/audio/img sources
222
+ page.screenshot("dashboard.png")
223
+
224
+ # 6. Inspect every request the session made (HTTP + JS-driven)
225
+ for req in page.network.requests:
226
+ print(" net:", req.method, req.status, req.url)
227
+
228
+ # 7. Large download, streamed to disk
229
+ client.stream_to_file("https://example.com/db.sqlite", "db.sqlite")
230
+
231
+ # 8. Plain HTTP / JSON when you don't need a page
232
+ r = client.post("https://example.com/api", json={"a": 1})
233
+ print(r.json())
234
+
235
+ # 9. Async when you need concurrency
236
+ async def main():
237
+ async with AsyncClient() as ac:
238
+ results = await asyncio.gather(*[ac.get("https://example.com") for _ in range(5)])
239
+ print([r.status_code for r in results])
240
+ asyncio.run(main())
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Config reference
246
+
247
+ All options work as **direct keyword args** to `Client(...)` or via a `Config`/JSON file.
248
+
249
+ | Option | Default | What it does |
250
+ |---|---|---|
251
+ | `profile` | `"chrome-windows"` | Coherent browser identity (UA + headers + JA3 + navigator). |
252
+ | `fingerprint` | `None` | Persistent identity name; same name = same profile across runs. |
253
+ | `platform` | `None` | Force `windows` / `macos` / `linux` / `android` / `ios`. |
254
+ | `browser` | `None` | Force `chrome` / `firefox` / `opera` / `brave`. |
255
+ | `proxy` | `None` | Single proxy URL (http/https/socks4/socks4a/socks5/socks5h). |
256
+ | `proxies` | `None` | List of proxy URLs, rotated round-robin per request. |
257
+ | `verify_ssl` | `True` | Verify server certificates. |
258
+ | `ca_cert`, `client_cert`, `client_key` | `None` | Custom CA bundle and client certificate (mTLS). |
259
+ | `trust_env` | `True` | Respect `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` env vars. |
260
+ | `tls_spoof` | `None` | Explicitly enable/disable TLS (JA3) impersonation. |
261
+ | `http3` | `False` | Try HTTP/3 (QUIC) with graceful h1/h2 fallback. |
262
+ | `execute_scripts` | `False` | Auto-run inline `<script>` on page load. |
263
+ | `js_timeout` | `10.0` | Max seconds a JS call may run. |
264
+ | `timeout` | `30.0` | Default request timeout (seconds). |
265
+ | `max_redirects` | `0` | Redirect limit (0=Go default 10, `-1`=don't follow). |
266
+ | `max_idle_conns`, `max_idle_conns_per_host` | `100`, `10` | HTTP connection-pool sizing. |
267
+ | `viewport_width`, `viewport_height` | platform default | Screen/inner size (`screen`, `innerWidth`, `matchMedia`). |
268
+ | `device_scale_factor` | platform default | `devicePixelRatio` (mobile auto-sets to 2). |
269
+ | `screen_color_depth` | `24` | `screen.colorDepth` / `pixelDepth`. |
270
+ | `hardware_concurrency`, `device_memory` | from fingerprint | `navigator.hardwareConcurrency` / `.deviceMemory`. |
271
+ | `max_touch_points` | `0` | `navigator.maxTouchPoints`. |
272
+ | `do_not_track` | `None` | `navigator.doNotTrack`. |
273
+ | `audio_sample_rate` | `44100` | `AudioContext().sampleRate`. |
274
+ | `screenshot_width`, `screenshot_height` | `1280`, `800` | PNG size for `page.screenshot()`. |
275
+ | `virtual_gpu` | `True` | Enable virtual WebGL context. |
276
+ | `canvas_noise` | `True` | Make `toDataURL` vary per identity. |
277
+ | `auto_location`, `auto_timezone`, `auto_currency`, `auto_language` | `None` | Auto-detect locale from your IP (via `geo_url`). |
278
+ | `geo_url` | ip-api.com | Custom geo lookup endpoint. |
279
+ | `storage_file` | `None` | Persist `localStorage` across runs/sessions. |
280
+ | `socket_timeout` | `None` | Seconds to wait for a reply from the runtime. |
281
+ | `shared` | `True` | Reuse one runtime process across clients. |
282
+ | `binary` | auto | Path to the runtime binary (override). |
283
+ | `verbose` | `False` | Print go-side logs to stderr. |
284
+ | `solution` | `False` | Attach live DNS/TCP/TLS diagnostics to errors. |
285
+
286
+ ---
287
+
288
+ ## Why lithium (comparison)
289
+
290
+ | Capability | **lithium** | primp | tls_client | curl_cffi |
291
+ |---|:---:|:---:|:---:|:---:|
292
+ | TLS (JA3) impersonation | ✅ | ✅ | ✅ | ✅ |
293
+ | Coherent multi-layer identity (UA↔headers↔navigator↔GPU) | ✅ | partial | partial | partial |
294
+ | **Selenium-style interaction** (`click`/`fill`/`wait_for`/`select`) | ✅ | ❌ | ❌ | ❌ |
295
+ | **Full DOM querying** (`query`/`query_all`) | ✅ | ❌ | ❌ | ❌ |
296
+ | **JavaScript execution** | ✅ | ❌ | ❌ | ❌ |
297
+ | **In-page `fetch()`/XHR → real HTTP** | ✅ | ❌ | ❌ | ❌ |
298
+ | **`localStorage`/`indexedDB`/`WebSocket`/`WebCrypto`** | ✅ | ❌ | ❌ | ❌ |
299
+ | **Screenshots** | ✅ | ❌ | ❌ | ❌ |
300
+ | **Network inspection** (`page.network.requests`) | ✅ | ❌ | ❌ | ❌ |
301
+ | **Zero-config browser identity** (no libs/browser install) | ✅ | ❌ | ❌ | ❌ |
302
+ | Async | ✅ | ✅ | ✅ | ✅ |
303
+ | HTTP/2 fingerprint (Chrome SETTINGS) | partial | ✅ | ✅ | ✅ |
304
+ | HTTP/3 | opt-in | ✅ | ✅ | ✅ |
305
+ | Streaming downloads | ✅ | ✅ | ✅ | ✅ |
306
+ | Proxy rotation / mTLS / CA | ✅ | ✅ | ✅ | ✅ |
307
+
308
+ ---
309
+
310
+ ## High stealth: Sannysoft proof
311
+
312
+ The runtime drives the **official `bot.sannysoft.com`** page and reads its **own detection table**: **8 / 8 passed, 0 failed**. Run it yourself:
313
+
314
+ ```python
315
+ import json, re
316
+ from lithium import Client
317
+
318
+ with Client(execute_scripts=False, browser="chrome", platform="windows") as client:
319
+ page = client.get("https://bot.sannysoft.com/")
320
+ html = page.text
321
+
322
+ # the site's own detection needs lodash (it uses _.has on navigator.webdriver)
323
+ page.execute_script(
324
+ client.get("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js", timeout=15).text
325
+ )
326
+
327
+ # run the site's OWN detection script (fills the real main result table)
328
+ for s in re.findall(r'<script(?![^>]*src)[^>]*>(.*?)</script>', html, re.S):
329
+ if "runBotDetection" in s:
330
+ try:
331
+ page.execute_script(s) # the site's real detection code
332
+ except Exception:
333
+ pass # harmless canvas-hash noise; table already filled
334
+ break
335
+
336
+ counts = json.loads(page.execute_script(
337
+ "JSON.stringify({passed: document.querySelectorAll('.result.passed').length,"
338
+ " failed: document.querySelectorAll('.result.failed').length})"))
339
+ print(f"OFFICIAL sannysoft: {counts['passed']} passed, {counts['failed']} failed")
340
+ ```
341
+
342
+ Output: `OFFICIAL sannysoft: 8 passed, 0 failed`.
343
+
344
+ > Proof of undetected: the default `Client()` also speaks a Chrome **JA3** (e.g. `771,4865-4866-4867-49195-49199-...`) verified against `https://tls.peet.ws/api/all`, and the JA3 version matches the UA version.
345
+
346
+ ---
347
+
348
+ ## Every feature at a glance
349
+
350
+ **HTTP**
351
+ - `get` / `post` / `put` / `patch` / `delete` / `head` / `options`
352
+ - Bodies: plain bytes/str, JSON (`json=`), urlencoded (`data=dict`), multipart (`files=`)
353
+ - Cookies persisted per session automatically
354
+ - Redirect policy (`max_redirects`)
355
+ - Rotating proxy pools (`proxies=[...]`)
356
+ - Connection pooling tuning (`max_idle_conns*`)
357
+ - Streaming downloads (`stream_to_file`)
358
+ - Custom CA + mTLS (`ca_cert` / `client_cert` / `client_key`)
359
+ - Geo / locale auto-detect (`auto_location` + friends, custom `geo_url`)
360
+ - Network inspection (`client.network`, `page.network.requests`)
361
+ - Async client (`AsyncClient`)
362
+ - HTTP trailers (`resp.trailers`)
363
+
364
+ **Browser runtime**
365
+ - DOM: `page.dom.query` / `query_all`, attributes, text, html, events
366
+ - Interaction: `fill`, `type`, `click`, `submit`, `select_option`, `check`, `uncheck`, `clear`, `wait_for`, `goto`
367
+ - JavaScript: `page.execute_script`, auto-run inline scripts
368
+ - In-page `fetch()` / `XMLHttpRequest` → real HTTP
369
+ - Storage: `localStorage`, `sessionStorage` (optionally persisted)
370
+ - APIs: `WebCrypto`, `MutationObserver`, `History`/`location`, `WebSocket`, `IndexedDB`, `matchMedia`, `getComputedStyle`, `performance`, `permissions`, `getBattery`
371
+ - Virtual graphics: Canvas 2D, WebGL, `AudioContext`
372
+ - Screenshots (`page.screenshot`)
373
+ - Media discovery + download (`page.media`, `download_media`)
374
+ - Coherent fingerprints (UA ↔ JA3 ↔ `navigator` ↔ GPU ↔ screen)
375
+
376
+ ---
377
+
378
+ ## API reference — unique functions
379
+
380
+ **`Client`**
381
+ - `client.get/post/put/patch/delete/head/options(...)`
382
+ - `client.download(url, path)` — fetch and write to disk
383
+ - `client.stream_to_file(url, path)` — stream response straight to disk
384
+ - `client.network` — `NetworkLog` of every request (iterable, `.requests`, `.responses`)
385
+ - `client.close()` / context manager
386
+
387
+ **`Response` (a.k.a. the page)**
388
+ - `status_code`, `headers`, `trailers`, `url`, `elapsed_ms`, `ok`, `text`, `json()`, `content`
389
+ - `timezone`, `currency`, `country_code` (when auto-location on)
390
+ - `dom` → query/query_all/wait_for
391
+ - `execute_script(js)`
392
+ - `fill`, `type`, `click`, `submit`, `select_option`, `check`, `uncheck`, `clear`
393
+ - `wait_for(selector, timeout=)`, `goto(url)` (both update the page in place)
394
+ - `screenshot(path=None)` → writes/returns PNG
395
+ - `network` → `NetworkLog`
396
+ - `media` → discovered `<video>/<audio>/<img>` sources
397
+ - `download(url, path)`, `download_media(dir, kinds=None)`
398
+ - `stream(chunk_size=)` — chunked iterator
399
+
400
+ **`Element`**
401
+ - `text`, `html`, `get_attr(name, default=None)`, `set_attr(name, value)`, `remove()`
402
+ - `add_event_listener(event, cb)`, `dispatch_event(event, detail=None)`
403
+ - `click()`, `fill(v)`, `type(text, delay=)`, `select_option(v)`, `clear()`, `check()`, `uncheck()`, `submit()`
404
+
405
+ **`AsyncClient`**
406
+ - `await` versions of `get/post/put/patch/delete/head/options/stream_to_file/download`, plus `network`.
407
+
408
+ ---
409
+
410
+ # Made by Blaze, available on [discord](https://discord.com/users/1238444724386533417) & [github](https://github.com/OfficialDex)
411
+ feel free to contact me regarding any issues, suggestions or queries
412
+
413
+ ## License & note
414
+
415
+ `lithium-web` is under active development. The name, PyPI, and trademark availability should be confirmed before any public release, since similarly-named projects already exist.
416
+
417
+
418
+