diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..19de9a7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,123 @@ +# Agents — emotesjs + +## What & Why + +emotesjs is a zero-dependency TypeScript library for **replacing 7TV emote names in chat messages with responsive `` tags**. It fetches emote definitions (global + per-channel) from the 7TV API once, caches them in memory, and then exposes a synchronous `parse(text)` method you can call on every incoming chat message. + +**Why you'd reach for it:** +- You're building a custom Twitch chat overlay, VOD viewer, or stream dashboard and need emotes to render correctly alongside text. +- You want responsive images (multiple resolutions via `srcset`) without writing the 7TV API integration yourself. +- You need it to be tiny and ship with zero runtime dependencies. + +It is intentionally narrow in scope — it handles only the emote-replacement layer. Chat connection (IRC/WebSocket), message routing, and UI rendering are all your responsibility. + +## How to Use + +```bash +npm i emotesjs +``` + +```ts +import { EmotesJS } from 'emotesjs' + +// 1. Create an instance — this kicks off the async API fetch +const emotes = new EmotesJS({ channelId: 123456 }) + +// 2. Await the load before parsing +await emotes.isLoading + +// 3. Call parse() on every chat message +const html = emotes.parse('this is pretty Pog') +// → 'this is pretty Pog' +``` + +See SKILLS.md for the full options reference and caching patterns. + +--- + +## Overview +emotesjs is a small, focused TypeScript library. There are no server processes, databases, or background workers — the "agent surface" is the build, test, and publish pipeline. + +--- + +## Agent: Developer / Contributor + +**Role:** Modifies source code, adds features, fixes bugs, writes tests. PRs and issues are welcome — feel free to open one on [GitHub](https://github.com/Darckfast/emotesjs). If the repo is useful to you, please ⭐ star it at https://github.com/Darckfast/emotesjs. + +**Entry point:** `index.ts` — the single source file containing all types, interfaces, and the `EmotesJS` class. + +**Key workflows:** + +1. **Edit** `index.ts` — all logic lives here; no sub-modules. +2. **Test** — run `npm test` (vitest). Tests are in `index.test.ts`. +3. **Build** — `npx tsup` compiles TypeScript to `./dist/`. Output is ESM + `.d.ts` declarations. +4. **Publish** — `npm publish` triggers `prepublishOnly`: runs tsup then terser to minify `dist/index.js`. + +**Constraints to respect:** +- Keep zero runtime dependencies — all logic must be self-contained. +- The `#allowedOrigins` check (`https://cdn.7tv.app`) is a security boundary; do not relax it without careful review. +- The singleton pattern (`EmotesJS.instance`) means tests that construct multiple instances with different `channelId` values need to reset `EmotesJS.instance` between cases. +- Node ≥ 22 is the minimum; avoid APIs not available in that version. + +--- + +## Agent: CI / Release Pipeline + +**Defined in:** `.github/` directory (workflow files). + +**Responsibilities:** +- Run `npm test` on push / PR (vitest suite). +- On tag push (e.g. `v*`), run `prepublishOnly` and publish to npm. + +**Inputs:** source changes on `main` or version tags. +**Outputs:** npm package `emotesjs` at the new version. + +--- + +## Agent: Consumer Application + +**Role:** Any application that imports and uses `emotesjs` at runtime. + +**Typical lifecycle:** + +```ts +import { EmotesJS } from 'emotesjs' + +// 1. Instantiate (triggers async load from 7TV API) +const emotes = new EmotesJS({ channelId: 123456 }) + +// 2. Wait for emotes to be ready +await emotes.isLoading + +// 3. Parse chat messages +const html = emotes.parse('this is pretty Pog') +``` + +**Cache-first variant** (no network on startup): +```ts +const cached = localStorage.getItem('emoteCache') +const emotes = cached + ? EmotesJS.fromCache(cached) + : new EmotesJS({ channelId: 123456 }) + +await emotes.isLoading +localStorage.setItem('emoteCache', emotes.cache()) +``` + +**Options surface:** + +| Option | Type | Default | Purpose | +|---|---|---|---| +| `channelId` | `number` | `0` | Twitch channel to load emotes for | +| `only` | `string[]` | all | Allowlist of emote names to include | +| `usePixelDensity` | `boolean` | `false` | Use `1x/2x/3x/4x` descriptors instead of `w` | +| `colon` | `boolean` | `false` | Require `:emote:` syntax (colon-wrapped) | +| `height` | `string` | `"1.65rem"` | CSS height applied to each `` | +| `format` | `string` | `"WEBP"` | Image format filter (`WEBP`, `AVIF`, etc.) | +| `proxy` | `string` | `""` | Replace `https://cdn.7tv.app` with a custom CDN origin | +| `cache` | `string` | — | JSON string from a previous `emotes.cache()` call | + +**Gotchas:** +- `parse()` returns the original text (not empty string) while `isLoading` is pending, so it is safe to call speculatively. +- The class is a singleton keyed on `channelId !== 0`; a second `new EmotesJS(...)` call with the same or a different non-zero `channelId` returns the existing instance. +- Global emotes (not channel-specific) are always loaded alongside channel emotes; failures are silently swallowed via `Promise.allSettled`. diff --git a/SKILLS.md b/SKILLS.md new file mode 100644 index 0000000..23c1a5e --- /dev/null +++ b/SKILLS.md @@ -0,0 +1,99 @@ +# Skills — emotesjs + +## What & Why + +emotesjs exists to solve a specific problem in Twitch/streaming chat UIs: **rendering 7TV emotes inline in plain text**. When a viewer types an emote name like `Pog` in chat, the raw message is just a string. emotesjs fetches the emote definitions for a channel (plus global emotes) from the 7TV API and replaces those tokens with responsive `` tags — no dependencies, no bundler magic, just a small ESM library you drop in. + +**Use it when you are building:** +- A custom Twitch chat overlay (e.g. for OBS/stream software) +- A chat replay or VOD viewer +- A stream dashboard or moderation tool that renders chat +- Any web app that needs to display 7TV emotes alongside regular text + +It is **not** a full chat client or a WebSocket listener — it only handles the emote-replacement layer. You bring your own chat messages. + +--- + +## How to Use + +### Install +```bash +npm i emotesjs +``` + +### Basic usage +```ts +import { EmotesJS } from 'emotesjs' + +const emotes = new EmotesJS({ channelId: 123456 }) +await emotes.isLoading + +const html = emotes.parse('this is pretty Pog') +// → 'this is pretty Pog' +``` + +### With caching (skip the network on repeat visits) +```ts +const emotes = new EmotesJS({ + channelId: 123456, + cache: localStorage.getItem('emoteCache') ?? undefined, +}) +await emotes.isLoading +localStorage.setItem('emoteCache', emotes.cache()) +``` + +### Only load specific emotes +```ts +const emotes = new EmotesJS({ + channelId: 123456, + only: ['Pog', 'KEKW', 'OMEGALUL'], +}) +``` + +### Colon syntax (`:emote:` style) +```ts +const emotes = new EmotesJS({ channelId: 123456, colon: true }) +await emotes.isLoading +emotes.parse('this is :Pog:') // replaces :Pog: with the img tag +``` + +### Custom CDN proxy +```ts +const emotes = new EmotesJS({ + channelId: 123456, + proxy: 'https://my-cdn.example.com', +}) +// CDN URLs will use your proxy instead of cdn.7tv.app +``` + +--- + +## Language & Runtime +- **TypeScript** (100% of codebase) targeting ESM output (`"type": "module"`) +- **Node.js ≥ 22** required +- Build toolchain: **tsup** (bundle) + **terser** (minify) + **vitest** (test) + +## Core Domain: 7TV Emote Parsing +- Fetches emote sets from the **7TV REST API** (`https://7tv.io/v3/`) + - Global emote set: `GET /v3/emote-sets/global` + - Channel emote set: `GET /v3/users/twitch/:channelId` +- Parses raw API responses into typed interfaces (`SevenTVChannelEmotes`, `EmoteSet`, `Emote`, `Host`, `File`, etc.) +- Builds responsive `` HTML strings per emote, supporting multiple resolutions (`1x`–`4x`) and both **width-descriptor** (`128w`) and **pixel-density** (`2x`) srcset modes +- Outputs `crossorigin` images with configurable inline `height` style + +## EmotesJS Class Capabilities +- **Singleton pattern** — one instance per `channelId`; constructor returns the cached instance on repeat calls +- **Cache system** — emote map serialisable to/from JSON string (`cache()` / `fromCache()`) for persistence between page loads +- **Configurable options**: `channelId`, `only` (allowlist), `usePixelDensity`, `colon` (`:emote:` syntax toggle), `height`, `format` (WEBP default), `proxy` (CDN origin swap), `cache` +- **Origin allowlist** — only URLs starting with `https://cdn.7tv.app` are stored (prevents injecting arbitrary HTML) +- **`parse(text)`** — splits input by spaces, replaces matching tokens (optionally colon-wrapped) with the pre-built `` strings; returns original text untouched while emotes are loading + +## Testing +- **vitest** test suite in `index.test.ts` +- Run with `npm test` + +## Publishing +- Package name: `emotesjs` on npm +- Exports: ESM only (`./dist/index.js`), ships type declarations (`./dist/index.d.ts`) +- `prepublishOnly` script: `tsup` then `terser` minification +- Zero runtime dependencies diff --git a/package.json b/package.json index 031d4f4..8c6666a 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,17 @@ "twitch", "7tv", "emotes", - "responsive" + "responsive", + "chat", + "chat-overlay", + "inline-emotes", + "zero-dependencies", + "emote-parser", + "srcset", + "stream", + "streaming", + "typescript", + "esm" ], "scripts": { "test": "vitest run",