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
31 changes: 3 additions & 28 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,17 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages

name: Node.js Package

on:
release:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
- run: pnpm i
- run: pnpm test

publish-npm:
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org/
- run: pnpm publish --access public --no-git-checks
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
- run: mise publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
18 changes: 4 additions & 14 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages

name: PR
on: [pull_request]

jobs:
build:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
- run: pnpm i
- run: pnpm test
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
- run: mise test
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,26 @@

Fast, dependency free, and responsive 7TV inline emotes parse

## Basic usage

```sh
npm i emotesjs
```

```ts
import { EmotesJS } from 'emotesjs'

let emotes = new EmotesJS({ channelId: 123456 })

let html = emotes.parse('this is pretty Pog')

console.log(html)
// this is pretty <img srcset="https://cdn.7tv.app/emote/01EZTCN91800012PTN006Q50PR/4x.webp 128w,
// https://cdn.7tv.app/emote/01EZTCN91800012PTN006Q50PR/3x.webp 96w,
// https://cdn.7tv.app/emote/01EZTCN91800012PTN006Q50PR/2x.webp 64w,
// https://cdn.7tv.app/emote/01EZTCN91800012PTN006Q50PR/1x.webp 32w"
// alt="Pog" style="height:1.65rem"
// />
```

[Check the documentation here](https://darckfast.com/docs/emotesjs)
2 changes: 1 addition & 1 deletion index.test.js → index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { beforeAll, describe, expect, test } from 'vitest'
import { EmotesJS } from './index'

describe('EmotesJS: parse', () => {
let emotes
let emotes: EmotesJS

beforeAll(async () => {
emotes = new EmotesJS({ channelId: 38746172, colon: true })
Expand Down
202 changes: 145 additions & 57 deletions index.js → index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,144 @@
class EmotesJS {
/** @default Map<string, string> */
#cachedEmotes = new Map();
/** @default true */
export interface SevenTVChannelEmotes {
id: string
platform: string
username: string
display_name: string
linked_at: number
emote_capacity: number
emote_set_id: string
emote_set: EmoteSet
user: User
}

export interface EmoteSet {
id: string
name: string
flags: number
tags: any[]
immutable: boolean
privileged: boolean
emotes: Emote[]
emote_count: number
capacity: number
owner: any
}

export interface Emote {
id: string
name: string
flags: number
timestamp: number
actor_id?: string
data: Data
origin_id: any
}

export interface Data {
id: string
name: string
flags: number
lifecycle: number
state: string[]
listed: boolean
animated: boolean
owner?: Owner
host: Host
tags?: string[]
}

export interface Owner {
id: string
username: string
display_name: string
avatar_url?: string
style: Style
role_ids: string[]
connections: Connection[]
}

export interface Style {
color?: number
paint_id?: string
badge_id?: string
}



export interface Connection {
id: string
platform: string
username: string
display_name: string
linked_at: number
emote_capacity: number
emote_set_id: string
emote_set: any
}

export interface Host {
url: string
files: File[]
}

export interface File {
name: string
static_name: string
width: number
height: number
frame_count: number
size: number
format: string
}

export interface User {
id: string
username: string
display_name: string
created_at: number
avatar_url: string
style: Style
emote_sets: EmoteSet[]
editors: Editor[]
roles: string[]
connections: Connection[]
}

export interface Editor {
id: string
permissions: number
visible: boolean
added_at: number
}



interface Opts {
channelId?: number
only?: Array<string>
usePixelDensity?: boolean
colon?: boolean
height?: string
format?: string
cache?: string
proxy?: string
}

export class EmotesJS {
#cachedEmotes = new Map<string, string>();
#colon = true;
/** @default "1.65rem" */
#height = "1.65rem";
/** @default "WEBP" */
#format = "WEBP";
/** @default "https://cdn.7tv.app" */
#allowedOrigins = "https://cdn.7tv.app";
/** @default false */
#isReady = false;
/** @default false */
#usePixelDensity = false;
/** @default 0 */
total = 0;
/** @default 0 */
channelId = 0;
/** @default Promise<void> */
isLoading = Promise.resolve();
/** @default "" */
proxy = ""

/**
* @static
*/
static instance;
/**
* @param {Opts} opts
*/
constructor(opts) {
static instance: EmotesJS;

constructor(opts: Opts) {
if (EmotesJS.instance && EmotesJS.instance.channelId !== 0) {
return EmotesJS.instance;
}
Expand All @@ -47,37 +156,28 @@ class EmotesJS {
this.#isReady = true;
}

this.proxy = opts.proxy
this.proxy = opts.proxy || ""
}
this.isLoading = this.load(only);
EmotesJS.instance = this;
}
/**
* @static
* @param {string} cache
* @returns {EmotesJS}
*/
static fromCache(cache) {

static fromCache(cache: string) {
return new EmotesJS({ cache });
}
/**
* @returns {string}
*/

cache() {
return JSON.stringify(Object.fromEntries(this.#cachedEmotes.entries()));
}
/**
* @param {string[]} [only=[]]
* @returns {Promise<void>}
*/
async load(only = []) {
let globalProm = fetch("https://7tv.io/v3/emote-sets/global").then(r => {

async load(only: Array<string> = []) {
let globalProm: Promise<EmoteSet> = fetch("https://7tv.io/v3/emote-sets/global").then(r => {
if (r.ok) {
return r.json();
}
throw new Error("fetch unsuccessful");
});
let chProm = fetch("https://7tv.io/v3/users/twitch/" + this.channelId).then(r => {
let chProm: Promise<SevenTVChannelEmotes> = fetch("https://7tv.io/v3/users/twitch/" + this.channelId).then(r => {
if (r.ok) {
return r.json();
}
Expand Down Expand Up @@ -110,7 +210,8 @@ class EmotesJS {
let srcset = files.reduce((acc, curr) => {
let w = `${curr.width}w`;
if (this.#usePixelDensity) {
[w] = curr.name.split('.');
//@ts-ignore
[w] = curr.name.split('.') || '';
}
return `${url}/${curr.name} ${w}, ${acc}`;
}, "");
Expand All @@ -120,11 +221,8 @@ class EmotesJS {
this.total = this.#cachedEmotes.size;
this.#isReady = true;
}
/**
* @param {string | undefined} text
* @returns {string}
*/
parse(text) {

parse(text: string) {
if (!text) {
console.log("no text to parse emotes");
return "";
Expand All @@ -141,11 +239,11 @@ class EmotesJS {
let fullText = "";
for (let i = 0; i < words.length; i++) {
let word = words[i];
if (this.#colon && !word.startsWith(":")) {
if (this.#colon && !word?.startsWith(":")) {
fullText += word + " ";
continue;
}
let wordKey = word.replaceAll(":", "");
let wordKey = word?.replaceAll(":", "") || '';
let emote = this.#cachedEmotes.get(wordKey);
if (emote) {
fullText += emote + " ";
Expand All @@ -156,14 +254,4 @@ class EmotesJS {
return fullText.trim();
}
}
module.exports = { EmotesJS };
/**
* @typedef {Object} Opts
* @property {number} [channelId]
* @property {string[]} [only]
* @property {boolean} [colon]
* @property {string} [height]
* @property {"WEBP" | "AVIF"} [format]
* @property {boolean} [usePixelDensity]
* @property {string} [cache]
*/

18 changes: 18 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[tools]
node = "26"
pnpm = "10.33"

[tasks.prepare]
run = "pnpm install"

[tasks.build]
depends = ["prepare"]
run = "pnpx tsup && pnpx terser ./dist/index.js -o ./dist/index.js"

[tasks.test]
depends = ["prepare"]
run = "pnpm run test"

[tasks.publish]
depends = ["prepare"]
run = "pnpm publish --access public --no-git-checks"
Loading