├── FUNDING.yml ├── src ├── generated.rs ├── http.rs ├── generated │ ├── messages │ │ └── mod.rs │ ├── events.rs │ ├── events_mapper.rs │ └── events_builder.rs ├── core.rs ├── core │ ├── live_client_events.rs │ ├── live_client_mapper.rs │ ├── live_client_builder.rs │ ├── live_client.rs │ ├── live_client_http.rs │ └── live_client_websocket.rs ├── lib.rs ├── http │ ├── http_data.rs │ ├── http_request_builder.rs │ └── http_data_mappers.rs ├── data.rs ├── errors.rs ├── main.rs └── data │ └── live_common.rs ├── .idea ├── vcs.xml ├── .gitignore ├── modules.xml └── TikTokLiveRust.iml ├── .github └── workflows │ └── rust.yml ├── license.txt ├── Cargo.toml ├── .gitignore ├── README.md └── Cargo.lock /FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: jwdeveloper 2 | custom: ["https://www.buymeacoffee.com/jwdev"] 3 | -------------------------------------------------------------------------------- /src/generated.rs: -------------------------------------------------------------------------------- 1 | pub mod events; 2 | pub mod events_mapper; 3 | pub mod messages; 4 | -------------------------------------------------------------------------------- /src/http.rs: -------------------------------------------------------------------------------- 1 | pub mod http_data; 2 | 3 | pub mod http_data_mappers; 4 | pub mod http_request_builder; 5 | -------------------------------------------------------------------------------- /src/generated/messages/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | 3 | pub mod data; 4 | pub mod enums; 5 | pub mod webcast; 6 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | pub mod live_client; 2 | pub mod live_client_builder; 3 | pub mod live_client_events; 4 | pub mod live_client_http; 5 | pub mod live_client_mapper; 6 | pub mod live_client_websocket; 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/core/live_client_events.rs: -------------------------------------------------------------------------------- 1 | use crate::core::live_client::TikTokLiveClient; 2 | use crate::generated::events::TikTokLiveEvent; 3 | 4 | pub type TikTokEventHandler = fn(client: &TikTokLiveClient, event: &TikTokLiveEvent); 5 | 6 | #[derive(Clone)] 7 | pub struct TikTokLiveEventObserver { 8 | pub events: Vec, 9 | } 10 | 11 | impl TikTokLiveEventObserver { 12 | pub fn new() -> Self { 13 | TikTokLiveEventObserver { events: vec![] } 14 | } 15 | 16 | pub fn subscribe(&mut self, handler: TikTokEventHandler) { 17 | self.events.push(handler); 18 | } 19 | 20 | pub fn publish(&self, client: &TikTokLiveClient, event: TikTokLiveEvent) { 21 | for handler in &self.events { 22 | handler(client, &event); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/core/live_client_mapper.rs: -------------------------------------------------------------------------------- 1 | use crate::core::live_client::TikTokLiveClient; 2 | use crate::generated::events::{TikTokLiveEvent, TikTokWebsocketResponseEvent}; 3 | use crate::generated::messages::webcast::WebcastResponse; 4 | 5 | #[derive(Clone)] 6 | pub struct TikTokLiveMessageMapper {} 7 | 8 | impl TikTokLiveMessageMapper { 9 | pub fn handle_webcast_response( 10 | &self, 11 | webcast_response: WebcastResponse, 12 | client: &TikTokLiveClient, 13 | ) { 14 | client.publish_event(TikTokLiveEvent::OnWebsocketResponse( 15 | TikTokWebsocketResponseEvent { 16 | raw_data: webcast_response.clone(), 17 | }, 18 | )); 19 | for message in &webcast_response.messages { 20 | self.handle_single_message(message.clone(), client); 21 | } 22 | } 23 | 24 | pub fn dupa(&self, _webcast_response: WebcastResponse, _client: &TikTokLiveClient) { 25 | println!("XD") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::core::live_client_builder::TikTokLiveBuilder; 2 | 3 | /// # Data structures 4 | pub mod data; 5 | 6 | /// # Core functionality of library 7 | pub mod core; 8 | 9 | /// # Http client and request handling 10 | pub mod http; 11 | 12 | /// # ProtocolBuffer structures 13 | pub mod generated; 14 | 15 | // #Errors Library 16 | pub mod errors; 17 | 18 | /// 19 | /// # Entry point of library used to create new instance of TikTokLive client 20 | /// 21 | /// ``` 22 | /// use tiktoklive::TikTokLive; 23 | /// 24 | /// let client = TikTokLive::new_client("some-user"); 25 | // .configure(configure) 26 | // .on_event(on_event) 27 | // .build(); 28 | /// client.connect().await 29 | /// ``` 30 | /// 31 | pub struct TikTokLive {} 32 | 33 | impl TikTokLive { 34 | /// 35 | /// Returns builder for creating new TikTokLiveClient 36 | /// 37 | pub fn new_client(user_name: &str) -> TikTokLiveBuilder { 38 | TikTokLiveBuilder::new(user_name) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Publish New Version 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | CARGO_TERM_COLOR: always 8 | 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 # Ensures history is available for tags 16 | 17 | - name: Set up Rust 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: stable 21 | override: true 22 | 23 | - name: Bump version and create tag 24 | run: | 25 | git config --local user.email "action@github.com" 26 | git config --local user.name "GitHub Action" 27 | cargo install cargo-bump 28 | VERSION=$(cargo bump patch) 29 | git commit -am "Bump version to $VERSION" 30 | git tag $VERSION 31 | git push && git push --tags 32 | git branch $VERSION 33 | 34 | - name: Publish to crates.io 35 | uses: actions-rs/cargo@v1 36 | with: 37 | command: publish 38 | args: --token ${{ secrets.CRATES_IO_TOKEN }} 39 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 jwdeveloper 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tiktoklive" 3 | version = "0.0.19" 4 | description = "A Rust library. Use it to receive live stream events such as comments and gifts in realtime from TikTok LIVE No credentials are required." 5 | homepage = "https://github.com/jwdeveloper/TikTokLiveRust" 6 | repository = "https://github.com/jwdeveloper/TikTokLiveRust" 7 | keywords = ["api", "stream", "tiktok", "live", "tiktok-live", ] 8 | categories = ["api-bindings", "game-development","games","development-tools","network-programming"] 9 | license = "MIT OR Apache-2.0" 10 | edition = "2021" 11 | authors = ["JW"] 12 | readme = "README.md" 13 | 14 | 15 | [dependencies] 16 | log = "0.4" 17 | env_logger = "0.10.1" 18 | reqwest = "0.11" 19 | urlencoding = "2.1.3" 20 | serde = "1.0" 21 | serde_json = "1.0" 22 | serde_derive = "1.0" 23 | tokio = { version = "1.35.1", features = ["full"] } 24 | tokio-tungstenite = { version = "0.16.0", features = ["native-tls"] } 25 | bytes = "1.5.0" 26 | 27 | protobuf-codegen = "3.5.1" 28 | protoc-bin-vendored = "3.0.0" 29 | protobuf = { version = "3.5.1", features = ["bytes"] } 30 | 31 | url = "2.5.0" 32 | tungstenite = "0.16.0" 33 | futures-util = "0.3.30" 34 | 35 | [build-dependencies] 36 | protobuf-codegen = "3.5.1" 37 | protoc-bin-vendored = "3.0.0" 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/http/http_data.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use url::Url; 4 | 5 | pub struct LiveUserDataRequest { 6 | pub user_name: String, 7 | } 8 | 9 | pub struct LiveUserDataResponse { 10 | pub room_id: String, 11 | pub started_at_timestamp: i64, 12 | pub user_status: UserStatus, 13 | pub json: String, 14 | } 15 | 16 | pub struct LiveDataRequest { 17 | pub room_id: String, 18 | } 19 | 20 | #[derive(Debug, PartialEq)] 21 | pub struct LiveDataResponse { 22 | pub json: String, 23 | pub live_status: LiveStatus, 24 | pub title: String, 25 | pub likes: i64, 26 | pub viewers: i64, 27 | pub total_viewers: i64, 28 | } 29 | 30 | pub struct LiveConnectionDataRequest { 31 | pub room_id: String, 32 | } 33 | 34 | pub struct LiveConnectionDataResponse { 35 | pub web_socket_timeout: Duration, 36 | pub web_socket_cookies: String, 37 | pub web_socket_url: Url, 38 | } 39 | 40 | pub struct SignServerResponse { 41 | pub signed_url: String, 42 | pub user_agent: String, 43 | } 44 | 45 | pub enum UserStatus { 46 | NotFound, 47 | Offline, 48 | LivePaused, 49 | Live, 50 | } 51 | 52 | #[derive(Debug, PartialEq)] 53 | pub enum LiveStatus { 54 | HostNotFound, 55 | HostOnline, 56 | HostOffline, 57 | } 58 | -------------------------------------------------------------------------------- /.idea/TikTokLiveRust.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | tokens 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | debug/ 6 | target/ 7 | 8 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 9 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 10 | Cargo.lock 11 | 12 | # These are backup files generated by rustfmt 13 | **/*.rs.bk 14 | 15 | # MSVC Windows builds of rustc generate these, which store debugging information 16 | *.pdb 17 | 18 | # This file should only ignore things that are generated during a `x.py` build, 19 | # generated by common IDEs, and optional files controlled by the user that 20 | # affect the build (such as config.toml). 21 | # In particular, things like `mir_dump` should not be listed here; they are only 22 | # created during manual debugging and many people like to clean up instead of 23 | # having git ignore such leftovers. You can use `.git/info/exclude` to 24 | # configure your local ignore list. 25 | 26 | ## File system 27 | .DS_Store 28 | desktop.ini 29 | 30 | ## Editor 31 | *.swp 32 | *.swo 33 | Session.vim 34 | .cproject 35 | .idea 36 | *.iml 37 | .vscode 38 | .project 39 | .favorites.json 40 | .settings/ 41 | .vs/ 42 | 43 | ## Tool 44 | .valgrindrc 45 | .cargo 46 | # Included because it is part of the test case 47 | !/tests/run-make/thumb-none-qemu/example/.cargo 48 | 49 | ## Configuration 50 | /config.toml 51 | /Makefile 52 | config.mk 53 | config.stamp 54 | no_llvm_build 55 | 56 | ## Build 57 | /dl/ 58 | /doc/ 59 | /inst/ 60 | /llvm/ 61 | /mingw-build/ 62 | build/ 63 | !/compiler/rustc_mir_build/src/build/ 64 | /build-rust-analyzer/ 65 | /dist/ 66 | /unicode-downloads 67 | /target 68 | /src/bootstrap/target 69 | /src/tools/x/target 70 | # Created by default with `src/ci/docker/run.sh` 71 | /obj/ 72 | 73 | ## Temporary files 74 | *~ 75 | \#* 76 | \#*\# 77 | .#* 78 | 79 | ## Tags 80 | tags 81 | tags.* 82 | TAGS 83 | TAGS.* 84 | 85 | ## Python 86 | __pycache__/ 87 | *.py[cod] 88 | *$py.class 89 | 90 | ## Node 91 | node_modules 92 | package-lock.json 93 | package.json 94 | 95 | ## Rustdoc GUI tests 96 | tests/rustdoc-gui/src/**.lock 97 | 98 | # Before adding new lines, see the comment at the top. -------------------------------------------------------------------------------- /src/core/live_client_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::core::live_client::TikTokLiveClient; 2 | use crate::core::live_client_events::{TikTokEventHandler, TikTokLiveEventObserver}; 3 | use crate::core::live_client_http::TikTokLiveHttpClient; 4 | use crate::core::live_client_mapper::TikTokLiveMessageMapper; 5 | use crate::core::live_client_websocket::TikTokLiveWebsocketClient; 6 | use crate::data::create_default_settings; 7 | use crate::data::live_common::{TikTokLiveInfo, TikTokLiveSettings}; 8 | use crate::http::http_request_builder::HttpRequestFactory; 9 | 10 | pub struct TikTokLiveBuilder { 11 | settings: TikTokLiveSettings, 12 | pub(crate) event_observer: TikTokLiveEventObserver, 13 | } 14 | 15 | impl TikTokLiveBuilder { 16 | /// 17 | /// # Create new tiktok live builder 18 | /// 19 | /// ### user_name - name of tiktok user that can be found in the live link 20 | /// 21 | pub fn new(user_name: &str) -> Self { 22 | Self { 23 | settings: create_default_settings(user_name), 24 | event_observer: TikTokLiveEventObserver::new(), 25 | } 26 | } 27 | 28 | /// 29 | /// # Configure live connection settings 30 | /// 31 | /// 32 | pub fn configure(&mut self, on_configure: F) -> &mut Self 33 | where 34 | F: FnOnce(&mut TikTokLiveSettings), 35 | { 36 | on_configure(&mut self.settings); 37 | self 38 | } 39 | 40 | /// 41 | /// # Invoked every time new event is coming from tiktok 42 | /// 43 | /// ## client - instance of TikTokLiveClient 44 | /// ## event - invoked event 45 | /// ``` 46 | /// 47 | pub fn on_event(&mut self, on_event: TikTokEventHandler) -> &mut Self { 48 | self.event_observer.subscribe(on_event); 49 | self 50 | } 51 | 52 | /// 53 | /// Returns new instance of TikTokLiveClient 54 | /// 55 | pub fn build(&self) -> TikTokLiveClient { 56 | let settings = &self.settings; 57 | let observer = self.event_observer.clone(); 58 | let mapper = TikTokLiveMessageMapper {}; 59 | let websocket_client = TikTokLiveWebsocketClient::new(mapper); 60 | let http_factory = HttpRequestFactory { 61 | settings: settings.clone(), 62 | }; 63 | let http_client = TikTokLiveHttpClient { 64 | settings: settings.clone(), 65 | factory: http_factory, 66 | }; 67 | 68 | TikTokLiveClient::new( 69 | settings.clone(), 70 | http_client, 71 | observer, 72 | websocket_client, 73 | TikTokLiveInfo::default(), 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::time::Duration; 3 | 4 | use crate::data::live_common::{HttpData, TikTokLiveSettings}; 5 | 6 | pub mod live_common; 7 | 8 | pub fn create_default_settings(host_name: &str) -> TikTokLiveSettings { 9 | TikTokLiveSettings { 10 | language: "en-US".to_string(), 11 | sign_api_key: "".to_string(), 12 | print_logs: true, 13 | reconnect_on_fail: true, 14 | host_name: host_name.to_string(), 15 | http_data: HttpData { 16 | time_out: Duration::from_secs(3), 17 | cookies: create_default_cookies(), 18 | headers: create_default_headers(), 19 | params: create_default_params(), 20 | }, 21 | } 22 | } 23 | 24 | fn create_default_params() -> HashMap { 25 | let mut params: Vec<(&str, &str)> = Vec::new(); 26 | params.push(("aid", "1988")); 27 | params.push(("app_language", "en-US")); 28 | params.push(("app_name", "tiktok_web")); 29 | params.push(("browser_language", "en")); 30 | params.push(("browser_name", "Mozilla")); 31 | params.push(("browser_online", "true")); 32 | params.push(("browser_platform", "Win32")); 33 | params.push(("browser_version", "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36")); 34 | params.push(("cookie_enabled", "true")); 35 | params.push(("cursor", "")); 36 | params.push(("internal_ext", "")); 37 | params.push(("device_platform", "web")); 38 | params.push(("focus_state", "true")); 39 | params.push(("from_page", "user")); 40 | params.push(("history_len", "4")); 41 | params.push(("is_fullscreen", "false")); 42 | params.push(("is_page_visible", "true")); 43 | params.push(("did_rule", "3")); 44 | params.push(("fetch_rule", "1")); 45 | params.push(("identity", "audience")); 46 | params.push(("last_rtt", "0")); 47 | params.push(("live_id", "12")); 48 | params.push(("resp_content_type", "protobuf")); 49 | params.push(("screen_height", "1152")); 50 | params.push(("screen_width", "2048")); 51 | params.push(("tz_name", "Europe/Berlin")); 52 | params.push(("referer", "https, //www.core.com/")); 53 | params.push(("root_referer", "https, //www.core.com/")); 54 | params.push(("msToken", "")); 55 | params.push(("version_code", "180800")); 56 | params.push(("webcast_sdk_version", "1.3.0")); 57 | params.push(("update_version_code", "1.3.0")); 58 | 59 | params 60 | .iter() 61 | .map(|(key, value)| (key.to_string(), value.to_string())) 62 | .collect() 63 | } 64 | 65 | fn create_default_headers() -> HashMap { 66 | let mut headers: Vec<(&str, &str)> = Vec::new(); 67 | 68 | headers.push(("authority", "www.core.com")); 69 | 70 | headers.push(("Cache-Control", "max-age=0")); 71 | headers.push(("Accept", "text/html,application/json,application/protobuf")); 72 | headers.push(("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36")); 73 | headers.push(("Referer", "https://www.tiktok.com/")); 74 | headers.push(("Origin", "https://www.tiktok.com")); 75 | headers.push(("Accept-Language", "en-US,en; q=0.9")); 76 | 77 | headers 78 | .iter() 79 | .map(|(key, value)| (key.to_string(), value.to_string())) 80 | .collect() 81 | } 82 | 83 | fn create_default_cookies() -> HashMap { 84 | HashMap::new() 85 | } 86 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug)] 4 | pub enum LibError { 5 | RoomIDFieldMissing, 6 | UserFieldMissing, 7 | UserDataFieldMissing, 8 | LiveDataFieldMissing, 9 | JsonParseError, 10 | UserMessageFieldMissing, 11 | ParamsError, 12 | UserStatusFieldMissing, 13 | LiveStatusFieldMissing, 14 | TitleFieldMissing, 15 | UserCountFieldMissing, 16 | StatsFieldMissing, 17 | LikeCountFieldMissing, 18 | TotalUserFieldMissing, 19 | LiveRoomFieldMissing, 20 | StartTimeFieldMissing, 21 | UserNotFound, 22 | HostNotOnline, 23 | InvalidHost, 24 | WebSocketConnectFailed, 25 | PushFrameParseError, 26 | WebcastResponseParseError, 27 | AckPacketSendError, 28 | HttpRequestFailed, 29 | UrlSigningFailed, 30 | HeaderNotReceived, 31 | BytesParseError, 32 | } 33 | 34 | impl fmt::Display for LibError { 35 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 36 | match *self { 37 | LibError::RoomIDFieldMissing => { 38 | write!(f, "Room ID field is missing, contact developer") 39 | } 40 | LibError::UserFieldMissing => write!(f, "User field is missing"), 41 | LibError::UserDataFieldMissing => write!(f, "User data field is missing"), 42 | LibError::LiveDataFieldMissing => write!(f, "Live data field is missing"), 43 | LibError::JsonParseError => write!(f, "Error parsing JSON"), 44 | LibError::UserMessageFieldMissing => write!(f, "User message field is missing"), 45 | LibError::ParamsError => write!(f, "Params error"), 46 | LibError::UserStatusFieldMissing => write!(f, "User status field is missing"), 47 | LibError::LiveStatusFieldMissing => write!(f, "Live status field is missing"), 48 | LibError::TitleFieldMissing => write!(f, "Title field is missing"), 49 | LibError::UserCountFieldMissing => write!(f, "User count field is missing"), 50 | LibError::StatsFieldMissing => write!(f, "Stats field is missing"), 51 | LibError::LikeCountFieldMissing => write!(f, "Like count is missing"), 52 | LibError::TotalUserFieldMissing => write!(f, "Total user field is missing"), 53 | LibError::LiveRoomFieldMissing => write!(f, "Live room field is missing"), 54 | LibError::StartTimeFieldMissing => write!(f, "Start time field is missing"), 55 | LibError::UserNotFound => write!(f, "User not found"), 56 | LibError::HostNotOnline => write!( 57 | f, 58 | "Live stream for host is not online!, current status HostOffline" 59 | ), 60 | LibError::InvalidHost => write!(f, "Invalid host in WebSocket URL"), 61 | LibError::WebSocketConnectFailed => write!(f, "Failed to connect to WebSocket"), 62 | LibError::PushFrameParseError => write!(f, "Unable to read push frame"), 63 | LibError::WebcastResponseParseError => write!(f, "Unable to read webcast response"), 64 | LibError::AckPacketSendError => write!(f, "Unable to send ack packet"), 65 | LibError::HttpRequestFailed => write!(f, "HTTP request failed"), 66 | LibError::UrlSigningFailed => write!(f, "URL signing failed"), 67 | LibError::HeaderNotReceived => write!(f, "Header was not received"), 68 | LibError::BytesParseError => write!(f, "Unable to parse bytes to Push Frame!"), 69 | } 70 | } 71 | } 72 | 73 | impl std::error::Error for LibError {} 74 | -------------------------------------------------------------------------------- /src/http/http_request_builder.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::time::Duration; 3 | 4 | use bytes::Bytes; 5 | use reqwest::{Client, RequestBuilder}; 6 | use urlencoding::encode; 7 | 8 | use crate::data::live_common::{HttpData, TikTokLiveSettings}; 9 | 10 | pub struct HttpRequestFactory { 11 | pub(crate) settings: TikTokLiveSettings, 12 | } 13 | 14 | impl HttpRequestFactory { 15 | pub fn request(&self) -> HttpRequestBuilder { 16 | HttpRequestBuilder { 17 | url: "".to_string(), 18 | http_data: self.settings.http_data.clone(), 19 | } 20 | } 21 | } 22 | 23 | pub struct HttpRequestBuilder { 24 | url: String, 25 | http_data: HttpData, 26 | } 27 | 28 | impl HttpRequestBuilder { 29 | pub fn with_reset(&mut self) -> &mut Self { 30 | self.http_data = HttpData::default(); 31 | self 32 | } 33 | 34 | pub fn with_time_out(&mut self, time_out: Duration) -> &mut Self { 35 | self.http_data.time_out = time_out; 36 | self 37 | } 38 | pub fn with_url(&mut self, url: &str) -> &mut Self { 39 | self.url = url.to_string(); 40 | self 41 | } 42 | 43 | pub fn with_param(&mut self, name: &str, value: &str) -> &mut Self { 44 | self.http_data 45 | .params 46 | .insert(name.to_string(), value.to_string()); 47 | self 48 | } 49 | 50 | pub fn with_params(&mut self, params: &HashMap) -> &mut Self { 51 | for entry in params { 52 | self.with_param(entry.0, entry.1); 53 | } 54 | 55 | self 56 | } 57 | 58 | pub fn with_header(&mut self, name: &str, value: &str) -> &mut Self { 59 | self.http_data 60 | .headers 61 | .insert(name.to_string(), value.to_string()); 62 | self 63 | } 64 | 65 | pub fn with_cookie(&mut self, name: &str, value: &str) -> &mut Self { 66 | self.http_data 67 | .cookies 68 | .insert(name.to_string(), value.to_string()); 69 | self 70 | } 71 | 72 | pub fn build_client(&mut self) -> Client { 73 | let client = Client::builder() 74 | .timeout(self.http_data.time_out) 75 | .build() 76 | .unwrap(); 77 | 78 | client 79 | } 80 | pub fn build_get_request(&mut self) -> RequestBuilder { 81 | let client = self.build_client(); 82 | let url = self.as_url(); 83 | let mut res = client.get(url); 84 | for header in self.http_data.headers.clone() { 85 | res = res.header(header.0, header.1); 86 | } 87 | res 88 | } 89 | 90 | pub async fn as_json(&mut self) -> Option { 91 | let result = self.build_get_request().send().await.unwrap(); 92 | 93 | if result.status().is_success() { 94 | let json_res = result.text().await.unwrap(); 95 | Some(json_res) 96 | } else { 97 | None 98 | } 99 | } 100 | 101 | pub async fn as_bytes(&mut self) -> Option { 102 | let result = self.build_get_request().send().await.unwrap(); 103 | 104 | if result.status().is_success() { 105 | let bytes = result.bytes().await.unwrap(); 106 | Some(bytes) 107 | } else { 108 | None 109 | } 110 | } 111 | 112 | pub fn as_url(&mut self) -> String { 113 | if self.http_data.params.len() == 0 { 114 | return self.url.to_string(); 115 | } 116 | 117 | let query = self 118 | .http_data 119 | .params 120 | .iter() 121 | .map(|(key, value)| format!("{}={}", key, encode(value))) 122 | .collect::>() 123 | .join("&"); 124 | let url = format!("{}?{}", self.url, query); 125 | url 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/http/http_data_mappers.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::LibError; 2 | use crate::http::http_data::LiveStatus::{HostNotFound, HostOffline, HostOnline}; 3 | use crate::http::http_data::UserStatus::{Live, LivePaused, NotFound, Offline}; 4 | use crate::http::http_data::{LiveDataResponse, LiveUserDataResponse, SignServerResponse}; 5 | use serde_json::Value; 6 | 7 | pub fn map_live_user_data_response(json: String) -> Result { 8 | let json_value: Value = 9 | serde_json::from_str(json.as_str()).map_err(|_| LibError::JsonParseError)?; 10 | 11 | let message = json_value["message"] 12 | .as_str() 13 | .ok_or(LibError::UserMessageFieldMissing)?; 14 | match message { 15 | "params_error" => return Err(LibError::ParamsError), 16 | "user_not_found" => return Err(LibError::UserNotFound), 17 | _ => {} 18 | } 19 | 20 | let option_data = json_value["data"] 21 | .as_object() 22 | .ok_or(LibError::UserDataFieldMissing)?; 23 | let user = option_data["user"] 24 | .as_object() 25 | .ok_or(LibError::UserFieldMissing)?; 26 | let room_id = user["roomId"] 27 | .as_str() 28 | .ok_or(LibError::RoomIDFieldMissing)?; 29 | let status = user["status"] 30 | .as_i64() 31 | .ok_or(LibError::UserStatusFieldMissing)?; 32 | 33 | let user_status = match status { 34 | 2 => Live, 35 | 3 => LivePaused, 36 | 4 => Offline, 37 | _ => NotFound, 38 | }; 39 | 40 | let live_room = option_data["liveRoom"] 41 | .as_object() 42 | .ok_or(LibError::LiveRoomFieldMissing)?; 43 | let start_time = live_room["startTime"] 44 | .as_i64() 45 | .ok_or(LibError::StartTimeFieldMissing)?; 46 | 47 | Ok(LiveUserDataResponse { 48 | user_status, 49 | json, 50 | room_id: room_id.to_string(), 51 | started_at_timestamp: start_time, 52 | }) 53 | } 54 | 55 | pub fn map_live_data_response(json: String) -> Result { 56 | let json_value: Value = serde_json::from_str(&json).map_err(|_| LibError::JsonParseError)?; 57 | 58 | let data = json_value["data"] 59 | .as_object() 60 | .ok_or(LibError::LiveDataFieldMissing)?; 61 | 62 | let status = data 63 | .get("status") 64 | .and_then(|v| v.as_i64()) 65 | .ok_or(LibError::LiveStatusFieldMissing)?; 66 | let title = data 67 | .get("title") 68 | .and_then(|v| v.as_str()) 69 | .ok_or(LibError::TitleFieldMissing)?; 70 | let viewers = data 71 | .get("user_count") 72 | .and_then(|v| v.as_i64()) 73 | .ok_or(LibError::UserCountFieldMissing)?; 74 | let live_status = match status { 75 | 2 => HostOnline, 76 | 4 => HostOffline, 77 | _ => HostNotFound, 78 | }; 79 | 80 | let stats = data 81 | .get("stats") 82 | .and_then(|v| v.as_object()) 83 | .ok_or(LibError::StatsFieldMissing)?; 84 | let likes = stats 85 | .get("like_count") 86 | .and_then(|v| v.as_i64()) 87 | .ok_or(LibError::LikeCountFieldMissing)?; 88 | let total_viewers = stats 89 | .get("total_user") 90 | .and_then(|v| v.as_i64()) 91 | .ok_or(LibError::TotalUserFieldMissing)?; 92 | 93 | Ok(LiveDataResponse { 94 | json, 95 | live_status, 96 | total_viewers, 97 | viewers, 98 | likes, 99 | title: title.to_string(), 100 | }) 101 | } 102 | 103 | pub fn map_sign_server_response(json: String) -> SignServerResponse { 104 | let json_value: Value = serde_json::from_str(json.as_str()).unwrap(); 105 | let signed_url = json_value["signedUrl"].as_str().unwrap(); 106 | let user_agent = json_value["User-Agent"].as_str().unwrap(); 107 | 108 | SignServerResponse { 109 | signed_url: signed_url.to_string(), 110 | user_agent: user_agent.to_string(), 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/core/live_client.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicBool; 2 | use std::sync::Arc; 3 | 4 | use log::{error, info, warn}; 5 | 6 | use crate::core::live_client_events::TikTokLiveEventObserver; 7 | use crate::core::live_client_http::TikTokLiveHttpClient; 8 | use crate::core::live_client_mapper::TikTokLiveMessageMapper; 9 | use crate::core::live_client_websocket::TikTokLiveWebsocketClient; 10 | use crate::data::live_common::ConnectionState::{CONNECTING, DISCONNECTED}; 11 | use crate::data::live_common::{ConnectionState, TikTokLiveInfo, TikTokLiveSettings}; 12 | use crate::errors::LibError; 13 | use crate::generated::events::TikTokLiveEvent; 14 | use crate::http::http_data::LiveStatus::HostOnline; 15 | use crate::http::http_data::{LiveConnectionDataRequest, LiveDataRequest, LiveUserDataRequest}; 16 | 17 | pub struct TikTokLiveClient { 18 | settings: TikTokLiveSettings, 19 | http_client: TikTokLiveHttpClient, 20 | event_observer: TikTokLiveEventObserver, 21 | websocket_client: Arc, 22 | room_info: TikTokLiveInfo, 23 | } 24 | 25 | impl TikTokLiveClient { 26 | pub(crate) fn new( 27 | settings: TikTokLiveSettings, 28 | http_client: TikTokLiveHttpClient, 29 | event_observer: TikTokLiveEventObserver, 30 | websocket_client: TikTokLiveWebsocketClient, 31 | room_info: TikTokLiveInfo, 32 | ) -> Self { 33 | TikTokLiveClient { 34 | settings, 35 | http_client, 36 | event_observer, 37 | websocket_client: Arc::new(websocket_client), 38 | room_info, 39 | } 40 | } 41 | 42 | pub async fn connect(mut self) -> Result<(), LibError> { 43 | if *(self.room_info.connection_state.lock().unwrap()) != DISCONNECTED { 44 | warn!("Client already connected!"); 45 | return Ok(()); 46 | } 47 | 48 | self.set_connection_state(CONNECTING); 49 | 50 | info!("Getting live user information's"); 51 | let response = self 52 | .http_client 53 | .fetch_live_user_data(LiveUserDataRequest { 54 | user_name: self.settings.host_name.clone(), 55 | }) 56 | .await?; 57 | 58 | info!("Getting live room information's"); 59 | let room_id = response.room_id; 60 | let response = self 61 | .http_client 62 | .fetch_live_data(LiveDataRequest { 63 | room_id: room_id.clone(), 64 | }) 65 | .await?; 66 | 67 | self.room_info.client_data = response.json; 68 | if response.live_status != HostOnline { 69 | error!( 70 | "Live stream for host is not online!, current status {:?}", 71 | response.live_status 72 | ); 73 | self.set_connection_state(DISCONNECTED); 74 | return Err(LibError::HostNotOnline); 75 | } 76 | 77 | info!("Getting live connections information's"); 78 | let response = self 79 | .http_client 80 | .fetch_live_connection_data(LiveConnectionDataRequest { 81 | room_id: room_id.clone(), 82 | }) 83 | .await; 84 | 85 | let client_arc = Arc::new(self); 86 | 87 | let ws = TikTokLiveWebsocketClient { 88 | message_mapper: TikTokLiveMessageMapper {}, 89 | running: Arc::new(AtomicBool::new(false)), 90 | }; 91 | let _ = ws.start(response?, client_arc).await; 92 | 93 | Ok(()) 94 | } 95 | 96 | pub fn disconnect(&self) { 97 | self.websocket_client.stop(); 98 | self.set_connection_state(DISCONNECTED) 99 | } 100 | 101 | pub fn publish_event(&self, event: TikTokLiveEvent) { 102 | self.event_observer.publish(self, event); 103 | } 104 | 105 | pub fn get_room_info(&self) -> &String { 106 | &self.room_info.client_data 107 | } 108 | 109 | pub fn set_connection_state(&self, state: ConnectionState) { 110 | let mut data = self.room_info.connection_state.lock().unwrap(); 111 | *data = state; 112 | info!("TikTokLive: {:?}", *data); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/core/live_client_http.rs: -------------------------------------------------------------------------------- 1 | use protobuf::Message; 2 | 3 | use crate::data::live_common::TikTokLiveSettings; 4 | use crate::errors::LibError; 5 | use crate::generated::messages::webcast::WebcastResponse; 6 | use crate::http::http_data::{ 7 | LiveConnectionDataRequest, LiveConnectionDataResponse, LiveDataRequest, LiveDataResponse, 8 | LiveUserDataRequest, LiveUserDataResponse, 9 | }; 10 | use crate::http::http_data_mappers::{ 11 | map_live_data_response, map_live_user_data_response, map_sign_server_response, 12 | }; 13 | use crate::http::http_request_builder::HttpRequestFactory; 14 | 15 | pub struct TikTokLiveHttpClient { 16 | pub(crate) settings: TikTokLiveSettings, 17 | pub(crate) factory: HttpRequestFactory, 18 | } 19 | 20 | pub const TIKTOK_URL_WEB: &str = "https://www.tiktok.com/"; 21 | pub const TIKTOK_URL_WEBCAST: &str = "https://webcast.tiktok.com/webcast/"; 22 | pub const TIKTOK_SIGN_API: &str = "https://tiktok.eulerstream.com/webcast/sign_url"; 23 | 24 | impl TikTokLiveHttpClient { 25 | pub async fn fetch_live_user_data( 26 | &self, 27 | request: LiveUserDataRequest, 28 | ) -> Result { 29 | let url = format!("{}{}", TIKTOK_URL_WEB, "api-live/user/room"); 30 | let option = self 31 | .factory 32 | .request() 33 | .with_url(url.as_str()) 34 | .with_param("uniqueId", request.user_name.as_str()) 35 | .with_param("sourceType", "54") 36 | .as_json() 37 | .await; 38 | 39 | let json = option.ok_or(LibError::HttpRequestFailed)?; 40 | map_live_user_data_response(json).map_err(|e| e) 41 | } 42 | 43 | pub async fn fetch_live_data( 44 | &self, 45 | request: LiveDataRequest, 46 | ) -> Result { 47 | let url = format!("{}{}", TIKTOK_URL_WEBCAST, "room/info"); 48 | let option = self 49 | .factory 50 | .request() 51 | .with_url(url.as_str()) 52 | .with_param("room_id", request.room_id.as_str()) 53 | .as_json() 54 | .await; 55 | 56 | let json = option.ok_or(LibError::HttpRequestFailed)?; 57 | map_live_data_response(json).map_err(|e| e) 58 | } 59 | 60 | pub async fn fetch_live_connection_data( 61 | &self, 62 | request: LiveConnectionDataRequest, 63 | ) -> Result { 64 | // Preparing URL to sign 65 | let url_to_sign = self 66 | .factory 67 | .request() 68 | .with_url(format!("{}{}", TIKTOK_URL_WEBCAST, "im/fetch").as_str()) 69 | .with_param("room_id", request.room_id.as_str()) 70 | .as_url(); 71 | 72 | // Signing URL 73 | let option = self 74 | .factory 75 | .request() 76 | .with_url(TIKTOK_SIGN_API) 77 | .with_param("client", "ttlive-rust") 78 | .with_param("uuc", "1") 79 | .with_param("url", url_to_sign.as_str()) 80 | .with_param("apiKey", self.settings.sign_api_key.as_str()) 81 | .as_json() 82 | .await; 83 | 84 | let json = option.ok_or(LibError::UrlSigningFailed)?; 85 | let sign_server_response = map_sign_server_response(json); 86 | 87 | // Getting credentials for connection to websocket 88 | let response = self 89 | .factory 90 | .request() 91 | .with_reset() 92 | .with_time_out(self.settings.http_data.time_out) 93 | .with_url(sign_server_response.signed_url.as_str()) 94 | .build_get_request() 95 | .send() 96 | .await 97 | .map_err(|_| LibError::HttpRequestFailed)?; 98 | 99 | let optional_header = response.headers().get("set-cookie"); 100 | let header_value = optional_header 101 | .ok_or(LibError::HeaderNotReceived)? 102 | .to_str() 103 | .map_err(|_| LibError::HeaderNotReceived)? 104 | .to_string(); 105 | 106 | let protocol_buffer_message = response 107 | .bytes() 108 | .await 109 | .map_err(|_| LibError::BytesParseError)?; 110 | let webcast_response = WebcastResponse::parse_from_bytes(protocol_buffer_message.as_ref()) 111 | .map_err(|_| LibError::BytesParseError)?; 112 | 113 | // Preparing websocket URL 114 | let web_socket_url = self 115 | .factory 116 | .request() 117 | .with_url(webcast_response.pushServer.as_str()) 118 | .with_param("room_id", request.room_id.as_str()) 119 | .with_param("cursor", webcast_response.cursor.as_str()) 120 | .with_param("resp_content_type", "protobuf") 121 | .with_param("internal_ext", webcast_response.internalExt.as_str()) 122 | .with_params(&webcast_response.routeParamsMap) 123 | .as_url(); 124 | 125 | let url = url::Url::parse(web_socket_url.as_str()).map_err(|_| LibError::InvalidHost)?; 126 | Ok(LiveConnectionDataResponse { 127 | web_socket_timeout: self.settings.http_data.time_out, 128 | web_socket_cookies: header_value, 129 | web_socket_url: url, 130 | }) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/generated/events.rs: -------------------------------------------------------------------------------- 1 | use crate::generated::messages::webcast::webcast_response::Message; 2 | use crate::generated::messages::webcast::*; 3 | /// 4 | /// Generated file 5 | /// 6 | pub struct TikTokRankTextEvent { 7 | pub raw_data: WebcastRankTextMessage, 8 | } 9 | pub struct TikTokPollEvent { 10 | pub raw_data: WebcastPollMessage, 11 | } 12 | pub struct TikTokSubNotifyEvent { 13 | pub raw_data: WebcastSubNotifyMessage, 14 | } 15 | pub struct TikTokGoalUpdateEvent { 16 | pub raw_data: WebcastGoalUpdateMessage, 17 | } 18 | pub struct TikTokPushFrameEvent { 19 | pub raw_data: WebcastPushFrame, 20 | } 21 | pub struct TikTokEnvelopeEvent { 22 | pub raw_data: WebcastEnvelopeMessage, 23 | } 24 | pub struct TikTokSocialEvent { 25 | pub raw_data: WebcastSocialMessage, 26 | } 27 | pub struct TikTokRoomUserSeqEvent { 28 | pub raw_data: WebcastRoomUserSeqMessage, 29 | } 30 | pub struct TikTokLinkEvent { 31 | pub raw_data: WebcastLinkMessage, 32 | } 33 | pub struct TikTokDisconnectedEvent {} 34 | pub struct TikTokMemberEvent { 35 | pub raw_data: WebcastMemberMessage, 36 | } 37 | pub struct TikTokLikeEvent { 38 | pub raw_data: WebcastLikeMessage, 39 | } 40 | pub struct TikTokLinkMicBattleEvent { 41 | pub raw_data: WebcastLinkMicBattle, 42 | } 43 | pub struct TikTokCaptionEvent { 44 | pub raw_data: WebcastCaptionMessage, 45 | } 46 | pub struct TikTokRoomPinEvent { 47 | pub raw_data: WebcastRoomPinMessage, 48 | } 49 | pub struct TikTokWebsocketUnknownMessageEvent { 50 | pub message_name: String, 51 | pub raw_data: Message, 52 | } 53 | pub struct TikTokLinkmicBattleTaskEvent { 54 | pub raw_data: WebcastLinkmicBattleTaskMessage, 55 | } 56 | pub struct TikTokChatEvent { 57 | pub raw_data: WebcastChatMessage, 58 | } 59 | pub struct TikTokLinkMicArmiesEvent { 60 | pub raw_data: WebcastLinkMicArmies, 61 | } 62 | pub struct TikTokControlEvent { 63 | pub raw_data: WebcastControlMessage, 64 | } 65 | pub struct TikTokWebsocketResponseEvent { 66 | pub raw_data: WebcastResponse, 67 | } 68 | pub struct TikTokGiftEvent { 69 | pub raw_data: WebcastGiftMessage, 70 | } 71 | pub struct TikTokLinkMicBattlePunishFinishEvent { 72 | pub raw_data: WebcastLinkMicBattlePunishFinish, 73 | } 74 | pub struct TikTokHourlyRankEvent { 75 | pub raw_data: WebcastHourlyRankMessage, 76 | } 77 | pub struct TikTokLinkLayerEvent { 78 | pub raw_data: WebcastLinkLayerMessage, 79 | } 80 | pub struct TikTokEmoteChatEvent { 81 | pub raw_data: WebcastEmoteChatMessage, 82 | } 83 | pub struct TikTokRoomEvent { 84 | pub raw_data: WebcastRoomMessage, 85 | } 86 | pub struct TikTokInRoomBannerEvent { 87 | pub raw_data: WebcastInRoomBannerMessage, 88 | } 89 | pub struct TikTokConnectedEvent {} 90 | pub struct TikTokLinkMicFanTicketMethodEvent { 91 | pub raw_data: WebcastLinkMicFanTicketMethod, 92 | } 93 | pub struct TikTokUnauthorizedMemberEvent { 94 | pub raw_data: WebcastUnauthorizedMemberMessage, 95 | } 96 | pub struct TikTokRoomVerifyEvent { 97 | pub raw_data: RoomVerifyMessage, 98 | } 99 | pub struct TikTokOecLiveShoppingEvent { 100 | pub raw_data: WebcastOecLiveShoppingMessage, 101 | } 102 | pub struct TikTokLiveIntroEvent { 103 | pub raw_data: WebcastLiveIntroMessage, 104 | } 105 | pub struct TikTokImDeleteEvent { 106 | pub raw_data: WebcastImDeleteMessage, 107 | } 108 | pub struct TikTokRankUpdateEvent { 109 | pub raw_data: WebcastRankUpdateMessage, 110 | } 111 | pub struct TikTokQuestionNewEvent { 112 | pub raw_data: WebcastQuestionNewMessage, 113 | } 114 | pub struct TikTokSystemEvent { 115 | pub raw_data: WebcastSystemMessage, 116 | } 117 | pub struct TikTokResponseEvent { 118 | pub raw_data: WebcastResponse, 119 | } 120 | pub struct TikTokMsgDetectEvent { 121 | pub raw_data: WebcastMsgDetectMessage, 122 | } 123 | pub struct TikTokLinkMicMethodEvent { 124 | pub raw_data: WebcastLinkMicMethod, 125 | } 126 | pub struct TikTokBarrageEvent { 127 | pub raw_data: WebcastBarrageMessage, 128 | } 129 | pub enum TikTokLiveEvent { 130 | OnLike(TikTokLikeEvent), 131 | OnQuestionNew(TikTokQuestionNewEvent), 132 | OnLinkMicBattlePunishFinish(TikTokLinkMicBattlePunishFinishEvent), 133 | OnRankUpdate(TikTokRankUpdateEvent), 134 | OnLinkMicFanTicketMethod(TikTokLinkMicFanTicketMethodEvent), 135 | OnLiveIntro(TikTokLiveIntroEvent), 136 | OnMember(TikTokMemberEvent), 137 | OnChat(TikTokChatEvent), 138 | OnLinkMicArmies(TikTokLinkMicArmiesEvent), 139 | OnLinkLayer(TikTokLinkLayerEvent), 140 | OnWebsocketResponse(TikTokWebsocketResponseEvent), 141 | OnResponse(TikTokResponseEvent), 142 | OnPushFrame(TikTokPushFrameEvent), 143 | OnRankText(TikTokRankTextEvent), 144 | OnSystem(TikTokSystemEvent), 145 | OnLinkmicBattleTask(TikTokLinkmicBattleTaskEvent), 146 | OnGift(TikTokGiftEvent), 147 | OnInRoomBanner(TikTokInRoomBannerEvent), 148 | OnMsgDetect(TikTokMsgDetectEvent), 149 | OnControl(TikTokControlEvent), 150 | OnLinkMicMethod(TikTokLinkMicMethodEvent), 151 | OnGoalUpdate(TikTokGoalUpdateEvent), 152 | OnCaption(TikTokCaptionEvent), 153 | OnHourlyRank(TikTokHourlyRankEvent), 154 | OnDisconnected(TikTokDisconnectedEvent), 155 | OnBarrage(TikTokBarrageEvent), 156 | OnSubNotify(TikTokSubNotifyEvent), 157 | OnRoomVerify(TikTokRoomVerifyEvent), 158 | OnSocial(TikTokSocialEvent), 159 | OnEmoteChat(TikTokEmoteChatEvent), 160 | OnPoll(TikTokPollEvent), 161 | OnRoomPin(TikTokRoomPinEvent), 162 | OnRoom(TikTokRoomEvent), 163 | OnEnvelope(TikTokEnvelopeEvent), 164 | OnWebsocketUnknownMessage(TikTokWebsocketUnknownMessageEvent), 165 | OnConnected(TikTokConnectedEvent), 166 | OnImDelete(TikTokImDeleteEvent), 167 | OnRoomUserSeq(TikTokRoomUserSeqEvent), 168 | OnUnauthorizedMember(TikTokUnauthorizedMemberEvent), 169 | OnOecLiveShopping(TikTokOecLiveShoppingEvent), 170 | OnLink(TikTokLinkEvent), 171 | OnLinkMicBattle(TikTokLinkMicBattleEvent), 172 | } 173 | -------------------------------------------------------------------------------- /src/core/live_client_websocket.rs: -------------------------------------------------------------------------------- 1 | use futures_util::{SinkExt, StreamExt}; 2 | use log::info; 3 | use protobuf::Message; 4 | use std::sync::atomic::{AtomicBool, Ordering}; 5 | use std::sync::Arc; 6 | use tokio::sync::Mutex; 7 | use tokio::time::{interval, timeout, Duration}; 8 | use tokio_tungstenite::tungstenite::handshake::client::Request; 9 | use tokio_tungstenite::{connect_async, tungstenite::protocol::Message as WsMessage}; 10 | 11 | use crate::core::live_client::TikTokLiveClient; 12 | use crate::core::live_client_mapper::TikTokLiveMessageMapper; 13 | use crate::data::live_common::ConnectionState::CONNECTED; 14 | use crate::errors::LibError; 15 | use crate::generated::events::{TikTokConnectedEvent, TikTokLiveEvent}; 16 | use crate::generated::messages::webcast::{WebcastPushFrame, WebcastResponse}; 17 | use crate::http::http_data::LiveConnectionDataResponse; 18 | 19 | pub struct TikTokLiveWebsocketClient { 20 | pub(crate) message_mapper: TikTokLiveMessageMapper, 21 | pub(crate) running: Arc, 22 | } 23 | 24 | impl TikTokLiveWebsocketClient { 25 | pub fn new(message_mapper: TikTokLiveMessageMapper) -> Self { 26 | TikTokLiveWebsocketClient { 27 | message_mapper, 28 | running: Arc::new(AtomicBool::new(false)), 29 | } 30 | } 31 | 32 | pub async fn start( 33 | &self, 34 | response: LiveConnectionDataResponse, 35 | client: Arc, 36 | ) -> Result<(), LibError> { 37 | let host = response 38 | .web_socket_url 39 | .host_str() 40 | .ok_or(LibError::InvalidHost)?; 41 | 42 | let request = Request::builder() 43 | .method("GET") 44 | .uri(response.web_socket_url.to_string()) 45 | .header("Host", host) 46 | .header("Upgrade", "websocket") 47 | .header("Connection", "keep-alive") 48 | .header("Cache-Control", "max-age=0") 49 | .header("Accept", "text/html,application/json,application/protobuf") 50 | .header("Sec-Websocket-Key", "asd") 51 | .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36") 52 | .header("Referer", "https://www.tiktok.com/") 53 | .header("Origin", "https://www.tiktok.com") 54 | .header("Accept-Language", "en-US,en;q=0.9") 55 | .header("Accept-Encoding", "gzip, deflate") 56 | .header("Cookie", response.web_socket_cookies) 57 | .header("Sec-Websocket-Version", "13") 58 | .body(()) 59 | .map_err(|_| LibError::ParamsError)?; 60 | 61 | let (ws_stream, _) = connect_async(request) 62 | .await 63 | .map_err(|_| LibError::WebSocketConnectFailed)?; 64 | let (write, mut read) = ws_stream.split(); 65 | let write = Arc::new(Mutex::new(write)); 66 | 67 | client.set_connection_state(CONNECTED); 68 | client.publish_event(TikTokLiveEvent::OnConnected(TikTokConnectedEvent {})); 69 | 70 | let running = self.running.clone(); 71 | running.store(true, Ordering::SeqCst); 72 | 73 | let message_mapper = self.message_mapper.clone(); 74 | let client_clone = client.clone(); 75 | let write_clone = write.clone(); 76 | let running_clone = running.clone(); 77 | 78 | tokio::spawn(async move { 79 | info!("Websocket connected"); 80 | while running_clone.load(Ordering::SeqCst) { 81 | if let Some(Ok(message)) = read.next().await { 82 | if let WsMessage::Binary(buffer) = message { 83 | let mut push_frame = match WebcastPushFrame::parse_from_bytes(&buffer) { 84 | Ok(frame) => frame, 85 | Err(_) => continue, 86 | }; 87 | 88 | let webcast_response = match WebcastResponse::parse_from_bytes( 89 | push_frame.Payload.as_mut_slice(), 90 | ) { 91 | Ok(response) => response, 92 | Err(_) => continue, 93 | }; 94 | 95 | if webcast_response.needsAck { 96 | let mut push_frame_ack = WebcastPushFrame::new(); 97 | push_frame_ack.PayloadType = "ack".to_string(); 98 | push_frame_ack.LogId = push_frame.LogId; 99 | push_frame_ack.Payload = 100 | webcast_response.internalExt.clone().into_bytes(); 101 | 102 | let binary = match push_frame_ack.write_to_bytes() { 103 | Ok(bytes) => bytes, 104 | Err(_) => continue, 105 | }; 106 | 107 | let message = WsMessage::Binary(binary); 108 | if write_clone.lock().await.send(message).await.is_err() { 109 | continue; 110 | } 111 | } 112 | 113 | message_mapper 114 | .handle_webcast_response(webcast_response, client_clone.as_ref()); 115 | } 116 | } 117 | } 118 | }); 119 | 120 | let write_clone = write.clone(); 121 | let running_clone = running.clone(); 122 | tokio::spawn(async move { 123 | let mut interval = interval(Duration::from_secs(9)); 124 | while running_clone.load(Ordering::SeqCst) { 125 | interval.tick().await; 126 | 127 | let heartbeat_message = WsMessage::Binary(vec![0x3a, 0x02, 0x68, 0x62]); 128 | 129 | match timeout( 130 | Duration::from_secs(5), 131 | write_clone.lock().await.send(heartbeat_message), 132 | ) 133 | .await 134 | { 135 | Ok(Ok(_)) => { 136 | log::info!("Heartbeat sent"); 137 | } 138 | Ok(Err(e)) => { 139 | log::error!("Failed to send heartbeat: {:?}", e); 140 | break; 141 | } 142 | Err(e) => { 143 | log::error!("Heartbeat send timed out: {:?}", e); 144 | break; 145 | } 146 | } 147 | } 148 | log::info!("Heartbeat task stopped"); 149 | }); 150 | Ok(()) 151 | } 152 | 153 | pub fn stop(&self) { 154 | self.running.store(false, Ordering::SeqCst); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use env_logger::{Builder, Env}; // Importing the logger builder and environment configuration 2 | use log::LevelFilter; // Importing log level filter 3 | use log::{error, warn}; 4 | use std::time::Duration; // Importing Duration for timeout settings 5 | use tiktoklive::{ 6 | // Importing necessary modules and structs from tiktoklive crate 7 | core::live_client::TikTokLiveClient, 8 | data::live_common::{ClientData, StreamData, TikTokLiveSettings}, 9 | errors::LibError, 10 | generated::events::TikTokLiveEvent, 11 | TikTokLive, 12 | }; 13 | use tokio::signal; // Importing signal handling from tokio 14 | 15 | #[tokio::main] // Main function is asynchronous and uses tokio runtime 16 | async fn main() { 17 | init_logger("info"); // Initialize logger with "info" level 18 | let user_name = "tragdate"; 19 | 20 | let client = create_client(user_name); // Create a client for the given username 21 | 22 | // Spawn a new asynchronous task to connect the client 23 | let handle = tokio::spawn(async move { 24 | // Attempt to connect the client 25 | if let Err(e) = client.connect().await { 26 | match e { 27 | // Match on the error type 28 | LibError::LiveStatusFieldMissing => { 29 | // Specific error case 30 | warn!( 31 | "Failed to get live status (probably needs authenticated client): {}", 32 | e 33 | ); 34 | let auth_client = create_client_with_cookies(user_name); // Create an authenticated client 35 | if let Err(e) = auth_client.connect().await { 36 | // Attempt to connect the authenticated client 37 | error!("Error connecting to TikTok Live after retry: {}", e); 38 | } 39 | } 40 | LibError::HeaderNotReceived => { 41 | error!("Error connecting to TikTok Live: {}", e); 42 | } 43 | 44 | _ => { 45 | // General error case 46 | error!("Error connecting to TikTok Live: {}", e); 47 | } 48 | } 49 | } 50 | }); 51 | 52 | signal::ctrl_c().await.expect("Failed to listen for Ctrl+C"); // Wait for Ctrl+C signal to gracefully shut down 53 | 54 | handle.await.expect("The spawned task has panicked"); // Await the spawned task to ensure it completes 55 | } 56 | 57 | fn handle_event(client: &TikTokLiveClient, event: &TikTokLiveEvent) { 58 | match event { 59 | TikTokLiveEvent::OnConnected(..) => { 60 | // This is an EXPERIMENTAL and UNSTABLE feature 61 | // Get room info from the client 62 | let room_info = client.get_room_info(); 63 | // // Parse the room info 64 | let client_data: ClientData = serde_json::from_str(room_info).unwrap(); 65 | // // Parse the stream data 66 | let stream_data: StreamData = serde_json::from_str( 67 | &client_data 68 | .data 69 | .stream_url 70 | .live_core_sdk_data 71 | .unwrap() 72 | .pull_data 73 | .stream_data, 74 | ) 75 | .unwrap(); 76 | // Get the video URL for the low definition stream with fallback to the high definition stream in a flv format 77 | let video_url = stream_data 78 | .data 79 | .ld 80 | .map(|ld| ld.main.flv) 81 | .or_else(|| stream_data.data.sd.map(|sd| sd.main.flv)) 82 | .or_else(|| stream_data.data.origin.map(|origin| origin.main.flv)) 83 | .expect("None of the stream types set"); 84 | println!("room info: {}", video_url); 85 | } 86 | 87 | // Match on the event type 88 | TikTokLiveEvent::OnMember(join_event) => { 89 | // Handle member join event 90 | println!("user: {} joined", join_event.raw_data.user.nickname); 91 | } 92 | TikTokLiveEvent::OnChat(chat_event) => { 93 | // Handle chat event 94 | println!( 95 | "user: {} -> {}", 96 | chat_event.raw_data.user.nickname, chat_event.raw_data.content 97 | ); 98 | } 99 | TikTokLiveEvent::OnGift(gift_event) => { 100 | // Handle gift event 101 | let nick = &gift_event.raw_data.user.nickname; 102 | let gift_name = &gift_event.raw_data.gift.name; 103 | let gifts_amount = gift_event.raw_data.gift.combo; 104 | println!( 105 | "user: {} sends gift: {} x {}", 106 | nick, gift_name, gifts_amount 107 | ); 108 | } 109 | TikTokLiveEvent::OnLike(like_event) => { 110 | // Handle like event 111 | let nick = &like_event.raw_data.user.nickname; 112 | println!("user: {} likes", nick); 113 | } 114 | _ => {} // Ignore other events 115 | } 116 | } 117 | 118 | // Function to initialize the logger with a default log level 119 | fn init_logger(default_level: &str) { 120 | let env = Env::default().filter_or("LOG_LEVEL", default_level); // Set default log level from environment or use provided level 121 | Builder::from_env(env) // Build the logger from environment settings 122 | .filter_module("tiktoklive", LevelFilter::Debug) // Set log level for tiktoklive module 123 | .init(); // Initialize the logger 124 | } 125 | 126 | // Function to configure the TikTok live settings 127 | fn configure(settings: &mut TikTokLiveSettings) { 128 | settings.http_data.time_out = Duration::from_secs(12); // Set HTTP timeout to 12 seconds 129 | settings.sign_api_key = "".to_string(); // Provide your own api key here 130 | } 131 | 132 | // Function to configure the TikTok live settings with cookies for authentication 133 | fn configure_with_cookies(settings: &mut TikTokLiveSettings) { 134 | settings.http_data.time_out = Duration::from_secs(12); // Set HTTP timeout to 12 seconds 135 | settings.sign_api_key = "".to_string(); // Provide your own api key here 136 | let contents = ""; // Placeholder for cookies 137 | settings 138 | .http_data 139 | .headers 140 | .insert("Cookie".to_string(), contents.to_string()); 141 | // Insert cookies into HTTP headers 142 | } 143 | 144 | // Function to create a TikTok live client for the given username 145 | fn create_client(user_name: &str) -> TikTokLiveClient { 146 | TikTokLive::new_client(user_name) // Create a new client 147 | .configure(configure) // Configure the client 148 | .on_event(handle_event) // Set the event handler 149 | .build() // Build the client 150 | } 151 | 152 | // Function to create a TikTok live client with cookies for the given username 153 | fn create_client_with_cookies(user_name: &str) -> TikTokLiveClient { 154 | TikTokLive::new_client(user_name) // Create a new client 155 | .configure(configure_with_cookies) // Configure the client with cookies 156 | .on_event(handle_event) // Set the event handler 157 | .build() // Build the client 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |
7 |

