Browse Source

Added moderation module.

Added help module.
Added better access checking.
Added more channel/nick updating.
Added better control of commands.
tags/3.0.0
Teknikode 4 years ago
parent
commit
08923b3987

+ 6
- 0
Combot.sln View File

@@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Combot", "Combot\Combot.csp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IRCServices", "IRCServices\IRCServices.csproj", "{65FCBF1C-8C9E-4688-BECC-185D9030899F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0B78E5A1-A302-4587-8004-3D41DCEF5CD2}"
ProjectSection(SolutionItems) = preProject
Bot.ico = Bot.ico
readme.md = readme.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

+ 196
- 21
Combot/Bot.cs View File

@@ -19,22 +19,29 @@ namespace Combot
public List<Module> Modules;
public bool Connected = false;
public bool LoggedIn = false;
public static Dictionary<PrivilegeMode, AccessType> AccessTypeMapping = new Dictionary<PrivilegeMode, AccessType>() { { PrivilegeMode.v, AccessType.Voice }, { PrivilegeMode.h, AccessType.HalfOperator }, { PrivilegeMode.o, AccessType.Operator }, { PrivilegeMode.a, AccessType.SuperOperator }, { PrivilegeMode.q, AccessType.Founder } };
public Dictionary<PrivilegeMode, AccessType> PrivilegeModeMapping = new Dictionary<PrivilegeMode, AccessType>() { { PrivilegeMode.v, AccessType.Voice }, { PrivilegeMode.h, AccessType.HalfOperator }, { PrivilegeMode.o, AccessType.Operator }, { PrivilegeMode.a, AccessType.SuperOperator }, { PrivilegeMode.q, AccessType.Founder } };
public Dictionary<ChannelMode, AccessType> ChannelModeMapping = new Dictionary<ChannelMode, AccessType>() { { ChannelMode.v, AccessType.Voice }, { ChannelMode.h, AccessType.HalfOperator }, { ChannelMode.o, AccessType.Operator }, { ChannelMode.a, AccessType.SuperOperator }, { ChannelMode.q, AccessType.Founder } };

private bool GhostSent;
private int CurNickChoice;

