Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V1.1.1 - Bugfix of Websocket-Client #83

Merged
merged 11 commits into from
May 28, 2024
19 changes: 19 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion Setup_CSharp.MD
Original file line number Diff line number Diff line change
@@ -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/)
Expand Down
2 changes: 1 addition & 1 deletion Setup_Unity.MD
Original file line number Diff line number Diff line change
@@ -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)**
Expand Down
19 changes: 19 additions & 0 deletions TikTokLiveSharp/Client/Config/ClientSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ public struct ClientSettings
public bool DownloadGiftInfo;
#endregion

#region Signing
/// <summary>
/// API-Key for Signing Server
/// </summary>
#if UNITY
[UnityEngine.Header("Signing-Server")]
[UnityEngine.Tooltip("API-Key for Signing Server")]
#endif
public string SigningKey;

/// <summary>
/// Custom URL for Signing Server
/// </summary>
#if UNITY
[UnityEngine.Tooltip("Custom URL for Signing Server")]
#endif
public string CustomSigningServerUrl;
#endregion

#region Debug
/// <summary>
/// Whether to print Logs to Console
Expand Down
93 changes: 55 additions & 38 deletions TikTokLiveSharp/Client/Config/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@ public static class Constants
/// <summary>
/// Web-URL for TikTok
/// </summary>
public const string TIKTOK_URL_WEB = "https://www.tiktok.com/";
public const string TIKTOK_URL_WEB = @"https://www.tiktok.com/";
/// <summary>
/// WebCast-BaseURL for TikTok
/// </summary>
public const string TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/";
public const string TIKTOK_URL_WEBCAST = @"https://webcast.tiktok.com/webcast/";
/// <summary>
/// Signing API by Isaac Kogan
/// </summary>
public const string TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/fetch";

public const string TIKTOK_SIGN_API = @"https://tiktok.eulerstream.com/webcast/fetch";
/// <summary>
/// User-Agent for WebClients (Http/WebSocket)
/// </summary>
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";
/// <summary>
/// Default TimeOut for Connections
/// </summary>
Expand Down Expand Up @@ -62,51 +65,65 @@ public static class Constants
public static readonly IReadOnlyDictionary<string, object> DEFAULT_CLIENT_PARAMS = new ReadOnlyDictionary<string, object>(new Dictionary<string, object>()
{
{ "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" }
});

/// <summary>
/// Default Headers for HTTP-Request
/// </summary>
public static readonly IReadOnlyDictionary<string, string> DEFAULT_REQUEST_HEADERS = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>()
public static readonly IReadOnlyDictionary<string, string> DEFAULT_HTTP_HEADERS = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>()
{
{ "Connection", "keep-alive" },
{ "authority", "www.tiktok.com" },
{ "Cache-Control", "no-cache" },
{ "Accept", "text/html,application/json,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" }
{ "User-Agent", USER_AGENT },
{ "Referer", TIKTOK_URL_WEB },
{ "Origin", TIKTOK_URL_WEB },
{ "Accept-Language", "en; q=0.8" }
});

