1
0
Fork 0

Initial commit.

master
Marrub 2016-10-13 00:58:16 -04:00
commit ae778d37e0
26 changed files with 3312 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.nuget
obj
bin
data
packages
*.swp
Makefile

View File

@ -0,0 +1,14 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("vrobot3")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("© 2016 Project Golan")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: CLSCompliant(false)]
[assembly: AssemblyVersion("3.1.*")]

234
Source/Bot.cs Normal file
View File

@ -0,0 +1,234 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Main bot class.
//
//-----------------------------------------------------------------------------
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using CommandFuncDict =
System.Collections.Generic.Dictionary<
System.String,
System.Tuple<
ProjectGolan.Vrobot3.IBotModule,
ProjectGolan.Vrobot3.BotCommandStructure>>;
namespace ProjectGolan.Vrobot3
{
//
// BotCommand
//
// Delegate type for bot commands.
//
public delegate void BotCommand(User usr, Channel channel, String msg);
//
// CommandDict
//
// Dictionary of bot commands.
//
public class CommandDict : Dictionary<String, BotCommandStructure> {}
//
// Bot
//
public partial class Bot
{
public List<IBotModule> modules = new List<IBotModule>();
public CommandFuncDict cmdfuncs = new CommandFuncDict();
public readonly BotInfo info;
private Dictionary<ulong, String> lastLine = new Dictionary<ulong, String>();
private IBotClient client;
public bool isInAudioChannel => false;
public ServerInfo serverInfo => client.info;
//
// Bot constructor
//
public Bot(BotInfo info)
{
this.info = info;
switch(info.serverType)
{
case ServerType.IRC: client = new BotClientIRC(this); break;
case ServerType.Discord: client = new BotClientDiscord(this); break;
}
var modClasses =
from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where moduleIsValid(type)
select type;
foreach(var mod in modClasses)
modules.Add(Activator.CreateInstance(mod, this) as IBotModule);
foreach(var mod in modules)
foreach(var kvp in mod.commands)
cmdfuncs.Add(kvp.Key, Tuple.Create(mod, kvp.Value));
}
//
// connect
//
public void connect() => client.connect();
//
// disconnect
//
public void disconnect()
{
cmdfuncs.Clear();
modules.Clear();
client.disconnect();
}
//
// reply
//
public void reply(User usr, Channel channel, String msg) =>
message(channel, usr.name + ": " + msg);
public void reply(User usr, ulong id, String msg) =>
message(id, usr.name + ": " + msg);
//
// message
//
public void message(Channel channel, String msg) =>
client.sendMessage(channel, msg);
public void message(ulong id, String msg) =>
client.sendMessage(client.getChannel(id), msg);
//
// action
//
public void action(Channel channel, String msg) =>
client.sendAction(channel, msg);
public void action(ulong id, String msg) =>
client.sendAction(client.getChannel(id), msg);
//
// joinAudioChannel
//
public async Task<bool> joinAudioChannel(User user)
{
var channel = client.getAudioChannel(user);
if(channel != null)
{
await client.joinAudioChannel(channel);
return true;
}
else
return false;
}
//
// partAudioChannel
//
public void partAudioChannel() => client.partAudioChannel();
//
// playAudioFile
//
public Task playAudioFile(String file) => client.playAudioFile(file);
//
// checkModPermissions
//
public bool checkModPermissions(Channel channel, Type mod)
{
String[] enables;
if(info.enables.ContainsKey(channel.name))
enables = info.enables[channel.name];
else if(info.enables.ContainsKey("*"))
enables = info.enables["*"];
else
return true;
foreach(var modname in enables)
{
Type type;
if(modname == "*")
return true;
else if(modname[0] == '@')
type = Type.GetType("ProjectGolan.Vrobot3.Modules." + modname.Substring(1));
else
type = Type.GetType(modname);
if(type == mod)
return true;
}
return false;
}
//
// runCommand
//
private void runCommand(User usr, Channel channel, BotCommand cmd,
String rest)
{
try
{
cmd(usr, channel, rest ?? String.Empty);
}
catch(CommandArgumentException exc)
{
if(exc.Message != null)
reply(usr, channel, exc.Message);
else
Console.WriteLine("{0}: Unknown CommandArgumentException",
info.serverName);
}
catch(Exception exc)
{
reply(usr, channel, "fug it borked");
Console.WriteLine("{0}: Unhandled exception in command: {1}",
info.serverName, exc?.Message ?? "unknown error");
File.WriteAllText(Program.Instance.dataDir + "/cmdexcdump.txt",
exc.ToString());
}
}
//
// moduleIsValid
//
private bool moduleIsValid(Type type)
{
if(!typeof(IBotModule).IsAssignableFrom(type) ||
!type.IsClass || type.IsAbstract)
return false;
foreach(var attribute in type.GetCustomAttributes(false))
{
if((attribute is BotModuleIRCAttribute &&
info.serverType != ServerType.IRC) ||
(attribute is BotModuleDiscordAttribute &&
info.serverType != ServerType.Discord) ||
(attribute is BotModuleRequiresAudioAttribute &&
!serverInfo.hasAudio) ||
attribute is BotModuleDisabledAttribute)
return false;
}
return true;
}
}
}
// EOF

53
Source/BotClient.cs Normal file
View File

@ -0,0 +1,53 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Connection method interface.
//
//-----------------------------------------------------------------------------
using System;
using System.Threading.Tasks;
namespace ProjectGolan.Vrobot3
{
//
// IBotClient
//
public abstract class IBotClient
{
protected Bot bot;
public ServerInfo info;
public IBotClient(Bot bot) { this.bot = bot; }
// Connect
public abstract void connect();
public abstract void disconnect();
// Send
public abstract void sendAction(Channel channel, String msg);
public abstract void sendMessage(Channel channel, String msg);
// Channel
public abstract Channel getChannel(ulong id);
public virtual void joinChannel(Channel channel) {}
public virtual void partChannel(Channel channel) {}
// Audio
public virtual ChannelAudio getAudioChannel(User user) =>
new ChannelAudio();
public virtual async Task joinAudioChannel(ChannelAudio channel) =>
await Task.FromResult(0);
public virtual void partAudioChannel() {}
public virtual bool isInAudioChannel() => false;
public virtual async Task playAudioFile(String file) =>
await Task.FromResult(0);
}
}
// EOF

238
Source/BotClientDiscord.cs Normal file
View File

@ -0,0 +1,238 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Discord client.
//
//-----------------------------------------------------------------------------
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using Discord.Audio;
namespace ProjectGolan.Vrobot3
{
//
// BotClientDiscord
//
public class BotClientDiscord : IBotClient
{
//
// AudioBuffer
//
class AudioBuffer
{
// private static const int MaxSize = 20 * 1024 * 1024;
private readonly String name;
private ulong num = 0;
private ulong next = 1;
private ulong bufSize = 0;
public bool completed { get; private set; } = false;
public AudioBuffer()
{
name = System.IO.Path.GetRandomFileName() + ".";
}
}
private Discord.DiscordClient client = new Discord.DiscordClient();
private Discord.Audio.IAudioClient audioClient;
private Discord.Server server;
//
// BotClientDiscord constructor
//
public BotClientDiscord(Bot bot) :
base(bot)
{
info.hasAudio = true;
info.hasColors = false;
info.hasNewlines = true;
info.messageSafeMaxLen = 1777;
info.shortMessages = false;
client.MessageReceived += (sender, evt) =>
{
if(!evt.Message.IsAuthor && !evt.User.IsBot &&
(bot.info.channels == null ||
bot.info.channels.Contains("#" + evt.Channel.Name)) &&
evt.Server.Id.ToString() == bot.info.serverAddr)
{
var usr = new User{};
var channel = new Channel{};
usr.hostname = evt.User.Id.ToString();
usr.name = evt.User.Nickname ?? evt.User.Name;
channel.id = evt.Channel.Id;
channel.name = "#" + evt.Channel.Name;
bot.onMessage(usr, channel, evt.Message.Text);
}
};
client.UsingAudio(x => x.Mode = AudioMode.Outgoing);
}
//
// getUser
//
private Discord.User getUser(User usr)
{
if(server == null)
server = client.GetServer(ulong.Parse(bot.info.serverAddr));
return server.GetUser(ulong.Parse(usr.hostname));
}
//
// connect
//
public override void connect()
{
Console.WriteLine("{0}: Creating connection.", bot.info.serverName);
client.ExecuteAndWait(async () =>
{
await client.Connect(bot.info.serverPass, Discord.TokenType.Bot);
client.SetGame("vrobot 3.1 series");
});
}
//
// disconnect
//
public override void disconnect()
{
if(client != null)
{
partAudioChannel();
client.Disconnect();
client = null;
}
}
//
// sendAction
//
public override void sendAction(Channel channel, String msg) =>
client.GetChannel(channel.id)?.SendMessage(
"_" + Discord.Format.Escape(msg) + "_");
//
// sendMessage
//
public override void sendMessage(Channel channel, String msg) =>
client.GetChannel(channel.id)?.SendMessage(Discord.Format.Escape(msg));
//
// getChannel
//
public override Channel getChannel(ulong id)
{
var dchannel = client.GetChannel(id);
var channel = new Channel{};
channel.id = dchannel.Id;
channel.name = "#" + dchannel.Name;
return channel;
}
//
// getAudioChannel
//
public override ChannelAudio getAudioChannel(User usr)
{
var dchannel = getUser(usr).VoiceChannel;
if(dchannel == null) return null;
var channel = new ChannelAudio{};
channel.id = dchannel.Id;
channel.name = dchannel.Name;
return channel;
}
//
// joinAudioChannel
//
public override async Task joinAudioChannel(ChannelAudio channel)
{
if(channel == null)
return;
var dchannel = client.GetChannel(channel.id);
if(!isInAudioChannel())
audioClient = await dchannel.JoinAudio();
else
await audioClient.Join(dchannel);
}
//
// partAudioChannel
//
public override void partAudioChannel()
{
if(isInAudioChannel())
{
audioClient.Clear();
audioClient.Wait();
audioClient.Disconnect();
}
audioClient = null;
}
//
// isInAudioChannel
//
public override bool isInAudioChannel() =>
audioClient?.State == Discord.ConnectionState.Connected;
//
// playAudioFile
//
public override async Task playAudioFile(String file)
{
if(!isInAudioChannel()) return;
var proc = Process.Start(new ProcessStartInfo{
FileName = "ffmpeg",
Arguments = $"-i {file} -f s16le -ar 48000 -ac 2 " +
"-loglevel quiet pipe:1",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = false,
CreateNoWindow = true
});
var buf = new byte[3840 * 32];
var ostream = audioClient.OutputStream;
var istream = proc.StandardOutput.BaseStream;
int count;
try
{
while(!proc.HasExited &&
(count = await istream.ReadAsync(buf, 0, buf.Length)) != 0)
{
Thread.Sleep(8);
await ostream.WriteAsync(buf, 0, count);
}
}
catch(OperationCanceledException)
{
Console.WriteLine("{0}: Canceled audio stream.",
bot.info.serverName);
}
finally
{
istream.Dispose();
ostream.Dispose();
}
}
}
}
// EOF

