Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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 `<img>` 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 <img srcset="..." alt="Pog" style="height:1.65rem" crossorigin/>'
```

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 `<img>` |
| `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`.
99 changes: 99 additions & 0 deletions SKILLS.md
Original file line number Diff line number Diff line change
@@ -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 `<img>` 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 <img srcset="..." alt="Pog" style="height:1.65rem" crossorigin/>'
```

### 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 `<img srcset="...">` 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 `<img>` 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
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down