From e5b1dde0a8709e60606c0cfc21e750bed8a10cb3 Mon Sep 17 00:00:00 2001 From: MaxRodin Date: Tue, 16 Apr 2024 22:22:54 -0700 Subject: [PATCH 01/10] add RunAsync method --- TikTokLiveSharp/Client/TikTokBaseClient.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/TikTokLiveSharp/Client/TikTokBaseClient.cs b/TikTokLiveSharp/Client/TikTokBaseClient.cs index b3b55c7..7f1b620 100644 --- a/TikTokLiveSharp/Client/TikTokBaseClient.cs +++ b/TikTokLiveSharp/Client/TikTokBaseClient.cs @@ -339,6 +339,23 @@ public void Run(CancellationToken? cancellationToken = null, Action o pollingTask.Wait(cancellationToken.Value); } + /// + /// Asynchronously Creates Threads for & Runs Connection with TikTokServers + /// + /// Token used to Cancel Client + /// Callback for Errors during Exception + /// Whether to Retry connections that might be recoverable + public async Task RunAsync(CancellationToken? cancellationToken = null, Action onConnectException = null, bool retryConnection = false) + { + token = cancellationToken ?? new CancellationToken(); + token.ThrowIfCancellationRequested(); + if (ShouldLog(LogLevel.Information)) + Debug.Log("Starting Threads"); + await Start(token, onConnectException, retryConnection); + await runningTask; + await pollingTask; + } + /// /// Starts Connection with TikTokServers /// From 7fe3e1fb22f708b91309abc4e22ea42ea6c3d594 Mon Sep 17 00:00:00 2001 From: MaxRodin Date: Tue, 16 Apr 2024 22:38:24 -0700 Subject: [PATCH 02/10] adding --async flag to test application for RunAsync test. --- TikTokLiveSharp_TestApplication/Program.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/TikTokLiveSharp_TestApplication/Program.cs b/TikTokLiveSharp_TestApplication/Program.cs index 3c613b3..e9d9741 100644 --- a/TikTokLiveSharp_TestApplication/Program.cs +++ b/TikTokLiveSharp_TestApplication/Program.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using TikTokLiveSharp.Client; using TikTokLiveSharp.Events; @@ -7,8 +8,9 @@ namespace TikTokLiveSharpTestApplication { internal class Program { - static void Main(string[] args) + static async Task Main(string[] args) { + Console.WriteLine("Enter a username:"); TikTokLiveClient client = new TikTokLiveClient(Console.ReadLine(), ""); client.OnConnected += Client_OnConnected; @@ -23,9 +25,18 @@ static void Main(string[] args) client.OnLike += Client_OnLike; client.OnGiftMessage += Client_OnGiftMessage; client.OnEmoteChat += Client_OnEmote; - client.Run(new System.Threading.CancellationToken()); + + if (args.Length > 0 && args[0] == "--async") + { + await client.RunAsync(new System.Threading.CancellationToken()); + } + + else + { + client.Run(new System.Threading.CancellationToken()); + } } - + private static void Client_OnConnected(TikTokLiveClient sender, bool e) { SetConsoleColor(ConsoleColor.White); From 0901c3f6f7daf420b4171219f00236a3cf10b9b2 Mon Sep 17 00:00:00 2001 From: Frank van Hoof Date: Fri, 24 May 2024 20:30:58 +0200 Subject: [PATCH 03/10] Bugfix AlreadyConnectedException on Retry. Connecting was still true when retrying, meaning the retry would instantly fail. --- TikTokLiveSharp/Client/TikTokBaseClient.cs | 1 + TikTokLiveUnity/Runtime/TikTokLiveManager.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TikTokLiveSharp/Client/TikTokBaseClient.cs b/TikTokLiveSharp/Client/TikTokBaseClient.cs index 7f1b620..101c41d 100644 --- a/TikTokLiveSharp/Client/TikTokBaseClient.cs +++ b/TikTokLiveSharp/Client/TikTokBaseClient.cs @@ -390,6 +390,7 @@ public async Task Start(CancellationToken? cancellationToken = null, Act { if (ShouldLog(LogLevel.Information)) Debug.Log("Retrying"); + Connecting = false; await Task.Delay(TimeSpan.FromSeconds(settings.ReconnectInterval), cancellationToken.Value); return await Start(cancellationToken, onConnectException, true); } diff --git a/TikTokLiveUnity/Runtime/TikTokLiveManager.cs b/TikTokLiveUnity/Runtime/TikTokLiveManager.cs index 04c66a2..ed4aea5 100644 --- a/TikTokLiveUnity/Runtime/TikTokLiveManager.cs +++ b/TikTokLiveUnity/Runtime/TikTokLiveManager.cs @@ -848,7 +848,7 @@ public async Task Connect(string userId, string roomId, Action onConn try { await client.Start(tokenSource.Token, onConnectException, settings.RetryOnConnectionFailure); - if (ShouldLog(LogLevel.Verbose)) + if (ShouldLog(LogLevel.Verbose) && Connected) Debug.Log("Connected"); } catch (Exception e) From 5b7242905fb622f12a3903e48216c7c38994f9c0 Mon Sep 17 00:00:00 2001 From: Frank van Hoof Date: Fri, 24 May 2024 20:38:36 +0200 Subject: [PATCH 04/10] Add Websocket-Headers --- TikTokLiveSharp/Client/Config/Constants.cs | 19 +++++++++++++++++-- .../Client/HTTP/TikTokHttpRequest.cs | 2 +- .../Client/Socket/TikTokWebSocket.cs | 3 +++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/TikTokLiveSharp/Client/Config/Constants.cs b/TikTokLiveSharp/Client/Config/Constants.cs index e54e088..1cbad23 100644 --- a/TikTokLiveSharp/Client/Config/Constants.cs +++ b/TikTokLiveSharp/Client/Config/Constants.cs @@ -97,12 +97,27 @@ public static class Constants /// /// Default Headers for HTTP-Request /// - public static readonly IReadOnlyDictionary DEFAULT_REQUEST_HEADERS = new ReadOnlyDictionary(new Dictionary() + public static readonly IReadOnlyDictionary DEFAULT_HTTP_HEADERS = new ReadOnlyDictionary(new Dictionary() { { "Connection", "keep-alive" }, { "authority", "www.tiktok.com" }, { "Cache-Control", "no-cache" }, - { "Accept", "text/html,application/json,application/protobuf" }, + { "Accept", "text/html,application/json" }, + { "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" }, + { "Referer", "https://www.tiktok.com/" }, + { "Origin", "https://www.tiktok.com" }, + { "Accept-Language", "en-US,en; q=0.8" } + }); + + /// + /// Default Headers for Websocket-Client + /// + public static readonly IReadOnlyDictionary DEFAULT_SOCKET_HEADERS = new ReadOnlyDictionary(new Dictionary() + { + { "Connection", "keep-alive" }, + { "authority", "www.tiktok.com" }, + { "Cache-Control", "no-cache" }, + { "Accept", "application/protobuf" }, { "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" }, { "Referer", "https://www.tiktok.com/" }, { "Origin", "https://www.tiktok.com" }, diff --git a/TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs b/TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs index bb53d12..5f154b8 100644 --- a/TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs +++ b/TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs @@ -157,7 +157,7 @@ public TikTokHttpRequest(string url, bool enableCompression = true, bool useCook { Timeout = Timeout }; - foreach (KeyValuePair header in Constants.DEFAULT_REQUEST_HEADERS) + foreach (KeyValuePair header in Constants.DEFAULT_HTTP_HEADERS) client.DefaultRequestHeaders.Add(header.Key, header.Value); if (enableCompression) client.DefaultRequestHeaders.Add(Constants.COMPRESSION_HEADER.Key, Constants.COMPRESSION_HEADER.Value); diff --git a/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs b/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs index 14c2498..d8c6490 100644 --- a/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs +++ b/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using TikTokLiveSharp.Client.Config; using TikTokLiveSharp.Client.HTTP; namespace TikTokLiveSharp.Client.Socket @@ -79,6 +80,8 @@ public TikTokWebSocket(TikTokCookieJar cookieContainer, uint buffSize = 500_000, foreach (string additionalHeader in headers.Values) cookieHeader.Append(additionalHeader); clientWebSocket.Options.SetRequestHeader("Cookie", cookieHeader.ToString()); + foreach (KeyValuePair header in Constants.DEFAULT_SOCKET_HEADERS) + clientWebSocket.Options.SetRequestHeader(header.Key, header.Value); } #endregion From a05685039c9264c57c47ac80136c56912a13bb8e Mon Sep 17 00:00:00 2001 From: Frank van Hoof Date: Fri, 24 May 2024 20:40:08 +0200 Subject: [PATCH 05/10] Replace HTTPUtility with WebUtility for URL-encoding. (HttpUtility is not available in the 'Client Profile' version of .NET Framework v4.X) --- TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs | 3 +-- TikTokLiveSharp/Client/TikTokBaseClient.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs b/TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs index 5f154b8..6677153 100644 --- a/TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs +++ b/TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs @@ -5,7 +5,6 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; -using System.Web; using TikTokLiveSharp.Client.Config; namespace TikTokLiveSharp.Client.HTTP @@ -210,7 +209,7 @@ public ITikTokHttpRequest SetQueries(IDictionary queries) { if (queries == null) return this; - query = string.Join("&", queries.Select(x => $"{x.Key}={HttpUtility.UrlEncode(x.Value.ToString())}")); + query = string.Join("&", queries.Select(x => $"{x.Key}={WebUtility.UrlEncode(x.Value.ToString())}")); return this; } diff --git a/TikTokLiveSharp/Client/TikTokBaseClient.cs b/TikTokLiveSharp/Client/TikTokBaseClient.cs index 101c41d..0c094ed 100644 --- a/TikTokLiveSharp/Client/TikTokBaseClient.cs +++ b/TikTokLiveSharp/Client/TikTokBaseClient.cs @@ -12,7 +12,6 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using System.Web; using TikTokLiveSharp.Client.Config; using TikTokLiveSharp.Client.HTTP; using TikTokLiveSharp.Client.Socket; @@ -520,7 +519,7 @@ private async Task CreateWebSocket(TikTokWebSocketConnectionData connectionData) Debug.Log($"Adding Custom Param {param.Key}-{param.Value}"); clientParams[param.Key] = param.Value; } - string url = $"{response.PushServer}?{string.Join("&", clientParams.Select(x => $"{x.Key}={HttpUtility.UrlEncode(x.Value.ToString())}"))}"; + string url = $"{response.PushServer}?{string.Join("&", clientParams.Select(x => $"{x.Key}={WebUtility.UrlEncode(x.Value.ToString())}"))}"; if (ShouldLog(LogLevel.Verbose)) Debug.Log($"Creating Socket with URL {url}"); Dictionary customHeaders = new Dictionary From 3ac432f723a59ffa163e1f22a79c0be968c20d7f Mon Sep 17 00:00:00 2001 From: Frank van Hoof Date: Fri, 24 May 2024 21:12:34 +0200 Subject: [PATCH 06/10] Websocket-Header order (Default headers before Cookie, in case of any overwrites) --- TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs b/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs index d8c6490..ceb49e8 100644 --- a/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs +++ b/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs @@ -79,9 +79,9 @@ public TikTokWebSocket(TikTokCookieJar cookieContainer, uint buffSize = 500_000, cookieHeader.Append(cookie); foreach (string additionalHeader in headers.Values) cookieHeader.Append(additionalHeader); - clientWebSocket.Options.SetRequestHeader("Cookie", cookieHeader.ToString()); foreach (KeyValuePair header in Constants.DEFAULT_SOCKET_HEADERS) clientWebSocket.Options.SetRequestHeader(header.Key, header.Value); + clientWebSocket.Options.SetRequestHeader("Cookie", cookieHeader.ToString()); } #endregion From d0586b55190d3a1c7f0569173bcc9884484f7007 Mon Sep 17 00:00:00 2001 From: Frank van Hoof Date: Fri, 24 May 2024 22:47:16 +0200 Subject: [PATCH 07/10] Add settings for custom signing server & api-key --- .../Client/Config/ClientSettings.cs | 19 +++++++++++++ .../Client/HTTP/TikTokHTTPClient.cs | 27 ++++++++++++------- TikTokLiveSharp/Client/TikTokBaseClient.cs | 10 ++++--- TikTokLiveSharp/Client/TikTokLiveClient.cs | 8 ++++-- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/TikTokLiveSharp/Client/Config/ClientSettings.cs b/TikTokLiveSharp/Client/Config/ClientSettings.cs index b7e4f22..c094071 100644 --- a/TikTokLiveSharp/Client/Config/ClientSettings.cs +++ b/TikTokLiveSharp/Client/Config/ClientSettings.cs @@ -104,6 +104,25 @@ public struct ClientSettings public bool DownloadGiftInfo; #endregion + #region Signing + /// + /// API-Key for Signing Server + /// +#if UNITY + [UnityEngine.Header("Signing-Server")] + [UnityEngine.Tooltip("API-Key for Signing Server")] +#endif + public string SigningKey; + + /// + /// Custom URL for Signing Server + /// +#if UNITY + [UnityEngine.Tooltip("Custom URL for Signing Server")] +#endif + public string CustomSigningServerUrl; + #endregion + #region Debug /// /// Whether to print Logs to Console diff --git a/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs b/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs index 23700fd..b25564b 100644 --- a/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs +++ b/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs @@ -118,15 +118,20 @@ internal async Task GetProfilePage(string uniqueId, IDictionary GetSignedWebsocketData(string roomId) + internal async Task GetSignedWebsocketData(string roomId, string customServerUrl = null, string apiKey = null) { - ITikTokHttpRequest request = new TikTokHttpRequest(Constants.TIKTOK_SIGN_API, false, false) - .SetQueries(new Dictionary() - { - { "client", CLIENT_NAME }, - { "uuc", clientNum }, - { "room_id", roomId } - }); + Dictionary queries = new Dictionary() + { + { "client", CLIENT_NAME }, + { "uuc", clientNum }, + { "room_id", roomId }, + }; + if (!string.IsNullOrEmpty(apiKey)) + { + queries.Add("authorization", apiKey); + } + ITikTokHttpRequest request = new TikTokHttpRequest(string.IsNullOrEmpty(customServerUrl) ? Constants.TIKTOK_SIGN_API : customServerUrl, false, false) + .SetQueries(queries); HttpResponseMessage response = await request.GetResponse(); if (response.StatusCode == HttpStatusCode.NotFound) throw new HttpRequestException("Request responded with 404 NOT_FOUND"); @@ -144,9 +149,13 @@ internal async Task GetSignedWebsocketData(string throw new HttpRequestException($"[{(int)response.StatusCode}] Rate Limit Reached."); } } + else if ((int)response.StatusCode == 502) // Bad Gateway + { + throw new HttpRequestException($"[{(int)response.StatusCode}] Signing Server not reachable."); + } else { - throw new HttpRequestException($"Request was unsuccessful [{(int)response.StatusCode}]."); + throw new HttpRequestException($"Signing request was unsuccessful [{(int)response.StatusCode}]."); } } if (response.Headers.TryGetValues("x-set-tt-cookie", out IEnumerable cookieHeaders)) diff --git a/TikTokLiveSharp/Client/TikTokBaseClient.cs b/TikTokLiveSharp/Client/TikTokBaseClient.cs index 0c094ed..72b2221 100644 --- a/TikTokLiveSharp/Client/TikTokBaseClient.cs +++ b/TikTokLiveSharp/Client/TikTokBaseClient.cs @@ -196,7 +196,9 @@ protected TikTokBaseClient(string uniqueId, bool logDebug = true, LogLevel logLevel = LogLevel.Error | LogLevel.Warning, bool printMessageData = false, - bool checkForUnparsedData = false) + bool checkForUnparsedData = false, + string customSigningServer = null, + string signingServerApiKey = null) : this(uniqueId, roomId, new ClientSettings { @@ -213,7 +215,9 @@ protected TikTokBaseClient(string uniqueId, PrintToConsole = logDebug, LogLevel = logLevel, PrintMessageData = printMessageData, - CheckForUnparsedData = checkForUnparsedData + CheckForUnparsedData = checkForUnparsedData, + CustomSigningServerUrl = customSigningServer, + SigningKey = signingServerApiKey }, clientParams) { } @@ -483,7 +487,7 @@ protected virtual async Task Connect(Action onConnectExceptio token.ThrowIfCancellationRequested(); if (ShouldLog(LogLevel.Verbose)) Debug.Log("Fetch ConnectionData"); - TikTokWebSocketConnectionData connectionData = await httpClient.GetSignedWebsocketData(RoomID); + TikTokWebSocketConnectionData connectionData = await httpClient.GetSignedWebsocketData(RoomID, settings.CustomSigningServerUrl, settings.SigningKey); token.ThrowIfCancellationRequested(); if (ShouldLog(LogLevel.Information)) Debug.Log("Creating WebSocketClient"); diff --git a/TikTokLiveSharp/Client/TikTokLiveClient.cs b/TikTokLiveSharp/Client/TikTokLiveClient.cs index 419231d..65ca3e5 100644 --- a/TikTokLiveSharp/Client/TikTokLiveClient.cs +++ b/TikTokLiveSharp/Client/TikTokLiveClient.cs @@ -297,7 +297,9 @@ public TikTokLiveClient(string uniqueID, bool logDebug = true, LogLevel logLevel = LogLevel.Error | LogLevel.Warning, bool printMessageData = false, - bool checkForUnparsedData = false) + bool checkForUnparsedData = false, + string customSigningServer = null, + string signingServerApiKey = null) : base(uniqueID, timeout, reconnectInterval, @@ -314,7 +316,9 @@ public TikTokLiveClient(string uniqueID, logDebug, logLevel, printMessageData, - checkForUnparsedData) + checkForUnparsedData, + customSigningServer, + signingServerApiKey) { } #endregion From f8c6282d63afde6fbb32d93336422d2994f81bf9 Mon Sep 17 00:00:00 2001 From: Frank van Hoof Date: Sat, 25 May 2024 00:40:42 +0200 Subject: [PATCH 08/10] Additional StatusCode handling for Signing-Server --- TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs b/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs index b25564b..9630ddc 100644 --- a/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs +++ b/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs @@ -128,7 +128,7 @@ internal async Task GetSignedWebsocketData(string }; if (!string.IsNullOrEmpty(apiKey)) { - queries.Add("authorization", apiKey); + queries.Add("apiKey", apiKey); } ITikTokHttpRequest request = new TikTokHttpRequest(string.IsNullOrEmpty(customServerUrl) ? Constants.TIKTOK_SIGN_API : customServerUrl, false, false) .SetQueries(queries); @@ -142,17 +142,21 @@ internal async Task GetSignedWebsocketData(string if (response.Headers.TryGetValues("RateLimit-Reset", out IEnumerable rateHeaders)) { TimeSpan span = TimeSpan.FromSeconds(long.Parse(rateHeaders.First())); - throw new HttpRequestException($"[{(int)response.StatusCode}] Rate Limit Reached. Try again in {span:mm\\:ss}."); + throw new HttpRequestException($"[{(int)response.StatusCode}] Signing Rate Limit Reached. Try again in {span:mm\\:ss}."); } else { - throw new HttpRequestException($"[{(int)response.StatusCode}] Rate Limit Reached."); + throw new HttpRequestException($"[{(int)response.StatusCode}] Signing Rate Limit Reached."); } } else if ((int)response.StatusCode == 502) // Bad Gateway { throw new HttpRequestException($"[{(int)response.StatusCode}] Signing Server not reachable."); } + else if ((int)response.StatusCode == 503) // Unavailable + { + throw new HttpRequestException($"[{(int)response.StatusCode}] Signing Server unavailable."); + } else { throw new HttpRequestException($"Signing request was unsuccessful [{(int)response.StatusCode}]."); From 7b54fa82da1d3825480d46d570229ff4c845f89c Mon Sep 17 00:00:00 2001 From: Frank van Hoof Date: Tue, 28 May 2024 22:49:22 +0200 Subject: [PATCH 09/10] Bugfix for CookieHeader on WebSocket-Client --- TikTokLiveSharp/Client/Config/Constants.cs | 92 ++++++++++--------- .../Client/HTTP/TikTokCookieJar.cs | 5 + .../Client/HTTP/TikTokHTTPClient.cs | 2 +- .../Client/Socket/TikTokWebSocket.cs | 13 ++- .../Socket/TikTokWebSocketConnectionData.cs | 24 +++-- TikTokLiveSharp/Client/TikTokBaseClient.cs | 20 ++-- 6 files changed, 92 insertions(+), 64 deletions(-) diff --git a/TikTokLiveSharp/Client/Config/Constants.cs b/TikTokLiveSharp/Client/Config/Constants.cs index 1cbad23..12aae61 100644 --- a/TikTokLiveSharp/Client/Config/Constants.cs +++ b/TikTokLiveSharp/Client/Config/Constants.cs @@ -11,16 +11,19 @@ public static class Constants /// /// Web-URL for TikTok /// - public const string TIKTOK_URL_WEB = "https://www.tiktok.com/"; + public const string TIKTOK_URL_WEB = @"https://www.tiktok.com/"; /// /// WebCast-BaseURL for TikTok /// - public const string TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/"; + public const string TIKTOK_URL_WEBCAST = @"https://webcast.tiktok.com/webcast/"; /// /// Signing API by Isaac Kogan /// - public const string TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/fetch"; - + public const string TIKTOK_SIGN_API = @"https://tiktok.eulerstream.com/webcast/fetch"; + /// + /// User-Agent for WebClients (Http/WebSocket) + /// + public const string USER_AGENT = "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36"; /// /// Default TimeOut for Connections /// @@ -62,36 +65,36 @@ public static class Constants public static readonly IReadOnlyDictionary DEFAULT_CLIENT_PARAMS = new ReadOnlyDictionary(new Dictionary() { { "aid", 1988 }, - { "app_language", "en-US" }, - { "app_name", "tiktok_web" }, - { "browser_language", "en-US" }, - { "browser_name", "Mozilla" }, - { "browser_online", true }, - { "browser_platform", "Win32" }, - { "browser_version", "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36" }, - { "cookie_enabled", true }, - { "cursor", "" }, - { "internal_ext", "" }, - { "device_platform", "web" }, - { "focus_state", true }, - { "from_page", "user" }, - { "history_len", 4 }, - { "is_fullscreen", false }, - { "is_page_visible", true }, - { "did_rule", 3 }, - { "fetch_rule", 1 }, - { "last_rtt", 0 }, - { "live_id", 12 }, - { "resp_content_type", "protobuf" }, - { "screen_height", 1152 }, - { "screen_width", 2048 }, - { "tz_name", "Europe/London" }, - { "referer", "https, //www.tiktok.com/" }, - { "root_referer", "https, //www.tiktok.com/" }, + { "app_language", "en-US" }, + { "app_name", "tiktok_web" }, + { "browser_language", "en" }, + { "browser_name", "Mozilla" }, + { "browser_online", true }, + { "browser_platform", "Win32" }, + { "browser_version", USER_AGENT }, + { "cookie_enabled", true }, + { "cursor", "" }, + { "internal_ext", "" }, + { "device_platform", "web" }, + { "focus_state", true }, + { "from_page", "user" }, + { "history_len", 4 }, + { "is_fullscreen", false }, + { "is_page_visible", true }, + { "did_rule", 3 }, + { "fetch_rule", 1 }, + { "last_rtt", 0 }, + { "live_id", 12 }, + { "resp_content_type", "protobuf" }, + { "screen_height", 1080 }, + { "screen_width", 1920 }, + { "tz_name", "Europe/Berlin" }, + { "referer", "https, //www.tiktok.com/" }, + { "root_referer", "https, //www.tiktok.com/" }, { "msToken", "" }, - { "version_code", 180800 }, - { "webcast_sdk_version", "1.3.0" }, - { "update_version_code", "1.3.0" } + { "version_code", 180800 }, + { "webcast_sdk_version", "1.3.0" }, + { "update_version_code", "1.3.0" } }); /// @@ -102,11 +105,11 @@ public static class Constants { "Connection", "keep-alive" }, { "authority", "www.tiktok.com" }, { "Cache-Control", "no-cache" }, - { "Accept", "text/html,application/json" }, - { "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" }, - { "Referer", "https://www.tiktok.com/" }, - { "Origin", "https://www.tiktok.com" }, - { "Accept-Language", "en-US,en; q=0.8" } + { "Accept", "text/html,application/json,application/protobuf" }, + { "User-Agent", USER_AGENT }, + { "Referer", TIKTOK_URL_WEB }, + { "Origin", TIKTOK_URL_WEB }, + { "Accept-Language", "en; q=0.8" } }); /// @@ -114,14 +117,13 @@ public static class Constants /// public static readonly IReadOnlyDictionary DEFAULT_SOCKET_HEADERS = new ReadOnlyDictionary(new Dictionary() { - { "Connection", "keep-alive" }, { "authority", "www.tiktok.com" }, - { "Cache-Control", "no-cache" }, - { "Accept", "application/protobuf" }, - { "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" }, - { "Referer", "https://www.tiktok.com/" }, - { "Origin", "https://www.tiktok.com" }, - { "Accept-Language", "en-US,en; q=0.8" } + { "Cache-Control", "max-age=0" }, + { "Accept", "text/html,application/json,application/protobuf" }, + { "User-Agent", USER_AGENT }, + { "Referer", TIKTOK_URL_WEB }, + { "Origin", TIKTOK_URL_WEB }, + { "Accept-Language", "en; q=0.8" } }); public static readonly KeyValuePair COMPRESSION_HEADER = new KeyValuePair( "Accept-Encoding", "gzip, deflate" ); diff --git a/TikTokLiveSharp/Client/HTTP/TikTokCookieJar.cs b/TikTokLiveSharp/Client/HTTP/TikTokCookieJar.cs index 143ea17..663d343 100644 --- a/TikTokLiveSharp/Client/HTTP/TikTokCookieJar.cs +++ b/TikTokLiveSharp/Client/HTTP/TikTokCookieJar.cs @@ -26,6 +26,11 @@ public string this[string key] /// public int Count => cookies?.Count ?? 0; + /// + /// Cookies in Jar + /// + public IDictionary Cookies => cookies; + /// /// Cookies in Jar /// diff --git a/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs b/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs index 9630ddc..d92ab33 100644 --- a/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs +++ b/TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs @@ -167,7 +167,7 @@ internal async Task GetSignedWebsocketData(string try { Response signingResponse = Serializer.Deserialize(await response.Content.ReadAsStreamAsync()); - return new TikTokWebSocketConnectionData(roomId, cookieHeaders.First(), signingResponse); + return new TikTokWebSocketConnectionData(roomId, cookieHeaders, signingResponse); } catch (Exception ex) { diff --git a/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs b/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs index ceb49e8..27e48bf 100644 --- a/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs +++ b/TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs @@ -74,11 +74,16 @@ public TikTokWebSocket(TikTokCookieJar cookieContainer, uint buffSize = 500_000, clientWebSocket.Options.Proxy = webProxy; clientWebSocket.Options.AddSubProtocol("echo-protocol"); clientWebSocket.Options.KeepAliveInterval = TimeSpan.Zero; + if (headers.ContainsKey("ttwid")) + cookieContainer["ttwid"] = headers["ttwid"]; + Dictionary combinedCookies = new Dictionary(cookieContainer.Cookies); + foreach (KeyValuePair overrideCookie in headers) + { + combinedCookies[overrideCookie.Key] = overrideCookie.Value; + } StringBuilder cookieHeader = new StringBuilder(cookieContainer.Count * 20); - foreach (string cookie in cookieContainer) - cookieHeader.Append(cookie); - foreach (string additionalHeader in headers.Values) - cookieHeader.Append(additionalHeader); + foreach (KeyValuePair cookie in combinedCookies) + cookieHeader.Append($"{cookie.Key}={cookie.Value};"); foreach (KeyValuePair header in Constants.DEFAULT_SOCKET_HEADERS) clientWebSocket.Options.SetRequestHeader(header.Key, header.Value); clientWebSocket.Options.SetRequestHeader("Cookie", cookieHeader.ToString()); diff --git a/TikTokLiveSharp/Client/Socket/TikTokWebSocketConnectionData.cs b/TikTokLiveSharp/Client/Socket/TikTokWebSocketConnectionData.cs index d0057c7..6077a0f 100644 --- a/TikTokLiveSharp/Client/Socket/TikTokWebSocketConnectionData.cs +++ b/TikTokLiveSharp/Client/Socket/TikTokWebSocketConnectionData.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using TikTokLiveSharp.Models.Protobuf.Messages; namespace TikTokLiveSharp.Client.Socket @@ -12,25 +14,33 @@ public readonly struct TikTokWebSocketConnectionData /// public readonly string RoomId; /// - /// Required Cookies obtained from signing-server Headers - /// - public readonly string WebSocketCookies; - /// /// TikTokServer-Response obtained from signing-server /// public readonly Response InitialWebcastResponse; + /// + /// Headers required for Websocket-Connection + /// + public readonly Dictionary CookieHeaders; /// /// Creates instance of TikTokWebSocketConnectionData /// /// RoomID for stream to connect to - /// Required Cookies obtained from signing-server Headers /// TikTokServer-Response obtained from signing-server - public TikTokWebSocketConnectionData(string roomId, string cookies, Response initialResponse) + public TikTokWebSocketConnectionData(string roomId, IEnumerable cookies, Response initialResponse) { RoomId = roomId; - WebSocketCookies = cookies; InitialWebcastResponse = initialResponse; + CookieHeaders = new Dictionary(cookies.Count()); + foreach (string val in cookies) + { + string[] cookieString = val.Split(';', System.StringSplitOptions.RemoveEmptyEntries); + foreach (string cookie in cookieString) + { + string[] parsed = cookie.Split('='); + CookieHeaders.Add(parsed[0], parsed[1]); + } + } } } } diff --git a/TikTokLiveSharp/Client/TikTokBaseClient.cs b/TikTokLiveSharp/Client/TikTokBaseClient.cs index 72b2221..c13781c 100644 --- a/TikTokLiveSharp/Client/TikTokBaseClient.cs +++ b/TikTokLiveSharp/Client/TikTokBaseClient.cs @@ -9,6 +9,7 @@ using System.Net.Http; using System.Net.WebSockets; using System.Runtime.CompilerServices; +using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -456,6 +457,8 @@ protected virtual async Task Connect(Action onConnectExceptio if (ShouldLog(LogLevel.Verbose)) Debug.Log($"Fetching RoomID based on HostId {HostName}"); RoomID = await FetchRoomId(); + if (ShouldLog(LogLevel.Verbose)) + Debug.Log($"Found RoomId {RoomID} for Host {HostName}"); } token.ThrowIfCancellationRequested(); if (!settings.SkipRoomInfo) @@ -486,7 +489,7 @@ protected virtual async Task Connect(Action onConnectExceptio } token.ThrowIfCancellationRequested(); if (ShouldLog(LogLevel.Verbose)) - Debug.Log("Fetch ConnectionData"); + Debug.Log("Signing Connection"); TikTokWebSocketConnectionData connectionData = await httpClient.GetSignedWebsocketData(RoomID, settings.CustomSigningServerUrl, settings.SigningKey); token.ThrowIfCancellationRequested(); if (ShouldLog(LogLevel.Information)) @@ -526,11 +529,7 @@ private async Task CreateWebSocket(TikTokWebSocketConnectionData connectionData) string url = $"{response.PushServer}?{string.Join("&", clientParams.Select(x => $"{x.Key}={WebUtility.UrlEncode(x.Value.ToString())}"))}"; if (ShouldLog(LogLevel.Verbose)) Debug.Log($"Creating Socket with URL {url}"); - Dictionary customHeaders = new Dictionary - { - { "Cookie", connectionData.WebSocketCookies } - }; - socketClient = new TikTokWebSocket(TikTokHttpRequest.CookieJar, settings.SocketBufferSize, customHeaders, token, settings.Proxy); + socketClient = new TikTokWebSocket(TikTokHttpRequest.CookieJar, settings.SocketBufferSize, connectionData.CookieHeaders, token, settings.Proxy); connectedSocketUrl = url; await socketClient.Connect(url); if (ShouldLog(LogLevel.Information)) @@ -853,7 +852,14 @@ private async Task CheckSocketConnection() // Reconnect with new SocketClient Dictionary customHeaders = new Dictionary(); if (connectionData.HasValue) - customHeaders.Add("Cookie", connectionData.Value.WebSocketCookies); + { + StringBuilder cookieString = new StringBuilder(); + foreach (KeyValuePair cookie in connectionData.Value.CookieHeaders) + { + cookieString.Append($"{cookie.Key}={cookie.Value};"); + } + customHeaders.Add("Cookie", cookieString.ToString()); + } socketClient = new TikTokWebSocket(TikTokHttpRequest.CookieJar, settings.SocketBufferSize, customHeaders, token, settings.Proxy); await socketClient.Connect(connectedSocketUrl); } From dfb7a112697317f45a39f6a33199ca1d8d92566d Mon Sep 17 00:00:00 2001 From: Frank van Hoof Date: Tue, 28 May 2024 22:58:09 +0200 Subject: [PATCH 10/10] Update versioning for release --- CHANGELOG.MD | 19 +++++++++++++++++++ README.md | 2 +- Setup_CSharp.MD | 2 +- Setup_Unity.MD | 2 +- TikTokLiveSharp/TikTokLiveSharp.csproj | 2 +- .../TikTokLiveSharpNetCoreWPFTestApp.csproj | 2 +- ...kTokLiveSharpNetFrameworkWPFTestApp.csproj | 2 +- .../TikTokLiveSharpWinFormsTestApp.csproj | 4 ++-- ...kTokLiveSharpConsoleTestApplication.csproj | 2 +- package.json | 2 +- 10 files changed, 29 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index e9f8801..9115a49 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.1] - Websocket Bugfix + +Bugfix for failing Websocket-Connection + +### Added + +- RunAsync for TTLiveSharp, for more control over Threads +- Settings for Custom Signing-Server & Signing-ApiKey +- Additional StatusCode-Messaging for Signing-Server + +### Fixed + +- Fixed incorrect Header(s) on WebSocket-Client +- Fixed AlreadyConnectedException when retrying a Connect() + +### Changed + +- Replaced HTTPUtility with WebUtility for URL-Encoding + ## [1.1.0] - Bugfix for failing/closing Websocket-Connections diff --git a/README.md b/README.md index 215c612..d54b776 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# TikTokLiveSharp / TikTokLiveUnity v1.1.0 +# TikTokLiveSharp / TikTokLiveUnity v1.1.1 [![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white&style=flat-square)](https://www.linkedin.com/in/frankvhoof93/ ) ![Issues](https://img.shields.io/github/issues/frankvHoof93/TikTokLiveSharp) diff --git a/Setup_CSharp.MD b/Setup_CSharp.MD index 719d669..dfc1f0a 100644 --- a/Setup_CSharp.MD +++ b/Setup_CSharp.MD @@ -1,4 +1,4 @@ -# TikTokLiveSharp v1.1.0 +# TikTokLiveSharp v1.1.1 ## Downloading the Source Code Download the TikTokLiveSharp-SourceCode from the [Releases-Page](https://github.com/frankvHoof93/TikTokLiveSharp/releases/) diff --git a/Setup_Unity.MD b/Setup_Unity.MD index a275f89..029fb08 100644 --- a/Setup_Unity.MD +++ b/Setup_Unity.MD @@ -1,4 +1,4 @@ -# TikTokLiveUnity v1.1.0 +# TikTokLiveUnity v1.1.1 ## Installing the Project in Unity - Open the Package Manager Window **(Window -> Package Manager)** diff --git a/TikTokLiveSharp/TikTokLiveSharp.csproj b/TikTokLiveSharp/TikTokLiveSharp.csproj index 9685d3a..e4e62f2 100644 --- a/TikTokLiveSharp/TikTokLiveSharp.csproj +++ b/TikTokLiveSharp/TikTokLiveSharp.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 1.1.0 + 1.1.1 Frank van Hoof True https://github.com/frankvHoof93/TikTokLiveSharp diff --git a/TikTokLiveSharpNetCoreWPFTestApp/TikTokLiveSharpNetCoreWPFTestApp.csproj b/TikTokLiveSharpNetCoreWPFTestApp/TikTokLiveSharpNetCoreWPFTestApp.csproj index 7d892da..65dd11d 100644 --- a/TikTokLiveSharpNetCoreWPFTestApp/TikTokLiveSharpNetCoreWPFTestApp.csproj +++ b/TikTokLiveSharpNetCoreWPFTestApp/TikTokLiveSharpNetCoreWPFTestApp.csproj @@ -15,7 +15,7 @@ Library TikTok;Live en - 1.1.0 + 1.1.1 diff --git a/TikTokLiveSharpNetFrameworkWPFTestApp/TikTokLiveSharpNetFrameworkWPFTestApp.csproj b/TikTokLiveSharpNetFrameworkWPFTestApp/TikTokLiveSharpNetFrameworkWPFTestApp.csproj index bbb7749..3f8c65e 100644 --- a/TikTokLiveSharpNetFrameworkWPFTestApp/TikTokLiveSharpNetFrameworkWPFTestApp.csproj +++ b/TikTokLiveSharpNetFrameworkWPFTestApp/TikTokLiveSharpNetFrameworkWPFTestApp.csproj @@ -26,7 +26,7 @@ false true 0 - 1.1.0.0 + 1.1.1.0 false true diff --git a/TikTokLiveSharpWinFormsTestApp/TikTokLiveSharpWinFormsTestApp.csproj b/TikTokLiveSharpWinFormsTestApp/TikTokLiveSharpWinFormsTestApp.csproj index 67e3b21..9076c09 100644 --- a/TikTokLiveSharpWinFormsTestApp/TikTokLiveSharpWinFormsTestApp.csproj +++ b/TikTokLiveSharpWinFormsTestApp/TikTokLiveSharpWinFormsTestApp.csproj @@ -12,6 +12,7 @@ 512 true true + false publish\ true Disk @@ -23,8 +24,7 @@ false true 0 - 1.1.0.0 - false + 1.1.1.0 false true diff --git a/TikTokLiveSharp_TestApplication/TikTokLiveSharpConsoleTestApplication.csproj b/TikTokLiveSharp_TestApplication/TikTokLiveSharpConsoleTestApplication.csproj index 9c8133c..9fa5b03 100644 --- a/TikTokLiveSharp_TestApplication/TikTokLiveSharpConsoleTestApplication.csproj +++ b/TikTokLiveSharp_TestApplication/TikTokLiveSharpConsoleTestApplication.csproj @@ -3,7 +3,7 @@ Exe netcoreapp3.1 - 1.1.0 + 1.1.1 Console-TestApp with .NET Core for TikTokLive_Sharp 2024 Frank van Hoof LICENSE.md diff --git a/package.json b/package.json index e8b00ff..83519b2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "dev.vanhoof.tiktokliveunity", "displayName": "TikTokLive Unity", "description": "TikTokLive-Client for Unity", - "version": "1.1.0", + "version": "1.1.1", "author": { "name": "Frank van Hoof", "email": "frank_van_hoof@hotmail.com",