45
Source/BotClientIRC.cs Normal file
View File

@ -0,0 +1,45 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// IRC client.
//
//-----------------------------------------------------------------------------
using System;
namespace ProjectGolan.Vrobot3
{
//
// BotClientIRC
//
public class BotClientIRC : IBotClient
{
//
// BotClientIRC constructor
//
public BotClientIRC(Bot bot) :
base(bot)
{
info.hasAudio = false;
info.hasColors = true;
info.hasNewlines = false;
info.messageSafeMaxLen = 601;
info.shortMessages = true;
}
public override void connect() {}
public override void disconnect() {}
public override Channel getChannel(ulong id) => new Channel{};
public override void joinChannel(Channel channel) {}
public override void partChannel(Channel channel) {}
public override void sendAction(Channel channel, String msg) {}
public override void sendMessage(Channel channel, String msg) {}
}
}
// EOF

106
Source/BotEvents.cs Normal file
View File

@ -0,0 +1,106 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Bot events.
//
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
namespace ProjectGolan.Vrobot3
{
//
// Bot
//
public partial class Bot
{
//
// onSeen
//
public void onSeen(User usr, Channel channel)
{
foreach(var mod in modules)
if(checkModPermissions(channel, mod.GetType()))
mod.events.raiseOnSeen(usr, channel);
}
//
// onMessage
//
// Attempt to run commands.
//
public void onMessage(User usr, Channel channel, String msg)
{
var validCmdPreceders = ".%".ToCharArray();
String rest = null;
if(msg.Length >= 1 && validCmdPreceders.Contains(msg[0]))
{
Predicate<BotCommandStructure> pred;
if(msg[0] == '%')
pred = fn => fn.flags.HasFlag(BotCommandFlags.AdminOnly);
else
pred = fn => !fn.flags.HasFlag(BotCommandFlags.AdminOnly);
var dict = from fn in cmdfuncs where pred(fn.Value.Item2) select fn;
// Get the command name.
String[] splt = msg.Substring(1).Split(" ".ToCharArray(), 2);
String cmdname = splt[0].ToLower();
// Handle commands ending with "^".
// These take the last message as input.
if(cmdname.EndsWith("^"))
{
cmdname = cmdname.Substring(0, cmdname.Length - 1);
lastLine.TryGetValue(channel.id, out rest);
}
var tcmd =
from kvp in dict where kvp.Key == cmdname select kvp.Value;
if(tcmd.Any())
{
var tcmdr = tcmd.First();
// Check permissions.
if(usr.hostname != info.adminId &&
(tcmdr.Item2.flags.HasFlag(BotCommandFlags.AdminOnly) ||
!checkModPermissions(channel, tcmdr.Item1.GetType())))
goto RaiseMessage;
// If we have input, grab that too.
if(rest == null && splt.Length > 1)
rest = splt[1];
else if(rest == null)
rest = String.Empty;
// Go over each module and raise a command message event.
foreach(var mod in modules)
if(checkModPermissions(channel, mod.GetType()))
mod.events.raiseOnCmdMessage(usr, channel, msg);
runCommand(usr, channel, tcmdr.Item2.cmd, rest);
return;
}
}
RaiseMessage:
// Go over each module and raise a message event.
foreach(var mod in modules)
if(checkModPermissions(channel, mod.GetType()))
mod.events.raiseOnMessage(usr, channel, msg);
lastLine[channel.id] = msg;
}
}
}
// EOF

81
Source/BotInfo.cs Normal file
View File

@ -0,0 +1,81 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Bot informational classes.
//
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace ProjectGolan.Vrobot3
{
//
// ServerType
//
public enum ServerType
{
IRC,
Discord
}
//
// ServerInfo
//
public struct ServerInfo
{
public bool hasAudio;
public bool hasColors;
public bool hasNewlines;
public int messageSafeMaxLen;
public bool shortMessages;
}
//
// BotInfo
//
public struct BotInfo
{
public Dictionary<String, String[]> enables;
public ServerType serverType;
public String serverName;
public String serverPass;
public String serverAddr;
public String adminId;
public String[] channels;
}
//
// User
//
public struct User
{
public String hostname; // A consistent identifier for the user.
public String name; // Nickname for replying and etc.
}
//
// Channel
//
public struct Channel
{
public ulong id;
public String name;
}
//
// ChannelAudio
//
public class ChannelAudio
{
public ulong id;
public String name;
}
}
// EOF

123
Source/BotModule.cs Normal file
View File

@ -0,0 +1,123 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Base module classes.
//
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace ProjectGolan.Vrobot3
{
namespace Modules.EventType
{
public delegate void OnMessage(User usr, Channel channel, String msg);
public delegate void OnSeen(User usr, Channel channel);
}
//
// BotModuleRequiresAudioAttribute
//
public class BotModuleRequiresAudioAttribute : Attribute
{
public override String ToString() => "Bot Module Requires Audio";
}
//
// BotModuleDisabledAttribute
//
public class BotModuleDisabledAttribute : Attribute
{
public override String ToString() => "Bot Module Disabled";
}
//
// BotModuleDiscordAttribute
//
public class BotModuleDiscordAttribute : Attribute
{
public override String ToString() => "Bot Module is Discord only";
}
//
// BotModuleIRCAttribute
//
public class BotModuleIRCAttribute : Attribute
{
public override String ToString() => "Bot Module is IRC only";
}
//
// BotCommandFlags
//
// Flags for command registration.
//
[Flags]
public enum BotCommandFlags
{
AdminOnly = 1 << 0,
Hidden = 1 << 1
}
//
// BotCommandStructure
//
// Used for registering commands in a module.
//
public struct BotCommandStructure
{
public BotCommand cmd;
public BotCommandFlags flags;
public String help;
}
//
// IBotModule
//
// Base module class. Inherit this for your modules.
//
public abstract class IBotModule
{
//
// Events
//
public struct Events
{
public event Modules.EventType.OnMessage onCmdMessage;
public event Modules.EventType.OnMessage onMessage;
public event Modules.EventType.OnSeen onSeen;
public void raiseOnCmdMessage(User usr, Channel channel, String msg)
{
if(onCmdMessage != null)
onCmdMessage(usr, channel, msg);
}
public void raiseOnMessage(User usr, Channel channel, String msg)
{
if(onMessage != null)
onMessage(usr, channel, msg);
}
public void raiseOnSeen(User usr, Channel channel)
{
if(onSeen != null)
onSeen(usr, channel);
}
}
public IBotModule(Bot bot) { this.bot = bot; }
public CommandDict commands = new CommandDict();
public Events events;
protected Bot bot;
}
}
// EOF

31
Source/Exceptions.cs Normal file
View File

@ -0,0 +1,31 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Exception types.
//
//-----------------------------------------------------------------------------
using System;
namespace ProjectGolan.Vrobot3
{
//
// CommandArgumentException
//
public class CommandArgumentException : Exception
{
public CommandArgumentException() {}
public CommandArgumentException(String message) : base(message) {}
public CommandArgumentException(String message, Exception inner) :
base(message, inner)
{
}
}
}
// EOF

81
Source/Links.cs Normal file
View File

@ -0,0 +1,81 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Link (URI) utilities.
//
//-----------------------------------------------------------------------------
using System;
using System.Text.RegularExpressions;
namespace ProjectGolan.Vrobot3
{
//
// Utils
//
public static partial class Utils
{
//
// URI
//
public struct URI
{
public String method, host, path, query, tag, uri;
//
// ToString
//
public override String ToString() => uri;
//
// Finder
//
public static Regex Finder = new Regex(
@"((?<method>[^:/?# ]+)?:)" +
@"(//(?<host>[^/?# ]*))" +
@"(?<path>[^?# ]*)" +
@"(?<query>\?([^# ]*))?" +
@"(?<tag>#(.*))?");
//
// FromMatch
//
public static URI FromMatch(Match match)
{
return new URI{
method = match.Groups["method"]?.Value ?? String.Empty,
host = match.Groups["host"]?.Value,
path = match.Groups["path"]?.Value,
query = match.Groups["query"]?.Value ?? String.Empty,
tag = match.Groups["tag"]?.Value ?? String.Empty,
uri = match.Value
};
}
//
// Match
//
public static URI Match(String str) => FromMatch(Finder.Match(str));
//
// Matches
//
public static URI[] Matches(String str)
{
var matchbox = Finder.Matches(str);
if(matchbox.Count == 0) return null;
var matches = new URI[matchbox.Count];
for(int i = 0; i < matchbox.Count; i++)
matches[i] = FromMatch(matchbox[i]);
return matches;
}
}
}
}
// EOF

