Preventing Discord Bot Spam When Monitoring Twitch Stream Status with C# Timer

I’ve got a working Discord bot that posts embeds when a Twitch streamer goes live. The issue is I set up a timer to check every 5 minutes if the stream is active, but it keeps spamming the Discord channel with the same embed repeatedly.

What I need is a way to check if I have already sent a “live” notification and only post it once for each stream session. Additionally, I want to handle brief disconnections (like when streamers experience internet issues) without flooding the channel with duplicate alerts.

Here’s the code I’m currently using to monitor streams:

using Microsoft.Extensions.DependencyInjection;
using Discord;
using Discord.WebSocket;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace StreamBot.Handlers
{
    public class YouTubeMonitor
    {
        private readonly IConfiguration _settings;
        private readonly DiscordSocketClient _bot;
        private readonly IServiceProvider _provider;
        private readonly Microsoft.Extensions.Logging.ILogger _log;
        
        public YouTubeMonitor(IServiceProvider provider)
        {
            System.Timers.Timer checkTimer = new System.Timers.Timer(15000); // 15 seconds
            checkTimer.Elapsed += async (sender, e) => CheckStreamStatus();
            checkTimer.Start();

            _settings = provider.GetRequiredService<IConfiguration>();
            _bot = provider.GetRequiredService<DiscordSocketClient>();
            _log = provider.GetRequiredService<ILogger<StreamHandler>>();
            _provider = provider;

            _bot.Ready += CheckStreamStatus;
        }

        public class StreamData
        {
            [JsonProperty("stream_id")] public string stream_id { get; set; }
            [JsonProperty("channel_id")] public string channel_id { get; set; }
            [JsonProperty("streamer_name")] public string streamer_name { get; set; }
            [JsonProperty("category_id")] public string category_id { get; set; }
            [JsonProperty("category_name")] public string category_name { get; set; }
            [JsonProperty("broadcast_type")] public string broadcast_type { get; set; }
            [JsonProperty("stream_title")] public string stream_title { get; set; }
            [JsonProperty("viewers")] public int viewers { get; set; }
            [JsonProperty("preview_url")] public string preview_url { get; set; }
        }

        public class StreamResponse
        {
            public List<StreamData> data { get; set; }
        }

        public class ChannelInfo
        {
            [JsonProperty("avatar_url")] public string avatar_url { get; set; }
        }

        public class ChannelResponse
        {
            public List<ChannelInfo> data { get; set; }
        }

        public async Task CheckStreamStatus()
        {
            var httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Add("Client-ID", $"{_settings["api_key"]}");
            httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", $"{_settings["access_token"]}");

            HttpResponseMessage streamResponse = await httpClient.GetAsync($"https://api.youtube.com/v3/streams?channel={_settings["monitored_channel"]}");
            HttpContent streamContent = streamResponse.Content;
            string streamJson = await streamResponse.Content.ReadAsStringAsync();
            var streamInfo = JsonConvert.DeserializeObject<StreamResponse>(streamJson);

            HttpResponseMessage channelResponse = await httpClient.GetAsync($"https://api.youtube.com/v3/channels?name={_settings["monitored_channel"]}");
            HttpContent channelContent = channelResponse.Content;
            string channelJson = await channelResponse.Content.ReadAsStringAsync();
            var channelInfo = JsonConvert.DeserializeObject<ChannelResponse>(channelJson);

            if (!(streamInfo == default(StreamResponse) || streamInfo.data.Count == 0))
            {
                Console.WriteLine($"Status: {streamInfo.data[0].broadcast_type}");
                Console.WriteLine($"Creator: {streamInfo.data[0].streamer_name}");
                Console.WriteLine($"Title: {streamInfo.data[0].stream_title}");
                Console.WriteLine($"Category: {streamInfo.data[0].category_name}");
                Console.WriteLine($"Thumbnail: {streamInfo.data[0].preview_url.Replace("{w}x{h}", "1280x720")}");
                Console.WriteLine($"Watchers: {streamInfo.data[0].viewers}");

                var notification = new EmbedBuilder{};
                notification.WithFooter(footer => footer.Text = "StreamBot")
                    .WithTitle($"{streamInfo.data[0].stream_title}")
                    .WithAuthor($"{streamInfo.data[0].streamer_name} started streaming!", $"{channelInfo.data[0].avatar_url}")
                    .WithImageUrl($"{streamInfo.data[0].preview_url.Replace("{w}x{h}", "1280x720")}")
                    .WithDescription($"Now playing {streamInfo.data[0].category_name} with {streamInfo.data[0].viewers} watchers \n [Join Stream](https://youtube.com/{streamInfo.data[0].streamer_name})")
                    .WithColor(Color.Red)
                    .WithUrl($"https://youtube.com/{streamInfo.data[0].streamer_name}")
                    .WithCurrentTimestamp();

                ulong targetChannel = Convert.ToUInt64(_settings["notification_channel"]);
                var messageChannel = _bot.GetChannel(targetChannel) as IMessageChannel;
                var notification_msg = await messageChannel.SendMessageAsync(embed: notification.Build());
            }
            else
            {
                Console.WriteLine("Stream Offline");
            }
            _log.LogInformation("YouTube Monitor executed");
        }
    }
}

What changes can I make to avoid spam while keeping checks for stream status frequent?

You need state tracking with a dictionary or database to store stream sessions. I had this exact problem - fixed it by storing the stream_id and timestamp when notifications go out. Just check if the current stream_id matches what you’ve stored before sending anything. For disconnections, add a 10-15 minute grace period before marking streams as offline. Stops spam during those brief network hiccups streamers always get. Also throw notification history into a text file or SQLite database so your bot remembers previous sessions after restarts. Right now your logic fires notifications every time it detects a live stream - that’s your spam issue.

Add a boolean flag like isStreamLive to track the state. Only send the embed when the stream goes from offline to online - not on every check. Store the current stream_id too so you don’t spam notifications if they restart the same stream.

Had this exact problem 6 months ago. You need a cooldown period with your state tracking. Even with stream_id comparisons, you’ll still get spam when the API returns wonky data or streamers restart multiple times quickly. I added a timestamp check - once you send a notification for a streamer, wait at least 30 minutes before sending another one, regardless of stream changes. Store both the last notification time and stream_id in a concurrent dictionary. Also throw in exponential backoff for your API calls when streams are live - don’t hammer the API every 15 seconds once you know someone’s streaming. Check every 5 minutes for live streams, every 30 seconds for offline ones waiting to go live.

Had this exact problem with my Twitch bot last year. You need a state machine that tracks stream status changes, not just whether streams are live. Set up a class-level dictionary to store each channel’s previous state and stream ID. When CheckStreamStatus runs, compare current state to stored state - only notify when going from offline to online, not when already online. For brief disconnections, add delayed offline detection by storing the last seen online timestamp. Only mark streams offline after missing them for 3-4 consecutive checks. This stops spam during network hiccups while keeping your frequent monitoring.

Just wrap your notification code in an if statement that checks if the stream_id changed from last time. Store the previous stream_id in a class variable and compare it before sending embeds. Works like a charm for my bot.