Metadata-Version: 2.4
Name: luaprot
Version: 2.0.1
Summary: The official LuaProt API client for Python.
Author: LuaProt
License-Expression: ISC
Project-URL: Homepage, https://luaprot.net
Keywords: luaprot,roblox,whitelist,api
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Operating System :: OS Independent
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=6; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Dynamic: license-file

# LuaProt

The official LuaProt API client for Python 3.10 and newer. It uses Python's standard library and has no runtime dependencies.

```sh
pip install luaprot
```

```python
import os
from luaprot import LuaProt

lua_prot = LuaProt(api_key=os.environ["LUAPROT_API_KEY"])
result = lua_prot.whitelist("123456789012345678", "hub-id")
print(result["key"]["key"])

status = lua_prot.get_status()
print(status["validToken"])
```

The npm-style constructor and method names are also supported:

```python
luaProt = LuaProt({"apiKey": os.environ["LUAPROT_API_KEY"]})
status = luaProt.getStatus()
```

Methods are synchronous. In an async application, call them with `await asyncio.to_thread(lua_prot.get_status)` to avoid blocking the event loop.

Run the client in a trusted server application. Your API key has account-level access; your application is responsible for authenticating callers and checking their permissions.

## Configuration

Pass an API key as `api_key` with optional keyword arguments, or a single dictionary with the corresponding npm names:

| Python option | Dictionary/npm name | Default |
| --- | --- | --- |
| `api_key` | `apiKey` | Required |
| `base_url` | `baseUrl` | `https://luaprot.net` |
| `timeout` | `timeout` | `30000` milliseconds |
| `discord_id` | `discordId` | No default caller |
| `script_url` | `scriptUrl` | No default script URL |
| `scripts` | `scripts` | `{}` |
| `block_advertise_keys` | `blockAdvertiseKeys` | `False` |

`base_url` may contain a path prefix. Python's timeout applies to blocking socket operations; JavaScript's timeout covers the complete request. `discord_id` supplies the caller for `info()`, `redeem_key()`, and `get_script()` when it is omitted.

## Commands

The methods use the Discord bot's command names and argument order. Python uses `snake_case`; every matching `camelCase` npm name is also available. IDs must be strings. Optional arguments can be omitted, passed by keyword, or skipped with `None`.

| Discord command | Python method and arguments |
| --- | --- |
| `/api-status` | `api_status()`; also `get_status()` / `getStatus()` |
| `/whitelist` | `whitelist(user, hub=None, days=0, hours=0, minutes=0, note=None)` |
| `/remove-whitelist` | `remove_whitelist(user)` |
| `/mass-whitelist` | `mass_whitelist(users, hub, time=None, note=None, limited_scripts=None)` |
| `/generate-keys` | `generate_keys(hub, amount, time=None, note=None, limited_scripts=None)` |
| `/bulk-delete-keys` | `bulk_delete_keys(keys)` |
| `/info` | `info(user=None, key=None)` |
| `/redeem-key` | `redeem_key(key, user=None)` |
| `/get-script` | `get_script(script=None, user=None)` |
| `/reset-hwid` | `reset_hwid(user)` |
| `/freeze-key` | `freeze_key(query)` |
| `/unfreeze-key` | `unfreeze_key(query)` |
| `/set-note` | `set_note(query, note)` |
| `/compensate` | `compensate(query, time, include_advertise=False)` |
| `/compensate-all` | `compensate_all(time, include_advertise=False)` |
| `/get-keys` | `get_keys(hub=None, page=1, key=None, user=None, hwid=None, note=None, state=None)` |
| `/blacklist` | `blacklist(user=None, hwid=None, key=None, hub=None, reason="No reason provided.")` |
| `/remove-blacklist` | `remove_blacklist(user=None, hwid=None, key=None, hub=None, id=None)` |
| `/get-blacklists` | `get_blacklists(hub, hwid=None)` |
| `/clear-blacklists` | `clear_blacklists(hub)` |
| `/get-sessions` | `get_sessions(key=None, user=None, hub=None)` |
| `/session-info` | `session_info(session_id)` |
| `/message-session` | `message_session(session_id, message)` |
| `/disconnect-session` | `disconnect_session(session_id)` |
| `/message-sessions` | `message_sessions(message, key=None, hub=None)` |
| `/disconnect-sessions` | `disconnect_sessions(key=None, hub=None)` |
| `/storage list\|get\|create\|update\|delete` | `storage(action, hub, options=None)` |

`get_hubs()` lists accessible hubs. `get_stats(hub=None)` fetches the statistics used by the bot's statistics channels.

The client performs LuaProt operations. It does not assign Discord roles, send DMs/webhooks, or create channels. `/setup`, `/send-panel`, `/auto-role-update`, `/booster-config`, and `/stats-channels` remain Discord configuration commands. Mass whitelisting accepts the role members' Discord IDs instead of a Discord role object. Methods that normally use the invoking Discord user accept an explicit user ID or use `discord_id`.