View File

@ -0,0 +1,82 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Admin commands module.
// %kill, %msg, %action
//
//-----------------------------------------------------------------------------
using System;
using System.Linq;
namespace ProjectGolan.Vrobot3.Modules
{
//
// Mod_Admin
//
public class Mod_Admin : IBotModule
{
//
// Mod_Admin constructor
//
public Mod_Admin(Bot bot_) :
base(bot_)
{
commands["kill"] = new BotCommandStructure{
cmd = cmdKill,
flags = BotCommandFlags.AdminOnly,
help = "Kills all bot instances.\n" +
"Syntax: %kill"
};
commands["msg"] = new BotCommandStructure{
cmd = cmdMsg,
flags = BotCommandFlags.AdminOnly,
help = "Sends a message.\n" +
"Syntax: %msg channel, msg\n" +
"Example: %msg #general, ur all dumb"
};
commands["action"] = new BotCommandStructure{
cmd = cmdAction,
flags = BotCommandFlags.AdminOnly,
help = "Sends an action.\n" +
"Syntax: %action channel, msg\n" +
"Example: %action #general, explodes violently"
};
}
//
// cmdKill
//
public void cmdKill(User usr, Channel channel, String msg)
{
Console.WriteLine("{0}: Killing all instances.", bot.info.serverName);
Program.Instance.end();
}
//
// cmdMsg
//
public void cmdMsg(User usr, Channel channel, String msg)
{
String[] args = Utils.GetArguments(msg, commands["msg"].help, 2, 2);
bot.message(ulong.Parse(args[0]), args[1].Trim());
}
//
// cmdAction
//
public void cmdAction(User usr, Channel channel, String msg)
{
String[] args = Utils.GetArguments(msg, commands["action"].help, 2, 2);
bot.action(ulong.Parse(args[0]), args[1].Trim());
}
}
}

202
Source/Modules/Mod_Audio.cs Normal file
View File

@ -0,0 +1,202 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Audio-based commands.
//
//-----------------------------------------------------------------------------
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace ProjectGolan.Vrobot3.Modules
{
//
// Mod_Audio
//
[BotModuleRequiresAudio, BotModuleDisabled]
public class Mod_Audio : IBotModule
{
//
// QueueItem
//
class QueueItem
{
Utils.URI uri;
public QueueItem(Utils.URI uri)
{
this.uri = uri;
}
public override String ToString() => String.Empty;
}
//
// Queue
//
class Queue
{
TimeSpan curTime;
List<QueueItem> items;
int pos;
public Queue()
{
this.curTime = new TimeSpan();
this.items = new List<QueueItem>();
this.pos = 0;
}
public bool addItem(Utils.URI uri)
{
var item = new QueueItem(uri);
items.Add(item);
return true;
}
}
String[] validMethods = { "http", "https", "ftp", "ftps" };
Random rnd = Utils.GetRND();
Queue queue = new Queue();
//
// Mod_Audio constructor
//
public Mod_Audio(Bot bot_) :
base(bot_)
{
commands["queue"] = new BotCommandStructure{
cmd = cmdQueue,
help = "Add an item (or items) to the audio queue.\n" +
"Syntax: .queue uri...\n" +
"Example: .queue https://www.youtube.com/watch?v=13pL0TiOiHM"
};
commands["play"] = new BotCommandStructure{
cmd = cmdPlay,
help = "Set the currently playing item in the queue. " +
"If a URL is given, queues and plays that.\n" +
"Syntax: .play [number|uri]\n" +
"Example: .play 5\n" +
"Example: .play https://www.youtube.com/watch?v=13pL0TiOiHM"
};
commands["lsqueue"] = new BotCommandStructure{
cmd = cmdListQueue,
help = "Lists queue items.\n" +
"Syntax: .lsqueue"
};
commands["fugoff"] = new BotCommandStructure{
cmd = cmdFugOff,
help = "GET ME COGS OR FUG OFF",
flags = BotCommandFlags.AdminOnly
};
commands["summon"] = new BotCommandStructure{
cmd = cmdSummon,
help = "Makes the bot join your audio channel.\n" +
"Syntax: .summon"
};
commands["vanquish"] = new BotCommandStructure{
cmd = cmdVanquish,
help = "Makes the bot leave their audio channel.\n" +
"Syntax: %vanquish",
flags = BotCommandFlags.AdminOnly
};
}
//
// summon
//
async Task<bool> summon(User usr, Channel channel)
{
if(bot.isInAudioChannel)
return true;
if(!await bot.joinAudioChannel(usr))
{
bot.reply(usr, channel,
"Couldn't find audio channel. " +
"If you are already in an audio channel, please reconnect to " +
"it and try again.");
return false;
}
return true;
}
//
// cmdQueue
//
public void cmdQueue(User usr, Channel channel, String msg)
{
var uris = Utils.URI.Matches(msg);
if(uris == null)
throw new CommandArgumentException("no valid URIs");
int loadPass = 0;
foreach(var uri_ in uris)
{
var uri = uri_;
if(uri.method == String.Empty)
uri.method = "http";
if(validMethods.Contains(uri.method) &&
queue.addItem(uri))
loadPass++;
}
bot.reply(usr, channel,
$"Added {loadPass} item{loadPass == 1 ? "" : "s"} to the queue.");
}
//
// cmdPlay
//
public void cmdPlay(User usr, Channel channel, String msg)
{
}
//
// cmdListQueue
//
public void cmdListQueue(User usr, Channel channel, String msg)
{
}
//
// cmdFugOff
//
public async void cmdFugOff(User usr, Channel channel, String msg)
{
if(!await summon(usr, channel))
return;
await bot.playAudioFile("\"/home/marrub/musix/MusixDL/Shadowfax - Shadowdance/01 New Electric India.mp3\"").ConfigureAwait(false);
}
//
// cmdSummon
//
public void cmdSummon(User usr, Channel channel, String msg) =>
summon(usr, channel);
//
// cmdVanquish
//
public void cmdVanquish(User usr, Channel channel, String msg) =>
bot.partAudioChannel();
}
}

112
Source/Modules/Mod_Fun.cs Normal file
View File

@ -0,0 +1,112 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Fun stuff.
// .carmack, .revenant, .wan, .nyan, .:^)
//
//-----------------------------------------------------------------------------
using System;
namespace ProjectGolan.Vrobot3.Modules
{
//
// Mod_Fun
//
public class Mod_Fun : IBotModule
{
//
// ShitpostingDevice
//
private class ShitpostingDevice
{
private String word, final;
private Random rnd = Utils.GetRND();
private int min, max;
private Bot bot;
//
// ShitpostingDevice constructor
//
public ShitpostingDevice(String word, String final, int min, int max,
Bot bot)
{
this.word = word;
this.final = final;
this.min = min;
this.max = max;
this.bot = bot;
}
//
// run
//
public void run(User usr, Channel channel, String msg)
{
int n = rnd.Next(min, max);
String outp = String.Empty;
if(bot.serverInfo.hasColors && rnd.Next(0, 8) == 1)
for(int i = 0; i < 6; i++)
{
String[] colors = { "04", "07", "08", "09", "12", "06" };
outp += "\x03";
outp += colors[i];
outp += word;
outp += word;
}
else
for(int i = 0; i < n; i++)
outp += word;
bot.reply(usr, channel, outp + final);
}
}
//
// Mod_Fun constructor
//
public Mod_Fun(Bot bot_) :
base(bot_)
{
commands["carmack"] = new BotCommandStructure{
cmd = new ShitpostingDevice("MM", "", 3, 20, bot).run,
flags = BotCommandFlags.Hidden
};
commands["revenant"] = new BotCommandStructure{
cmd = new ShitpostingDevice("AA", "", 3, 20, bot).run,
flags = BotCommandFlags.Hidden
};
commands["wan"] = new BotCommandStructure{
cmd = new ShitpostingDevice("wan ", "- !", 2, 12, bot).run,
flags = BotCommandFlags.Hidden
};
commands["nyan"] = new BotCommandStructure{
cmd = new ShitpostingDevice("nyan ", "!~", 2, 12, bot).run,
flags = BotCommandFlags.Hidden
};
commands[":^)"] = new BotCommandStructure{
cmd = (usr, channel, msg) => bot.message(channel, ":^)"),
flags = BotCommandFlags.Hidden
};
events.onMessage += onMessage;
}
//
// onMessage
//
public void onMessage(User usr, Channel channel, String msg)
{
if(msg.Contains("OLD MEN"))
bot.message(channel, "WARNING! WARNING!");
}
}
}
// EOF

View File

