Initial commit.
commit
ae778d37e0
|
@ -0,0 +1,7 @@
|
|||
.nuget
|
||||
obj
|
||||
bin
|
||||
data
|
||||
packages
|
||||
*.swp
|
||||
Makefile
|
|
@ -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.*")]
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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 ? "+)" : ")")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
Loading…
Reference in New Issue