diff --git a/.env.example b/.env.example deleted file mode 100644 index fdced1b..0000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -YOUTUBE_LIVE_ID="" -INNERTUBE_API_KEY="" # optional override -INNERTUBE_CLIENT_NAME="" # optional override -INNERTUBE_CLIENT_VERSION="" # optional override -SESSION_SECRET="change-me" diff --git a/README.md b/README.md new file mode 100644 index 0000000..0c6b75a --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# YouTube Live Chat Client (WIP) + +Build a desktop-operated, open-source YouTube Live chat client that delivers a fast, reliable stream of messages and allows a streamer to spotlight a selected chat message on an OBS-ready overlay. + +Currently work in progress bugs might be there. + +## Key Features + +- **High-Performance Chat**: A sleek, minimal dashboard for monitoring YouTube Live chat in real-time. +- **OBS Integration**: Select any message to instantly display it on an OBS-ready overlay. +- **Rich Message Support**: Full support for Super Chats, gifted memberships, and user badges. +- **Compact and Efficient**: A dark, minimalist UI designed to be space-efficient and easy on the eyes. +- **Intelligent Auto-Scroll**: The chat automatically scrolls to new messages but stops when you scroll up to read previous messages. + +## Getting Started + +Follow these instructions to set up the project for local development. + +### Prerequisites + +- [Node.js](https://nodejs.org/) (v20.x or later) +- [pnpm](https://pnpm.io/) + +### Installation + +1. **Clone the repository:** + + ```bash + git clone https://github.com/your-username/youtube-client.git + cd youtube-client + ``` + +2. **Install dependencies:** + + ```bash + pnpm install + ``` + +### Running the Application + +To start the development server for both the backend and frontend, run: + +```bash +pnpm dev +``` + +- The dashboard will be available at `http://localhost:3000/dashboard`. +- The OBS overlay will be available at `http://localhost:3000/overlay`. + +## Usage + +Once the application is running, open the dashboard. It will automatically connect to the YouTube Live stream specified in your `.env` file. If you need to connect to a different stream, you can use the connection prompt on the dashboard. + +## Technology Stack + +- **Frontend**: [Next.js](https://nextjs.org/) (React) +- **Backend**: [Node.js](https://nodejs.org/) with [Fastify](https://www.fastify.io/) and [tsx](https://github.com/esbuild-kit/tsx) +- **YouTube Integration**: [youtubei.js](https://github.com/LuanRT/YouTube.js) +- **Styling**: Handcrafted CSS diff --git a/client/app/dashboard/page.tsx b/client/app/dashboard/page.tsx index 91d6c72..baa44c2 100644 --- a/client/app/dashboard/page.tsx +++ b/client/app/dashboard/page.tsx @@ -105,21 +105,22 @@ export default function DashboardPage() {

💬 CHAT MESSAGES

-
- {regularMessages.map((message) => ( + ( handleSelect(message)} /> - ))} - {regularMessages.length === 0 && ( + )} + emptyState={

⏳ Waiting for chat messages...

- )} -
+ } + />
@@ -127,42 +128,44 @@ export default function DashboardPage() {

🔥 SUPER CHATS

-
- {superChats.map((message) => ( + ( handleSelect(message)} /> - ))} - {superChats.length === 0 && ( + )} + emptyState={

No super chats yet

- )} -
+ } + />

⭐ MEMBERSHIPS & MILESTONES

-
- {newMembers.map((message) => ( + ( handleSelect(message)} /> - ))} - {newMembers.length === 0 && ( + )} + emptyState={

No new members yet

- )} -
+ } + />
@@ -380,6 +383,33 @@ type ChatItemProps = { onSelect: () => void; }; +type ChatListPanelProps = { + messages: ChatMessage[]; + renderItem: (message: ChatMessage) => React.ReactNode; + emptyState: React.ReactNode; +}; + +function ChatListPanel({ messages, renderItem, emptyState }: ChatListPanelProps) { + const scrollRef = useRef(null); + + useEffect(() => { + const node = scrollRef.current; + if (node) { + // Check if user is near the bottom before scrolling + const isAtBottom = node.scrollHeight - node.scrollTop - node.clientHeight <= 100; + if (isAtBottom) { + node.scrollTop = node.scrollHeight; + } + } + }, [messages]); + + return ( +
+ {messages.length > 0 ? messages.map(renderItem) : emptyState} +
+ ); +} + function ChatItem({ message, isSelected, onSelect }: ChatItemProps) { return (