feat: complete api docs

This commit is contained in:
2025-09-02 00:03:53 +02:00
parent a4f940b990
commit 6710423b92
12 changed files with 1197 additions and 21 deletions

View File

@@ -0,0 +1,155 @@
---
title: Chat
description: Chat websocket
---
The chat system is powered by a websocket server. Please read the entire page before implementing anything, as there are some important notes.
## Connection and messages
The websocket server is located at `wss://hctv.srizan.dev/api/chat/ws/:username`, where `:username` is the channel you want to connect to.
You'll need to provide authentication, which can be done by providing an `auth_session` cookie, just like the REST API.
Once connected, you must implement a subroutine in your code to send ping messages every 5 seconds. This is because of Cloudflare limitations.
Messages are sent and received in JSON format. The following message types are supported:
- `message`: a chat message.
- sent by client:
```json
{
"type": "message",
"content": "Hello, world!"
}
```
- received by client:
```json
{
"user": {
"id": "user_id",
"username": "user_who_sent_message",
"avatar": "https://emoji.slack-edge.com/avatar.png"
},
"message": "Hello, world!",
}
```
- `ping`: a ping message to keep the connection alive.
- sent by client:
```json
{
"type": "ping"
}
```
- received by client:
```json
{
"type": "ping"
}
```
- `history`: a message containing the chat history. This is sent upon connection.
- received by client:
```json
{
"type": "history",
"messages": [
{
"user": {
"id": "user_id",
"username": "user_who_sent_message",
"avatar": "https://emoji.slack-edge.com/avatar.png"
},
"message": "Hello, world!",
"type": "message",
},
...
]
}
```
## Emoji handling
*diagram source: devin deepwiki*
```mermaid
graph TB
subgraph "Emoji Processing Pipeline"
CHAT_MSG["Chat Message"]
PATTERN_MATCH["Regex :emoji: Pattern"]
EMOJI_REQUEST["emojiMsg WebSocket"]
REDIS_LOOKUP["Redis HGET emojis"]
FUZZY_SEARCH["uFuzzy"]
EMOJI_RESPONSE["emojiMsgResponse"]
end
subgraph "Redis Storage"
EMOJI_HASH["emojis hash key"]
EMOJI_PREFIXED["emoji:{name} url"]
EMOJIS_PREFIXED["emojis:{name} url"]
end
CHAT_MSG --> PATTERN_MATCH
PATTERN_MATCH --":emojiname:"--> EMOJI_REQUEST
EMOJI_REQUEST --> REDIS_LOOKUP
REDIS_LOOKUP --> EMOJI_HASH
REDIS_LOOKUP --> EMOJI_PREFIXED
REDIS_LOOKUP --> EMOJIS_PREFIXED
REDIS_LOOKUP --> EMOJI_RESPONSE
FUZZY_SEARCH --> EMOJI_HASH
FUZZY_SEARCH --"search results"--> EMOJI_RESPONSE
```
When a chat message is sent, the server looks for patterns in the format `:emojiname:` using regex. For each match, it sends a request to the `emojiMsg` WebSocket.
The server then checks Redis for the emoji URL and returns it.
When a user wants to look up an emoji (by typing `:(partial name)`), the server uses uFuzzy to find matching emojis in the Redis `emojis` hash key and returns the results.
Here's what gets sent on the websocket:
- `emojiMsg`: Looks up emojis
- sent by client:
```json
{
"type": "emojiMsg",
"emojis": ["aga", "yapa", "heavysob", "yay", "yay-bounce"]
}
```
- received by client:
```json
{
"type": "emojiMsgResponse",
"emojis": {
// rough example of urls
"aga": "https://emoji.slack-edge.com/aga.png",
"yapa": "https://emoji.slack-edge.com/yapa.png",
"heavysob": "https://emoji.slack-edge.com/heavysob.png",
"yay": "https://emoji.slack-edge.com/yay.png",
"yay-bounce": "https://emoji.slack-edge.com/yay-bounce.png"
}
}
```
- `emojiSearch`: Searches for emojis
- sent by client:
```json
{
"type": "emojiSearch",
"searchTerm": "aga"
}
```
- received by client:
```json
{
"type": "emojiSearchResponse",
"results": [
// real results btw
"aga",
"aga-brick-throw",
"aga-dance",
"aga-transparent",
"a-aga",
"a-aga-transparent",
"agaban",
"agaboing",
"agabounce",
"agabusiness"
]
}
```

View File

@@ -5,7 +5,7 @@ description: Documented API endpoints for hackclub.tv
hctv is meant to be one of the most hackable streaming platforms out there. to that end, we have a (currently limited) public API that you can use to interact with the platform.
since this is beta software, the API is subject to change. additionally, many endpoints are yet not implemented. please send a message in #hctv on the Hack Club Slack if you have any requests.
since this is beta software, the API is subject to change. additionally, many endpoints are yet not implemented or not fun to implement. please send a message in #hctv on the Hack Club Slack if you have any requests.
## Base url
@@ -16,4 +16,4 @@ base url for all endpoints is `https://hctv.srizan.dev/api`.
most endpoints require authentication. this will be pointed out in the documentation.
for now, it is done via a cookie called `auth_session` (as per lucia auth). this will change in the future as bot accounts are planned.
you'll need your user account to authenticate. as a recommendation, open an incognito window, log in to hctv, and copy the `auth_session` cookie from there.
you'll need your user account to authenticate. as a recommendation, open an incognito window, log in to hctv, and copy the `auth_session` cookie from there.

View File