public Bot(ServerConfig serverConfig)
{
IRC = new IRC();
Modules = new List<Module>();
GhostSent = false;
CurNickChoice = 0;
ServerConfig = serverConfig;

IRC = new IRC(serverConfig.MaxMessageLength, serverConfig.MessageSendDelay);
IRC.ConnectEvent += HandleConnectEvent;
IRC.DisconnectEvent += HandleDisconnectEvent;
IRC.Message.ServerReplyEvent += HandleReplyEvent;
IRC.Message.ChannelMessageReceivedEvent += HandleChannelMessageReceivedEvent;
IRC.Message.PrivateMessageReceivedEvent += HandlePrivateMessageReceivedEvent;
IRC.Message.PrivateNoticeReceivedEvent += HandlePrivateNoticeReceivedEvent;
IRC.Message.JoinChannelEvent += HandleJoinEvent;
IRC.Message.KickEvent += HandleKickEvent;
IRC.Message.ChannelModeChangeEvent += HandleChannelModeChangeEvent;

LoadModules();
}
@@ -45,6 +52,7 @@ namespace Combot
public void Connect()
{
GhostSent = false;
CurNickChoice = 0;
bool serverConnected = false;
int i = 0;
do
@@ -54,7 +62,7 @@ namespace Combot
IPAddress[] ipList = Dns.GetHostAddresses(ServerConfig.Hosts[i].Host);
foreach (IPAddress ip in ipList)
{
serverConnected = IRC.Connect(ip, ServerConfig.Hosts[i].Port, 5000);
serverConnected = IRC.Connect(ip, ServerConfig.Hosts[i].Port);
if (serverConnected)
{
break;
@@ -73,7 +81,7 @@ namespace Combot
{
IRC.Login(ServerConfig.Name, new Nick()
{
Nickname = ServerConfig.Nickname,
Nickname = ServerConfig.Nicknames[CurNickChoice],
Host = Dns.GetHostName(),
Realname = ServerConfig.Realname,
Username = ServerConfig.Username
@@ -106,6 +114,80 @@ namespace Combot
}
}

public bool CheckChannelAccess(string channel, string nickname, AccessType access)
{
Channel foundChannel = IRC.Channels.Find(chan => chan.Name == channel);
if (foundChannel != null)
{
Nick foundNick = foundChannel.Nicks.Find(nick => nick.Nickname == nickname);
if (foundNick != null)
{
for (int i = 0; i < foundNick.Privileges.Count; i++)
{
switch (PrivilegeModeMapping[foundNick.Privileges[i]])
{
case AccessType.User:
if (access == AccessType.User)
{
return true;
}
break;
case AccessType.Voice:
if (access == AccessType.User || access == AccessType.Voice)
{
return true;
}
break;
case AccessType.HalfOperator:
if (access == AccessType.User || access == AccessType.Voice || access == AccessType.HalfOperator)
{
return true;
}
break;
case AccessType.Operator:
if (access == AccessType.User || access == AccessType.Voice || access == AccessType.HalfOperator || access == AccessType.Operator)
{
return true;
}
break;
case AccessType.SuperOperator:
if (access == AccessType.User || access == AccessType.Voice || access == AccessType.HalfOperator || access == AccessType.Operator || access == AccessType.SuperOperator)
{
return true;
}
break;
case AccessType.Founder:
if (access == AccessType.User || access == AccessType.Voice || access == AccessType.HalfOperator || access == AccessType.Operator || access == AccessType.SuperOperator || access == AccessType.Founder)
{
return true;
}
break;
case AccessType.Owner:
return true;
break;
}
}
}
}
return false;
}

public bool CheckChannelAccess(string channel, string nickname, List<AccessType> access)
{
bool hasAccess = false;

for (int i = 0; i < access.Count; i++)
{
hasAccess = CheckChannelAccess(channel, nickname, access[i]);
if (hasAccess)
{
break;
}
}

return hasAccess;
}

private void HandleConnectEvent()
{
Connected = true;
@@ -139,7 +221,32 @@ namespace Combot
}
}

private async void HandleReplyEvent(object sender, IReply e)
private void HandleChannelModeChangeEvent(object sender, ChannelModeChangeInfo e)
{
ChannelConfig channel = ServerConfig.Channels.Find(chan => chan.Name == e.Channel);
if (channel != null)
{
foreach (ChannelModeInfo mode in e.Modes)
{
switch (mode.Mode)
{
case ChannelMode.k:
if (mode.Set)
{
channel.Key = mode.Parameter;
}
else
{
channel.Key = string.Empty;
}
ServerConfig.Save();
break;
}
}
}
}

private void HandleReplyEvent(object sender, IReply e)
{
if (e.GetType() == typeof(ServerReplyMessage))
{
@@ -149,11 +256,11 @@ namespace Combot
case IRCReplyCode.RPL_WELCOME:
// If the reply is Welcome, that means we are fully connected to the server.
LoggedIn = true;
if (!GhostSent && IRC.Nickname != ServerConfig.Nickname)
if (!GhostSent && IRC.Nickname != ServerConfig.Nicknames[CurNickChoice])
{
IRC.SendPrivateMessage("NickServ", string.Format("GHOST {0} {1}", ServerConfig.Nickname, ServerConfig.Password));
IRC.SendPrivateMessage("NickServ", string.Format("GHOST {0} {1}", ServerConfig.Nicknames[CurNickChoice], ServerConfig.Password));
Thread.Sleep(1000);
IRC.SendNick(ServerConfig.Nickname);
IRC.SendNick(ServerConfig.Nicknames[CurNickChoice]);
GhostSent = true;
}
// Identify to NickServ if need be
@@ -175,7 +282,7 @@ namespace Combot
switch (error.ErrorCode)
{
case IRCErrorCode.ERR_NOTREGISTERED:
if (ServerConfig.Password != string.Empty && ServerConfig.Email != string.Empty)
if (ServerConfig.AutoRegister && ServerConfig.Password != string.Empty && ServerConfig.Email != string.Empty)
{
IRC.SendPrivateMessage("NickServ", string.Format("REGISTER {0} {1}", ServerConfig.Password, ServerConfig.Email));
}
@@ -184,14 +291,15 @@ namespace Combot
if (LoggedIn == false)
{
string nick = string.Empty;
if (IRC.Nickname == ServerConfig.Nickname && ServerConfig.SecondaryNickname != string.Empty)
if (IRC.Nickname == ServerConfig.Nicknames[CurNickChoice] && ServerConfig.Nicknames.Count > CurNickChoice + 1)
{
nick = ServerConfig.SecondaryNickname;
CurNickChoice++;
nick = ServerConfig.Nicknames[CurNickChoice];
}
else
{
Random rand = new Random();
nick = string.Format("{0}_{1}", ServerConfig.Nickname, rand.Next(100000).ToString());
nick = string.Format("{0}_{1}", ServerConfig.Nicknames.First(), rand.Next(100000).ToString());
}
IRC.Login(ServerConfig.Name, new Nick()
{
@@ -215,12 +323,36 @@ namespace Combot
&& !ServerConfig.NickBlacklist.Contains(e.Sender.Nickname)
)
{
ParseCommandMessage(e.TimeStamp, e.Message, e.Sender, e.Channel, LocationType.Channel);
ParseCommandMessage(e.TimeStamp, e.Message, e.Sender, e.Channel, MessageType.Channel);
}
}
}

private void HandlePrivateMessageReceivedEvent(object sender, PrivateMessage e)
{
// The message was a command
if (e.Message.StartsWith(ServerConfig.CommandPrefix))
{
if (!ServerConfig.NickBlacklist.Contains(e.Sender.Nickname))
{
ParseCommandMessage(e.TimeStamp, e.Message, e.Sender, e.Sender.Nickname, MessageType.Query);
}
}
}

private void HandlePrivateNoticeReceivedEvent(object sender, PrivateNotice e)
{
// The notice was a command
if (e.Message.StartsWith(ServerConfig.CommandPrefix))
{
if (!ServerConfig.NickBlacklist.Contains(e.Sender.Nickname))
{
ParseCommandMessage(e.TimeStamp, e.Message, e.Sender, e.Sender.Nickname, MessageType.Notice);
}
}
}

private void ParseCommandMessage(DateTime timestamp, string message, Nick sender, string location, LocationType locationType)
private void ParseCommandMessage(DateTime timestamp, string message, Nick sender, string location, MessageType messageType)
{
// Extract command and arguments
string[] msgArgs = message.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
@@ -228,13 +360,16 @@ namespace Combot
List<string> argsOnly = msgArgs.ToList();
argsOnly.RemoveAt(0);

// Find the module that contains the command
Module module = Modules.Find(mod => mod.Commands.Exists(c => c.Triggers.Contains(command)) && mod.Loaded);
if (module != null)
{
// Find the command
Command cmd = module.Commands.Find(c => c.Triggers.Contains(command));
if (cmd != null)
{
CommandMessage newCommand = new CommandMessage();
List<CommandArgument> validArguments = cmd.Arguments.FindAll(arg => arg.MessageTypes.Contains(messageType));
newCommand.Nick.Copy(sender);
IRC.Channels.ForEach(channel => channel.Nicks.ForEach(nick =>
{
@@ -245,21 +380,61 @@ namespace Combot
}));
newCommand.TimeStamp = timestamp;
newCommand.Location = location;
newCommand.LocationType = locationType;
newCommand.MessageType = messageType;
newCommand.Command = command;
if (argsOnly.Count > 0)
{
string[] argSplit = argsOnly.First().Split(new[] {' '}, cmd.Arguments.Count + 1, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < cmd.Arguments.Count && i <= argSplit.GetUpperBound(0); i++)
string[] argSplit = argsOnly.First().Split(new[] { ' ' }, validArguments.Count, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < validArguments.Count && i <= argSplit.GetUpperBound(0); i++)
{
newCommand.Arguments.Add(cmd.Arguments[i].Name, argSplit[i]);
newCommand.Arguments.Add(validArguments[i].Name, argSplit[i]);
}
}
bool invalidArgs = false;
for (int i = 0; i < newCommand.Arguments.Count; i++)
{
if (validArguments[i].AllowedValues.Count > 0)
{
// Check to see if any of the arguments are invalid
string argVal = newCommand.Arguments[validArguments[i].Name];
if (!validArguments[i].AllowedValues.Exists(val => val.ToLower() == argVal.ToLower()))
{
invalidArgs = true;
string invalidMessage = string.Format("Invalid value for \u0002{0}\u000F in \u0002{1}{2} {3}\u000F. Valid options are \u0002{4}\u000F.", validArguments[i].Name, ServerConfig.CommandPrefix, command, string.Join(" ", validArguments.Select(arg => { if (arg.Required) { return "\u001F" + arg.Name + "\u000F\u0002"; } return "[\u001F" + arg.Name + "\u000F\u0002]"; })), string.Join(", ", validArguments[i].AllowedValues));
switch (messageType)
{
case MessageType.Channel:
case MessageType.Query:
IRC.SendPrivateMessage(location, invalidMessage);
break;
case MessageType.Notice:
IRC.SendNotice(location, invalidMessage);
break;
}
break;
}
}
}
if (cmd.Arguments.FindAll(arg => arg.Required).Count <= newCommand.Arguments.Count)
if (validArguments.FindAll(arg => arg.Required).Count <= newCommand.Arguments.Count)
{
if (CommandReceivedEvent != null)
if (!invalidArgs)
{
if (CommandReceivedEvent != null)
{
CommandReceivedEvent(newCommand);
}
}
}
else
{
string missingArgument = string.Format("Missing a required argument for \u0002{0}{1} {2}\u000F. The required arguments are \u0002{3}\u000F.", ServerConfig.CommandPrefix, command, string.Join(" ", validArguments.Select(arg => { if (arg.Required) { return "\u001F" + arg.Name + "\u000F\u0002"; } return "[\u001F" + arg.Name + "\u000F\u0002]"; })), string.Join(", ", validArguments.Where(arg => arg.Required).Select(arg => arg.Name)));
if (messageType == MessageType.Channel || messageType == MessageType.Query)
{
IRC.SendPrivateMessage(location, missingArgument);
}
else if (messageType == MessageType.Notice)
{
CommandReceivedEvent(newCommand);
IRC.SendNotice(location, missingArgument);
}
}
}

+ 3
- 1
Combot/Combot.csproj View File

@@ -60,16 +60,18 @@
<ItemGroup>
<Compile Include="AccessType.cs" />
<Compile Include="Bot.cs" />
<Compile Include="MessageType.cs" />
<Compile Include="Modules\Command.cs" />
<Compile Include="Configurations\ChannelConfig.cs" />
<Compile Include="Configurations\Config.cs" />
<Compile Include="Configurations\HostConfig.cs" />
<Compile Include="Configurations\JsonHelper.cs" />
<Compile Include="Configurations\ServerConfig.cs" />
<Compile Include="Help.cs" />
<Compile Include="Modules\CommandArgument.cs" />
<Compile Include="Modules\CommandMessage.cs" />
<Compile Include="Modules\ModuleClasses\Help.cs" />
<Compile Include="Modules\ModuleClasses\Invite.cs" />
<Compile Include="Modules\ModuleClasses\Moderation.cs" />
<Compile Include="Modules\ModuleClasses\PingMe.cs" />
<Compile Include="Modules\ModuleClasses\Version.cs" />
<Compile Include="Modules\Option.cs" />

+ 12
- 8
Combot/Configurations/ServerConfig.cs View File

@@ -12,8 +12,7 @@ namespace Combot.Configurations
{
public event Action ModifyEvent;
public string Name { get; set; }
public string Nickname { get; set; }
public string SecondaryNickname { get; set; }
public List<string> Nicknames { get; set; }
public string Realname { get; set; }
public string Username { get; set; }
public string Password { get; set; }
@@ -25,8 +24,11 @@ namespace Combot.Configurations
public List<ChannelConfig> Channels { get; set; }
public List<Module> Modules { get; set; }
public bool AutoConnect { get; set; }
public bool AutoRegister { get; set; }
public string CommandPrefix { get; set; }
public int JoinDelay { get; set; }
public int MaxMessageLength { get; set; }
public int MessageSendDelay { get; set; }

public ServerConfig()
{
@@ -36,21 +38,23 @@ namespace Combot.Configurations
public void SetDefaults()
{
Name = string.Empty;
Nicknames = new List<string>();
Realname = string.Empty;
Username = string.Empty;
Password = string.Empty;
Email = string.Empty;
AutoConnect = false;
AutoRegister = false;
CommandPrefix = string.Empty;
JoinDelay = 0;
MaxMessageLength = 400;
MessageSendDelay = 0;
Owners = new List<string>();
ChannelBlacklist = new List<string>();
NickBlacklist = new List<string>();
Channels = new List<ChannelConfig>();
Modules = new List<Module>();
Hosts = new List<HostConfig>();
Nickname = string.Empty;
SecondaryNickname = string.Empty;
Realname = string.Empty;
Username = string.Empty;
Password = string.Empty;
Email = string.Empty;
}

public void Save()

+ 0
- 18
Combot/Help.cs View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Combot
{
public class Help
{
private Bot _Bot;

public Help(Bot bot)
{
_Bot = bot;
}
}
}

+ 9
- 0
Combot/MessageType.cs View File

@@ -0,0 +1,9 @@
namespace Combot
{
public enum MessageType
{
Channel,
Query,
Notice
}
}

+ 7
- 0
Combot/Modules/Command.cs View File

@@ -11,6 +11,7 @@ namespace Combot.Modules
public List<string> NickBlacklist { get; set; }
public List<string> Triggers { get; set; }
public List<CommandArgument> Arguments { get; set; }
public List<MessageType> AllowedMessageTypes { get; set; }
public List<AccessType> AllowedAccess { get; set; }
public bool ShowHelp { get; set; }
public bool SpamCheck { get; set; }
@@ -34,6 +35,7 @@ namespace Combot.Modules
ChannelBlacklist = new List<string>();
NickBlacklist = new List<string>();
Arguments = new List<CommandArgument>();
AllowedMessageTypes = new List<MessageType>();
AllowedAccess = new List<AccessType>();
ShowHelp = true;
SpamCheck = true;
@@ -66,6 +68,11 @@ namespace Combot.Modules
newArg.Copy(arg);
Arguments.Add(newArg);
}
AllowedMessageTypes = new List<MessageType>();
foreach (MessageType messageType in command.AllowedMessageTypes)
{
AllowedMessageTypes.Add(messageType);
}
AllowedAccess = new List<AccessType>();
foreach (AccessType accessType in command.AllowedAccess)
{

+ 14
- 0
Combot/Modules/CommandArgument.cs View File

@@ -6,6 +6,8 @@ namespace Combot.Modules
{
public string Name { get; set; }
public string Description { get; set; }
public List<string> AllowedValues { get; set; }
public List<MessageType> MessageTypes { get; set; }
public bool Required { get; set; }

public CommandArgument()
@@ -17,6 +19,8 @@ namespace Combot.Modules
{
Name = string.Empty;
Description = string.Empty;
AllowedValues = new List<string>();
MessageTypes = new List<MessageType>();
Required = false;
}

@@ -24,6 +28,16 @@ namespace Combot.Modules
{
Name = argument.Name;
Description = argument.Description;
AllowedValues = new List<string>();
foreach (string value in argument.AllowedValues)
{
AllowedValues.Add(value);
}
MessageTypes = new List<MessageType>();
foreach (MessageType value in argument.MessageTypes)
{
MessageTypes.Add(value);
}
Required = argument.Required;
}
}

+ 4
- 9
Combot/Modules/CommandMessage.cs View File

@@ -7,27 +7,22 @@ namespace Combot.Modules
public class CommandMessage
{
public string Location { get; set; }
public LocationType LocationType { get; set; }
public MessageType MessageType { get; set; }
public Nick Nick { get; set; }
public DateTime TimeStamp { get; set; }
public string Command { get; set; }
public Dictionary<string, dynamic> Arguments { get; set; }
public List<AccessType> Access { get; set; }

public CommandMessage()
{
Location = string.Empty;
LocationType = LocationType.Channel;
MessageType = MessageType.Channel;
Nick = new Nick();
TimeStamp = DateTime.Now;
Command = string.Empty;
Arguments = new Dictionary<string, dynamic>();
Access = new List<AccessType>();
}
}

public enum LocationType
{
Channel,
Query,
Notice
}
}

+ 19
- 2
Combot/Modules/Module.cs View File

@@ -49,17 +49,34 @@ namespace Combot.Modules
List<AccessType> nickAccessTypes = new List<AccessType>() { AccessType.User };
foreach (PrivilegeMode privilege in command.Nick.Privileges)
{
nickAccessTypes.Add(Bot.AccessTypeMapping[privilege]);
nickAccessTypes.Add(Bot.PrivilegeModeMapping[privilege]);
}
if (Bot.ServerConfig.Owners.Contains(command.Nick.Nickname) && command.Nick.Identified)
if (Bot.ServerConfig.Owners.Contains(command.Nick.Nickname) && command.Nick.Modes.Contains(UserMode.r))
{
nickAccessTypes.Add(AccessType.Owner);
}
command.Access.AddRange(nickAccessTypes);
// If they have the correct access for the command, send it
if (cmd.AllowedAccess.Exists(access => nickAccessTypes.Contains(access)))
{
ParseCommand(command);
}
else
{
string noAccessMessage = string.Format("You do not have access to use \u0002{0}\u000F.", command.Command);
switch (command.MessageType)
{
case MessageType.Channel:
Bot.IRC.SendPrivateMessage(command.Location, noAccessMessage);
break;
case MessageType.Query:
Bot.IRC.SendPrivateMessage(command.Nick.Nickname, noAccessMessage);
break;
case MessageType.Notice:
Bot.IRC.SendNotice(command.Nick.Nickname, noAccessMessage);
break;
}
}
}
}


+ 151
- 0
Combot/Modules/ModuleClasses/Help.cs View File

@@ -0,0 +1,151 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Policy;

namespace Combot.Modules.ModuleClasses
{
public class Help : Module
{
public override void Initialize()
{
Bot.CommandReceivedEvent += HandleCommandEvent;
}

public override void ParseCommand(CommandMessage command)
{
Command foundCommand = Commands.Find(c => c.Triggers.Contains(command.Command));

if (foundCommand.Name == "Help")
{
if (command.Arguments.Count == 0)
{
SendFullHelp(command.Nick.Nickname, command.Access);
}
else if (command.Arguments.ContainsKey("Command"))
{
SendCommandHelp(command.Nick.Nickname, command.Access, command.Arguments["Command"].ToString());
}
}
}

private void SendFullHelp(string recipient, List<AccessType> access)
{
Bot.IRC.SendNotice(recipient, string.Format("You have the following commands available to use. " +
"To use them either type \u0002{1}\u001Fcommand\u000F into a channel, send a private message by typing \u0002/msg {0} \u001Fcommand\u000F, or send a notice by typing \u0002/notice {0} \u001Fcommand\u000F. " +
"For more information on a specific command, type \u0002{1}help \u001Fcommand\u000F.",
Bot.IRC.Nickname, Bot.ServerConfig.CommandPrefix));
Bot.IRC.SendNotice(recipient, "\u200B");
foreach (Module module in Bot.Modules)
{
if (module.Commands.Exists(command => command.AllowedAccess.Exists(allowed => access.Contains(allowed)) && command.ShowHelp))
{
Bot.IRC.SendNotice(recipient, string.Format("\u0002\u001F{0} Module\u000F\u0002\u000F", module.Name));
}
module.Commands.ForEach(command =>
{
if (command.AllowedAccess.Exists(allowed => access.Contains(allowed)) && command.ShowHelp)
{
string commandDesc = string.Empty;
if (command.Description != string.Empty)
{
commandDesc = string.Format(" - {0}", command.Description);
}
Bot.IRC.SendNotice(recipient, string.Format("\t\t\u0002{0}\u000F{1}", command.Name, commandDesc));
}
});
}
}

private void SendCommandHelp(string recipient, List<AccessType> access, string command)
{
Module foundModule = Bot.Modules.Find(mod => mod.Commands.Exists(cmd => (cmd.Name == command || cmd.Triggers.Contains(command)) && cmd.ShowHelp));
if (foundModule != null)
{
Command foundCommand = foundModule.Commands.Find(cmd => (cmd.Name == command || cmd.Triggers.Contains(command)));
if (foundCommand != null)
{
if (foundCommand.AllowedAccess.Exists(allowed => access.Contains(allowed)))
{
Bot.IRC.SendNotice(recipient, string.Format("Help information for \u0002{0}\u000F", foundCommand.Name));
if (foundCommand.Description != string.Empty)
{
Bot.IRC.SendNotice(recipient, string.Format("{0}", foundCommand.Description));
}
Bot.IRC.SendNotice(recipient, "\u200B");
for (int i = 0; i < foundCommand.AllowedMessageTypes.Count; i++)
{
MessageType messageType = foundCommand.AllowedMessageTypes[i];
string messageSyntax = string.Empty;
switch (messageType)
{
case MessageType.Channel:
messageSyntax = "\u0002/msg \u001Fchannel\u000F";
break;
case MessageType.Query:
messageSyntax = string.Format("\u0002/msg {0}\u000F", Bot.IRC.Nickname);
break;
case MessageType.Notice:
messageSyntax = string.Format("\u0002/notice {0}\u000F", Bot.IRC.Nickname);
break;
}
List<CommandArgument> validArguments = foundCommand.Arguments.FindAll(arg => arg.MessageTypes.Contains(messageType));
string argHelp = string.Empty;
Bot.IRC.SendNotice(recipient, string.Format("Message Type: \u0002{0}\u000F", messageType.ToString()));
if (validArguments.Count > 0)
{
argHelp = string.Format(" \u0002\u001F{0}\u000F", string.Join("\u000F \u0002", validArguments.Select(arg =>
{
if (arg.Required)
{
return "\u001F" + arg.Name + "\u000F\u0002";
}
return "[\u001F" + arg.Name + "\u000F\u0002]";
})));
}
foundCommand.Triggers.ForEach(trigger =>
{
Bot.IRC.SendNotice(recipient, string.Format("\t\tSyntax: {0} {1}\u0002{2}\u000F{3}", messageSyntax, Bot.ServerConfig.CommandPrefix, trigger, argHelp));
});

if (validArguments.Count > 0)
{
Bot.IRC.SendNotice(recipient, "\u200B");
validArguments.ForEach(arg =>
{
string commandDesc = string.Empty;
if (arg.Description != string.Empty)
{
commandDesc = string.Format(" - {0}", arg.Description);
}
string required = string.Empty;
if (arg.Required)
{
required = " - Required";
}
Bot.IRC.SendNotice(recipient, string.Format("\t\t\u0002{0}{1}\u000F{2}", arg.Name, required, commandDesc));
if (arg.AllowedValues.Count > 0)
{
Bot.IRC.SendNotice(recipient, string.Format("\t\t\t\t- Allowed Values: \u0002{0}\u000F", string.Join(", ", arg.AllowedValues)));
}
});
}
Bot.IRC.SendNotice(recipient, "\u200B");
}
}
else
{
Bot.IRC.SendNotice(recipient, string.Format("You do not have access to view help on \u0002{0}\u000F.", command));
}
}
else
{
Bot.IRC.SendNotice(recipient, string.Format("The command \u0002{0}\u000F does not exist.", command));
}
}
else
{
Bot.IRC.SendNotice(recipient, string.Format("The command \u0002{0}\u000F does not exist.", command));
}
}
}
}

+ 236
- 0
Combot/Modules/ModuleClasses/Moderation.cs View File

@@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Combot.IRCServices;
using Combot.IRCServices.Messaging;

namespace Combot.Modules.ModuleClasses
{
public class Moderation : Module
{
public override void Initialize()
{
Bot.CommandReceivedEvent += HandleCommandEvent;
}

public override void ParseCommand(CommandMessage command)
{
Command foundCommand = Commands.Find(c => c.Triggers.Contains(command.Command));

switch (foundCommand.Name)
{
// Privilege Mode Commands
case "Founder":
ModifyUserPrivilege(true, command, ChannelMode.q);
break;
case "Remove Founder":
ModifyUserPrivilege(false, command, ChannelMode.q);
break;
case "SOP":
ModifyUserPrivilege(true, command, ChannelMode.a);
break;
case "Remove SOP":
ModifyUserPrivilege(false, command, ChannelMode.a);
break;
case "OP":
ModifyUserPrivilege(true, command, ChannelMode.o);
break;
case "Remove OP":
ModifyUserPrivilege(false, command, ChannelMode.o);
break;
case "HOP":
ModifyUserPrivilege(true, command, ChannelMode.h);
break;
case "Remove HOP":
ModifyUserPrivilege(false, command, ChannelMode.h);
break;
case "Voice":
ModifyUserPrivilege(true, command, ChannelMode.v);
break;
case "Remove Voice":
ModifyUserPrivilege(false, command, ChannelMode.v);
break;
// Auto Privilege Management
case "ASOP":
ModifyAutoUserPrivilege("SOP", command, ChannelMode.a);
break;
case "AOP":
ModifyAutoUserPrivilege("AOP", command, ChannelMode.o);
break;
case "AHOP":
ModifyAutoUserPrivilege("HOP", command, ChannelMode.h);
break;
case "AVoice":
ModifyAutoUserPrivilege("VOP", command, ChannelMode.v);
break;
case "Mode":
ModifyChannelModes(command);
break;
case "Topic":
ModifyChannelTopic(command);
break;
case "Invite":
break;
case "Ban":
break;
case "UnBan":
break;
case "Clear Ban":
break;
case "Kick Ban":
break;
case "Timed Ban":
break;
case "Timed Kick Ban":
break;
case "Kick":
break;
case "Kick Me":
break;
}
}

private void ModifyUserPrivilege(bool set, CommandMessage command, ChannelMode mode)
{
string channel = command.Arguments.ContainsKey("Channel") ? command.Arguments["Channel"] : command.Location;
if (Bot.CheckChannelAccess(channel, command.Nick.Nickname, Bot.ChannelModeMapping[mode]))
{
SetMode(set, channel, mode, command.Arguments["Nickname"]);
}
else
{
string noAccessMessage = string.Format("You do not have access to set mode \u0002+{0}\u000F for \u0002{1}\u000F on \u0002{2}\u000F.", mode, command.Arguments["Nickname"], channel);
switch (command.MessageType)
{
case MessageType.Channel:
Bot.IRC.SendPrivateMessage(command.Location, noAccessMessage);
break;
case MessageType.Query:
Bot.IRC.SendPrivateMessage(command.Nick.Nickname, noAccessMessage);
break;
case MessageType.Notice:
Bot.IRC.SendNotice(command.Nick.Nickname, noAccessMessage);
break;
}
}
}

private void ModifyAutoUserPrivilege(string optionCommand, CommandMessage command, ChannelMode mode)
{
bool set = true;
if (command.Arguments["Option"].ToLower() == "del")
{
set = false;
}
string channel = command.Arguments.ContainsKey("Channel") ? command.Arguments["Channel"] : command.Location;
if (Bot.CheckChannelAccess(channel, command.Nick.Nickname, Bot.ChannelModeMapping[mode]))
{
SetMode(set, channel, mode, command.Arguments["Nickname"]);
Bot.IRC.SendPrivateMessage("ChanServ", string.Format("{0} {1} {2} {3}", optionCommand, channel, command.Arguments["Option"], command.Arguments["Nickname"]));
}
else
{
string noAccessMessage = string.Format("You do not have access to \u0002{0}\u000F \u0002{1}\u000F to the \u0002{2}\u000F list on \u0002{3}\u000F.", command.Arguments["Option"].ToLower(), command.Arguments["Nickname"], optionCommand, channel);
switch (command.MessageType)
{
case MessageType.Channel:
Bot.IRC.SendPrivateMessage(command.Location, noAccessMessage);
break;
case MessageType.Query:
Bot.IRC.SendPrivateMessage(command.Nick.Nickname, noAccessMessage);
break;
case MessageType.Notice:
Bot.IRC.SendNotice(command.Nick.Nickname, noAccessMessage);
break;
}
}
}

private void ModifyChannelModes(CommandMessage command)
{
List<ChannelModeInfo> modeList = new List<ChannelModeInfo>();
if (command.Arguments.ContainsKey("Parameters"))
{
modeList = Bot.IRC.ParseChannelModeString(command.Arguments["Modes"], command.Arguments["Parameters"]);
}
else
{
modeList = Bot.IRC.ParseChannelModeString(command.Arguments["Modes"], string.Empty);
}
string channel = command.Arguments.ContainsKey("Channel") ? command.Arguments["Channel"] : command.Location;
bool allowedMode = true;
ChannelMode mode = ChannelMode.q;
for (int i = 0; i < modeList.Count; i++)
{
switch (modeList[i].Mode)
{
case ChannelMode.v:
case ChannelMode.h:
case ChannelMode.o:
case ChannelMode.a:
case ChannelMode.q:
allowedMode = Bot.CheckChannelAccess(channel, command.Nick.Nickname, Bot.ChannelModeMapping[modeList[i].Mode]);
if (!allowedMode)
{
mode = modeList[i].Mode;
}
break;
}
}
if (allowedMode)
{
Bot.IRC.SendMode(channel, modeList);
}
else
{
string noAccessMessage = string.Format("You do not have access to set mode \u0002+{0}\u000F on \u0002{1}\u000F.", mode, channel);
switch (command.MessageType)
{
case MessageType.Channel:
Bot.IRC.SendPrivateMessage(command.Location, noAccessMessage);
break;
case MessageType.Query:
Bot.IRC.SendPrivateMessage(command.Nick.Nickname, noAccessMessage);
break;
case MessageType.Notice:
Bot.IRC.SendNotice(command.Nick.Nickname, noAccessMessage);
break;
}
}
}

private void SetMode(bool set, string channel, ChannelMode mode, string nickname)
{
ChannelModeInfo modeInfo = new ChannelModeInfo();
modeInfo.Mode = mode;
modeInfo.Parameter = nickname;
modeInfo.Set = set;
Bot.IRC.SendMode(channel, modeInfo);
}

private void ModifyChannelTopic(CommandMessage command)
{
string channel = command.Arguments.ContainsKey("Channel") ? command.Arguments["Channel"] : command.Location;
if (Bot.CheckChannelAccess(channel, command.Nick.Nickname, command.Access))
{
Bot.IRC.SendTopic(channel, command.Arguments["Message"]);
}
else
{
string noAccessMessage = string.Format("You do not have access to change the topic on \u0002{0}\u000F.", channel);
switch (command.MessageType)
{
case MessageType.Channel:
Bot.IRC.SendPrivateMessage(command.Location, noAccessMessage);
break;
case MessageType.Query:
Bot.IRC.SendPrivateMessage(command.Nick.Nickname, noAccessMessage);
break;
case MessageType.Notice:
Bot.IRC.SendNotice(command.Nick.Nickname, noAccessMessage);
break;
}
}
}
}
}

+ 10
- 8
Combot/Modules/ModuleClasses/PingMe.cs View File

@@ -22,13 +22,15 @@ namespace Combot.Modules.ModuleClasses

public override void ParseCommand(CommandMessage command)
{
if (Commands.Find(cmd => cmd.Name == "Ping Me").Triggers.Contains(command.Command))
Command foundCommand = Commands.Find(c => c.Triggers.Contains(command.Command));

if (foundCommand.Name == "Ping Me")
{
int epoch = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
PingItem tmpItem = new PingItem();
tmpItem.Nick = command.Nick.Nickname;
tmpItem.Location = command.Location;
tmpItem.LocationType = command.LocationType;
tmpItem.MessageType = command.MessageType;
tmpItem.Timestamp = DateTime.Now;
listLock.EnterWriteLock();
if (pingList.Exists(item => item.Nick == command.Nick.Nickname))
@@ -74,15 +76,15 @@ namespace Combot.Modules.ModuleClasses
{
timeString += difTime.Milliseconds.ToString() + " Milliseconds";
}
switch (pingItem.LocationType)
switch (pingItem.MessageType)
{
case LocationType.Channel:
case MessageType.Channel:
Bot.IRC.SendPrivateMessage(pingItem.Location, string.Format("{0}, your ping is {1}", pingItem.Nick, timeString));
break;
case LocationType.Notice:
case MessageType.Notice:
Bot.IRC.SendNotice(pingItem.Nick, string.Format("Your ping is {0}", timeString));
break;
case LocationType.Query:
case MessageType.Query:
Bot.IRC.SendPrivateMessage(pingItem.Nick, string.Format("Your ping is {0}", timeString));
break;
}
@@ -97,14 +99,14 @@ namespace Combot.Modules.ModuleClasses
{
public string Nick { get; set; }
public string Location { get; set; }
public LocationType LocationType { get; set; }
public MessageType MessageType { get; set; }
public DateTime Timestamp { get; set; }

public PingItem()
{
Nick = string.Empty;
Location = string.Empty;
LocationType = LocationType.Channel;
MessageType = MessageType.Channel;
Timestamp = DateTime.Now;
}
}

+ 22
- 20
Combot/Modules/ModuleClasses/Version.cs View File

@@ -22,11 +22,13 @@ namespace Combot.Modules.ModuleClasses

public override void ParseCommand(CommandMessage command)
{
if (Commands.Find(cmd => cmd.Name == "Version Check").Triggers.Contains(command.Command))
Command foundCommand = Commands.Find(c => c.Triggers.Contains(command.Command));

if (foundCommand.Name == "Version Check")
{
VersionItem tmpItem = new VersionItem();
tmpItem.Location = command.Location;
tmpItem.LocationType = command.LocationType;
tmpItem.MessageType = command.MessageType;
tmpItem.Nick = command.Arguments["Nickname"];
listLock.EnterWriteLock();
if (versionList.Exists(item => item.Nick == command.Arguments["Nickname"]))
@@ -52,20 +54,20 @@ namespace Combot.Modules.ModuleClasses
if (message.Command == "VERSION")
{
listLock.EnterReadLock();
VersionItem versionItem = versionList.Find(item => item.Nick == message.Sender.Nickname);
VersionItem versionItem = versionList.Find(item => item.Nick.ToLower() == message.Sender.Nickname.ToLower());
listLock.ExitReadLock();
if (versionItem != null)
{
switch (versionItem.LocationType)
switch (versionItem.MessageType)
{
case LocationType.Channel:
case MessageType.Channel:
Bot.IRC.SendPrivateMessage(versionItem.Location, string.Format("[{0}] Using version: {1}", versionItem.Nick, message.Arguments));
break;
case LocationType.Query:
Bot.IRC.SendPrivateMessage(versionItem.Nick, string.Format("[{0}] Using version: {1}", versionItem.Nick, message.Arguments));
case MessageType.Query:
Bot.IRC.SendPrivateMessage(message.Sender.Nickname, string.Format("[{0}] Using version: {1}", versionItem.Nick, message.Arguments));
break;
case LocationType.Notice:
Bot.IRC.SendNotice(versionItem.Nick, string.Format("[{0}] Using version: {1}", versionItem.Nick, message.Arguments));
case MessageType.Notice:
Bot.IRC.SendNotice(message.Sender.Nickname, string.Format("[{0}] Using version: {1}", versionItem.Nick, message.Arguments));
break;
}
listLock.EnterWriteLock();
@@ -74,19 +76,19 @@ namespace Combot.Modules.ModuleClasses
}
}
}
}

public class VersionItem
{
public string Nick { get; set; }
public string Location { get; set; }
public LocationType LocationType { get; set; }

public VersionItem()
private class VersionItem
{
Nick = string.Empty;
Location = string.Empty;
LocationType = LocationType.Channel;
public string Nick { get; set; }
public string Location { get; set; }
public MessageType MessageType { get; set; }

public VersionItem()
{
Nick = string.Empty;
Location = string.Empty;
MessageType = MessageType.Channel;
}
}
}
}

+ 0
- 9
Combot/Types.cs View File

@@ -17,15 +17,6 @@ namespace Combot
Framework = 3
}

public enum MessageType
{
Service = 0,
Channel = 1,
Query = 2,
Notice = 3,
CTCP = 4
}

public class BotError
{
public ErrorType Type { get; set; }

+ 0
- 4
IRCServices/Channel.cs View File

@@ -11,8 +11,6 @@ namespace Combot.IRCServices
public string Name { get; set; }
public string Topic { get; set; }
public string Key { get; set; }
public bool AutoJoin { get; set; }
public bool Joined { get; set; }
public DateTime Registration { get; set; }
public List<string> Bans { get; set; }
public List<ChannelMode> Modes { get; set; }
@@ -23,8 +21,6 @@ namespace Combot.IRCServices
Name = string.Empty;
Topic = string.Empty;
Key = string.Empty;
AutoJoin = false;
Joined = false;
Registration = DateTime.Now;
Bans = new List<string>();
Modes = new List<ChannelMode>();

+ 97
- 10
IRCServices/IRC.cs View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -17,20 +18,30 @@ namespace Combot.IRCServices
public event Action ConnectEvent;
public event Action DisconnectEvent;
public event Action<TCPError> TCPErrorEvent;
public string Nickname { get; set; }
public string Nickname;
public Dictionary<string, PrivilegeMode> PrivilegeMapping = new Dictionary<string, PrivilegeMode>() { { "+", PrivilegeMode.v }, { "%", PrivilegeMode.h }, { "@", PrivilegeMode.o }, { "&", PrivilegeMode.a }, { "~", PrivilegeMode.q } };

private int MaxMessageLength;
private int MessageSendDelay;
private DateTime LastMessageSend;
private int ReadTimeout;
private int AllowedFailedReads;
private Thread TCPReader;
private event Action<string> TCPMessageEvent;
private readonly TCPInterface _TCP;
private readonly ReaderWriterLockSlim ChannelRWLock;

public IRC()
public IRC(int maxMessageLength, int messageSendDelay = 0, int readTimeout = 5000, int allowedFailedReads = 0)
{
_TCP = new TCPInterface();
Message = new Messages(this);
Nickname = string.Empty;
ChannelRWLock = new ReaderWriterLockSlim();
LastMessageSend = DateTime.Now;
MaxMessageLength = maxMessageLength;
MessageSendDelay = messageSendDelay;
ReadTimeout = readTimeout;
AllowedFailedReads = allowedFailedReads;

TCPMessageEvent += Message.ParseTCPMessage;
_TCP.TCPConnectionEvent += HandleTCPConnection;
@@ -55,12 +66,12 @@ namespace Combot.IRCServices
/// <param name="readTimeout">The timeout for read operations in milliseconds.</param>
/// <param name="allowedFailedCount">Number of times a read can fail before disconnecting.</param>
/// <returns></returns>
public bool Connect(IPAddress IP, int port, int readTimeout = 5000, int allowedFailedCount = 0)
public bool Connect(IPAddress IP, int port)
{
bool result = false;
if (!_TCP.Connected)
{
result = _TCP.Connect(IP, port, readTimeout, allowedFailedCount);
result = _TCP.Connect(IP, port, ReadTimeout, AllowedFailedReads);
if (result)
{
TCPReader = new Thread(ReadTCPMessages);
@@ -114,6 +125,71 @@ namespace Combot.IRCServices
SendUser(nick.Username, nick.Host, serverName, nick.Realname);
}

/// <summary>
/// Parses a given mode and parameter string to generate a channel mode list.
/// </summary>
/// <param name="modeString">The mode string that contains the mode info.</param>
/// <param name="parameterString">The parameter string that is associated with the mode info.</param>
/// <returns></returns>
public List<ChannelModeInfo> ParseChannelModeString(string modeString, string parameterString)
{
string[] modeArgs = parameterString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
char[] modeInfo = modeString.ToCharArray();
bool set = true;
int argIndex = 0;
List<ChannelModeInfo> modeInfos = new List<ChannelModeInfo>();
foreach (char mode in modeInfo)
{
if (mode.Equals('-'))
{
set = false;
}
else if (mode.Equals('+'))
{
set = true;
}
else
{
ChannelModeInfo newMode = new ChannelModeInfo();
newMode.Set = set;
ChannelMode foundMode;
bool validMode = Enum.TryParse(mode.ToString(), false, out foundMode);
if (validMode)
{
newMode.Mode = foundMode;
if (modeArgs.GetUpperBound(0) >= argIndex)
{
switch (newMode.Mode)
{
case ChannelMode.k:
case ChannelMode.l:
case ChannelMode.b:
case ChannelMode.e:
case ChannelMode.I:
case ChannelMode.v:
case ChannelMode.h:
case ChannelMode.o:
case ChannelMode.a:
case ChannelMode.q:
newMode.Parameter = modeArgs[argIndex];
argIndex++;
break;
default:
newMode.Parameter = string.Empty;
break;
}
}
else
{
newMode.Parameter = string.Empty;
}
modeInfos.Add(newMode);
}
}
}
return modeInfos;
}

private void ReadTCPMessages()
{
while (_TCP.Connected)
@@ -324,6 +400,7 @@ namespace Combot.IRCServices
break;
}
}
SendWho(channel.Name);
}
ChannelRWLock.ExitWriteLock();
}
@@ -393,10 +470,6 @@ namespace Combot.IRCServices
{
Channel newChannel = new Channel();
newChannel.Name = e.Channel;
if (e.Nick.Nickname == Nickname)
{
newChannel.Joined = true;
}
newChannel.Nicks.Add(e.Nick);
Channels.Add(newChannel);
SendWho(newChannel.Name);
@@ -415,7 +488,14 @@ namespace Combot.IRCServices
Channel channel = Channels.Find(chan => chan.Name == e.Channel);
if (channel != null)
{
channel.RemoveNick(e.Nick.Nickname);
if (e.Nick.Nickname == Nickname)
{
Channels.Remove(channel);
}
else
{
channel.RemoveNick(e.Nick.Nickname);
}
}
ChannelRWLock.ExitWriteLock();
}
@@ -431,7 +511,14 @@ namespace Combot.IRCServices
Channel channel = Channels.Find(chan => chan.Name == e.Channel);
if (channel != null)
{
channel.RemoveNick(e.Nick.Nickname);
if (e.KickedNick.Nickname == Nickname)
{
Channels.Remove(channel);
}
else
{
channel.RemoveNick(e.KickedNick.Nickname);
}
}
ChannelRWLock.ExitWriteLock();
}

+ 43
- 12
IRCServices/IRCSend.cs View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Threading;

namespace Combot.IRCServices
{
@@ -16,7 +17,27 @@ namespace Combot.IRCServices
/// <param name="message"></param>
public void SendPrivateMessage(string recipient, string message)
{
SendTCPMessage(string.Format("PRIVMSG {0} :{1}", recipient, message));
TimeSpan sinceLastMessage = (DateTime.Now - LastMessageSend);
if (sinceLastMessage.TotalMilliseconds < MessageSendDelay)
{
Thread.Sleep((int) sinceLastMessage.TotalMilliseconds);
SendPrivateMessage(recipient, message);
}
else
{
LastMessageSend = DateTime.Now;
if (message.Length > MaxMessageLength)
{
string subMessage = message.Substring(0, MaxMessageLength);
string nextMessage = message.Remove(0, MaxMessageLength);
SendTCPMessage(string.Format("PRIVMSG {0} :{1}", recipient, subMessage));
SendPrivateMessage(recipient, nextMessage);
}
else
{
SendTCPMessage(string.Format("PRIVMSG {0} :{1}", recipient, message));
}
}
}

public void SendPrivateMessage(List<string> recipients, string message)
@@ -26,8 +47,7 @@ namespace Combot.IRCServices
{
recipient_list += recipient + ",";
}

SendTCPMessage(string.Format("PRIVMSG {0} :{1}", recipient_list.TrimEnd(','), message));
SendPrivateMessage(recipient_list.TrimEnd(','), message);
}

/// <summary>
@@ -37,7 +57,17 @@ namespace Combot.IRCServices
/// <param name="message"></param>
public void SendNotice(string recipient, string message)
{
SendTCPMessage(string.Format("NOTICE {0} :{1}", recipient, message));
if (message.Length > MaxMessageLength)
{
string subMessage = message.Substring(0, MaxMessageLength);
string nextMessage = message.Remove(0, MaxMessageLength);
SendTCPMessage(string.Format("NOTICE {0} :{1}", recipient, message));
SendNotice(recipient, nextMessage);
}
else
{
SendTCPMessage(string.Format("NOTICE {0} :{1}", recipient, message));
}
}

public void SendNotice(List<string> recipients, string message)
@@ -48,7 +78,7 @@ namespace Combot.IRCServices
recipient_list += recipient + ",";
}

SendTCPMessage(string.Format("NOTICE {0} :{1}", recipient_list.TrimEnd(','), message));
SendNotice(recipient_list.TrimEnd(','), message);
}

/// <summary>
@@ -77,7 +107,7 @@ namespace Combot.IRCServices
{
message = " " + message;
}
SendTCPMessage(string.Format("PRIVMSG {0} :\u0001{1}{2}\u0001", recipient_list.TrimEnd(','), command, message));
SendCTCPMessage(recipient_list.TrimEnd(','), command, message);
}

/// <summary>
@@ -106,7 +136,7 @@ namespace Combot.IRCServices
{
message = " " + message;
}
SendTCPMessage(string.Format("NOTICE {0} :\u0001{1}{2}\u0001", recipient_list.TrimEnd(','), command, message));
SendCTCPNotice(recipient_list.TrimEnd(','), command, message);
}

/// <summary>
@@ -167,7 +197,7 @@ namespace Combot.IRCServices
public void SendJoin(string channel, string key = "")
{
string message = string.Empty;
message = (key != string.Empty) ? string.Format("{0}; {1}", channel, key) : channel;
message = (key != string.Empty) ? string.Format("{0} {1}", channel, key) : channel;
SendTCPMessage(string.Format("JOIN {0}", message));
}

@@ -191,7 +221,7 @@ namespace Combot.IRCServices
channel_string = channel_string.TrimEnd(',');
key_string = key_string.TrimEnd(',');

message = (key_string != string.Empty) ? string.Format("{0}; {1}", channel_string, key_string) : channel_string;
message = (key_string != string.Empty) ? string.Format("{0} {1}", channel_string, key_string) : channel_string;
SendTCPMessage(string.Format("JOIN {0}", message));
}

@@ -212,7 +242,7 @@ namespace Combot.IRCServices
channel_list += channel + ",";
}

SendTCPMessage(string.Format("PART {0}", channel_list.TrimEnd(',')));
SendPart(channel_list.TrimEnd(','));
}