### Keys and durations

- Whitelisting defaults to the first accessible hub. No duration means no expiry. Days, hours and minutes are whole numbers.
- Key generation accepts 5–300 keys. Its `time` and mass whitelisting's `time` are hours, at least 1, including fractional hours. Omit time for no expiry.
- Compensation accepts `2h`, `1d 6h`, `30m`, `1w`, and combinations of `y`, `mo`, `w`, `d`, `h`, `m`/`min`, `s`. The minimum is one minute. Advertisement keys are excluded unless requested.
- A `query` accepts a key, Discord ID, mention, `key:...`, or `id:...`. Prefix numeric keys with `key:`.
- `info()` accepts either `user` or `key`; the default caller is ignored when `key` is supplied.
- Notes are limited to 100 characters; blacklist reasons to 60. List arguments accept lists or comma/whitespace-separated strings and remove duplicates.
- Key states are `active`, `expired`, `blacklisted`, or `unassigned`.
- Blacklisting accepts exactly one user/HWID/key target. A missing hub applies to all matching accessible hubs. Removing a blacklist also accepts `hubId|type:entryId` (`key`, `local`, or `global`).
- Session messages are limited to 100 characters. Bulk session actions require either a key or a hub. Session lookup accepts a hub or a key/user query.

```python
lua_prot.whitelist("123456789012345678", "hub-id", days=7)
lua_prot.generate_keys("hub-id", 10, time=24, note="Trial")
lua_prot.compensate("id:123456789012345678", "1d 6h")
lua_prot.message_sessions("Update available", hub="hub-id")
```

### Storage

Storage takes an options dictionary, with the same camelCase keys in both packages: `valueId`, `key`, `hwid`, `id`, `value`, `type`, `originalValueId`, `originalKey`, `originalHwid`.

```python
lua_prot.storage("create", "hub-id", {
    "valueId": "autoFarm",
    "key": "license-key",
    "value": False,
})
entry = lua_prot.storage("get", "hub-id", {
    "valueId": "autoFarm",
    "key": "license-key",
})
```

`valueId` is required except for `list`. Exact reads and writes require a key or HWID; free-for-all storage uses `"key": "x"` with an HWID. Deletion can use an internal `id`. Updates default the original target to the destination; pass the `original*` fields to move an entry.

Values can be strings, numbers, or booleans. Native values retain their types, including `False`, `0`, and `""`. Text can be converted with `type: "string"`, `"number"`, or `"boolean"`. Strings are limited to 4,000 characters. The exported `StorageOptions` TypedDict and `py.typed` marker support type checkers.

### Scripts

Named scripts replace the bot's saved panel configuration. Names are case-insensitive.

```python
lua_prot = LuaProt(
    api_key=os.environ["LUAPROT_API_KEY"],
    scripts={
        "Example": {"hubId": "hub-id", "scriptUrl": "https://example.com/loader.lua"},
        "Inline": {"hubId": "hub-id", "script": 'lp_key="{key}";\nprint("ready")'},
    },
)
result = lua_prot.get_script("Example", "123456789012345678")
print(result["script"])
```

The result contains `success`, `hubId`, `key`, and `script`. Inline scripts substitute `{key}` or receive an `lp_key` prefix. Loader text is never executed. Without a script name, the method uses `script_url` and the user's first matching key.

### Results and errors

Single requests return parsed API dictionaries directly. HTTP, API, timeout and network failures raise `LuaProtError`; invalid arguments raise `TypeError`.

```python
from luaprot import LuaProtError

try:
    lua_prot.get_status()
except LuaProtError as error:
    print(str(error), error.status)
```

Errors contain `status` (0 for transport/client errors) and `response` (API JSON when available). The historical `succcess` spelling is normalized to `success`. Requests are not retried automatically, and redirects are not followed.

Multi-hub changes return `{"success": ..., "results": [...]}`. Each result contains `hubId`, `success`, and either `data` or `error`. Overall success is false if any hub fails. These operations are not transactional: inspect individual results before retrying. Lookup failures stop the operation before writes begin.

## Development and packaging

```sh
python -m pip install -e ".[dev]"
python -m unittest discover -s tests -v
python -m mypy luaprot
python -m build
python -m twine check dist/*
```

Tests use a local HTTP server, never a live LuaProt account. Keep `tests/contracts.json` identical to `npm-package/test/contracts.json` when updating either client. Both packages expose the same camelCase methods and API behavior.

Publishing uses a PyPI API token rather than your account password. After reviewing the built files, run `python -m twine upload dist/*` and enter the token at its hidden prompt. Keep the token out of source files. Version 2 replaces the old placeholder package with the client class.