/// <summary>
/// Default Headers for Websocket-Client
/// </summary>
public static readonly IReadOnlyDictionary<string, string> DEFAULT_SOCKET_HEADERS = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>()
{
{ "authority", "www.tiktok.com" },
{ "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<string, string> COMPRESSION_HEADER = new KeyValuePair<string, string>( "Accept-Encoding", "gzip, deflate" );
Expand Down
5 changes: 5 additions & 0 deletions TikTokLiveSharp/Client/HTTP/TikTokCookieJar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public string this[string key]
/// </summary>
public int Count => cookies?.Count ?? 0;

/// <summary>
/// Cookies in Jar
/// </summary>
public IDictionary<string, string> Cookies => cookies;

/// <summary>
/// Cookies in Jar
/// </summary>
Expand Down
37 changes: 25 additions & 12 deletions TikTokLiveSharp/Client/HTTP/TikTokHTTPClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,20 @@ internal async Task<string> GetProfilePage(string uniqueId, IDictionary<string,
return await get.ReadAsStringAsync();
}

internal async Task<TikTokWebSocketConnectionData> GetSignedWebsocketData(string roomId)
internal async Task<TikTokWebSocketConnectionData> GetSignedWebsocketData(string roomId, string customServerUrl = null, string apiKey = null)
{
ITikTokHttpRequest request = new TikTokHttpRequest(Constants.TIKTOK_SIGN_API, false, false)
.SetQueries(new Dictionary<string, object>()
{
{ "client", CLIENT_NAME },
{ "uuc", clientNum },
{ "room_id", roomId }
});
Dictionary<string, object> queries = new Dictionary<string, object>()
{
{ "client", CLIENT_NAME },
{ "uuc", clientNum },
{ "room_id", roomId },
};
if (!string.IsNullOrEmpty(apiKey))
{
queries.Add("apiKey", 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");
Expand All @@ -137,24 +142,32 @@ internal async Task<TikTokWebSocketConnectionData> GetSignedWebsocketData(string
if (response.Headers.TryGetValues("RateLimit-Reset", out IEnumerable<string> 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($"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<string> cookieHeaders))
{
try
{
Response signingResponse = Serializer.Deserialize<Response>(await response.Content.ReadAsStreamAsync());
return new TikTokWebSocketConnectionData(roomId, cookieHeaders.First(), signingResponse);
return new TikTokWebSocketConnectionData(roomId, cookieHeaders, signingResponse);
}
catch (Exception ex)
{
Expand Down
5 changes: 2 additions & 3 deletions TikTokLiveSharp/Client/HTTP/TikTokHttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -157,7 +156,7 @@ public TikTokHttpRequest(string url, bool enableCompression = true, bool useCook
{
Timeout = Timeout
};
foreach (KeyValuePair<string, string> header in Constants.DEFAULT_REQUEST_HEADERS)
foreach (KeyValuePair<string, string> 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);
Expand Down Expand Up @@ -210,7 +209,7 @@ public ITikTokHttpRequest SetQueries(IDictionary<string, object> 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;
}

Expand Down
16 changes: 12 additions & 4 deletions TikTokLiveSharp/Client/Socket/TikTokWebSocket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,11 +74,18 @@ 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<string, string> combinedCookies = new Dictionary<string, string>(cookieContainer.Cookies);
foreach (KeyValuePair<string, string> 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<string, string> cookie in combinedCookies)
cookieHeader.Append($"{cookie.Key}={cookie.Value};");
foreach (KeyValuePair<string, string> header in Constants.DEFAULT_SOCKET_HEADERS)
clientWebSocket.Options.SetRequestHeader(header.Key, header.Value);
clientWebSocket.Options.SetRequestHeader("Cookie", cookieHeader.ToString());
}
#endregion
Expand Down
24 changes: 17 additions & 7 deletions TikTokLiveSharp/Client/Socket/TikTokWebSocketConnectionData.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using TikTokLiveSharp.Models.Protobuf.Messages;

namespace TikTokLiveSharp.Client.Socket
Expand All @@ -12,25 +14,33 @@ public readonly struct TikTokWebSocketConnectionData
/// </summary>
public readonly string RoomId;
/// <summary>
/// Required Cookies obtained from signing-server Headers
/// </summary>
public readonly string WebSocketCookies;
/// <summary>
/// TikTokServer-Response obtained from signing-server
/// </summary>
public readonly Response InitialWebcastResponse;
/// <summary>
/// Headers required for Websocket-Connection
/// </summary>
public readonly Dictionary<string, string> CookieHeaders;

/// <summary>
/// Creates instance of TikTokWebSocketConnectionData
/// </summary>
/// <param name="roomId">RoomID for stream to connect to</param>
/// <param name="cookies">Required Cookies obtained from signing-server Headers</param>
/// <param name="initialResponse">TikTokServer-Response obtained from signing-server</param>
public TikTokWebSocketConnectionData(string roomId, string cookies, Response initialResponse)
public TikTokWebSocketConnectionData(string roomId, IEnumerable<string> cookies, Response initialResponse)
{
RoomId = roomId;
WebSocketCookies = cookies;
InitialWebcastResponse = initialResponse;
CookieHeaders = new Dictionary<string, string>(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]);
}
}
}
}
}
Loading