Initial commit.
commit
ae778d37e0
@ -0,0 +1,7 @@
|
||||
.nuget
|
||||
obj
|
||||
bin
|
||||
data
|
||||
packages
|
||||
*.swp
|
||||
Makefile
|
@ -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,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
|