Skip to content

Commit

Permalink
experimental room info access
Browse files Browse the repository at this point in the history
experimental room info access
  • Loading branch information
ZmoleCristian committed Jun 12, 2024
2 parents 1575e89 + 20906a8 commit 3a653ff
Show file tree
Hide file tree
Showing 10 changed files with 1,194 additions and 91 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
patreon: jwdeveloper
custom: ["https://www.buymeacoffee.com/jwdev"]
custom: ["https://www.buymeacoffee.com/jwdev"]
patreon: tragdate
96 changes: 87 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ Do you prefer other programming languages?

```toml
[dependencies]
tiktoklive = "0.0.12"
tiktoklive = "0.0.13"
tokio = { version = "1.35.1", features = ["full"] }
serde_json = "1.0"
log = "0.4"
env_logger = "0.10.1"
```
Expand All @@ -57,11 +58,12 @@ env_logger = "0.10.1"
```rust
use env_logger::{Builder, Env}; // Importing the logger builder and environment configuration
use log::LevelFilter; // Importing log level filter
use log::{error, warn};
use std::time::Duration; // Importing Duration for timeout settings
use tiktoklive::{
// Importing necessary modules and structs from tiktoklive crate
core::live_client::TikTokLiveClient,
data::live_common::TikTokLiveSettings,
data::live_common::{ClientData, StreamData, TikTokLiveSettings},
errors::LibError,
generated::events::TikTokLiveEvent,
TikTokLive,
Expand All @@ -84,19 +86,19 @@ async fn main() {
// Match on the error type
LibError::LiveStatusFieldMissing => {
// Specific error case
println!(
warn!(
"Failed to get live status (probably needs authenticated client): {}",
e
);
let auth_client = create_client_with_cookies(user_name); // Create an authenticated client
if let Err(e) = auth_client.connect().await {
// Attempt to connect the authenticated client
eprintln!("Error connecting to TikTok Live after retry: {}", e);
error!("Error connecting to TikTok Live after retry: {}", e);
}
}
_ => {
// General error case
eprintln!("Error connecting to TikTok Live: {}", e);
error!("Error connecting to TikTok Live: {}", e);
}
}
}
Expand All @@ -107,9 +109,35 @@ async fn main() {
handle.await.expect("The spawned task has panicked"); // Await the spawned task to ensure it completes
}

// Function to handle different types of TikTok live events
fn handle_event(_client: &TikTokLiveClient, event: &TikTokLiveEvent) {
fn handle_event(client: &TikTokLiveClient, event: &TikTokLiveEvent) {
match event {
TikTokLiveEvent::OnConnected(..) => {
// This is an experimental and unstable feature
// Get room info from the client
let room_info = client.get_room_info();
// Parse the room info
let client_data: ClientData = serde_json::from_str(room_info).unwrap();
// Parse the stream data
let stream_data: StreamData = serde_json::from_str(
&client_data
.data
.stream_url
.live_core_sdk_data
.pull_data
.stream_data,
)
.unwrap();
// Get the video URL for the low definition stream with fallback to the high definition stream in a flv format
let video_url = stream_data
.data
.ld
.map(|ld| ld.main.flv)
.or_else(|| stream_data.data.sd.map(|sd| sd.main.flv))
.or_else(|| stream_data.data.origin.map(|origin| origin.main.flv))
.expect("None of the stream types set");
println!("room info: {}", video_url);
}

// Match on the event type
TikTokLiveEvent::OnMember(join_event) => {
// Handle member join event
Expand Down Expand Up @@ -157,11 +185,12 @@ fn configure(settings: &mut TikTokLiveSettings) {
// Function to configure the TikTok live settings with cookies for authentication
fn configure_with_cookies(settings: &mut TikTokLiveSettings) {
settings.http_data.time_out = Duration::from_secs(12); // Set HTTP timeout to 12 seconds
let contents = "YOUR_COOKIES"; // Placeholder for cookies
let contents = ""; // Placeholder for cookies
settings
.http_data
.headers
.insert("Cookie".to_string(), contents.to_string()); // Insert cookies into HTTP headers
.insert("Cookie".to_string(), contents.to_string());
// Insert cookies into HTTP headers
}

// Function to create a TikTok live client for the given username
Expand All @@ -181,6 +210,55 @@ fn create_client_with_cookies(user_name: &str) -> TikTokLiveClient {
}
```

## Library errors table

You can catch errors on events with

```rust
use tiktoklive::LibError;

if let Err(e) = client.connect().await {
match e {
LibError::UserFieldMissing => {
println!("User field is missing");
}
_ => {
eprintln!("Error connecting to TikTok Live: {}", e);
}
}
}
```

| Error type | Description |
| --- | --- |
| RoomIDFieldMissing | Room ID field is missing, contact developer |
| UserFieldMissing | User field is missing |
| UserDataFieldMissing | User data field is missing |
| LiveDataFieldMissing | Live data field is missing |
| JsonParseError | Error parsing JSON |
| UserMessageFieldMissing | User message field is missing |
| ParamsError | Params error |
| UserStatusFieldMissing | User status field is missing |
| LiveStatusFieldMissing | Live status field is missing |
| TitleFieldMissing | Title field is missing |
| UserCountFieldMissing | User count field is missing |
| StatsFieldMissing | Stats field is missing |
| LikeCountFieldMissing | Like count is missing |
| TotalUserFieldMissing | Total user field is missing |
| LiveRoomFieldMissing | Live room field is missing |
| StartTimeFieldMissing | Start time field is missing |
| UserNotFound | User not found |
| HostNotOnline | Live stream for host is not online!, current status HostOffline |
| InvalidHost | Invalid host in WebSocket URL |
| WebSocketConnectFailed | Failed to connect to WebSocket |
| PushFrameParseError | Unable to read push frame |
| WebcastResponseParseError | Unable to read webcast response |
| AckPacketSendError | Unable to send ack packet |
| HttpRequestFailed | HTTP request failed |
| UrlSigningFailed | URL signing failed |
| HeaderNotReceived | Header was not received |
| BytesParseError | Unable to parse bytes to Push Frame |


