|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +tts.bot is a Twitch Text-to-Speech bot for streamers. It connects to Twitch chat via TMI.js, synthesizes speech using AWS Polly, and supports real-time translation via AWS Translate. The app runs entirely client-side (no backend). |
| 8 | + |
| 9 | +The **original** static HTML/JS app lives in `webfront/` (served by nginx, kept for reference). |
| 10 | +The **React rewrite** lives in `react-app/` and is the active codebase. |
| 11 | + |
| 12 | +## Development (react-app/) |
| 13 | + |
| 14 | +```bash |
| 15 | +cd react-app |
| 16 | +npm install |
| 17 | +npm run dev # Vite dev server at localhost:5173 |
| 18 | +npm run build # Produces dist/ — point nginx root here |
| 19 | +npm run typecheck # tsc -b with no emit |
| 20 | +``` |
| 21 | + |
| 22 | +**Nginx**: point the server root at `react-app/dist/`. The `react-app/nginx.conf` file contains the SPA config with `try_files $uri /index.html` fallback. Run `npm run build` and reload nginx after changes. |
| 23 | + |
| 24 | +## Architecture (react-app/src/) |
| 25 | + |
| 26 | +### Auth flow |
| 27 | +1. `/login` — user picks voice/language settings, clicks "Authorize on Twitch" |
| 28 | +2. Twitch redirects to `/callback` with `#access_token=…` in URL hash |
| 29 | +3. `CallbackPage` extracts token → writes to Zustand (persisted in localStorage) → redirects to `/app` |
| 30 | +4. `/app` is protected — redirects to `/login` if no token |
| 31 | + |
| 32 | +### Pages |
| 33 | +| Route | File | Purpose | |
| 34 | +|---|---|---| |
| 35 | +| `/login` | `pages/LoginPage.tsx` | Voice setup + Twitch OAuth | |
| 36 | +| `/callback` | `pages/CallbackPage.tsx` | Extracts OAuth token from URL hash | |
| 37 | +| `/app` | `pages/StreamerDashboard.tsx` | Main TTS dashboard (protected) | |
| 38 | +| `/cct` | `pages/CCTOverlay.tsx` | Closed Captions/Translation standalone popup | |
| 39 | + |
| 40 | +### State (Zustand stores — `store/index.ts`) |
| 41 | +All stores use `zustand/middleware/persist` to localStorage except session stores. |
| 42 | + |
| 43 | +| Store | Persisted | Contents | |
| 44 | +|---|---|---| |
| 45 | +| `useAuthStore` | ✓ | `accessToken`, `twitchUsername` | |
| 46 | +| `useVoiceStore` | ✓ | All voice/language selections | |
| 47 | +| `useChattersStore` | ✓ | Per-user voice configs (`Record<username, ChatterConfig>`) | |
| 48 | +| `useSettingsStore` | ✓ | All checkboxes (`cb*`) and text inputs (`txt*`) | |
| 49 | +| `useAppearanceStore` | ✓ | CCT overlay visual settings | |
| 50 | +| `useConnectionStore` | ✗ | `channel`, `connectionStatus` | |
| 51 | +| `useChatUiStore` | ✗ | `messages[]`, `queueCount`, `isPaused`, `currentSpeakingId` | |
| 52 | +| `useVoicesDataStore` | ✗ | `voices`, `voicesDesc`, `awsInitialized` | |
| 53 | + |
| 54 | +### AudioPlayer (`audio/AudioPlayer.ts`) |
| 55 | +A plain TypeScript class — **not** React state. Created once as a singleton in `hooks/useAudioPlayer.ts` via `useRef`. Key methods: `Speak`, `SpeakNow`, `SpeakNext`, `SpeakCustom`, `SpeakGame2TTS`, `Pause`, `Continue`, `Skip`, `SkipByID`, `Dump`, `DumpByUser`, `PopLastMessage`. Calls `synthesizeSpeech()` from `services/awsService.ts`. |
| 56 | + |
| 57 | +Configure the player after AWS is initialized: |
| 58 | +```ts |
| 59 | +player.configure({ getSystemVoice, getSystemVoiceOption, getVoices, onQueueCount, onSpeakingId }); |
| 60 | +``` |
| 61 | + |
| 62 | +### TMI.js (`hooks/useTwitchChat.ts`) |
| 63 | +`useTwitchChat` hook owns the Twitch chat connection lifecycle. The connection only starts when `enabled: true`. TMI client is stored in a `useRef` to avoid stale closures in handlers. All event handlers are forwarded through a stable `handlers` ref. |
| 64 | + |
| 65 | +### AWS (`services/awsService.ts`) |
| 66 | +- `initializeAWS()` — Cognito unauthenticated identity → credentials |
| 67 | +- `buildVoiceLookup()` — calls `DescribeVoices`, returns normalized `voices` and `voicesDesc` |
| 68 | +- `synthesizeSpeech()` — calls Polly, returns `Uint8Array` |
| 69 | +- `translateText()` — calls Translate, returns `{ translatedText, sourceLangCode }` |
| 70 | + |
| 71 | +AWS clients are module-level singletons (not React state). `useAWSServices` hook initializes them once. |
| 72 | + |
| 73 | +### Chat pipeline (`services/chatProcessor.ts`) |
| 74 | +`doChat()` is the main async function — ported from the original `doChat()` in `webfront/js/script.js`. It handles: |
| 75 | +- Emote stripping (BTTV, FFZ, Twitch) |
| 76 | +- SSML detection |
| 77 | +- Permission checks (everyone/mod/sub/VIP) |
| 78 | +- AWS Translate |
| 79 | +- Levenshtein dedup (per-user and chat-wide) |
| 80 | +- @username replacement with spoken names |
| 81 | +- Link filtering (TLD regex built in `StreamerDashboard`) |
| 82 | +- Calling `onSpeak` → `AudioPlayer.Speak()` |
| 83 | + |
| 84 | +### WebSocket support |
| 85 | +Two optional WebSocket connections managed in `StreamerDashboard.tsx`: |
| 86 | +- `websocketCustomRef` — custom URL, handles `TTS`, `game2tts`, `GPT-Moderated` topics |
| 87 | +- `websocketProdRef` — AWS WebSocket backend |
| 88 | + |
| 89 | +### CCT cross-window communication |
| 90 | +The original app called `window.cctPopup.processResults()` directly. The React version uses `window.postMessage` with `{ type: 'cct-result', text, isFinal }`. `CCTOverlay` also exposes `window.processResults` for legacy compatibility. |
| 91 | + |
| 92 | +## Key Constants (`constants/index.ts`) |
| 93 | +- `TWITCH_CLIENT_ID` — `dan71ek0pct1u7b8ht5u4h55zlcxvq` |
| 94 | +- `COGNITO_IDENTITY_POOL_ID` — `us-east-1:e9babc40-c043-4729-91be-de6c1d22b919` |
| 95 | +- `AWS_REGION` — `us-east-1` |
| 96 | + |
| 97 | +## Original app (`webfront/`) |
| 98 | +Static HTML/JS, no build step. `webfront/js/script.js` (4,300+ lines) is the reference implementation. Do not modify — it serves as documentation for the React rewrite. |
0 commit comments