@@ -234,6 +264,7 @@ namespace Combot.IRCServices
SendMode(channel, modeInfo);
}
}

public void SendMode(string nick, UserModeInfo modeInfo)
{
string mode_set = modeInfo.Set ? "+" : "-";
@@ -282,7 +313,7 @@ namespace Combot.IRCServices
{
channel_list += channel + ",";
}
SendTCPMessage(string.Format("NAMES {0}", channel_list.TrimEnd(',')));
SendNames(channel_list.TrimEnd(','));
}

/// <summary>
@@ -305,7 +336,7 @@ namespace Combot.IRCServices
{
channel_list += channel + ",";
}
SendTCPMessage(string.Format("LIST {0}", channel_list.TrimEnd(',')));
SendList(channel_list.TrimEnd(','));
}

/// <summary>

+ 3
- 47
IRCServices/Messaging/Messages.cs View File

@@ -244,53 +244,9 @@ namespace Combot.IRCServices.Messaging
modeMsg.Nick = new Nick() { Nickname = senderNick, Realname = senderRealname, Host = senderHost };

string[] modeArgs = args.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
char[] modeInfo = modeArgs[0].TrimStart(':').ToCharArray();
bool set = true;
int argIndex = 1;
foreach (char mode in modeInfo)
{
if (mode.Equals('-'))
{
set = false;
}
else if (mode.Equals('+'))
{
set = true;
}
else
{
ChannelModeInfo newMode = new ChannelModeInfo();
newMode.Set = set;
newMode.Mode = (ChannelMode)Enum.Parse(typeof(ChannelMode), mode.ToString());
if (modeArgs.GetUpperBound(0) >= argIndex)
{
switch (newMode.Mode)
{
case ChannelMode.k:
case ChannelMode.l:
case ChannelMode.v:
case ChannelMode.h:
case ChannelMode.o:
case ChannelMode.a:
case ChannelMode.q:
case ChannelMode.b:
case ChannelMode.e:
case ChannelMode.I:
newMode.Parameter = modeArgs[argIndex];
argIndex++;
break;
default:
newMode.Parameter = string.Empty;
break;
}
}
else
{
newMode.Parameter = string.Empty;
}
modeMsg.Modes.Add(newMode);
}
}
List<string> argList = modeArgs.ToList();
argList.RemoveAt(0);
modeMsg.Modes.AddRange(_IRC.ParseChannelModeString(modeArgs[0].TrimStart(':'), string.Join(" ", argList)));

