import { EventEmitter } from 'events'; interface TikTokUser { id: string; nickname: string; uniqueId: string; profilePicture?: string; /** High-resolution avatar URL from protobuf field 11 (avatarLarge ~720x720) */ avatarLargeUrl?: string; badges?: string[]; } interface BaseEvent { type: string; timestamp: number; msgId: string; } interface ChatEvent extends BaseEvent { type: 'chat'; user: TikTokUser; comment: string; emotes?: Array<{ emoteId: string; imageUrl: string; placeInComment: number; }>; } interface MemberEvent extends BaseEvent { type: 'member'; user: TikTokUser; action: number; } interface LikeEvent extends BaseEvent { type: 'like'; user: TikTokUser; likeCount: number; totalLikes: number; } interface GiftEvent extends BaseEvent { type: 'gift'; user: TikTokUser; giftId: number; giftName: string; diamondCount: number; repeatCount: number; repeatEnd: boolean; combo: boolean; giftType: number; groupId: string; } interface SocialEvent extends BaseEvent { type: 'social'; user: TikTokUser; action: 'follow' | 'share' | string; } interface RoomUserSeqEvent extends BaseEvent { type: 'roomUserSeq'; viewerCount: number; totalViewers: number; } interface BattleTeamUser { user: TikTokUser; score: number; } interface BattleTeam { hostUserId: string; hostUser?: TikTokUser; score: number; users: BattleTeamUser[]; } interface BattleEvent extends BaseEvent { type: 'battle'; battleId: string; status: number; battleDuration: number; teams: BattleTeam[]; } interface BattleContributor { /** TikTok userId of the gifter */ userId: string; /** Diamond score this gifter contributed to the host's team */ score: number; /** Display nickname (may be empty if not set on the account) */ nickname: string; } interface BattleHost { /** TikTok userId of the host on this PK side */ hostUserId: string; /** Total diamonds for this host's team */ teamTotalScore: number; /** Side index (0 = left side, 1 = right side, …) */ teamIdx: number; /** Per-gifter breakdown, sorted MVP first (highest score → lowest). */ contributors: BattleContributor[]; } interface BattleArmiesEvent extends BaseEvent { type: 'battleArmies'; battleId: string; status: number; teams: BattleTeam[]; /** Battle start timestamp in milliseconds (from f18.f2) */ battleStartMs: number; /** Total battle duration in seconds (from f18.f3, typically 300) */ battleDurationSeconds: number; /** Seconds remaining in the battle, computed as max(0, duration - elapsed). Note: uses VPS clock, may be off by up to 30s */ timeLeftSeconds: number; /** Battle end timestamp in milliseconds (battleStartMs + duration*1000). Use for clock-independent timer: Math.max(0, (endTimeMs - Date.now()) / 1000) */ endTimeMs: number; /** Stable match ID across multi-round PK (from top-level field 2). */ matchId?: string; /** Per-round session ID (from top-level field 4). */ sessionId?: string; /** TikTok server-side clock at frame emit (ms epoch). */ serverTsMs?: number; /** Hex string tag used by TikTok for per-session matching (~34 chars). */ sessionTag?: string; /** Total battle duration in seconds (from f18.f3). Alias for battleDurationSeconds. */ durationSec?: number; /** Seconds remaining computed using TikTok server clock (no VPS drift). */ secsRemaining?: number; /** * Multi-guest host breakdown — one entry per host on a PK side. * Each entry includes per-gifter contributors sorted MVP first. */ hosts?: BattleHost[]; } /** * Battle Item Card event — booster multipliers (x2/x3), gloves, mist, thunder, * extra-time, match-guide, etc. Emitted on `battleItemCard`. */ interface BattleItemCardEvent extends BaseEvent { type: 'battleItemCard'; battleId: string; /** 2=gloves/crit, 3=mist, 4=match_guide, 10=x2?, 11=x3, … */ cardType: number; /** 'gloves' | 'mist' | 'booster_x2' | 'booster_x3' | 'match_guide' | 'thunder' | 'extra_time' | raw resource key */ effect: string; /** Raw TikTok resource key (e.g. 'pm_mt_boost_crit_name'). */ effectKey: string; /** 2 or 3 for booster_x2/x3, otherwise 0. */ multiplier: number; senderUserId: string; senderNickname: string; senderUniqueId: string; /** First CDN URL for sender's avatar (best-resolution). */ senderAvatarUrl: string; /** Unix seconds when the buff activated. */ activatedAtSec: number; /** Total active duration in seconds. */ durationSec: number; /** Unix seconds when the buff ends. */ endsAtSec: number; /** Comment template, e.g. "{0:user} sent 1 magic mist". */ commentTemplate: string; /** Full TikTok CDN URL for the card art (webp/jpeg). */ iconUrl: string; /** Short identifier e.g. 'card_mist_v3' / 'card_crit_v3' / 'top3_buffer'. */ iconKey: string; /** Hex e.g. '#BCD9E0' (mist blue), '#E0D4BC' (gloves tan). */ accentColor: string; } interface SubscribeEvent extends BaseEvent { type: 'subscribe'; user: TikTokUser; subMonth: number; } interface EmoteChatEvent extends BaseEvent { type: 'emoteChat'; user: TikTokUser; emoteId: string; emoteUrl: string; } interface EnvelopeEvent extends BaseEvent { type: 'envelope'; envelopeId: string; diamondCount: number; } interface QuestionEvent extends BaseEvent { type: 'question'; user: TikTokUser; questionText: string; } interface ControlEvent extends BaseEvent { type: 'control'; action: number; } interface RoomEvent extends BaseEvent { type: 'room'; status: number; } interface LiveIntroEvent extends BaseEvent { type: 'liveIntro'; roomId: string; title: string; } interface RankUpdateEvent extends BaseEvent { type: 'rankUpdate'; rankType: string; rankList: Array<{ user: TikTokUser; rank: number; score: number; }>; } interface LinkMicEvent extends BaseEvent { type: 'linkMic'; action: string; users: TikTokUser[]; } interface UnknownEvent extends BaseEvent { type: 'unknown'; method: string; } type LiveEvent = ChatEvent | MemberEvent | LikeEvent | GiftEvent | SocialEvent | RoomUserSeqEvent | BattleEvent | BattleArmiesEvent | BattleItemCardEvent | SubscribeEvent | EmoteChatEvent | EnvelopeEvent | QuestionEvent | ControlEvent | RoomEvent | LiveIntroEvent | RankUpdateEvent | LinkMicEvent | UnknownEvent; interface TikTokLiveEvents { connected: () => void; disconnected: (code: number, reason: string) => void; roomInfo: (info: RoomInfo) => void; error: (error: Error) => void; chat: (event: ChatEvent) => void; member: (event: MemberEvent) => void; like: (event: LikeEvent) => void; gift: (event: GiftEvent) => void; social: (event: SocialEvent) => void; roomUserSeq: (event: RoomUserSeqEvent) => void; battle: (event: BattleEvent) => void; battleArmies: (event: BattleArmiesEvent) => void; battleItemCard: (event: BattleItemCardEvent) => void; subscribe: (event: SubscribeEvent) => void; emoteChat: (event: EmoteChatEvent) => void; envelope: (event: EnvelopeEvent) => void; question: (event: QuestionEvent) => void; control: (event: ControlEvent) => void; room: (event: RoomEvent) => void; liveIntro: (event: LiveIntroEvent) => void; rankUpdate: (event: RankUpdateEvent) => void; linkMic: (event: LinkMicEvent) => void; unknown: (event: UnknownEvent) => void; event: (event: LiveEvent) => void; } interface RoomInfo { roomId: string; wsHost: string; clusterRegion: string; connectedAt: string; } /** Quality tiers available for live stream video */ type StreamQuality = 'FULL_HD1' | 'HD1' | 'SD1' | 'SD2' | 'origin' | string; /** Stream URLs for a specific quality level */ interface StreamUrls { /** FLV pull URL for this quality */ flv?: string; /** HLS (m3u8) pull URL for this quality */ hls?: string; } /** Full stream info returned by getStreamUrl() */ interface StreamInfo { /** Room ID of the live stream */ roomId: string; /** Whether the user is currently live */ alive: boolean; /** Stream URLs keyed by quality (FULL_HD1, HD1, SD1, SD2) */ streamUrls: Record; /** Recommended default quality */ defaultQuality: StreamQuality; /** Best available FLV URL (convenience shortcut) */ flvPullUrl?: string; /** Best available HLS URL (convenience shortcut) */ hlsPullUrl?: string; } interface TikTokLiveOptions { uniqueId: string; signServerUrl?: string; apiKey: string; autoReconnect?: boolean; maxReconnectAttempts?: number; heartbeatInterval?: number; debug?: boolean; /** Pre-resolved room ID — skips direct TikTok page fetch when provided with sessionId */ roomId?: string; /** Pre-resolved ttwid session cookie — skips direct TikTok page fetch when provided with roomId */ sessionId?: string; /** * Optional outbound HTTP(S) gateway URL. When set, the SDK's outbound * WebSocket and HTTP requests are routed through this gateway instead * of the host's default network interface. Useful for environments that * need a stable egress IP or that already centralize outbound traffic * through a corporate gateway. * * Format: `http://user:pass@host:port` or `http://host:port`. */ proxy?: string; /** * Connection mode. * - `'direct'` (default): SDK opens the WebSocket from your runtime to * TikTok using credentials signed by the TikTools sign server. * Best for low-volume use where you'd rather not stream events * through our infrastructure. * - `'relayed'`: SDK connects to the TikTools managed edge * (`wss://api.tik.tools/...`). The TikTools service handles the * upstream TikTok session and forwards decoded events to your * client. Best for production scale and for environments where * you want a single, stable egress through TikTools. * * The decoded events emitted are identical in both modes — code that * uses `client.on('chat', e => ...)` works without changes. */ mode?: 'direct' | 'relayed'; } declare class TikTokLive extends EventEmitter { private ws; private heartbeatTimer; private reconnectAttempts; private intentionalClose; private _connected; private _destroyed; private _eventCount; private _roomId; private static readonly MAX_BATTLE_HOSTS; private _battleHosts; private _pendingHostResolves; private readonly uniqueId; private readonly signServerUrl; private readonly apiKey; private readonly autoReconnect; private readonly maxReconnectAttempts; private readonly heartbeatInterval; private readonly debug; private readonly _presetRoomId; private readonly _presetSessionId; private readonly proxyUrl; private readonly mode; constructor(options: TikTokLiveOptions); /** * Build an HttpsProxyAgent when `proxy` option is set, else undefined. * Lazy-required so users without proxy don't pay the dependency cost. */ private getProxyAgent; connect(): Promise; disconnect(): void; /** * Fully destroy the client, releasing all resources and listeners. * After calling destroy(), the instance cannot be reused — create a new one. */ destroy(): void; get connected(): boolean; get destroyed(): boolean; get eventCount(): number; get roomId(): string; /** * Get live stream video URLs for a TikTok user. * Returns FLV & HLS URLs segmented by quality (FULL_HD1, HD1, SD1, SD2). * This is a standalone method — no WebSocket connection required. * * @example * ```ts * const stream = await TikTokLive.getStreamUrl({ * uniqueId: 'username', * apiKey: 'your-api-key', * }); * if (stream.alive) { * console.log(stream.streamUrls.HD1?.hls); // HLS URL for HD quality * console.log(stream.flvPullUrl); // Best FLV URL * } * ``` */ static getStreamUrl(options: { uniqueId: string; apiKey: string; signServerUrl?: string; quality?: string; }): Promise; /** * Resolve a TikTok username to the streamer's full public profile — * numeric user ID, secUid, nickname, bio, avatars, follower stats. * * Useful when you need the numeric TikTok ID for a username (the inverse * of `resolve_user_ids`, which only goes userId → username). * * Requires Pro tier+. Cached server-side for 24h. * * @example * ```ts * const profile = await TikTokLive.getUserProfile({ * uniqueId: 'dalga.ahmedov', * apiKey: 'YOUR_KEY', * }); * console.log(profile.id); // "7355610677036581896" (numeric) * console.log(profile.nickname); // display name * console.log(profile.stats.followerCount); // 12345 * console.log(profile.avatarLarger); // CDN URL * ``` */ static getUserProfile(options: { uniqueId: string; apiKey: string; signServerUrl?: string; /** Set `true` to bypass the 24h server cache (e.g. after the user updated their bio). */ nocache?: boolean; }): Promise<{ id: string; uniqueId: string; secUid: string; nickname: string; signature: string; verified: boolean; avatarThumb: string; avatarMedium: string; avatarLarger: string; stats: { followerCount: number; followingCount: number; heartCount: number; videoCount: number; }; }>; /** * Instance shortcut — fetch the profile of the streamer this client is * connected to (or any other user if `uniqueId` is passed). */ getUserProfile(uniqueId?: string): Promise<{ id: string; uniqueId: string; secUid: string; nickname: string; signature: string; verified: boolean; avatarThumb: string; avatarMedium: string; avatarLarger: string; stats: { followerCount: number; followingCount: number; heartCount: number; videoCount: number; }; }>; on(event: K, listener: TikTokLiveEvents[K]): this; once(event: K, listener: TikTokLiveEvents[K]): this; off(event: K, listener: TikTokLiveEvents[K]): this; emit(event: K, ...args: Parameters): boolean; private handleFrame; /** * Resolve unknown host user IDs via the sign server API. * Caches results and re-emits the battleArmies event with enriched hostUser data. */ private resolveHostUsers; private startHeartbeat; private stopHeartbeat; /** * Relayed-mode connect. Opens a plain WebSocket to TikTools' managed relay * endpoint. Events arrive pre-decoded as `{event, data}` JSON envelopes. * The SDK re-emits them on the same event names as Direct mode so user * code is identical regardless of mode. */ private _connectRelayed; } export { type BaseEvent, type BattleArmiesEvent, type BattleContributor, type BattleEvent, type BattleHost, type BattleItemCardEvent, type BattleTeam, type BattleTeamUser, type ChatEvent, type ControlEvent, type EmoteChatEvent, type EnvelopeEvent, type GiftEvent, type LikeEvent, type LinkMicEvent, type LiveEvent, type LiveIntroEvent, type MemberEvent, type QuestionEvent, type RankUpdateEvent, type RoomEvent, type RoomInfo, type RoomUserSeqEvent, type SocialEvent, type StreamInfo, type StreamQuality, type StreamUrls, type SubscribeEvent, TikTokLive, type TikTokLiveEvents, type TikTokLiveOptions, type TikTokUser, type UnknownEvent };