This commit is contained in:
Finite Reality
2016-08-03 15:34:53 +01:00
19 changed files with 295 additions and 61 deletions

View File

@@ -357,6 +357,12 @@ namespace Discord.API
await SendAsync("DELETE", $"channels/{channelId}/pins/{messageId}", options: options).ConfigureAwait(false);
}
public async Task<IReadOnlyCollection<Message>> GetPinsAsync(ulong channelId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
return await SendAsync<IReadOnlyCollection<Message>>("GET", $"channels/{channelId}/pins", options: options).ConfigureAwait(false);
}
//Channel Recipients
public async Task AddGroupRecipientAsync(ulong channelId, ulong userId, RequestOptions options = null)
@@ -810,7 +816,7 @@ namespace Discord.API
{
return CreateMessageInternalAsync(0, channelId, args);
}
public async Task<Message> CreateMessageInternalAsync(ulong guildId, ulong channelId, CreateMessageParams args, RequestOptions options = null)
private async Task<Message> CreateMessageInternalAsync(ulong guildId, ulong channelId, CreateMessageParams args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
Preconditions.NotNull(args, nameof(args));
@@ -1010,7 +1016,7 @@ namespace Discord.API
public async Task ModifyMyNickAsync(ulong guildId, ModifyCurrentUserNickParams args, RequestOptions options = null)
{
Preconditions.NotNull(args, nameof(args));
Preconditions.NotEmpty(args.Nickname, nameof(args.Nickname));
Preconditions.NotNull(args.Nickname, nameof(args.Nickname));
await SendAsync("PATCH", $"guilds/{guildId}/members/@me/nick", args, options: options).ConfigureAwait(false);
}

View File

@@ -23,6 +23,8 @@ namespace Discord
Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch);
/// <summary> Gets a collection of messages in this channel. </summary>
Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch);
/// <summary> Gets a collection of pinned messages in this channel. </summary>
Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync();
/// <summary> Bulk deletes multiple messages. </summary>
Task DeleteMessagesAsync(IEnumerable<IMessage> messages);

View File

@@ -108,6 +108,11 @@ namespace Discord
{
await Discord.ApiClient.DeleteDMMessagesAsync(Id, new DeleteMessagesParams { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false);
}
public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync()
{
var models = await Discord.ApiClient.GetPinsAsync(Id);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
}
public async Task TriggerTypingAsync()
{

View File

@@ -133,6 +133,11 @@ namespace Discord
{
await Discord.ApiClient.DeleteDMMessagesAsync(Id, new DeleteMessagesParams { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false);
}
public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync()
{
var models = await Discord.ApiClient.GetPinsAsync(Id);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
}
public async Task TriggerTypingAsync()
{

View File

@@ -102,7 +102,12 @@ namespace Discord
{
await Discord.ApiClient.DeleteMessagesAsync(Guild.Id, Id, new DeleteMessagesParams { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false);
}
public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync()
{
var models = await Discord.ApiClient.GetPinsAsync(Id);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
}
public async Task TriggerTypingAsync()
{
await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false);

View File

@@ -115,6 +115,7 @@ namespace Discord.Rpc
/// <inheritdoc />
public async Task DisconnectAsync()
{
if (_connectTask?.TrySetCanceled() ?? false) return;
await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
@@ -122,16 +123,6 @@ namespace Discord.Rpc
}
finally { _connectionLock.Release(); }
}
private async Task DisconnectAsync(Exception ex, bool isReconnecting)
{
if (_connectTask?.TrySetException(ex) ?? false) return;
await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
await DisconnectInternalAsync(ex, isReconnecting).ConfigureAwait(false);
}
finally { _connectionLock.Release(); }
}
private async Task DisconnectInternalAsync(Exception ex, bool isReconnecting)
{
if (!isReconnecting)
@@ -173,7 +164,14 @@ namespace Discord.Rpc
}
private async Task ReconnectInternalAsync(Exception ex, CancellationToken cancelToken)
{
await DisconnectAsync(null, true).ConfigureAwait(false);
if (ex == null)
{
if (_connectTask?.TrySetCanceled() ?? false) return;
}
else
{
if (_connectTask?.TrySetException(ex) ?? false) return;
}
try
{

View File

@@ -191,16 +191,6 @@ namespace Discord.WebSocket
}
finally { _connectionLock.Release(); }
}
private async Task DisconnectAsync(Exception ex, bool isReconnecting)
{
if (_connectTask?.TrySetException(ex) ?? false) return;
await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
await DisconnectInternalAsync(ex, isReconnecting).ConfigureAwait(false);
}
finally { _connectionLock.Release(); }
}
private async Task DisconnectInternalAsync(Exception ex, bool isReconnecting)
{
if (!isReconnecting)
@@ -270,7 +260,14 @@ namespace Discord.WebSocket
}
private async Task ReconnectInternalAsync(Exception ex, CancellationToken cancelToken)
{
await DisconnectAsync(null, true).ConfigureAwait(false);
if (ex == null)
{
if (_connectTask?.TrySetCanceled() ?? false) return;
}
else
{
if (_connectTask?.TrySetException(ex) ?? false) return;
}
try
{
@@ -580,7 +577,7 @@ namespace Discord.WebSocket
}
catch (Exception ex)
{
await DisconnectAsync(new Exception("Processing READY failed", ex), false);
_connectTask.TrySetException(new Exception("Processing READY failed", ex));
return;
}
@@ -1402,12 +1399,17 @@ namespace Discord.WebSocket
{
before = guild.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false);
after = guild.AddOrUpdateVoiceState(data, DataStore);
if (data.UserId == _currentUser.Id)
{
var _ = guild.FinishJoinAudioChannel().ConfigureAwait(false);
}
}
else
{
before = guild.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false);
after = new VoiceState(null, data);
}
user = guild.GetUser(data.UserId);
}
else
@@ -1460,7 +1462,7 @@ namespace Discord.WebSocket
if (guild != null)
{
string endpoint = data.Endpoint.Substring(0, data.Endpoint.LastIndexOf(':'));
var _ = guild.ConnectAudio(_nextAudioId++, endpoint, data.Token).ConfigureAwait(false);
var _ = guild.FinishConnectAudio(_nextAudioId++, endpoint, data.Token).ConfigureAwait(false);
}
else
{

View File

@@ -41,12 +41,10 @@ namespace Discord
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,
return await Guild.ConnectAudioAsync(Id,
(audioMode & AudioMode.Incoming) == 0,
(audioMode & AudioMode.Outgoing) == 0).ConfigureAwait(false);
return null;
//TODO: Block and return
}
public SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel;

