Browse Source

Added SendFile(User), reworked the message queue, adding edit message queuing

tags/docs-0.9
RogueException 9 years ago
parent
commit
946820d08a
3 changed files with 157 additions and 106 deletions
  1. +128
    -92
      src/Discord.Net/DiscordClient.Messages.cs
  2. +29
    -13
      src/Discord.Net/DiscordClient.cs
  3. +0
    -1
      src/Discord.Net/Net/Rest/SharpRestEngine.cs

+ 128
- 92
src/Discord.Net/DiscordClient.Messages.cs View File

@@ -2,8 +2,8 @@ using Discord.API;
using Discord.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -37,7 +37,20 @@ namespace Discord
=> base.Import(messages);
}

public class MessageEventArgs : EventArgs
internal class MessageQueueItem
{
public readonly Message Message;
public readonly string Text;
public readonly long[] MentionedUsers;
public MessageQueueItem(Message msg, string text, long[] userIds)
{
Message = msg;
Text = text;
MentionedUsers = userIds;
}
}

public class MessageEventArgs : EventArgs
{
public Message Message { get; }
public User User => Message.User;
@@ -83,10 +96,13 @@ namespace Discord
}
internal Messages Messages => _messages;
private readonly Messages _messages;
private readonly Random _nonceRand;
private readonly Messages _messages;
private readonly JsonSerializer _messageImporter;
private readonly ConcurrentQueue<MessageQueueItem> _pendingMessages;

/// <summary> Returns the message with the specified id, or null if none was found. </summary>
public Message GetMessage(long id)
/// <summary> Returns the message with the specified id, or null if none was found. </summary>
public Message GetMessage(long id)
{
if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id));
CheckReady();
@@ -101,93 +117,112 @@ namespace Discord
if (text == null) throw new ArgumentNullException(nameof(text));
CheckReady();

return SendMessage(channel, text, false);
}
/// <summary> Sends a text-to-speech message to the provided channel. To include a mention, see the Mention static helper class. </summary>
public Task<Message> SendTTSMessage(Channel channel, string text)
return SendMessageInternal(channel, text, false);
}
/// <summary> Sends a private message to the provided user. </summary>
public async Task<Message> SendMessage(User user, string text)
{
if (user == null) throw new ArgumentNullException(nameof(user));
if (text == null) throw new ArgumentNullException(nameof(text));
CheckReady();

var channel = await CreatePMChannel(user).ConfigureAwait(false);
return await SendMessageInternal(channel, text, false).ConfigureAwait(false);
}
/// <summary> Sends a text-to-speech message to the provided channel. To include a mention, see the Mention static helper class. </summary>
public Task<Message> SendTTSMessage(Channel channel, string text)
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
if (text == null) throw new ArgumentNullException(nameof(text));
CheckReady();

return SendMessage(channel, text, true);
return SendMessageInternal(channel, text, true);
}
/// <summary> Sends a private message to the provided user. </summary>
public async Task<Message> SendPrivateMessage(User user, string text)
{
if (user == null) throw new ArgumentNullException(nameof(user));
if (text == null) throw new ArgumentNullException(nameof(text));
/// <summary> Sends a file to the provided channel. </summary>
public Task<Message> SendFile(Channel channel, string filePath)
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
if (filePath == null) throw new ArgumentNullException(nameof(filePath));
CheckReady();

var channel = await CreatePMChannel(user).ConfigureAwait(false);
return await SendMessage(channel, text).ConfigureAwait(false);
}
private async Task<Message> SendMessage(Channel channel, string text, bool isTextToSpeech)
return SendFile(channel, Path.GetFileName(filePath), File.OpenRead(filePath));
}
/// <summary> Sends a file to the provided channel. </summary>
public async Task<Message> SendFile(Channel channel, string filename, Stream stream)
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
if (filename == null) throw new ArgumentNullException(nameof(filename));
if (stream == null) throw new ArgumentNullException(nameof(stream));
CheckReady();

