@adamjessop/tiktok-live-connector
Version:
Node.js module to receive live stream chat events like comments and gifts from TikTok LIVE
963 lines (849 loc) • 80.2 kB
Markdown
# TikTok-Live-Connector
A Node.js library to receive live stream events such as comments and gifts in realtime
from [TikTok LIVE](https://www.tiktok.com/live) by connecting to TikTok's internal Webcast push service.
This package includes a wrapper that connects to the Webcast service using just the username (`@uniqueId`).
This allows you to connect to your own live chat as well as the live chat of other streamers. No credentials are
required. Besides [Chat Comments](#chat), other events such
as [Members Joining](#member), [Gifts](#gift), [Subscriptions](#subscribe), [Viewers](#roomuser), [Follows](#social), [Shares](#social), [Questions](#questionnew), [Likes](#like)
and [Battles](#linkmicbattle) can be tracked.





> [!NOTE]
> This is not an official API. It is a reverse engineering project. You are responsible for your usage of this library.
> [!TIP]
> An example project is available
> at https://tiktok-chat-reader.zerody.one/ - [View Source](https://github.com/zerodytrash/TikTok-Chat-Reader)
> [!WARNING]
> This JavaScript library is intended for use in [Node.js](https://nodejs.org/) environments. If you want to process or
> display the data in the browser (client-side), you need to transfer the data from the Node.js environment to the
> browser. A good approach for this is to use [Socket.IO](https://socket.io/) or a different low-latency communication
> framework. A complete example project can be found
> here: [TikTok-Chat-Reader](https://github.com/zerodytrash/TikTok-Chat-Reader)
> [!WARNING]
> Due to a change on the part of TikTok, versions prior **v1.1.7** are no longer functional. If you are using one of
> these versions, upgrade to the latest version using the `npm i tiktok-live-connector` command.
### 📌 2.0.0 Now Available - Upgrade Now!
We are thrilled to announce that the 2.0.0 release of TikTok Live Connector is now available!
This release converts the package to exclusively typescript-based, also providing complete backwards compatibility for users.
#### Upgrade Guide
- Install via `npm i tiktok-live-connector@2.0.0`
- Switch `WebcastPushConnection` to `TikTokLiveConnection` (or keep for legacy support)
- Switch Event Names to `WebcastEvent.EVENT_NAME` from `event_name` format
- That's it! You're good to go!
- If you have any issues, please open an issue on GitHub or ask on [Discord](https://discord.gg/2c6kX6g3Pa).
#### Breaking Changes
- The `sendMessage` function has been removed (for now)
- `WebcastPushConnection` has been renamed to `TikTokLiveConnection`
- CommonJS support has been removed
- Data fetching functions have been renamed
### Do you prefer other programming languages?
- **Python** rewrite: [TikTokLive](https://github.com/isaackogan/TikTokLive)
by [@isaackogan](https://github.com/isaackogan) and [@eulerstream](https://github.com/eulerstream/)
- **Java** rewrite: [TikTokLiveJava](https://github.com/jwdeveloper/TikTokLiveJava)
by [@jwdeveloper](https://github.com/jwdeveloper) and [@kohlerpop1](https://github.com/kohlerpop1)
- **Go** rewrite: [GoTikTokLive](https://github.com/steampoweredtaco/gotiktoklive)
by [@steampoweredtaco](https://github.com/steampoweredtaco) and [@davincible](https://github.com/davincible)
- **C#** rewrite: [TikTokLiveSharp](https://github.com/frankvHoof93/TikTokLiveSharp)
by [@frankvHoof93](https://github.com/frankvHoof93)
### Table of Contents
- [Getting Started](#getting-started)
- [Params and Options](#params-and-options)
- [Methods](#methods)
- [Properties](#properties)
- [Events](#events)
- [Examples](#examples)
- [Contributing](#contributing)
## Getting Started
1. Install the package via NPM
```bash
npm i tiktok-live-connector
```
2. Create your first TikTok LIVE chat connection
```ts
// Username of someone who is currently live
import { TikTokLiveConnection, WebcastEvent } from 'tiktok-live-connector';
const tiktokUsername = 'officialgeilegisela';
// Create a new wrapper object and pass the username
const connection = new TikTokLiveConnection(tiktokUsername);
// Connect to the chat (await can be used as well)
connection.connect().then(state => {
console.info(`Connected to roomId ${state.roomId}`);
}).catch(err => {
console.error('Failed to connect', err);
});
// Define the events that you want to handle
// In this case we listen to chat messages (comments)
connection.on(WebcastEvent.CHAT, data => {
console.log(`${data.user.uniqueId} (userId:${data.user.uniqueId}) writes: ${data.comment}`);
});
// And here we receive gifts sent to the streamer
connection.on(WebcastEvent.GIFT, data => {
console.log(`${data.user.uniqueId} (userId:${data.user.userId}) sends ${data.giftId}`);
});
// ...and more events described in the documentation below
```
## Params and Options
To create a new `TikTokLiveConnection` object the following parameters can be specified.
`TikTokLiveConnection(uniqueId, [options])`
| Param Name | Required | Description |
|------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| uniqueId | Yes | The unique username of the broadcaster. You can find this name in the URL.<br>Example: `https://www.tiktok.com/@officialgeilegisela/live` => `officialgeilegisela` |
| options | No | Here you can set the following optional connection properties. If you do not specify a value, the default value will be used.<br><br>`processInitialData` (default: `true`) <br> Define if you want to process the initital data which includes old messages of the last seconds.<br><br>`fetchRoomInfoOnConnect` (default: `true`) <br> Define if you want to fetch all room information on [`connect()`](#methods). If this option is enabled, the connection to offline rooms will be prevented. If enabled, the connect result contains the room info via the `roomInfo` attribute. You can also manually retrieve the room info (even in an unconnected state) using the [`getRoomInfo()`](#methods) function.<br><br>`enableExtendedGiftInfo` (default: `false`) <br> Define if you want to receive extended information about gifts like gift name, cost and images. This information will be provided at the [gift event](#gift). <br><br>`requestPollingIntervalMs` (default: `1000`) <br> Request polling interval if WebSocket is not used.<br><br>`sessionId` (default: `null`) <br> Here you can specify the current Session ID of your TikTok account <br><br>`webClientParams` (default: `{}`) <br> Custom client params for Webcast API.<br><br>`webClientHeaders` (default: `{}`) <br> Custom request headers passed to [axios](https://github.com/axios/axios).<br><br>`websocketHeaders` (default: `{}`) <br> Custom websocket headers passed to [websocket.client](https://github.com/theturtle32/WebSocket-Node). <br><br>`webClientOptions` (default: `{}`) <br> Custom request options passed to [axios](https://github.com/axios/axios). Here you can specify an `httpsAgent` to use a proxy and a `timeout` value. See [Example](#proxied-connection). <br><br>`wsClientParams` (default: `${}`)<br/> WebSocket parameters to be appended to the connection url. <br><br>`wsClientHeaders` (default: {})<br/> Custom WebSocket headers to be sent when connecting. <br><br>`wsClientOptions` (default: `{}`) <br> Custom websocket options passed to [websocket.client](https://github.com/theturtle32/WebSocket-Node). Here you can specify an `agent` to use a proxy and a `timeout` value. See [Example](#proxied-connection). <br/><br/> `authenticateWs` (default: `false`) <br/> By default, WebSocket connections are not authenticated, even when passing a sessionid, for security. This is because 'signing' (generating the URL) for connecting is done by a 3rd-party freeware service. It must be manually enabled, and you assume the risks associated with sending over a session id when you enable it.<br/><br/>`preferredAgentIds` (default: `[]`)<br/>WebSocket URL generation is done by a 3rd-party service. This option allows you to specify a specific 'agent' on the 3rd-party service to handle the URL generation.<br/><br/>`connectWithUniqueId` (default: `false`)<br/>This option allows the 3rd-party service to determine the Room ID _for_ you, rather than retrieving it through scraping. This may be preferable on low-quality IPs, as it bypasses captchas.<br/><br/>`logFetchFallbackErrors` (default: false)<br/>This option logs exceptions to the console when fallbacks are required to resolve a Room Id from a uniqueId.<br/><br/>`signedWebSocketProvider` (default: `(props: FetchSignedWebSocketParams) => Promise<WebcastResponse>`)<br/> The function responsible for signing (generating a valid WebSocket URL) can be swapped out for your own backend, if you do not want to use the free 3rd-party service bundled into the client that generates them. |
#### Example Options
```ts
const tikTokLiveConnection = new TikTokLiveConnection(tiktokUsername, {
processInitialData: false,
enableExtendedGiftInfo: true,
requestPollingIntervalMs: 2000,
webClientParams: {
"app_language": "en-US",
"device_platform": "web"
},
webClientHeaders: {
"headerName": "headerValue"
},
wsClientHeaders: {
"headerName": "headerValue"
},
wsClientParams: {
"app_language": "en-US",
},
webClientOptions: {
timeout: 10000
},
wsClientOptions: {
timeout: 10000
}
});
```
## Methods
| Method Name | Description |
|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| connect(`[roomId]`) | Connects to the live stream chat.<br>Returns a `Promise` which will be resolved when the connection is successfully established. Optionally, you can provide a Room Id to bypass Room Id scraping. |
| disconnect() | Disconnects from the stream if connected. |
| fetchRoomId(`[uniqueId]`) | Fetch the Room Id associated with the currently configured `uniqueId`. Optionally, provide a `uniqueId` for a different user to check. Returns a `Promise` which is resolved when the Room Id is fetched. |
| fetchIsLive() | Fetch whether the user is currently streaming. Returns a `Promise` which resolves when the state is fetched. |
| waitUntilLive() | Returns a blocking promise that resolves when the user goes live. |
| fetchRoomInfo() | Gets the current room info from TikTok API including streamer info, room status and statistics.<br>Returns a `Promise` which will be resolved when the API request is done.<br>*<b>Note: </b>You can call this function even if you're not connected.*<br>[Example](#retrieve-room-info) |
| fetchAvailableGifts() | Gets a list of all available gifts including gift name, image url, diamont cost and a lot of other information.<br>Returns a `Promise` that will be resolved when all available gifts has been retrieved from the API.<br>*<b>Note: </b>You can call this function even if you're not connected.*<br>[Example](#retrieve-available-gifts) |
## Properties
| Property Name | Description |
|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| webClient: `TikTokWebClient` | An API wrapper for TikTok with routes for scraping the TikTok site & internal APIs. This is used to fetch the initial data and room info. |
| wsClient: `TikTokWsClient \| null` | The WebSocket client object. This manages the lifecycle of the WebSocket connection to TikTok, and is where all events originate from. |
| options: `TikTokLiveConnectionOptions` | Options described in [Params and Options](#params-and-options) |
| roomInfo: `RoomInfoResponse` | The room info object. This contains all information about the current room. |
| availableGifts: `RoomGiftInfo` | The list of available gifts. This is only filled if `enableExtendedGiftInfo` is set to `true`. |
| isConnecting: `boolean` | Indicates whether the connection is currently being established. |
| isConnected: `boolean` | Indicates whether the connection is currently established. |
| webClientParams: `Record<string, string>` | Base URI parameters that are sent with every request of `TikTokWebClient`.<br/> |
| roomId: `string` | The current room id. This is only available after a successful connection. |
| state: `TikTokLiveConnectionState` | The current state of the connection. Includes info such as connection status, Room Id, room info, etc. |
## Signing Configuration
It is possible to configure the [3rd-party library](https://github.com/EulerStream/EulerApiSdk) used to generate
WebSocket URLs, a process referred to as 'signing'. An example is provided below:
```ts
// SignConfig is an instance of Partial<ClientConfiguration>
import { SignConfig } from 'tiktok-live-connector';
SignConfig.apiKey = "your api key" // An API key created at https://www.eulerstream.com
SignConfig.basePath = "https://your-custom-sign-server.com" // Optionally, you can even define your own server
SignConfig.baseOptions.headers['X-Custom-Header'] = 'Custom-Header-Value'
```
## Accessing TikTok LIVE Routes
The `TikTokWebClient` object is used to access TikTok's internal API routes. This object is available via
the `webClient` property of the `TikTokLiveConnection` object.
The following routes come bundled with the web client:
- `connection.webClient.fetchRoomInfo`
- `connection.webClient.sendRoomChat`
- `connection.webClient.fetchRoomInfoFromApiLive`
- `connection.webClient.fetchRoomInfoFromHtml`
- `connection.webClient.fetchSignedWebSocketFromEuler`
- `connection.webClient.fetchRoomIdFromEuler`
- `connection.webClient.fetchRoomInfoFromEuler`
### Example Usage:
All routes are callable classes that accepts a singular object with the required parameters, and return a promise
that resolves with the route data. For instance, here is how you can fetch Room Info from the page HTML:
```ts
const connection = new TikTokLiveConnection('officialgeilegisela');
connection.webClient.fetchRoomInfoFromHtml({ uniqueId: uniqueId })
.then((response: FetchRoomInfoFromHtmlRouteResponse) => {
console.log('Room Info:', response.data);
});
```
## Accessing 3rd-Party Routes
The `TikTokWebClient` comes bundled with an instance of `EulerSigner`, a 3rd-party library that provides the WebSocket
connection URL used to
connect to TikTok LIVE.
This is publicly accessible via `connection.webClient.webSigner` and exposes additional 3rd-party routes related to
TikTok LIVE.
### Example 1: Fetching Rate Limits
For example, here's how you can fetch the rate limits for your current API key:
```ts
import { SignConfig } from 'tiktok-live-connector';
import { IGetRateLimits } from '@eulerstream/euler-api-sdk';
// Configure an API Key
SignConfig.apiKey = 'your api key'; // An API key created at https://www.eulerstream.com
// Create a connection
const connection = new TikTokLiveConnection();
// Fetch the limits
connection.webClient.webSigner.webcast.getRateLimits()
.then((response: AxiosResponse<IGetRateLimits>) => {
console.log('Rate Limits:', response.data);
});
```
### Example 2: Using JWT Authentication
If you intend to run the TikTok-Live-Connector in a client environment (e.g. a bundled desktop app), you won't want to
give the user your API key. Instead, you can create a JWT for the user to connect to the API.
### Server-Side:
First, generate the JWT and return it to your user:
```ts
import { SignConfig, TikTokWebClient } from './src';
SignConfig.apiKey = 'your_api_key_here';
const connection = new TikTokWebClient();
connection.webSigner.authentication.createJWT(
122, // Your account ID
{
limits: {
minute: 5,
day: 5,
hour: 5
},
expireAfter: 60 * 60 * 2 // 2 hours is the max accepted value
}
).then((res) => {
console.log('Generated JWT:', res.data.token);
});
```
### Client-Side:
Then, use the JWT to connect to the API in the client-side NodeJS application:
```ts
import { SignConfig } from './config';
import { TikTokLiveConnection } from './client';
SignConfig.baseOptions.headers['x-jwt-key'] = 'generated-jwt-key';
const connection = new TikTokLiveConnection('tv_asahi_news');
```
## Events
A `TikTokLiveConnection` object has the following events which can be handled with an event listener.
The simplest event handler is a connect event:
```ts
const connection = new TikTokLiveConnection('officialgeilegisela');
connection.on(ControlEvent.CONNECTED, () => console.log("Connected!"));
```
### Control Events:
- [`ControleEvent.CONNECTED`](#connected) or `"connected"`
- [`ControleEvent.DISCONNECTED`](#disconnected) or `"disconnected"`
- [`ControleEvent.STREAM_END`](#streamend) or `"streamEnd"`
- [`ControleEvent.RAW_DATA`](#rawdata) or `"rawData"`
- [`ControleEvent.DECODED_DATA`](#decodeddata) or `"decodedData"`
- [`ControleEvent.WEBSOCKET_CONNECTED`](#websocketconnected) or `"websocketConnected"`
- [`ControleEvent.ERROR`](#error) or `"error"`
### Message Events:
- [`WebcastEvent.CHAT`](#chat) or `"chat"`
- [`WebcastEvent.GIFT`](#gift) or `"gift"`
- [`WebcastEvent.MEMBER`](#member) or `"member"`
- [`WebcastEvent.LIKE`](#like) or `"like"`
- [`WebcastEvent.SOCIAL`](#social) or `"social"`
- [`WebcastEvent.ENVELOPE`](#envelope) or `"envelope"`
- [`WebcastEvent.QUESTION_NEW`](#questionnew) or `"questionNew"`
- [`WebcastEvent.LINK_MIC_BATTLE`](#linkmicbattle) or `"linkMicBattle"`
- [`WebcastEvent.LINK_MIC_ARMIES`](#linkmicarmies) or `"linkMicArmies"`
- [`WebcastEvent.LIVE_INTRO`](#liveintro) or `"liveIntro"`
- [`WebcastEvent.SUBSCRIBE`](#subscribe) or `"subscribe"`
- [`WebcastEvent.FOLLOW`](#follow) or `"follow"`
- [`WebcastEvent.SHARE`](#share) or `"share"`
- [`WebcastEvent.STREAM_END`](#streamend) or `"streamEnd"`
- [`WebcastEvent.ROOM_USER`](#roomuser) or `"roomUser"`
- [`WebcastEvent.EMOTE`](#emote) or `"emote"`
- [`WebcastEvent.FOLLOW`](#follow) or `"follow"`
- [`WebcastEvent.SHARE`](#share) or `"share"`
## Control Events
### `connected`
Triggered when the connection is successfully established.
```ts
client.on(
WebcastEvent.CONNECTED,
(state: TikTokLiveConnectionState) => console.log('Hurray! Connected!', state)
);
```
<details><summary>⚡ See Data Structure</summary><p>
```json5
{
isConnected: true,
upgradedToWebsocket: true,
roomId: '7137682087200557829',
roomInfo: {
AnchorABMap: {
},
admin_user_ids: [],
anchor_scheduled_time_text: '',
anchor_share_text: '',
anchor_tab_type: 7,
answering_question_content: '',
app_id: 1233,
audio_mute: 0,
auto_cover: 0,
book_end_time: 0,
book_time: 0,
business_live: 0,
challenge_info: '',
client_version: 250701,
comment_has_text_emoji_emote: 0,
comment_name_mode: 0,
commerce_info: {
commerce_permission: 0,
oec_live_enter_room_init_data: '',
use_async_load: false
},
common_label_list: '',
content_tag: '',
cover: {
avg_color: '',
height: 0,
image_type: 0,
is_animated: false,
open_web_url: '',
uri: '720x720/tos-maliva-avt-0068/4e64db7f7c37caf9b2df71df8580a9b0',
url_list: [
"<Array>"
],
width: 0
},
create_time: 1661871149,
deco_list: [],
deprecated10: '',
deprecated11: '',
deprecated12: '',
deprecated13: '',
deprecated14: 0,
deprecated15: 0,
deprecated16: 0,
deprecated17: [],
deprecated18: 0,
deprecated19: '',
deprecated195: false,
deprecated2: '',
deprecated20: 0,
deprecated21: false,
deprecated22: 0,
deprecated23: '',
deprecated24: 0,
deprecated26: '',
deprecated28: '',
deprecated3: {
},
deprecated30: '',
deprecated31: false,
deprecated32: '',
deprecated35: 0,
deprecated36: 0,
deprecated39: '',
deprecated4: 0,
deprecated41: 0,
deprecated43: false,
deprecated44: 0,
deprecated5: false,
deprecated6: '',
deprecated7: 0,
deprecated8: '',
deprecated9: '',
disable_preload_stream: false,
drawer_tab_position: '',
effect_info: [],
existed_commerce_goods: false,
fansclub_msg_style: 2,
feed_room_label: {
avg_color: '#F1FFEB',
height: 0,
image_type: 0,
is_animated: false,
open_web_url: '',
uri: 'webcast-sg/2ea90002aca1159b5c67',
url_list: [
"<Array>"
],
width: 0
},
feed_room_labels: [],
filter_msg_rules: [],
finish_reason: 0,
finish_time: 1661878842,
finish_url: '',
finish_url_v2: '',
follow_msg_style: 2,
forum_extra_data: '',
game_tag: [],
gift_msg_style: 2,
gift_poll_vote_enabled: false,
group_source: 0,
has_commerce_goods: false,
have_wishlist: false,
hot_sentence_info: '',
id: 7137682087200558000,
id_str: '7137682087200557829',
indicators: [],
interaction_question_version: 0,
introduction: '',
is_gated_room: false,
is_replay: false,
is_show_user_card_switch: false,
last_ping_time: 1661878842,
layout: 0,
like_count: 0,
link_mic: {
audience_id_list: [],
battle_scores: [],
battle_settings: [
"<Object>"
],
channel_id: 0,
followed_count: 0,
linked_user_list: [],
multi_live_enum: 1,
rival_anchor_id: 0,
show_user_list: []
},
linker_map: {
},
linkmic_layout: 0,
live_distribution: [],
live_id: 12,
live_reason: '',
live_room_mode: 0,
live_sub_only: 0,
live_type_audio: false,
live_type_linkmic: false,
live_type_normal: true,
live_type_sandbox: false,
live_type_screenshot: false,
live_type_social_live: false,
live_type_third_party: false,
living_room_attrs: {
admin_flag: 0,
rank: 0,
room_id: 7137682087200558000,
room_id_str: '7137682087200557829',
silence_flag: 0
},
lottery_finish_time: 0,
mosaic_status: 0,
os_type: 1,
owner: {
allow_find_by_contacts: false,
allow_others_download_video: false,
allow_others_download_when_sharing_video: false,
allow_share_show_profile: false,
allow_show_in_gossip: false,
allow_show_my_action: false,
allow_strange_comment: false,
allow_unfollower_comment: false,
allow_use_linkmic: false,
avatar_large: [
"<Object>"
],
avatar_medium: [
"<Object>"
],
avatar_thumb: [
"<Object>"
],
badge_image_list: [],
badge_list: [],
bg_img_url: '',
bio_description: 'HH📍🇩🇪تابعوني انستغرام\nاذا سقطت سأخذ الجميع معي\n👻Alin_issa22👻',
block_status: 0,
border_list: [],
comment_restrict: 0,
commerce_webcast_config_ids: [],
constellation: '',
create_time: 0,
deprecated1: 0,
deprecated12: 0,
deprecated13: 0,
deprecated15: 0,
deprecated16: false,
deprecated17: false,
deprecated18: '',
deprecated19: false,
deprecated2: 0,
deprecated21: 0,
deprecated28: false,
deprecated29: '',
deprecated3: 0,
deprecated4: 0,
deprecated5: '',
deprecated6: 0,
deprecated7: '',
deprecated8: 0,
disable_ichat: 0,
display_id: 'alin.i7',
enable_ichat_img: 0,
exp: 0,
fan_ticket_count: 0,
fold_stranger_chat: false,
follow_info: [
"<Object>"
],
follow_status: 0,
ichat_restrict_type: 0,
id: 6672446849804223000,
id_str: '6672446849804223493',
is_follower: false,
is_following: false,
link_mic_stats: 0,
media_badge_image_list: [],
modify_time: 1661427082,
need_profile_guide: false,
new_real_time_icons: [],
nickname: '🦋ALIN🦋',
own_room: [
"<Object>"
],
pay_grade: [
"<Object>"
],
pay_score: 0,
pay_scores: 0,
push_comment_status: false,
push_digg: false,
push_follow: false,
push_friend_action: false,
push_ichat: false,
push_status: false,
push_video_post: false,
push_video_recommend: false,
real_time_icons: [],
sec_uid: 'MS4wLjABAAAAuUKuWAiw0GQO2_zOeyns0YCBRK7ztdoDWAAQ6gPFLBNSdTs-g5BsgScwTD9jWeK_',
secret: 0,
share_qrcode_uri: '',
special_id: '',
status: 1,
ticket_count: 0,
top_fans: [],
top_vip_no: 0,
upcoming_event_list: [],
user_attr: [
"<Object>"
],
user_role: 0,
verified: false,
verified_content: '',
verified_reason: '',
with_car_management_permission: false,
with_commerce_permission: false,
with_fusion_shop_entry: false
},
owner_device_id: 0,
owner_device_id_str: '',
owner_user_id: 6672446849804223000,
owner_user_id_str: '',
pre_enter_time: 0,
preview_flow_tag: 0,
ranklist_audience_type: 0,
relation_tag: '',
replay: true,
room_audit_status: 0,
room_auth: {
Banner: 1,
BroadcastMessage: 0,
Chat: true,
ChatL2: false,
ChatSubOnly: false,
CommercePermission: 0,
CustomizablePoll: 0,
Danmaku: false,
Digg: true,
DonationSticker: 2,
EventPromotion: 0,
Gift: true,
GiftAnchorMt: 1,
GiftPoll: 0,
GoldenEnvelope: 0,
GoldenEnvelopeActivity: 0,
InteractionQuestion: true,
Landscape: 2,
LandscapeChat: 0,
LuckMoney: true,
Pictionary: 0,
Poll: 0,
Promote: false,
PromoteOther: 0,
Props: false,
PublicScreen: 1,
QuickChat: 0,
Rank: 0,
RoomContributor: false,
Share: true,
ShareEffect: 0,
ShoppingRanking: 0,
UserCard: true,
UserCount: 0,
Viewers: false,
deprecated1: false,
deprecated2: 0,
deprecated3: 0,
deprecated4: 0,
deprecated5: 0,
deprecated6: 0,
deprecated7: 0,
deprecated8: 0,
deprecated9: 0,
transaction_history: 1,
use_user_pv: false
},
room_create_ab_param: '',
room_layout: 0,
room_sticker_list: [],
room_tabs: [],
room_tag: 0,
scroll_config: '',
search_id: 0,
share_msg_style: 2,
share_url: 'https://m.tiktok.com/share/live/7137682087200557829/?language=en',
short_title: '',
short_touch_items: [],
social_interaction: {
linkmic_scene_linker: {
},
multi_live: [
"<Object>"
]
},
start_time: 0,
stats: {
deprecated1: 0,
deprecated2: '',
digg_count: 0,
enter_count: 0,
fan_ticket: 0,
follow_count: 686,
gift_uv_count: 0,
id: 7137682087200558000,
id_str: '7137682087200557829',
like_count: 0,
replay_fan_ticket: 0,
replay_viewers: 64076,
share_count: 0,
total_user: 104582,
total_user_desp: '',
user_count_composition: [
"<Object>"
],
watermelon: 0
},
status: 2,
sticker_list: [],
stream_id: 2993830046178738000,
stream_id_str: '2993830046178738249',
stream_status: 0,
stream_url: {
candidate_resolution: [
"<Array>"
],
complete_push_urls: [],
default_resolution: 'ORIGION',
extra: [
"<Object>"
],
flv_pull_url: [
"<Object>"
],
flv_pull_url_params: [
"<Object>"
],
hls_pull_url: 'https://pull-hls-f16-va01.tiktokcdn.com/stage/stream-2993830046178738249_or4/index.m3u8',
hls_pull_url_map: {
},
hls_pull_url_params: '{"VCodec":"h264"}',
id: 2993830046178738000,
id_str: '2993830046178738249',
live_core_sdk_data: [
"<Object>"
],
provider: 0,
push_urls: [],
resolution_name: [
"<Object>"
],
rtmp_pull_url: 'https://pull-f5-va01.tiktokcdn.com/stage/stream-2993830046178738249_or4.flv',
rtmp_pull_url_params: '{"VCodec":"h264"}',
rtmp_push_url: '',
rtmp_push_url_params: '',
stream_control_type: 0
},
stream_url_filtered_info: {
is_gated_room: false,
is_paid_event: false
},
title: 'انا جيت😍 🥰',
top_fans: [
[
"<Object>"
],
[
"<Object>"
],
[
"<Object>"
]
],
use_filter: false,
user_count: 1136,
user_share_text: '',
video_feed_tag: '',
webcast_comment_tcs: 0,
webcast_sdk_version: 0,
with_draw_something: false,
with_ktv: false,
with_linkmic: true
},
availableGifts: []
}
```
</p></details>
### `disconnected`
Triggered when the connection gets disconnected. In that case you can call `connect()` again to have a reconnect logic.
Note that you should wait a little bit before attempting a reconnect to to avoid being rate-limited.
```ts
connection.on(ControlEvent.DISCONNECTED, () => console.log('Disconnected :('));
```
### `streamEnd`
Triggered when the live stream gets terminated by the host. Will also trigger the [`disconnected`](#disconnected) event.
```ts
connection.on(ControlEvent.STREAM_END, ({ action }: { action: ControlAction }) => {
if (action === ControlAction.CONTROL_ACTION_STREAM_ENDED) {
console.log('Stream ended by user');
}
if (action === ControlAction.CONTROL_ACTION_STREAM_SUSPENDED) {
console.log('Stream ended by platform moderator (ban)');
}
});
```
### `rawData`
Triggered every time a protobuf encoded webcast message arrives. You can deserialize the binary object depending on the
use case with <a href="https://www.npmjs.com/package/protobufjs">protobufjs</a>.
```ts
connection.on(ControlEvent.RAW_DATA, (messageTypeName, binary) => {
console.log(messageTypeName, binary);
});
```
### `decodedData`
Triggered every time a decoded message arrives. This is the same as the `rawData` event but the binary object is already
decoded. You can use this event to handle the protobuf messages without the need of deserializing them yourself.
```ts
connection.on(ControlEvent.DECODED_DATA, (event: string, decodedData: any, binary: Uint8Array) => {
console.log(event, decodedData);
});
```
### `websocketConnected`
Will be triggered as soon as a WebSocket connection is established. The WebSocket client object is passed.
```ts
connection.on(ControlEvent.WEBSOCKET_CONNECTED, (client: TikTokWsClient) => {
console.log('WebSocket Client:', websocketClient.connection);
});
```
### `error`
General error event. You should handle this.
```ts
connection.on(ControlEvent.ERROR, err => {
console.error('Error!', err);
});
```
## Message Events
### `member`
Triggered every time a new viewer joins the live stream.
```ts
connection.on(WebcastEvent.MEMBER, (data: WebcastMemberMessage) => {
console.log(`${data.uniqueId} joins the stream!`);
});
```
<details><summary>⚡ See Data Structure</summary><p>
```json5
{
actionId: 1,
userId: "6813181309701719620",
secUid: "<redacted>",
uniqueId: "zerodytester",
nickname: "Zerody Tester",
profilePictureUrl: "https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp",
followRole: 0,
// 0 = none; 1 = follower; 2 = friends
userBadges: [
{
type: "pm_mt_moderator_im",
name: "Moderator"
},
{
type: "image",
displayType: 1,
url: "https://p19-webcast.tiktokcdn.com/webcast-va/rankl...image"
},
{
type: "image",
displayType: 1,
url: "https://p19-webcast.tiktokcdn.com/webcast-va/....~...image"
}
],
userDetails: {
createTime: "0",
bioDescription: "",
profilePictureUrls: [
"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp",
"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp",
"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...jpeg"
]
},
followInfo: {
followingCount: 2139,
followerCount: 853,
followStatus: 0,
pushStatus: 0
},
isModerator: false,
isNewGifter: false,
isSubscriber: false,
topGifterRank: null,
msgId: "7137750885996120859",
createTime: "1661887134195",
displayType: "live_room_enter_toast",
label: "{0:user} joined"
}
```
</p></details>
### `chat`
Triggered every time a new chat comment arrives.
```ts
connection.on(WebcastEvent.CHAT, (data: WebcastChatMessage) => {
console.log(`${data.uniqueId} -> ${data.comment}`);
});
```
<details><summary>⚡ See Data Structure</summary><p>
```json5
{
comment: "How are you?",
userId: "6813181309701719620",
secUid: "<redacted>",
uniqueId: "zerodytester",
nickname: "Zerody Tester",
profilePictureUrl: "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp",
followRole: 0,
// 0 = none; 1 = follower; 2 = friends
userBadges: [
{
// Moderator badge
type: "pm_mt_moderator_im",
name: "Moderator"
},
{
// Top Gifter badge
type: "image",
displayType: 1,
url: "https://p19-webcast.tiktokcdn.com/webcast-va/rankl...image"
},
{
// Subscriber Badge
type: "image",
displayType: 1,
url: "https://p19-webcast