7 |
TikTok Live Rust
8 |
9 | ❤️❤️🎁 _Connect to TikTok live in 3 lines_ 🎁❤️❤️
10 |
11 |
22 |
23 |
24 | # Introduction
25 |
26 | A Rust library. Use it to receive live stream events such as comments and gifts in realtime from [TikTok LIVE](https://www.tiktok.com/live) No credentials are required.
27 |
28 | Join the support [discord](https://discord.gg/e2XwPNTBBr) and visit the `#rust-support` channel for questions, contributions and ideas. Feel free to make pull requests with missing/new features, fixes, etc
29 |
30 | Do you prefer other programming languages?
31 |
32 | - **Java** [TikTokLiveJava](https://github.com/jwdeveloper/TikTokLiveJava)
33 | - **Node** [TikTok-Live-Connector](https://github.com/zerodytrash/TikTok-Live-Connector) by [@zerodytrash](https://github.com/zerodytrash)
34 | - **Python** [TikTokLive](https://github.com/isaackogan/TikTokLive) by [@isaackogan](https://github.com/isaackogan)
35 | - **C#** [TikTokLiveSharp](https://github.com/frankvHoof93/TikTokLiveSharp) by [@frankvHoof93](https://github.com/frankvHoof93)
36 |
37 | **NOTE:** This is not an official API. It's a reverse engineering project.
38 |
39 | #### Overview
40 |
41 | - [Getting started](#getting-started)
42 | - [Documentation](https://docs.rs/tiktoklive/latest/tiktoklive/)
43 | - [Contributing](#contributing)
44 |
45 | ## Getting started
46 |
47 | ### Signing server API key
48 |
49 | If you don't have a signing server you can obtain a free API key from [EulerStream](https://www.eulerstream.com/)
50 |
51 | ### Dependencies
52 |
53 | ```toml
54 | [dependencies]
55 | tiktoklive = "0.0.19"
56 | tokio = { version = "1.35.1", features = ["full"] }
57 | serde_json = "1.0"
58 | log = "0.4"
59 | env_logger = "0.10.1"
60 | ```
61 |
62 | ### Usage example
63 |
64 | ```rust
65 | use env_logger::{Builder, Env}; // Importing the logger builder and environment configuration
66 | use log::LevelFilter; // Importing log level filter
67 | use log::{error, warn};
68 | use std::time::Duration; // Importing Duration for timeout settings
69 | use tiktoklive::{
70 | // Importing necessary modules and structs from tiktoklive crate
71 | core::live_client::TikTokLiveClient,
72 | data::live_common::{ClientData, StreamData, TikTokLiveSettings},
73 | errors::LibError,
74 | generated::events::TikTokLiveEvent,
75 | TikTokLive,
76 | };
77 | use tokio::signal; // Importing signal handling from tokio
78 |
79 | #[tokio::main] // Main function is asynchronous and uses tokio runtime
80 | async fn main() {
81 | init_logger("info"); // Initialize logger with "info" level
82 | let user_name = "tragdate";
83 |
84 | let client = create_client(user_name); // Create a client for the given username
85 |
86 | // Spawn a new asynchronous task to connect the client
87 | let handle = tokio::spawn(async move {
88 | // Attempt to connect the client
89 | if let Err(e) = client.connect().await {
90 | match e {
91 | // Match on the error type
92 | LibError::LiveStatusFieldMissing => {
93 | // Specific error case
94 | warn!(
95 | "Failed to get live status (probably needs authenticated client): {}",
96 | e
97 | );
98 | let auth_client = create_client_with_cookies(user_name); // Create an authenticated client
99 | if let Err(e) = auth_client.connect().await {
100 | // Attempt to connect the authenticated client
101 | error!("Error connecting to TikTok Live after retry: {}", e);
102 | }
103 | }
104 | LibError::HeaderNotReceived => {
105 | error!("Error connecting to TikTok Live: {}", e);
106 | }
107 |
108 | _ => {
109 | // General error case
110 | error!("Error connecting to TikTok Live: {}", e);
111 | }
112 | }
113 | }
114 | });
115 |
116 | signal::ctrl_c().await.expect("Failed to listen for Ctrl+C"); // Wait for Ctrl+C signal to gracefully shut down
117 |
118 | handle.await.expect("The spawned task has panicked"); // Await the spawned task to ensure it completes
119 | }
120 |
121 | fn handle_event(client: &TikTokLiveClient, event: &TikTokLiveEvent) {
122 | match event {
123 | TikTokLiveEvent::OnConnected(..) => {
124 | // This is an EXPERIMENTAL and UNSTABLE feature
125 | // Get room info from the client
126 | let room_info = client.get_room_info();
127 | // // Parse the room info
128 | let client_data: ClientData = serde_json::from_str(room_info).unwrap();
129 | // // Parse the stream data
130 | let stream_data: StreamData = serde_json::from_str(
131 | &client_data
132 | .data
133 | .stream_url
134 | .live_core_sdk_data
135 | .unwrap()
136 | .pull_data
137 | .stream_data,
138 | )
139 | .unwrap();
140 | // Get the video URL for the low definition stream with fallback to the high definition stream in a flv format
141 | let video_url = stream_data
142 | .data
143 | .ld
144 | .map(|ld| ld.main.flv)
145 | .or_else(|| stream_data.data.sd.map(|sd| sd.main.flv))
146 | .or_else(|| stream_data.data.origin.map(|origin| origin.main.flv))
147 | .expect("None of the stream types set");
148 | println!("room info: {}", video_url);
149 | }
150 |
151 | // Match on the event type
152 | TikTokLiveEvent::OnMember(join_event) => {
153 | // Handle member join event
154 | println!("user: {} joined", join_event.raw_data.user.nickname);
155 | }
156 | TikTokLiveEvent::OnChat(chat_event) => {
157 | // Handle chat event
158 | println!(
159 | "user: {} -> {}",
160 | chat_event.raw_data.user.nickname, chat_event.raw_data.content
161 | );
162 | }
163 | TikTokLiveEvent::OnGift(gift_event) => {
164 | // Handle gift event
165 | let nick = &gift_event.raw_data.user.nickname;
166 | let gift_name = &gift_event.raw_data.gift.name;
167 | let gifts_amount = gift_event.raw_data.gift.combo;
168 | println!(
169 | "user: {} sends gift: {} x {}",
170 | nick, gift_name, gifts_amount
171 | );
172 | }
173 | TikTokLiveEvent::OnLike(like_event) => {
174 | // Handle like event
175 | let nick = &like_event.raw_data.user.nickname;
176 | println!("user: {} likes", nick);
177 | }
178 | _ => {} // Ignore other events
179 | }
180 | }
181 |
182 | // Function to initialize the logger with a default log level
183 | fn init_logger(default_level: &str) {
184 | let env = Env::default().filter_or("LOG_LEVEL", default_level); // Set default log level from environment or use provided level
185 | Builder::from_env(env) // Build the logger from environment settings
186 | .filter_module("tiktoklive", LevelFilter::Debug) // Set log level for tiktoklive module
187 | .init(); // Initialize the logger
188 | }
189 |
190 | // Function to configure the TikTok live settings
191 | fn configure(settings: &mut TikTokLiveSettings) {
192 | settings.http_data.time_out = Duration::from_secs(12); // Set HTTP timeout to 12 seconds
193 | settings.sign_api_key = "".to_string(); // Provide your own api key here
194 | }
195 |
196 | // Function to configure the TikTok live settings with cookies for authentication
197 | fn configure_with_cookies(settings: &mut TikTokLiveSettings) {
198 | settings.http_data.time_out = Duration::from_secs(12); // Set HTTP timeout to 12 seconds
199 | settings.sign_api_key = "".to_string(); // Provide your own api key here
200 | let contents = ""; // Placeholder for cookies
201 | settings
202 | .http_data
203 | .headers
204 | .insert("Cookie".to_string(), contents.to_string());
205 | // Insert cookies into HTTP headers
206 | }
207 |
208 | // Function to create a TikTok live client for the given username
209 | fn create_client(user_name: &str) -> TikTokLiveClient {
210 | TikTokLive::new_client(user_name) // Create a new client
211 | .configure(configure) // Configure the client
212 | .on_event(handle_event) // Set the event handler
213 | .build() // Build the client
214 | }
215 |
216 | // Function to create a TikTok live client with cookies for the given username
217 | fn create_client_with_cookies(user_name: &str) -> TikTokLiveClient {
218 | TikTokLive::new_client(user_name) // Create a new client
219 | .configure(configure_with_cookies) // Configure the client with cookies
220 | .on_event(handle_event) // Set the event handler
221 | .build() // Build the client
222 | }
223 | ```
224 |
225 | ## Library errors table
226 |
227 | You can catch errors on events with
228 |
229 | ```rust
230 | use tiktoklive::LibError;
231 |
232 | if let Err(e) = client.connect().await {
233 | match e {
234 | LibError::UserFieldMissing => {
235 | println!("User field is missing");
236 | }
237 | _ => {
238 | eprintln!("Error connecting to TikTok Live: {}", e);
239 | }
240 | }
241 | }
242 | ```
243 |
244 | | Error type | Description |
245 | | ------------------------- | --------------------------------------------------------------- |
246 | | RoomIDFieldMissing | Room ID field is missing, contact developer |
247 | | UserFieldMissing | User field is missing |
248 | | UserDataFieldMissing | User data field is missing |
249 | | LiveDataFieldMissing | Live data field is missing |
250 | | JsonParseError | Error parsing JSON |
251 | | UserMessageFieldMissing | User message field is missing |
252 | | ParamsError | Params error |
253 | | UserStatusFieldMissing | User status field is missing |
254 | | LiveStatusFieldMissing | Live status field is missing |
255 | | TitleFieldMissing | Title field is missing |
256 | | UserCountFieldMissing | User count field is missing |
257 | | StatsFieldMissing | Stats field is missing |
258 | | LikeCountFieldMissing | Like count is missing |
259 | | TotalUserFieldMissing | Total user field is missing |
260 | | LiveRoomFieldMissing | Live room field is missing |
261 | | StartTimeFieldMissing | Start time field is missing |
262 | | UserNotFound | User not found |
263 | | HostNotOnline | Live stream for host is not online!, current status HostOffline |
264 | | InvalidHost | Invalid host in WebSocket URL |
265 | | WebSocketConnectFailed | Failed to connect to WebSocket |
266 | | PushFrameParseError | Unable to read push frame |
267 | | WebcastResponseParseError | Unable to read webcast response |
268 | | AckPacketSendError | Unable to send ack packet |
269 | | HttpRequestFailed | HTTP request failed |
270 | | UrlSigningFailed | URL signing failed |
271 | | HeaderNotReceived | Header was not received |
272 | | BytesParseError | Unable to parse bytes to Push Frame |
273 |
274 | ## Contributing
275 |
276 | Your improvements are welcome! Feel free to open an