Split config into builder and immutable classes, added some audioservice extension methods.
This commit is contained in:
@@ -47,6 +47,9 @@
|
||||
<Compile Include="..\Discord.Net.Audio\AudioExtensions.cs">
|
||||
<Link>AudioExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net.Audio\AudioMode.cs">
|
||||
<Link>AudioMode.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net.Audio\AudioService.cs">
|
||||
<Link>AudioService.cs</Link>
|
||||
</Compile>
|
||||
|
||||
@@ -61,8 +61,8 @@ namespace Discord.Audio
|
||||
public Stream OutputStream { get; }
|
||||
|
||||
public CancellationToken CancelToken { get; private set; }
|
||||
public string SessionId { get; private set; }
|
||||
|
||||
public string SessionId => GatewaySocket.SessionId;
|
||||
|
||||
public ConnectionState State => VoiceSocket.State;
|
||||
public Server Server => VoiceSocket.Server;
|
||||
public Channel Channel => VoiceSocket.Channel;
|
||||
@@ -71,7 +71,7 @@ namespace Discord.Audio
|
||||
{
|
||||
Id = id;
|
||||
_config = client.Config;
|
||||
Service = client.Audio();
|
||||
Service = client.Services.Get<AudioService>();
|
||||
Config = Service.Config;
|
||||
Serializer = client.Serializer;
|
||||
_gatewayState = (int)ConnectionState.Disconnected;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Audio
|
||||
{
|
||||
@@ -9,14 +10,17 @@ namespace Discord.Audio
|
||||
client.Services.Add(new AudioService(config));
|
||||
return client;
|
||||
}
|
||||
public static DiscordClient UsingAudio(this DiscordClient client, Action<AudioServiceConfig> configFunc = null)
|
||||
public static DiscordClient UsingAudio(this DiscordClient client, Action<AudioServiceConfigBuilder> configFunc = null)
|
||||
{
|
||||
var config = new AudioServiceConfig();
|
||||
configFunc(config);
|
||||
client.Services.Add(new AudioService(config));
|
||||
var builder = new AudioServiceConfigBuilder();
|
||||
configFunc(builder);
|
||||
client.Services.Add(new AudioService(builder));
|
||||
return client;
|
||||
}
|
||||
public static AudioService Audio(this DiscordClient client, bool required = true)
|
||||
=> client.Services.Get<AudioService>(required);
|
||||
|
||||
public static Task<IAudioClient> JoinAudio(this Channel channel) => channel.Client.Services.Get<AudioService>().Join(channel);
|
||||
public static Task LeaveAudio(this Channel channel) => channel.Client.Services.Get<AudioService>().Leave(channel);
|
||||
public static Task LeaveAudio(this Server server) => server.Client.Services.Get<AudioService>().Leave(server);
|
||||
public static IAudioClient GetAudioClient(Server server) => server.Client.Services.Get<AudioService>().GetClient(server);
|
||||
}
|
||||
}
|
||||
|
||||
9
src/Discord.Net.Audio/AudioMode.cs
Normal file
9
src/Discord.Net.Audio/AudioMode.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Discord.Audio
|
||||
{
|
||||
public enum AudioMode : byte
|
||||
{
|
||||
Outgoing = 1,
|
||||
Incoming = 2,
|
||||
Both = Outgoing | Incoming
|
||||
}
|
||||
}
|
||||
@@ -29,16 +29,23 @@ namespace Discord.Audio
|
||||
private void OnUserIsSpeakingUpdated(User user, bool isSpeaking)
|
||||
=> UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking));
|
||||
|
||||
public AudioService(AudioServiceConfig config)
|
||||
public AudioService()
|
||||
: this(new AudioServiceConfigBuilder())
|
||||
{
|
||||
}
|
||||
public AudioService(AudioServiceConfigBuilder builder)
|
||||
: this(builder.Build())
|
||||
{
|
||||
}
|
||||
public AudioService(AudioServiceConfig config)
|
||||
{
|
||||
Config = config;
|
||||
Config = config;
|
||||
_asyncLock = new AsyncLock();
|
||||
|
||||
}
|
||||
void IService.Install(DiscordClient client)
|
||||
{
|
||||
Client = client;
|
||||
Config.Lock();
|
||||
|
||||
if (Config.EnableMultiserver)
|
||||
_voiceClients = new ConcurrentDictionary<ulong, AudioClient>();
|
||||
|
||||
@@ -1,62 +1,51 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace Discord.Audio
|
||||
namespace Discord.Audio
|
||||
{
|
||||
public enum AudioMode : byte
|
||||
public class AudioServiceConfigBuilder
|
||||
{
|
||||
Outgoing = 1,
|
||||
Incoming = 2,
|
||||
Both = Outgoing | Incoming
|
||||
}
|
||||
/// <summary> Enables the voice websocket and UDP client and specifies how it will be used. </summary>
|
||||
public AudioMode Mode { get; set; } = AudioMode.Outgoing;
|
||||
|
||||
public class AudioServiceConfig
|
||||
{
|
||||
public const int MaxBitrate = 128;
|
||||
|
||||
/// <summary> Max time in milliseconds to wait for DiscordAudioClient to connect and initialize. </summary>
|
||||
public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } }
|
||||
private int _connectionTimeout = 30000;
|
||||
|
||||
//Experimental Features
|
||||
/// <summary> (Experimental) Enables the voice websocket and UDP client and specifies how it will be used. </summary>
|
||||
public AudioMode Mode { get { return _mode; } set { SetValue(ref _mode, value); } }
|
||||
private AudioMode _mode = AudioMode.Outgoing;
|
||||
|
||||
/// <summary> (Experimental) Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local or system folder. </summary>
|
||||
public bool EnableEncryption { get { return _enableEncryption; } set { SetValue(ref _enableEncryption, value); } }
|
||||
private bool _enableEncryption = true;
|
||||
|
||||
/// <summary> (Experimental) Enables the client to be simultaneously connected to multiple channels at once (Discord still limits you to one channel per server). </summary>
|
||||
public bool EnableMultiserver { get { return _enableMultiserver; } set { SetValue(ref _enableMultiserver, value); } }
|
||||
private bool _enableMultiserver = false;
|
||||
/// <summary> Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local or system folder. </summary>
|
||||
public bool EnableEncryption { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Enables the client to be simultaneously connected to multiple channels at once (Discord still limits you to one channel per server).
|
||||
/// This option uses a lot of CPU power and network bandwidth, as a new gateway connection needs to be spun up per server. Use sparingly.
|
||||
/// </summary>
|
||||
public bool EnableMultiserver { get; set; } = false;
|
||||
|
||||
/// <summary> Gets or sets the buffer length (in milliseconds) for outgoing voice packets. </summary>
|
||||
public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } }
|
||||
private int _bufferLength = 1000;
|
||||
|
||||
public int BufferLength { get; set; } = 1000;
|
||||
/// <summary> Gets or sets the bitrate used (in kbit/s, between 1 and MaxBitrate inclusively) for outgoing voice packets. A null value will use default Opus settings. </summary>
|
||||
public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } }
|
||||
private int? _bitrate = null;
|
||||
public int? Bitrate { get; set; } = null;
|
||||
/// <summary> Gets or sets the number of channels (1 or 2) used in both input provided to IAudioClient and output send to Discord. Defaults to 2 (stereo). </summary>
|
||||
public int Channels { get { return _channels; } set { SetValue(ref _channels, value); } }
|
||||
private int _channels = 2;
|
||||
public int Channels { get; set; } = 2;
|
||||
|
||||
//Lock
|
||||
protected bool _isLocked;
|
||||
internal void Lock() { _isLocked = true; }
|
||||
protected void SetValue<T>(ref T storage, T value)
|
||||
{
|
||||
if (_isLocked)
|
||||
throw new InvalidOperationException("Unable to modify a service's configuration after it has been created.");
|
||||
storage = value;
|
||||
}
|
||||
public AudioServiceConfig Build() => new AudioServiceConfig(this);
|
||||
}
|
||||
|
||||
public AudioServiceConfig Clone()
|
||||
{
|
||||
var config = MemberwiseClone() as AudioServiceConfig;
|
||||
config._isLocked = false;
|
||||
return config;
|
||||
}
|
||||
}
|
||||
public class AudioServiceConfig
|
||||
{
|
||||
public const int MaxBitrate = 128;
|
||||
|
||||
public AudioMode Mode { get; }
|
||||
|
||||
public bool EnableEncryption { get; }
|
||||
public bool EnableMultiserver { get; }
|
||||
|
||||
public int BufferLength { get; }
|
||||
public int? Bitrate { get; }
|
||||
public int Channels { get; }
|
||||
|
||||
internal AudioServiceConfig(AudioServiceConfigBuilder builder)
|
||||
{
|
||||
Mode = builder.Mode;
|
||||
|
||||
EnableEncryption = builder.EnableEncryption;
|
||||
EnableMultiserver = builder.EnableMultiserver;
|
||||
|
||||
BufferLength = builder.BufferLength;
|
||||
Bitrate = builder.Bitrate;
|
||||
Channels = builder.Channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,12 @@ namespace Discord.Commands
|
||||
client.Services.Add(new CommandService(config));
|
||||
return client;
|
||||
}
|
||||
public static DiscordClient UsingCommands(this DiscordClient client, Action<CommandServiceConfig> configFunc = null)
|
||||
public static DiscordClient UsingCommands(this DiscordClient client, Action<CommandServiceConfigBuilder> configFunc = null)
|
||||
{
|
||||
var config = new CommandServiceConfig();
|
||||
configFunc(config);
|
||||
client.Services.Add(new CommandService(config));
|
||||
var builder = new CommandServiceConfigBuilder();
|
||||
configFunc(builder);
|
||||
client.Services.Add(new CommandService(builder));
|
||||
return client;
|
||||
}
|
||||
public static CommandService Commands(this DiscordClient client, bool required = true)
|
||||
=> client.Services.Get<CommandService>(required);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,18 @@ namespace Discord.Commands
|
||||
private void OnCommandError(CommandErrorType errorType, CommandEventArgs args, Exception ex = null)
|
||||
=> CommandErrored(this, new CommandErrorEventArgs(errorType, args, ex));
|
||||
|
||||
public CommandService()
|
||||
: this(new CommandServiceConfigBuilder())
|
||||
{
|
||||
}
|
||||
public CommandService(CommandServiceConfigBuilder builder)
|
||||
: this(builder.Build())
|
||||
{
|
||||
if (builder.ExecuteHandler != null)
|
||||
CommandExecuted += builder.ExecuteHandler;
|
||||
if (builder.ErrorHandler != null)
|
||||
CommandErrored += builder.ErrorHandler;
|
||||
}
|
||||
public CommandService(CommandServiceConfig config)
|
||||
{
|
||||
Config = config;
|
||||
@@ -42,9 +54,8 @@ namespace Discord.Commands
|
||||
void IService.Install(DiscordClient client)
|
||||
{
|
||||
Client = client;
|
||||
Config.Lock();
|
||||
|
||||
if (Config.HelpMode != HelpMode.Disable)
|
||||
if (Config.HelpMode != HelpMode.Disabled)
|
||||
{
|
||||
CreateCommand("help")
|
||||
.Parameter("command", ParameterType.Multiple)
|
||||
|
||||
@@ -2,36 +2,45 @@
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
public class CommandServiceConfig
|
||||
public class CommandServiceConfigBuilder
|
||||
{
|
||||
/// <summary> Gets or sets the prefix character used to trigger commands, if ActivationMode has the Char flag set. </summary>
|
||||
public char? PrefixChar { get { return _prefixChar; } set { SetValue(ref _prefixChar, value); } }
|
||||
private char? _prefixChar = null;
|
||||
|
||||
public char? PrefixChar { get; set; } = null;
|
||||
/// <summary> Gets or sets whether a message beginning with a mention to the logged-in user should be treated as a command. </summary>
|
||||
public bool AllowMentionPrefix { get { return _allowMentionPrefix; } set { SetValue(ref _allowMentionPrefix, value); } }
|
||||
private bool _allowMentionPrefix = true;
|
||||
|
||||
public bool AllowMentionPrefix { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a custom function used to detect messages that should be treated as commands.
|
||||
/// This function should a positive one indicating the index of where the in the message's RawText the command begins,
|
||||
/// and a negative value if the message should be ignored.
|
||||
/// </summary>
|
||||
public Func<Message, int> CustomPrefixHandler { get { return _customPrefixHandler; } set { SetValue(ref _customPrefixHandler, value); } }
|
||||
private Func<Message, int> _customPrefixHandler = null;
|
||||
public Func<Message, int> CustomPrefixHandler { get; set; } = null;
|
||||
|
||||
/// <summary> Gets or sets whether a help function should be automatically generated. </summary>
|
||||
public HelpMode HelpMode { get { return _helpMode; } set { SetValue(ref _helpMode, value); } }
|
||||
private HelpMode _helpMode = HelpMode.Disable;
|
||||
public HelpMode HelpMode { get; set; } = HelpMode.Disabled;
|
||||
|
||||
//Lock
|
||||
protected bool _isLocked;
|
||||
internal void Lock() { _isLocked = true; }
|
||||
protected void SetValue<T>(ref T storage, T value)
|
||||
{
|
||||
if (_isLocked)
|
||||
throw new InvalidOperationException("Unable to modify a service's configuration after it has been created.");
|
||||
storage = value;
|
||||
}
|
||||
|
||||
/// <summary> Gets or sets a handler that is called on any successful command execution. </summary>
|
||||
public EventHandler<CommandEventArgs> ExecuteHandler { get; set; }
|
||||
/// <summary> Gets or sets a handler that is called on any error during command parsing or execution. </summary>
|
||||
public EventHandler<CommandErrorEventArgs> ErrorHandler { get; set; }
|
||||
|
||||
public CommandServiceConfig Build() => new CommandServiceConfig(this);
|
||||
}
|
||||
public class CommandServiceConfig
|
||||
{
|
||||
public char? PrefixChar { get; }
|
||||
public bool AllowMentionPrefix { get; }
|
||||
public Func<Message, int> CustomPrefixHandler { get; }
|
||||
|
||||
/// <summary> Gets or sets whether a help function should be automatically generated. </summary>
|
||||
public HelpMode HelpMode { get; set; } = HelpMode.Disabled;
|
||||
|
||||
internal CommandServiceConfig(CommandServiceConfigBuilder builder)
|
||||
{
|
||||
PrefixChar = builder.PrefixChar;
|
||||
AllowMentionPrefix = builder.AllowMentionPrefix;
|
||||
CustomPrefixHandler = builder.CustomPrefixHandler;
|
||||
HelpMode = builder.HelpMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
public enum HelpMode
|
||||
{
|
||||
/// <summary> Disable the automatic help command. </summary>
|
||||
Disable,
|
||||
Disabled,
|
||||
/// <summary> Use the automatic help command and respond in the channel the command is used. </summary>
|
||||
Public,
|
||||
/// <summary> Use the automatic help command and respond in a private message. </summary>
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace Discord.Modules
|
||||
|
||||
public void CreateCommands(string prefix, Action<CommandGroupBuilder> config)
|
||||
{
|
||||
var commandService = Client.Commands(true);
|
||||
var commandService = Client.Services.Get<CommandService>();
|
||||
commandService.CreateGroup(prefix, x =>
|
||||
{
|
||||
x.Category(Name);
|
||||
|
||||
@@ -409,6 +409,9 @@
|
||||
<Compile Include="..\Discord.Net\Enums\ImageType.cs">
|
||||
<Link>Enums\ImageType.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Enums\LogSeverity.cs">
|
||||
<Link>Enums\LogSeverity.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Enums\PermissionTarget.cs">
|
||||
<Link>Enums\PermissionTarget.cs</Link>
|
||||
</Compile>
|
||||
|
||||
@@ -5,15 +5,11 @@ namespace Discord
|
||||
{
|
||||
internal static class TaskHelper
|
||||
{
|
||||
public static Task CompletedTask { get; }
|
||||
static TaskHelper()
|
||||
{
|
||||
#if DOTNET54
|
||||
CompletedTask = Task.CompletedTask;
|
||||
public static Task CompletedTask => Task.CompletedTask;
|
||||
#else
|
||||
CompletedTask = Task.Delay(0);
|
||||
public static Task CompletedTask => Task.Delay(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Func<Task> ToAsync(Action action)
|
||||
{
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public abstract class Config<T>
|
||||
where T : Config<T>
|
||||
{
|
||||
protected bool _isLocked;
|
||||
protected internal void Lock() { _isLocked = true; }
|
||||
protected void SetValue<U>(ref U storage, U value)
|
||||
{
|
||||
if (_isLocked)
|
||||
throw new InvalidOperationException("Unable to modify a discord client's configuration after it has been created.");
|
||||
storage = value;
|
||||
}
|
||||
|
||||
public T Clone()
|
||||
{
|
||||
var config = MemberwiseClone() as T;
|
||||
config._isLocked = false;
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,28 +76,33 @@ namespace Discord
|
||||
public IEnumerable<Region> Regions => _regions.Select(x => x.Value);
|
||||
|
||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary>
|
||||
public DiscordClient(Action<DiscordConfig> configFunc)
|
||||
public DiscordClient(Action<DiscordConfigBuilder> configFunc)
|
||||
: this(ProcessConfig(configFunc))
|
||||
{
|
||||
}
|
||||
private static DiscordConfig ProcessConfig(Action<DiscordConfig> func)
|
||||
private static DiscordConfigBuilder ProcessConfig(Action<DiscordConfigBuilder> func)
|
||||
{
|
||||
var config = new DiscordConfig();
|
||||
var config = new DiscordConfigBuilder();
|
||||
func(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary>
|
||||
public DiscordClient()
|
||||
: this((DiscordConfig)null)
|
||||
: this(new DiscordConfigBuilder())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary>
|
||||
public DiscordClient(DiscordConfigBuilder builder)
|
||||
: this(builder.Build())
|
||||
{
|
||||
if (builder.LogHandler != null)
|
||||
Log.Message += builder.LogHandler;
|
||||
}
|
||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary>
|
||||
public DiscordClient(DiscordConfig config)
|
||||
{
|
||||
Config = config ?? new DiscordConfig();
|
||||
Config.Lock();
|
||||
Config = config;
|
||||
|
||||
State = (int)ConnectionState.Disconnected;
|
||||
Status = UserStatus.Online;
|
||||
@@ -145,9 +150,8 @@ namespace Discord
|
||||
};
|
||||
//GatewaySocket.Disconnected += (s, e) => OnDisconnected(e.WasUnexpected, e.Exception);
|
||||
GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e);
|
||||
|
||||
if (Config.UseMessageQueue)
|
||||
MessageQueue = new MessageQueue(ClientAPI, Log.CreateLogger("MessageQueue"));
|
||||
|
||||
MessageQueue = new MessageQueue(ClientAPI, Log.CreateLogger("MessageQueue"));
|
||||
|
||||
//Extensibility
|
||||
Services = new ServiceManager(this);
|
||||
@@ -194,10 +198,11 @@ namespace Discord
|
||||
await Login(email, password, token).ConfigureAwait(false);
|
||||
await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false);
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
tasks.Add(CancelToken.Wait());
|
||||
if (Config.UseMessageQueue)
|
||||
tasks.Add(MessageQueue.Run(CancelToken, Config.MessageQueueInterval));
|
||||
Task[] tasks = new[]
|
||||
{
|
||||
CancelToken.Wait(),
|
||||
MessageQueue.Run(CancelToken)
|
||||
};
|
||||
|
||||
await _taskManager.Start(tasks, cancelSource).ConfigureAwait(false);
|
||||
GatewaySocket.WaitForConnection(CancelToken);
|
||||
@@ -222,7 +227,7 @@ namespace Discord
|
||||
byte[] cacheKey = null;
|
||||
|
||||
//Get Token
|
||||
if (email != null && Config.CacheToken)
|
||||
if (email != null && Config.CacheDir != null)
|
||||
{
|
||||
tokenPath = GetTokenCachePath(email);
|
||||
if (token == null && password != null)
|
||||
@@ -240,7 +245,7 @@ namespace Discord
|
||||
var request = new LoginRequest() { Email = email, Password = password };
|
||||
var response = await ClientAPI.Send(request).ConfigureAwait(false);
|
||||
token = response.Token;
|
||||
if (Config.CacheToken && token != oldToken && tokenPath != null)
|
||||
if (Config.CacheDir != null && token != oldToken && tokenPath != null)
|
||||
SaveToken(tokenPath, cacheKey, token);
|
||||
ClientAPI.Token = token;
|
||||
|
||||
@@ -270,9 +275,8 @@ namespace Discord
|
||||
try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); }
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
|
||||
if (Config.UseMessageQueue)
|
||||
MessageQueue.Clear();
|
||||
|
||||
MessageQueue.Clear();
|
||||
|
||||
await GatewaySocket.Disconnect().ConfigureAwait(false);
|
||||
ClientAPI.Token = null;
|
||||
@@ -1053,7 +1057,7 @@ namespace Discord
|
||||
StringBuilder filenameBuilder = new StringBuilder();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
filenameBuilder.Append(data[i].ToString("x2"));
|
||||
return Path.Combine(Path.GetTempPath(), Config.AppName ?? "Discord.Net", filenameBuilder.ToString());
|
||||
return Path.Combine(Config.CacheDir, filenameBuilder.ToString());
|
||||
}
|
||||
}
|
||||
private string LoadToken(string path, byte[] key)
|
||||
|
||||
@@ -1,127 +1,133 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public enum LogSeverity : byte
|
||||
{
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Info = 3,
|
||||
Verbose = 4,
|
||||
Debug = 5
|
||||
}
|
||||
|
||||
public class DiscordConfig : Config<DiscordConfig>
|
||||
{
|
||||
public class DiscordConfigBuilder
|
||||
{
|
||||
public const int MaxMessageSize = 2000;
|
||||
|
||||
public const string LibName = "Discord.Net";
|
||||
public static string LibVersion => typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3);
|
||||
public const string LibUrl = "https://github.com/RogueException/Discord.Net";
|
||||
|
||||
public const string ClientAPIUrl = "https://discordapp.com/api/";
|
||||
public const string StatusAPIUrl = "https://srhpyqt94yxb.statuspage.io/api/v2/"; //"https://status.discordapp.com/api/v2/";
|
||||
//public const string CDNUrl = "https://cdn.discordapp.com/";
|
||||
public const string InviteUrl = "https://discord.gg/";
|
||||
|
||||
//Global
|
||||
|
||||
/// <summary> Gets or sets name of your application, used both for the token cache directory and user agent. </summary>
|
||||
public string AppName { get { return _appName; } set { SetValue(ref _appName, value); UpdateUserAgent(); } }
|
||||
private string _appName = null;
|
||||
public string AppName { get; set; } = null;
|
||||
/// <summary> Gets or sets url to your application, used in the user agent. </summary>
|
||||
public string AppUrl { get { return _appUrl; } set { SetValue(ref _appUrl, value); UpdateUserAgent(); } }
|
||||
private string _appUrl = null;
|
||||
public string AppUrl { get; set; } = null;
|
||||
/// <summary> Gets or sets the version of your application, used in the user agent. </summary>
|
||||
public string AppVersion { get { return _appVersion; } set { SetValue(ref _appVersion, value); UpdateUserAgent(); } }
|
||||
private string _appVersion = null;
|
||||
public string AppVersion { get; set; } = null;
|
||||
|
||||
/// <summary> Gets or sets the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary>
|
||||
public LogSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } }
|
||||
private LogSeverity _logLevel = LogSeverity.Info;
|
||||
public LogSeverity LogLevel { get; set; } = LogSeverity.Info;
|
||||
/// <summary> Enables or disables the default event logger. </summary>
|
||||
public bool LogEvents { get { return _logEvents; } set { SetValue(ref _logEvents, value); } }
|
||||
private bool _logEvents = true;
|
||||
|
||||
/// <summary> Gets the user agent used when connecting to Discord. </summary>
|
||||
public string UserAgent { get; private set; }
|
||||
|
||||
//Rest
|
||||
|
||||
/// <summary> Gets or sets the max time (in milliseconds) to wait for an API request to complete. </summary>
|
||||
public int RestTimeout { get { return _restTimeout; } set { SetValue(ref _restTimeout, value); } }
|
||||
private int _restTimeout = 10000;
|
||||
|
||||
/// <summary> Enables or disables the internal message queue. This will allow SendMessage/EditMessage to return immediately and handle messages internally. </summary>
|
||||
public bool UseMessageQueue { get { return _useMessageQueue; } set { SetValue(ref _useMessageQueue, value); } }
|
||||
private bool _useMessageQueue = true;
|
||||
/// <summary> Gets or sets the time (in milliseconds) to wait when the message queue is empty before checking again. </summary>
|
||||
public int MessageQueueInterval { get { return _messageQueueInterval; } set { SetValue(ref _messageQueueInterval, value); } }
|
||||
private int _messageQueueInterval = 100;
|
||||
public bool LogEvents { get; set; } = true;
|
||||
|
||||
//WebSocket
|
||||
|
||||
/// <summary> Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. </summary>
|
||||
public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } }
|
||||
private int _connectionTimeout = 30000;
|
||||
public int ConnectionTimeout { get; set; } = 30000;
|
||||
/// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary>
|
||||
public int ReconnectDelay { get { return _reconnectDelay; } set { SetValue(ref _reconnectDelay, value); } }
|
||||
private int _reconnectDelay = 1000;
|
||||
public int ReconnectDelay { get; set; } = 1000;
|
||||
/// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary>
|
||||
public int FailedReconnectDelay { get { return _failedReconnectDelay; } set { SetValue(ref _failedReconnectDelay, value); } }
|
||||
private int _failedReconnectDelay = 15000;
|
||||
|
||||
/// <summary> Gets or sets the time (in milliseconds) to wait when the websocket's message queue is empty before checking again. </summary>
|
||||
public int WebSocketInterval { get { return _webSocketInterval; } set { SetValue(ref _webSocketInterval, value); } }
|
||||
private int _webSocketInterval = 100;
|
||||
public int FailedReconnectDelay { get; set; } = 15000;
|
||||
|
||||
//Performance
|
||||
|
||||
/// <summary> Cache an encrypted login token to temp dir after success login. </summary>
|
||||
public bool CacheToken { get { return _cacheToken; } set { SetValue(ref _cacheToken, value); } }
|
||||
private bool _cacheToken = true;
|
||||
/// <summary> Gets or sets whether an encrypted login token should be saved to temp dir after successful login. </summary>
|
||||
public bool CacheToken { get; set; } = true;
|
||||
/// <summary> Gets or sets whether Discord should send information about offline users, for servers with more than 100 users. </summary>
|
||||
public bool UseLargeThreshold { get { return _useLargeThreshold; } set { SetValue(ref _useLargeThreshold, value); } }
|
||||
private bool _useLargeThreshold = false;
|
||||
public bool UseLargeThreshold { get; set; } = false;
|
||||
/// <summary> Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. </summary>
|
||||
public int MessageCacheSize { get { return _messageCacheSize; } set { SetValue(ref _messageCacheSize, value); } }
|
||||
private int _messageCacheSize = 100;
|
||||
public int MessageCacheSize { get; set; } = 100;
|
||||
/// <summary> Gets or sets whether the permissions cache should be used. This makes operations such as User.GetPermissions(Channel), User.ServerPermissions and Channel.Members </summary>
|
||||
public bool UsePermissionsCache { get { return _usePermissionsCache; } set { SetValue(ref _usePermissionsCache, value); } }
|
||||
private bool _usePermissionsCache = true;
|
||||
public bool UsePermissionsCache { get; set; } = true;
|
||||
/// <summary> Gets or sets whether the a copy of a model is generated on an update event to allow a user to check which properties changed. </summary>
|
||||
public bool EnablePreUpdateEvents { get { return _enablePreUpdateEvents; } set { SetValue(ref _enablePreUpdateEvents, value); } }
|
||||
private bool _enablePreUpdateEvents = true;
|
||||
public bool EnablePreUpdateEvents { get; set; } = true;
|
||||
|
||||
public DiscordConfig()
|
||||
{
|
||||
UpdateUserAgent();
|
||||
}
|
||||
//Events
|
||||
|
||||
/// <summary> Gets or sets a handler for all log messages. </summary>
|
||||
public EventHandler<LogMessageEventArgs> LogHandler { get; set; }
|
||||
|
||||
public DiscordConfig Build() => new DiscordConfig(this);
|
||||
}
|
||||
|
||||
public class DiscordConfig
|
||||
{
|
||||
public const int MaxMessageSize = 2000;
|
||||
internal const int RestTimeout = 10000;
|
||||
internal const int MessageQueueInterval = 100;
|
||||
internal const int WebSocketQueueInterval = 100;
|
||||
|
||||
public const string LibName = "Discord.Net";
|
||||
public static string LibVersion => typeof(DiscordConfigBuilder).GetTypeInfo().Assembly.GetName().Version.ToString(3);
|
||||
public const string LibUrl = "https://github.com/RogueException/Discord.Net";
|
||||
|
||||
public const string ClientAPIUrl = "https://discordapp.com/api/";
|
||||
public const string StatusAPIUrl = "https://srhpyqt94yxb.statuspage.io/api/v2/"; //"https://status.discordapp.com/api/v2/";
|
||||
public const string CDNUrl = "https://cdn.discordapp.com/";
|
||||
public const string InviteUrl = "https://discord.gg/";
|
||||
|
||||
public LogSeverity LogLevel { get; }
|
||||
public bool LogEvents { get; }
|
||||
|
||||
private void UpdateUserAgent()
|
||||
public int ConnectionTimeout { get; }
|
||||
public int ReconnectDelay { get; }
|
||||
public int FailedReconnectDelay { get; }
|
||||
|
||||
public bool UseLargeThreshold { get; }
|
||||
public int MessageCacheSize { get; }
|
||||
public bool UsePermissionsCache { get; }
|
||||
public bool EnablePreUpdateEvents { get; }
|
||||
|
||||
public string UserAgent { get; }
|
||||
public string CacheDir { get; }
|
||||
|
||||
internal DiscordConfig(DiscordConfigBuilder builder)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (!string.IsNullOrEmpty(_appName))
|
||||
LogLevel = builder.LogLevel;
|
||||
LogEvents = builder.LogEvents;
|
||||
|
||||
ConnectionTimeout = builder.ConnectionTimeout;
|
||||
ReconnectDelay = builder.ReconnectDelay;
|
||||
FailedReconnectDelay = builder.FailedReconnectDelay;
|
||||
|
||||
UseLargeThreshold = builder.UseLargeThreshold;
|
||||
MessageCacheSize = builder.MessageCacheSize;
|
||||
UsePermissionsCache = builder.UsePermissionsCache;
|
||||
EnablePreUpdateEvents = builder.EnablePreUpdateEvents;
|
||||
|
||||
UserAgent = GetUserAgent(builder);
|
||||
CacheDir = GetCacheDir(builder);
|
||||
}
|
||||
|
||||
private string GetUserAgent(DiscordConfigBuilder builder)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!string.IsNullOrEmpty(builder.AppName))
|
||||
{
|
||||
builder.Append(_appName);
|
||||
if (!string.IsNullOrEmpty(_appVersion))
|
||||
sb.Append(builder.AppName);
|
||||
if (!string.IsNullOrEmpty(builder.AppVersion))
|
||||
{
|
||||
builder.Append('/');
|
||||
builder.Append(_appVersion);
|
||||
sb.Append('/');
|
||||
sb.Append(builder.AppVersion);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_appUrl))
|
||||
if (!string.IsNullOrEmpty(builder.AppUrl))
|
||||
{
|
||||
builder.Append(" (");
|
||||
builder.Append(_appUrl);
|
||||
builder.Append(')');
|
||||
sb.Append(" (");
|
||||
sb.Append(builder.AppUrl);
|
||||
sb.Append(')');
|
||||
}
|
||||
builder.Append(' ');
|
||||
sb.Append(' ');
|
||||
}
|
||||
builder.Append($"DiscordBot ({LibUrl}, v{LibVersion})");
|
||||
UserAgent = builder.ToString();
|
||||
sb.Append($"DiscordBot ({LibUrl}, v{LibVersion})");
|
||||
return sb.ToString();
|
||||
}
|
||||
private string GetCacheDir(DiscordConfigBuilder builder)
|
||||
{
|
||||
if (builder.CacheToken)
|
||||
return Path.Combine(Path.GetTempPath(), builder.AppName ?? "Discord.Net");
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/Discord.Net/Enums/LogSeverity.cs
Normal file
11
src/Discord.Net/Enums/LogSeverity.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Discord
|
||||
{
|
||||
public enum LogSeverity : byte
|
||||
{
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Info = 3,
|
||||
Verbose = 4,
|
||||
Debug = 5
|
||||
}
|
||||
}
|
||||
@@ -99,10 +99,10 @@ namespace Discord.Net
|
||||
_pendingActions.Enqueue(new DeleteAction(msg));
|
||||
}
|
||||
|
||||
internal Task Run(CancellationToken cancelToken, int interval)
|
||||
internal Task Run(CancellationToken cancelToken)
|
||||
{
|
||||
_nextWarning = WarningStart;
|
||||
return Task.Run(async () =>
|
||||
return Task.Run((Func<Task>)(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -121,11 +121,11 @@ namespace Discord.Net
|
||||
while (_pendingActions.TryDequeue(out queuedAction))
|
||||
await queuedAction.Do(this).ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(interval).ConfigureAwait(false);
|
||||
await Task.Delay((int)Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
internal async Task Send(Message msg)
|
||||
|
||||
@@ -343,26 +343,12 @@ namespace Discord
|
||||
if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text));
|
||||
return SendMessageInternal(text, true);
|
||||
}
|
||||
private async Task<Message> SendMessageInternal(string text, bool isTTS)
|
||||
private Task<Message> SendMessageInternal(string text, bool isTTS)
|
||||
{
|
||||
if (text.Length > DiscordConfig.MaxMessageSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less.");
|
||||
|
||||
if (Client.Config.UseMessageQueue)
|
||||
return Client.MessageQueue.QueueSend(this, text, isTTS);
|
||||
else
|
||||
{
|
||||
var request = new SendMessageRequest(Id)
|
||||
{
|
||||
Content = text,
|
||||
Nonce = null,
|
||||
IsTTS = isTTS
|
||||
};
|
||||
var model = await Client.ClientAPI.Send(request).ConfigureAwait(false);
|
||||
var msg = AddMessage(model.Id, IsPrivate ? Client.PrivateUser : Server.CurrentUser, model.Timestamp.Value);
|
||||
msg.Update(model);
|
||||
return msg;
|
||||
}
|
||||
|
||||
return Task.FromResult(Client.MessageQueue.QueueSend(this, text, isTTS));
|
||||
}
|
||||
|
||||
public async Task<Message> SendFile(string filePath)
|
||||
|
||||
@@ -310,7 +310,7 @@ namespace Discord
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Edit(string text)
|
||||
public Task Edit(string text)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
|
||||
@@ -318,28 +318,14 @@ namespace Discord
|
||||
|
||||
if (text.Length > DiscordConfig.MaxMessageSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less.");
|
||||
|
||||
if (Client.Config.UseMessageQueue)
|
||||
Client.MessageQueue.QueueEdit(this, text);
|
||||
else
|
||||
{
|
||||
var request = new UpdateMessageRequest(Channel.Id, Id)
|
||||
{
|
||||
Content = text
|
||||
};
|
||||
await Client.ClientAPI.Send(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Client.MessageQueue.QueueEdit(this, text);
|
||||
return TaskHelper.CompletedTask;
|
||||
}
|
||||
public async Task Delete()
|
||||
public Task Delete()
|
||||
{
|
||||
if (Client.Config.UseMessageQueue)
|
||||
Client.MessageQueue.QueueDelete(this);
|
||||
else
|
||||
{
|
||||
var request = new DeleteMessageRequest(Channel.Id, Id);
|
||||
try { await Client.ClientAPI.Send(request).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
Client.MessageQueue.QueueDelete(this);
|
||||
return TaskHelper.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary> Returns true if the logged-in user was mentioned. </summary>
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Discord.Net.Rest
|
||||
_client = new RestSharpClient(baseUrl)
|
||||
{
|
||||
PreAuthenticate = false,
|
||||
ReadWriteTimeout = _config.RestTimeout,
|
||||
ReadWriteTimeout = DiscordConfig.RestTimeout,
|
||||
UserAgent = config.UserAgent
|
||||
};
|
||||
_client.Proxy = null;
|
||||
|
||||
@@ -65,9 +65,7 @@ namespace Discord.Net.WebSockets
|
||||
{
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
var sendInterval = _config.WebSocketInterval;
|
||||
//var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]);
|
||||
var buffer = new byte[ReceiveChunkSize];
|
||||
var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]);
|
||||
var stream = new MemoryStream();
|
||||
|
||||
try
|
||||
@@ -81,7 +79,7 @@ namespace Discord.Net.WebSockets
|
||||
|
||||
try
|
||||
{
|
||||
result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancelToken).ConfigureAwait(false);
|
||||
result = await _webSocket.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT)
|
||||
{
|
||||
@@ -91,7 +89,7 @@ namespace Discord.Net.WebSockets
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
throw new WebSocketException((int)result.CloseStatus.Value, result.CloseStatusDescription);
|
||||
else
|
||||
stream.Write(buffer, 0, result.Count);
|
||||
stream.Write(buffer.Array, buffer.Offset, buffer.Count);
|
||||
|
||||
}
|
||||
while (result == null || !result.EndOfMessage);
|
||||
@@ -114,7 +112,6 @@ namespace Discord.Net.WebSockets
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
byte[] bytes = new byte[SendChunkSize];
|
||||
var sendInterval = _config.WebSocketInterval;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -147,7 +144,7 @@ namespace Discord.Net.WebSockets
|
||||
}
|
||||
}
|
||||
}
|
||||
await Task.Delay(sendInterval, cancelToken).ConfigureAwait(false);
|
||||
await Task.Delay(DiscordConfig.WebSocketQueueInterval, cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
|
||||
@@ -118,7 +118,6 @@ namespace Discord.Net.WebSockets
|
||||
|
||||
private Task SendAsync(CancellationToken cancelToken)
|
||||
{
|
||||
var sendInterval = _config.WebSocketInterval;
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
@@ -128,7 +127,7 @@ namespace Discord.Net.WebSockets
|
||||
string json;
|
||||
while (_sendQueue.TryDequeue(out json))
|
||||
_webSocket.Send(json);
|
||||
await Task.Delay(sendInterval, cancelToken).ConfigureAwait(false);
|
||||
await Task.Delay(DiscordConfig.WebSocketQueueInterval, cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
|
||||
Reference in New Issue
Block a user