await Task.Run(() =>
{

+ 0
- 6
IRCServices/Nick.cs View File

@@ -13,8 +13,6 @@ namespace Combot.IRCServices
public string Host { get; set; }
public string Nickname { get; set; }
public string Password { get; set; }
public bool Identified { get; set; }
public bool Registered { get; set; }
public List<UserMode> Modes { get; set; }
public List<PrivilegeMode> Privileges { get; set; }

@@ -25,8 +23,6 @@ namespace Combot.IRCServices
Host = string.Empty;
Nickname = string.Empty;
Password = string.Empty;
Identified = false;
Registered = false;
Modes = new List<UserMode>();
Privileges = new List<PrivilegeMode>();
}
@@ -38,8 +34,6 @@ namespace Combot.IRCServices
Host = nick.Host;
Nickname = nick.Nickname;
Password = nick.Password;
Identified = nick.Identified;
Registered = nick.Registered;
Modes = new List<UserMode>();
Modes.AddRange(nick.Modes);
Privileges = new List<PrivilegeMode>();

+ 1
- 1
IRCServices/Types.cs View File

@@ -66,7 +66,7 @@ namespace Combot.IRCServices
O,
[Description("Private")]
p,
[Description("Owner")]
[Description("Founder")]
q,
[Description("No Kicks Allowed")]
Q,

+ 0
- 102
Interface/ViewModels/MainViewModel.cs View File

@@ -33,108 +33,6 @@ namespace Interface.ViewModels
public MainViewModel()
{
ApplicationTitle = "Combot";
//Config.LoadServers();

ServerConfig serverConfig = new ServerConfig();
serverConfig.AutoConnect = true;
serverConfig.Channels = new List<ChannelConfig>
{
new ChannelConfig()
{
Name = "#testing",
Key = string.Empty
}/*,
new ChannelConfig()
{
Name = "#rice",
Key = string.Empty
}*/
};
serverConfig.Name = "Rizon";
serverConfig.Nickname = "Combot_V3";
serverConfig.Realname = "Combot_Realname";
serverConfig.Username = "Combot_Username";
serverConfig.Password = "24121exe";
serverConfig.CommandPrefix = ".";
serverConfig.JoinDelay = 1000;
serverConfig.Hosts = new List<HostConfig> { new HostConfig() { Host = "irc.rizon.net", Port = 6667 } };
serverConfig.Modules = new List<Module>
{
new Module
{
Name = "Ping Me",
ClassName = "PingMe",
Enabled = true,
Commands = new List<Command>
{
new Command
{
Name = "Ping Me",
Description = "Checks the time it takes for a PING to be returned from a nick.",
AllowedAccess = new List<AccessType>
{
AccessType.User,
AccessType.Voice,
AccessType.HalfOperator,
AccessType.Operator,
AccessType.SuperOperator,
AccessType.Founder,
AccessType.Owner
},
Triggers = new List<string>
{
"pingme"
}
}
}
},
new Module
{
Name = "Invite",
ClassName = "Invite",
Enabled = true
},
new Module
{
Name = "Version",
ClassName = "Version",
Enabled = true,
Commands = new List<Command>
{
new Command
{
Name = "Version Check",
Description = "Sends a version CTCP request and displays the response.",
AllowedAccess = new List<AccessType>
{
AccessType.User,
AccessType.Voice,
AccessType.HalfOperator,
AccessType.Operator,
AccessType.SuperOperator,
AccessType.Founder,
AccessType.Owner
},
Triggers = new List<string>
{
"version",
"ver"
},
Arguments = new List<CommandArgument>
{
new CommandArgument
{
Name = "Nickname",
Description = "The nickname you want to query for version information.",
Required = true
}
}
}
}
}
};
Config.Servers.Add(serverConfig);
Config.SaveServers();
Config.LoadServers();

foreach (ServerConfig server in Config.Servers)

+ 5
- 632
readme.md View File

@@ -6,41 +6,17 @@ The IRCBot is designed to provide an all-in-one solution for those who wish to r

## Feature Set

* Channel Moderation
* Custom Access Levels
* Full GUI Interface
* Console Interface
* Bot API for making custom interfaces
* Owner Control Functions
* Automatic nick registration
* Ghost on Nick in Use
* Flood Protection
* 4chan thread/reply viewing and searching
* URL/file parsing
* Google Search
* Wolfram Alpha Search
* SED
* Ping Requests
* Last Seen Nick
* Channel Rules
* Weather and Forcasts
* Magic 8ball
* Pass the Hbomb game
* Fun commands
* Chat Protocol (A.L.I.C.E.)
* Channel Roll Call
* Version Checker
* Idle
* Dice Rolls
* GitHub issue submission
* Full logging support
* Custom Alarms
* Surveys

## Installation - Windows

1) Download the Release.zip from the latest release in https://github.com/uncled1023/IRCBot/releases and extract the files to a directory of your choice.<br>
2) Run IRCBot-GUI.exe or IRCBot-Console.exe
1) Download the Release.zip from the latest release in https://github.com/uncled1023/Combot/releases and extract the files to a directory of your choice.<br>
2) Run Combot.exe

