forked from ComputerTech/aprhodite
682 lines
28 KiB
Markdown
682 lines
28 KiB
Markdown
# 💋 SexyChat (Aphrodite)
|
||
|
||
A real-time encrypted chat platform with an AI companion, role-based moderation, and a glassmorphism UI. Built with Flask-SocketIO, AES-GCM encryption, and a local Ollama-powered AI named **Violet**.
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
- [Features](#features)
|
||
- [Architecture](#architecture)
|
||
- [Project Structure](#project-structure)
|
||
- [Setup & Installation](#setup--installation)
|
||
- [Configuration](#configuration)
|
||
- [Running the Server](#running-the-server)
|
||
- [Database Models](#database-models)
|
||
- [Socket Events Reference](#socket-events-reference)
|
||
- [REST API Endpoints](#rest-api-endpoints)
|
||
- [Role System & Admin Panel](#role-system--admin-panel)
|
||
- [Encryption & Security](#encryption--security)
|
||
- [Theme System](#theme-system)
|
||
- [Settings Panel](#settings-panel)
|
||
- [Violet AI Companion](#violet-ai-companion)
|
||
- [Premium / Paywall](#premium--paywall)
|
||
|
||
---
|
||
|
||
## Features
|
||
|
||
### Core Chat
|
||
- **Public Lobby** — Real-time ephemeral group chat. Messages are never persisted; they live only in connected clients' browsers.
|
||
- **Private Messaging** — AES-GCM-256 encrypted PMs with persistent history (up to 500 messages per conversation).
|
||
- **Guest Mode** — Join and chat anonymously without an account.
|
||
- **Registration & Login** — Bcrypt password hashing, JWT token auth (7-day expiry), automatic session restore on page refresh.
|
||
- **Rate Limiting** — 6 messages per 5-second window per session.
|
||
|
||
### Violet AI Companion
|
||
- Local Ollama inference (`sadiq-bd/llama3.2-3b-uncensored:latest` by default).
|
||
- Transit-encrypted messages — decrypted server-side only for inference, never stored in plaintext.
|
||
- Per-user conversation memory (last 20 turns), persisted across sessions. `/reset` command to clear.
|
||
- Free trial (configurable, default: 3 messages), then paywall — admins can grant unlimited access.
|
||
- Real-time typing indicator while Violet is generating a response.
|
||
- Friendly signup prompt for unregistered guests who try to PM Violet.
|
||
|
||
### Moderation & Roles
|
||
- **4-tier role hierarchy:** `root` (👑) > `admin` (⚔️) > `mod` (🛡️) > `user`
|
||
- Kick, ban (username + IP), kickban, mute/unmute, verify/unverify.
|
||
- Persistent bans and mutes survive server restarts (stored in DB, loaded on startup).
|
||
- Admin panel with three tabs: Users, Bans, Mutes.
|
||
- Privilege escalation prevention — you can't modify users at or above your own power level.
|
||
|
||
### User Features
|
||
- Persistent ignore list (stealth blocking — ignored users receive no indication).
|
||
- Context menu (right-click a username) for PM, ignore, and mod actions.
|
||
- Settings panel with four tabs: Account, Chat, Violet, Premium.
|
||
- Password change from settings (requires current password).
|
||
- Unread PM indicators on conversation tabs.
|
||
|
||
### UI/UX
|
||
- Glassmorphism dark theme with CSS custom property theming.
|
||
- **10 color themes** (dark, light, neon, cyberpunk, and more).
|
||
- Responsive mobile layout with collapsible sidebar.
|
||
- Auto-expanding message textarea.
|
||
- Enter-to-send toggle (configurable).
|
||
- Google Fonts (Inter + Outfit).
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌─────────────┐ WebSocket / HTTP ┌──────────────────────────────┐
|
||
│ Browser │ ◄────────────────────► │ Gunicorn + Eventlet │
|
||
│ (chat.js │ │ Flask-SocketIO │
|
||
│ crypto.js) │ │ │
|
||
└─────────────┘ │ ┌────────────────────────┐ │
|
||
│ │ REST Blueprint │ │
|
||
│ │ (routes.py) │ │
|
||
│ └────────────────────────┘ │
|
||
│ │
|
||
│ ┌────────────────────────┐ │
|
||
│ │ AI Worker (greenlet) │ │
|
||
│ │ Serialized Ollama queue │ │
|
||
│ └───────────┬────────────┘ │
|
||
└──────────────┼───────────────┘
|
||
│
|
||
┌──────────────▼───────────────┐
|
||
│ Ollama (localhost:11434) │
|
||
│ LLaMA 3.2 3B Uncensored │
|
||
└──────────────────────────────┘
|
||
│
|
||
┌──────────────▼───────────────┐
|
||
│ SQLite (instance/sexchat.db)│
|
||
│ Users, Messages, Bans, │
|
||
│ Mutes, VioletHistory │
|
||
└──────────────────────────────┘
|
||
```
|
||
|
||
### Key Design Decisions
|
||
|
||
- **Single-process, single-worker** — Eventlet async mode with one Gunicorn worker. All in-memory state (connected users, rooms, rate limits) lives in-process.
|
||
- **AI inference queue** — A dedicated eventlet greenlet serialises Ollama requests one at a time, broadcasting `violet_typing` indicators while busy. Runs within Flask app context for DB access.
|
||
- **Lobby is ephemeral** — No lobby messages are ever written to the database.
|
||
- **PMs are encrypted at rest** — The server stores only AES-GCM ciphertext and nonces for registered-user PMs.
|
||
- **Shared config module** — `config.py` centralises all constants, config loading, AES-GCM helpers, and JWT helpers so `app.py` and `routes.py` share a single source of truth.
|
||
|
||
### Tech Stack
|
||
|
||
| Layer | Technology |
|
||
|-------|-----------|
|
||
| Web server | Gunicorn 21.x with eventlet worker |
|
||
| Framework | Flask 3.x + Flask-SocketIO 5.x |
|
||
| Concurrency | Eventlet (monkey-patched greenlets) |
|
||
| Database | SQLite (default) or PostgreSQL via `DATABASE_URL` |
|
||
| ORM | SQLAlchemy 3.x + Flask-Migrate 4.x (Alembic) |
|
||
| AI | Ollama HTTP API (local inference) |
|
||
| Auth | JWT (PyJWT 2.x) + bcrypt 4.x |
|
||
| Encryption | AES-GCM-256 (server: `cryptography` 42.x, client: Web Crypto API) |
|
||
| Frontend | Vanilla JS, Socket.IO 4 client, custom CSS |
|
||
| Scaling | Optional Redis message queue for multi-worker deployments |
|
||
|
||
---
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
aprhodite/
|
||
├── app.py # Flask-SocketIO app: socket events, AI worker, admin panel, moderation
|
||
├── config.py # Shared config loader, constants, AES-GCM helpers, JWT helpers, Violet prompt
|
||
├── database.py # SQLAlchemy + Flask-Migrate init, Violet bot user seeding
|
||
├── models.py # ORM models: User, Message, Ban, Mute, UserIgnore, VioletHistory
|
||
├── routes.py # REST API blueprint: auth, PM history, AI message, payment webhook
|
||
├── start.py # Process manager: start / stop / restart / status / debug
|
||
├── gunicorn.conf.py # Gunicorn configuration (workers, bind, worker class)
|
||
├── config.json # Runtime secrets & overrides (gitignored)
|
||
├── requirements.txt # Python dependencies
|
||
├── index.html # Single-page app: join screen, chat, settings modal, admin panel modal
|
||
├── static/
|
||
│ ├── chat.js # Frontend logic: socket events, PMs, settings, admin panel, themes
|
||
│ ├── crypto.js # Client-side AES-GCM encryption (PBKDF2 key derivation, SubtleCrypto)
|
||
│ ├── style.css # All styles: glassmorphism, 10 themes, admin panel, responsive layout
|
||
│ └── socket.io.min.js# Socket.IO v4 client library
|
||
└── instance/
|
||
└── sexchat.db # SQLite database (auto-created on first run)
|
||
```
|
||
|
||
---
|
||
|
||
## Setup & Installation
|
||
|
||
### Prerequisites
|
||
|
||
- **Python 3.11+**
|
||
- **[Ollama](https://ollama.ai)** (required for Violet AI)
|
||
|
||
### Install
|
||
|
||
```bash
|
||
git clone https://git.computertech.dev/lord3nd3r/aprhodite.git
|
||
cd aprhodite
|
||
|
||
python -m venv venv
|
||
source venv/bin/activate
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
### Pull the AI Model
|
||
|
||
```bash
|
||
ollama pull sadiq-bd/llama3.2-3b-uncensored:latest
|
||
```
|
||
|
||
### Create `config.json`
|
||
|
||
```json
|
||
{
|
||
"SECRET_KEY": "your-random-secret-key",
|
||
"JWT_SECRET": "your-random-jwt-secret",
|
||
"ADMIN_PASSWORD": "a-strong-moderator-password",
|
||
"AI_FREE_LIMIT": 3,
|
||
"HOST": "0.0.0.0",
|
||
"PORT": 5000
|
||
}
|
||
```
|
||
|
||
> **Tip:** Generate secrets with `python -c "import uuid; print(uuid.uuid4().hex)"`.
|
||
|
||
### Database
|
||
|
||
On first run, `database.py` calls `db.create_all()` and seeds the **Violet** bot user automatically. No manual migration is needed for a fresh install.
|
||
|
||
For schema changes after the initial setup:
|
||
|
||
```bash
|
||
flask db migrate -m "description of changes"
|
||
flask db upgrade
|
||
```
|
||
|
||
---
|
||
|
||
## Configuration
|
||
|
||
Configuration is resolved with the following precedence: **Environment Variable → `config.json` → Default Value**.
|
||
|
||
All config loading is centralised in `config.py`.
|
||
|
||
| Key | Default | Description |
|
||
|-----|---------|-------------|
|
||
| `SECRET_KEY` | Random UUID hex | Flask session secret |
|
||
| `JWT_SECRET` | Random UUID hex | JWT HMAC signing key |
|
||
| `ADMIN_PASSWORD` | `"admin1234"` | Shared mod password for guest elevation |
|
||
| `DATABASE_URL` | `"sqlite:///sexchat.db"` | SQLAlchemy database URI |
|
||
| `PAYMENT_SECRET` | `"change-me-payment-secret"` | Payment webhook validation secret |
|
||
| `CORS_ORIGINS` | `None` | Socket.IO CORS allowlist |
|
||
| `HOST` | `"0.0.0.0"` | Server bind address |
|
||
| `PORT` | `5000` | Server bind port |
|
||
| `OLLAMA_URL` | `"http://localhost:11434"` | Ollama API endpoint |
|
||
| `VIOLET_MODEL` | `"sadiq-bd/llama3.2-3b-uncensored:latest"` | Ollama model tag for Violet |
|
||
| `AI_FREE_LIMIT` | `3` | Free Violet messages before paywall |
|
||
| `SOCKETIO_MESSAGE_QUEUE` / `REDIS_URL` | `None` | Redis URL for multi-worker Socket.IO |
|
||
|
||
### Hardcoded Constants (in `config.py`)
|
||
|
||
| Constant | Value | Description |
|
||
|----------|-------|-------------|
|
||
| `MAX_MSG_LEN` | 500 chars | Maximum message length |
|
||
| `MAX_HISTORY` | 500 messages | PM history cap per conversation pair |
|
||
| `JWT_EXPIRY_DAYS` | 7 days | JWT token lifespan |
|
||
| `AI_BOT_NAME` | `"Violet"` | AI bot display name |
|
||
| `PBKDF2_ITERATIONS` | 100,000 | Client-side key derivation iterations (`crypto.js`) |
|
||
| Rate limit | 6 msgs / 5 sec | Per-session lobby rate limit (`app.py`) |
|
||
| `MAX_HISTORY_PER_USER` | 20 turns | Violet conversation memory depth (`app.py`) |
|
||
|
||
---
|
||
|
||
## Running the Server
|
||
|
||
### Debug Mode (foreground, full logging)
|
||
|
||
```bash
|
||
python start.py debug
|
||
```
|
||
|
||
### Daemon Mode
|
||
|
||
```bash
|
||
python start.py start # Start in background (PID written to .pid file)
|
||
python start.py stop # Graceful shutdown
|
||
python start.py restart # Stop + start
|
||
python start.py status # Check if running
|
||
```
|
||
|
||
### Direct Gunicorn
|
||
|
||
```bash
|
||
gunicorn --worker-class eventlet -w 1 --bind 0.0.0.0:5000 start:application
|
||
```
|
||
|
||
The server will be available at `http://localhost:5000`.
|
||
|
||
---
|
||
|
||
## Database Models
|
||
|
||
Six models defined in `models.py`. All timestamps use `datetime.now(timezone.utc)`.
|
||
|
||
### `users`
|
||
|
||
| Column | Type | Description |
|
||
|--------|------|-------------|
|
||
| `id` | Integer, PK | Auto-increment |
|
||
| `username` | String(20), unique, indexed | Alphanumeric + `-` + `_` |
|
||
| `password_hash` | String(128) | Bcrypt hash |
|
||
| `email` | String(255), unique, nullable | Optional email |
|
||
| `role` | String(10), default `"user"` | `root`, `admin`, `mod`, or `user` |
|
||
| `has_ai_access` | Boolean, default `False` | Unlimited Violet access flag |
|
||
| `ai_messages_used` | Integer, default `0` | Free trial counter |
|
||
| `is_verified` | Boolean, default `False` | Manual verification status |
|
||
| `created_at` | DateTime | UTC timestamp |
|
||
|
||
### `messages` (Encrypted PM History)
|
||
|
||
| Column | Type | Description |
|
||
|--------|------|-------------|
|
||
| `id` | Integer, PK | |
|
||
| `sender_id` | FK → `users.id` | |
|
||
| `recipient_id` | FK → `users.id` | |
|
||
| `encrypted_content` | Text | Base64-encoded AES-GCM ciphertext |
|
||
| `nonce` | String(64) | Base64-encoded 12-byte IV |
|
||
| `timestamp` | DateTime | Indexed with sender/recipient for query performance |
|
||
|
||
### `bans`
|
||
|
||
| Column | Type | Description |
|
||
|--------|------|-------------|
|
||
| `id` | Integer, PK | |
|
||
| `username` | String(20), indexed | Banned username (lowercase) |
|
||
| `ip` | String(45), nullable, indexed | Banned IP address |
|
||
| `reason` | String(255), nullable | Ban reason |
|
||
| `created_at` | DateTime | |
|
||
|
||
### `mutes`
|
||
|
||
| Column | Type | Description |
|
||
|--------|------|-------------|
|
||
| `id` | Integer, PK | |
|
||
| `username` | String(20), unique, indexed | Muted username |
|
||
| `created_at` | DateTime | |
|
||
|
||
### `user_ignores`
|
||
|
||
| Column | Type | Description |
|
||
|--------|------|-------------|
|
||
| `id` | Integer, PK | |
|
||
| `ignorer_id` | FK → `users.id` | User doing the ignoring |
|
||
| `ignored_id` | FK → `users.id` | User being ignored |
|
||
| `created_at` | DateTime | |
|
||
|
||
Unique composite index on `(ignorer_id, ignored_id)`.
|
||
|
||
### `violet_history`
|
||
|
||
| Column | Type | Description |
|
||
|--------|------|-------------|
|
||
| `id` | Integer, PK | |
|
||
| `user_id` | FK → `users.id` | Owning user |
|
||
| `role` | String(10) | `"user"` or `"assistant"` |
|
||
| `text` | Text | Plaintext conversation turn |
|
||
| `timestamp` | DateTime | |
|
||
|
||
---
|
||
|
||
## Socket Events Reference
|
||
|
||
### Connection & Auth
|
||
|
||
| Direction | Event | Data | Description |
|
||
|-----------|-------|------|-------------|
|
||
| C→S | `join` | `{ mode, username, password?, email?, mod_password? }` | Authenticate and enter lobby. `mode`: `guest`, `login`, `register`, or `restore`. |
|
||
| S→C | `joined` | `{ username, is_admin, role, is_registered, has_ai_access, ai_messages_used, email?, token?, ignored_list? }` | Auth success — includes JWT token for session restore. |
|
||
| S→C | `error` | `{ msg }` | Error message. |
|
||
| S→C | `kicked` | `{ msg }` | User was kicked/banned. |
|
||
|
||
### Lobby Chat
|
||
|
||
| Direction | Event | Data | Description |
|
||
|-----------|-------|------|-------------|
|
||
| C→S | `message` | `{ text }` | Send lobby message (rate-limited). |
|
||
| S→C | `message` | `{ username, text, is_admin, is_registered, ts }` | Broadcast lobby message. |
|
||
| S→C | `system` | `{ msg, ts }` | System notification (joins, parts, mod actions). |
|
||
| S→C | `nicklist` | `{ users: [{ username, is_admin, is_registered, is_verified, role }] }` | Full online user list (includes Violet bot). |
|
||
|
||
### Private Messaging
|
||
|
||
| Direction | Event | Data | Description |
|
||
|-----------|-------|------|-------------|
|
||
| C→S | `pm_open` | `{ target }` | Initiate PM with target user. |
|
||
| C→S | `pm_accept` | `{ room }` | Accept an incoming PM invitation (validated against pending invites). |
|
||
| C→S | `pm_message` | `{ room, text? }` or `{ room, ciphertext, nonce, transit_key? }` | Send PM — plaintext (guests) or encrypted (registered). Include `transit_key` for Violet. |
|
||
| S→C | `pm_invite` | `{ from, room, room_key }` | Incoming PM invitation with server-derived room key. |
|
||
| S→C | `pm_ready` | `{ with, room, room_key }` | PM room opened — sent to the initiator. |
|
||
| S→C | `pm_message` | `{ from, text?, ciphertext?, nonce?, room, ts }` | Receive a PM. |
|
||
|
||
### Violet AI
|
||
|
||
| Direction | Event | Data | Description |
|
||
|-----------|-------|------|-------------|
|
||
| C→S | `pm_message` (to Violet room) | `{ room, ciphertext, nonce, transit_key }` | Encrypted message routed to Violet via AI worker. |
|
||
| C→S | `violet_reset` | _(none)_ | Clear your conversation memory with Violet. |
|
||
| S→C | `violet_typing` | `{ busy, room? }` | AI processing indicator (shown while Ollama generates). |
|
||
| S→C | `ai_response` | `{ error: "ai_limit_reached", room }` | Free trial exhausted. |
|
||
| S→C | `ai_unlock` | `{ has_ai_access, msg? }` | Premium access granted or revoked (pushed live). |
|
||
|
||
### Moderation
|
||
|
||
| Direction | Event | Data | Description |
|
||
|-----------|-------|------|-------------|
|
||
| C→S | `mod_kick` | `{ target }` | Kick user (mod+). |
|
||
| C→S | `mod_ban` | `{ target }` | Ban username + IP (mod+). |
|
||
| C→S | `mod_kickban` | `{ target }` | Kick + ban simultaneously (mod+). |
|
||
| C→S | `mod_mute` | `{ target }` | Toggle mute on user (mod+). |
|
||
| C→S | `mod_verify` | `{ target }` | Toggle verification on user (mod+). |
|
||
|
||
### User Actions
|
||
|
||
| Direction | Event | Data | Description |
|
||
|-----------|-------|------|-------------|
|
||
| C→S | `user_ignore` | `{ target }` | Ignore a user (registered only). |
|
||
| C→S | `user_unignore` | `{ target }` | Unignore a user. |
|
||
| C→S | `change_password` | `{ old_password, new_password }` | Change password (6-char minimum). |
|
||
| S→C | `ignore_status` | `{ target, ignored }` | Ignore list update confirmation. |
|
||
| S→C | `password_changed` | `{ success, msg? }` | Password change result. |
|
||
| S→C | `role_updated` | `{ role }` | Live notification when your role is changed by an admin. |
|
||
|
||
### Admin Panel
|
||
|
||
| Direction | Event | Data | Description |
|
||
|-----------|-------|------|-------------|
|
||
| C→S | `admin_get_users` | _(none)_ | Request full user list (mod+). |
|
||
| C→S | `admin_get_bans` | _(none)_ | Request ban list (mod+). |
|
||
| C→S | `admin_get_mutes` | _(none)_ | Request mute list (mod+). |
|
||
| C→S | `admin_set_role` | `{ user_id, role }` | Change a user's role (admin+ only, can't set ≥ own role). |
|
||
| C→S | `admin_verify_user` | `{ user_id }` | Toggle verification (mod+). |
|
||
| C→S | `admin_toggle_ai` | `{ user_id }` | Toggle unlimited AI access (admin+). Notifies user in real-time. |
|
||
| C→S | `admin_unban` | `{ ban_id }` | Remove a ban (mod+). |
|
||
| C→S | `admin_unmute` | `{ mute_id }` | Remove a mute (mod+). |
|
||
| S→C | `admin_users` | `{ users: [{ id, username, role, is_verified, has_ai_access, email, created_at, online }] }` | User list with online status. |
|
||
| S→C | `admin_bans` | `{ bans: [{ id, username, ip?, reason?, created_at }] }` | Ban list. |
|
||
| S→C | `admin_mutes` | `{ mutes: [{ id, username, created_at }] }` | Mute list. |
|
||
| S→C | `admin_action_ok` | `{ msg }` | Action success toast (panel auto-refreshes). |
|
||
|
||
---
|
||
|
||
## REST API Endpoints
|
||
|
||
All endpoints are under the `/api` prefix.
|
||
|
||
### `POST /api/auth/register`
|
||
|
||
Create a new account.
|
||
|
||
```json
|
||
// Request
|
||
{ "username": "alice", "password": "hunter2", "email": "alice@example.com" }
|
||
|
||
// Response 201
|
||
{
|
||
"token": "eyJ...",
|
||
"user": { "id": 3, "username": "alice", "has_ai_access": false, "ai_messages_used": 0 }
|
||
}
|
||
```
|
||
|
||
### `POST /api/auth/login`
|
||
|
||
Authenticate and receive a JWT.
|
||
|
||
```json
|
||
// Request
|
||
{ "username": "alice", "password": "hunter2" }
|
||
|
||
// Response 200
|
||
{ "token": "eyJ...", "user": { "id": 3, "username": "alice", ... } }
|
||
```
|
||
|
||
### `GET /api/pm/history?with={username}`
|
||
|
||
Fetch encrypted PM history with another user. Requires `Authorization: Bearer <jwt>`.
|
||
|
||
```json
|
||
// Response 200
|
||
{
|
||
"room_key": "base64...",
|
||
"messages": [
|
||
{ "from_me": true, "ciphertext": "...", "nonce": "...", "ts": "2025-04-12T..." },
|
||
{ "from_me": false, "ciphertext": "...", "nonce": "...", "ts": "2025-04-12T..." }
|
||
]
|
||
}
|
||
```
|
||
|
||
### `POST /api/ai/message`
|
||
|
||
Send an encrypted message to Violet via REST (alternative to the socket path).
|
||
|
||
```json
|
||
// Request (Authorization: Bearer <jwt>)
|
||
{ "ciphertext": "...", "nonce": "...", "transit_key": "..." }
|
||
|
||
// Response 200
|
||
{ "ciphertext": "...", "nonce": "...", "ai_messages_used": 2, "has_ai_access": false }
|
||
```
|
||
|
||
### `POST /api/payment/success`
|
||
|
||
Payment webhook (server-to-server). Validates `secret` with constant-time HMAC comparison.
|
||
|
||
```json
|
||
// Request
|
||
{ "secret": "PAYMENT_SECRET", "user_id": 3 }
|
||
|
||
// Response 200
|
||
{ "status": "ok", "has_ai_access": true }
|
||
```
|
||
|
||
---
|
||
|
||
## Role System & Admin Panel
|
||
|
||
### Hierarchy
|
||
|
||
| Role | Power Level | Badge | Capabilities |
|
||
|------|-------------|-------|-------------|
|
||
| `root` | 3 | 👑 | Everything. Can promote to admin. Cannot be demoted. |
|
||
| `admin` | 2 | ⚔️ | Set roles (mod/user), toggle AI access, all mod actions. |
|
||
| `mod` | 1 | 🛡️ | Kick, ban, mute, verify, view admin panel. |
|
||
| `user` | 0 | — | Standard chat, PMs, Violet AI (free trial). |
|
||
|
||
**Rules:**
|
||
- You cannot modify a user whose power level is ≥ your own.
|
||
- You cannot assign a role whose power level is ≥ your own.
|
||
- The first registered human user should be set to `root` in the database.
|
||
- **Legacy:** Guests can enter the shared mod password at login to gain temporary `mod` session access.
|
||
|
||
### Admin Panel
|
||
|
||
Accessible via the shield icon (🛡️) in the header (visible to `mod` and above). Three tabs:
|
||
|
||
| Tab | Features |
|
||
|-----|----------|
|
||
| **Users** | Searchable user list. Change roles (dropdown), toggle verification (checkmark), grant/revoke AI access (brain icon). Shows online status. |
|
||
| **Bans** | All active bans with username, IP, and date. One-click unban button. |
|
||
| **Mutes** | All active mutes. One-click unmute button. |
|
||
|
||
All actions trigger a system message in the lobby, auto-refresh the panel, and (where applicable) push live updates to the affected user's session.
|
||
|
||
---
|
||
|
||
## Encryption & Security
|
||
|
||
### PM Encryption (AES-GCM-256)
|
||
|
||
```
|
||
1. Server generates a deterministic room key: HMAC-SHA256(JWT_SECRET, room_name)
|
||
2. Room key is delivered to both clients via pm_invite / pm_ready events
|
||
3. Client encrypts each message with AES-GCM-256 using the room key + random 12-byte nonce
|
||
4. Server stores only base64(ciphertext) + base64(nonce) — never sees plaintext
|
||
5. Recipient decrypts client-side with the same room key
|
||
```
|
||
|
||
### Violet AI Transit Encryption
|
||
|
||
```
|
||
1. Client generates a random AES-256 transit key
|
||
2. Client encrypts message with transit key → sends { ciphertext, nonce, transit_key }
|
||
3. Server decrypts with transit key → sends plaintext to Ollama for inference
|
||
4. Server encrypts Violet's response with the same transit key → delivers to client
|
||
5. Transit key is ephemeral — used only in memory, never stored
|
||
```
|
||
|
||
### Security Measures
|
||
|
||
| Category | Implementation |
|
||
|----------|---------------|
|
||
| **Password hashing** | Bcrypt with adaptive cost factor |
|
||
| **Authentication** | JWT tokens (HMAC-SHA256), 7-day expiry |
|
||
| **Session restore** | JWT stored in `localStorage`, validated on reconnect |
|
||
| **Input validation** | Regex-enforced usernames (`[a-zA-Z0-9_-]`, 1–20 chars), message length limits, SQLAlchemy parameterized queries |
|
||
| **Rate limiting** | 6 messages per 5-second window per session |
|
||
| **RBAC** | `@_require_role()` decorator with power-level hierarchy checks |
|
||
| **Webhook auth** | Constant-time HMAC comparison for payment secret |
|
||
| **Ban persistence** | Username + IP stored in DB `bans` table, loaded into memory on startup |
|
||
| **Mute persistence** | Stored in DB `mutes` table, loaded on startup |
|
||
| **PM invite validation** | Server-side pending invite mapping — `pm_accept` only works for legitimately invited rooms |
|
||
|
||
---
|
||
|
||
## Theme System
|
||
|
||
10 themes available in **Settings → Chat → Theme**. Each theme is a set of CSS custom properties applied via the `data-theme` attribute on the document root.
|
||
|
||
| Theme | Style | Palette |
|
||
|-------|-------|---------|
|
||
| **Midnight Purple** | Dark | Deep purple + neon magenta *(default)* |
|
||
| **Crimson Noir** | Dark | Black + crimson red |
|
||
| **Ocean Deep** | Dark | Navy + cyan / teal |
|
||
| **Ember** | Dark | Dark brown + orange / amber |
|
||
| **Neon Green** | Dark | Black + bright lime |
|
||
| **Cyberpunk** | Dark | Purple + electric yellow |
|
||
| **Rose Gold** | Dark | Dark rose + blush pink |
|
||
| **Arctic** | Light | Cool blue-white + indigo accents |
|
||
| **Daylight** | Light | White + pink / purple accents |
|
||
| **Midnight Blue** | Dark | Deep navy + blue glow |
|
||
|
||
Theme preference is saved to `localStorage` (`sc_theme`) and applied immediately on page load — no flash of default theme.
|
||
|
||
---
|
||
|
||
## Settings Panel
|
||
|
||
Accessed via the gear icon (⚙️) in the header. Four tabs:
|
||
|
||
### Account
|
||
- View your username and email.
|
||
- Change password (requires current password, 6-character minimum for the new one).
|
||
|
||
### Chat
|
||
- **Theme** — 10-option grid with gradient color swatches.
|
||
- **Font Size** — 12–20px slider.
|
||
- **Timestamp Format** — 12-hour / 24-hour toggle.
|
||
- **Enter to Send** — On/Off. When off, Enter inserts a newline and Shift+Enter sends.
|
||
- **Notification Sounds** — On/Off.
|
||
|
||
### Violet
|
||
- AI access status display (Unlimited ✓ or X remaining free messages).
|
||
- Messages used counter.
|
||
- Reset conversation memory button.
|
||
|
||
### Premium
|
||
- Feature showcase (Unlimited Violet, Encrypted PMs, Priority Access, Custom Themes).
|
||
- Pricing display ($10/month).
|
||
- Coming soon placeholder.
|
||
|
||
All preferences are persisted in `localStorage` with the `sc_` prefix (e.g., `sc_theme`, `sc_fontSize`, `sc_enterSend`).
|
||
|
||
---
|
||
|
||
## Violet AI Companion
|
||
|
||
Violet is an AI chat companion powered by a local Ollama model. She's configured as a *"flirtatious and sophisticated nightclub hostess at an exclusive, dimly-lit members-only club"* via her system prompt in `config.py`.
|
||
|
||
### How It Works
|
||
|
||
1. User clicks **Violet** in the nicklist → PM room opens automatically (no invite needed for the bot).
|
||
2. User's message is transit-encrypted (AES-GCM) and queued to a single-threaded inference greenlet.
|
||
3. The AI worker decrypts the message, builds a prompt from the last 20 conversation turns (`violet_history` table), and calls Ollama's `/api/generate` endpoint.
|
||
4. The response is transit-encrypted with the same key and delivered as a PM back to the user.
|
||
5. Both the user's message and Violet's reply are saved to `violet_history` for context continuity across sessions.
|
||
|
||
### Commands
|
||
|
||
| Command | Description |
|
||
|---------|-------------|
|
||
| `/reset` | Clear Violet's memory of your conversation (deletes all `violet_history` rows for your user). |
|
||
|
||
### Guest Behavior
|
||
|
||
Unregistered guests who PM Violet receive a friendly plaintext reply:
|
||
|
||
> *"Hey hun 💜 You'll need to register an account before we can chat privately. Go back to the join screen and sign up — I'll be waiting for you! 😘"*
|
||
|
||
### Free Trial & Paywall
|
||
|
||
Users get `AI_FREE_LIMIT` (default: 3) free messages. After that, a paywall modal is shown. Admins can grant unlimited access via the admin panel's "Grant AI" button — the user is notified in real-time via `ai_unlock`.
|
||
|
||
### Fallback
|
||
|
||
If Ollama is unreachable or returns an error, Violet sends a graceful fallback message instead of crashing.
|
||
|
||
---
|
||
|
||
## Premium / Paywall
|
||
|
||
When a user exhausts their free Violet messages, a paywall modal appears with pricing and feature highlights. Premium access (`has_ai_access = True`) can be granted two ways:
|
||
|
||
1. **Admin Panel** — Any `admin`+ can click the brain icon (🧠) on a user in the Users tab to toggle unlimited AI access. The user receives a real-time notification.
|
||
2. **Payment Webhook** — `POST /api/payment/success` with the payment secret and user ID. Validated with constant-time comparison.
|
||
|
||
Premium unlocks unlimited Violet messages. The `ai_unlock` event is pushed to the user's active socket session immediately.
|
||
|
||
> **Note:** The payment flow is currently a stub. For production, replace the secret-comparison logic with a proper payment provider webhook (e.g., Stripe's `stripe.Webhook.construct_event()`).
|
||
|
||
---
|
||
|
||
## Dependencies
|
||
|
||
From `requirements.txt`:
|
||
|
||
| Package | Version | Purpose |
|
||
|---------|---------|---------|
|
||
| `flask` | ≥3.0 | Web framework |
|
||
| `flask-socketio` | ≥5.3 | WebSocket support |
|
||
| `eventlet` | ≥0.35 | Async concurrency |
|
||
| `gunicorn` | ≥21.0 | Production WSGI server |
|
||
| `flask-sqlalchemy` | ≥3.1 | ORM |
|
||
| `flask-migrate` | ≥4.0 | Database migrations (Alembic) |
|
||
| `psycopg2-binary` | ≥2.9 | PostgreSQL driver (optional) |
|
||
| `bcrypt` | ≥4.0 | Password hashing |
|
||
| `PyJWT` | ≥2.8 | JWT auth tokens |
|
||
| `cryptography` | ≥42.0 | Server-side AES-GCM |
|
||
| `redis` | ≥5.0 | Socket.IO multi-worker adapter (optional) |
|
||
| `requests` | ≥2.31 | Ollama HTTP client |
|
||
|
||
---
|
||
|
||
## License
|
||
|
||
Private project. All rights reserved.
|
||
|
||
---
|
||
|
||
## Credits
|
||
|
||
Built by **End3r** — [git.computertech.dev/lord3nd3r/aprhodite](https://git.computertech.dev/lord3nd3r/aprhodite)
|