?
## Contributing
Expand Down
10 changes: 8 additions & 2 deletions src/core/live_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl TikTokLiveClient {
}
}

pub async fn connect(self) -> Result<(), LibError> {
pub async fn connect(mut self) -> Result<(), LibError> {
if *(self.room_info.connection_state.lock().unwrap()) != DISCONNECTED {
warn!("Client already connected!");
return Ok(());
Expand All @@ -63,6 +63,8 @@ impl TikTokLiveClient {
room_id: room_id.clone(),
})
.await?;

self.room_info.client_data = response.json;
if response.live_status != HostOnline {
error!(
"Live stream for host is not online!, current status {:?}",
Expand All @@ -86,7 +88,7 @@ impl TikTokLiveClient {
message_mapper: TikTokLiveMessageMapper {},
running: Arc::new(AtomicBool::new(false)),
};
ws.start(response, client_arc).await;
let _ = ws.start(response?, client_arc).await;

Ok(())
}
Expand All @@ -100,6 +102,10 @@ impl TikTokLiveClient {
self.event_observer.publish(self, event);
}

pub fn get_room_info(&self) -> &String {
&self.room_info.client_data
}

pub fn set_connection_state(&self, state: ConnectionState) {
let mut data = self.room_info.connection_state.lock().unwrap();
*data = state;
Expand Down
65 changes: 26 additions & 39 deletions src/core/live_client_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,10 @@ impl TikTokLiveHttpClient {
.as_json()
.await;

if option.is_none() {
panic!("Unable to get info about user ")
}
let json = option.unwrap();
match map_live_user_data_response(json) {
Ok(response) => Ok(response),
Err(e) => Err(e),
}
let json = option.ok_or(LibError::HttpRequestFailed)?;
map_live_user_data_response(json).map_err(|e| e)
}

pub async fn fetch_live_data(
&self,
request: LiveDataRequest,
Expand All @@ -58,30 +53,23 @@ impl TikTokLiveHttpClient {
.as_json()
.await;

if option.is_none() {
panic!("Unable to get info about live room")
}
let json = option.unwrap();

match map_live_data_response(json) {
Ok(response) => Ok(response),
Err(e) => Err(e),
}
let json = option.ok_or(LibError::HttpRequestFailed)?;
map_live_data_response(json).map_err(|e| e)
}

pub async fn fetch_live_connection_data(
&self,
request: LiveConnectionDataRequest,
) -> LiveConnectionDataResponse {
//Preparing URL to sign
) -> Result<LiveConnectionDataResponse, LibError> {
// Preparing URL to sign
let url_to_sign = self
.factory
.request()
.with_url(format!("{}{}", TIKTOK_URL_WEBCAST, "im/fetch").as_str())
.with_param("room_id", request.room_id.as_str())
.as_url();

//Signing URL
// Signing URL
let option = self
.factory
.request()
Expand All @@ -92,13 +80,10 @@ impl TikTokLiveHttpClient {
.as_json()
.await;

if option.is_none() {
panic!("Unable sign url {}", url_to_sign.as_str())
}
let json = option.unwrap();
let json = option.ok_or(LibError::UrlSigningFailed)?;
let sign_server_response = map_sign_server_response(json);

//Getting credentials for connection to websocket
// Getting credentials for connection to websocket
let response = self
.factory
.request()
Expand All @@ -108,20 +93,23 @@ impl TikTokLiveHttpClient {
.build_get_request()
.send()
.await
.unwrap();
.map_err(|_| LibError::HttpRequestFailed)?;

let optional_header = response.headers().get("set-cookie");

if optional_header.is_none() {
panic!("Header was not received not provided")
}
let header_value = optional_header.unwrap().to_str().unwrap().to_string();

let protocol_buffer_message = response.bytes().await.unwrap();
let header_value = optional_header
.ok_or(LibError::HeaderNotReceived)?
.to_str()
.map_err(|_| LibError::HeaderNotReceived)?
.to_string();

let protocol_buffer_message = response
.bytes()
.await
.map_err(|_| LibError::BytesParseError)?;
let webcast_response = WebcastResponse::parse_from_bytes(protocol_buffer_message.as_ref())
.expect("Unable to parse bytes to Push Frame!");
.map_err(|_| LibError::BytesParseError)?;

//preparing websocket url
// Preparing websocket URL
let web_socket_url = self
.factory
.request()
Expand All @@ -133,12 +121,11 @@ impl TikTokLiveHttpClient {
.with_params(&webcast_response.routeParamsMap)
.as_url();

let url = url::Url::parse(web_socket_url.as_str()).unwrap();

return LiveConnectionDataResponse {
let url = url::Url::parse(web_socket_url.as_str()).map_err(|_| LibError::InvalidHost)?;
Ok(LiveConnectionDataResponse {
web_socket_timeout: self.settings.http_data.time_out,
web_socket_cookies: header_value,
web_socket_url: url,
};
})
}
}
Loading

0 comments on commit 3a653ff

Please sign in to comment.