## Installation - Linux (Alpha)

@@ -49,608 +25,11 @@ The IRCBot is designed to provide an all-in-one solution for those who wish to r
* config/servers.xml contains all the server settings for the bot. Edit the default server and add more <server></server> if you want.<br>
* config/modules.xml contains all the module config settings. It is usually a good idea to separate them into separate folders/files for each server. You specify the modules.xml file in the server config.<br>

4) Open a terminal emulator and cd it to the directory with the IRCBot-Console.exe.<br>
5) Type: `mono IRCBot-Console.exe`
4) Open a terminal emulator and cd it to the directory with the Combot.exe.<br>
5) Type: `mono Combot.exe`

* Current Limitations: Does not display any output, some functions may not work, buggy.

## Configuration

When you first start up the IRC Bot, you will need to add your details into the configuration. You can do this one of two ways: By using the configuration manager in tools, or by editing the config.xml directly in the /config/ folder. The first is preferred as to reduce the chance of messing up the configuration file.

After clicking tools->configuration, you will then be presented with the configuration manager. From here, you can Add a new server, and configure bot settings

Once you have added your server, just click "Connect" and if you entered your configuration correctly your bot will then connect to the server and channels you specified.
Adding a Server

To add a new server, click the "Add Server" button in the Configuration window. The required fields are as follows:

