Metadata-Version: 2.4
Name: hawoltpy
Version: 0.1.0
Summary: Typed async Python SDK for Hawolt live chat
Project-URL: Documentation, https://live.hawolt.com/wiki#chat
Project-URL: Homepage, https://live.hawolt.com
License-Expression: MIT
License-File: LICENSE
Keywords: bot,chat,hawolt,irc,websocket
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Communications :: Chat
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: websockets<17,>=14
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mypy>=1.13; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: ruff>=0.9; extra == 'dev'
Requires-Dist: twine>=6.0; extra == 'dev'
Description-Content-Type: text/markdown

# hawoltpy

`hawoltpy` is a typed, async Python SDK for the
[official Hawolt chat protocol](https://live.hawolt.com/wiki#chat).

It provides:

- WebSocket and TCP transports
- token authentication and read-only guest mode
- automatic IRC registration and `PING`/`PONG`
- typed messages, replies, redactions, notices, members, and roster snapshots
- all documented roles and moderation commands
- outgoing rate limiting and UTF-8-safe 512-byte line handling
- reconnects with capped exponential backoff and jitter
- a decorator-based command bot

## Install

```console
pip install hawoltpy
```

Python 3.10 or newer is required.

Generate a chat token in [Hawolt settings](https://live.hawolt.com/dashboard/settings).
Tokens do not expire, but rotation or account suspension revokes them. Keep the token
secret and load it from an environment variable.

## Receive and send messages

```python
import asyncio
import os

from hawoltpy import ChatClient, ChatMessage, Redaction


async def main() -> None:
    async with ChatClient("streamer", token=os.environ["HAWOLT_TOKEN"]) as chat:
        async for event in chat:
            if isinstance(event, ChatMessage):
                print(f"{event.author}: {event.content}")
                if event.content == "!hello":
                    await chat.reply(event, f"Hello, {event.author}!")
            elif isinstance(event, Redaction):
                print(f"Delete {event.message_id} from local logs")


asyncio.run(main())
```

Hawolt replays the last ten messages and pinned messages after joining. `ChatMessage`
includes the server time when the server supplies a `time` tag.

## Build a bot with commands

```python
import asyncio
import os

from hawoltpy import Bot, CommandContext

bot = Bot("streamer", token=os.environ["HAWOLT_TOKEN"])


@bot.command(aliases=("hi",))
async def hello(ctx: CommandContext) -> None:
    await ctx.reply(f"Hello, {ctx.author}!")


@bot.on_error
async def handle_error(error: Exception, event: object) -> None:
    print(f"handler failed: {error}")


asyncio.run(bot.start())
```

The bot reconnects automatically. Replayed commands with a server timestamp older
than the current connection are ignored by default.

## Read-only guest mode

Omit the token to monitor public chat without sending:

```python
async with ChatClient("streamer") as chat:
    async for event in chat:
        print(event)
```

Hawolt explicitly makes connections without `PASS` read-only. Calling a send or
moderation method in guest mode raises `AuthenticationRequired`.

## Roster and roles

`chat.roster` is updated from `NAMES`, joins, leaves, kicks, nick changes, and role
modes. `Names` contains a stable snapshot. Roles are:

| Prefix | `Role` |
|---|---|
| `%` | `STAFF` |
| `@` | `OWNER` |
| `+` | `MODERATOR` |
| `~` | `VIP` |
| `=` | `UNVERIFIED` |
| `?` | `GUEST` |
| none | `USER` |

`ChatMessage.is_moderator` is true for staff, owners, and moderators.

## Sending and moderation

```python
await chat.send_message("hello")
await chat.reply(message, "threaded reply")
await chat.whisper("alice", "private hello")
await chat.ban("spammer")
await chat.timeout("noisy-user", 10)
await chat.unban("spammer")
await chat.delete("message-id")
await chat.pin("message-id")
await chat.unpin()  # or chat.unpin("message-id")
await chat.mod("helper")
await chat.unmod("helper")
await chat.vip("regular")
await chat.unvip("regular")
```

Hawolt enforces permissions server-side. Bots only receive moderator rank after the
channel owner runs `.mod <bot-name>`.


## Reconnect and low-level access

Use `ClientConfig` to change rate and reconnect behavior:

```python
from hawoltpy import ChatClient, ClientConfig

config = ClientConfig(
    channel="streamer",
    token="...",
    message_rate=2.0,
    reconnect=True,
    reconnect_min_delay=1,
    reconnect_max_delay=60,
)
chat = ChatClient(config=config)
await chat.run_forever(handle_event)
```

Unrecognized server lines are preserved as `RawEvent`. For protocol-level use,
`parse_irc`, `IRCMessage`, `WebSocketTransport`, and `TCPTransport` are public.

## Development

See the [API guide](docs/api.md) for the public surface and

## Protocol scope

`hawoltpy` implements the public chat contract documented by Hawolt. It does not
scrape the website or expose undocumented account, stream, or viewer APIs.