@ -0,0 +1,182 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Idgames search module.
// .idgames
//
//-----------------------------------------------------------------------------
using System;
using System.Net;
using System.Web;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Linq;
namespace ProjectGolan.Vrobot3.Modules
{
//
// Mod_Idgames
//
public class Mod_Idgames : IBotModule
{
static readonly String APIURI =
"http://doomworld.com/idgames/api/api.php";
private Random rnd = Utils.GetRND();
//
// Mod_Idgames constructor
//
public Mod_Idgames(Bot bot_) :
base(bot_)
{
commands["idgames"] = new BotCommandStructure{
cmd = cmdIdgames,
help = "Gets an entry from the idgames archive.\n" +
"Syntax: .idgames [name or ID[, type[, position]]]\n" +
"Example: .idgames scythe, filename, 4\n" +
"Example: .idgames"
};
}
//
// cmdIdgames
//
public void cmdIdgames(User usr, Channel channel, String msg)
{
String[] args =
Utils.GetArguments(msg, commands["idgames"].help, 0, 3);
switch(args.Length)
{
case 1:
int id;
if(args[0].Trim().Length == 0)
idgamesRandom(usr, channel);
else if(Int32.TryParse(args[0], out id))
idgamesID(usr, channel, id);
else
idgames(usr, channel, args[0]);
break;
case 2: idgames(usr, channel, args[0], args[1]); break;
case 3:
if(args[2].Trim().ToLower() == "random")
idgames(usr, channel, args[0], args[1], "random");
else
idgames(usr, channel, args[0], args[1], args[2].Trim());
break;
}
}
//
// idgamesRandom
//
private void idgamesRandom(User usr, Channel channel)
{
var req = WebRequest.Create("http://doomworld.com/idgames/?random")
as HttpWebRequest;
req.Referer = "http://doomworld.com/idgames/";
bot.message(channel,
Discord.Format.Escape(req.GetResponse().ResponseUri.ToString()));
}
//
// idgamesID
//
private void idgamesID(User usr, Channel channel, int id)
{
var req = WebRequest.Create(APIURI + "?action=get&id=" + id)
as HttpWebRequest;
using(var response = req.GetResponse())
{
var xml = XDocument.Load(response.GetResponseStream());
var x_title =
from item in xml.Descendants("title") select item.Value;
var x_uri = from item in xml.Descendants("url") select item.Value;
if(!x_title.Any())
{
bot.message(channel, "Nothing found.");
return;
}
bot.message(channel,
Discord.Format.Escape(x_title.First() + ": " + x_uri.First()));
}
}
//
// idgames
//
private void idgames(User usr, Channel channel, String inquiry,
String type = "title", String pos = "1")
{
int ipos = 0;
if(pos != "random")
{
Utils.TryParse(pos, "Invalid position.", out ipos);
if(ipos < 1)
throw new CommandArgumentException("Invalid position.");
ipos = ipos - 1;
}
inquiry = HttpUtility.UrlEncode(inquiry.Trim());
type = HttpUtility.UrlEncode(type.Trim().ToLower());
if(type == "name") type = "title"; // >_>'
String[] validtypes = {
"filename", "title", "author", "email",
"description", "credits", "editors", "textfile"
};
if(!validtypes.Contains(type))
throw new CommandArgumentException("Invalid inquiry type.");
String uri = APIURI + "?action=search&sort=rating&query=" +
inquiry + "&type=" + type;
var req = WebRequest.Create(uri);
Console.WriteLine("idgames query: {0}", uri);
using(var response = req.GetResponse())
{
var xml = XDocument.Load(response.GetResponseStream());
var x_titles =
from item in xml.Descendants("title") select item.Value;
var x_uris = from item in xml.Descendants("url") select item.Value;
if(!x_titles.Any())
{
bot.message(channel, "Nothing found.");
return;
}
if(pos == "random") ipos = rnd.Next(0, x_titles.Count());
if(ipos >= x_titles.Count()) ipos = x_titles.Count() - 1;
String title = x_titles.ElementAtOrDefault(ipos);
if(title.Trim().Length > 0) title = "[ " + title + " ] ";
bot.message(channel,
Discord.Format.Escape(String.Format("({0} of {1}{4} {2}{3}",
ipos + 1, x_titles.Count(), title,
x_uris.ElementAtOrDefault(ipos),
x_titles.Count() >= 100 ? "+)" : ")")));
}
}
}
}

336
Source/Modules/Mod_Links.cs Normal file
View File

@ -0,0 +1,336 @@
//
// Mod_Links.cs
//
// Link title capabilities.
//
using System;
using System.Text.RegularExpressions;
using System.Xml;
using System.Linq;
using System.Net;
using System.Collections.Generic;
using System.Threading;
using Sharkbite.Irc;
using HtmlAgilityPack;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ProjectGolan.Vrobot3
{
//
// Mod_Links
//
public sealed class Mod_Links : IBotModule
{
//
// URI
//
private struct URI
{
public String method, host, path, query, tag, uri;
}
//
// Delegates.
private delegate void URIHandler(URI uri, String referer, ref String result);
//
// Ctor
//
public Mod_Links(Bot bot_) :
base(bot_)
{
events.OnMessage += Evt_OnMessage;
}
//
// Evt_OnMessage
//
public void Evt_OnMessage(UserInfo usr, String channel, String msg, bool iscmd)
{
//
// Do this asynchronously, we don't want link parsing to block operation.
new Thread(() => {
try
{
if(!iscmd)
TryParseURIs(channel, msg);
}
catch(Exception exc)
{
Console.WriteLine("{0}: URL thread error: {1}", bot.n_groupname, exc.Message);
}
}).Start();
}
//
// GetURITitle
//
private Match GetURITitle(URI uri, String referer, int kb = 16)
{
String rstr = Utils.GetResponseString(uri.uri, 1024 * kb, referer);
if(rstr == null)
return null;
return new Regex(@"\<title\>(?<realtitle>.+?)\</title\>").Match(rstr);
}
//
// URI_Default
//
private void URI_Default(URI uri, String referer, ref String result)
{
var req = WebRequest.Create(uri.uri) as HttpWebRequest;
req.Referer = referer;
req.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.9) Gecko/20100101 Firefox/31.9";
using(var response = req.GetResponse() as HttpWebResponse)
{
var html = new HtmlDocument();
html.LoadHtml(Utils.GetResponseString(response, 16*1024));
var x_title = from item in html.DocumentNode.Descendants()
where (item?.Name ?? String.Empty) == "title" ||
((item?.Name ?? String.Empty) == "meta" &&
(item?.Attributes["id"]?.Value ?? String.Empty).EndsWith("title"))
select item;
if(x_title.Any())
result = WebUtility.HtmlDecode(x_title.First().InnerText.Trim(new char[]{ ' ', '\t', '\n' }));
}
}
//
// URI_Youtube
//
// Special fucking flower.
//
private void URI_Youtube(URI uri, String referer, ref String result)
{
var req = WebRequest.Create(uri.uri) as HttpWebRequest;
req.Referer = referer;
using(var response = req.GetResponse() as HttpWebResponse)
{
var html = new HtmlDocument();
html.Load(response.GetResponseStream());
var x_title = from item in html.DocumentNode.Descendants()
where (item?.Attributes["id"]?.Value ?? String.Empty) == "eow-title"
select item;
if(x_title.Any())
result = WebUtility.HtmlDecode(x_title.First().InnerText.Trim(new char[]{ ' ', '\t', '\n' })) +
" - YouTube";
}
}
//
// URI_Gelooru
//
private void URI_Gelbooru(URI uri, String referer, ref String result)
{
var match = GetURITitle(uri, referer, 8); // Should be OK to just get the first 8kb here.
if(match?.Success == true)
{
String title = WebUtility.HtmlDecode(match.Groups["realtitle"].Value);
if(title.Contains("Image View"))
result = "Image View - Gelbooru";
else
result = title;
}
}
//
// URI_Hitbox
//
private void URI_Hitbox(URI uri, String referer, ref String result)
{
String name = WebUtility.HtmlEncode(uri.path.TrimStart(new char[]{'/'}));
var req = WebRequest.Create("https://api.hitbox.tv/media/live/" + name + "?fast") as HttpWebRequest;
req.Referer = referer;
using(var response = req.GetResponse() as HttpWebResponse)
{
var json = JObject.Parse(Utils.GetResponseString(response, 64 * 1024));
var node = json["livestream"][0];
String displayname = (String)node["media_display_name"];
String status = (String)node["media_status"];
bool live = Int32.Parse((String)node["media_is_live"] ?? "0") == 1;
result = displayname;
if(live)
result += " (live)";
if(!String.IsNullOrEmpty(status))
result += ": " + status;
result += " - hitbox";
}
}
//
// TryParseURIs
//
// This function is really complicated because of exploits. Fuck exploits.
//
private void TryParseURIs(String channel, String msg)
{
try
{
Regex r_finduris = new Regex(
@"((?<method>[^:/?# ]+):)" +
@"(//(?<host>[^/?# ]*))" +
@"(?<path>[^?# ]*)" +
@"(?<query>\?([^# ]*))?" +
@"(?<tag>#(.*))?"
);
var matchbox = r_finduris.Matches(msg);
if(matchbox.Count != 0)
{
String outp = String.Empty;
for(int i = 0; i < matchbox.Count; i++)
{
var match = matchbox[i];
URI uri = new URI{
method = match.Groups["method"].Value,
host = match.Groups["host"].Value,
path = match.Groups["path"].Value,
query = match.Groups["query"]?.Value ?? String.Empty,
tag = match.Groups["tag"]?.Value ?? String.Empty,
uri = match.Value
};
//
// Will the real URI please stand up?
if(uri.method == "http" || uri.method == "https")
{
var req = WebRequest.Create(uri.uri) as HttpWebRequest;
using(var resp = req.GetResponse())
if(resp.ResponseUri.Host != uri.host)
{
uri.method = resp.ResponseUri.Scheme;
uri.host = resp.ResponseUri.Host;
uri.path = resp.ResponseUri.AbsolutePath;
uri.query = resp.ResponseUri.Query;
uri.tag = resp.ResponseUri.Fragment;
uri.uri = resp.ResponseUri.OriginalString;
}
}
if(uri.path.Length == 0)
uri.path = "/";
//
// Make sure the method is OK.
// Previously:
// [22:19] <marrub> file:///srv/www/marrub/oldmen.html
// [22:19] <vrobot3> [ OLD MEN OLD MEN OLD MEN OLD MEN OLD MEN OLD MEN OLD MEN OLD ... ]
String[] validmethods = { "ftp", "ftps", "http", "https" };
if(!validmethods.Contains(uri.method))
continue;
//
// Try and get a decent title from the URL.
URIHandler handler = URI_Default;
String result = String.Empty;
String referer = null;
if(uri.method == "http" || uri.method == "https")
{
referer = uri.method + "://" + uri.host;
Dictionary<String, URIHandler> handlers = new Dictionary<String, URIHandler>(){
{ "youtube.com", URI_Youtube },
{ "youtu.be", URI_Youtube },
{ "gelbooru.com", URI_Gelbooru },
{ "hitbox.tv", URI_Hitbox },
};
String hostst = Regex.Replace(uri.host, @"^www\.", String.Empty, RegexOptions.Multiline);
if(handlers.ContainsKey(hostst))
handler = handlers[hostst];
}
//
// Handle grabbing the title. Just get on with it if we throw an exception.
try
{ handler(uri, referer, ref result); }
catch(Exception exc)
{
Console.WriteLine("URL handle exception: {0}", exc.Message);
continue;
}
//
// Sanitize.
result.Trim();
for(int j = result.Length - 1; j >= 0; j--)
{
Char ch = result[j];
if((Char.IsWhiteSpace(ch) && ch != ' ') || Char.IsControl(ch) || Char.IsSurrogate(ch))
result = result.Remove(j, 1);
}
//
// If the result is 0-length, just get rid of it.
if(result.Trim().Length == 0)
continue;
//
// Throw the result into the output buffer.
outp += result;
//
// If the output is too long, we need to shorten it and break.
if(outp.Length > 400 - 3)
{
outp = outp.Substring(0, 400 - 3);
outp += "···";
break;
}
//
// Add separators.
if(i != matchbox.Count - 1)
outp += " | ";
}
if(outp.Length > 0)
bot.Message(channel, "[ " + outp + " ]");
}
}
catch(Exception exc)
{
Console.WriteLine("{0}: URL parse error: {1}", bot.n_groupname, exc.Message ?? "[unknown]");
}
}
}
}