<table>
<tr>
<th>Property</th><th>Format</th><th>Default Value</th>
</tr>
<tr>
<td>Server Name</td><td>string</td><td></td>
</tr>
<tr>
<td>Server Address</td><td>irc.hostname.net</td><td></td>
</tr>
<tr>
<td>Port Number</td><td>int32</td><td>6667</td>
</tr>
<tr>
<td>Name</td><td>string</td><td></td>
</tr>
<tr>
<td>Nick</td><td>string</td><td></td>
</tr>
</table>

Each server has it's own settings for the Modules and Commands within the modules. You also can control the access level for each XOP level within the Op Levels Configuration tab.

## Command List

Each command has the following properties:

<table>
<tr>
<th>Property</th><th>Format</th>
</tr>
<tr>
<td>name</td><td>string</td>
</tr>
<tr>
<td>description</td><td>string</td>
</tr>
<tr>
<td>triggers</td><td>comma separated string array</td>
</tr>
<tr>
<td>syntax</td><td>string</td>
</tr>
<tr>
<td>access_level</td><td>int32</td>
</tr>
<tr>
<td>blacklist</td><td>comma separated string array</td>
</tr>
<tr>
<td>show_help</td><td>boolean</td>
</tr>
<tr>
<td>spam_check</td><td>boolean</td>
</tr>
</table>

