'How to send a message to a Discord Channel every X seconds?

I created a Discord Bot where I can send text commands. For example, typing !start, would send the message that has the start parameter set. The bot works, I can send the commands, I'm just having a hard time figuring out how to set the timer and tie it to the command. Maybe setting it up as a command is the wrong way to go about it, but I can't figure out another way to send a message.

Here is my code so far. I've looked in several places online. Many resources say I need to include the System.Timer using, which I have. I'm just having a hard time figuring out how to tie the timer, the command itself, and which channel it goes into, all together. Any help would be appreciated.

using Discord.Commands;
using System.Timers;

namespace MessageTimerBot.Modules
{

    public class Commands : ModuleBase<SocketCommandContext>
    {
        [Command("start")]
        public async Task StartMessageTimer()
        {

        }

        public class MessageTimer
        {

            private static System.Timers.Timer messageTimer;

            public static void StartTimer()
            {
                messageTimer = new System.Timers.Timer(30000);
                messageTimer.Elapsed += OnTimerElapsed;
                messageTimer.AutoReset = true;
                messageTimer.Enabled = true;
            }

            public static void OnTimerElapsed(object source, ElapsedEventArgs e)
            {
                Console.WriteLine("Test Message");
            }
        }
    }
}


Solution 1:[1]

If I'm understanding it correctly you just need to make your OnTimerElapsed method async:

public static async void OnTimerElapsed(...)

Then create a global variable SocketCommandContext _Context; in your MessageTimer Class, add the attribute SocketCommandContext context to the StartTime method and set _Context to context

And then Call await _Context.Channel.SendMessageAsync("Test Message"); in your OnTimerElapsed method:

public static Class MessageTimer
{
   private static System.Timers.Timer messageTimer;
   private static SocketCommandContext _Context;

   public static void StartTimer(SocketCommandContext context)
   {
      _Context = context;

      messageTimer = new System.Timers.Timer(30000);
      messageTimer.Elapsed += OnTimerElapsed;
      messageTimer.AutoReset = true;
      messageTimer.Enabled = true;
   }

   public static void OnTimerElapsed(object source, ElapsedEventArgs e)
   {
      _Context.Channel.SendMessageAsync("Test Message");
      Console.WriteLine("Test Message");
   }
}

Now you just need to call MessageTimer.StartTimer(Context); in your start command

public class Commands : ModuleBase<SocketCommandContext>
{
   [Command("start")]
   public async Task StartMessageTimer()
   {
      MessageTimer.StartTimer(Context);
   }
}

Solution 2:[2]

I followed this example to set up my automated message system. It still works on the latest version of Discord.NET (v 3.6.1 as of the date this post goes up)

using System;
using System.Threading; // 1) Add this namespace
using System.Threading.Tasks;
using Discord.Commands;

public class TimerService
{
    private readonly Timer _timer; // 2) Add a field like this
    // This example only concerns a single timer.
    // If you would like to have multiple independant timers,
    // you could use a collection such as List<Timer>,
    // or even a Dictionary<string, Timer> to quickly get
    // a specific Timer instance by name.

    public TimerService(DiscordSocketClient client)
    {
        _timer = new Timer(async _ =>
        {
            // 3) Any code you want to periodically run goes here, for example:
            var chan = client.GetChannel(knownId) as IMessageChannel;
            if (chan != null)
                await chan.SendMessageAsync("hi");
        },
        null,
        TimeSpan.FromMinutes(10),  // 4) Time that message should fire after the timer is created
        TimeSpan.FromMinutes(30)); // 5) Time after which message should repeat (use `Timeout.Infinite` for no repeat)
    }

    public void Stop() // 6) Example to make the timer stop running
    {
        _timer.Change(Timeout.Infinite, Timeout.Infinite);
    }

    public void Restart() // 7) Example to restart the timer
    {
        _timer.Change(TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(30));
    }
}

public class TimerModule : ModuleBase
{
    private readonly TimerService _service;

    public TimerModule(TimerService service) // Make sure to configure your DI with your TimerService instance
    {
        _service = service;
    }

    // Example commands
    [Command("stoptimer")]
    public async Task StopCmd()
    {
        _service.Stop();
        await ReplyAsync("Timer stopped.");
    }

    [Command("starttimer")]
    public async Task RestartCmd()
    {
        _service.Restart();
        await ReplyAsync("Timer (re)started.");
    }
}

You would need to set up Dependency Injection in your startup file for this to work correctly. If you don't know how to do that, the Discord.NET and Microsoft docs should give you a good idea of what to do.

Solution 3:[3]

Since your question is tagged C++20, I'm going to show a C++20 solution, even though your compiler does not yet implement these parts of C++20. However, there exists a free, open-source, header-only preview of this part of C++20 that you can use in the interim which gives you a very nice migration path to C++20. Additionally the C++20 tools in this area are far easier to use than in prior C++ standards.

#include <cassert>
#include <chrono>
#include <format>
#include <iostream>
#include <sstream>

int
main()
{
    using namespace std;
    using namespace std::chrono;

    stringstream io;
    sys_time<nanoseconds> now = system_clock::now();
    io << format("{:%Y%m%d-%T}", now);  // store
    cout << io.str() << '\n';           // 20220110-15:13:19.576742000
    now = {};                           // "zero out" now just to tell if the parse works
    io >> parse("%Y%m%d-%T", now);      // parse
    assert(!io.fail());                 // another way to tell if the parse works
    cout << now << '\n';                // 2022-01-10 15:13:19.576742000
}
  • No casts
  • Simple
  • nanosecond precision
  • Note that this is a UTC timestamp
  • On gcc, you could use auto in place of sys_time<nanoseconds>, but this makes the precision platform-dependent. E.g. macOS would give you microseconds.

To use the free, open-source, header-only preview:

  • Replace #include <format> with #include "date/date.h".
  • Add using namespace date;
  • Change the first format string from "{:%Y%m%d-%T}" to "%Y%m%d-%T"

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3