Metadata-Version: 2.4
Name: hg_router
Version: 0.0.2
Summary: Add your description here
Requires-Python: >=3.14
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pytest>=9.1.1
Requires-Dist: requests>=2.32
Dynamic: license-file

# hg-router

A small client for the web UI of a Huawei HG-series residential
gateway/ONT (the box at `192.168.100.1`) — login, connected-device list, and
MAC filtering (block/unblock a device's internet access).

This is **not** a HiLink Mobile WiFi device, so the `huaweisms` package
(which targets `/api/webserver/...` JSON/XML endpoints) doesn't work
against it. Everything here was reverse-engineered from the router's own
JavaScript (view-source on its login/config pages) — it replicates real
browser form submissions instead of using any documented API, because
there isn't one.

## Why this exists

The router's admin UI is the only way to see connected devices or manage
MAC filtering, and clicking through it by hand gets old. This scripts it.

The UI also has no clear way to actually remove a device from its list —
blocking it via MAC filtering is the closest equivalent, which is what
`block_device_by_name` / `unblock_device_by_name` do here.

## Install

`hg_router` is a regular Python package (see `pyproject.toml`), so any of
these work:

```bash
uv sync                       # dev setup -- creates .venv, installs into it
uv add /path/to/hg-router-script   # add this checkout as a dependency of another uv project
pip install /path/to/hg-router-script  # or plain pip, same idea
```

## Credentials

`HGRouter.login()` takes the router's admin username, password, and IP
address as plain arguments -- the package never reads them from the
environment, a file, or anywhere else, and never hardcodes them. It's up to
the caller to source them however they like (a secrets manager, a config
file, an env var you read yourself, ...) and pass them straight into
`login()`.

## Usage

```python
from hg_router import HGRouter

router = HGRouter()
router.login("root", "your-password", "192.168.100.1")
# router.login("root", "your-password", "192.168.100.1", use_https=True)  # if served over https

# List every device the router currently knows about
for d in router.get_connected_devices():
    print(d.name, d.ip, d.mac, d.status, d.interface)

# Block a device by name (looked up from the device list above)
router.set_mac_filter(enabled=True, mode="blacklist")  # one-time setup
router.block_device_by_name("Keshawn-s-tablet")

# ...and let it back on
router.unblock_device_by_name("Keshawn-s-tablet")
```

## Layout

```
hg_router/
  model.py              HGRouter — public facade: login, devices, MAC filtering
  client.py              HGRouterClient — session + login()
  devices.py               DeviceListMixin — get_connected_devices(), find_device_by_name()
  mac_filter.py               MacFilterMixin — get/set filter, block/unblock a device
  parsing.py                     shared page-scraping helpers (\xHH decoding, token extraction)
  errors.py                         HGLoginError
```

`HGRouter` wraps an `HGRouterClient` (itself `DeviceListMixin +
MacFilterMixin` composed together in `client.py`) and delegates to it; it's
the only entry point `hg_router/__init__.py` expects callers to construct
directly.

See [docs/HOW-IT-WORKS.md](docs/HOW-IT-WORKS.md) for how the client talks to the
router (the actual request sequences) and its verification status.

## Security note

Login is plain HTTP by default (see `use_https` above), and either way the
password is only base64-obscured (not hashed or encrypted) client-side,
matching what the router's own login page does. Don't run this against the
router over an untrusted network, and don't hardcode credentials in source
— pass them into `login()` from wherever you keep secrets.

