Metadata-Version: 2.4
Name: lantalk
Version: 2.0.0
Summary: Terminal-based LAN chat — async, discoverable, secure
Author-email: Ezra Destaw <ezradestaw@email.com>
Project-URL: Homepage, https://github.com/Ezradestaw/lantalk
Project-URL: Repository, https://github.com/Ezradestaw/lantalk
Project-URL: Issues, https://github.com/Ezradestaw/lantalk/issues
Keywords: chat,lan,wifi,terminal,networking,broadcast,asyncio
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
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: Topic :: Communications :: Chat
Classifier: Topic :: Internet
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENCE
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Dynamic: license-file

# LanTalk

> Terminal-based LAN chat — pure asyncio, auto-discoverable, zero runtime dependencies.

```
  _                 _____     _ _
 | |               |_   _|   | | |
 | |     __ _ _ __  | | __ _| | | __
 | |    / _` | '_ \ | |/ _` | | |/ /
 | |___| (_| | | | || | (_| | |   <
 |______\__,_|_| |_\_/ \__,_|_|_|\_\
```

[![PyPI version](https://img.shields.io/pypi/v/lantalk)](https://pypi.org/project/lantalk/)
[![Python](https://img.shields.io/pypi/pyversions/lantalk)](https://pypi.org/project/lantalk/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

---

## What is LanTalk?

LanTalk lets people on the **same Wi-Fi or LAN** chat instantly — no accounts, no internet, no servers in the cloud. One person runs `lantalk` and starts a server; everyone else runs `lantalk` and joins. That's it.

- Works on school networks, home networks, offices, hackathons
- Pure Python stdlib — nothing to `pip install` beyond the package itself
- Clients auto-discover the server via UDP broadcast (no IP typing required)

---

## Features

| | |
|---|---|
| 🔍 **Auto-discovery** | UDP beacon finds servers on your LAN automatically |
| 🔐 **Password auth** | Optional server password with SHA-256 hashing |
| 👤 **Username dedup** | Two people can't use the same name simultaneously |
| 💬 **Private messages** | `/pm <user> <message>` — end-to-end on the wire |
| 🛡 **Rate limiting** | Spammers get warned then kicked (10 msg / 5 s) |
| ⏱ **Idle timeouts** | Dead sockets cleaned up after 5 minutes of silence |
| 🚫 **Persistent bans** | Banned IPs survive server restarts via `bans.json` |
| 🔇 **Full validation** | Every message checked for type and field constraints |
| 📝 **Structured logging** | Console + `lantalk.log` file, levels DEBUG/INFO/WARNING |
| ⚡ **Pure asyncio** | Single event loop — no threads, no race conditions |

---

## Install

```bash
pip install lantalk
```

Python 3.10+ required. No third-party dependencies.

### From source

```bash
git clone https://github.com/yourusername/lantalk
cd lantalk
pip install -e ".[dev]"
```

---

## Quick start

### 1 — Start a server (one person does this)

```
$ lantalk

  [1]  Start a Server
  [2]  Join as Client

Choose (1 or 2): 1
Port (default 5050):
Server display name: Alice
Set password (leave blank for none): secret

══════════════════════════════════════════════════════
  LanTalk Server v2.0.0  —  Alice
  Listening on  192.168.1.5:5050
  Discovery beacon active (UDP 5051)
  Password protection: ON
══════════════════════════════════════════════════════
```

### 2 — Join as a client (everyone else)

```
$ lantalk

  [1]  Start a Server
  [2]  Join as Client

Choose (1 or 2): 2
Scanning for LanTalk servers…
Found 1 server(s):

  [1] 192.168.1.5:5050
  [m] Enter manually

Choose server: 1
Display name: Bob
Password: secret

══════════════════════════════════════════════════════
  LanTalk Client v2.0.0  —  Bob
  Connected to 192.168.1.5:5050
  Commands: /users  /pm <user> <msg>  /quit
══════════════════════════════════════════════════════
[Bob | lantalk] > Hey everyone!
[14:22] Alice: Welcome Bob!
[Bob | lantalk] > /pm Alice just between us 👋
[Bob | lantalk] > /users
[14:23] *** Online: Alice, Bob, Carol ***
[Bob | lantalk] >
```

---

## Commands

### Client commands

| Command | Description |
|---------|-------------|
| `/users` | List all connected users |
| `/pm <user> <message>` | Send a private message |
| `/quit` | Disconnect and exit |

### Server console commands

| Command | Description |
|---------|-------------|
| `/users` | List connected users |
| `/stats` | Show connection count and ban list size |
| `/kick <user>` | Disconnect a user |
| `/ban <user>` | Ban a user's IP (persisted to `bans.json`) |
| `/bans` | Show all banned IPs |
| `/unban <ip>` | Remove an IP from the ban list |
| `/help` | Show this list |
| `/quit` | Shut down the server |
| *(anything else)* | Broadcast as a server message to all clients |

---

## Architecture

```
┌──────────────────────────────────────────────────────────────┐
│  LAN  (e.g. 192.168.1.0/24)                                  │
│                                                              │
│  ┌────────────────────────────────────┐                      │
│  │  LanTalkServer  (pure asyncio)     │                      │
│  │                                    │◄─── TCP :5050 ───┐   │
│  │  _discovery_beacon()  ─────────────┼──── UDP bcast    │   │
│  │  asyncio coroutine, no threads     │     every 2 s    │   │
│  │                                    │                  │   │
│  │  per-client _RateLimiter           │                  │   │
│  │  _validate() on every message      │                  │   │
│  │  asyncio.wait_for() timeouts       │                  │   │
│  │  bans.json  (persistent)           │                  │   │
│  │  lantalk.log  (structured)         │                  │   │
│  └────────────────────────────────────┘                  │   │
│                                                          │   │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┴─┐ │
│  │ Client A     │  │ Client B     │  │ Client C           │ │
│  │ UDP discover │  │ UDP discover │  │ manual IP entry    │ │
│  │ asyncio      │  │ asyncio      │  │ asyncio            │ │
│  └──────────────┘  └──────────────┘  └────────────────────┘ │
└──────────────────────────────────────────────────────────────┘

Message flow (post-auth):
  Client ──JSON──► Server ──broadcast JSON──► all other Clients
                          └──(PM)──────────► one Client
```

### Concurrency model

Everything runs in **one asyncio event loop** — no `threading.Thread`, no locks between threads, no shared mutable state across OS threads. The only `asyncio.Lock` is used to guard the `_clients` dict between concurrent coroutines.

### JSON wire protocol

Every message is a single UTF-8 JSON line terminated with `\n`.

```jsonc
// Server → Client: auth challenge
{"type": "auth_request", "ts": "14:22", "needs_password": true}

// Client → Server: credentials
{"username": "Bob", "password": "secret"}

// Server → Client: success / failure
{"type": "auth_ok",   "ts": "…", "username": "Bob", "server_name": "Alice"}
{"type": "auth_fail", "ts": "…", "reason": "Username already taken."}

// Client → Server: chat message
{"type": "message", "text": "Hello!"}

// Server → all clients: broadcast
{"type": "message", "ts": "14:22", "user": "Bob", "text": "Hello!"}

// Server → one client: private message
{"type": "pm", "ts": "14:22", "from_user": "Alice", "text": "hey"}

// Server → clients: system event
{"type": "system", "ts": "14:22", "text": "Bob joined from 192.168.1.10."}
```

---

## Security notes

- Passwords are **never stored or sent in plaintext**. The server stores a SHA-256 hash; clients send the raw password only during the auth handshake (upgrade to TLS in v3 is planned).
- Banned IPs are written to `bans.json` in the server's working directory and reloaded on every start.
- Rate limiting: each client is capped at **10 messages per 5 seconds**. Exceeding this sends a warning; repeat offences kick the user.
- Usernames are limited to 24 alphanumeric/underscore/hyphen characters.
- All incoming messages are schema-validated before processing; malformed JSON is silently dropped.

---

## Logging

LanTalk writes structured logs to both the terminal and `lantalk.log`:

```
[14:22:01] INFO   Server started on 192.168.1.5:5050
[14:22:15] INFO   Bob joined from 192.168.1.10
[14:23:44] WARNING  Carol hit rate limit
[14:25:01] INFO   Kicked Dave
[14:25:03] INFO   Banned Dave (192.168.1.22)
```

Set `LANTALK_LOG_LEVEL=DEBUG` to see per-message traces.

---

## Running tests

```bash
pip install -e ".[dev]"
pytest -v
```

---

## Changelog

```
2.0.0  — pure asyncio (no threads), validation, rate limiting,
          timeouts, persistent bans, structured logging, v2 prompt
1.1.0  — asyncio rewrite, UDP discovery, JSON protocol, auth, commands, pytest
1.0.0  — initial: threading, basic TCP, plain-text protocol
```

---

## Roadmap

- [ ] TLS encryption (v3)
- [ ] File transfer (`/send file.pdf`)
- [ ] Web UI mode (`http://localhost:5050`)
- [ ] Plugin system (`@lantalk.plugin`)

---

## License

MIT © Ezra