var model = await _api.SendFile(channel.Id, filename, stream).ConfigureAwait(false);
var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id);
msg.Update(model);
RaiseMessageSent(msg);
return msg;
}
/// <summary> Sends a file to the provided channel. </summary>
public async Task<Message> SendFile(User user, string filePath)
{
if (user == null) throw new ArgumentNullException(nameof(user));
if (filePath == null) throw new ArgumentNullException(nameof(filePath));
CheckReady();

var channel = await CreatePMChannel(user).ConfigureAwait(false);
return await SendFile(channel, Path.GetFileName(filePath), File.OpenRead(filePath)).ConfigureAwait(false);
}
/// <summary> Sends a file to the provided channel. </summary>
public async Task<Message> SendFile(User user, string filename, Stream stream)
{
if (user == null) throw new ArgumentNullException(nameof(user));
if (filename == null) throw new ArgumentNullException(nameof(filename));
if (stream == null) throw new ArgumentNullException(nameof(stream));
CheckReady();

var channel = await CreatePMChannel(user).ConfigureAwait(false);
return await SendFile(channel, filename, stream).ConfigureAwait(false);
}
private async Task<Message> SendMessageInternal(Channel channel, string text, bool isTextToSpeech)
{
Message msg;
var server = channel.Server;

if (Config.UseMessageQueue)
var mentionedUsers = new List<User>();
text = Mention.CleanUserMentions(this, server, text, mentionedUsers);
if (text.Length > MaxMessageSize)
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less.");

if (Config.UseMessageQueue)
{
var nonce = GenerateNonce();
msg = _messages.GetOrAdd(nonce, channel.Id, _userId.Value);
var currentUser = msg.User;
msg.Update(new MessageInfo
{
Content = text,
msg.Update(new MessageInfo
{
Content = text,
Timestamp = DateTime.UtcNow,
Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = _userId.Value, Username = currentUser.Name },
ChannelId = channel.Id,
Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = _userId.Value, Username = currentUser.Name },
ChannelId = channel.Id,
Nonce = IdConvert.ToString(nonce),
IsTextToSpeech = isTextToSpeech
});
msg.State = MessageState.Queued;

if (text.Length > MaxMessageSize)
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less.");

_pendingMessages.Enqueue(msg);
}
_pendingMessages.Enqueue(new MessageQueueItem(msg, text, mentionedUsers.Select(x => x.Id).ToArray()));
}
else
{
var mentionedUsers = new List<User>();
if (!channel.IsPrivate)
text = Mention.CleanUserMentions(this, server, text, mentionedUsers);

if (text.Length > MaxMessageSize)
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less.");

var model = await _api.SendMessage(channel.Id, text, mentionedUsers.Select(x => x.Id), null, isTextToSpeech).ConfigureAwait(false);
msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id);
msg.Update(model);
RaiseMessageSent(msg);
}
var model = await _api.SendMessage(channel.Id, text, mentionedUsers.Select(x => x.Id), null, isTextToSpeech).ConfigureAwait(false);
msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id);
msg.Update(model);
RaiseMessageSent(msg);
}
return msg;
}


/// <summary> Sends a file to the provided channel. </summary>
public Task SendFile(Channel channel, string filePath)
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
if (filePath == null) throw new ArgumentNullException(nameof(filePath));
CheckReady();
return _api.SendFile(channel.Id, Path.GetFileName(filePath), File.OpenRead(filePath));
}
/// <summary> Sends a file to the provided channel. </summary>
public Task SendFile(Channel channel, string filename, Stream stream)
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
if (filename == null) throw new ArgumentNullException(nameof(filename));
if (stream == null) throw new ArgumentNullException(nameof(stream));
CheckReady();

return _api.SendFile(channel.Id, filename, stream);
}

/// <summary> Edits the provided message, changing only non-null attributes. </summary>
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks>
public Task EditMessage(Message message, string text)
/// <summary> Edits the provided message, changing only non-null attributes. </summary>
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks>
public async Task EditMessage(Message message, string text)
{
if (message == null) throw new ArgumentNullException(nameof(message));
if (text == null) throw new ArgumentNullException(nameof(text));
@@ -200,8 +235,11 @@ namespace Discord

if (text.Length > MaxMessageSize)
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less.");

return _api.EditMessage(message.Id, message.Channel.Id, text, mentionedUsers.Select(x => x.Id));
if (Config.UseMessageQueue)
_pendingMessages.Enqueue(new MessageQueueItem(message, text, mentionedUsers.Select(x => x.Id).ToArray()));
else
await _api.EditMessage(message.Id, message.Channel.Id, text, mentionedUsers.Select(x => x.Id)).ConfigureAwait(false);
}

/// <summary> Deletes the provided message. </summary>
@@ -302,46 +340,44 @@ namespace Discord
return JsonConvert.SerializeObject(channel.Messages);
}