### Fortunes

* `fortune` Displays a fortune.<br>
Usage: `fortune`

### Trivia

* `trivia` Starts a new game of trivia.<br>
Usage: `trivia`

* `stoptrivia` Stops a running game of trivia.<br>
Aliases: `strivia`<br>
Usage: `stoptrivia`

* `scores` Displays the top 10 scores.<br>
Usage: `scores`

* `score` Shows your current rank and score.<br>
Usage: `score`

### 4chan

* `4chan` Views a specific thread ID or OP number of a board, or a list of boards on 4chan.<br>
Usage: `4chan [{board}] [{(#)thread_ID|OP_index}] [{(#)reply_ID|reply_index}]`

* `next_thread` Displays the next OP on the current board.<br>
Aliases: `nt`<br>
Usage: `next_thread`

* `next_reply` Displays the next reply on the current thread.<br>
Aliases: `nr`<br>
Usage: `nr`

* `4chansearch` Searchs a specific board for a thread that contains the specified query.<br>
Aliases: `4chs`, `4cs`, `4chans`<br>
Usage: `4chansearch {board} {query}`

### Is It Up

* `isitup` Checks if the web address specified is accessible from the bot.<br>
Aliases: `isup`<br>
Usage: `isitup {url}`

### Ping Me

* `pingme` Gets the ping time between the bot and the client requesting the ping.<br>
Usage: `pingme`