View File

@@ -25,6 +25,7 @@ namespace Discord
private readonly SemaphoreSlim _audioLock;
private TaskCompletionSource<bool> _syncPromise, _downloaderPromise;
private TaskCompletionSource<AudioClient> _audioConnectPromise;
private ConcurrentHashSet<ulong> _channels;
private ConcurrentDictionary<ulong, SocketGuildUser> _members;
private ConcurrentDictionary<ulong, VoiceState> _voiceStates;
@@ -260,38 +261,99 @@ namespace Discord
return null;
}
public async Task ConnectAudio(int id, string url, string token)
public async Task<IAudioClient> ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute)
{
AudioClient audioClient;
await _audioLock.WaitAsync().ConfigureAwait(false);
var voiceState = GetVoiceState(CurrentUser.Id).Value;
try
{
audioClient = AudioClient;
if (audioClient == null)
TaskCompletionSource<AudioClient> promise;
await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
audioClient = new AudioClient(this, id);
await DisconnectAudioInternalAsync().ConfigureAwait(false);
promise = new TaskCompletionSource<AudioClient>();
_audioConnectPromise = promise;
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false);
}
finally
{
_audioLock.Release();
}
var timeoutTask = Task.Delay(15000);
if (await Task.WhenAny(promise.Task, timeoutTask) == timeoutTask)
throw new TimeoutException();
return await promise.Task.ConfigureAwait(false);
}
catch (Exception)
{
await DisconnectAudioInternalAsync().ConfigureAwait(false);
throw;
}
}
public async Task DisconnectAudioAsync(AudioClient client = null)
{
await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
await DisconnectAudioInternalAsync(client).ConfigureAwait(false);
}
finally
{
_audioLock.Release();
}
}
private async Task DisconnectAudioInternalAsync(AudioClient client = null)
{
var oldClient = AudioClient;
if (oldClient != null)
{
if (client == null || oldClient == client)
{
_audioConnectPromise?.TrySetCanceledAsync(); //Cancel any previous audio connection
_audioConnectPromise = null;
}
if (oldClient == client)
{
AudioClient = null;
await oldClient.DisconnectAsync().ConfigureAwait(false);
}
}
}
public async Task FinishConnectAudio(int id, string url, string token)
{
var voiceState = GetVoiceState(CurrentUser.Id).Value;
await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
if (AudioClient == null)
{
var audioClient = new AudioClient(this, id);
audioClient.Disconnected += async ex =>
{
await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
if (ex != null)
if (AudioClient == audioClient) //Only reconnect if we're still assigned as this guild's audio client
{
//Reconnect if we still have channel info.
//TODO: Is this threadsafe? Could channel data be deleted before we access it?
var voiceState2 = GetVoiceState(CurrentUser.Id);
if (voiceState2.HasValue)
if (ex != null)
{
var voiceChannelId = voiceState2.Value.VoiceChannel?.Id;
if (voiceChannelId != null)
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted);
//Reconnect if we still have channel info.
//TODO: Is this threadsafe? Could channel data be deleted before we access it?
var voiceState2 = GetVoiceState(CurrentUser.Id);
if (voiceState2.HasValue)
{
var voiceChannelId = voiceState2.Value.VoiceChannel?.Id;
if (voiceChannelId != null)
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted);
}
}
else
{
try { AudioClient.Dispose(); } catch { }
AudioClient = null;
}
}
else
{
try { AudioClient.Dispose(); } catch { }
AudioClient = null;
}
}
finally
@@ -301,12 +363,35 @@ namespace Discord
};
AudioClient = audioClient;
}
await AudioClient.ConnectAsync(url, CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false);
await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
await DisconnectAudioAsync();
}
catch (Exception e)
{
await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false);
await DisconnectAudioAsync();
}
finally
{
_audioLock.Release();
}
}
public async Task FinishJoinAudioChannel()
{
await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
if (AudioClient != null)
await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false);
}
finally
{
_audioLock.Release();
}
await audioClient.ConnectAsync(url, CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false);
}
public SocketGuild Clone() => MemberwiseClone() as SocketGuild;

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace Discord.WebSocket.Extensions
namespace Discord.WebSocket
{
public static class ChannelExtensions
{

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
namespace Discord.WebSocket.Extensions
namespace Discord.WebSocket
{
// Todo: Docstrings
public static class GuildExtensions