public async Task Subscribe(WebSubHandler webSubHandler) { string externalAddress = await webAccessConfig.GetExternalWebSubAddress(); externalURL = $"{externalAddress}/TASagentBotAPI/WebSub/Stream"; subURL = $"https://api.twitch.tv/helix/streams?user_id={botConfig.BroadcasterId}"; bool success = await helixHelper.WebhookSubscribe( callback : externalURL, mode : "subscribe", topic : subURL, lease : 48 * 60 * 60, secret : webSubHandler.CreateSecretForRoute("/TASagentBotAPI/WebSub/Stream")); if (!success) { communication.SendErrorMessage("Failed to subscribe to Stream Changes. Aborting."); externalURL = null; subURL = null; return; } TwitchStreams streamData = await helixHelper.GetStreams(userIDs : new List <string>() { botConfig.BroadcasterId }); if (streamData.Data is null || streamData.Data.Count == 0) { currentStreamData = null; }
public IActionResult Stream( [FromServices] WebSub.IStreamChangeSubscriber streamChangeSubscriber, TwitchStreams _) { //I am unclear on why, but I have to deserialize the follow from the body manually Request.Body.Position = 0; using StreamReader reader = new StreamReader( Request.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: -1, leaveOpen: true); string body = reader.ReadToEnd(); TwitchStreams channelData = JsonSerializer.Deserialize <TwitchStreams>(body); if (channelData.Data is not null && channelData.Data.Count > 0) { streamChangeSubscriber.NotifyUpdate(channelData.Data[0]); }
public void JoinOrPartChannels() { // Channel Status: // -2 = Queued Part // -1 = Queued Join // 0+ = Joined (Value represents strike count) List <String> forcedChannels = new List <String>(Properties.Settings.Default.ChannelList.Split(',')); List <TwitchStream> streams = TwitchStreams.GetStreamsByMinViewers(Convert.ToInt32(Properties.Settings.Default.AutojoinViewerMinimum * 0.8)); if (streams.Count == 0) { Logger.Warn("No Twitch.tv streams found meeting viewer minimums. Skipping Join/Part process."); } else { // Join channels which meet the minimum viewer requirement foreach (TwitchStream curStream in streams) { if (curStream.Viewers >= Properties.Settings.Default.AutojoinViewerMinimum) { // We're above the Viewer Minimum so join if (_channelStatus.ContainsKey(curStream.Name.ToLower())) { // We have some sort of status for this channel Int32 status; _channelStatus.TryGetValue(curStream.Name.ToLower(), out status); if (status == -2) { // We're already set to part this channel // We're above the Viewer Minimum so queue a join even though we're already set to part Logger.Info($"Stream {curStream.Name} has {curStream.Viewers} viewers. Joining..."); QueueSend(IrcFunctions.Join('#' + curStream.Name.ToLower())); _channelStatus.AddOrUpdate(curStream.Name.ToLower(), -1, (name, count) => - 1); } else if (status > 0) { // We're in the channel, so reset it's strike counter since it it's in the "OK" list _channelStatus.AddOrUpdate(curStream.Name.ToLower(), 0, (name, count) => 0); } } else { // We've never done anything with this channel before so queue a join. Logger.Info($"Stream {curStream.Name} has {curStream.Viewers} viewers. Joining..."); QueueSend(IrcFunctions.Join('#' + curStream.Name.ToLower())); _channelStatus.AddOrUpdate(curStream.Name.ToLower(), -1, (name, count) => - 1); } } } foreach (String curChannel in forcedChannels) { // This channel is a forced channel if (_channelStatus.ContainsKey(curChannel.ToLower())) { // We have some sort of status for this channel Int32 status; _channelStatus.TryGetValue(curChannel.ToLower(), out status); if (status == -2) { // We're already set to part this channel (this should only happen if we're kicked since we don't volunteer to part forced channels) // We're above the Viewer Minimum so join even though we're already set to part Logger.Info($"Stream {curChannel} is in the forced list. Joining..."); QueueSend(IrcFunctions.Join('#' + curChannel.ToLower())); _channelStatus.AddOrUpdate(curChannel.ToLower(), -1, (name, count) => - 1); } } else { // We've never done anything with this channel before Logger.Info($"Stream {curChannel} is in the forced list. Joining..."); QueueSend(IrcFunctions.Join('#' + curChannel.ToLower())); _channelStatus.AddOrUpdate(curChannel.ToLower(), -1, (name, count) => - 1); } } // Leave channels which don't meet 80% of the minimum viewer requirement foreach (KeyValuePair <String, Int32> curChanKVP in _channelStatus) { if (!forcedChannels.Contains(curChanKVP.Key.ToLower())) { // If this is not a forced channel Int32 findIndex = streams.FindIndex(f => String.Equals(f.Name, curChanKVP.Key, StringComparison.CurrentCultureIgnoreCase)); if (findIndex == -1) { // Channel didn't come back in our twitch query (meaning it is offline or below min viewer count) if (_channelStatus.ContainsKey(curChanKVP.Key.ToLower())) { // We have some sort of status for this channel Int32 status; _channelStatus.TryGetValue(curChanKVP.Key.ToLower(), out status); if (status >= 0) { // We're already in the channel (don't give strikes until we're in the channel) // Increment the number of strikes _channelStatus[curChanKVP.Key.ToLower()]++; Logger.Info( $"Stream {curChanKVP.Key} has less than {Properties.Settings.Default.AutojoinViewerMinimum * 0.8} viewers. Strike {_channelStatus[curChanKVP.Key.ToLower()]}/{Properties.Settings.Default.AutojoinStrikeLimit}..."); if (_channelStatus[curChanKVP.Key.ToLower()] >= Properties.Settings.Default.AutojoinStrikeLimit) { // We're at max strikes, queue a leave Logger.Info( $"Strike {_channelStatus[curChanKVP.Key.ToLower()]}/{Properties.Settings.Default.AutojoinStrikeLimit} for {curChanKVP.Key}... Leaving..."); QueueSend(IrcFunctions.Part('#' + curChanKVP.Key.ToLower())); _channelStatus.AddOrUpdate(curChanKVP.Key.ToLower(), -2, (name, count) => - 2); } } } } } } } if (_joinTimer == null) { Logger.Info($"Starting Channel Maintainer ({Properties.Settings.Default.AutojoinCheckFrequencyMS}ms)"); _joinTimer = new Timer(_ => JoinOrPartChannels(), null, Properties.Settings.Default.AutojoinCheckFrequencyMS, Timeout.Infinite); } else { _joinTimer.Change(Properties.Settings.Default.AutojoinCheckFrequencyMS, Timeout.Infinite); } }