@@ -1,5 +1,4 @@
{
"title": "API",
"description": "Documented API endpoints for hackclub.tv",
"root": true
"description": "Documented API endpoints for hackclub.tv"
}

View File

@@ -0,0 +1,47 @@
---
title: Stream
description: Stream related endpoint group
---
## GET `/stream/follow`
checks if **authenticated user** is following a certain channel.
query parameters:
- `username`: string - the channel name you want to check.
response (json):
- `following`: boolean - whether the authenticated user is following the channel or not.
## POST `/stream/follow`
cycle through follow or unfollow a channel. **authentication required**.
body parameters (json):
- `channel`: string - the channel name you want to make the action. must be one of your channels.
response (json):
- `success`: boolean - whether the operation was successful or not.
## GET `/stream/followers/:channel`
gets the followcount of a channel.
path parameters:
- `channel`: string - the channel name you want to get the followcount of.
response (json):
- `count`: integer - the number of followers the channel has.
- `success`: boolean - whether the operation was successful or not. (true if 200 status code)
## GET `/stream/info`
get stream info of certain channels by filtering. **authentication on some**.
query parameters:
- `owned`: boolean (optional) - if true, only returns channels owned by the authenticated user. requires authentication.
- `personal`: boolean (optional) - if true, only returns personal channels. requires authentication.
- `live`: boolean (optional) - if true, only returns channels that are currently live.
response (json):
- StreamInfo[] (check database schema for all returned data or just try it out!)
## GET `/stream/thumb/:channel`
gets the preview thumbnail of a channel's livestream. **authentication required**
path parameters:
- `channel`: string - the channel name you want to get the thumbnail of.
response: image (webp)

View File

@@ -1,5 +1,4 @@
{
"title": "Guides",
"description": "Useful guides to help you get started with hackclub.tv",
"root": true
"description": "Useful guides to help you get started with hackclub.tv"
}

View File

@@ -7,9 +7,9 @@ description: Get started with OBS and streaming on hackclub.tv
- open settings
- open "Stream"
- set service to custom
- set url to rtmp://hackclub.app:45913/live
- set url to `rtmp://hackclub.app:45913/live`
- on the website, click "Regenerate key"
- paste the key into your obs "Stream Key"
- start streaming!!
- start streaming!
(screenshots to be added soon)

View File

@@ -9,21 +9,23 @@
"postinstall": "fumadocs-mdx"
},
"dependencies": {
"next": "15.5.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"fumadocs-ui": "15.7.7",
"fumadocs-core": "15.7.7",
"fumadocs-mdx": "11.8.2"
"fumadocs-mdx": "11.8.2",
"fumadocs-ui": "15.7.7",
"mermaid": "^11.10.1",
"next": "15.5.2",
"next-themes": "^0.4.6",
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.12",
"@types/mdx": "^2.0.13",
"@types/node": "24.3.0",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"typescript": "^5.9.2",
"@types/mdx": "^2.0.13",
"@tailwindcss/postcss": "^4.1.12",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.12",
"postcss": "^8.5.6"
"typescript": "^5.9.2"
}
}
}

View File

@@ -4,6 +4,7 @@ import {
frontmatterSchema,
metaSchema,
} from 'fumadocs-mdx/config';
import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';
// You can customise Zod schemas for frontmatter and `meta.json` here
// see https://fumadocs.dev/docs/mdx/collections#define-docs
@@ -19,5 +20,6 @@ export const docs = defineDocs({
export default defineConfig({
mdxOptions: {
// MDX options
remarkPlugins: [remarkMdxMermaid],
},
});

View File

@@ -0,0 +1,60 @@
'use client';
import { use, useEffect, useId, useState } from 'react';
import { useTheme } from 'next-themes';
export function Mermaid({ chart }: { chart: string }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return;
return <MermaidContent chart={chart} />;
}
const cache = new Map<string, Promise<unknown>>();
function cachePromise<T>(
key: string,
setPromise: () => Promise<T>,
): Promise<T> {
const cached = cache.get(key);
if (cached) return cached as Promise<T>;
const promise = setPromise();
cache.set(key, promise);
return promise;
}
function MermaidContent({ chart }: { chart: string }) {
const id = useId();
const { resolvedTheme } = useTheme();
const { default: mermaid } = use(
cachePromise('mermaid', () => import('mermaid')),
);
mermaid.initialize({
startOnLoad: false,
securityLevel: 'loose',
fontFamily: 'inherit',
themeCSS: 'margin: 1.5rem auto 0;',
theme: resolvedTheme === 'dark' ? 'dark' : 'default',
});
const { svg, bindFunctions } = use(
cachePromise(`${chart}-${resolvedTheme}`, () => {
return mermaid.render(id, chart.replaceAll('\\n', '\n'));
}),
);
return (
<div
ref={(container) => {
if (container) bindFunctions?.(container);
}}
dangerouslySetInnerHTML={{ __html: svg }}
/>
);
}

View File

@@ -1,10 +1,12 @@
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { Mermaid } from '@/components/mdx/mermaid';
import type { MDXComponents } from 'mdx/types';
// use this function to get MDX components, you will need it for rendering MDX
export function getMDXComponents(components?: MDXComponents): MDXComponents {
return {
...defaultMdxComponents,
Mermaid,
...components,
};
}

View File

@@ -1,7 +1,7 @@
// FIXME: THIS EFFING SUCKS OH MY GOD
import { validateRequest } from '@/lib/auth/validate';
import { Channel, Prisma, prisma, StreamInfo } from '@hctv/db';
import { Prisma, prisma } from '@hctv/db';
import type { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {

914
yarn.lock

File diff suppressed because it is too large Load Diff