{"id":16555202,"url":"https://github.com/zerodytrash/tiktok-live-connector","last_synced_at":"2025-05-13T22:03:43.001Z","repository":{"id":39667733,"uuid":"455400811","full_name":"zerodytrash/TikTok-Live-Connector","owner":"zerodytrash","description":"Node.js library to receive live stream events (comments, gifts, etc.) in realtime from TikTok LIVE.","archived":false,"fork":false,"pushed_at":"2025-05-05T20:35:32.000Z","size":518,"stargazers_count":1548,"open_issues_count":120,"forks_count":334,"subscribers_count":43,"default_branch":"ts-rewrite","last_synced_at":"2025-05-07T16:11:38.770Z","etag":null,"topics":["api","api-wrapper","bot","broadcast","chat","chat-reader","connector","hacktoberfest","javascript","live","livestream","nodejs","package","scraper","stream","tiktok","tiktok-api","tiktok-live","webcast","websocket"],"latest_commit_sha":null,"homepage":"https://discord.gg/2c6kX6g3Pa","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zerodytrash.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"zerodytrash","ko_fi":"zerody","patreon":"zerody","custom":"https://www.buymeacoffee.com/zerody"}},"created_at":"2022-02-04T02:41:27.000Z","updated_at":"2025-05-06T08:12:25.000Z","dependencies_parsed_at":"2023-09-27T06:49:44.196Z","dependency_job_id":"7897f3b6-690a-4810-8f81-40bafaabe275","html_url":"https://github.com/zerodytrash/TikTok-Live-Connector","commit_stats":{"total_commits":153,"total_committers":10,"mean_commits":15.3,"dds":0.08496732026143794,"last_synced_commit":"3277f0707dd4702650e682eed0bcd06b936de7e3"},"previous_names":["zerodytrash/tiktok-livestream-chat-connector"],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zerodytrash%2FTikTok-Live-Connector","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zerodytrash%2FTikTok-Live-Connector/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zerodytrash%2FTikTok-Live-Connector/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zerodytrash%2FTikTok-Live-Connector/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zerodytrash","download_url":"https://codeload.github.com/zerodytrash/TikTok-Live-Connector/tar.gz/refs/heads/ts-rewrite","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254036811,"owners_count":22003653,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-wrapper","bot","broadcast","chat","chat-reader","connector","hacktoberfest","javascript","live","livestream","nodejs","package","scraper","stream","tiktok","tiktok-api","tiktok-live","webcast","websocket"],"created_at":"2024-10-11T19:53:23.374Z","updated_at":"2025-05-13T22:03:42.993Z","avatar_url":"https://github.com/zerodytrash.png","language":"TypeScript","funding_links":["https://github.com/sponsors/zerodytrash","https://ko-fi.com/zerody","https://patreon.com/zerody","https://www.buymeacoffee.com/zerody"],"categories":[],"sub_categories":[],"readme":"# TikTok-Live-Connector\n\nA Node.js library to receive live stream events such as comments and gifts in realtime\nfrom [TikTok LIVE](https://www.tiktok.com/live) by connecting to TikTok's internal Webcast push service.\nThis package includes a wrapper that connects to the Webcast service using just the username (`@uniqueId`).\nThis allows you to connect to your own live chat as well as the live chat of other streamers. No credentials are\nrequired. Besides [Chat Comments](#chat), other events such\nas [Members Joining](#member), [Gifts](#gift), [Subscriptions](#subscribe), [Viewers](#roomuser), [Follows](#social), [Shares](#social), [Questions](#questionnew), [Likes](#like)\nand [Battles](#linkmicbattle) can be tracked.\n\n[![Discord](https://img.shields.io/discord/977648006063091742?logo=discord\u0026label=TikTokLive%20Discord\u0026labelColor=%23171717\u0026color=%231877af)](https://discord.gg/N3KSxzvDX8)\n![Connections](https://tiktok.eulerstream.com/analytics/pips/1?client=ttlive-node)\n![Downloads](https://img.shields.io/npm/dw/tiktok-live-connector?style=flat\u0026color=0274b5\u0026alt=1)\n![Stars](https://img.shields.io/github/stars/zerodytrash/tiktok-live-connector?style=flat\u0026color=0274b5\u0026alt=1)\n![Issues](https://img.shields.io/github/issues/zerodytrash/tiktok-live-connector?style=flat\u0026color=0274b5\u0026alt=1)\n![Forks](https://img.shields.io/github/forks/zerodytrash/tiktok-live-connector?style=flat\u0026color=0274b5\u0026alt=1)\n\n\u003e [!NOTE]\n\u003e This is not an official API. It is a reverse engineering project. You are responsible for your usage of this library.\n\n\u003e [!TIP]\n\u003e An example project is available\n\u003e at https://tiktok-chat-reader.zerody.one/ - [View Source](https://github.com/zerodytrash/TikTok-Chat-Reader)\n\n\u003e [!WARNING]\n\u003e This JavaScript library is intended for use in [Node.js](https://nodejs.org/) environments. If you want to process or\n\u003e display the data in the browser (client-side), you need to transfer the data from the Node.js environment to the\n\u003e browser. A good approach for this is to use [Socket.IO](https://socket.io/) or a different low-latency communication\n\u003e framework. A complete example project can be found\n\u003e here: [TikTok-Chat-Reader](https://github.com/zerodytrash/TikTok-Chat-Reader)\n\n\u003e [!WARNING]\n\u003e Due to a change on the part of TikTok, versions prior **v1.1.7** are no longer functional. If you are using one of\n\u003e these versions, upgrade to the latest version using the `npm i tiktok-live-connector` command.\n\n### 📌 2.0.0 Now Available - Upgrade Now!\n\nWe are thrilled to announce that the 2.0.0 release of TikTok Live Connector is now available!\nThis release converts the package to exclusively typescript-based, also providing complete backwards compatibility for\nusers.\n\n#### Upgrade Guide\n\n- Install via `npm i tiktok-live-connector@2.0.0`\n- Switch `WebcastPushConnection` to `TikTokLiveConnection` (or keep for legacy support)\n- Switch Event Names to `WebcastEvent.EVENT_NAME` from `event_name` format\n- That's it! You're good to go!\n- If you have any issues, please open an issue on GitHub or ask on [Discord](https://discord.gg/2c6kX6g3Pa).\n\n#### Breaking Changes\n\n- `WebcastPushConnection` has been renamed to `TikTokLiveConnection`\n- CommonJS support has been removed\n- Data fetching functions have been renamed\n\n### Do you prefer other programming languages?\n\n- **Python** rewrite: [TikTokLive](https://github.com/isaackogan/TikTokLive)\n  by [@isaackogan](https://github.com/isaackogan) and [@eulerstream](https://github.com/eulerstream/)\n- **Java** rewrite: [TikTokLiveJava](https://github.com/jwdeveloper/TikTokLiveJava)\n  by [@jwdeveloper](https://github.com/jwdeveloper) and [@kohlerpop1](https://github.com/kohlerpop1)\n- **Go** rewrite: [GoTikTokLive](https://github.com/steampoweredtaco/gotiktoklive)\n  by [@steampoweredtaco](https://github.com/steampoweredtaco) and [@davincible](https://github.com/davincible)\n- **C#** rewrite: [TikTokLiveSharp](https://github.com/frankvHoof93/TikTokLiveSharp)\n  by [@frankvHoof93](https://github.com/frankvHoof93)\n\n### Table of Contents\n\n- [Getting Started](#getting-started)\n- [Params and Options](#params-and-options)\n- [Methods](#methods)\n- [Properties](#properties)\n- [Events](#events)\n- [Examples](#examples)\n- [Contributing](#contributing)\n\n## Getting Started\n\n1. Install the package via NPM\n\n```bash\nnpm i tiktok-live-connector\n```\n\n2. Create your first TikTok LIVE chat connection\n\n```ts\n// Username of someone who is currently live\nimport { TikTokLiveConnection, WebcastEvent } from 'tiktok-live-connector';\n\nconst tiktokUsername = 'officialgeilegisela';\n\n// Create a new wrapper object and pass the username\nconst connection = new TikTokLiveConnection(tiktokUsername);\n\n// Connect to the chat (await can be used as well)\nconnection.connect().then(state =\u003e {\n    console.info(`Connected to roomId ${state.roomId}`);\n}).catch(err =\u003e {\n    console.error('Failed to connect', err);\n});\n\n// Define the events that you want to handle\n// In this case we listen to chat messages (comments)\nconnection.on(WebcastEvent.CHAT, data =\u003e {\n    console.log(`${data.user.uniqueId} (userId:${data.user.uniqueId}) writes: ${data.comment}`);\n});\n\n// And here we receive gifts sent to the streamer\nconnection.on(WebcastEvent.GIFT, data =\u003e {\n    console.log(`${data.user.uniqueId} (userId:${data.user.userId}) sends ${data.giftId}`);\n});\n\n// ...and more events described in the documentation below\n```\n\n## Params and Options\n\nTo create a new `TikTokLiveConnection` object the following parameters can be specified.\n\n`TikTokLiveConnection(uniqueId, [options])`\n\n| Param Name | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n|------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| uniqueId   | Yes      | The unique username of the broadcaster. You can find this name in the URL.\u003cbr\u003eExample: `https://www.tiktok.com/@officialgeilegisela/live` =\u003e `officialgeilegisela`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |\n| options    | No       | Here you can set the following optional connection properties. If you do not specify a value, the default value will be used.\u003cbr\u003e\u003cbr\u003e`processInitialData` (default: `true`) \u003cbr\u003e Define if you want to process the initital data which includes old messages of the last seconds.\u003cbr\u003e\u003cbr\u003e`signApiKey` (default: `null`) Configure a Sign API key. This API key will be used to generate the URL to facilitate connections to the TikTok LIVE WebSocket server. \u003cbr\u003e\u003cbr\u003e `fetchRoomInfoOnConnect` (default: `true`) \u003cbr\u003e 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.\u003cbr\u003e\u003cbr\u003e`enableExtendedGiftInfo` (default: `false`) \u003cbr\u003e 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). \u003cbr\u003e\u003cbr\u003e`requestPollingIntervalMs` (default: `1000`) \u003cbr\u003e Request polling interval if WebSocket is not used.\u003cbr\u003e\u003cbr\u003e`sessionId` (default: `null`) \u003cbr\u003e Here you can specify the current Session ID of your TikTok account \u003cbr\u003e\u003cbr\u003e`webClientParams` (default: `{}`) \u003cbr\u003e Custom client params for Webcast API.\u003cbr\u003e\u003cbr\u003e`webClientHeaders` (default: `{}`) \u003cbr\u003e Custom request headers passed to [axios](https://github.com/axios/axios).\u003cbr\u003e\u003cbr\u003e`websocketHeaders` (default: `{}`) \u003cbr\u003e Custom websocket headers passed to [websocket.client](https://github.com/theturtle32/WebSocket-Node). \u003cbr\u003e\u003cbr\u003e`webClientOptions` (default: `{}`) \u003cbr\u003e 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). \u003cbr\u003e\u003cbr\u003e`wsClientParams` (default: `${}`)\u003cbr/\u003e WebSocket parameters to be appended to the connection url. \u003cbr\u003e\u003cbr\u003e`wsClientHeaders` (default: {})\u003cbr/\u003e Custom WebSocket headers to be sent when connecting. \u003cbr\u003e\u003cbr\u003e`wsClientOptions` (default: `{}`) \u003cbr\u003e 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). \u003cbr/\u003e\u003cbr/\u003e `authenticateWs` (default: `false`) \u003cbr/\u003e 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.\u003cbr/\u003e\u003cbr/\u003e`preferredAgentIds` (default: `[]`)\u003cbr/\u003eWebSocket 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.\u003cbr/\u003e\u003cbr/\u003e`connectWithUniqueId` (default: `false`)\u003cbr/\u003eThis 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.\u003cbr/\u003e\u003cbr/\u003e`disableEulerFallbacks` (default: false)\u003cbr/\u003eThis option disables the Euler Stream API \"fallback\" routes used by default when scraping fails.\u003cbr/\u003e\u003cbr/\u003e`signedWebSocketProvider` (default: `(props: FetchSignedWebSocketParams) =\u003e Promise\u003cWebcastResponse\u003e`)\u003cbr/\u003e 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. |\n\n#### Example Options\n\n```ts\nconst tikTokLiveConnection = new TikTokLiveConnection(tiktokUsername, {\n    processInitialData: false,\n    enableExtendedGiftInfo: true,\n    requestPollingIntervalMs: 2000,\n    signApiKey: 'your-api-key',\n    webClientParams: {\n        \"app_language\": \"en-US\",\n        \"device_platform\": \"web\"\n    },\n    webClientHeaders: {\n        \"headerName\": \"headerValue\"\n    },\n    wsClientHeaders: {\n        \"headerName\": \"headerValue\"\n    },\n    wsClientParams: {\n        \"app_language\": \"en-US\",\n    },\n    webClientOptions: {\n        timeout: 10000\n    },\n    wsClientOptions: {\n        timeout: 10000\n    }\n});\n```\n\n## Methods\n\n| Method Name               | Description                                                                                                                                                                                                                                                                                                                               |\n|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| connect(`[roomId]`)       | Connects to the live stream chat.\u003cbr\u003eReturns a `Promise` which will be resolved when the connection is successfully established. Optionally, you can provide a Room Id to bypass Room Id scraping.                                                                                                                                        |\n| disconnect()              | Disconnects from the stream if connected.                                                                                                                                                                                                                                                                                                 |\n| sendMessage(`\u003ccontent\u003e`)  | Send a message to a TikTok LIVE chat. Simply speciy the message you want to send, and voila! Requires an API key. \u003cbr\u003e[Example](#send-messages)                                                                                                                                                                                           |\n| 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.                                                                                                                                 |\n| fetchIsLive()             | Fetch whether the user is currently streaming. Returns a `Promise` which resolves when the state is fetched.                                                                                                                                                                                                                              |\n| waitUntilLive()           | Returns a blocking promise that resolves when the user goes live.                                                                                                                                                                                                                                                                         |\n| fetchRoomInfo()           | Gets the current room info from TikTok API including streamer info, room status and statistics.\u003cbr\u003eReturns a `Promise` which will be resolved when the API request is done.\u003cbr\u003e*\u003cb\u003eNote: \u003c/b\u003eYou can call this function even if you're not connected.*\u003cbr\u003e[Example](#retrieve-room-info)                                                  |\n| fetchAvailableGifts()     | Gets a list of all available gifts including gift name, image url, diamont cost and a lot of other information.\u003cbr\u003eReturns a `Promise` that will be resolved when all available gifts has been retrieved from the API.\u003cbr\u003e*\u003cb\u003eNote: \u003c/b\u003eYou can call this function even if you're not connected.*\u003cbr\u003e[Example](#retrieve-available-gifts) |\n\n## Properties\n\n| Property Name                             | Description                                                                                                                               |\n|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|\n| webClient: `TikTokWebClient`              | An API wrapper for TikTok with routes for scraping the TikTok site \u0026 internal APIs. This is used to fetch the initial data and room info. |\n| wsClient: `TikTokWsClient \\| null`        | The WebSocket client object. This manages the lifecycle of the WebSocket connection to TikTok, and is where all events originate from.    |\n| options: `TikTokLiveConnectionOptions`    | Options described in [Params and Options](#params-and-options)                                                                            |\n| roomInfo: `RoomInfoResponse`              | The room info object. This contains all information about the current room.                                                               |\n| availableGifts: `RoomGiftInfo`            | The list of available gifts. This is only filled if `enableExtendedGiftInfo` is set to `true`.                                            |\n| isConnecting: `boolean`                   | Indicates whether the connection is currently being established.                                                                          |\n| isConnected: `boolean`                    | Indicates whether the connection is currently established.                                                                                |\n| webClientParams: `Record\u003cstring, string\u003e` | Base URI parameters that are sent with every request of `TikTokWebClient`.\u003cbr/\u003e                                                           |\n| roomId: `string`                          | The current room id. This is only available after a successful connection.                                                                |\n| state: `TikTokLiveConnectionState`        | The current state of the connection. Includes info such as connection status, Room Id, room info, etc.                                    |\n\n## Signing Configuration\n\nIt is possible to configure the [3rd-party library](https://github.com/EulerStream/EulerApiSdk) used to generate\nWebSocket URLs, a process referred to as 'signing'. An example is provided below:\n\n```ts\n// SignConfig is an instance of Partial\u003cClientConfiguration\u003e\nimport { SignConfig } from 'tiktok-live-connector';\n\nSignConfig.apiKey = \"your api key\" // An API key created at https://www.eulerstream.com\nSignConfig.basePath = \"https://your-custom-sign-server.com\" // Optionally, you can even define your own server\nSignConfig.baseOptions.headers['X-Custom-Header'] = 'Custom-Header-Value'\n```\n\n## Accessing TikTok LIVE Routes\n\nThe `TikTokWebClient` object is used to access TikTok's internal API routes. This object is available via\nthe `webClient` property of the `TikTokLiveConnection` object.\n\nThe following routes come bundled with the web client:\n\n- `connection.webClient.fetchRoomInfo`\n- `connection.webClient.sendRoomChat`\n- `connection.webClient.fetchRoomInfoFromApiLive`\n- `connection.webClient.fetchRoomInfoFromHtml`\n- `connection.webClient.fetchSignedWebSocketFromEuler`\n- `connection.webClient.fetchRoomIdFromEuler`\n- `connection.webClient.fetchRoomInfoFromEuler`\n\n### Example Usage:\n\nAll routes are callable classes that accepts a singular object with the required parameters, and return a promise\nthat resolves with the route data. For instance, here is how you can fetch Room Info from the page HTML:\n\n```ts\nconst connection = new TikTokLiveConnection('officialgeilegisela');\n\nconnection.webClient.fetchRoomInfoFromHtml({ uniqueId: uniqueId })\n    .then((response: FetchRoomInfoFromHtmlRouteResponse) =\u003e {\n        console.log('Room Info:', response.data);\n    });\n```\n\n## Accessing 3rd-Party Routes\n\nThe `TikTokWebClient` comes bundled with an instance of `EulerSigner`, a 3rd-party library that provides the WebSocket\nconnection URL used to\nconnect to TikTok LIVE.\n\nThis is publicly accessible via `connection.webClient.webSigner` and exposes additional 3rd-party routes related to\nTikTok LIVE.\n\n### Example 1: Fetching Rate Limits\n\nFor example, here's how you can fetch the rate limits for your current API key:\n\n```ts\nimport { SignConfig } from 'tiktok-live-connector';\nimport { IGetRateLimits } from '@eulerstream/euler-api-sdk';\n\n// Configure an API Key\nSignConfig.apiKey = 'your api key'; // An API key created at https://www.eulerstream.com\n\n// Create a connection\nconst connection = new TikTokLiveConnection();\n\n// Fetch the limits\nconnection.webClient.webSigner.webcast.getRateLimits()\n    .then((response: AxiosResponse\u003cIGetRateLimits\u003e) =\u003e {\n        console.log('Rate Limits:', response.data);\n    });\n```\n\n### Example 2: Using JWT Authentication\n\nIf you intend to run the TikTok-Live-Connector in a client environment (e.g. a bundled desktop app), you won't want to\ngive the user your API key. Instead, you can create a JWT for the user to connect to the API.\n\n### Server-Side:\n\nFirst, generate the JWT and return it to your user:\n\n```ts\nimport { SignConfig, TikTokWebClient } from './src';\n\nSignConfig.apiKey = 'your_api_key_here';\nconst connection = new TikTokWebClient();\n\nconnection.webSigner.authentication.createJWT(\n    122, // Your account ID\n    {\n        limits: {\n            minute: 5,\n            day: 5,\n            hour: 5\n        },\n        expireAfter: 60 * 60 * 2 // 2 hours is the max accepted value\n    }\n).then((res) =\u003e {\n    console.log('Generated JWT:', res.data.token);\n});\n```\n\n### Client-Side:\n\nThen, use the JWT to connect to the API in the client-side NodeJS application:\n\n```ts\nimport { SignConfig } from './config';\nimport { TikTokLiveConnection } from './client';\n\nSignConfig.baseOptions.headers['x-jwt-key'] = 'generated-jwt-key';\nconst connection = new TikTokLiveConnection('tv_asahi_news');\n```\n\n## Events\n\nA `TikTokLiveConnection` object has the following events which can be handled with an event listener.\n\nThe simplest event handler is a connect event:\n\n```ts\nconst connection = new TikTokLiveConnection('officialgeilegisela');\nconnection.on(ControlEvent.CONNECTED, () =\u003e console.log(\"Connected!\"));\n```\n\n### Control Events:\n\n- [`ControleEvent.CONNECTED`](#connected) or `\"connected\"`\n- [`ControleEvent.DISCONNECTED`](#disconnected) or `\"disconnected\"`\n- [`ControleEvent.STREAM_END`](#streamend) or `\"streamEnd\"`\n- [`ControleEvent.RAW_DATA`](#rawdata) or `\"rawData\"`\n- [`ControleEvent.DECODED_DATA`](#decodeddata) or `\"decodedData\"`\n- [`ControleEvent.WEBSOCKET_CONNECTED`](#websocketconnected) or `\"websocketConnected\"`\n- [`ControleEvent.ERROR`](#error) or `\"error\"`\n\n### Message Events:\n\n- [`WebcastEvent.CHAT`](#chat) or `\"chat\"`\n- [`WebcastEvent.GIFT`](#gift) or `\"gift\"`\n- [`WebcastEvent.MEMBER`](#member) or `\"member\"`\n- [`WebcastEvent.LIKE`](#like) or `\"like\"`\n- [`WebcastEvent.SOCIAL`](#social) or `\"social\"`\n- [`WebcastEvent.ENVELOPE`](#envelope) or `\"envelope\"`\n- [`WebcastEvent.QUESTION_NEW`](#questionnew) or `\"questionNew\"`\n- [`WebcastEvent.LINK_MIC_BATTLE`](#linkmicbattle) or `\"linkMicBattle\"`\n- [`WebcastEvent.LINK_MIC_ARMIES`](#linkmicarmies) or `\"linkMicArmies\"`\n- [`WebcastEvent.LIVE_INTRO`](#liveintro) or `\"liveIntro\"`\n- [`WebcastEvent.SUBSCRIBE`](#subscribe) or `\"subscribe\"`\n- [`WebcastEvent.FOLLOW`](#follow) or `\"follow\"`\n- [`WebcastEvent.SHARE`](#share) or `\"share\"`\n- [`WebcastEvent.STREAM_END`](#streamend) or `\"streamEnd\"`\n- [`WebcastEvent.ROOM_USER`](#roomuser) or `\"roomUser\"`\n- [`WebcastEvent.EMOTE`](#emote) or `\"emote\"`\n- [`WebcastEvent.FOLLOW`](#follow) or `\"follow\"`\n- [`WebcastEvent.SHARE`](#share) or `\"share\"`\n\n## Control Events\n\n### `connected`\n\nTriggered when the connection is successfully established.\n\n```ts\nclient.on(\n    WebcastEvent.CONNECTED,\n    (state: TikTokLiveConnectionState) =\u003e console.log('Hurray! Connected!', state)\n);\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    isConnected: true,\n    upgradedToWebsocket: true,\n    roomId: '7137682087200557829',\n    roomInfo: {\n        AnchorABMap: {\n        },\n        admin_user_ids: [],\n        anchor_scheduled_time_text: '',\n        anchor_share_text: '',\n        anchor_tab_type: 7,\n        answering_question_content: '',\n        app_id: 1233,\n        audio_mute: 0,\n        auto_cover: 0,\n        book_end_time: 0,\n        book_time: 0,\n        business_live: 0,\n        challenge_info: '',\n        client_version: 250701,\n        comment_has_text_emoji_emote: 0,\n        comment_name_mode: 0,\n        commerce_info: {\n            commerce_permission: 0,\n            oec_live_enter_room_init_data: '',\n            use_async_load: false\n        },\n        common_label_list: '',\n        content_tag: '',\n        cover: {\n            avg_color: '',\n            height: 0,\n            image_type: 0,\n            is_animated: false,\n            open_web_url: '',\n            uri: '720x720/tos-maliva-avt-0068/4e64db7f7c37caf9b2df71df8580a9b0',\n            url_list: [\n                \"\u003cArray\u003e\"\n            ],\n            width: 0\n        },\n        create_time: 1661871149,\n        deco_list: [],\n        deprecated10: '',\n        deprecated11: '',\n        deprecated12: '',\n        deprecated13: '',\n        deprecated14: 0,\n        deprecated15: 0,\n        deprecated16: 0,\n        deprecated17: [],\n        deprecated18: 0,\n        deprecated19: '',\n        deprecated195: false,\n        deprecated2: '',\n        deprecated20: 0,\n        deprecated21: false,\n        deprecated22: 0,\n        deprecated23: '',\n        deprecated24: 0,\n        deprecated26: '',\n        deprecated28: '',\n        deprecated3: {\n        },\n        deprecated30: '',\n        deprecated31: false,\n        deprecated32: '',\n        deprecated35: 0,\n        deprecated36: 0,\n        deprecated39: '',\n        deprecated4: 0,\n        deprecated41: 0,\n        deprecated43: false,\n        deprecated44: 0,\n        deprecated5: false,\n        deprecated6: '',\n        deprecated7: 0,\n        deprecated8: '',\n        deprecated9: '',\n        disable_preload_stream: false,\n        drawer_tab_position: '',\n        effect_info: [],\n        existed_commerce_goods: false,\n        fansclub_msg_style: 2,\n        feed_room_label: {\n            avg_color: '#F1FFEB',\n            height: 0,\n            image_type: 0,\n            is_animated: false,\n            open_web_url: '',\n            uri: 'webcast-sg/2ea90002aca1159b5c67',\n            url_list: [\n                \"\u003cArray\u003e\"\n            ],\n            width: 0\n        },\n        feed_room_labels: [],\n        filter_msg_rules: [],\n        finish_reason: 0,\n        finish_time: 1661878842,\n        finish_url: '',\n        finish_url_v2: '',\n        follow_msg_style: 2,\n        forum_extra_data: '',\n        game_tag: [],\n        gift_msg_style: 2,\n        gift_poll_vote_enabled: false,\n        group_source: 0,\n        has_commerce_goods: false,\n        have_wishlist: false,\n        hot_sentence_info: '',\n        id: 7137682087200558000,\n        id_str: '7137682087200557829',\n        indicators: [],\n        interaction_question_version: 0,\n        introduction: '',\n        is_gated_room: false,\n        is_replay: false,\n        is_show_user_card_switch: false,\n        last_ping_time: 1661878842,\n        layout: 0,\n        like_count: 0,\n        link_mic: {\n            audience_id_list: [],\n            battle_scores: [],\n            battle_settings: [\n                \"\u003cObject\u003e\"\n            ],\n            channel_id: 0,\n            followed_count: 0,\n            linked_user_list: [],\n            multi_live_enum: 1,\n            rival_anchor_id: 0,\n            show_user_list: []\n        },\n        linker_map: {\n        },\n        linkmic_layout: 0,\n        live_distribution: [],\n        live_id: 12,\n        live_reason: '',\n        live_room_mode: 0,\n        live_sub_only: 0,\n        live_type_audio: false,\n        live_type_linkmic: false,\n        live_type_normal: true,\n        live_type_sandbox: false,\n        live_type_screenshot: false,\n        live_type_social_live: false,\n        live_type_third_party: false,\n        living_room_attrs: {\n            admin_flag: 0,\n            rank: 0,\n            room_id: 7137682087200558000,\n            room_id_str: '7137682087200557829',\n            silence_flag: 0\n        },\n        lottery_finish_time: 0,\n        mosaic_status: 0,\n        os_type: 1,\n        owner: {\n            allow_find_by_contacts: false,\n            allow_others_download_video: false,\n            allow_others_download_when_sharing_video: false,\n            allow_share_show_profile: false,\n            allow_show_in_gossip: false,\n            allow_show_my_action: false,\n            allow_strange_comment: false,\n            allow_unfollower_comment: false,\n            allow_use_linkmic: false,\n            avatar_large: [\n                \"\u003cObject\u003e\"\n            ],\n            avatar_medium: [\n                \"\u003cObject\u003e\"\n            ],\n            avatar_thumb: [\n                \"\u003cObject\u003e\"\n            ],\n            badge_image_list: [],\n            badge_list: [],\n            bg_img_url: '',\n            bio_description: 'HH📍🇩🇪تابعوني انستغرام\\nاذا سقطت سأخذ الجميع معي\\n👻Alin_issa22👻',\n            block_status: 0,\n            border_list: [],\n            comment_restrict: 0,\n            commerce_webcast_config_ids: [],\n            constellation: '',\n            create_time: 0,\n            deprecated1: 0,\n            deprecated12: 0,\n            deprecated13: 0,\n            deprecated15: 0,\n            deprecated16: false,\n            deprecated17: false,\n            deprecated18: '',\n            deprecated19: false,\n            deprecated2: 0,\n            deprecated21: 0,\n            deprecated28: false,\n            deprecated29: '',\n            deprecated3: 0,\n            deprecated4: 0,\n            deprecated5: '',\n            deprecated6: 0,\n            deprecated7: '',\n            deprecated8: 0,\n            disable_ichat: 0,\n            display_id: 'alin.i7',\n            enable_ichat_img: 0,\n            exp: 0,\n            fan_ticket_count: 0,\n            fold_stranger_chat: false,\n            follow_info: [\n                \"\u003cObject\u003e\"\n            ],\n            follow_status: 0,\n            ichat_restrict_type: 0,\n            id: 6672446849804223000,\n            id_str: '6672446849804223493',\n            is_follower: false,\n            is_following: false,\n            link_mic_stats: 0,\n            media_badge_image_list: [],\n            modify_time: 1661427082,\n            need_profile_guide: false,\n            new_real_time_icons: [],\n            nickname: '🦋ALIN🦋',\n            own_room: [\n                \"\u003cObject\u003e\"\n            ],\n            pay_grade: [\n                \"\u003cObject\u003e\"\n            ],\n            pay_score: 0,\n            pay_scores: 0,\n            push_comment_status: false,\n            push_digg: false,\n            push_follow: false,\n            push_friend_action: false,\n            push_ichat: false,\n            push_status: false,\n            push_video_post: false,\n            push_video_recommend: false,\n            real_time_icons: [],\n            sec_uid: 'MS4wLjABAAAAuUKuWAiw0GQO2_zOeyns0YCBRK7ztdoDWAAQ6gPFLBNSdTs-g5BsgScwTD9jWeK_',\n            secret: 0,\n            share_qrcode_uri: '',\n            special_id: '',\n            status: 1,\n            ticket_count: 0,\n            top_fans: [],\n            top_vip_no: 0,\n            upcoming_event_list: [],\n            user_attr: [\n                \"\u003cObject\u003e\"\n            ],\n            user_role: 0,\n            verified: false,\n            verified_content: '',\n            verified_reason: '',\n            with_car_management_permission: false,\n            with_commerce_permission: false,\n            with_fusion_shop_entry: false\n        },\n        owner_device_id: 0,\n        owner_device_id_str: '',\n        owner_user_id: 6672446849804223000,\n        owner_user_id_str: '',\n        pre_enter_time: 0,\n        preview_flow_tag: 0,\n        ranklist_audience_type: 0,\n        relation_tag: '',\n        replay: true,\n        room_audit_status: 0,\n        room_auth: {\n            Banner: 1,\n            BroadcastMessage: 0,\n            Chat: true,\n            ChatL2: false,\n            ChatSubOnly: false,\n            CommercePermission: 0,\n            CustomizablePoll: 0,\n            Danmaku: false,\n            Digg: true,\n            DonationSticker: 2,\n            EventPromotion: 0,\n            Gift: true,\n            GiftAnchorMt: 1,\n            GiftPoll: 0,\n            GoldenEnvelope: 0,\n            GoldenEnvelopeActivity: 0,\n            InteractionQuestion: true,\n            Landscape: 2,\n            LandscapeChat: 0,\n            LuckMoney: true,\n            Pictionary: 0,\n            Poll: 0,\n            Promote: false,\n            PromoteOther: 0,\n            Props: false,\n            PublicScreen: 1,\n            QuickChat: 0,\n            Rank: 0,\n            RoomContributor: false,\n            Share: true,\n            ShareEffect: 0,\n            ShoppingRanking: 0,\n            UserCard: true,\n            UserCount: 0,\n            Viewers: false,\n            deprecated1: false,\n            deprecated2: 0,\n            deprecated3: 0,\n            deprecated4: 0,\n            deprecated5: 0,\n            deprecated6: 0,\n            deprecated7: 0,\n            deprecated8: 0,\n            deprecated9: 0,\n            transaction_history: 1,\n            use_user_pv: false\n        },\n        room_create_ab_param: '',\n        room_layout: 0,\n        room_sticker_list: [],\n        room_tabs: [],\n        room_tag: 0,\n        scroll_config: '',\n        search_id: 0,\n        share_msg_style: 2,\n        share_url: 'https://m.tiktok.com/share/live/7137682087200557829/?language=en',\n        short_title: '',\n        short_touch_items: [],\n        social_interaction: {\n            linkmic_scene_linker: {\n            },\n            multi_live: [\n                \"\u003cObject\u003e\"\n            ]\n        },\n        start_time: 0,\n        stats: {\n            deprecated1: 0,\n            deprecated2: '',\n            digg_count: 0,\n            enter_count: 0,\n            fan_ticket: 0,\n            follow_count: 686,\n            gift_uv_count: 0,\n            id: 7137682087200558000,\n            id_str: '7137682087200557829',\n            like_count: 0,\n            replay_fan_ticket: 0,\n            replay_viewers: 64076,\n            share_count: 0,\n            total_user: 104582,\n            total_user_desp: '',\n            user_count_composition: [\n                \"\u003cObject\u003e\"\n            ],\n            watermelon: 0\n        },\n        status: 2,\n        sticker_list: [],\n        stream_id: 2993830046178738000,\n        stream_id_str: '2993830046178738249',\n        stream_status: 0,\n        stream_url: {\n            candidate_resolution: [\n                \"\u003cArray\u003e\"\n            ],\n            complete_push_urls: [],\n            default_resolution: 'ORIGION',\n            extra: [\n                \"\u003cObject\u003e\"\n            ],\n            flv_pull_url: [\n                \"\u003cObject\u003e\"\n            ],\n            flv_pull_url_params: [\n                \"\u003cObject\u003e\"\n            ],\n            hls_pull_url: 'https://pull-hls-f16-va01.tiktokcdn.com/stage/stream-2993830046178738249_or4/index.m3u8',\n            hls_pull_url_map: {\n            },\n            hls_pull_url_params: '{\"VCodec\":\"h264\"}',\n            id: 2993830046178738000,\n            id_str: '2993830046178738249',\n            live_core_sdk_data: [\n                \"\u003cObject\u003e\"\n            ],\n            provider: 0,\n            push_urls: [],\n            resolution_name: [\n                \"\u003cObject\u003e\"\n            ],\n            rtmp_pull_url: 'https://pull-f5-va01.tiktokcdn.com/stage/stream-2993830046178738249_or4.flv',\n            rtmp_pull_url_params: '{\"VCodec\":\"h264\"}',\n            rtmp_push_url: '',\n            rtmp_push_url_params: '',\n            stream_control_type: 0\n        },\n        stream_url_filtered_info: {\n            is_gated_room: false,\n            is_paid_event: false\n        },\n        title: 'انا جيت😍 🥰',\n        top_fans: [\n            [\n                \"\u003cObject\u003e\"\n            ],\n            [\n                \"\u003cObject\u003e\"\n            ],\n            [\n                \"\u003cObject\u003e\"\n            ]\n        ],\n        use_filter: false,\n        user_count: 1136,\n        user_share_text: '',\n        video_feed_tag: '',\n        webcast_comment_tcs: 0,\n        webcast_sdk_version: 0,\n        with_draw_something: false,\n        with_ktv: false,\n        with_linkmic: true\n    },\n    availableGifts: []\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `disconnected`\n\nTriggered when the connection gets disconnected. In that case you can call `connect()` again to have a reconnect logic.\nNote that you should wait a little bit before attempting a reconnect to to avoid being rate-limited.\n\n```ts\nconnection.on(ControlEvent.DISCONNECTED, () =\u003e console.log('Disconnected :('));\n```\n\n### `streamEnd`\n\nTriggered when the live stream gets terminated by the host. Will also trigger the [`disconnected`](#disconnected) event.\n\n```ts\nconnection.on(ControlEvent.STREAM_END, ({ action }: { action: ControlAction }) =\u003e {\n    if (action === ControlAction.CONTROL_ACTION_STREAM_ENDED) {\n        console.log('Stream ended by user');\n    }\n    if (action === ControlAction.CONTROL_ACTION_STREAM_SUSPENDED) {\n        console.log('Stream ended by platform moderator (ban)');\n    }\n});\n```\n\n### `rawData`\n\nTriggered every time a protobuf encoded webcast message arrives. You can deserialize the binary object depending on the\nuse case with \u003ca href=\"https://www.npmjs.com/package/protobufjs\"\u003eprotobufjs\u003c/a\u003e.\n\n```ts\nconnection.on(ControlEvent.RAW_DATA, (messageTypeName, binary) =\u003e {\n    console.log(messageTypeName, binary);\n});\n```\n\n### `decodedData`\n\nTriggered every time a decoded message arrives. This is the same as the `rawData` event but the binary object is already\ndecoded. You can use this event to handle the protobuf messages without the need of deserializing them yourself.\n\n```ts\nconnection.on(ControlEvent.DECODED_DATA, (event: string, decodedData: any, binary: Uint8Array) =\u003e {\n    console.log(event, decodedData);\n});\n```\n\n### `websocketConnected`\n\nWill be triggered as soon as a WebSocket connection is established. The WebSocket client object is passed.\n\n```ts\nconnection.on(ControlEvent.WEBSOCKET_CONNECTED, (client: TikTokWsClient) =\u003e {\n    console.log('WebSocket Client:', websocketClient.connection);\n});\n```\n\n### `error`\n\nGeneral error event. You should handle this.\n\n```ts\nconnection.on(ControlEvent.ERROR, err =\u003e {\n    console.error('Error!', err);\n});\n```\n\n## Message Events\n\n### `member`\n\nTriggered every time a new viewer joins the live stream.\n\n```ts\nconnection.on(WebcastEvent.MEMBER, (data: WebcastMemberMessage) =\u003e {\n    console.log(`${data.uniqueId} joins the stream!`);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    actionId: 1,\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp\",\n    followRole: 0,\n    // 0 = none; 1 = follower; 2 = friends\n    userBadges: [\n        {\n            type: \"pm_mt_moderator_im\",\n            name: \"Moderator\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/rankl...image\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/....~...image\"\n        }\n    ],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp\",\n            \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp\",\n            \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 2139,\n        followerCount: 853,\n        followStatus: 0,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null,\n    msgId: \"7137750885996120859\",\n    createTime: \"1661887134195\",\n    displayType: \"live_room_enter_toast\",\n    label: \"{0:user} joined\"\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `chat`\n\nTriggered every time a new chat comment arrives.\n\n```ts\nconnection.on(WebcastEvent.CHAT, (data: WebcastChatMessage) =\u003e {\n    console.log(`${data.uniqueId} -\u003e ${data.comment}`);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    comment: \"How are you?\",\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n    followRole: 0,\n    // 0 = none; 1 = follower; 2 = friends\n    userBadges: [\n        {\n            // Moderator badge\n            type: \"pm_mt_moderator_im\",\n            name: \"Moderator\"\n        },\n        {\n            // Top Gifter badge\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/rankl...image\"\n        },\n        {\n            // Subscriber Badge\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/....~...image\"\n        }\n    ],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 10000,\n        followerCount: 606,\n        followStatus: 0,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null,\n    msgId: \"7137750790064065286\",\n    createTime: \"1661887134718\"\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `gift`\n\nTriggered every time a gift arrives. You will receive additional information via the `extendedGiftInfo` attribute when\nyou enable the [`enableExtendedGiftInfo`](#params-and-options) option.\n\n\u003e **NOTE:** Users have the capability to send gifts in a streak. This increases the `repeatCount` value until the user\n\u003e terminates the streak. During this time new gift events are triggered again and again with an increased `repeatCount`\n\u003e value. It should be noted that after the end of the streak, another gift event is triggered, which signals the end of\n\u003e the streak via `repeatEnd`:`true`. This applies only to gifts with `giftType`:`1`. This means that even if the user\n\u003e sends a `giftType`:`1` gift only once, you will receive the event twice. Once with `repeatEnd`:`false` and once\n\u003e with `repeatEnd`:`true`. Therefore, the event should be handled as follows:\n\n```ts\nconnection.on(WebcastEvent.GIFT, (event: WebcastGiftMessage) =\u003e {\n    if (data.giftType === 1 \u0026\u0026 !data.repeatEnd) {\n        // Streak in progress =\u003e show only temporary\n        console.log(`${data.uniqueId} is sending gift ${data.giftName} x${data.repeatCount}`);\n    } else {\n        // Streak ended or non-streakable gift =\u003e process the gift with final repeat_count\n        console.log(`${data.uniqueId} has sent gift ${data.giftName} x${data.repeatCount}`);\n    }\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    // Gift Details\n    giftId: 5953,\n    repeatCount: 1,\n    repeatEnd: true,\n    groupId: \"1661887131074\",\n    monitorExtra: {\n        anchor_id: 7087613897129494000,\n        from_idc: \"maliva\",\n        from_user_id: 7044640112358049000,\n        gift_id: 5953,\n        gift_type: 1,\n        log_id: \"20220830191849010192055159174B7670\",\n        msg_id: 7137749190944230000,\n        repeat_count: 1,\n        repeat_end: 1,\n        room_id: 7137728632142843000,\n        send_gift_profit_core_start_ms: 0,\n        send_gift_send_message_success_ms: 1661887134397,\n        to_user_id: 7087613897129494000\n    },\n    // Sender Details\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n    followRole: 0,\n    // 0 = none; 1 = follower; 2 = friends\n    userBadges: [],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 360,\n        followerCount: 740,\n        followStatus: 0,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null,\n    msgId: \"7137749190944230150\",\n    createTime: \"1661887134397\",\n    displayType: \"webcast_aweme_gift_send_message\",\n    label: \"{0:user} sent {1:gift} {2:string}\",\n    gift: {\n        gift_id: 5953,\n        repeat_count: 1,\n        repeat_end: 1,\n        gift_type: 1\n    },\n    describe: \"Sent Nevalyashka doll\",\n    giftType: 1,\n    diamondCount: 25,\n    giftName: \"Nevalyashka doll\",\n    giftPictureUrl: \"https://p19-webcast.tiktokcdn.com/img/maliva/webca...png\",\n    timestamp: 1661887134397,\n    extendedGiftInfo: {\n        // This will be filled when you enable the `enableExtendedGiftInfo` option\n    },\n    // Receiver Details (can also be a guest broadcaster)\n    receiverUserId: \"7087613897129493510\"\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `roomUser`\n\nTriggered every time a statistic message arrives. This message currently contains the viewer count and a top gifter\nlist.\n\n```ts\nconnection.on(WebcastEvent.ROOM_USER, data =\u003e {\n    console.log(`Viewer Count: ${data.viewerCount}`);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    topViewers: [\n        {\n            user: {\n                userId: \"6822565897317303297\",\n                secUid: \"MS4wLjABAAAALIKFhzvmiCws6B6KWfRgWr5MbyGVPXevakvnP8xc7VLkWtcqNeEe9coyRA74KNxm\",\n                uniqueId: \"linmjh\",\n                nickname: \"gì z má ( ^ㆍㅅㆍ^)\",\n                profilePictureUrl: \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...webp\",\n                followRole: 0,\n                userBadges: [],\n                userDetails: {\n                    createTime: \"1588502711\",\n                    bioDescription: \"\",\n                    profilePictureUrls: [\n                        \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...webp\",\n                        \"https://p9-sign-sg.tiktokcdn.com/aweme/100x100/tos...webp\",\n                        \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...jpeg\"\n                    ]\n                },\n                followInfo: {\n                    followingCount: 781,\n                    followerCount: 51,\n                    followStatus: 0,\n                    pushStatus: 0\n                },\n                isModerator: false,\n                isNewGifter: false,\n                isSubscriber: false,\n                topGifterRank: null\n            },\n            coinCount: 0\n        },\n        {\n            user: {\n                userId: \"6828542044454863874\",\n                secUid: \"MS4wLjABAAAAxP4NgzG7uJz1tcB8o3JN8PxHWej20NJWCHP1IG1PZ0OmQLB6SVORRSoX0Ool4dwj\",\n                uniqueId: \"xuanthainguyen0\",\n                nickname: \"Xuan Thai Nguyen\",\n                profilePictureUrl: \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/ti...webp\",\n                followRole: 0,\n                userBadges: [],\n                userDetails: {\n                    createTime: \"1593865836\",\n                    bioDescription: \"\",\n                    profilePictureUrls: [\n                        \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/ti...webp\",\n                        \"https://p9-sign-sg.tiktokcdn.com/aweme/100x100/tik...webp\",\n                        \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/ti...jpeg\"\n                    ]\n                },\n                followInfo: {\n                    followingCount: 6,\n                    followerCount: 6,\n                    followStatus: 0,\n                    pushStatus: 0\n                },\n                isModerator: false,\n                isNewGifter: false,\n                isSubscriber: false,\n                topGifterRank: null\n            },\n            coinCount: 0\n        },\n        {\n            user: {\n                userId: \"7014684709204624385\",\n                secUid: \"MS4wLjABAAAAnVMJ9MXN5HqjnpyEwgEhjv97Pc_ixtG4Iwnnagbrd99WhEATfhZLW6McX-uErTp9\",\n                uniqueId: \"dyip0c3sbo2t\",\n                nickname: \"Huu Trân572\",\n                profilePictureUrl: \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/ti...webp\",\n                followRole: 0,\n                userBadges: [],\n                userDetails: {\n                    createTime: \"1640318249\",\n                    bioDescription: \"\",\n                    profilePictureUrls: [\n                        \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/ti...webp\",\n                        \"https://p9-sign-sg.tiktokcdn.com/aweme/100x100/tik...webp\",\n                        \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/ti...jpeg\"\n                    ]\n                },\n                followInfo: {\n                    followingCount: 35,\n                    followerCount: 21,\n                    followStatus: 0,\n                    pushStatus: 0\n                },\n                isModerator: false,\n                isNewGifter: false,\n                isSubscriber: false,\n                topGifterRank: null\n            },\n            coinCount: 0\n        },\n        {\n            user: {\n                userId: \"7133413217468187675\",\n                secUid: \"MS4wLjABAAAA2u64n6KnroBOMQo4pR9bLv0twyCIy0X-wd7S__WR4d2VObktWAfs_ck08pjD4hIV\",\n                uniqueId: \"uservay64gw9d5\",\n                nickname: \"uservay64gw9d5\",\n                profilePictureUrl: \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp\",\n                followRole: 0,\n                userBadges: [],\n                userDetails: {\n                    createTime: \"1660877330\",\n                    bioDescription: \"\",\n                    profilePictureUrls: [\n                        \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp\",\n                        \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...jpeg\"\n                    ]\n                },\n                followInfo: {\n                    followingCount: 2,\n                    followerCount: 0,\n                    followStatus: 0,\n                    pushStatus: 0\n                },\n                isModerator: false,\n                isNewGifter: false,\n                isSubscriber: false,\n                topGifterRank: null\n            },\n            coinCount: 0\n        },\n        {\n            user: {\n                userId: \"6800374961430791170\",\n                secUid: \"MS4wLjABAAAAF3tD_kSi9qas_10I5I5YUIBfXKd0KlKvKTKACzfXS1Wwp04e03xJCTswwzCMRgEu\",\n                uniqueId: \"hungtran0293\",\n                nickname: \"Trần Hùng\",\n                profilePictureUrl: \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp\",\n                followRole: 0,\n                userBadges: [],\n                userDetails: {\n                    createTime: \"1585370455\",\n                    bioDescription: \"\",\n                    profilePictureUrls: [\n                        \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...webp\",\n                        \"https://p16-sign-va.tiktokcdn.com/tos-useast2a-avt...jpeg\"\n                    ]\n                },\n                followInfo: {\n                    followingCount: 1735,\n                    followerCount: 313,\n                    followStatus: 0,\n                    pushStatus: 0\n                },\n                isModerator: false,\n                isNewGifter: false,\n                isSubscriber: false,\n                topGifterRank: null\n            },\n            coinCount: 0\n        }\n    ],\n    viewerCount: 630\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `like`\n\nTriggered when a viewer sends likes to the streamer. For streams with many viewers, this event is not always triggered\nby TikTok.\n\n```ts\nconnection.on(WebcastEvent.LIKE, data =\u003e {\n    console.log(`${data.uniqueId} sent ${data.likeCount} likes, total likes: ${data.totalLikeCount}`);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    likeCount: 6,\n    // likes given by the user (taps on screen)\n    totalLikeCount: 21349,\n    // likes that this stream has received in total (from all users)\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n    followRole: 0,\n    // 0 = none; 1 = follower; 2 = friends,\n    userBadges: [\n        {\n            type: \"pm_mt_moderator_im\",\n            name: \"Moderator\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/rankl...image\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/....~...image\"\n        }\n    ],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p19-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 617,\n        followerCount: 112,\n        followStatus: 1,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null,\n    msgId: \"7137750883651619630\",\n    createTime: \"1661887134554\",\n    displayType: \"pm_mt_msg_viewer\",\n    label: \"{0:user} liked the LIVE\"\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `social`\n\nTriggered every time someone shares the stream or follows the host.\n\n```ts\nconnection.on(WebcastEvent.SOCIAL, data =\u003e {\n    console.log('social event data:', data);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n    followRole: 1,\n    userBadges: [],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p19-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 277,\n        followerCount: 96,\n        followStatus: 1,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null,\n    msgId: \"7137750889884076842\",\n    createTime: \"1661887134629\",\n    displayType: \"pm_main_follow_message_viewer_2\",\n    // or pm_mt_guidance_share\n    label: \"{0:user} followed the host\"\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `emote`\n\nTriggered every time a subscriber sends an emote (sticker).\n\n```ts\nconnection.on(WebcastEvent.EMOTE, (data: WebcastEmoteChatMessage) =\u003e {\n    console.log('emote received', data);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...webp\",\n    followRole: 0,\n    // 0 = none; 1 = follower; 2 = friends,\n    userBadges: [],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign-sg.tiktokcdn.com/tos-alisg-avt-00...webp\",\n            \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...webp\",\n            \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 14,\n        followerCount: 6,\n        followStatus: 1,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: true,\n    topGifterRank: null,\n    emoteId: \"7121025198379731714\",\n    emoteImageUrl: \"https://p19-webcast.tiktokcdn.com/webcast-sg/61964...image\"\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `envelope`\n\nTriggered every time someone sends a treasure chest.\n\n```ts\nconnection.on(WebcastEvent.ENVELOPE, data =\u003e {\n    console.log('envelope received', data);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-webcast.tiktokcdn.com/img/alisg/webcas...png\",\n    followRole: 0,\n    // 0 = none; 1 = follower; 2 = friends\n    userBadges: [],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-webcast.tiktokcdn.com/img/alisg/webcas...png\",\n            \"https://p19-webcast.tiktokcdn.com/img/alisg/webcas...png\"\n        ]\n    },\n    followInfo: {\n        followingCount: 828,\n        followerCount: 1353,\n        followStatus: 0,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null,\n    coins: 20,\n    canOpen: 20,\n    timestamp: 1661887422\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `questionNew`\n\nTriggered every time someone asks a new question via the question feature.\n\n```ts\nconnection.on(WebcastEvent.QUESTION_NEW, data =\u003e {\n    console.log(`${data.uniqueId} asks ${data.questionText}`);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    questionText: \"Do you know why TikTok has such a complicated API?\",\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n    followRole: 0,\n    // 0 = none; 1 = follower; 2 = friends\n    userBadges: [],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 982,\n        followerCount: 175,\n        followStatus: 0,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `linkMicBattle`\n\nTriggered every time a battle starts.\n\n```ts\nconnection.on(WebcastEvent.LINK_MIC_BATTLE, (data) =\u003e {\n    console.log(`New Battle: ${data.battleUsers[0].uniqueId} VS ${data.battleUsers[1].uniqueId}`);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    battleUsers: [\n        {\n            userId: \"6901252963970515973\",\n            // Host\n            uniqueId: \"growsa_fluffynation\",\n            nickname: \"GrowSA_FluffyNation\",\n            profilePictureUrl: \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n            userBadges: [],\n            userDetails: {\n                profilePictureUrls: [\n                    \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n                    \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...jpeg\"\n                ]\n            },\n            isModerator: false,\n            isNewGifter: false,\n            isSubscriber: false,\n            topGifterRank: null\n        },\n        {\n            userId: \"262781145296064512\",\n            // Guest\n            uniqueId: \"real_martinpinkysmith\",\n            nickname: \"Martin Pinky Smith\",\n            profilePictureUrl: \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...webp\",\n            userBadges: [],\n            userDetails: {\n                profilePictureUrls: [\n                    \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...webp\",\n                    \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...jpeg\"\n                ]\n            },\n            isModerator: false,\n            isNewGifter: false,\n            isSubscriber: false,\n            topGifterRank: null\n        }\n    ]\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `linkMicArmies`\n\nTriggered every time a battle participant receives points. Contains the current status of the battle and the army that\nsuported the group.\n\n```ts\nconnection.on(WebcastEvent.LINK_MIC_ARMIES, (data) =\u003e {\n    console.log('linkMicArmies', data);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    battleStatus: 1,\n    battleArmies: [\n        {\n            hostUserId: \"6842213780475085829\",\n            points: 0,\n            participants: []\n        },\n        {\n            hostUserId: \"6722878711857579013\",\n            points: 33,\n            participants: [\n                {\n                    userId: \"7122168301669204994\",\n                    secUid: \"\",\n                    nickname: \"🦋\",\n                    profilePictureUrl: null,\n                    userBadges: [],\n                    userDetails: {\n                        createTime: \"0\",\n                        bioDescription: \"\"\n                    },\n                    isModerator: false,\n                    isNewGifter: false,\n                    isSubscriber: false,\n                    topGifterRank: null\n                },\n                {\n                    userId: \"7112729060212966406\",\n                    secUid: \"\",\n                    nickname: \"ealkaabi44\",\n                    profilePictureUrl: null,\n                    userBadges: [],\n                    userDetails: {\n                        createTime: \"0\",\n                        bioDescription: \"\"\n                    },\n                    isModerator: false,\n                    isNewGifter: false,\n                    isSubscriber: false,\n                    topGifterRank: null\n                },\n                {\n                    userId: \"7006435669158708229\",\n                    secUid: \"\",\n                    nickname: \"worood🦁 🌹🌹🌹🌹\",\n                    profilePictureUrl: null,\n                    userBadges: [],\n                    userDetails: {\n                        createTime: \"0\",\n                        bioDescription: \"\"\n                    },\n                    isModerator: false,\n                    isNewGifter: false,\n                    isSubscriber: false,\n                    topGifterRank: null\n                }\n            ]\n        }\n    ]\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `liveIntro`\n\nTriggered when a live intro message appears.\n\n```ts\nconnection.on(WebcastEvent.LIVE_INTRO, (msg) =\u003e {\n    console.log(msg);\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    id: \"1658723381\",\n    description: \"welcome to my broadcast!\",\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n    followRole: 0,\n    userBadges: [\n        {\n            type: \"pm_mt_moderator_im\",\n            name: \"Moderator\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/rankl...image\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/....~...image\"\n        }\n    ],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n            \"https://p77-sign-va.tiktokcdn.com/tos-maliva-avt-0...webp\",\n            \"https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 886,\n        followerCount: 57141,\n        followStatus: 0,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `subscribe`\n\nTriggers when a user creates a subscription.\n\n```ts\nconnection.on(WebcastEvent.SUBSCRIBE, (data) =\u003e {\n    console.log(data.uniqueId, 'subscribed!');\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    subMonth: 1,\n    oldSubscribeStatus: 2,\n    subscribingStatus: 1,\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...webp\",\n    followRole: 0,\n    userBadges: [],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...webp\",\n            \"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/to...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 23,\n        followerCount: 43,\n        followStatus: 0,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null,\n    msgId: \"7137745705032043266\",\n    createTime: \"1661885986187\",\n    displayType: \"pm_mt_subinfo_user\",\n    label: \"{0:user} just subscribed to the host\"\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### Custom Events\n\nThese events are based on message events.\n\n### `follow`\n\nTriggers when a user follows the streamer. Based on `social` event.\n\n```ts\nconnection.on(WebcastEvent.FOLLOW, (data) =\u003e {\n    console.log(data.uniqueId, 'followed!');\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n    followRole: 1,\n    userBadges: [\n        {\n            type: \"pm_mt_moderator_im\",\n            name: \"Moderator\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/rankl...image\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/....~...image\"\n        }\n    ],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p19-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 277,\n        followerCount: 96,\n        followStatus: 1,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null,\n    msgId: \"7137750889884076842\",\n    createTime: \"1661887134629\",\n    displayType: \"pm_main_follow_message_viewer_2\",\n    label: \"{0:user} followed the host\"\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n### `share`\n\nTriggers when a user shares the stream. Based on `social` event.\n\n```ts\nconnection.on(WebcastEvent.SHARE, (data) =\u003e {\n    console.log(data.uniqueId, \"shared the stream!\");\n})\n```\n\n\u003cdetails\u003e\u003csummary\u003e⚡ See Data Structure\u003c/summary\u003e\u003cp\u003e\n\n```json5\n{\n    userId: \"6813181309701719620\",\n    secUid: \"\u003credacted\u003e\",\n    uniqueId: \"zerodytester\",\n    nickname: \"Zerody Tester\",\n    profilePictureUrl: \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n    followRole: 1,\n    userBadges: [\n        {\n            type: \"pm_mt_moderator_im\",\n            name: \"Moderator\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/rankl...image\"\n        },\n        {\n            type: \"image\",\n            displayType: 1,\n            url: \"https://p19-webcast.tiktokcdn.com/webcast-va/....~...image\"\n        }\n    ],\n    userDetails: {\n        createTime: \"0\",\n        bioDescription: \"\",\n        profilePictureUrls: [\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p19-sign.tiktokcdn-us.com/tos-useast5-avt-...webp\",\n            \"https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-...jpeg\"\n        ]\n    },\n    followInfo: {\n        followingCount: 277,\n        followerCount: 96,\n        followStatus: 1,\n        pushStatus: 0\n    },\n    isModerator: false,\n    isNewGifter: false,\n    isSubscriber: false,\n    topGifterRank: null,\n    msgId: \"7137750889884076842\",\n    createTime: \"1661887134629\",\n    displayType: \"pm_mt_guidance_share\",\n    label: \"{0:user} shared the live\"\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n## Examples\n\n### Retrieve Room Info\n\n````ts\nconst connection = new TikTokLiveConnection('@username');\n\nconnection.fetchRoomInfo().then((roomInfo: RoomInfoResponse) =\u003e {\n    console.log(roomInfo);\n    console.log(`Stream started timestamp: ${roomInfo.create_time}, Streamer bio: ${roomInfo.owner.bio_description}`);\n    console.log(`HLS URL: ${roomInfo.stream_url.hls_pull_url}`); // Can be played or recorded with e.g. VLC\n}).catch(err =\u003e {\n    console.error(err);\n});\n````\n\n### Retrieve Available Gifts\n\n````ts\nconst connection = new TikTokLiveConnection('@username');\n\nconnection.fetchAvailableGifts().then((giftList: RoomGiftInfo) =\u003e {\n    console.log(giftList);\n    giftList.forEach(gift =\u003e {\n        console.log(`id: ${gift.id}, name: ${gift.name}, cost: ${gift.diamond_count}`)\n    });\n}).catch(err =\u003e {\n    console.error(err);\n})\n````\n\n### Proxied Connection\n\nIt is possible to proxy connections. The library [proxy-agent](https://www.npmjs.com/package/proxy-agent)\nsupports `http`, `https`, `socks4` and `socks5` proxies:\n\n````\nnpm i proxy-agent\n````\n\nYou can specify if you want to use a proxy for https requests, websockets or both:\n\n````ts\nimport { TikTokLiveConnection } from 'tiktok-live-connector';\nimport ProxyAgent from 'proxy-agent';\n\nconst connection = new TikTokLiveConnection('@username', {\n    webClientOptions: {\n        httpsAgent: new ProxyAgent('https://username:password@host:port'),\n        timeout: 10000 // 10 seconds\n    },\n    wsClientOptions: {\n        agent: new ProxyAgent('https://username:password@host:port'),\n        timeout: 10000 // 10 seconds\n    }\n});\n\n// Connect as per usual\n````\n\n### Send Messages\n\nAs of `2.0.2` you can now send messages to TikTok LIVE chats!\n\n```ts\nimport { TikTokLiveConnection } from 'tiktok-live-connector';\nimport { connection } from 'websocket';\n\nconst connection = new TikTokLiveConnection(\n    'tv_asahi_news',\n    {\n        sessionId: '\u003caccount_session_id\u003e',\n        signApiKey: '\u003cyour_sign_api_key\u003e'\n    }\n);\n\n// Connect, then send a chat!\nconnection.connect().then(() =\u003e {\n    connection.sendMessage('Hello world!');\n    console.log('Connected to TikTok LIVE chat!');\n}).catch(err =\u003e {\n    console.error('Error connecting to TikTok LIVE chat:', err);\n});\n```\n\n## Contributors\n\n* **Zerody** - *Initial Reverse-Engineering and Protobuf-Decoding* - [Zerody](https://github.com/zerodytrash/)\n* **Isaac Kogan** - *TypeScript Rewrite, Sign-Server Maintainer* - [isaackogan](https://github.com/isaackogan)\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzerodytrash%2Ftiktok-live-connector","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzerodytrash%2Ftiktok-live-connector","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzerodytrash%2Ftiktok-live-connector/lists"}