TikTok Live Rust

8 | 9 | ❤️❤️🎁 _Connect to TikTok live in 3 lines_ 🎁❤️❤️ 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
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 issue or pull request. 277 | 278 | ## Contributors 279 | 280 | [Zmole Cristian](https://github.com/ZmoleCristian) 281 | -------------------------------------------------------------------------------- /src/generated/events_mapper.rs: -------------------------------------------------------------------------------- 1 | use crate::core::live_client::TikTokLiveClient; 2 | use crate::core::live_client_mapper::TikTokLiveMessageMapper; 3 | use crate::generated::events::*; 4 | use crate::generated::messages::webcast::*; 5 | use protobuf::Message; 6 | impl TikTokLiveMessageMapper { 7 | pub fn handle_single_message( 8 | &self, 9 | message: crate::generated::messages::webcast::webcast_response::Message, 10 | client: &TikTokLiveClient, 11 | ) { 12 | let proto_message_name = &message.method; 13 | let proto_message_content = &message.payload; 14 | match proto_message_name.as_str() { 15 | "WebcastLikeMessage" => { 16 | let raw_data = WebcastLikeMessage::parse_from_bytes(proto_message_content).unwrap(); 17 | let event = TikTokLikeEvent { raw_data }; 18 | client.publish_event(TikTokLiveEvent::OnLike(event)); 19 | } 20 | "WebcastQuestionNewMessage" => { 21 | let raw_data = 22 | WebcastQuestionNewMessage::parse_from_bytes(proto_message_content).unwrap(); 23 | let event = TikTokQuestionNewEvent { raw_data }; 24 | client.publish_event(TikTokLiveEvent::OnQuestionNew(event)); 25 | } 26 | "WebcastLinkMicBattlePunishFinish" => { 27 | let raw_data = 28 | WebcastLinkMicBattlePunishFinish::parse_from_bytes(proto_message_content) 29 | .unwrap(); 30 | let event = TikTokLinkMicBattlePunishFinishEvent { raw_data }; 31 | client.publish_event(TikTokLiveEvent::OnLinkMicBattlePunishFinish(event)); 32 | } 33 | "WebcastRankUpdateMessage" => { 34 | let raw_data = 35 | WebcastRankUpdateMessage::parse_from_bytes(proto_message_content).unwrap(); 36 | let event = TikTokRankUpdateEvent { raw_data }; 37 | client.publish_event(TikTokLiveEvent::OnRankUpdate(event)); 38 | } 39 | "WebcastLinkMicFanTicketMethod" => { 40 | let raw_data = 41 | WebcastLinkMicFanTicketMethod::parse_from_bytes(proto_message_content).unwrap(); 42 | let event = TikTokLinkMicFanTicketMethodEvent { raw_data }; 43 | client.publish_event(TikTokLiveEvent::OnLinkMicFanTicketMethod(event)); 44 | } 45 | "WebcastLiveIntroMessage" => { 46 | let raw_data = 47 | WebcastLiveIntroMessage::parse_from_bytes(proto_message_content).unwrap(); 48 | let event = TikTokLiveIntroEvent { raw_data }; 49 | client.publish_event(TikTokLiveEvent::OnLiveIntro(event)); 50 | } 51 | "WebcastMemberMessage" => { 52 | let raw_data = 53 | WebcastMemberMessage::parse_from_bytes(proto_message_content).unwrap(); 54 | let event = TikTokMemberEvent { raw_data }; 55 | client.publish_event(TikTokLiveEvent::OnMember(event)); 56 | } 57 | "WebcastChatMessage" => { 58 | let raw_data = WebcastChatMessage::parse_from_bytes(proto_message_content).unwrap(); 59 | let event = TikTokChatEvent { raw_data }; 60 | client.publish_event(TikTokLiveEvent::OnChat(event)); 61 | } 62 | "WebcastLinkMicArmies" => { 63 | let raw_data = 64 | WebcastLinkMicArmies::parse_from_bytes(proto_message_content).unwrap(); 65 | let event = TikTokLinkMicArmiesEvent { raw_data }; 66 | client.publish_event(TikTokLiveEvent::OnLinkMicArmies(event)); 67 | } 68 | "WebcastLinkLayerMessage" => { 69 | let raw_data = 70 | WebcastLinkLayerMessage::parse_from_bytes(proto_message_content).unwrap(); 71 | let event = TikTokLinkLayerEvent { raw_data }; 72 | client.publish_event(TikTokLiveEvent::OnLinkLayer(event)); 73 | } 74 | "WebcastResponse" => { 75 | let raw_data = WebcastResponse::parse_from_bytes(proto_message_content).unwrap(); 76 | let event = TikTokResponseEvent { raw_data }; 77 | client.publish_event(TikTokLiveEvent::OnResponse(event)); 78 | } 79 | "WebcastPushFrame" => { 80 | let raw_data = WebcastPushFrame::parse_from_bytes(proto_message_content).unwrap(); 81 | let event = TikTokPushFrameEvent { raw_data }; 82 | client.publish_event(TikTokLiveEvent::OnPushFrame(event)); 83 | } 84 | "WebcastRankTextMessage" => { 85 | let raw_data = 86 | WebcastRankTextMessage::parse_from_bytes(proto_message_content).unwrap(); 87 | let event = TikTokRankTextEvent { raw_data }; 88 | client.publish_event(TikTokLiveEvent::OnRankText(event)); 89 | } 90 | "WebcastSystemMessage" => { 91 | let raw_data = 92 | WebcastSystemMessage::parse_from_bytes(proto_message_content).unwrap(); 93 | let event = TikTokSystemEvent { raw_data }; 94 | client.publish_event(TikTokLiveEvent::OnSystem(event)); 95 | } 96 | "WebcastLinkmicBattleTaskMessage" => { 97 | let raw_data = 98 | WebcastLinkmicBattleTaskMessage::parse_from_bytes(proto_message_content) 99 | .unwrap(); 100 | let event = TikTokLinkmicBattleTaskEvent { raw_data }; 101 | client.publish_event(TikTokLiveEvent::OnLinkmicBattleTask(event)); 102 | } 103 | "WebcastGiftMessage" => { 104 | let raw_data = WebcastGiftMessage::parse_from_bytes(proto_message_content).unwrap(); 105 | let event = TikTokGiftEvent { raw_data }; 106 | client.publish_event(TikTokLiveEvent::OnGift(event)); 107 | } 108 | "WebcastInRoomBannerMessage" => { 109 | let raw_data = 110 | WebcastInRoomBannerMessage::parse_from_bytes(proto_message_content).unwrap(); 111 | let event = TikTokInRoomBannerEvent { raw_data }; 112 | client.publish_event(TikTokLiveEvent::OnInRoomBanner(event)); 113 | } 114 | "WebcastMsgDetectMessage" => { 115 | let raw_data = 116 | WebcastMsgDetectMessage::parse_from_bytes(proto_message_content).unwrap(); 117 | let event = TikTokMsgDetectEvent { raw_data }; 118 | client.publish_event(TikTokLiveEvent::OnMsgDetect(event)); 119 | } 120 | "WebcastControlMessage" => { 121 | let raw_data = 122 | WebcastControlMessage::parse_from_bytes(proto_message_content).unwrap(); 123 | let event = TikTokControlEvent { raw_data }; 124 | client.publish_event(TikTokLiveEvent::OnControl(event)); 125 | } 126 | "WebcastLinkMicMethod" => { 127 | let raw_data = 128 | WebcastLinkMicMethod::parse_from_bytes(proto_message_content).unwrap(); 129 | let event = TikTokLinkMicMethodEvent { raw_data }; 130 | client.publish_event(TikTokLiveEvent::OnLinkMicMethod(event)); 131 | } 132 | "WebcastGoalUpdateMessage" => { 133 | let raw_data = 134 | WebcastGoalUpdateMessage::parse_from_bytes(proto_message_content).unwrap(); 135 | let event = TikTokGoalUpdateEvent { raw_data }; 136 | client.publish_event(TikTokLiveEvent::OnGoalUpdate(event)); 137 | } 138 | "WebcastCaptionMessage" => { 139 | let raw_data = 140 | WebcastCaptionMessage::parse_from_bytes(proto_message_content).unwrap(); 141 | let event = TikTokCaptionEvent { raw_data }; 142 | client.publish_event(TikTokLiveEvent::OnCaption(event)); 143 | } 144 | "WebcastHourlyRankMessage" => { 145 | let raw_data = 146 | WebcastHourlyRankMessage::parse_from_bytes(proto_message_content).unwrap(); 147 | let event = TikTokHourlyRankEvent { raw_data }; 148 | client.publish_event(TikTokLiveEvent::OnHourlyRank(event)); 149 | } 150 | "WebcastBarrageMessage" => { 151 | let raw_data = 152 | WebcastBarrageMessage::parse_from_bytes(proto_message_content).unwrap(); 153 | let event = TikTokBarrageEvent { raw_data }; 154 | client.publish_event(TikTokLiveEvent::OnBarrage(event)); 155 | } 156 | "WebcastSubNotifyMessage" => { 157 | let raw_data = 158 | WebcastSubNotifyMessage::parse_from_bytes(proto_message_content).unwrap(); 159 | let event = TikTokSubNotifyEvent { raw_data }; 160 | client.publish_event(TikTokLiveEvent::OnSubNotify(event)); 161 | } 162 | "RoomVerifyMessage" => { 163 | let raw_data = RoomVerifyMessage::parse_from_bytes(proto_message_content).unwrap(); 164 | let event = TikTokRoomVerifyEvent { raw_data }; 165 | client.publish_event(TikTokLiveEvent::OnRoomVerify(event)); 166 | } 167 | "WebcastSocialMessage" => { 168 | let raw_data = 169 | WebcastSocialMessage::parse_from_bytes(proto_message_content).unwrap(); 170 | let event = TikTokSocialEvent { raw_data }; 171 | client.publish_event(TikTokLiveEvent::OnSocial(event)); 172 | } 173 | "WebcastEmoteChatMessage" => { 174 | let raw_data = 175 | WebcastEmoteChatMessage::parse_from_bytes(proto_message_content).unwrap(); 176 | let event = TikTokEmoteChatEvent { raw_data }; 177 | client.publish_event(TikTokLiveEvent::OnEmoteChat(event)); 178 | } 179 | "WebcastPollMessage" => { 180 | let raw_data = WebcastPollMessage::parse_from_bytes(proto_message_content).unwrap(); 181 | let event = TikTokPollEvent { raw_data }; 182 | client.publish_event(TikTokLiveEvent::OnPoll(event)); 183 | } 184 | "WebcastRoomPinMessage" => { 185 | let raw_data = 186 | WebcastRoomPinMessage::parse_from_bytes(proto_message_content).unwrap(); 187 | let event = TikTokRoomPinEvent { raw_data }; 188 | client.publish_event(TikTokLiveEvent::OnRoomPin(event)); 189 | } 190 | "WebcastRoomMessage" => { 191 | let raw_data = WebcastRoomMessage::parse_from_bytes(proto_message_content).unwrap(); 192 | let event = TikTokRoomEvent { raw_data }; 193 | client.publish_event(TikTokLiveEvent::OnRoom(event)); 194 | } 195 | "WebcastEnvelopeMessage" => { 196 | let raw_data = 197 | WebcastEnvelopeMessage::parse_from_bytes(proto_message_content).unwrap(); 198 | let event = TikTokEnvelopeEvent { raw_data }; 199 | client.publish_event(TikTokLiveEvent::OnEnvelope(event)); 200 | } 201 | "WebcastImDeleteMessage" => { 202 | let raw_data = 203 | WebcastImDeleteMessage::parse_from_bytes(proto_message_content).unwrap(); 204 | let event = TikTokImDeleteEvent { raw_data }; 205 | client.publish_event(TikTokLiveEvent::OnImDelete(event)); 206 | } 207 | "WebcastRoomUserSeqMessage" => { 208 | let raw_data = 209 | WebcastRoomUserSeqMessage::parse_from_bytes(proto_message_content).unwrap(); 210 | let event = TikTokRoomUserSeqEvent { raw_data }; 211 | client.publish_event(TikTokLiveEvent::OnRoomUserSeq(event)); 212 | } 213 | "WebcastUnauthorizedMemberMessage" => { 214 | let raw_data = 215 | WebcastUnauthorizedMemberMessage::parse_from_bytes(proto_message_content) 216 | .unwrap(); 217 | let event = TikTokUnauthorizedMemberEvent { raw_data }; 218 | client.publish_event(TikTokLiveEvent::OnUnauthorizedMember(event)); 219 | } 220 | "WebcastOecLiveShoppingMessage" => { 221 | let raw_data = 222 | WebcastOecLiveShoppingMessage::parse_from_bytes(proto_message_content).unwrap(); 223 | let event = TikTokOecLiveShoppingEvent { raw_data }; 224 | client.publish_event(TikTokLiveEvent::OnOecLiveShopping(event)); 225 | } 226 | "WebcastLinkMessage" => { 227 | let raw_data = WebcastLinkMessage::parse_from_bytes(proto_message_content).unwrap(); 228 | let event = TikTokLinkEvent { raw_data }; 229 | client.publish_event(TikTokLiveEvent::OnLink(event)); 230 | } 231 | "WebcastLinkMicBattle" => { 232 | let raw_data = 233 | WebcastLinkMicBattle::parse_from_bytes(proto_message_content).unwrap(); 234 | let event = TikTokLinkMicBattleEvent { raw_data }; 235 | client.publish_event(TikTokLiveEvent::OnLinkMicBattle(event)); 236 | } 237 | _ => { 238 | client.publish_event(TikTokLiveEvent::OnWebsocketUnknownMessage( 239 | TikTokWebsocketUnknownMessageEvent { 240 | message_name: message.method.clone(), 241 | raw_data: message, 242 | }, 243 | )); 244 | } 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/generated/events_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::core::live_client::TikTokLiveClient; 2 | use crate::core::live_client_builder::TikTokLiveBuilder; 3 | use crate::generated::events::*; 4 | /// 5 | /// Generated code 6 | /// 7 | /// 8 | impl TikTokLiveBuilder { 9 | pub fn on_like( 10 | &mut self, 11 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLikeEvent), 12 | ) -> &mut Self { 13 | self.on_event(|client, incoming_event| match incoming_event { 14 | TikTokLiveEvent::OnLike(event_instance) => { 15 | on_event(client, event_instance); 16 | } 17 | _ => {} 18 | }) 19 | } 20 | pub fn on_question_new( 21 | &mut self, 22 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokQuestionNewEvent), 23 | ) -> &mut Self { 24 | self.on_event(|client, incoming_event| match incoming_event { 25 | TikTokLiveEvent::OnQuestionNew(event_instance) => { 26 | on_event(client, event_instance); 27 | } 28 | _ => {} 29 | }) 30 | } 31 | pub fn on_link_mic_battle_punish_finish( 32 | &mut self, 33 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLinkMicBattlePunishFinishEvent), 34 | ) -> &mut Self { 35 | self.on_event(|client, incoming_event| match incoming_event { 36 | TikTokLiveEvent::OnLinkMicBattlePunishFinish(event_instance) => { 37 | on_event(client, event_instance); 38 | } 39 | _ => {} 40 | }) 41 | } 42 | pub fn on_rank_update( 43 | &mut self, 44 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokRankUpdateEvent), 45 | ) -> &mut Self { 46 | self.on_event(|client, incoming_event| match incoming_event { 47 | TikTokLiveEvent::OnRankUpdate(event_instance) => { 48 | on_event(client, event_instance); 49 | } 50 | _ => {} 51 | }) 52 | } 53 | pub fn on_link_mic_fan_ticket_method( 54 | &mut self, 55 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLinkMicFanTicketMethodEvent), 56 | ) -> &mut Self { 57 | self.on_event(|client, incoming_event| match incoming_event { 58 | TikTokLiveEvent::OnLinkMicFanTicketMethod(event_instance) => { 59 | on_event(client, event_instance); 60 | } 61 | _ => {} 62 | }) 63 | } 64 | pub fn on_live_intro( 65 | &mut self, 66 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLiveIntroEvent), 67 | ) -> &mut Self { 68 | self.on_event(|client, incoming_event| match incoming_event { 69 | TikTokLiveEvent::OnLiveIntro(event_instance) => { 70 | on_event(client, event_instance); 71 | } 72 | _ => {} 73 | }) 74 | } 75 | pub fn on_member( 76 | &mut self, 77 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokMemberEvent), 78 | ) -> &mut Self { 79 | self.on_event(|client, incoming_event| match incoming_event { 80 | TikTokLiveEvent::OnMember(event_instance) => { 81 | on_event(client, event_instance); 82 | } 83 | _ => {} 84 | }) 85 | } 86 | pub fn on_chat( 87 | &mut self, 88 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokChatEvent), 89 | ) -> &mut Self { 90 | self.on_event(|client, incoming_event| match incoming_event { 91 | TikTokLiveEvent::OnChat(event_instance) => { 92 | on_event(client, event_instance); 93 | } 94 | _ => {} 95 | }) 96 | } 97 | pub fn on_link_mic_armies( 98 | &mut self, 99 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLinkMicArmiesEvent), 100 | ) -> &mut Self { 101 | self.on_event(|client, incoming_event| match incoming_event { 102 | TikTokLiveEvent::OnLinkMicArmies(event_instance) => { 103 | on_event(client, event_instance); 104 | } 105 | _ => {} 106 | }) 107 | } 108 | pub fn on_link_layer( 109 | &mut self, 110 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLinkLayerEvent), 111 | ) -> &mut Self { 112 | self.on_event(|client, incoming_event| match incoming_event { 113 | TikTokLiveEvent::OnLinkLayer(event_instance) => { 114 | on_event(client, event_instance); 115 | } 116 | _ => {} 117 | }) 118 | } 119 | pub fn on_websocket_response( 120 | &mut self, 121 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokWebsocketResponseEvent), 122 | ) -> &mut Self { 123 | self.on_event(|client, incoming_event| match incoming_event { 124 | TikTokLiveEvent::OnWebsocketResponse(event_instance) => { 125 | on_event(client, event_instance); 126 | } 127 | _ => {} 128 | }) 129 | } 130 | pub fn on_response( 131 | &mut self, 132 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokResponseEvent), 133 | ) -> &mut Self { 134 | self.on_event(|client, incoming_event| match incoming_event { 135 | TikTokLiveEvent::OnResponse(event_instance) => { 136 | on_event(client, event_instance); 137 | } 138 | _ => {} 139 | }) 140 | } 141 | pub fn on_push_frame( 142 | &mut self, 143 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokPushFrameEvent), 144 | ) -> &mut Self { 145 | self.on_event(|client, incoming_event| match incoming_event { 146 | TikTokLiveEvent::OnPushFrame(event_instance) => { 147 | on_event(client, event_instance); 148 | } 149 | _ => {} 150 | }) 151 | } 152 | pub fn on_rank_text( 153 | &mut self, 154 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokRankTextEvent), 155 | ) -> &mut Self { 156 | self.on_event(|client, incoming_event| match incoming_event { 157 | TikTokLiveEvent::OnRankText(event_instance) => { 158 | on_event(client, event_instance); 159 | } 160 | _ => {} 161 | }) 162 | } 163 | pub fn on_system( 164 | &mut self, 165 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokSystemEvent), 166 | ) -> &mut Self { 167 | self.on_event(|client, incoming_event| match incoming_event { 168 | TikTokLiveEvent::OnSystem(event_instance) => { 169 | on_event(client, event_instance); 170 | } 171 | _ => {} 172 | }) 173 | } 174 | pub fn on_linkmic_battle_task( 175 | &mut self, 176 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLinkmicBattleTaskEvent), 177 | ) -> &mut Self { 178 | self.on_event(|client, incoming_event| match incoming_event { 179 | TikTokLiveEvent::OnLinkmicBattleTask(event_instance) => { 180 | on_event(client, event_instance); 181 | } 182 | _ => {} 183 | }) 184 | } 185 | pub fn on_gift( 186 | &mut self, 187 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokGiftEvent), 188 | ) -> &mut Self { 189 | self.on_event(|client, incoming_event| match incoming_event { 190 | TikTokLiveEvent::OnGift(event_instance) => { 191 | on_event(client, event_instance); 192 | } 193 | _ => {} 194 | }) 195 | } 196 | pub fn on_in_room_banner( 197 | &mut self, 198 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokInRoomBannerEvent), 199 | ) -> &mut Self { 200 | self.on_event(|client, incoming_event| match incoming_event { 201 | TikTokLiveEvent::OnInRoomBanner(event_instance) => { 202 | on_event(client, event_instance); 203 | } 204 | _ => {} 205 | }) 206 | } 207 | pub fn on_msg_detect( 208 | &mut self, 209 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokMsgDetectEvent), 210 | ) -> &mut Self { 211 | self.on_event(|client, incoming_event| match incoming_event { 212 | TikTokLiveEvent::OnMsgDetect(event_instance) => { 213 | on_event(client, event_instance); 214 | } 215 | _ => {} 216 | }) 217 | } 218 | pub fn on_control( 219 | &mut self, 220 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokControlEvent), 221 | ) -> &mut Self { 222 | self.on_event(|client, incoming_event| match incoming_event { 223 | TikTokLiveEvent::OnControl(event_instance) => { 224 | on_event(client, event_instance); 225 | } 226 | _ => {} 227 | }) 228 | } 229 | pub fn on_link_mic_method( 230 | &mut self, 231 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLinkMicMethodEvent), 232 | ) -> &mut Self { 233 | self.on_event(|client, incoming_event| match incoming_event { 234 | TikTokLiveEvent::OnLinkMicMethod(event_instance) => { 235 | on_event(client, event_instance); 236 | } 237 | _ => {} 238 | }) 239 | } 240 | pub fn on_goal_update( 241 | &mut self, 242 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokGoalUpdateEvent), 243 | ) -> &mut Self { 244 | self.on_event(|client, incoming_event| match incoming_event { 245 | TikTokLiveEvent::OnGoalUpdate(event_instance) => { 246 | on_event(client, event_instance); 247 | } 248 | _ => {} 249 | }) 250 | } 251 | pub fn on_caption( 252 | &mut self, 253 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokCaptionEvent), 254 | ) -> &mut Self { 255 | self.on_event(|client, incoming_event| match incoming_event { 256 | TikTokLiveEvent::OnCaption(event_instance) => { 257 | on_event(client, event_instance); 258 | } 259 | _ => {} 260 | }) 261 | } 262 | pub fn on_hourly_rank( 263 | &mut self, 264 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokHourlyRankEvent), 265 | ) -> &mut Self { 266 | self.on_event(|client, incoming_event| match incoming_event { 267 | TikTokLiveEvent::OnHourlyRank(event_instance) => { 268 | on_event(client, event_instance); 269 | } 270 | _ => {} 271 | }) 272 | } 273 | pub fn on_disconnected( 274 | &mut self, 275 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokDisconnectedEvent), 276 | ) -> &mut Self { 277 | self.on_event(|client, incoming_event| match incoming_event { 278 | TikTokLiveEvent::OnDisconnected(event_instance) => { 279 | on_event(client, event_instance); 280 | } 281 | _ => {} 282 | }) 283 | } 284 | pub fn on_barrage( 285 | &mut self, 286 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokBarrageEvent), 287 | ) -> &mut Self { 288 | self.on_event(|client, incoming_event| match incoming_event { 289 | TikTokLiveEvent::OnBarrage(event_instance) => { 290 | on_event(client, event_instance); 291 | } 292 | _ => {} 293 | }) 294 | } 295 | pub fn on_sub_notify( 296 | &mut self, 297 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokSubNotifyEvent), 298 | ) -> &mut Self { 299 | self.on_event(|client, incoming_event| match incoming_event { 300 | TikTokLiveEvent::OnSubNotify(event_instance) => { 301 | on_event(client, event_instance); 302 | } 303 | _ => {} 304 | }) 305 | } 306 | pub fn on_room_verify( 307 | &mut self, 308 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokRoomVerifyEvent), 309 | ) -> &mut Self { 310 | self.on_event(|client, incoming_event| match incoming_event { 311 | TikTokLiveEvent::OnRoomVerify(event_instance) => { 312 | on_event(client, event_instance); 313 | } 314 | _ => {} 315 | }) 316 | } 317 | pub fn on_social( 318 | &mut self, 319 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokSocialEvent), 320 | ) -> &mut Self { 321 | self.on_event(|client, incoming_event| match incoming_event { 322 | TikTokLiveEvent::OnSocial(event_instance) => { 323 | on_event(client, event_instance); 324 | } 325 | _ => {} 326 | }) 327 | } 328 | pub fn on_emote_chat( 329 | &mut self, 330 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokEmoteChatEvent), 331 | ) -> &mut Self { 332 | self.on_event(|client, incoming_event| match incoming_event { 333 | TikTokLiveEvent::OnEmoteChat(event_instance) => { 334 | on_event(client, event_instance); 335 | } 336 | _ => {} 337 | }) 338 | } 339 | pub fn on_poll( 340 | &mut self, 341 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokPollEvent), 342 | ) -> &mut Self { 343 | self.on_event(|client, incoming_event| match incoming_event { 344 | TikTokLiveEvent::OnPoll(event_instance) => { 345 | on_event(client, event_instance); 346 | } 347 | _ => {} 348 | }) 349 | } 350 | pub fn on_room_pin( 351 | &mut self, 352 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokRoomPinEvent), 353 | ) -> &mut Self { 354 | self.on_event(|client, incoming_event| match incoming_event { 355 | TikTokLiveEvent::OnRoomPin(event_instance) => { 356 | on_event(client, event_instance); 357 | } 358 | _ => {} 359 | }) 360 | } 361 | pub fn on_room( 362 | &mut self, 363 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokRoomEvent), 364 | ) -> &mut Self { 365 | self.on_event(|client, incoming_event| match incoming_event { 366 | TikTokLiveEvent::OnRoom(event_instance) => { 367 | on_event(client, event_instance); 368 | } 369 | _ => {} 370 | }) 371 | } 372 | pub fn on_envelope( 373 | &mut self, 374 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokEnvelopeEvent), 375 | ) -> &mut Self { 376 | self.on_event(|client, incoming_event| match incoming_event { 377 | TikTokLiveEvent::OnEnvelope(event_instance) => { 378 | on_event(client, event_instance); 379 | } 380 | _ => {} 381 | }) 382 | } 383 | pub fn on_websocket_unknown_message( 384 | &mut self, 385 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokWebsocketUnknownMessageEvent), 386 | ) -> &mut Self { 387 | self.on_event(|client, incoming_event| match incoming_event { 388 | TikTokLiveEvent::OnWebsocketUnknownMessage(event_instance) => { 389 | on_event(client, event_instance); 390 | } 391 | _ => {} 392 | }) 393 | } 394 | pub fn on_connected( 395 | &mut self, 396 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokConnectedEvent), 397 | ) -> &mut Self { 398 | self.on_event(|client, incoming_event| match incoming_event { 399 | TikTokLiveEvent::OnConnected(event_instance) => { 400 | on_event(client, event_instance); 401 | } 402 | _ => {} 403 | }) 404 | } 405 | pub fn on_im_delete( 406 | &mut self, 407 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokImDeleteEvent), 408 | ) -> &mut Self { 409 | self.on_event(|client, incoming_event| match incoming_event { 410 | TikTokLiveEvent::OnImDelete(event_instance) => { 411 | on_event(client, event_instance); 412 | } 413 | _ => {} 414 | }) 415 | } 416 | pub fn on_room_user_seq( 417 | &mut self, 418 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokRoomUserSeqEvent), 419 | ) -> &mut Self { 420 | self.on_event(|client, incoming_event| match incoming_event { 421 | TikTokLiveEvent::OnRoomUserSeq(event_instance) => { 422 | on_event(client, event_instance); 423 | } 424 | _ => {} 425 | }) 426 | } 427 | pub fn on_unauthorized_member( 428 | &mut self, 429 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokUnauthorizedMemberEvent), 430 | ) -> &mut Self { 431 | self.on_event(|client, incoming_event| match incoming_event { 432 | TikTokLiveEvent::OnUnauthorizedMember(event_instance) => { 433 | on_event(client, event_instance); 434 | } 435 | _ => {} 436 | }) 437 | } 438 | pub fn on_oec_live_shopping( 439 | &mut self, 440 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokOecLiveShoppingEvent), 441 | ) -> &mut Self { 442 | self.on_event(|client, incoming_event| match incoming_event { 443 | TikTokLiveEvent::OnOecLiveShopping(event_instance) => { 444 | on_event(client, event_instance); 445 | } 446 | _ => {} 447 | }) 448 | } 449 | pub fn on_link( 450 | &mut self, 451 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLinkEvent), 452 | ) -> &mut Self { 453 | self.on_event(|client, incoming_event| match incoming_event { 454 | TikTokLiveEvent::OnLink(event_instance) => { 455 | on_event(client, event_instance); 456 | } 457 | _ => {} 458 | }) 459 | } 460 | pub fn on_link_mic_battle( 461 | &mut self, 462 | on_event: fn(client: &TikTokLiveClient, event_data: &TikTokLinkMicBattleEvent), 463 | ) -> &mut Self { 464 | self.on_event(|client, incoming_event| match incoming_event { 465 | TikTokLiveEvent::OnLinkMicBattle(event_instance) => { 466 | on_event(client, event_instance); 467 | } 468 | _ => {} 469 | }) 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /src/data/live_common.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Deserialize; 2 | use serde_derive::Serialize; 3 | use serde_json::Value; 4 | use std::collections::HashMap; 5 | use std::sync::Mutex; 6 | use std::time::Duration; 7 | 8 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 9 | pub struct ClientData { 10 | pub data: Data, 11 | pub extra: Extra2, 12 | pub status_code: i64, 13 | } 14 | 15 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 16 | pub struct Data { 17 | #[serde(rename = "AnchorABMap")] 18 | pub anchor_abmap: AnchorAbmap, 19 | pub adjust_display_order: i64, 20 | pub admin_ec_show_permission: AnchorAbmap, 21 | pub admin_user_ids: Vec, 22 | pub age_restricted: AgeRestricted, 23 | pub allow_preview_time: i64, 24 | pub anchor_scheduled_time_text: String, 25 | pub anchor_share_text: String, 26 | pub anchor_tab_type: i64, 27 | pub answering_question_content: String, 28 | pub app_id: i64, 29 | pub audio_mute: i64, 30 | pub auto_cover: i64, 31 | pub book_end_time: i64, 32 | pub book_time: i64, 33 | pub business_live: i64, 34 | pub challenge_info: String, 35 | pub client_version: i64, 36 | pub comment_has_text_emoji_emote: i64, 37 | pub comment_name_mode: i64, 38 | pub commerce_info: CommerceInfo, 39 | pub common_label_list: String, 40 | pub content_tag: String, 41 | pub cover: Cover, 42 | pub cpp_version: i64, 43 | pub create_time: i64, 44 | pub deco_list: Vec, 45 | pub deprecated10: String, 46 | pub deprecated11: String, 47 | pub deprecated12: String, 48 | pub deprecated13: String, 49 | pub deprecated14: i64, 50 | pub deprecated15: i64, 51 | pub deprecated16: i64, 52 | pub deprecated17: Vec, 53 | pub deprecated18: i64, 54 | pub deprecated19: String, 55 | pub deprecated195: bool, 56 | pub deprecated2: String, 57 | pub deprecated20: i64, 58 | pub deprecated21: bool, 59 | pub deprecated22: i64, 60 | pub deprecated23: String, 61 | pub deprecated24: i64, 62 | pub deprecated26: String, 63 | pub deprecated28: String, 64 | pub deprecated3: AnchorAbmap, 65 | pub deprecated30: String, 66 | pub deprecated31: bool, 67 | pub deprecated32: String, 68 | pub deprecated35: i64, 69 | pub deprecated36: i64, 70 | pub deprecated39: String, 71 | pub deprecated4: i64, 72 | pub deprecated41: i64, 73 | pub deprecated43: bool, 74 | pub deprecated44: i64, 75 | pub deprecated5: bool, 76 | pub deprecated6: String, 77 | pub deprecated7: i64, 78 | pub deprecated8: String, 79 | pub deprecated9: String, 80 | pub disable_preload_stream: bool, 81 | pub disable_preview_sub_only: i64, 82 | pub drawer_tab_position: String, 83 | pub drop_comment_group: i64, 84 | pub effect_info: Vec, 85 | pub enable_server_drop: i64, 86 | pub existed_commerce_goods: bool, 87 | pub fansclub_msg_style: i64, 88 | pub feed_room_label: Cover, 89 | pub feed_room_labels: Vec, 90 | pub filter_msg_rules: Vec, 91 | pub finish_reason: i64, 92 | pub finish_time: i64, 93 | pub finish_url: String, 94 | pub finish_url_v2: String, 95 | pub follow_msg_style: i64, 96 | pub forum_extra_data: String, 97 | pub game_demo: i64, 98 | pub game_tag: Vec, 99 | pub gift_msg_style: i64, 100 | pub gift_poll_vote_enabled: bool, 101 | pub group_source: i64, 102 | pub has_commerce_goods: bool, 103 | pub has_more_history_comment: bool, 104 | pub has_used_music: bool, 105 | pub hashtag: Option, 106 | pub have_wishlist: bool, 107 | pub history_comment_cursor: String, 108 | pub history_comment_list: Vec, 109 | pub hot_sentence_info: String, 110 | pub id: i64, 111 | pub id_str: String, 112 | pub idc_region: String, 113 | pub indicators: Vec, 114 | pub interaction_question_version: i64, 115 | pub introduction: String, 116 | pub is_gated_room: bool, 117 | pub is_replay: bool, 118 | pub is_show_user_card_switch: bool, 119 | pub last_ping_time: i64, 120 | pub layout: i64, 121 | pub like_count: i64, 122 | pub link_mic: LinkMic, 123 | pub linker_map: AnchorAbmap, 124 | pub linkmic_layout: i64, 125 | pub lite_user_not_visible: bool, 126 | pub lite_user_visible: bool, 127 | pub live_distribution: Vec, 128 | pub live_id: i64, 129 | pub live_reason: String, 130 | pub live_room_mode: i64, 131 | pub live_sub_only: i64, 132 | pub live_sub_only_use_music: i64, 133 | pub live_type_audio: bool, 134 | pub live_type_linkmic: bool, 135 | pub live_type_normal: bool, 136 | pub live_type_sandbox: bool, 137 | pub live_type_screenshot: bool, 138 | pub live_type_social_live: bool, 139 | pub live_type_third_party: bool, 140 | pub living_room_attrs: LivingRoomAttrs, 141 | pub lottery_finish_time: i64, 142 | pub max_preview_time: i64, 143 | pub mosaic_status: i64, 144 | pub multi_stream_id: i64, 145 | pub multi_stream_id_str: String, 146 | pub multi_stream_scene: i64, 147 | pub multi_stream_source: i64, 148 | pub net_mode: i64, 149 | pub os_type: i64, 150 | pub owner: Owner, 151 | pub owner_device_id: i64, 152 | pub owner_device_id_str: String, 153 | pub owner_user_id: i64, 154 | pub owner_user_id_str: String, 155 | pub paid_event: PaidEvent, 156 | pub pico_live_type: i64, 157 | pub polling_star_comment: bool, 158 | pub pre_enter_time: i64, 159 | pub preview_flow_tag: i64, 160 | pub quota_config: AnchorAbmap, 161 | pub rank_comment_groups: Vec, 162 | pub ranklist_audience_type: i64, 163 | pub regional_restricted: RegionalRestricted, 164 | pub relation_tag: String, 165 | pub replay: bool, 166 | pub room_audit_status: i64, 167 | pub room_auth: RoomAuth, 168 | pub room_create_ab_param: String, 169 | pub room_layout: i64, 170 | pub room_pcu: i64, 171 | pub room_sticker_list: Vec, 172 | pub room_tabs: Vec, 173 | pub room_tag: i64, 174 | pub rtc_app_id: String, 175 | pub scroll_config: String, 176 | pub search_id: i64, 177 | pub share_msg_style: i64, 178 | pub share_url: String, 179 | pub short_title: String, 180 | pub short_touch_items: Vec, 181 | pub show_star_comment_entrance: bool, 182 | pub social_interaction: SocialInteraction, 183 | pub start_time: i64, 184 | pub stats: Stats, 185 | pub status: i64, 186 | pub sticker_list: Vec, 187 | pub stream_id: i64, 188 | pub stream_id_str: String, 189 | pub stream_status: i64, 190 | pub stream_url: StreamUrl, 191 | pub stream_url_filtered_info: StreamUrlFilteredInfo, 192 | pub support_quiz: i64, 193 | pub title: String, 194 | pub top_fans: Vec, 195 | pub use_filter: bool, 196 | pub user_count: i64, 197 | pub user_share_text: String, 198 | pub video_feed_tag: String, 199 | pub webcast_comment_tcs: i64, 200 | pub webcast_sdk_version: i64, 201 | pub with_draw_something: bool, 202 | pub with_ktv: bool, 203 | pub with_linkmic: bool, 204 | } 205 | 206 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 207 | pub struct AnchorAbmap {} 208 | 209 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 210 | pub struct AgeRestricted { 211 | #[serde(rename = "AgeInterval")] 212 | pub age_interval: i64, 213 | pub restricted: bool, 214 | pub source: i64, 215 | } 216 | 217 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 218 | pub struct CommerceInfo { 219 | pub commerce_permission: i64, 220 | pub oec_live_enter_room_init_data: String, 221 | pub product_num: i64, 222 | pub use_async_load: bool, 223 | pub use_new_promotion: i64, 224 | } 225 | 226 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 227 | pub struct Cover { 228 | pub avg_color: String, 229 | pub height: i64, 230 | pub image_type: i64, 231 | pub is_animated: bool, 232 | pub open_web_url: String, 233 | pub uri: String, 234 | pub url_list: Vec, 235 | pub width: i64, 236 | } 237 | 238 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 239 | pub struct GameTag { 240 | pub bundle_id: String, 241 | pub full_name: String, 242 | pub game_category: Vec, 243 | pub hashtag_id: Vec, 244 | pub hashtag_list: Vec, 245 | pub id: i64, 246 | pub is_new_game: bool, 247 | pub landscape: i64, 248 | pub package_name: String, 249 | pub short_name: String, 250 | pub show_name: String, 251 | } 252 | 253 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 254 | pub struct Hashtag { 255 | pub id: i64, 256 | pub image: Cover, 257 | pub namespace: i64, 258 | pub title: String, 259 | } 260 | 261 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 262 | pub struct LinkMic { 263 | pub audience_id_list: Vec, 264 | pub battle_scores: Vec, 265 | pub battle_settings: BattleSettings, 266 | pub channel_id: i64, 267 | pub followed_count: i64, 268 | pub linked_user_list: Vec, 269 | pub multi_live_enum: i64, 270 | pub rival_anchor_id: i64, 271 | pub show_user_list: Vec, 272 | } 273 | 274 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 275 | pub struct BattleSettings { 276 | pub battle_id: i64, 277 | pub channel_id: i64, 278 | pub duration: i64, 279 | pub finished: i64, 280 | pub match_type: i64, 281 | pub start_time: i64, 282 | pub start_time_ms: i64, 283 | pub theme: String, 284 | } 285 | 286 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 287 | pub struct LivingRoomAttrs { 288 | pub admin_flag: i64, 289 | pub rank: i64, 290 | pub room_id: i64, 291 | pub room_id_str: String, 292 | pub silence_flag: i64, 293 | } 294 | 295 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 296 | pub struct Owner { 297 | pub allow_find_by_contacts: bool, 298 | pub allow_others_download_video: bool, 299 | pub allow_others_download_when_sharing_video: bool, 300 | pub allow_share_show_profile: bool, 301 | pub allow_show_in_gossip: bool, 302 | pub allow_show_my_action: bool, 303 | pub allow_strange_comment: bool, 304 | pub allow_unfollower_comment: bool, 305 | pub allow_use_linkmic: bool, 306 | pub avatar_large: Cover, 307 | pub avatar_medium: Cover, 308 | pub avatar_thumb: Cover, 309 | pub badge_image_list: Vec, 310 | pub badge_list: Vec, 311 | pub bg_img_url: String, 312 | pub bio_description: String, 313 | pub block_status: i64, 314 | pub border_list: Vec, 315 | pub comment_restrict: i64, 316 | pub commerce_webcast_config_ids: Vec, 317 | pub constellation: String, 318 | pub create_time: i64, 319 | pub deprecated1: i64, 320 | pub deprecated12: i64, 321 | pub deprecated13: i64, 322 | pub deprecated15: i64, 323 | pub deprecated16: bool, 324 | pub deprecated17: bool, 325 | pub deprecated18: String, 326 | pub deprecated19: bool, 327 | pub deprecated2: i64, 328 | pub deprecated21: i64, 329 | pub deprecated28: bool, 330 | pub deprecated29: String, 331 | pub deprecated3: i64, 332 | pub deprecated4: i64, 333 | pub deprecated5: String, 334 | pub deprecated6: i64, 335 | pub deprecated7: String, 336 | pub deprecated8: i64, 337 | pub disable_ichat: i64, 338 | pub display_id: String, 339 | pub enable_ichat_img: i64, 340 | pub exp: i64, 341 | pub fan_ticket_count: i64, 342 | pub fold_stranger_chat: bool, 343 | pub follow_info: FollowInfo, 344 | pub follow_status: i64, 345 | pub ichat_restrict_type: i64, 346 | pub id: i64, 347 | pub id_str: String, 348 | pub is_block: bool, 349 | pub is_follower: bool, 350 | pub is_following: bool, 351 | pub is_subscribe: bool, 352 | pub link_mic_stats: i64, 353 | pub media_badge_image_list: Vec, 354 | pub mint_type_label: Vec, 355 | pub modify_time: i64, 356 | pub need_profile_guide: bool, 357 | pub new_real_time_icons: Vec, 358 | pub nickname: String, 359 | pub own_room: OwnRoom, 360 | pub pay_grade: PayGrade, 361 | pub pay_score: i64, 362 | pub pay_scores: i64, 363 | pub push_comment_status: bool, 364 | pub push_digg: bool, 365 | pub push_follow: bool, 366 | pub push_friend_action: bool, 367 | pub push_ichat: bool, 368 | pub push_status: bool, 369 | pub push_video_post: bool, 370 | pub push_video_recommend: bool, 371 | pub real_time_icons: Vec, 372 | pub scm_label: String, 373 | pub sec_uid: String, 374 | pub secret: i64, 375 | pub share_qrcode_uri: String, 376 | pub special_id: String, 377 | pub status: i64, 378 | pub ticket_count: i64, 379 | pub top_fans: Vec, 380 | pub top_vip_no: i64, 381 | pub upcoming_event_list: Vec, 382 | pub user_attr: UserAttr, 383 | pub user_role: i64, 384 | pub verified: bool, 385 | pub verified_content: String, 386 | pub verified_reason: String, 387 | pub with_car_management_permission: bool, 388 | pub with_commerce_permission: bool, 389 | pub with_fusion_shop_entry: bool, 390 | } 391 | 392 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 393 | pub struct BadgeList { 394 | #[serde(rename = "OpenWebURL")] 395 | pub open_web_url: String, 396 | pub combine: Option, 397 | pub display: bool, 398 | pub display_status: i64, 399 | pub display_type: i64, 400 | pub exhibition_type: i64, 401 | pub greyed_by_client: i64, 402 | pub is_customized: bool, 403 | pub position: i64, 404 | pub priority_type: i64, 405 | pub privilege_log_extra: PrivilegeLogExtra, 406 | pub scene_type: i64, 407 | } 408 | 409 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 410 | pub struct Combine { 411 | pub background: Background, 412 | pub background_auto_mirrored: bool, 413 | pub background_dark_mode: Background, 414 | pub display_type: i64, 415 | pub font_style: Option, 416 | pub icon: Cover, 417 | pub icon_auto_mirrored: bool, 418 | pub multi_guest_show_style: i64, 419 | pub padding: Option, 420 | pub padding_new_font: Option, 421 | pub personal_card_show_style: i64, 422 | pub profile_card_panel: Option, 423 | pub public_screen_show_style: i64, 424 | pub ranklist_online_audience_show_style: i64, 425 | pub str: String, 426 | } 427 | 428 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 429 | pub struct Background { 430 | pub background_color_code: String, 431 | pub border_color_code: String, 432 | pub image: Image, 433 | } 434 | 435 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 436 | pub struct Image { 437 | pub avg_color: String, 438 | pub height: i64, 439 | pub image_type: i64, 440 | pub is_animated: bool, 441 | pub open_web_url: String, 442 | pub uri: String, 443 | pub url_list: Vec, 444 | pub width: i64, 445 | } 446 | 447 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 448 | pub struct FontStyle { 449 | pub border_color: String, 450 | pub font_color: String, 451 | pub font_size: i64, 452 | pub font_width: i64, 453 | } 454 | 455 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 456 | pub struct Padding { 457 | pub badge_width: i64, 458 | pub horizontal_padding_rule: i64, 459 | pub icon_bottom_padding: i64, 460 | pub icon_top_padding: i64, 461 | pub left_padding: i64, 462 | pub middle_padding: i64, 463 | pub right_padding: i64, 464 | pub use_specific: bool, 465 | pub vertical_padding_rule: i64, 466 | } 467 | 468 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 469 | pub struct ProfileCardPanel { 470 | pub badge_text_position: i64, 471 | pub profile_content: ProfileContent, 472 | pub projection_config: ProjectionConfig, 473 | pub use_new_profile_card_style: bool, 474 | } 475 | 476 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 477 | pub struct ProfileContent { 478 | pub icon_list: Vec, 479 | pub use_content: bool, 480 | } 481 | 482 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 483 | pub struct ProjectionConfig { 484 | pub icon: Image, 485 | pub use_projection: bool, 486 | } 487 | 488 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 489 | pub struct PrivilegeLogExtra { 490 | pub data_version: String, 491 | pub level: String, 492 | pub privilege_id: String, 493 | pub privilege_order_id: String, 494 | pub privilege_version: String, 495 | } 496 | 497 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 498 | pub struct FollowInfo { 499 | pub follow_status: i64, 500 | pub follower_count: i64, 501 | pub following_count: i64, 502 | pub push_status: i64, 503 | } 504 | 505 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 506 | pub struct OwnRoom { 507 | pub room_ids: Vec, 508 | pub room_ids_str: Vec, 509 | } 510 | 511 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 512 | pub struct PayGrade { 513 | pub deprecated20: i64, 514 | pub deprecated22: i64, 515 | pub deprecated23: i64, 516 | pub deprecated24: i64, 517 | pub deprecated25: i64, 518 | pub deprecated26: i64, 519 | pub grade_banner: String, 520 | pub grade_describe: String, 521 | pub grade_icon_list: Vec, 522 | pub level: i64, 523 | pub name: String, 524 | pub next_name: String, 525 | pub next_privileges: String, 526 | pub score: i64, 527 | pub screen_chat_type: i64, 528 | pub upgrade_need_consume: i64, 529 | } 530 | 531 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 532 | pub struct UserAttr { 533 | pub admin_permissions: AnchorAbmap, 534 | pub is_admin: bool, 535 | pub is_muted: bool, 536 | pub is_super_admin: bool, 537 | pub mute_duration: i64, 538 | } 539 | 540 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 541 | pub struct PaidEvent { 542 | pub event_id: i64, 543 | pub paid_type: i64, 544 | } 545 | 546 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 547 | pub struct RegionalRestricted { 548 | pub block_list: Vec, 549 | } 550 | 551 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 552 | pub struct RoomAuth { 553 | #[serde(rename = "Banner")] 554 | pub banner: i64, 555 | #[serde(rename = "BroadcastMessage")] 556 | pub broadcast_message: i64, 557 | #[serde(rename = "Chat")] 558 | pub chat: bool, 559 | #[serde(rename = "ChatL2")] 560 | pub chat_l2: bool, 561 | #[serde(rename = "ChatSubOnly")] 562 | pub chat_sub_only: bool, 563 | #[serde(rename = "CommercePermission")] 564 | pub commerce_permission: i64, 565 | #[serde(rename = "CommunityFlagged")] 566 | pub community_flagged: bool, 567 | #[serde(rename = "CommunityFlaggedReview")] 568 | pub community_flagged_review: bool, 569 | #[serde(rename = "CustomizableGiftPoll")] 570 | pub customizable_gift_poll: i64, 571 | #[serde(rename = "CustomizablePoll")] 572 | pub customizable_poll: i64, 573 | #[serde(rename = "Danmaku")] 574 | pub danmaku: bool, 575 | #[serde(rename = "Digg")] 576 | pub digg: bool, 577 | #[serde(rename = "DonationSticker")] 578 | pub donation_sticker: i64, 579 | #[serde(rename = "EnableFansLevel")] 580 | pub enable_fans_level: bool, 581 | #[serde(rename = "EventPromotion")] 582 | pub event_promotion: i64, 583 | #[serde(rename = "Explore")] 584 | pub explore: bool, 585 | #[serde(rename = "GameRankingSwitch")] 586 | pub game_ranking_switch: i64, 587 | #[serde(rename = "Gift")] 588 | pub gift: bool, 589 | #[serde(rename = "GiftAnchorMt")] 590 | pub gift_anchor_mt: i64, 591 | #[serde(rename = "GiftPoll")] 592 | pub gift_poll: i64, 593 | #[serde(rename = "GoldenEnvelope")] 594 | pub golden_envelope: i64, 595 | #[serde(rename = "GoldenEnvelopeActivity")] 596 | pub golden_envelope_activity: i64, 597 | #[serde(rename = "InteractionQuestion")] 598 | pub interaction_question: bool, 599 | #[serde(rename = "Landscape")] 600 | pub landscape: i64, 601 | #[serde(rename = "LandscapeChat")] 602 | pub landscape_chat: i64, 603 | #[serde(rename = "LuckMoney")] 604 | pub luck_money: bool, 605 | #[serde(rename = "MultiEnableReserve")] 606 | pub multi_enable_reserve: bool, 607 | #[serde(rename = "Pictionary")] 608 | pub pictionary: i64, 609 | #[serde(rename = "PictionaryBubble")] 610 | pub pictionary_bubble: i64, 611 | #[serde(rename = "PictionaryPermission")] 612 | pub pictionary_permission: i64, 613 | #[serde(rename = "Poll")] 614 | pub poll: i64, 615 | #[serde(rename = "Promote")] 616 | pub promote: bool, 617 | #[serde(rename = "PromoteOther")] 618 | pub promote_other: i64, 619 | #[serde(rename = "Props")] 620 | pub props: bool, 621 | #[serde(rename = "PublicScreen")] 622 | pub public_screen: i64, 623 | #[serde(rename = "QuickChat")] 624 | pub quick_chat: i64, 625 | #[serde(rename = "Rank")] 626 | pub rank: i64, 627 | #[serde(rename = "RankingChangeAlterSwitch")] 628 | pub ranking_change_alter_switch: i64, 629 | #[serde(rename = "RoomContributor")] 630 | pub room_contributor: bool, 631 | #[serde(rename = "SecretRoom")] 632 | pub secret_room: i64, 633 | #[serde(rename = "Share")] 634 | pub share: bool, 635 | #[serde(rename = "ShareEffect")] 636 | pub share_effect: i64, 637 | #[serde(rename = "ShoppingRanking")] 638 | pub shopping_ranking: i64, 639 | #[serde(rename = "SpamComments")] 640 | pub spam_comments: bool, 641 | #[serde(rename = "UserCard")] 642 | pub user_card: bool, 643 | #[serde(rename = "UserCount")] 644 | pub user_count: i64, 645 | #[serde(rename = "Viewers")] 646 | pub viewers: bool, 647 | pub comment_tray_status: i64, 648 | pub credit_entrance_for_audience: bool, 649 | pub deprecated1: bool, 650 | pub deprecated118: Vec, 651 | pub deprecated119: i64, 652 | pub deprecated2: i64, 653 | pub deprecated3: i64, 654 | pub deprecated4: i64, 655 | pub deprecated5: i64, 656 | pub deprecated6: i64, 657 | pub deprecated7: i64, 658 | pub deprecated8: i64, 659 | pub deprecated9: i64, 660 | pub game_guess_permission: bool, 661 | pub guess_entrance_for_host: bool, 662 | pub show_credit_widget: bool, 663 | pub transaction_history: i64, 664 | pub use_user_pv: bool, 665 | } 666 | 667 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 668 | pub struct SocialInteraction { 669 | pub linkmic_scene_linker: AnchorAbmap, 670 | pub multi_live: MultiLive, 671 | } 672 | 673 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 674 | pub struct MultiLive { 675 | pub audience_shared_invitee_panel_type: i64, 676 | pub host_gifter_linkmic_enum: i64, 677 | pub host_multi_guest_dev_mode: i64, 678 | pub linkmic_service_version: i64, 679 | pub try_open_multi_guest_when_create_room: bool, 680 | pub user_settings: UserSettings, 681 | pub viewer_gifter_linkmic_enum: i64, 682 | } 683 | 684 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 685 | pub struct UserSettings { 686 | pub applier_sort_gift_score_threshold: i64, 687 | pub applier_sort_setting: i64, 688 | pub multi_live_apply_permission: i64, 689 | } 690 | 691 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 692 | pub struct Stats { 693 | pub deprecated1: i64, 694 | pub deprecated2: String, 695 | pub digg_count: i64, 696 | pub enter_count: i64, 697 | pub fan_ticket: i64, 698 | pub follow_count: i64, 699 | pub gift_uv_count: i64, 700 | pub id: i64, 701 | pub id_str: String, 702 | pub like_count: i64, 703 | pub replay_fan_ticket: i64, 704 | pub replay_viewers: i64, 705 | pub share_count: i64, 706 | pub total_user: i64, 707 | pub total_user_desp: String, 708 | pub watermelon: i64, 709 | } 710 | 711 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 712 | pub struct StreamUrl { 713 | pub candidate_resolution: Vec, 714 | pub complete_push_urls: Vec, 715 | pub default_resolution: String, 716 | pub extra: Extra, 717 | pub flv_pull_url: Option, 718 | pub flv_pull_url_params: FlvPullUrl, 719 | pub hls_pull_url: String, 720 | pub hls_pull_url_map: AnchorAbmap, 721 | pub hls_pull_url_params: String, 722 | pub id: i64, 723 | pub id_str: String, 724 | pub live_core_sdk_data: Option, 725 | pub provider: i64, 726 | pub push_resolution: String, 727 | pub push_urls: Vec, 728 | pub resolution_name: ResolutionName, 729 | pub rtmp_pull_url: String, 730 | pub rtmp_pull_url_params: String, 731 | pub rtmp_push_url: String, 732 | pub rtmp_push_url_params: String, 733 | pub stream_app_id: i64, 734 | pub stream_control_type: i64, 735 | pub stream_delay_ms: i64, 736 | pub vr_type: i64, 737 | } 738 | 739 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 740 | pub struct Extra { 741 | pub anchor_interact_profile: i64, 742 | pub audience_interact_profile: i64, 743 | pub bframe_enable: bool, 744 | pub bitrate_adapt_strategy: i64, 745 | pub bytevc1_enable: bool, 746 | pub default_bitrate: i64, 747 | pub deprecated1: bool, 748 | pub fps: i64, 749 | pub gop_sec: i64, 750 | pub hardware_encode: bool, 751 | pub height: i64, 752 | pub max_bitrate: i64, 753 | pub min_bitrate: i64, 754 | pub roi: bool, 755 | pub sw_roi: bool, 756 | pub video_profile: i64, 757 | pub width: i64, 758 | } 759 | 760 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 761 | pub struct FlvPullUrl { 762 | #[serde(rename = "HD1")] 763 | pub hd1: Option, 764 | #[serde(rename = "SD1")] 765 | pub sd1: Option, 766 | #[serde(rename = "SD2")] 767 | pub sd2: Option, 768 | } 769 | 770 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 771 | pub struct LiveCoreSdkData { 772 | pub pull_data: PullData, 773 | } 774 | 775 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 776 | pub struct PullData { 777 | pub options: Options, 778 | pub stream_data: String, 779 | } 780 | 781 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 782 | pub struct Options { 783 | pub default_quality: DefaultQuality, 784 | pub qualities: Vec, 785 | pub show_quality_button: bool, 786 | } 787 | 788 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 789 | pub struct DefaultQuality { 790 | pub icon_type: i64, 791 | pub level: i64, 792 | pub name: String, 793 | pub resolution: String, 794 | pub sdk_key: String, 795 | pub v_codec: String, 796 | } 797 | 798 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 799 | pub struct ResolutionName { 800 | #[serde(rename = "AUTO")] 801 | pub auto: Option, 802 | #[serde(rename = "FULL_HD1")] 803 | pub full_hd1: Option, 804 | #[serde(rename = "HD1")] 805 | pub hd1: Option, 806 | #[serde(rename = "ORIGION")] 807 | pub origion: Option, 808 | #[serde(rename = "SD1")] 809 | pub sd1: Option, 810 | #[serde(rename = "SD2")] 811 | pub sd2: Option, 812 | pub pm_mt_video_1080p60: Option, 813 | pub pm_mt_video_720p60: Option, 814 | } 815 | 816 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 817 | pub struct StreamUrlFilteredInfo { 818 | pub is_gated_room: bool, 819 | pub is_paid_event: bool, 820 | } 821 | 822 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 823 | pub struct TopFan { 824 | pub fan_ticket: i64, 825 | pub user: User, 826 | } 827 | 828 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 829 | pub struct User { 830 | pub allow_find_by_contacts: bool, 831 | pub allow_others_download_video: bool, 832 | pub allow_others_download_when_sharing_video: bool, 833 | pub allow_share_show_profile: bool, 834 | pub allow_show_in_gossip: bool, 835 | pub allow_show_my_action: bool, 836 | pub allow_strange_comment: bool, 837 | pub allow_unfollower_comment: bool, 838 | pub allow_use_linkmic: bool, 839 | pub avatar_large: Cover, 840 | pub avatar_medium: Cover, 841 | pub avatar_thumb: Cover, 842 | pub badge_image_list: Vec, 843 | pub badge_list: Vec, 844 | pub bg_img_url: String, 845 | pub bio_description: String, 846 | pub block_status: i64, 847 | pub border_list: Vec, 848 | pub comment_restrict: i64, 849 | pub commerce_webcast_config_ids: Vec, 850 | pub constellation: String, 851 | pub create_time: i64, 852 | pub deprecated1: i64, 853 | pub deprecated12: i64, 854 | pub deprecated13: i64, 855 | pub deprecated15: i64, 856 | pub deprecated16: bool, 857 | pub deprecated17: bool, 858 | pub deprecated18: String, 859 | pub deprecated19: bool, 860 | pub deprecated2: i64, 861 | pub deprecated21: i64, 862 | pub deprecated28: bool, 863 | pub deprecated29: String, 864 | pub deprecated3: i64, 865 | pub deprecated4: i64, 866 | pub deprecated5: String, 867 | pub deprecated6: i64, 868 | pub deprecated7: String, 869 | pub deprecated8: i64, 870 | pub disable_ichat: i64, 871 | pub display_id: String, 872 | pub enable_ichat_img: i64, 873 | pub exp: i64, 874 | pub fan_ticket_count: i64, 875 | pub fold_stranger_chat: bool, 876 | pub follow_info: FollowInfo, 877 | pub follow_status: i64, 878 | pub ichat_restrict_type: i64, 879 | pub id: i64, 880 | pub id_str: String, 881 | pub is_block: bool, 882 | pub is_follower: bool, 883 | pub is_following: bool, 884 | pub is_subscribe: bool, 885 | pub link_mic_stats: i64, 886 | pub media_badge_image_list: Vec, 887 | pub mint_type_label: Vec, 888 | pub modify_time: i64, 889 | pub need_profile_guide: bool, 890 | pub new_real_time_icons: Vec, 891 | pub nickname: String, 892 | pub pay_grade: PayGrade, 893 | pub pay_score: i64, 894 | pub pay_scores: i64, 895 | pub push_comment_status: bool, 896 | pub push_digg: bool, 897 | pub push_follow: bool, 898 | pub push_friend_action: bool, 899 | pub push_ichat: bool, 900 | pub push_status: bool, 901 | pub push_video_post: bool, 902 | pub push_video_recommend: bool, 903 | pub real_time_icons: Vec, 904 | pub scm_label: String, 905 | pub sec_uid: String, 906 | pub secret: i64, 907 | pub share_qrcode_uri: String, 908 | pub special_id: String, 909 | pub status: i64, 910 | pub ticket_count: i64, 911 | pub top_fans: Vec, 912 | pub top_vip_no: i64, 913 | pub upcoming_event_list: Vec, 914 | pub user_attr: UserAttr, 915 | pub user_role: i64, 916 | pub verified: bool, 917 | pub verified_content: String, 918 | pub verified_reason: String, 919 | pub with_car_management_permission: bool, 920 | pub with_commerce_permission: bool, 921 | pub with_fusion_shop_entry: bool, 922 | } 923 | 924 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 925 | pub struct Extra2 { 926 | pub now: i64, 927 | } 928 | 929 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 930 | pub struct StreamData { 931 | pub common: Common, 932 | pub data: NestedData, 933 | } 934 | 935 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 936 | pub struct Common { 937 | pub peer_anchor_level: i64, 938 | pub session_id: String, 939 | } 940 | 941 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 942 | pub struct NestedData { 943 | pub ao: Option, 944 | pub hd: Option, 945 | pub ld: Option, 946 | pub origin: Option, 947 | pub sd: Option, 948 | pub uhd: Option, 949 | } 950 | 951 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 952 | pub struct Ao { 953 | pub main: Main, 954 | } 955 | 956 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 957 | pub struct Main { 958 | pub cmaf: String, 959 | pub dash: String, 960 | pub flv: String, 961 | pub hls: String, 962 | pub lls: String, 963 | pub tile: String, 964 | pub tsl: String, 965 | } 966 | 967 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 968 | pub struct Demotion { 969 | #[serde(rename = "StallCount")] 970 | pub stall_count: i64, 971 | } 972 | 973 | #[derive(Clone)] 974 | pub struct TikTokLiveSettings { 975 | pub host_name: String, 976 | pub language: String, 977 | pub sign_api_key: String, 978 | pub reconnect_on_fail: bool, 979 | pub print_logs: bool, 980 | pub http_data: HttpData, 981 | } 982 | 983 | #[derive(Clone, Default)] 984 | pub struct HttpData { 985 | pub time_out: Duration, 986 | pub params: HashMap, 987 | pub headers: HashMap, 988 | pub cookies: HashMap, 989 | } 990 | 991 | #[derive(Default)] 992 | pub struct TikTokLiveInfo { 993 | pub room_id: String, 994 | pub client_data: String, 995 | pub likes: i32, 996 | pub viewers: i32, 997 | pub total_viewers: i32, 998 | pub host_name: String, 999 | pub title: String, 1000 | pub language: String, 1001 | pub connection_state: Mutex, 1002 | } 1003 | 1004 | #[derive(PartialEq, Debug)] 1005 | pub enum ConnectionState { 1006 | CONNECTING, 1007 | CONNECTED, 1008 | DISCONNECTED, 1009 | } 1010 | 1011 | impl Default for ConnectionState { 1012 | fn default() -> Self { 1013 | ConnectionState::DISCONNECTED 1014 | } 1015 | } 1016 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.22.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anyhow" 31 | version = "1.0.86" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 34 | 35 | [[package]] 36 | name = "autocfg" 37 | version = "1.3.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 40 | 41 | [[package]] 42 | name = "backtrace" 43 | version = "0.3.72" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" 46 | dependencies = [ 47 | "addr2line", 48 | "cc", 49 | "cfg-if", 50 | "libc", 51 | "miniz_oxide", 52 | "object", 53 | "rustc-demangle", 54 | ] 55 | 56 | [[package]] 57 | name = "base64" 58 | version = "0.13.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 61 | 62 | [[package]] 63 | name = "base64" 64 | version = "0.21.7" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 67 | 68 | [[package]] 69 | name = "bitflags" 70 | version = "1.3.2" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 73 | 74 | [[package]] 75 | name = "bitflags" 76 | version = "2.5.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 79 | 80 | [[package]] 81 | name = "block-buffer" 82 | version = "0.9.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 85 | dependencies = [ 86 | "generic-array", 87 | ] 88 | 89 | [[package]] 90 | name = "bumpalo" 91 | version = "3.16.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 94 | 95 | [[package]] 96 | name = "byteorder" 97 | version = "1.5.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 100 | 101 | [[package]] 102 | name = "bytes" 103 | version = "1.6.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 106 | 107 | [[package]] 108 | name = "cc" 109 | version = "1.0.98" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" 112 | 113 | [[package]] 114 | name = "cfg-if" 115 | version = "1.0.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 118 | 119 | [[package]] 120 | name = "core-foundation" 121 | version = "0.9.4" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 124 | dependencies = [ 125 | "core-foundation-sys", 126 | "libc", 127 | ] 128 | 129 | [[package]] 130 | name = "core-foundation-sys" 131 | version = "0.8.6" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 134 | 135 | [[package]] 136 | name = "cpufeatures" 137 | version = "0.2.12" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 140 | dependencies = [ 141 | "libc", 142 | ] 143 | 144 | [[package]] 145 | name = "digest" 146 | version = "0.9.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 149 | dependencies = [ 150 | "generic-array", 151 | ] 152 | 153 | [[package]] 154 | name = "either" 155 | version = "1.12.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" 158 | 159 | [[package]] 160 | name = "encoding_rs" 161 | version = "0.8.34" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 164 | dependencies = [ 165 | "cfg-if", 166 | ] 167 | 168 | [[package]] 169 | name = "env_logger" 170 | version = "0.10.2" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 173 | dependencies = [ 174 | "humantime", 175 | "is-terminal", 176 | "log", 177 | "regex", 178 | "termcolor", 179 | ] 180 | 181 | [[package]] 182 | name = "equivalent" 183 | version = "1.0.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 186 | 187 | [[package]] 188 | name = "errno" 189 | version = "0.3.9" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 192 | dependencies = [ 193 | "libc", 194 | "windows-sys 0.52.0", 195 | ] 196 | 197 | [[package]] 198 | name = "fastrand" 199 | version = "2.1.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 202 | 203 | [[package]] 204 | name = "fnv" 205 | version = "1.0.7" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 208 | 209 | [[package]] 210 | name = "foreign-types" 211 | version = "0.3.2" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 214 | dependencies = [ 215 | "foreign-types-shared", 216 | ] 217 | 218 | [[package]] 219 | name = "foreign-types-shared" 220 | version = "0.1.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 223 | 224 | [[package]] 225 | name = "form_urlencoded" 226 | version = "1.2.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 229 | dependencies = [ 230 | "percent-encoding", 231 | ] 232 | 233 | [[package]] 234 | name = "futures-channel" 235 | version = "0.3.30" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 238 | dependencies = [ 239 | "futures-core", 240 | ] 241 | 242 | [[package]] 243 | name = "futures-core" 244 | version = "0.3.30" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 247 | 248 | [[package]] 249 | name = "futures-macro" 250 | version = "0.3.30" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 253 | dependencies = [ 254 | "proc-macro2", 255 | "quote", 256 | "syn", 257 | ] 258 | 259 | [[package]] 260 | name = "futures-sink" 261 | version = "0.3.30" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 264 | 265 | [[package]] 266 | name = "futures-task" 267 | version = "0.3.30" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 270 | 271 | [[package]] 272 | name = "futures-util" 273 | version = "0.3.30" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 276 | dependencies = [ 277 | "futures-core", 278 | "futures-macro", 279 | "futures-sink", 280 | "futures-task", 281 | "pin-project-lite", 282 | "pin-utils", 283 | "slab", 284 | ] 285 | 286 | [[package]] 287 | name = "generic-array" 288 | version = "0.14.7" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 291 | dependencies = [ 292 | "typenum", 293 | "version_check", 294 | ] 295 | 296 | [[package]] 297 | name = "getrandom" 298 | version = "0.2.15" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 301 | dependencies = [ 302 | "cfg-if", 303 | "libc", 304 | "wasi", 305 | ] 306 | 307 | [[package]] 308 | name = "gimli" 309 | version = "0.29.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 312 | 313 | [[package]] 314 | name = "h2" 315 | version = "0.3.26" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 318 | dependencies = [ 319 | "bytes", 320 | "fnv", 321 | "futures-core", 322 | "futures-sink", 323 | "futures-util", 324 | "http", 325 | "indexmap", 326 | "slab", 327 | "tokio", 328 | "tokio-util", 329 | "tracing", 330 | ] 331 | 332 | [[package]] 333 | name = "hashbrown" 334 | version = "0.14.5" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 337 | 338 | [[package]] 339 | name = "hermit-abi" 340 | version = "0.3.9" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 343 | 344 | [[package]] 345 | name = "home" 346 | version = "0.5.9" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 349 | dependencies = [ 350 | "windows-sys 0.52.0", 351 | ] 352 | 353 | [[package]] 354 | name = "http" 355 | version = "0.2.12" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 358 | dependencies = [ 359 | "bytes", 360 | "fnv", 361 | "itoa", 362 | ] 363 | 364 | [[package]] 365 | name = "http-body" 366 | version = "0.4.6" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 369 | dependencies = [ 370 | "bytes", 371 | "http", 372 | "pin-project-lite", 373 | ] 374 | 375 | [[package]] 376 | name = "httparse" 377 | version = "1.8.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 380 | 381 | [[package]] 382 | name = "httpdate" 383 | version = "1.0.3" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 386 | 387 | [[package]] 388 | name = "humantime" 389 | version = "2.1.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 392 | 393 | [[package]] 394 | name = "hyper" 395 | version = "0.14.30" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" 398 | dependencies = [ 399 | "bytes", 400 | "futures-channel", 401 | "futures-core", 402 | "futures-util", 403 | "h2", 404 | "http", 405 | "http-body", 406 | "httparse", 407 | "httpdate", 408 | "itoa", 409 | "pin-project-lite", 410 | "socket2", 411 | "tokio", 412 | "tower-service", 413 | "tracing", 414 | "want", 415 | ] 416 | 417 | [[package]] 418 | name = "hyper-tls" 419 | version = "0.5.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 422 | dependencies = [ 423 | "bytes", 424 | "hyper", 425 | "native-tls", 426 | "tokio", 427 | "tokio-native-tls", 428 | ] 429 | 430 | [[package]] 431 | name = "idna" 432 | version = "0.5.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 435 | dependencies = [ 436 | "unicode-bidi", 437 | "unicode-normalization", 438 | ] 439 | 440 | [[package]] 441 | name = "indexmap" 442 | version = "2.2.6" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 445 | dependencies = [ 446 | "equivalent", 447 | "hashbrown", 448 | ] 449 | 450 | [[package]] 451 | name = "ipnet" 452 | version = "2.9.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 455 | 456 | [[package]] 457 | name = "is-terminal" 458 | version = "0.4.12" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" 461 | dependencies = [ 462 | "hermit-abi", 463 | "libc", 464 | "windows-sys 0.52.0", 465 | ] 466 | 467 | [[package]] 468 | name = "itoa" 469 | version = "1.0.11" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 472 | 473 | [[package]] 474 | name = "js-sys" 475 | version = "0.3.69" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 478 | dependencies = [ 479 | "wasm-bindgen", 480 | ] 481 | 482 | [[package]] 483 | name = "libc" 484 | version = "0.2.155" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 487 | 488 | [[package]] 489 | name = "linux-raw-sys" 490 | version = "0.4.14" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 493 | 494 | [[package]] 495 | name = "lock_api" 496 | version = "0.4.12" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 499 | dependencies = [ 500 | "autocfg", 501 | "scopeguard", 502 | ] 503 | 504 | [[package]] 505 | name = "log" 506 | version = "0.4.21" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 509 | 510 | [[package]] 511 | name = "memchr" 512 | version = "2.7.2" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 515 | 516 | [[package]] 517 | name = "mime" 518 | version = "0.3.17" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 521 | 522 | [[package]] 523 | name = "miniz_oxide" 524 | version = "0.7.3" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" 527 | dependencies = [ 528 | "adler", 529 | ] 530 | 531 | [[package]] 532 | name = "mio" 533 | version = "0.8.11" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 536 | dependencies = [ 537 | "libc", 538 | "wasi", 539 | "windows-sys 0.48.0", 540 | ] 541 | 542 | [[package]] 543 | name = "native-tls" 544 | version = "0.2.12" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 547 | dependencies = [ 548 | "libc", 549 | "log", 550 | "openssl", 551 | "openssl-probe", 552 | "openssl-sys", 553 | "schannel", 554 | "security-framework", 555 | "security-framework-sys", 556 | "tempfile", 557 | ] 558 | 559 | [[package]] 560 | name = "num_cpus" 561 | version = "1.16.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 564 | dependencies = [ 565 | "hermit-abi", 566 | "libc", 567 | ] 568 | 569 | [[package]] 570 | name = "object" 571 | version = "0.35.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" 574 | dependencies = [ 575 | "memchr", 576 | ] 577 | 578 | [[package]] 579 | name = "once_cell" 580 | version = "1.19.0" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 583 | 584 | [[package]] 585 | name = "opaque-debug" 586 | version = "0.3.1" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 589 | 590 | [[package]] 591 | name = "openssl" 592 | version = "0.10.64" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" 595 | dependencies = [ 596 | "bitflags 2.5.0", 597 | "cfg-if", 598 | "foreign-types", 599 | "libc", 600 | "once_cell", 601 | "openssl-macros", 602 | "openssl-sys", 603 | ] 604 | 605 | [[package]] 606 | name = "openssl-macros" 607 | version = "0.1.1" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 610 | dependencies = [ 611 | "proc-macro2", 612 | "quote", 613 | "syn", 614 | ] 615 | 616 | [[package]] 617 | name = "openssl-probe" 618 | version = "0.1.5" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 621 | 622 | [[package]] 623 | name = "openssl-sys" 624 | version = "0.9.102" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" 627 | dependencies = [ 628 | "cc", 629 | "libc", 630 | "pkg-config", 631 | "vcpkg", 632 | ] 633 | 634 | [[package]] 635 | name = "parking_lot" 636 | version = "0.12.3" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 639 | dependencies = [ 640 | "lock_api", 641 | "parking_lot_core", 642 | ] 643 | 644 | [[package]] 645 | name = "parking_lot_core" 646 | version = "0.9.10" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 649 | dependencies = [ 650 | "cfg-if", 651 | "libc", 652 | "redox_syscall", 653 | "smallvec", 654 | "windows-targets 0.52.5", 655 | ] 656 | 657 | [[package]] 658 | name = "percent-encoding" 659 | version = "2.3.1" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 662 | 663 | [[package]] 664 | name = "pin-project-lite" 665 | version = "0.2.14" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 668 | 669 | [[package]] 670 | name = "pin-utils" 671 | version = "0.1.0" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 674 | 675 | [[package]] 676 | name = "pkg-config" 677 | version = "0.3.30" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 680 | 681 | [[package]] 682 | name = "ppv-lite86" 683 | version = "0.2.17" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 686 | 687 | [[package]] 688 | name = "proc-macro2" 689 | version = "1.0.85" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 692 | dependencies = [ 693 | "unicode-ident", 694 | ] 695 | 696 | [[package]] 697 | name = "protobuf" 698 | version = "3.5.1" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "0bcc343da15609eaecd65f8aa76df8dc4209d325131d8219358c0aaaebab0bf6" 701 | dependencies = [ 702 | "bytes", 703 | "once_cell", 704 | "protobuf-support", 705 | "thiserror", 706 | ] 707 | 708 | [[package]] 709 | name = "protobuf-codegen" 710 | version = "3.5.1" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "c4d0cde5642ea4df842b13eb9f59ea6fafa26dcb43e3e1ee49120e9757556189" 713 | dependencies = [ 714 | "anyhow", 715 | "once_cell", 716 | "protobuf", 717 | "protobuf-parse", 718 | "regex", 719 | "tempfile", 720 | "thiserror", 721 | ] 722 | 723 | [[package]] 724 | name = "protobuf-parse" 725 | version = "3.5.1" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "1b0e9b447d099ae2c4993c0cbb03c7a9d6c937b17f2d56cfc0b1550e6fcfdb76" 728 | dependencies = [ 729 | "anyhow", 730 | "indexmap", 731 | "log", 732 | "protobuf", 733 | "protobuf-support", 734 | "tempfile", 735 | "thiserror", 736 | "which", 737 | ] 738 | 739 | [[package]] 740 | name = "protobuf-support" 741 | version = "3.5.1" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "f0766e3675a627c327e4b3964582594b0e8741305d628a98a5de75a1d15f99b9" 744 | dependencies = [ 745 | "thiserror", 746 | ] 747 | 748 | [[package]] 749 | name = "protoc-bin-vendored" 750 | version = "3.0.0" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "005ca8623e5633e298ad1f917d8be0a44bcf406bf3cde3b80e63003e49a3f27d" 753 | dependencies = [ 754 | "protoc-bin-vendored-linux-aarch_64", 755 | "protoc-bin-vendored-linux-ppcle_64", 756 | "protoc-bin-vendored-linux-x86_32", 757 | "protoc-bin-vendored-linux-x86_64", 758 | "protoc-bin-vendored-macos-x86_64", 759 | "protoc-bin-vendored-win32", 760 | ] 761 | 762 | [[package]] 763 | name = "protoc-bin-vendored-linux-aarch_64" 764 | version = "3.0.0" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "8fb9fc9cce84c8694b6ea01cc6296617b288b703719b725b8c9c65f7c5874435" 767 | 768 | [[package]] 769 | name = "protoc-bin-vendored-linux-ppcle_64" 770 | version = "3.0.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "02d2a07dcf7173a04d49974930ccbfb7fd4d74df30ecfc8762cf2f895a094516" 773 | 774 | [[package]] 775 | name = "protoc-bin-vendored-linux-x86_32" 776 | version = "3.0.0" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "d54fef0b04fcacba64d1d80eed74a20356d96847da8497a59b0a0a436c9165b0" 779 | 780 | [[package]] 781 | name = "protoc-bin-vendored-linux-x86_64" 782 | version = "3.0.0" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "b8782f2ce7d43a9a5c74ea4936f001e9e8442205c244f7a3d4286bd4c37bc924" 785 | 786 | [[package]] 787 | name = "protoc-bin-vendored-macos-x86_64" 788 | version = "3.0.0" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "b5de656c7ee83f08e0ae5b81792ccfdc1d04e7876b1d9a38e6876a9e09e02537" 791 | 792 | [[package]] 793 | name = "protoc-bin-vendored-win32" 794 | version = "3.0.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "9653c3ed92974e34c5a6e0a510864dab979760481714c172e0a34e437cb98804" 797 | 798 | [[package]] 799 | name = "quote" 800 | version = "1.0.36" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 803 | dependencies = [ 804 | "proc-macro2", 805 | ] 806 | 807 | [[package]] 808 | name = "rand" 809 | version = "0.8.5" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 812 | dependencies = [ 813 | "libc", 814 | "rand_chacha", 815 | "rand_core", 816 | ] 817 | 818 | [[package]] 819 | name = "rand_chacha" 820 | version = "0.3.1" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 823 | dependencies = [ 824 | "ppv-lite86", 825 | "rand_core", 826 | ] 827 | 828 | [[package]] 829 | name = "rand_core" 830 | version = "0.6.4" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 833 | dependencies = [ 834 | "getrandom", 835 | ] 836 | 837 | [[package]] 838 | name = "redox_syscall" 839 | version = "0.5.1" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 842 | dependencies = [ 843 | "bitflags 2.5.0", 844 | ] 845 | 846 | [[package]] 847 | name = "regex" 848 | version = "1.10.4" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 851 | dependencies = [ 852 | "aho-corasick", 853 | "memchr", 854 | "regex-automata", 855 | "regex-syntax", 856 | ] 857 | 858 | [[package]] 859 | name = "regex-automata" 860 | version = "0.4.6" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 863 | dependencies = [ 864 | "aho-corasick", 865 | "memchr", 866 | "regex-syntax", 867 | ] 868 | 869 | [[package]] 870 | name = "regex-syntax" 871 | version = "0.8.3" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 874 | 875 | [[package]] 876 | name = "reqwest" 877 | version = "0.11.27" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 880 | dependencies = [ 881 | "base64 0.21.7", 882 | "bytes", 883 | "encoding_rs", 884 | "futures-core", 885 | "futures-util", 886 | "h2", 887 | "http", 888 | "http-body", 889 | "hyper", 890 | "hyper-tls", 891 | "ipnet", 892 | "js-sys", 893 | "log", 894 | "mime", 895 | "native-tls", 896 | "once_cell", 897 | "percent-encoding", 898 | "pin-project-lite", 899 | "rustls-pemfile", 900 | "serde", 901 | "serde_json", 902 | "serde_urlencoded", 903 | "sync_wrapper", 904 | "system-configuration", 905 | "tokio", 906 | "tokio-native-tls", 907 | "tower-service", 908 | "url", 909 | "wasm-bindgen", 910 | "wasm-bindgen-futures", 911 | "web-sys", 912 | "winreg", 913 | ] 914 | 915 | [[package]] 916 | name = "rustc-demangle" 917 | version = "0.1.24" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 920 | 921 | [[package]] 922 | name = "rustix" 923 | version = "0.38.34" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 926 | dependencies = [ 927 | "bitflags 2.5.0", 928 | "errno", 929 | "libc", 930 | "linux-raw-sys", 931 | "windows-sys 0.52.0", 932 | ] 933 | 934 | [[package]] 935 | name = "rustls-pemfile" 936 | version = "1.0.4" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 939 | dependencies = [ 940 | "base64 0.21.7", 941 | ] 942 | 943 | [[package]] 944 | name = "ryu" 945 | version = "1.0.18" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 948 | 949 | [[package]] 950 | name = "schannel" 951 | version = "0.1.23" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 954 | dependencies = [ 955 | "windows-sys 0.52.0", 956 | ] 957 | 958 | [[package]] 959 | name = "scopeguard" 960 | version = "1.2.0" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 963 | 964 | [[package]] 965 | name = "security-framework" 966 | version = "2.11.0" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" 969 | dependencies = [ 970 | "bitflags 2.5.0", 971 | "core-foundation", 972 | "core-foundation-sys", 973 | "libc", 974 | "security-framework-sys", 975 | ] 976 | 977 | [[package]] 978 | name = "security-framework-sys" 979 | version = "2.11.0" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" 982 | dependencies = [ 983 | "core-foundation-sys", 984 | "libc", 985 | ] 986 | 987 | [[package]] 988 | name = "serde" 989 | version = "1.0.203" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 992 | dependencies = [ 993 | "serde_derive", 994 | ] 995 | 996 | [[package]] 997 | name = "serde_derive" 998 | version = "1.0.203" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 1001 | dependencies = [ 1002 | "proc-macro2", 1003 | "quote", 1004 | "syn", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "serde_json" 1009 | version = "1.0.117" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" 1012 | dependencies = [ 1013 | "itoa", 1014 | "ryu", 1015 | "serde", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "serde_urlencoded" 1020 | version = "0.7.1" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1023 | dependencies = [ 1024 | "form_urlencoded", 1025 | "itoa", 1026 | "ryu", 1027 | "serde", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "sha-1" 1032 | version = "0.9.8" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" 1035 | dependencies = [ 1036 | "block-buffer", 1037 | "cfg-if", 1038 | "cpufeatures", 1039 | "digest", 1040 | "opaque-debug", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "signal-hook-registry" 1045 | version = "1.4.2" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1048 | dependencies = [ 1049 | "libc", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "slab" 1054 | version = "0.4.9" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1057 | dependencies = [ 1058 | "autocfg", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "smallvec" 1063 | version = "1.13.2" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1066 | 1067 | [[package]] 1068 | name = "socket2" 1069 | version = "0.5.7" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1072 | dependencies = [ 1073 | "libc", 1074 | "windows-sys 0.52.0", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "syn" 1079 | version = "2.0.66" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 1082 | dependencies = [ 1083 | "proc-macro2", 1084 | "quote", 1085 | "unicode-ident", 1086 | ] 1087 | 1088 | [[package]] 1089 | name = "sync_wrapper" 1090 | version = "0.1.2" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1093 | 1094 | [[package]] 1095 | name = "system-configuration" 1096 | version = "0.5.1" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1099 | dependencies = [ 1100 | "bitflags 1.3.2", 1101 | "core-foundation", 1102 | "system-configuration-sys", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "system-configuration-sys" 1107 | version = "0.5.0" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1110 | dependencies = [ 1111 | "core-foundation-sys", 1112 | "libc", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "tempfile" 1117 | version = "3.10.1" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 1120 | dependencies = [ 1121 | "cfg-if", 1122 | "fastrand", 1123 | "rustix", 1124 | "windows-sys 0.52.0", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "termcolor" 1129 | version = "1.4.1" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1132 | dependencies = [ 1133 | "winapi-util", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "thiserror" 1138 | version = "1.0.61" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 1141 | dependencies = [ 1142 | "thiserror-impl", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "thiserror-impl" 1147 | version = "1.0.61" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 1150 | dependencies = [ 1151 | "proc-macro2", 1152 | "quote", 1153 | "syn", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "tiktoklive" 1158 | version = "0.0.18" 1159 | dependencies = [ 1160 | "bytes", 1161 | "env_logger", 1162 | "futures-util", 1163 | "log", 1164 | "protobuf", 1165 | "protobuf-codegen", 1166 | "protoc-bin-vendored", 1167 | "reqwest", 1168 | "serde", 1169 | "serde_derive", 1170 | "serde_json", 1171 | "tokio", 1172 | "tokio-tungstenite", 1173 | "tungstenite", 1174 | "url", 1175 | "urlencoding", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "tinyvec" 1180 | version = "1.6.0" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1183 | dependencies = [ 1184 | "tinyvec_macros", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "tinyvec_macros" 1189 | version = "0.1.1" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1192 | 1193 | [[package]] 1194 | name = "tokio" 1195 | version = "1.38.0" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" 1198 | dependencies = [ 1199 | "backtrace", 1200 | "bytes", 1201 | "libc", 1202 | "mio", 1203 | "num_cpus", 1204 | "parking_lot", 1205 | "pin-project-lite", 1206 | "signal-hook-registry", 1207 | "socket2", 1208 | "tokio-macros", 1209 | "windows-sys 0.48.0", 1210 | ] 1211 | 1212 | [[package]] 1213 | name = "tokio-macros" 1214 | version = "2.3.0" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" 1217 | dependencies = [ 1218 | "proc-macro2", 1219 | "quote", 1220 | "syn", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "tokio-native-tls" 1225 | version = "0.3.1" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1228 | dependencies = [ 1229 | "native-tls", 1230 | "tokio", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "tokio-tungstenite" 1235 | version = "0.16.1" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "e80b39df6afcc12cdf752398ade96a6b9e99c903dfdc36e53ad10b9c366bca72" 1238 | dependencies = [ 1239 | "futures-util", 1240 | "log", 1241 | "native-tls", 1242 | "tokio", 1243 | "tokio-native-tls", 1244 | "tungstenite", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "tokio-util" 1249 | version = "0.7.11" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" 1252 | dependencies = [ 1253 | "bytes", 1254 | "futures-core", 1255 | "futures-sink", 1256 | "pin-project-lite", 1257 | "tokio", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "tower-service" 1262 | version = "0.3.2" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1265 | 1266 | [[package]] 1267 | name = "tracing" 1268 | version = "0.1.40" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1271 | dependencies = [ 1272 | "pin-project-lite", 1273 | "tracing-core", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "tracing-core" 1278 | version = "0.1.32" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1281 | dependencies = [ 1282 | "once_cell", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "try-lock" 1287 | version = "0.2.5" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1290 | 1291 | [[package]] 1292 | name = "tungstenite" 1293 | version = "0.16.0" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" 1296 | dependencies = [ 1297 | "base64 0.13.1", 1298 | "byteorder", 1299 | "bytes", 1300 | "http", 1301 | "httparse", 1302 | "log", 1303 | "native-tls", 1304 | "rand", 1305 | "sha-1", 1306 | "thiserror", 1307 | "url", 1308 | "utf-8", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "typenum" 1313 | version = "1.17.0" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1316 | 1317 | [[package]] 1318 | name = "unicode-bidi" 1319 | version = "0.3.15" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1322 | 1323 | [[package]] 1324 | name = "unicode-ident" 1325 | version = "1.0.12" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1328 | 1329 | [[package]] 1330 | name = "unicode-normalization" 1331 | version = "0.1.23" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1334 | dependencies = [ 1335 | "tinyvec", 1336 | ] 1337 | 1338 | [[package]] 1339 | name = "url" 1340 | version = "2.5.0" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 1343 | dependencies = [ 1344 | "form_urlencoded", 1345 | "idna", 1346 | "percent-encoding", 1347 | ] 1348 | 1349 | [[package]] 1350 | name = "urlencoding" 1351 | version = "2.1.3" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 1354 | 1355 | [[package]] 1356 | name = "utf-8" 1357 | version = "0.7.6" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1360 | 1361 | [[package]] 1362 | name = "vcpkg" 1363 | version = "0.2.15" 1364 | source = "registry+https://github.com/rust-lang/crates.io-index" 1365 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1366 | 1367 | [[package]] 1368 | name = "version_check" 1369 | version = "0.9.4" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1372 | 1373 | [[package]] 1374 | name = "want" 1375 | version = "0.3.1" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1378 | dependencies = [ 1379 | "try-lock", 1380 | ] 1381 | 1382 | [[package]] 1383 | name = "wasi" 1384 | version = "0.11.0+wasi-snapshot-preview1" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1387 | 1388 | [[package]] 1389 | name = "wasm-bindgen" 1390 | version = "0.2.92" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1393 | dependencies = [ 1394 | "cfg-if", 1395 | "wasm-bindgen-macro", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "wasm-bindgen-backend" 1400 | version = "0.2.92" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1403 | dependencies = [ 1404 | "bumpalo", 1405 | "log", 1406 | "once_cell", 1407 | "proc-macro2", 1408 | "quote", 1409 | "syn", 1410 | "wasm-bindgen-shared", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "wasm-bindgen-futures" 1415 | version = "0.4.42" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 1418 | dependencies = [ 1419 | "cfg-if", 1420 | "js-sys", 1421 | "wasm-bindgen", 1422 | "web-sys", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "wasm-bindgen-macro" 1427 | version = "0.2.92" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1430 | dependencies = [ 1431 | "quote", 1432 | "wasm-bindgen-macro-support", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "wasm-bindgen-macro-support" 1437 | version = "0.2.92" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1440 | dependencies = [ 1441 | "proc-macro2", 1442 | "quote", 1443 | "syn", 1444 | "wasm-bindgen-backend", 1445 | "wasm-bindgen-shared", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "wasm-bindgen-shared" 1450 | version = "0.2.92" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1453 | 1454 | [[package]] 1455 | name = "web-sys" 1456 | version = "0.3.69" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 1459 | dependencies = [ 1460 | "js-sys", 1461 | "wasm-bindgen", 1462 | ] 1463 | 1464 | [[package]] 1465 | name = "which" 1466 | version = "4.4.2" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 1469 | dependencies = [ 1470 | "either", 1471 | "home", 1472 | "once_cell", 1473 | "rustix", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "winapi-util" 1478 | version = "0.1.8" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 1481 | dependencies = [ 1482 | "windows-sys 0.52.0", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "windows-sys" 1487 | version = "0.48.0" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1490 | dependencies = [ 1491 | "windows-targets 0.48.5", 1492 | ] 1493 | 1494 | [[package]] 1495 | name = "windows-sys" 1496 | version = "0.52.0" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1499 | dependencies = [ 1500 | "windows-targets 0.52.5", 1501 | ] 1502 | 1503 | [[package]] 1504 | name = "windows-targets" 1505 | version = "0.48.5" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1508 | dependencies = [ 1509 | "windows_aarch64_gnullvm 0.48.5", 1510 | "windows_aarch64_msvc 0.48.5", 1511 | "windows_i686_gnu 0.48.5", 1512 | "windows_i686_msvc 0.48.5", 1513 | "windows_x86_64_gnu 0.48.5", 1514 | "windows_x86_64_gnullvm 0.48.5", 1515 | "windows_x86_64_msvc 0.48.5", 1516 | ] 1517 | 1518 | [[package]] 1519 | name = "windows-targets" 1520 | version = "0.52.5" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 1523 | dependencies = [ 1524 | "windows_aarch64_gnullvm 0.52.5", 1525 | "windows_aarch64_msvc 0.52.5", 1526 | "windows_i686_gnu 0.52.5", 1527 | "windows_i686_gnullvm", 1528 | "windows_i686_msvc 0.52.5", 1529 | "windows_x86_64_gnu 0.52.5", 1530 | "windows_x86_64_gnullvm 0.52.5", 1531 | "windows_x86_64_msvc 0.52.5", 1532 | ] 1533 | 1534 | [[package]] 1535 | name = "windows_aarch64_gnullvm" 1536 | version = "0.48.5" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1539 | 1540 | [[package]] 1541 | name = "windows_aarch64_gnullvm" 1542 | version = "0.52.5" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 1545 | 1546 | [[package]] 1547 | name = "windows_aarch64_msvc" 1548 | version = "0.48.5" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1551 | 1552 | [[package]] 1553 | name = "windows_aarch64_msvc" 1554 | version = "0.52.5" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 1557 | 1558 | [[package]] 1559 | name = "windows_i686_gnu" 1560 | version = "0.48.5" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1563 | 1564 | [[package]] 1565 | name = "windows_i686_gnu" 1566 | version = "0.52.5" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 1569 | 1570 | [[package]] 1571 | name = "windows_i686_gnullvm" 1572 | version = "0.52.5" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 1575 | 1576 | [[package]] 1577 | name = "windows_i686_msvc" 1578 | version = "0.48.5" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1581 | 1582 | [[package]] 1583 | name = "windows_i686_msvc" 1584 | version = "0.52.5" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 1587 | 1588 | [[package]] 1589 | name = "windows_x86_64_gnu" 1590 | version = "0.48.5" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1593 | 1594 | [[package]] 1595 | name = "windows_x86_64_gnu" 1596 | version = "0.52.5" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 1599 | 1600 | [[package]] 1601 | name = "windows_x86_64_gnullvm" 1602 | version = "0.48.5" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1605 | 1606 | [[package]] 1607 | name = "windows_x86_64_gnullvm" 1608 | version = "0.52.5" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 1611 | 1612 | [[package]] 1613 | name = "windows_x86_64_msvc" 1614 | version = "0.48.5" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1617 | 1618 | [[package]] 1619 | name = "windows_x86_64_msvc" 1620 | version = "0.52.5" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 1623 | 1624 | [[package]] 1625 | name = "winreg" 1626 | version = "0.50.0" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 1629 | dependencies = [ 1630 | "cfg-if", 1631 | "windows-sys 0.48.0", 1632 | ] 1633 | --------------------------------------------------------------------------------