Advice for programming structure for a chatbot

Thanks for your reply.
How can I be more specific?

Edit:

  1. How should I handle multiple channels for each bot?
    Do I have one connection for every channel or just one connection for all of them.

  2. What exactly is the use of these chat servers for bots?
    I will have to rewrite my code if it works how I think it does (seperate IRC servers). So instead of having a Bot as parent and Channel as child I’d have to reverse that.

I’m just lookiing for guidance on these things, I wonder if I’m doing it correctly, where can things be improved etc.

This is the main code that I use for connecting to IRC and joining channel(s):

    public void DoMainWork(object sender, DoWorkEventArgs e)
    {
        Irc.SendDelay = 200;
        Irc.Encoding = Encoding.UTF8;
        Irc.AutoRetry = true;
        Irc.AutoReconnect = true;
        Irc.AutoRelogin = true;
        Irc.AutoRejoin = true;
        Irc.ActiveChannelSyncing = true;

        Irc.OnOp += OnOp;
        Irc.OnDeop += OnDeop;
        Irc.OnConnected += OnConnected;
        Irc.OnConnecting += OnConnecting;
        Irc.OnDisconnected += OnDisconnected;
        Irc.OnError += OnError;
        Irc.OnRawMessage += OnRawMessage;
        Irc.OnJoin += OnJoin;
        Irc.OnPart += OnPart;
        Irc.OnChannelMessage += OnChannelMessage; 

        Irc.Connect(Config.IrcHost, Config.IrcPort);

        AddLogMessage("Authenticating", "");
        Irc.Login(Nick, "AccursedNetwork Bot", 0, Nick, "oauth:" + Password);

        foreach (var channel in Channels)
        {
            AddLogMessage("[{0}] joining {1}.".InjectWith(Nick, channel.NamePrefixed));
            Irc.RfcJoin(channel.NamePrefixed);
        }

        while (!_mainWorker.CancellationPending)
        {
            if (Working)
            {
                try
                {
                    Irc.ListenOnce(false);
                }
                catch (ArgumentException)
                {
                }

                Uptime = Uptime + TimeSpan.FromMilliseconds(50);
                Iterations++;
            }

            Thread.Sleep(50);
        }
        Irc.Disconnect();
    }

So basically it hooks all of the events and then processes mostly everything with string comparisons.

OnChannelMessage

private void OnChannelMessage(object s, IrcEventArgs e)
{
    if (!Working) 
        return;

    var channel = TwitchChannels.Find(c => c.NamePrefixed == e.Data.Channel);
    if (channel == null)
        return;

    var user = channel.Users.Find(u => u.Name == e.Data.Nick) ?? new User(channel.Id, e.Data.Nick);

    if (channel.Mods.Contains(user.Name.Lc()) || Irc.GetChannel(channel.NamePrefixed).Ops.ContainsKey(user.Name.Lc()))
        user.AccessLevel = AccessLevel.Operator;

    user.AccessLevel = user.Name == channel.Name ? AccessLevel.Owner : user.AccessLevel;
    user.MessagesSent++;
    user.NeedSave = true;

    if (channel.StealthMode)
        return;

    var message = e.Data.Message;
    if (message.IsNullOrEmpty())
        return;

    string[] parts;
    if (channel.Live && (e.Data.Nick == "twitchnotify" && message.Contains("subscribed")))
    {
        parts = e.Data.Message.Split(' ');
        var nick = parts[0];
        if (message.Contains("months in a row!"))
        {
            var months = parts[3];
            SendMessage(
                "{0} - thank you so much for the continued support with a subscription ({1} months)! <3"
                    .InjectWith(nick.UcFirst(), months), channel.NamePrefixed);
            return;
        }

        SendMessage("{0} - thank you so much for subscribing! <3".InjectWith(nick.UcFirst()),
            channel.NamePrefixed);
        return;
    }

    var isCommand = message.StartsWith("!");
    if (!isCommand) 
        return;

    parts = e.Data.MessageArray;
    var commandName = parts[0].Lc();

    if (commandName == "!{0}".InjectWith(channel.CurrencyName.Lc()))
    {
        SendMessage("{0} -> you have [{1}] {2}!".InjectWith(user.Name, user.Currency, channel.CurrencyName.UcFirst()), channel.NamePrefixed);
        return;
    }

    if (commandName == "!reload")
    {
        channel.Load();
        SendMessage("{0} -> channel configuration reloaded.".InjectWith(user.Name), channel.NamePrefixed);
        return;
    }

    var command = channel.Commands.Find(cmd => cmd.Name == commandName);
    if (command == null)
        return;

    if (user.AccessLevel < command.AccessLevel)
    {
        SendMessage("{0} -> you do not have access to this command.".InjectWith(user.Name), channel.NamePrefixed);
        return;
    }
    var commandText = command.Text;
    if (commandText.IsNullOrEmpty())
        return;

    var commandArgsCount = Regex.Matches(commandText, @"\{[0-9]+\}", RegexOptions.Singleline | RegexOptions.IgnoreCase).Count;
    var messageArgsCount = 0;

    var temp = new string[message.Split(' ').Count()-1];
    for (var i = 0; i < temp.Count(); i++)
    {
        temp[i] = message.Split(' ')[i+1];
    }

    var messageArgs = temp;
    messageArgsCount = messageArgs.Count();
 
    if (messageArgsCount < commandArgsCount)
    {
        SendMessage("{0} -> incorrect syntax for command {1}.".InjectWith(user.Name, commandName), channel.NamePrefixed);
        return;
    }

    // ReSharper disable once CoVariantArrayConversion
    commandText = commandText.InjectWith(messageArgs);
    SendMessage(commandText, channel.NamePrefixed);
}

As you can see, every channel is a Channel and every bot is a TwitchBot and every new/existing user is a User.

This is what happens on JOIN/PART (for part in reverse):

private void OnJoin(object s, JoinEventArgs e)
{
    var channel = TwitchChannels.Find(c => c.NamePrefixed == e.Data.Channel);
    if (channel == null)
        return;

    var user = channel.Users.Find(u => u.Name == e.Data.Nick);
    if (user != null) 
        return;

    user = new User(channel.Id, e.Data.Nick);

    AddLogMessage("user {0} joined the channel".InjectWith(user.Name));
    channel.Users.Add(user);
}