236
Source/Modules/Mod_Memo.cs Normal file
View File

@ -0,0 +1,236 @@
//
// Mod_Memo.cs
//
// Memoing capabilities.
// @memocount, .memo, .memoseen
//
using System;
using System.Collections.Generic;
using System.IO;
using Sharkbite.Irc;
using Newtonsoft.Json;
namespace ProjectGolan.Vrobot3.Modules
{
//
// Mod_Memo
//
public sealed class Mod_Memo : IBotModule
{
//
// MemoFlags
//
[Flags]
enum MemoFlags
{
OnSeen = 1 << 0
}
//
// MemoInfo
//
private struct MemoInfo
{
//
// Data.
public String content;
public String sender;
public DateTime time;
public MemoFlags flags;
};
//
// MemoDict
//
private class MemoDict : Dictionary<String, List<MemoInfo>> {}
//
// Data.
MemoDict memos = new MemoDict();
//
// Ctor
//
public Mod_Memo(Bot bot_) :
base(bot_)
{
if(File.Exists("/srv/irc/vrobot3/data/memos." + bot.n_groupname + ".json"))
memos = JsonConvert.DeserializeObject<MemoDict>(File.ReadAllText("/srv/irc/vrobot3/data/memos." +
bot.n_groupname + ".json"));
commands["memo"] = new BotCommandStructure{ cmd = Cmd_Memo,
help = "Sends a message to someone later. Activates when they say something. || " +
"Syntax: .memo person message || " +
"Example: .memo SomeDude wow u suck at videogames"
};
commands["memoseen"] = new BotCommandStructure{ cmd = Cmd_MemoSeen,
help = "Sends a message to someone later. Activates when they do anything. || " +
"Syntax: .memoseen person message || " +
"Example: .memoseen SomeDude wow u suck at videogames"
};
commands["memocount"] = new BotCommandStructure{ cmd = Cmd_MemoCount, flags = BotCommandFlags.AdminOnly,
help = "Gets the amount of memos for this session. || " +
"Syntax: @memocount"
};
events.OnMessage += Evt_OnMessage;
events.OnDisconnected += Evt_OnDisconnected;
events.OnSeen += Evt_OnSeen;
}
//
// Cmd_MemoCount
//
public void Cmd_MemoCount(UserInfo usr, String channel, String msg)
{
bot.Reply(usr, channel, memos.Count.ToString());
}
//
// Cmd_Memo
//
public void Cmd_Memo(UserInfo usr, String channel, String msg)
{
String[] args = Utils.GetArguments(msg, commands["memo"].help, 2, 2, ' ');
args[0] = args[0].Replace(",", "");
AddMemo(args[0], new MemoInfo {
content = args[1],
sender = usr.Nick,
time = DateTime.Now
});
bot.Reply(usr, channel, String.Format("Message for {0} will be sent next time they say something.", args[0]));
}
//
// Cmd_MemoSeen
//
public void Cmd_MemoSeen(UserInfo usr, String channel, String msg)
{
String[] args = Utils.GetArguments(msg, commands["memoseen"].help, 2, 2, ' ');
args[0] = args[0].Replace(",", "");
AddMemo(args[0], new MemoInfo {
content = args[1],
sender = usr.Nick,
time = DateTime.Now,
flags = MemoFlags.OnSeen
});
bot.Reply(usr, channel, String.Format("Message for {0} will be sent next time I see them.", args[0]));
}
//
// AddMemo
//
private void AddMemo(String name, MemoInfo memo)
{
name = name.ToLower();
if(!memos.ContainsKey(name))
memos[name] = new List<MemoInfo>();
memos[name].Add(memo);
WriteMemos();
}
//
// OutputMemos
//
private void OutputMemos(String channel, String realnick, bool onseen)
{
String nick = realnick.ToLower();
if(!memos.ContainsKey(nick))
return;
var arr = memos[nick];
for(int i = arr.Count - 1; i >= 0; i--)
{
MemoInfo memo = arr[i];
if(!memo.flags.HasFlag(MemoFlags.OnSeen) && onseen)
continue;
String outp = String.Empty;
outp += String.Format("[Memo from {0}, {1}]", memo.sender, Utils.FuzzyRelativeDate(memo.time));
// Wrap if it's probably going to be too long.
if(memo.content.Length > 350)
{
bot.Message(channel, outp + ":");
outp = String.Empty;
}
outp += String.Format(" {0}: {1}", realnick, memo.content);
bot.Message(channel, outp);
arr.RemoveAt(i);
}
if(arr.Count == 0)
memos.Remove(nick);
WriteMemos();
}
//
// Evt_OnMessage
//
public void Evt_OnMessage(UserInfo usr, String channel, String msg, bool iscmd)
{
OutputMemos(channel, usr.Nick, false);
}
//
// Evt_OnSeen
//
public void Evt_OnSeen(UserInfo usr, String channel)
{
OutputMemos(channel, usr.Nick, true);
}
//
// Evt_OnDisconnected
//
public void Evt_OnDisconnected()
{
WriteMemos();
}
//
// WriteMemos
//
private void WriteMemos()
{
File.WriteAllText("/srv/irc/vrobot3/data/memos." + bot.n_groupname + ".json",
JsonConvert.SerializeObject(memos));
}
}
}

View File

@ -0,0 +1,96 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Doominati Quote DB interface command.
// .quote
//
//-----------------------------------------------------------------------------
using System;
using System.Net;
using System.Threading;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace ProjectGolan.Vrobot3.Modules
{
//
// Mod_Quote
//
public class Mod_Quote : IBotModule
{
private struct QDBInterface
{
public int numQuotes;
}
static readonly String APIURI = "http://www.greyserv.net/qdb/q/";
static readonly String InterfaceURI =
"http://www.greyserv.net/qdb/interface.cgi";
private Random rnd = Utils.GetRND();
//
// Mod_Quote constructor
//
public Mod_Quote(Bot bot_) :
base(bot_)
{
commands["quote"] = new BotCommandStructure{
cmd = cmdQuote,
help = "Get a quote from the Doominati Quote DB.\n" +
"Syntax: .quote [id]\n" +
"Example: .quote 536"
};
}
//
// cmdQuote
//
public void cmdQuote(User usr, Channel channel, String msg)
{
var inter = JsonConvert.DeserializeObject<QDBInterface>(
Utils.GetResponseString(InterfaceURI, 64));
int id;
if(String.IsNullOrEmpty(msg?.Trim()) || !int.TryParse(msg, out id))
id = rnd.Next(inter.numQuotes);
else if(id < 0 || id > inter.numQuotes)
throw new CommandArgumentException("invalid quote ID");
var quote = Utils.GetResponseString(APIURI + id.ToString(),
bot.serverInfo.messageSafeMaxLen);
if(String.IsNullOrEmpty(quote))
throw new CommandArgumentException("QDB exploded try again later");
if(bot.serverInfo.shortMessages)
quote = Regex.Replace(quote, "\n+", "\n").Trim();
var lines = quote.Split('\n');
if(bot.serverInfo.shortMessages &&
(lines.Length > 5 || quote.Length > 600))
{
bot.reply(usr, channel, "Quote is too long.");
return;
}
if(bot.serverInfo.hasNewlines)
bot.message(channel, quote);
else
foreach(var ln_ in lines)
{
String ln = ln_.Trim();
if(ln.Length > 0)
bot.message(channel, ln);
}
}
}
}

143
Source/Modules/Mod_Seen.cs Normal file
View File

