UNPKG

twitch-emote

Version:

splice twitch emotes from a message string

145 lines (131 loc) 4.67 kB
import { getSetting } from './settings' import { ChannelIdentifier, EmoteData } from './types' import { correctServices, isChannelThrow, Services, sleep } from './util' export type ApiResponseTypes = ChannelIdentifier | EmoteData[] export interface ApiResponseHeaders { limit: string | null remaining: string | null reset: string | null } export interface ApiResponse<T> extends ApiResponseHeaders { data: T | null error: string | null } /** * @returns ApiResponse or false * * false means the request was rate limited, and the time remaining has */ async function handleResponse<T>( url: string, retryCount: number = 0, prevHeaders?: ApiResponseHeaders ): Promise<ApiResponse<T>> { if (retryCount && prevHeaders && retryCount > getSetting('maxRetryRateLimit')) { return { ...prevHeaders, data: null, error: 'Rate limit exceeded', } } let res = await fetch(url) let code = res.status let headers = { limit: res.headers.get('X-Ratelimit-Limit'), remaining: res.headers.get('X-Ratelimit-Remaining'), reset: res.headers.get('X-Ratelimit-Reset'), } let data = null try { data = await res.json() } catch (e) { return { ...headers, data: null, error: (e as Error).message, } } if (code === 429) { //rate limited let retry = res.headers.get('Retry-After') || '20' //20 seconds is the max rate limit time let time = parseInt(retry) * 1000 await sleep(time) return await handleResponse(url, ++retryCount, headers) } else if (code === 400 || 'error' in data) { return { ...headers, data: null, error: data.error || 'Unknown error', } } return { ...headers, data: data, error: null, } } /** * @returns \{ data }: url to the emote image, or null if not found */ async function handleProxyResponse(url: string): Promise<ApiResponse<string>> { let res = await fetch(url) return { limit: res.headers.get('X-Ratelimit-Limit'), remaining: res.headers.get('X-Ratelimit-Remaining'), reset: res.headers.get('X-Ratelimit-Reset'), data: res.status === 307 ? res.url : null, error: null, } } /** * Returns global emotes * @param services Possible values: all or any combination of: twitch, 7tv, bttv, ffz combined using dots (e.g. twitch.7tv) * * Math pattern `/^[^\.]+(all|(\.?twitch|\.?7tv|\.?bttv|\.?ffz)+)$/` */ export const globalEmotes = (services: Services = 'all'): Promise<ApiResponse<EmoteData[]>> => handleResponse<EmoteData[]>( `https://emotes.adamcy.pl/v1/global/emotes/${correctServices(services)}` ) /** * Returns channel emotes * @param channel It's recommended to provide twitch id, but twitch login is also supported * @param services Possible values: all or any combination of: twitch, 7tv, bttv, ffz combined using dots (e.g. twitch.7tv) * * Math pattern `/^[^\.]+(all|(\.?twitch|\.?7tv|\.?bttv|\.?ffz)+)$/` */ export const channelEmotes = ( channel: string, services: Services = 'all' ): Promise<ApiResponse<EmoteData[]>> => handleResponse<EmoteData[]>( `https://emotes.adamcy.pl/v1/channel/${isChannelThrow(channel)}/emotes/${correctServices( services )}` ) /** * Returns basic identifiers (id, login, display name) * @param channel It's recommended to provide twitch id, but twitch login is also supported */ export const channelIdentifier = (channel: string): Promise<ApiResponse<ChannelIdentifier>> => handleResponse<ChannelIdentifier>( `https://emotes.adamcy.pl/v1/channel/${isChannelThrow(channel)}/id` ) /** * Proxies directly to emote's URL. * * This proxy can find a bunch of use-cases. One of them could be a simple img element, where you don't need to fetch, parse and extract the right emote URL on your own. * * To prevent flooding API with requests, each emote fetched thru proxy endpoint will be cached for 7 days by the browser. * @param channel It's recommended to provide twitch id, but twitch login is also supported * @param services Possible values: all or any combination of: twitch, 7tv, bttv, ffz combined using dots (e.g. twitch.7tv) */ export const proxyChannelEmote = ( channel: string, services: Services = 'all' ): Promise<ApiResponse<string | null>> => handleProxyResponse( `https://emotes.adamcy.pl/v1/channel/${isChannelThrow(channel)}/emotes/${correctServices( services )}/proxy` )