private Task MessageQueueLoop()
private Task MessageQueueAsync()
{
var cancelToken = _cancelToken;
int interval = Config.MessageQueueInterval;

return Task.Run(async () =>
{
Message msg;
MessageQueueItem queuedMessage;

while (!cancelToken.IsCancellationRequested)
{
while (_pendingMessages.TryDequeue(out msg))
while (_pendingMessages.TryDequeue(out queuedMessage))
{
bool hasFailed = false;
SendMessageResponse response = null;
try
{
response = await _api.SendMessage(msg.Channel.Id, msg.RawText, msg.MentionedUsers.Select(x => x.Id), IdConvert.ToString(msg.Id), msg.IsTTS).ConfigureAwait(false);
}
catch (WebException) { break; }
catch (HttpException) { hasFailed = true; }

if (!hasFailed)
{
_messages.Remap(msg.Id, response.Id);
msg.Id = response.Id;
msg.Update(response);
msg.State = MessageState.Normal;
var msg = queuedMessage.Message;
try
{
response = await _api.SendMessage(
msg.Channel.Id,
queuedMessage.Text,
queuedMessage.MentionedUsers,
IdConvert.ToString(msg.Id), //Nonce
msg.IsTTS)
.ConfigureAwait(false);
}
else
msg.State = MessageState.Failed;
catch (WebException) { break; }
catch (HttpException) { msg.State = MessageState.Failed; }
RaiseMessageSent(msg);
}
await Task.Delay(interval).ConfigureAwait(false);
}
await Task.Delay(interval).ConfigureAwait(false);
}
});
}
private long GenerateNonce()
{
lock (_rand)
return -_rand.Next(1, int.MaxValue - 1);
lock (_nonceRand)
return -_nonceRand.Next(1, int.MaxValue - 1);
}
}
}

+ 29
- 13
src/Discord.Net/DiscordClient.cs View File

@@ -53,9 +53,6 @@ namespace Discord

private readonly ManualResetEvent _disconnectedEvent;
private readonly ManualResetEventSlim _connectedEvent;
private readonly Random _rand;
private readonly JsonSerializer _messageImporter;
private readonly ConcurrentQueue<Message> _pendingMessages;
private readonly Dictionary<Type, object> _singletons;
private readonly LogService _log;
private readonly object _cacheLock;
@@ -114,7 +111,7 @@ namespace Discord
_config = config ?? new DiscordConfig();
_config.Lock();

_rand = new Random();
_nonceRand = new Random();
_state = (int)DiscordClientState.Disconnected;
_status = UserStatus.Online;

@@ -155,7 +152,7 @@ namespace Discord

_api = new DiscordAPIClient(_config);
if (Config.UseMessageQueue)
_pendingMessages = new ConcurrentQueue<Message>();
_pendingMessages = new ConcurrentQueue<MessageQueueItem>();
Connected += async (s, e) =>
{
_api.CancelToken = _cancelToken;
@@ -393,7 +390,7 @@ namespace Discord
List<Task> tasks = new List<Task>();
tasks.Add(_cancelToken.Wait());
if (_config.UseMessageQueue)
tasks.Add(MessageQueueLoop());
tasks.Add(MessageQueueAsync());

Task[] tasksArray = tasks.ToArray();
Task firstTask = Task.WhenAny(tasksArray);
@@ -432,7 +429,7 @@ namespace Discord
{
if (Config.UseMessageQueue)
{
Message ignored;
MessageQueueItem ignored;
while (_pendingMessages.TryDequeue(out ignored)) { }
}

@@ -668,9 +665,19 @@ namespace Discord
Message msg = null;

bool isAuthor = data.Author.Id == _userId;
int nonce = 0;

if (data.Author.Id == _privateUser.Id && Config.UseMessageQueue)
{
if (data.Nonce != null && int.TryParse(data.Nonce, out nonce))
msg = _messages[nonce];
}
if (msg == null)
{
msg = _messages.GetOrAdd(data.Id, data.ChannelId, data.Author.Id);
nonce = 0;
}

if (msg == null)
msg = _messages.GetOrAdd(data.Id, data.ChannelId, data.Author.Id);
msg.Update(data);
if (Config.TrackActivity)
{
@@ -683,7 +690,15 @@ namespace Discord
}
}

RaiseMessageReceived(msg);
//Remapped queued message
if (nonce != 0)
{
msg = _messages.Remap(nonce, data.Id);
msg.Id = data.Id;
}

msg.State = MessageState.Normal;
RaiseMessageReceived(msg);

if (Config.AckMessages && !isAuthor)
await _api.AckMessage(data.Id, data.ChannelId).ConfigureAwait(false);
@@ -694,9 +709,10 @@ namespace Discord
var data = e.Payload.ToObject<MessageUpdateEvent>(_webSocket.Serializer);
var msg = _messages[data.Id];
if (msg != null)
{
msg.Update(data);
RaiseMessageUpdated(msg);
{
msg.Update(data);
msg.State = MessageState.Normal;
RaiseMessageUpdated(msg);
}
}
break;


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

@@ -75,7 +75,6 @@ namespace Discord.Net.Rest
{
var retryAfter = response.Headers
.FirstOrDefault(x => x.Name.Equals("Retry-After", StringComparison.OrdinalIgnoreCase));
int milliseconds;
if (retryAfter != null)
{
await Task.Delay((int)retryAfter.Value);


Loading…
Cancel
Save