internal static void Start() { YouTubeMessageHandlers.InvokeHandler(new YouTubeMessage("youtube#onInitialize"), ""); TaskHelper.ScheduleUniqueActionAtTime("YouTubeOAuthRefresh", () => YouTubeOAuthToken.Refresh(), YouTubeOAuthToken.expireTime.Subtract(new TimeSpan(0, 1, 0))); TaskHelper.ScheduleUniqueRepeatingAction("YouTubeChannelRefresh", () => YouTubeLiveBroadcast.Refresh(), 60000 * 3); // Refresh our list of broadcasts 3 minutes (this task will automatically cancel it's self once it latches onto a broadcast) TaskHelper.ScheduleUniqueRepeatingAction("YouTubeLiveChatRefresh", () => YouTubeLiveChat.Refresh(), 0); }
internal static void Process(string json) { // Handle any json parsing errors if (json == string.Empty) { return; } // Parse the chat info into a json node, making sure it's not null JSONNode node = JSON.Parse(json); if (node == null || node.IsNull) { return; } // If the data has an error node, print out the entire json string for debugging purposes if (node.HasKey("error")) { Plugin.Log(json); return; } // Read in the json data to our data structs kind = node["kind"].Value; etag = node["etag"].Value; _nextPageToken = node["nextPageToken"].Value; _pollingIntervalMillis = node["pollingIntervalMillis"].AsInt; // Iterate through each message, invoking any regstered callbacks along the way foreach (JSONObject item in node["items"].AsArray) { YouTubeMessage newMessage = new YouTubeMessage(); newMessage.Update(item); YouTubeMessageHandlers.InvokeHandler(newMessage, ""); Thread.Sleep(100); } }
internal static void Refresh() { try { // Wait a few seconds then return if the current broadcast is null if (YouTubeLiveBroadcast.currentBroadcast == null) { Thread.Sleep(5000); return; } //Plugin.Log($"Requesting chat messages for live chat with id {YouTubeChannel.liveOrDefaultChatId}..."); HttpWebRequest web = (HttpWebRequest)WebRequest.Create($"https://www.googleapis.com/youtube/v3/liveChat/messages?liveChatId={YouTubeLiveBroadcast.currentBroadcast.snippet.liveChatId}&part=id%2Csnippet%2CauthorDetails{(_nextPageToken!=""? $"&pageToken={_nextPageToken}" : "")}"); web.Method = "GET"; web.Headers.Add("Authorization", $"{YouTubeOAuthToken.tokenType} {YouTubeOAuthToken.accessToken}"); web.Accept = "application/json"; using (HttpWebResponse resp = (HttpWebResponse)web.GetResponse()) { if (resp.StatusCode == HttpStatusCode.OK) { using (Stream dataStream = resp.GetResponseStream()) { using (StreamReader reader = new StreamReader(dataStream)) { string ret = reader.ReadToEnd(); Process(ret); //Plugin.Log($"Chat: {ret}"); } } } else { Plugin.Log($"Error: {resp.StatusCode.ToString()}"); } } } catch (WebException ex) { // Read the response and log it using (Stream dataStream = ex.Response.GetResponseStream()) { using (StreamReader reader = new StreamReader(dataStream)) { var response = reader.ReadToEnd(); Plugin.Log($"Status: {ex.Status}, Response: {response}"); switch (((HttpWebResponse)ex.Response).StatusCode) { // If we hit an unauthorized exception, the users auth token has expired case HttpStatusCode.Unauthorized: // Try to refresh the users auth token, forcing it through even if our local timestamp says it's not expired if (!YouTubeOAuthToken.Refresh(true)) { YouTubeOAuthToken.Invalidate(); YouTubeOAuthToken.Generate(); } break; case HttpStatusCode.Forbidden: var json = JSON.Parse(response); List <string> errorList = new List <string>(); foreach (var error in json["error"]["errors"].AsArray) { errorList.Add(error.Value["reason"].Value); } YouTubeConnection.lastError = "Unable to retrieve YouTube live chat messages. Have you reached your quota? Error(s): " + string.Join(", ", errorList); YouTubeMessageHandlers.InvokeHandler(new YouTubeMessage("youtube#onError"), ""); YouTubeLiveBroadcast.currentBroadcast = null; YouTubeConnection.Stop(); break; } } } _pollingIntervalMillis = 3000; } catch (Exception ex) { Plugin.Log(ex.ToString()); _pollingIntervalMillis = 3000; } Thread.Sleep(_pollingIntervalMillis); }
private static void SendMessageLoop() { while (!Globals.IsApplicationExiting) { Thread.Sleep(500); if (_sendQueue.Count > 0 && _sendQueue.TryPeek(out var messageToSend)) { try { HttpWebRequest web = (HttpWebRequest)WebRequest.Create($"https://www.googleapis.com/youtube/v3/liveChat/messages?part=snippet"); web.Method = "POST"; web.Headers.Add("Authorization", $"{YouTubeOAuthToken.tokenType} {YouTubeOAuthToken.accessToken}"); web.ContentType = "application/json"; JSONObject container = new JSONObject(); container["snippet"] = new JSONObject(); container["snippet"]["liveChatId"] = new JSONString(YouTubeLiveBroadcast.currentBroadcast.snippet.liveChatId); container["snippet"]["type"] = new JSONString("textMessageEvent"); container["snippet"]["textMessageDetails"] = new JSONObject(); container["snippet"]["textMessageDetails"]["messageText"] = new JSONString(messageToSend.Value); string snippetString = container.ToString(); Plugin.Log($"Sending {snippetString}"); var postData = Encoding.ASCII.GetBytes(snippetString); web.ContentLength = postData.Length; using (var stream = web.GetRequestStream()) stream.Write(postData, 0, postData.Length); using (HttpWebResponse resp = (HttpWebResponse)web.GetResponse()) { if (resp.StatusCode != HttpStatusCode.OK) { using (Stream dataStream = resp.GetResponseStream()) { using (StreamReader reader = new StreamReader(dataStream)) { var response = reader.ReadToEnd(); Plugin.Log($"Status: {resp.StatusCode} ({resp.StatusDescription}), Response: {response}"); continue; } } } using (Stream dataStream = resp.GetResponseStream()) { using (StreamReader reader = new StreamReader(dataStream)) { // Read the response into a JSON objecet var json = JSON.Parse(reader.ReadToEnd()).AsObject; // Then create a new YouTubeMessage object from it and send it along to the other StreamCore clients, excluding the assembly that sent the message var newMessage = new YouTubeMessage(); newMessage.Update(json); var assemblyHash = messageToSend.Key.ToString(); // Invoke YouTube message received callbacks YouTubeMessageHandlers.InvokeHandler(newMessage, assemblyHash); _sendQueue.TryDequeue(out var gone); } } } } catch (ThreadAbortException ex) { return; } catch (Exception ex) { // Failed the send the message for some other reason, it will be retried next iteration Plugin.Log($"Failed to send YouTube message, trying again in a few seconds! {ex.ToString()}"); Thread.Sleep(2500); } } } }
internal static void Refresh() { try { Plugin.Log($"Requesting live broadcast info..."); HttpWebRequest web = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/youtube/v3/liveBroadcasts?part=id%2Csnippet%2Cstatus&broadcastStatus=all&broadcastType=all&maxResults=50"); web.Method = "GET"; web.Headers.Add("Authorization", $"{YouTubeOAuthToken.tokenType} {YouTubeOAuthToken.accessToken}"); web.Accept = "application/json"; web.UserAgent = "StreamCoreClient"; using (HttpWebResponse resp = (HttpWebResponse)web.GetResponse()) { if (resp.StatusCode == HttpStatusCode.OK) { using (Stream dataStream = resp.GetResponseStream()) { using (StreamReader reader = new StreamReader(dataStream)) { string ret = reader.ReadToEnd(); if (Update(ret)) { channelName = GetChannelName(currentBroadcast.snippet.channelId); Plugin.Log("YouTube channel name: " + channelName); YouTubeMessageHandlers.InvokeHandler(new YouTubeMessage("youtube#onConnectedToLiveChat"), ""); TaskHelper.CancelTask("YouTubeChannelRefresh"); Plugin.Log($"There are currently {broadcasts.Count} broadcasts being tracked.");// Ret: {ret}"); } //Plugin.Log($"Broadcast \"{broadcast.Value.snippet.title}\" (ID: {broadcast.Value.id}, ChannelID: {broadcast.Value.snippet.channelId}) with description \"{broadcast.Value.snippet.description}\" status is \"{broadcast.Value.status.recordingStatus}\""); } } } else { Plugin.Log($"Error: {resp.StatusCode}"); } } } catch (WebException ex) { // Read the response and log it using (Stream dataStream = ex.Response.GetResponseStream()) { using (StreamReader reader = new StreamReader(dataStream)) { var response = reader.ReadToEnd(); Plugin.Log($"Status: {ex.Status}, Response: {response}"); switch (((HttpWebResponse)ex.Response).StatusCode) { // If we hit an unauthorized exception, the users auth token has expired case HttpStatusCode.Unauthorized: // Try to refresh the users auth token, forcing it through even if our local timestamp says it's not expired if (!YouTubeOAuthToken.Refresh(true)) { YouTubeOAuthToken.Invalidate(); YouTubeOAuthToken.Generate(); } break; case HttpStatusCode.Forbidden: var json = JSON.Parse(response); List <string> errorList = new List <string>(); foreach (var error in json["error"]["errors"].AsArray) { errorList.Add(error.Value["reason"].Value); } YouTubeConnection.lastError = "Unable to retrieve live broadcast data! Ensure you have configured the YouTube data API correctly, then try again. Error(s): " + string.Join(", ", errorList); YouTubeMessageHandlers.InvokeHandler(new YouTubeMessage("youtube#onError"), ""); currentBroadcast = null; YouTubeConnection.Stop(); break; } } } } catch (Exception ex) { Plugin.Log(ex.ToString()); } }