@@ -1,17 +0,0 @@ | |||
using Discord.Net.WebSockets; | |||
namespace Discord.Audio | |||
{ | |||
public class AudioConfig | |||
{ | |||
/// <summary> Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. </summary> | |||
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; set; } = 1000; | |||
/// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary> | |||
public int FailedReconnectDelay { get; set; } = 15000; | |||
/// <summary> Gets or sets the provider used to generate new websocket connections. </summary> | |||
public WebSocketProvider WebSocketProvider { get; set; } = () => new DefaultWebSocketClient(); | |||
} | |||
} |
@@ -1,18 +0,0 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project ToolsVersion="14.0.25123" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25123</VisualStudioVersion> | |||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||
<PropertyGroup Label="Globals"> | |||
<ProjectGuid>ddfcc44f-934e-478a-978c-69cdda2a1c5b</ProjectGuid> | |||
<RootNamespace>Discord.Audio</RootNamespace> | |||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||
</PropertyGroup> | |||
<PropertyGroup> | |||
<SchemaVersion>2.0</SchemaVersion> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||
</Project> |
@@ -1,6 +0,0 @@ | |||
namespace Discord.Audio | |||
{ | |||
internal class Logger | |||
{ | |||
} | |||
} |
@@ -1,74 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public class AsyncEvent<T> | |||
{ | |||
private readonly object _subLock = new object(); | |||
internal ImmutableArray<T> _subscriptions; | |||
public IReadOnlyList<T> Subscriptions => _subscriptions; | |||
public AsyncEvent() | |||
{ | |||
_subscriptions = ImmutableArray.Create<T>(); | |||
} | |||
public void Add(T subscriber) | |||
{ | |||
lock (_subLock) | |||
_subscriptions = _subscriptions.Add(subscriber); | |||
} | |||
public void Remove(T subscriber) | |||
{ | |||
lock (_subLock) | |||
_subscriptions = _subscriptions.Remove(subscriber); | |||
} | |||
} | |||
public static class EventExtensions | |||
{ | |||
public static async Task InvokeAsync(this AsyncEvent<Func<Task>> eventHandler) | |||
{ | |||
var subscribers = eventHandler.Subscriptions; | |||
if (subscribers.Count > 0) | |||
{ | |||
for (int i = 0; i < subscribers.Count; i++) | |||
await subscribers[i].Invoke().ConfigureAwait(false); | |||
} | |||
} | |||
public static async Task InvokeAsync<T>(this AsyncEvent<Func<T, Task>> eventHandler, T arg) | |||
{ | |||
var subscribers = eventHandler.Subscriptions; | |||
for (int i = 0; i < subscribers.Count; i++) | |||
await subscribers[i].Invoke(arg).ConfigureAwait(false); | |||
} | |||
public static async Task InvokeAsync<T1, T2>(this AsyncEvent<Func<T1, T2, Task>> eventHandler, T1 arg1, T2 arg2) | |||
{ | |||
var subscribers = eventHandler.Subscriptions; | |||
for (int i = 0; i < subscribers.Count; i++) | |||
await subscribers[i].Invoke(arg1, arg2).ConfigureAwait(false); | |||
} | |||
public static async Task InvokeAsync<T1, T2, T3>(this AsyncEvent<Func<T1, T2, T3, Task>> eventHandler, T1 arg1, T2 arg2, T3 arg3) | |||
{ | |||
var subscribers = eventHandler.Subscriptions; | |||
for (int i = 0; i < subscribers.Count; i++) | |||
await subscribers[i].Invoke(arg1, arg2, arg3).ConfigureAwait(false); | |||
} | |||
public static async Task InvokeAsync<T1, T2, T3, T4>(this AsyncEvent<Func<T1, T2, T3, T4, Task>> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4) | |||
{ | |||
var subscribers = eventHandler.Subscriptions; | |||
for (int i = 0; i < subscribers.Count; i++) | |||
await subscribers[i].Invoke(arg1, arg2, arg3, arg4).ConfigureAwait(false); | |||
} | |||
public static async Task InvokeAsync<T1, T2, T3, T4, T5>(this AsyncEvent<Func<T1, T2, T3, T4, T5, Task>> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) | |||
{ | |||
var subscribers = eventHandler.Subscriptions; | |||
for (int i = 0; i < subscribers.Count; i++) | |||
await subscribers[i].Invoke(arg1, arg2, arg3, arg4, arg5).ConfigureAwait(false); | |||
} | |||
} | |||
} |
@@ -1,35 +0,0 @@ | |||
{ | |||
"version": "1.0.0-dev", | |||
"description": "A Discord.Net extension adding audio support.", | |||
"authors": [ "RogueException" ], | |||
"packOptions": { | |||
"tags": [ "discord", "discordapp" ], | |||
"licenseUrl": "http://opensource.org/licenses/MIT", | |||
"projectUrl": "https://github.com/RogueException/Discord.Net", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/RogueException/Discord.Net" | |||
} | |||
}, | |||
"buildOptions": { | |||
"allowUnsafe": true, | |||
"warningsAsErrors": false | |||
}, | |||
"dependencies": { | |||
"Discord.Net": "1.0.0-dev", | |||
"System.Runtime.InteropServices": "4.1.0" | |||
}, | |||
"frameworks": { | |||
"netstandard1.3": { | |||
"imports": [ | |||
"dotnet5.4", | |||
"dnxcore50", | |||
"portable-net45+win8" | |||
] | |||
} | |||
} | |||
} |
@@ -398,6 +398,17 @@ namespace Discord.API | |||
{ | |||
await SendGatewayAsync(GatewayOpCode.RequestGuildMembers, new RequestMembersParams { GuildIds = guildIds, Query = "", Limit = 0 }, options: options).ConfigureAwait(false); | |||
} | |||
public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) | |||
{ | |||
var payload = new VoiceStateUpdateParams | |||
{ | |||
GuildId = guildId, | |||
ChannelId = channelId, | |||
SelfDeaf = selfDeaf, | |||
SelfMute = selfMute | |||
}; | |||
await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); | |||
} | |||
//Channels | |||
public async Task<Channel> GetChannelAsync(ulong channelId, RequestOptions options = null) | |||
@@ -14,7 +14,7 @@ using System.Threading.Tasks; | |||
namespace Discord.Audio | |||
{ | |||
public class AudioAPIClient | |||
public class DiscordVoiceAPIClient | |||
{ | |||
public const int MaxBitrate = 128; | |||
private const string Mode = "xsalsa20_poly1305"; | |||
@@ -40,7 +40,7 @@ namespace Discord.Audio | |||
public string SessionId { get; } | |||
public ConnectionState ConnectionState { get; private set; } | |||
internal AudioAPIClient(ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, JsonSerializer serializer = null) | |||
internal DiscordVoiceAPIClient(ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, JsonSerializer serializer = null) | |||
{ | |||
GuildId = guildId; | |||
_userId = userId; |
@@ -0,0 +1,12 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Gateway | |||
{ | |||
public class GuildEmojiUpdateEvent | |||
{ | |||
[JsonProperty("guild_id")] | |||
public ulong GuildId; | |||
[JsonProperty("emojis")] | |||
public Emoji[] Emojis; | |||
} | |||
} |
@@ -1,15 +1,19 @@ | |||
using Newtonsoft.Json; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord.API.Gateway | |||
{ | |||
public class RequestMembersParams | |||
{ | |||
[JsonProperty("guild_id")] | |||
public IEnumerable<ulong> GuildIds { get; set; } | |||
[JsonProperty("query")] | |||
public string Query { get; set; } | |||
[JsonProperty("limit")] | |||
public int Limit { get; set; } | |||
[JsonProperty("guild_id")] | |||
public IEnumerable<ulong> GuildIds { get; set; } | |||
[JsonIgnore] | |||
public IEnumerable<IGuild> Guilds { set { GuildIds = value.Select(x => x.Id); } } | |||
} | |||
} |
@@ -1,16 +0,0 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Gateway | |||
{ | |||
public class UpdateVoiceParams | |||
{ | |||
[JsonProperty("guild_id")] | |||
public ulong? GuildId { get; set; } | |||
[JsonProperty("channel_id")] | |||
public ulong? ChannelId { get; set; } | |||
[JsonProperty("self_mute")] | |||
public bool IsSelfMuted { get; set; } | |||
[JsonProperty("self_deaf")] | |||
public bool IsSelfDeafened { get; set; } | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Gateway | |||
{ | |||
public class VoiceStateUpdateParams | |||
{ | |||
[JsonProperty("self_mute")] | |||
public bool SelfMute { get; set; } | |||
[JsonProperty("self_deaf")] | |||
public bool SelfDeaf { get; set; } | |||
[JsonProperty("guild_id")] | |||
public ulong GuildId { get; set; } | |||
[JsonIgnore] | |||
public IGuild Guild { set { GuildId = value.Id; } } | |||
[JsonProperty("channel_id")] | |||
public ulong? ChannelId { get; set; } | |||
[JsonIgnore] | |||
public IChannel Channel { set { ChannelId = value?.Id; } } | |||
} | |||
} |
@@ -1,15 +1,15 @@ | |||
using Discord.API.Voice; | |||
using Discord.Logging; | |||
using Discord.Net.Converters; | |||
using Discord.Net.WebSockets; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace Discord.Audio | |||
{ | |||
public class AudioClient | |||
internal class AudioClient : IAudioClient | |||
{ | |||
public event Func<Task> Connected | |||
{ | |||
@@ -30,12 +30,11 @@ namespace Discord.Audio | |||
} | |||
private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | |||
private readonly ILogger _webSocketLogger; | |||
private readonly ILogger _webSocketLogger, _udpLogger; | |||
#if BENCHMARK | |||
private readonly ILogger _benchmarkLogger; | |||
#endif | |||
private readonly JsonSerializer _serializer; | |||
private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; | |||
internal readonly SemaphoreSlim _connectionLock; | |||
private TaskCompletionSource<bool> _connectTask; | |||
@@ -45,20 +44,18 @@ namespace Discord.Audio | |||
private bool _isReconnecting; | |||
private string _url; | |||
public AudioAPIClient ApiClient { get; private set; } | |||
/// <summary> Gets the current connection state of this client. </summary> | |||
private DiscordSocketClient Discord { get; } | |||
public DiscordVoiceAPIClient ApiClient { get; private set; } | |||
public ConnectionState ConnectionState { get; private set; } | |||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||
public int Latency { get; private set; } | |||
/// <summary> Creates a new REST/WebSocket discord client. </summary> | |||
internal AudioClient(ulong guildId, ulong userId, string sessionId, string token, AudioConfig config, ILogManager logManager) | |||
internal AudioClient(DiscordSocketClient discord, ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, ILogManager logManager) | |||
{ | |||
_connectionTimeout = config.ConnectionTimeout; | |||
_reconnectDelay = config.ReconnectDelay; | |||
_failedReconnectDelay = config.FailedReconnectDelay; | |||
Discord = discord; | |||
_webSocketLogger = logManager.CreateLogger("AudioWS"); | |||
_webSocketLogger = logManager.CreateLogger("Audio"); | |||
_udpLogger = logManager.CreateLogger("AudioUDP"); | |||
#if BENCHMARK | |||
_benchmarkLogger = logManager.CreateLogger("Benchmark"); | |||
#endif | |||
@@ -72,8 +69,7 @@ namespace Discord.Audio | |||
e.ErrorContext.Handled = true; | |||
}; | |||
var webSocketProvider = config.WebSocketProvider; //TODO: Clean this check | |||
ApiClient = new AudioAPIClient(guildId, userId, sessionId, token, config.WebSocketProvider); | |||
ApiClient = new DiscordVoiceAPIClient(guildId, userId, sessionId, token, webSocketProvider); | |||
ApiClient.SentGatewayMessage += async opCode => await _webSocketLogger.DebugAsync($"Sent {(VoiceOpCode)opCode}").ConfigureAwait(false); | |||
ApiClient.ReceivedEvent += ProcessMessageAsync; |
@@ -1,7 +1,11 @@ | |||
namespace Discord.Audio | |||
using System; | |||
namespace Discord.Audio | |||
{ | |||
[Flags] | |||
public enum AudioMode : byte | |||
{ | |||
Disabled = 0, | |||
Outgoing = 1, | |||
Incoming = 2, | |||
Both = Outgoing | Incoming |
@@ -0,0 +1,20 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Discord.Audio | |||
{ | |||
public interface IAudioClient | |||
{ | |||
event Func<Task> Connected; | |||
event Func<Task> Disconnected; | |||
event Func<int, int, Task> LatencyUpdated; | |||
DiscordVoiceAPIClient ApiClient { get; } | |||
/// <summary> Gets the current connection state of this client. </summary> | |||
ConnectionState ConnectionState { get; } | |||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||
int Latency { get; } | |||
Task DisconnectAsync(); | |||
} | |||
} |
@@ -36,7 +36,7 @@ namespace Discord.Audio.Opus | |||
{ | |||
if (channels != 1 && channels != 2) | |||
throw new ArgumentOutOfRangeException(nameof(channels)); | |||
if (bitrate != null && (bitrate < 1 || bitrate > AudioAPIClient.MaxBitrate)) | |||
if (bitrate != null && (bitrate < 1 || bitrate > DiscordVoiceAPIClient.MaxBitrate)) | |||
throw new ArgumentOutOfRangeException(nameof(bitrate)); | |||
OpusError error; |
@@ -1,8 +1,8 @@ | |||
using System.Runtime.InteropServices; | |||
namespace Discord.Net.Audio | |||
namespace Discord.Net.Audio.Sodium | |||
{ | |||
public unsafe static class LibSodium | |||
public unsafe static class SecretBox | |||
{ | |||
[DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] | |||
private static extern int SecretBoxEasy(byte* output, byte[] input, long inputLength, byte[] nonce, byte[] secret); |
@@ -3,6 +3,7 @@ using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
//TODO: Add event docstrings | |||
public partial class DiscordSocketClient | |||
{ | |||
//General | |||
@@ -1,7 +1,9 @@ | |||
using Discord.API.Gateway; | |||
using Discord.Audio; | |||
using Discord.Extensions; | |||
using Discord.Logging; | |||
using Discord.Net.Converters; | |||
using Discord.Net.WebSockets; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
@@ -14,9 +16,6 @@ using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
//TODO: Add event docstrings | |||
//TODO: Add reconnect logic (+ensure the heartbeat task to shut down) | |||
//TODO: Add resume logic | |||
public partial class DiscordSocketClient : DiscordClient, IDiscordClient | |||
{ | |||
private readonly ConcurrentQueue<ulong> _largeGuilds; | |||
@@ -25,9 +24,6 @@ namespace Discord | |||
private readonly ILogger _benchmarkLogger; | |||
#endif | |||
private readonly JsonSerializer _serializer; | |||
private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; | |||
private readonly int _largeThreshold; | |||
private readonly int _totalShards; | |||
private string _sessionId; | |||
private int _lastSeq; | |||
@@ -46,9 +42,17 @@ namespace Discord | |||
public ConnectionState ConnectionState { get; private set; } | |||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||
public int Latency { get; private set; } | |||
//From DiscordConfig | |||
internal int TotalShards { get; private set; } | |||
internal int ConnectionTimeout { get; private set; } | |||
internal int ReconnectDelay { get; private set; } | |||
internal int FailedReconnectDelay { get; private set; } | |||
internal int MessageCacheSize { get; private set; } | |||
internal int LargeThreshold { get; private set; } | |||
internal AudioMode AudioMode { get; private set; } | |||
internal DataStore DataStore { get; private set; } | |||
internal WebSocketProvider WebSocketProvider { get; private set; } | |||
internal CachedSelfUser CurrentUser => _currentUser as CachedSelfUser; | |||
internal IReadOnlyCollection<CachedGuild> Guilds => DataStore.Guilds; | |||
@@ -62,15 +66,15 @@ namespace Discord | |||
: base(config) | |||
{ | |||
ShardId = config.ShardId; | |||
_totalShards = config.TotalShards; | |||
_connectionTimeout = config.ConnectionTimeout; | |||
_reconnectDelay = config.ReconnectDelay; | |||
_failedReconnectDelay = config.FailedReconnectDelay; | |||
TotalShards = config.TotalShards; | |||
ConnectionTimeout = config.ConnectionTimeout; | |||
ReconnectDelay = config.ReconnectDelay; | |||
FailedReconnectDelay = config.FailedReconnectDelay; | |||
MessageCacheSize = config.MessageCacheSize; | |||
_largeThreshold = config.LargeThreshold; | |||
LargeThreshold = config.LargeThreshold; | |||
AudioMode = config.AudioMode; | |||
WebSocketProvider = config.WebSocketProvider; | |||
_gatewayLogger = _log.CreateLogger("Gateway"); | |||
#if BENCHMARK | |||
_benchmarkLogger = _log.CreateLogger("Benchmark"); | |||
@@ -471,7 +475,7 @@ namespace Discord | |||
case GatewayOpCode.Dispatch: | |||
switch (type) | |||
{ | |||
//Global | |||
//Connection | |||
case "READY": | |||
{ | |||
await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | |||
@@ -507,6 +511,11 @@ namespace Discord | |||
await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | |||
} | |||
break; | |||
case "RESUMED": | |||
await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); | |||
await _gatewayLogger.InfoAsync("Resume").ConfigureAwait(false); | |||
return; | |||
//Guilds | |||
case "GUILD_CREATE": | |||
@@ -569,6 +578,28 @@ namespace Discord | |||
} | |||
} | |||
break; | |||
case "GUILD_EMOJI_UPDATE": //TODO: Add | |||
{ | |||
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJI_UPDATE)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<API.Gateway.GuildEmojiUpdateEvent>(_serializer); | |||
var guild = DataStore.GetGuild(data.GuildId); | |||
if (guild != null) | |||
{ | |||
var before = guild.Clone(); | |||
guild.Update(data, UpdateSource.WebSocket); | |||
await _guildUpdatedEvent.InvokeAsync(before, guild).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
await _gatewayLogger.WarningAsync("GUILD_EMOJI_UPDATE referenced an unknown guild.").ConfigureAwait(false); | |||
return; | |||
} | |||
} | |||
return; | |||
case "GUILD_INTEGRATIONS_UPDATE": | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); | |||
return; | |||
case "GUILD_DELETE": | |||
{ | |||
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||
@@ -1099,26 +1130,17 @@ namespace Discord | |||
} | |||
} | |||
break; | |||
case "VOICE_SERVER_UPDATE": | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); | |||
return; | |||
//Ignored | |||
//Ignored (User only) | |||
case "USER_SETTINGS_UPDATE": | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | |||
return; | |||
case "MESSAGE_ACK": //TODO: Add (User only) | |||
case "MESSAGE_ACK": | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); | |||
return; | |||
case "GUILD_EMOJIS_UPDATE": //TODO: Add | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | |||
return; | |||
case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); | |||
return; | |||
case "VOICE_SERVER_UPDATE": //TODO: Add | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); | |||
return; | |||
case "RESUMED": //TODO: Add | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (RESUMED)").ConfigureAwait(false); | |||
return; | |||
//Others | |||
default: | |||
@@ -1,4 +1,5 @@ | |||
using Discord.Net.WebSockets; | |||
using Discord.Audio; | |||
using Discord.Net.WebSockets; | |||
namespace Discord | |||
{ | |||
@@ -28,7 +29,10 @@ namespace Discord | |||
/// Decreasing this may reduce CPU usage while increasing login time and network usage. | |||
/// </summary> | |||
public int LargeThreshold { get; set; } = 250; | |||
/// <summary> Gets or sets the type of audio this DiscordClient supports. </summary> | |||
public AudioMode AudioMode { get; set; } = AudioMode.Disabled; | |||
/// <summary> Gets or sets the provider used to generate new websocket connections. </summary> | |||
public WebSocketProvider WebSocketProvider { get; set; } = () => new DefaultWebSocketClient(); | |||
} | |||
@@ -1,4 +1,5 @@ | |||
using Discord.API.Rest; | |||
using Discord.Audio; | |||
using System; | |||
using System.Threading.Tasks; | |||
@@ -13,5 +14,7 @@ namespace Discord | |||
/// <summary> Modifies this voice channel. </summary> | |||
Task ModifyAsync(Action<ModifyVoiceChannelParams> func); | |||
/// <summary> Connects to this voice channel. </summary> | |||
Task<IAudioClient> ConnectAsync(); | |||
} | |||
} |
@@ -4,6 +4,7 @@ using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.Channel; | |||
using Discord.Audio; | |||
namespace Discord | |||
{ | |||
@@ -49,6 +50,8 @@ namespace Discord | |||
throw new NotSupportedException(); | |||
} | |||
public virtual Task<IAudioClient> ConnectAsync() { throw new NotSupportedException(); } | |||
private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using Discord.API.Rest; | |||
using Discord.Audio; | |||
using Discord.Extensions; | |||
using System; | |||
using System.Collections.Concurrent; | |||
@@ -311,6 +312,7 @@ namespace Discord | |||
IReadOnlyCollection<Emoji> IGuild.Emojis => Emojis; | |||
IReadOnlyCollection<string> IGuild.Features => Features; | |||
Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } | |||
IAudioClient IGuild.AudioClient => null; | |||
IRole IGuild.GetRole(ulong id) => GetRole(id); | |||
} | |||
@@ -2,6 +2,7 @@ | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using Discord.API.Rest; | |||
using Discord.Audio; | |||
namespace Discord | |||
{ | |||
@@ -37,6 +38,8 @@ namespace Discord | |||
/// <summary> Gets the id of the region hosting this guild's voice channels. </summary> | |||
string VoiceRegionId { get; } | |||
/// <summary> Gets the IAudioClient currently associated with this guild. </summary> | |||
IAudioClient AudioClient { get; } | |||
/// <summary> Gets the built-in role containing all users in this guild. </summary> | |||
IRole EveryoneRole { get; } | |||
/// <summary> Gets a collection of all custom emojis for this guild. </summary> | |||
@@ -1,4 +1,5 @@ | |||
using Discord.Extensions; | |||
using Discord.Audio; | |||
using Discord.Extensions; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
@@ -6,6 +7,7 @@ using System.Collections.Immutable; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using ChannelModel = Discord.API.Channel; | |||
using EmojiUpdateModel = Discord.API.Gateway.GuildEmojiUpdateEvent; | |||
using ExtendedModel = Discord.API.Gateway.ExtendedGuild; | |||
using MemberModel = Discord.API.GuildMember; | |||
using Model = Discord.API.Guild; | |||
@@ -25,6 +27,7 @@ namespace Discord | |||
public bool Available { get; private set; } | |||
public int MemberCount { get; private set; } | |||
public int DownloadedMemberCount { get; private set; } | |||
public IAudioClient AudioClient { get; private set; } | |||
public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; | |||
public Task DownloaderPromise => _downloaderPromise.Task; | |||
@@ -102,6 +105,16 @@ namespace Discord | |||
_voiceStates = voiceStates; | |||
} | |||
public void Update(EmojiUpdateModel model, UpdateSource source) | |||
{ | |||
if (source == UpdateSource.Rest && IsAttached) return; | |||
var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length); | |||
for (int i = 0; i < model.Emojis.Length; i++) | |||
emojis.Add(new Emoji(model.Emojis[i])); | |||
Emojis = emojis.ToImmutableArray(); | |||
} | |||
public override Task<IGuildChannel> GetChannelAsync(ulong id) => Task.FromResult<IGuildChannel>(GetChannel(id)); | |||
public override Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels); | |||
public void AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet<ulong> channels = null) | |||
@@ -1,4 +1,6 @@ | |||
using System.Collections.Generic; | |||
using Discord.Audio; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
@@ -33,6 +35,19 @@ namespace Discord | |||
return null; | |||
} | |||
public override async Task<IAudioClient> ConnectAsync() | |||
{ | |||
var audioMode = Discord.AudioMode; | |||
if (audioMode == AudioMode.Disabled) | |||
throw new InvalidOperationException($"Audio is not enabled on this client, {nameof(DiscordSocketConfig.AudioMode)} in {nameof(DiscordSocketConfig)} must be set."); | |||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, Id, | |||
(audioMode & AudioMode.Incoming) == 0, | |||
(audioMode & AudioMode.Outgoing) == 0).ConfigureAwait(false); | |||
return null; | |||
//TODO: Block and return | |||
} | |||
public CachedVoiceChannel Clone() => MemberwiseClone() as CachedVoiceChannel; | |||
ICachedChannel ICachedChannel.Clone() => Clone(); | |||
@@ -28,6 +28,7 @@ | |||
"System.Net.Http": "4.1.0", | |||
"System.Net.WebSockets.Client": "4.0.0", | |||
"System.Reflection.Extensions": "4.0.1", | |||
"System.Runtime.InteropServices": "4.1.0", | |||
"System.Runtime.Serialization.Primitives": "4.1.1", | |||
"System.Text.RegularExpressions": "4.1.0" | |||
}, | |||