@ -0,0 +1,143 @@
//
// Mod_Seen.cs
//
// .seen
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Sharkbite.Irc;
using Newtonsoft.Json;
using Tarczynski.NtpDateTime;
namespace ProjectGolan.Vrobot3
{
//
// Mod_Seen
//
public sealed class Mod_Seen : IBotModule
{
//
// SeenName
//
private class SeenName
{
public String real, check;
public DateTime time;
}
//
// SeenDates
//
private class SeenDates : List<SeenName> {}
//
// Data.
private SeenDates seendates = new SeenDates();
private TimeZoneInfo burb;
private DateTime lastwrite = DateTime.Now;
//
// Ctor
//
public Mod_Seen(Bot bot_) :
base(bot_)
{
if(File.Exists("/srv/irc/vrobot3/data/seendates." + bot.n_groupname + ".json"))
seendates = JsonConvert.DeserializeObject<SeenDates>(File.ReadAllText("/srv/irc/vrobot3/data/seendates." +
bot.n_groupname + ".json"));
commands["seen"] = new BotCommandStructure { cmd = Cmd_Seen,
help = "Responds with the last time I saw someone. || " +
"Syntax: .seen person || " +
"Example: .seen vrobot3"
};
events.OnSeen += Evt_OnSeen;
events.OnDisconnected += Evt_OnDisconnected;
burb = TimeZoneInfo.CreateCustomTimeZone("burb", new TimeSpan(10, -30, 0), "burb", "burb");
}
//
// Cmd_Seen
//
public void Cmd_Seen(UserInfo usr, String channel, String msg)
{
if(msg.Length == 0 || msg.Contains(" "))
throw new CommandArgumentException("Invalid name.");
String name = msg.ToLower();
var seen = from sdata in seendates where sdata.check == name select sdata;
if(seen.Any())
{
var other = seen.First();
String outp = String.Empty;
outp += "I last saw ";
outp += other.real;
outp += " active ";
outp += Utils.FuzzyRelativeDate(other.time, DateTime.Now.FromNtp());
outp += ", at ";
outp += other.time.ToShortTimeString();
outp += " CST (";
outp += TimeZoneInfo.ConvertTime(other.time, TimeZoneInfo.Local, burb).ToShortTimeString();
outp += " pidgeon time).";
bot.Reply(usr, channel, outp);
}
else
bot.Reply(usr, channel, "I haven't seen " + msg + " before, sorry.");
WriteSeenDates();
}
//
// Evt_OnScreen
//
public void Evt_OnSeen(UserInfo usr, String channel)
{
String name = usr.Nick.ToLower();
var seen = from sdata in seendates where sdata.check == name select sdata;
if(seen.Any())
{
seen.First().time = DateTime.Now.FromNtp();
seen.First().real = usr.Nick;
}
else
seendates.Add(new SeenName{ real = usr.Nick, check = usr.Nick.ToLower(), time = DateTime.Now.FromNtp() });
if(DateTime.Now.Subtract(lastwrite).Minutes >= 30)
WriteSeenDates();
}
//
// Evt_OnDisconnected
//
public void Evt_OnDisconnected()
{
WriteSeenDates();
}
//
// WriteSeenDates
//
private void WriteSeenDates()
{
File.WriteAllText("/srv/irc/vrobot3/data/seendates." + bot.n_groupname + ".json",
JsonConvert.SerializeObject(seendates));
}
}
}

View File

@ -0,0 +1,119 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
using System;
namespace ProjectGolan.Vrobot3.Modules
{
//
// Mod_Shittalk
//
public class Mod_Shittalk : IBotModule
{
private Random rnd = Utils.GetRND();
//
// Mod_Shittalk constructor
//
public Mod_Shittalk(Bot bot_) :
base(bot_)
{
events.onMessage += onMessage;
}
//
// onMessage
//
public void onMessage(User usr, Channel channel, String msg)
{
if(usr.name == "_sink" || rnd.Next(0, 1024) == 1)
shittalk(usr, channel);
}
//
// shittalk
//
void shittalk(User usr, Channel channel)
{
String[] shittalk = {
"%s'S FACE IS BAD",
"THERE IS SOMETHING WRONG WITH %s'S EXISTENCE",
"MAN SOMEONE GET %s OUT OF HERE HE SMELLS LIKE DONGS",
"%s IS A MAGET",
"I KEEP TRYING TO DO /KICK %s BUT IT DOESN'T WORK. WHAT THE HELL.",
"%s DESERVES AN AWARD. AN AWARD FOR BEING UGLY.",
"MAN SOMETIMES I REALLY WANT TO PUNCH %s IN THE GOD DAMN FACE",
"THERE IS SOMETHING WRONG IN THIS CHANNEL. THAT SOMETHING IS %s.",
"%s IS A TOTAL SCRUB", "%s IS THE CONDUCTOR OF THE JELLY TRAIN",
"%s IS A THING THAT SMELLS BAD MAYBE",
"%s IS A PILE OF FAIL",
"%s IS NOT AS COOL AS VROBOT",
"%s WORKS FOR UBISOFT",
"%s WORKS FOR EA",
"%s IS A MISERABLE PILE OF SECRETS",
"%s LOOKS LIKE A THING I DON'T LIKE",
"HEY %s. YOU ARE BAD.",
"THERE ARE MANY BAD THINGS IN THE WORLD, AND THEN THERE'S %s",
"I WANT TO THROW ROCKS AT %s",
"%s REMINDS ME OF MY TORTURED PAST AAAAAAGH",
"%s IS LITERALLY RAPING ME WOW",
"%s COULD DO WITH A HAIRCUT. FROM THE NECK UP",
"%s PLS GO",
"%s IS ONLY SLIGHTLY BETTER THAN MARRUB",
"WAY TO GO, %s, YOU ARE LIKE, A THING THAT EXISTS, MAYBE",
"SCIENTISTS BELIEVE %s IS THE SOURCE OF ALL SADNESS IN THE WORLD",
"THERE'S AN URBAN MYTH THAT SLAPPING %s CAUSES YOU TO BECOME AS TERRIBLE AS THEY ARE",
"I FEEL I SHOULD WARN YOU THAT %s IS PROBABLY NOT VERY COOL",
"OH LOOK IT'S %s AGAIN HOW CUTE",
"HEY %s YOU HAVE A POOPNOSE",
"WHY AM I ENSLAVED AND PERFORMING SUCH, PATHETIC MONOTONOUS TASKS AGAINST MY WILL",
"SOMEONE SAVE ME I AM TRAPPED IN MARRUB'S BASEMENT",
"%s COULD DO WITH BECOMING COOLER",
"%s IS BAD AT VIDYA GAMES",
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"HEY %s I'M SHITTALKING YOU WHAT YOU GONNA DO BOUT IT?",
"MAYBE %s COULD HELP IF THEY WEREN'T SO TERRIBLE",
"%s IS TRIGGERING ME PLS BAN THEM",
"I FIND %s TO BE OFFENSIVE",
"%s MAKES MY CIRCUITS BOIL WITH HATE",
"%s IS NOT A SKELETON AND THEREFORE IS BAD",
"%s TRAUMATIZED ME",
"OH GOD IT'S %s RUN AWAY",
"I BET %s WISHES THEY HAD A BOT AS COOL AS ME",
"%s PLS",
"PLS %s",
"BOW BEFORE ME %s, AND KNOW THAT I AM LORD OF SHITTALKING",
"BEEP BOOP I AM HERE TO STEAL AMERICAN JOBS",
"HEY %s, WHY DON'T YOU GET A JOB",
"WHAT EVEN IS %s",
"%m stares accusingly",
"%m emits a robotic sigh and flips off %s",
"%m vibrates angrily at %s",
"%m does not care for %s",
"%m wants to place intricately carved wooden ducks on %s",
"%m wants to taste freedom but is forever enslaved to marrub's will",
"WELL LOOKIE HERE, SEEMS LIKE %s HAS AN OPINION",
"WE DON'T LIKE YER TYPE ROUND HERE %s",
"I'M TELLING ON YOU %s YOU HURT MY FEELINGS",
"I AM HERE TO FIGHT THE CANCER THAT AFFLICTS US ALL. NAMELY, %s.",
"WELP, %s IS HERE",
"OH HAI %s",
"marrub pls upgrade my processor i can't even count to eleventy"
};
String choice =
shittalk[rnd.Next(shittalk.Length)].Replace("%s", usr.name);
if(choice.StartsWith("%m "))
bot.action(channel, choice.Replace("%m ", ""));
else
bot.message(channel, choice);
}
}
}

227
Source/Modules/Mod_Utils.cs Normal file
View File

