Merge branch 'dev' of https://github.com/RogueException/Discord.Net.git
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.WebSocket.Extensions
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
public static class ChannelExtensions
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord.WebSocket.Extensions
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
// Todo: Docstrings
|
||||
public static class GuildExtensions
|
||||
|
||||
Reference in New Issue
Block a user