### Seen

* `seen` Displays the last time the nick has been seen in the channel.<br>
Usage: `seen`

### Access

* `setaccess` Adds the specified nick to the access list with the specified level.<br>
Aliases: `addaccess`<br>
Usage: `setaccess {nick} {access_level}`

* `delaccess` Removes the specified access level from the nick.<br>
Usage: `delaccess {nick} {access_level}`

* `listaccess` Lists all the users with access on the channel and their level.<br>
Aliases: `accesslist`<br>
Usage: `listaccess`

* `getaccess` Displays the current access level of a user.<br>
Aliases: `access`<br>
Usage: `getaccess [{channel}] {nick}`

### Moderation

* `founder` Sets the nick to Owner of the chan.<br>
Usage: `founder {nick}`

* `defounder` Unsets the nick as Owner of the chan.<br>
Usage: `defounder {nick}

* `asop` Adds the nick to the Auto Super Op List.<br>
Usage: `asop {nick}`

* `deasop` Removes the nick from the Auto Super Op List.<br>
Usage: `deasop {nick}`

* `sop` Sets the nick as Super Op.<br>
Usage: `sop {nick}`

* `desop` Removes the nick as Super Op.<br>
Usage: `desop {nick}`

* `aop` Adds the nick to the Auto Op List.<br>
Usage: `aop {nick}`

* `deaop` Removes the nick from the Auto Op List.<br>
Usage: `deaop {nick}`

* `op` Sets the nick as an Op.<br>
Usage: `op {nick}`

* `deop` Removes the nick as an Op.<br>
Usage: `deop {nick}`

* `ahop` Adds the nick to the Auto HOP List.<br>
Usage: `ahop {nick}`

* `deahop` Removes the nick from the Auto HOP List.<br>
Usage: `deahop {nick}`

* `avoice` Adds the nick to the Auto Voice List.<br>
Usage: `avoice {nick}`

* `deavoice` Removes the nick from the Auto Voice List.<br>
Usage: `deavoice {nick}`

* `mode` Sets or unsets a channel mode.<br>
Usage: `mode +/-{flags}`

* `topic` Sets the channels topic.<br>
Usage: `topic {topic}`

* `invite` Invites the specified nick into the channel.<br>
Usage: `invite {nick}`

* `ak` Adds the specified nick to the auto kick list.<br>
Usage: `ak {nick} [{reason}]`

* `ab` Adds the specified nick to the auto ban list.<br>
Usage: `ab {nick} [{reason}]`

* `akb` Adds the specified nick to the auto kick-ban list.<br>
Usage: `akb {nick} [{reason}]`

* `deak` Removes the specified nick to the auto kick list.<br>
Usage: `deak {nick} [{reason}]`

* `deab` Removes the specified nick to the auto ban list.<br>
Usage: `deab {nick} [{reason}]`

* `deakb` Removes the specified nick to the auto kick-ban list.<br>
Usage: `deakb {nick} [{reason}]`

* `hop` Sets the nick as Half Op.<br>
Usage: `hop {nick}`

* `dehop` Removes the nick as Half Op.<br>
Usage: `dehop {nick}`

* `b` Bans the specified nick.<br>
Usage: `b {nick}`

* `ub` Unbans the specified nick.<br>
Usage: `ub {nick}`

* `clearban` Clears all the bans in the channel.<br>
Usage: `clearban`

* `kb` Bans and then Kicks the specified nick.<br>
Usage: `kb {nick} [{reason}]`

* `tb` Bans the specified nick for the amount of time specified.<br>
Usage: `tb {ban_time} {nick} [{reason}]`

* `tkb` Bans and then Kicks the specified nick for the amount of time specified.<br>
Usage: `tkb {ban_time} {nick} [{reason}]`

* `k` Kicks the nick from the channel.<br>
Usage: `k {nick} [{reason}]`

* `voice` Sets the nick as Voiced.<br>
Usage: `voice {nick}`

* `devoice` Removes the nick as Voiced.<br>
Usage: `devoice {nick}`

* `kme` Kicks the requesting nick from the channel.<br>
Usage: `kme`

### Owner

* `owner` Identifies the nick as the Bot's Owner.<br>
Usage: `owner {password}`

* `addowner` Adds the defined nick as an owner.<br>
Usage: `addowner {nick}`

* `delowner` Removes the defined nick from the owners list.<br>
Usage: `delowner {nick}`

* `nick` Changes the Bot's nickname to the one specified.<br>
Usage: `nick {new_nick}`

* `id` Has the Bot identify to nickserv.<br>
Usage: `id`

* `join` Tells the Bot to join the specified channel.<br>
Usage: `join {channel}`

* `part` Tells the Bot to part the channel.<br>
Usage: `part [{channel}]`

* `say` Has the Bot say the specified message to the channel.<br>
Usage: `say [{channel}] {message}`

* `action` Displays the specified message as an action in the channel.<br>
Alternate Commands: `me`<br>
Usage: `action [{channel}] {message}`

* `query` Private messages the specified nick.<br>
Usage: `query {nick} {message}`

* `quit` Quits the server instance.<br>
Usage: `quit`

* `quitall` Quits all of the connected server instances.<br>
Usage: `quitall`

* `cycle` Restarts the server instance.<br>
Usage: `cycle`

* `cycleall` Restarts all of the connected server instances.<br>
Usage: `cycleall`

* `exit` Closes the client.<br>
Usage: `restart`

* `restart` Restarts the client.<br>
Usage: `restart`

* `ignore` Adds the specified nick/chan to the ignore list.<br>
Usage: `ignore {nick|channel}`

* `unignore` Removes the specified nick/chan from the ignore list.<br>
Usage: `unignore {nick|channel}`

* `ignoremodule` Adds the specified nick/chan to a modules ignore list.<br>
Usage: `ignoremodule {module} {nick|chan}`

* `unignoremodule` Removes the specified nick/chan from a modules ignore list.<br>
Usage: `unignoremodule {module} {nick|chan}`

* `ignorecmd` Adds the specified nick/chan to a commands ignore list.<br>
Usage: `ignorecmd {command} {nick|chan}

* `unignorecmd` Removes the specified nick/chan from a commands ignore list.<br>
Usage: `unignorecmd {command} {nick|chan}`

* `blacklist` Adds the specified channel to the bot blacklist.<br>
Usage: `blacklist {channel}`