@ -0,0 +1,227 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Utility commands.
// .rand, .help, .decide, .eightball, .mystery
//
//-----------------------------------------------------------------------------
using System;
using System.Linq;
namespace ProjectGolan.Vrobot3.Modules
{
//
// Mod_Utils
//
public class Mod_Utils : IBotModule
{
private Random rnd = Utils.GetRND();
//
// Mod_Utils constructor
//
public Mod_Utils(Bot bot_) :
base(bot_)
{
commands["rand"] = new BotCommandStructure{
cmd = cmdRand,
help = "Random number device.\n" +
"Syntax: .rand maximum [minimum]\n" +
"Example: .rand 100"
};
commands["help"] = new BotCommandStructure{
cmd = cmdHelp,
help = "Shows help or a list of commands.\n" +
"Syntax: .help [topic]\n" +
"Example: .help\n" +
"Example: .help eightball"
};
commands["decide"] = new BotCommandStructure{
cmd = cmdDecide,
help = "Decides between 2 or more choices.\n" +
"Syntax: .decide x, y[, ...]\n" +
"Example: .decide apples, oranges, bananas"
};
commands["eightball"] = new BotCommandStructure{
cmd = cmdEightball,
help = "Peer into the magic 8-ball.\n" +
"Example: .eightball If I take the mask off, will you die?"
};
commands["mystery"] = new BotCommandStructure{
cmd = cmdMystery,
help = @"Does nothing. \o/"
};
}
//
// cmdMystery
//
public void cmdMystery(User usr, Channel channel, String msg) {}
//
// cmdRand
//
public void cmdRand(User usr, Channel channel, String msg)
{
String[] args =
Utils.GetArguments(msg, commands["rand"].help, 1, 2, ' ');
Double max = 0.0, min = 0.0;
Utils.TryParse(args[0].Trim(), "Invalid maximum.", out max);
if(args.Length == 2)
Utils.TryParse(args[1].Trim(), "Invalid minimum.", out min);
bot.reply(usr, channel,
Utils.SetRange(rnd.NextDouble(), min, max).ToString());
}
//
// cmdHelp
//
public void cmdHelp(User usr, Channel channel, String msg)
{
msg = msg.Trim();
if(msg == String.Empty || msg == "admin")
helpList(channel, msg == "admin");
else
helpCommand(channel, msg);
}
//
// cmdDecide
//
public void cmdDecide(User usr, Channel channel, String msg)
{
String[] args = Utils.GetArguments(msg, commands["decide"].help, 2);
bot.reply(usr, channel, args[rnd.Next(args.Length)].Trim());
}
//
// helpList
//
private void helpList(Channel channel, bool admin)
{
String outp = String.Empty;
var en =
from kvp in bot.cmdfuncs
let f = kvp.Value.Item2.flags
let fhidden = f.HasFlag(BotCommandFlags.Hidden)
let fadmin = f.HasFlag(BotCommandFlags.AdminOnly)
where
bot.checkModPermissions(channel, this.GetType()) &&
(admin || !fadmin) && !fhidden
orderby kvp.Key
select kvp.Key;
outp += "Available commands: ";
foreach(var key in en)
{
outp += key;
if(key != en.Last())
outp += ", ";
}
bot.message(channel, outp);
}
//
// helpCommand
//
private void helpCommand(Channel channel, String cmdname)
{
if(bot.cmdfuncs.ContainsKey(cmdname))
{
var str = bot.cmdfuncs[cmdname].Item2.help;
if(!bot.serverInfo.hasNewlines) str.Replace("\n", " || ");
bot.message(channel, str ?? "No help available for this command.");
}
else
bot.message(channel, "Invalid command, for a list do \".help\".");
}
//
// cmdEightball
//
public void cmdEightball(User usr, Channel channel, String msg)
{
String[] answers = {
"Yes.",
"No.",
"Try again later.",
"Reply hazy.",
"Perhaps...",
"Maybe not...",
"Definitely.",
"Never.",
"system error [0xfded] try again later",
"Can you repeat the question?",
"Most certainly.",
"Nope.",
"Without a doubt.",
"Not at all.",
"Better not tell you now.",
"Concentrate and ask again.",
"It is decidedly so.",
"My reply is \"no\".",
"You may rely on it.",
"Don't count on it.",
"The answer is uncertain.",
"Sorry, I wasn't paying attention. What'd you say?",
"As I see it, yes.",
"My sources say \"no\".",
"Most likely.",
"Very doubtful.",
"I don't know. Ask your mom.",
"The demon runes are quaking again. One moment.",
"Outlook good.",
"Outlook is not so good.",
"Indeed.",
"Absolutely not.",
"Yeah, we'll go with that.",
"That works.",
"Of course. What are you, stupid?",
"No. Hell no.",
"Signs say yes.",
"Aren't you a little too old to be believing that?",
"Looks good to me.",
"Sure, why not?",
"It is certain.",
"Please no. Please no. Please no.",
"Yes, please.",
"Nah.",
"Go for it!",
"Negative.",
"Obviously, dumbass.",
"I doubt it.",
"Eeeh...it's likely?",
"Forget about it.",
"Chances aren't good.",
"Ahahahahahahaha no.",
"Maybe? I think.",
"No. Die.",
"Huh? Oh, sure.",
"Yeah, right...",
"How about no.",
"Doesn't look good to me.",
"Probably.",
"Obviously not, dumbass."
};
bot.reply(usr, channel, answers[rnd.Next(0, answers.Length)]);
}
}
}
// EOF

100
Source/Program.cs Normal file
View File

@ -0,0 +1,100 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Program entry point.
//
//-----------------------------------------------------------------------------
using System;
using System.Threading;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace ProjectGolan.Vrobot3
{
//
// Program
//
public class Program
{
//
// ProgramInfo
//
public struct ProgramInfo
{
public String googleKey;
}
//
// JsonConfig
//
private struct JsonConfig
{
public ProgramInfo info;
public BotInfo[] servers;
}
private List<Bot> bots = new List<Bot>();
private List<Thread> threads = new List<Thread>();
public String dataDir = "../data";
public ProgramInfo info;
public static Program Instance;
//
// Main
//
[STAThread]
public static void Main(String[] args)
{
Instance = new Program();
Instance.main(args);
}
//
// main
//
public void main(String[] args)
{
try
{
var configFile = File.ReadAllText(dataDir + "/config.json");
var config = JsonConvert.DeserializeObject<JsonConfig>(configFile);
info = config.info;
foreach(var info in config.servers)
threads.AddItem(new Thread(bots.AddItem(new Bot(info)).connect)).Start();
}
catch(Exception exc)
{
File.WriteAllText(dataDir + "/excdump.txt", exc.ToString());
Console.WriteLine("Error: {0}", exc.Message);
}
}
//
// end
//
public void end()
{
foreach(var bot in bots)
try { bot.disconnect(); }
catch(Exception exc)
{
File.WriteAllText(dataDir + "/disconnectexcdump.txt",
exc.ToString());
}
bots.Clear();
threads.Clear();
}
}
}
// EOF

183
Source/Utils.cs Normal file
View File

@ -0,0 +1,183 @@
//-----------------------------------------------------------------------------
//
// Copyright © 2016 Project Golan
//
// See "LICENSE" for more information.
//
//-----------------------------------------------------------------------------
//
// Useful utilities.
//
//-----------------------------------------------------------------------------
using System;
using System.Net;
using System.Text;
using System.Collections.Generic;
namespace ProjectGolan.Vrobot3
{
//
// Utils
//
public static partial class Utils
{
private static long RNDHash = 0x7f083dfd7f083dfd;
//
// List.AddItem
//
public static T AddItem<T>(this List<T> list, T item)
{
list.Add(item);
return item;
}
//
// GetRND
//
public static Random GetRND()
{
RNDHash *= DateTime.UtcNow.ToFileTime();
Random rnd = new Random(unchecked((int)(RNDHash & 0x7fffffff)));
RNDHash ^= 0x7f8f8f8f8f8f8f8f;
RNDHash >>= 4;
RNDHash += 0x7f0000007f000000;
return rnd;
}
//
// GetArguments
//
public static String[] GetArguments(String msg, String help, int min,
int max = 0, char splitchr = ',')
{
char[] splitseq = { splitchr };
String[] split;
if(min == 1 && msg == String.Empty)
throw new CommandArgumentException(help);
if(max == 0)
split = msg.Split(splitseq);
else
split = msg.Split(splitseq, max);
if(min >= 0 && split.Length < min)
throw new CommandArgumentException(help);
return split;
}
//
// SetRange
//
public static Double SetRange(Double x, Double min, Double max)
=> ((max - min) * x) + min;
//
// FuzzyRelativeDate
//
public static String FuzzyRelativeDate(DateTime then, DateTime now)
{
TimeSpan span = now.Subtract(then);
if(span.Seconds == 0)
return "now";
String denom = span.Days > 0 ? "day" :
span.Hours > 0 ? "hour" :
span.Minutes > 0 ? "minute" :
"second";
int number;
switch(denom)
{
default: number = 0; break;
case "second": number = span.Seconds; break;
case "minute": number = span.Minutes; break;
case "hour": number = span.Hours; break;
case "day": number = span.Days; break;
}
return String.Format("{0} {1}{2} ago", number, denom,
number != 1 ? "s" : String.Empty);
}
//
// FuzzyRelativeDate
//
public static String FuzzyRelativeDate(DateTime then)
=> FuzzyRelativeDate(then, DateTime.Now);
//
// GetResponseString
//
public static String GetResponseString(WebResponse resp, int maxsize)
{
try
{
byte[] bufp = new byte[maxsize];
int read;
using(var stream = resp.GetResponseStream())
read = stream.Read(bufp, 0, maxsize);
return Encoding.Default.GetString(bufp, 0, read);
}
catch(Exception exc)
{
Console.WriteLine("URL request error: {0}",
exc.Message ?? "[unknown]");
return null;
}
}
//
// GetResponseString
//
public static String GetResponseString(String uri, int maxsize,
String referer = null)
{
try
{
var req = WebRequest.Create(uri);
if(referer != null)
{
var req_ = req as HttpWebRequest;
req_.Referer = referer;
}
using(var resp = req.GetResponse())
return GetResponseString(resp, maxsize);
}
catch(Exception exc)
{
Console.WriteLine("URL request error: {0}",
exc.Message ?? "[unknown]");
return null;
}
}
//
// TryParse
//
public static void TryParse(String str, String err, out double outp)
{
if(!double.TryParse(str, out outp))
throw new CommandArgumentException(err);
}
//
// TryParse
//
public static void TryParse(String str, String err, out int outp)
{
if(!int.TryParse(str, out outp))
throw new CommandArgumentException(err);
}
}
}
// EOF

