Browse Source

Split config into builder and immutable classes, added some audioservice extension methods.

tags/docs-0.9
RogueException 9 years ago
parent
commit
cb4d00ac4e
23 changed files with 277 additions and 283 deletions
  1. +3
    -0
      src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj
  2. +3
    -3
      src/Discord.Net.Audio/AudioClient.cs
  3. +10
    -6
      src/Discord.Net.Audio/AudioExtensions.cs
  4. +9
    -0
      src/Discord.Net.Audio/AudioMode.cs
  5. +10
    -3
      src/Discord.Net.Audio/AudioService.cs
  6. +40
    -51
      src/Discord.Net.Audio/AudioServiceConfig.cs
  7. +4
    -6
      src/Discord.Net.Commands/CommandExtensions.cs
  8. +13
    -2
      src/Discord.Net.Commands/CommandService.cs
  9. +29
    -20
      src/Discord.Net.Commands/CommandServiceConfig.cs
  10. +1
    -1
      src/Discord.Net.Commands/HelpMode.cs
  11. +1
    -1
      src/Discord.Net.Modules/ModuleManager.cs
  12. +3
    -0
      src/Discord.Net.Net45/Discord.Net.csproj
  13. +2
    -6
      src/Discord.Net.Shared/TaskHelper.cs
  14. +0
    -24
      src/Discord.Net/Config.cs
  15. +24
    -20
      src/Discord.Net/DiscordClient.cs
  16. +94
    -88
      src/Discord.Net/DiscordConfig.cs
  17. +11
    -0
      src/Discord.Net/Enums/LogSeverity.cs
  18. +4
    -4
      src/Discord.Net/MessageQueue.cs
  19. +3
    -17
      src/Discord.Net/Models/Channel.cs
  20. +7
    -21
      src/Discord.Net/Models/Message.cs
  21. +1
    -1
      src/Discord.Net/Net/Rest/SharpRestEngine.cs
  22. +4
    -7
      src/Discord.Net/Net/WebSockets/BuiltInEngine.cs
  23. +1
    -2
      src/Discord.Net/Net/WebSockets/WS4NetEngine.cs

+ 3
- 0
src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj View File

@@ -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>


+ 3
- 3
src/Discord.Net.Audio/AudioClient.cs View File

@@ -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;


+ 10
- 6
src/Discord.Net.Audio/AudioExtensions.cs View File

@@ -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
- 0
src/Discord.Net.Audio/AudioMode.cs View File

@@ -0,0 +1,9 @@
namespace Discord.Audio
{
public enum AudioMode : byte
{
Outgoing = 1,
Incoming = 2,
Both = Outgoing | Incoming
}
}

+ 10
- 3
src/Discord.Net.Audio/AudioService.cs View File

@@ -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>();


+ 40
- 51
src/Discord.Net.Audio/AudioServiceConfig.cs View File

@@ -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
{
/// <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; 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; 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; set; } = 2;

public AudioServiceConfig Build() => new AudioServiceConfig(this);
}

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;
public AudioMode Mode { get; }

//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;
public bool EnableEncryption { get; }
public bool EnableMultiserver { get; }

/// <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;
public int BufferLength { get; }
public int? Bitrate { get; }
public int Channels { get; }

/// <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;
internal AudioServiceConfig(AudioServiceConfigBuilder builder)
{
Mode = builder.Mode;

/// <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;
EnableEncryption = builder.EnableEncryption;
EnableMultiserver = builder.EnableMultiserver;

/// <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;
/// <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;

//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 Clone()
{
var config = MemberwiseClone() as AudioServiceConfig;
config._isLocked = false;
return config;
}
}
BufferLength = builder.BufferLength;
Bitrate = builder.Bitrate;
Channels = builder.Channels;
}
}
}

+ 4
- 6
src/Discord.Net.Commands/CommandExtensions.cs View File

@@ -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);
}
}

+ 13
- 2
src/Discord.Net.Commands/CommandService.cs View File

@@ -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)


+ 29
- 20
src/Discord.Net.Commands/CommandServiceConfig.cs View File

@@ -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; set; } = HelpMode.Disabled;


/// <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 { 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;
}
internal CommandServiceConfig(CommandServiceConfigBuilder builder)
{
PrefixChar = builder.PrefixChar;
AllowMentionPrefix = builder.AllowMentionPrefix;
CustomPrefixHandler = builder.CustomPrefixHandler;
HelpMode = builder.HelpMode;
}
}
}

+ 1
- 1
src/Discord.Net.Commands/HelpMode.cs View File

@@ -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>


+ 1
- 1
src/Discord.Net.Modules/ModuleManager.cs View File

@@ -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);


+ 3
- 0
src/Discord.Net.Net45/Discord.Net.csproj View File

@@ -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>


+ 2
- 6
src/Discord.Net.Shared/TaskHelper.cs View File

@@ -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)
{


+ 0
- 24
src/Discord.Net/Config.cs View File

@@ -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;
}
}
}

+ 24
- 20
src/Discord.Net/DiscordClient.cs View File

@@ -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)


+ 94
- 88
src/Discord.Net/DiscordConfig.cs View File

@@ -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;

//Events

public DiscordConfig()
/// <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; }
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)
{
UpdateUserAgent();
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 void UpdateUserAgent()
private string GetUserAgent(DiscordConfigBuilder builder)
{
StringBuilder builder = new StringBuilder();
if (!string.IsNullOrEmpty(_appName))
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
- 0
src/Discord.Net/Enums/LogSeverity.cs View File

@@ -0,0 +1,11 @@
namespace Discord
{
public enum LogSeverity : byte
{
Error = 1,
Warning = 2,
Info = 3,
Verbose = 4,
Debug = 5
}
}

+ 4
- 4
src/Discord.Net/MessageQueue.cs View File

@@ -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)


+ 3
- 17
src/Discord.Net/Models/Channel.cs View File

@@ -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)


+ 7
- 21
src/Discord.Net/Models/Message.cs View File

@@ -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>


+ 1
- 1
src/Discord.Net/Net/Rest/SharpRestEngine.cs View File

@@ -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;


+ 4
- 7
src/Discord.Net/Net/WebSockets/BuiltInEngine.cs View File

@@ -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) { }


+ 1
- 2
src/Discord.Net/Net/WebSockets/WS4NetEngine.cs View File

@@ -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) { }


Loading…
Cancel
Save