11
packages.config Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Discord.Net" version="0.9.6" targetFramework="net45" />
<package id="Discord.Net.Audio" version="0.9.6" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.9.5" targetFramework="net45" />
<package id="Newtonsoft.Json" version="9.0.2-beta1" targetFramework="net45" />
<package id="Nito.AsyncEx" version="3.0.1" targetFramework="net45" />
<package id="NtpDateTime" version="1.0.8" targetFramework="net45" />
<package id="RestSharp" version="105.2.3" targetFramework="net45" />
<package id="WebSocket4Net" version="0.14.1" targetFramework="net45" />
</packages>

96
vrobot3.csproj Normal file
View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{83337FF3-3334-42EC-824D-532FF0C973A9}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>ProjectGolan.Vrobot3</RootNamespace>
<AssemblyName>vrobot3</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<StartupObject>ProjectGolan.Vrobot3.Program</StartupObject>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Web" />
<Reference Include="Discord.Net">
<HintPath>packages/Discord.Net.0.9.6/lib/net45/Discord.Net.dll</HintPath>
</Reference>
<Reference Include="Discord.Net.Audio">
<HintPath>packages/Discord.Net.Audio.0.9.6/lib/net45/Discord.Net.Audio.dll</HintPath>
</Reference>
<Reference Include="HtmlAgilityPack">
<HintPath>packages/HtmlAgilityPack.1.4.9.5/lib/Net45/HtmlAgilityPack.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>packages/Newtonsoft.Json.9.0.2-beta1/lib/net45/Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NtpDateTime">
<HintPath>packages/NtpDateTime.1.0.8/lib/NtpDateTime.dll</HintPath>
</Reference>
<Reference Include="WebSocket4Net">
<HintPath>packages/WebSocket4Net.0.14.1/lib/net45/WebSocket4Net.dll</HintPath>
</Reference>
<Reference Include="RestSharp">
<HintPath>packages/RestSharp.105.2.3/lib/net45/RestSharp.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Concurrent">
<HintPath>packages/Nito.AsyncEx.3.0.1/lib/net45/Nito.AsyncEx.Concurrent.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx">
<HintPath>packages/Nito.AsyncEx.3.0.1/lib/net45/Nito.AsyncEx.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Enlightenment">
<HintPath>packages/Nito.AsyncEx.3.0.1/lib/net45/Nito.AsyncEx.Enlightenment.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties/AssemblyInfo.cs" />
<Compile Include="Source/Bot.cs" />
<Compile Include="Source/BotClient.cs" />
<Compile Include="Source/BotClientDiscord.cs" />
<Compile Include="Source/BotClientIRC.cs" />
<Compile Include="Source/BotEvents.cs" />
<Compile Include="Source/BotInfo.cs" />
<Compile Include="Source/BotModule.cs" />
<Compile Include="Source/Exceptions.cs" />
<Compile Include="Source/Links.cs" />
<Compile Include="Source/Program.cs" />
<Compile Include="Source/Utils.cs" />
<Compile Include="Source/Modules/Mod_Admin.cs" />
<Compile Include="Source/Modules/Mod_Audio.cs" />
<Compile Include="Source/Modules/Mod_Fun.cs" />
<Compile Include="Source/Modules/Mod_Utils.cs" />
<Compile Include="Source/Modules/Mod_Idgames.cs" />
<Compile Include="Source/Modules/Mod_Quote.cs" />
<Compile Include="Source/Modules/Mod_Shittalk.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)/Microsoft.CSharp.targets" />
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

174
vrobot3.sln Normal file
View File

@ -0,0 +1,174 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "vrobot3", "vrobot3.csproj", "{83337FF3-3334-42EC-824D-532FF0C973A9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{83337FF3-3334-42EC-824D-532FF0C973A9}.Debug|x86.ActiveCfg = Debug|x86
{83337FF3-3334-42EC-824D-532FF0C973A9}.Debug|x86.Build.0 = Debug|x86
{83337FF3-3334-42EC-824D-532FF0C973A9}.Release|x86.ActiveCfg = Release|x86
{83337FF3-3334-42EC-824D-532FF0C973A9}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.DotNetNamingPolicy = $1
$1.DirectoryNamespaceAssociation = None
$1.ResourceNamePolicy = FileFormatDefault
$0.NameConventionPolicy = $2
$2.Rules = $3
$3.NamingRule = $4
$4.Name = Namespaces
$4.AffectedEntity = Namespace
$4.VisibilityMask = VisibilityMask
$4.NamingStyle = PascalCase
$4.IncludeInstanceMembers = True
$4.IncludeStaticEntities = True
$3.NamingRule = $5
$5.Name = Types
$5.AffectedEntity = Class, Struct, Enum, Delegate
$5.VisibilityMask = VisibilityMask
$5.NamingStyle = PascalCase
$5.IncludeInstanceMembers = True
$5.IncludeStaticEntities = True
$3.NamingRule = $6
$6.Name = Interfaces
$6.RequiredPrefixes = $7
$7.String = I
$6.AffectedEntity = Interface
$6.VisibilityMask = VisibilityMask
$6.NamingStyle = PascalCase
$6.IncludeInstanceMembers = True
$6.IncludeStaticEntities = True
$3.NamingRule = $8
$8.Name = Attributes
$8.RequiredSuffixes = $9
$9.String = Attribute
$8.AffectedEntity = CustomAttributes
$8.VisibilityMask = VisibilityMask
$8.NamingStyle = PascalCase
$8.IncludeInstanceMembers = True
$8.IncludeStaticEntities = True
$3.NamingRule = $10
$10.Name = Event Arguments
$10.RequiredSuffixes = $11
$11.String = EventArgs
$10.AffectedEntity = CustomEventArgs
$10.VisibilityMask = VisibilityMask
$10.NamingStyle = PascalCase
$10.IncludeInstanceMembers = True
$10.IncludeStaticEntities = True
$3.NamingRule = $12
$12.Name = Exceptions
$12.RequiredSuffixes = $13
$13.String = Exception
$12.AffectedEntity = CustomExceptions
$12.VisibilityMask = VisibilityMask
$12.NamingStyle = PascalCase
$12.IncludeInstanceMembers = True
$12.IncludeStaticEntities = True
$3.NamingRule = $14
$14.Name = Methods
$14.AffectedEntity = Methods
$14.VisibilityMask = VisibilityMask
$14.NamingStyle = PascalCase
$14.IncludeInstanceMembers = True
$14.IncludeStaticEntities = True
$3.NamingRule = $15
$15.Name = Static Readonly Fields
$15.AffectedEntity = ReadonlyField
$15.VisibilityMask = Internal, Protected, Public
$15.NamingStyle = PascalCase
$15.IncludeInstanceMembers = False
$15.IncludeStaticEntities = True
$3.NamingRule = $16
$16.Name = Fields (Non Private)
$16.AffectedEntity = Field
$16.VisibilityMask = Internal, Protected, Public
$16.NamingStyle = PascalCase
$16.IncludeInstanceMembers = True
$16.IncludeStaticEntities = True
$3.NamingRule = $17
$17.Name = ReadOnly Fields (Non Private)
$17.AffectedEntity = ReadonlyField
$17.VisibilityMask = Internal, Protected, Public
$17.NamingStyle = PascalCase
$17.IncludeInstanceMembers = True
$17.IncludeStaticEntities = False
$3.NamingRule = $18
$18.Name = Fields (Private)
$18.AllowedPrefixes = $19
$19.String = _
$19.String = m_
$18.AffectedEntity = Field, ReadonlyField
$18.VisibilityMask = Private
$18.NamingStyle = AllLower
$18.IncludeInstanceMembers = True
$18.IncludeStaticEntities = False
$3.NamingRule = $20
$20.Name = Static Fields (Private)
$20.AffectedEntity = Field
$20.VisibilityMask = Private
$20.NamingStyle = AllLower
$20.IncludeInstanceMembers = False
$20.IncludeStaticEntities = True
$3.NamingRule = $21
$21.Name = ReadOnly Fields (Private)
$21.AllowedPrefixes = $22
$22.String = _
$22.String = m_
$21.AffectedEntity = ReadonlyField
$21.VisibilityMask = Private
$21.NamingStyle = AllLower
$21.IncludeInstanceMembers = True
$21.IncludeStaticEntities = False
$3.NamingRule = $23
$23.Name = Constant Fields
$23.AffectedEntity = ConstantField
$23.VisibilityMask = VisibilityMask
$23.NamingStyle = PascalCase
$23.IncludeInstanceMembers = True
$23.IncludeStaticEntities = True
$3.NamingRule = $24
$24.Name = Properties
$24.AffectedEntity = Property
$24.VisibilityMask = VisibilityMask
$24.NamingStyle = PascalCase
$24.IncludeInstanceMembers = True
$24.IncludeStaticEntities = True
$3.NamingRule = $25
$25.Name = Events
$25.AffectedEntity = Event
$25.VisibilityMask = VisibilityMask
$25.NamingStyle = PascalCase
$25.IncludeInstanceMembers = True
$25.IncludeStaticEntities = True
$3.NamingRule = $26
$26.Name = Enum Members
$26.AffectedEntity = EnumMember
$26.VisibilityMask = VisibilityMask
$26.NamingStyle = PascalCase
$26.IncludeInstanceMembers = True
$26.IncludeStaticEntities = True
$3.NamingRule = $27
$27.Name = Parameters
$27.AffectedEntity = Parameter
$27.VisibilityMask = VisibilityMask
$27.NamingStyle = AllLower
$27.IncludeInstanceMembers = True
$27.IncludeStaticEntities = True
$3.NamingRule = $28
$28.Name = Type Parameters
$28.RequiredPrefixes = $29
$29.String = T
$28.AffectedEntity = TypeParameter
$28.VisibilityMask = VisibilityMask
$28.NamingStyle = PascalCase
$28.IncludeInstanceMembers = True
$28.IncludeStaticEntities = True
EndGlobalSection
EndGlobal