Browse Source

Merge with 109f663a9a

pull/988/head
Hsu Still 7 years ago
parent
commit
50ce789320
No known key found for this signature in database GPG Key ID: 8601A145FDA95209
8 changed files with 147 additions and 96 deletions
  1. +1
    -1
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  2. +44
    -6
      src/Discord.Net.Commands/CommandService.cs
  3. +3
    -3
      src/Discord.Net.Commands/Info/CommandInfo.cs
  4. +15
    -6
      src/Discord.Net.Core/CDN.cs
  5. +71
    -75
      src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
  6. +2
    -0
      src/Discord.Net.Core/Entities/Users/IUser.cs
  7. +4
    -1
      src/Discord.Net.Rest/Entities/Users/RestUser.cs
  8. +7
    -4
      src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs

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

@@ -291,7 +291,7 @@ namespace Discord.Commands

//We dont have a cached type reader, create one
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, services);
service.AddTypeReader(paramType, reader);
service.AddTypeReader(paramType, reader, false);

return reader;
}


+ 44
- 6
src/Discord.Net.Commands/CommandService.cs View File

@@ -229,10 +229,11 @@ namespace Discord.Commands
return true;
}

//Type Readers
//Type Readers
/// <summary>
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
/// If <typeparamref name="T"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> will also be added.
/// If a default <see cref="TypeReader"/> exists for <typeparamref name="T"/>, a warning will be logged and the default <see cref="TypeReader"/> will be replaced.
/// </summary>
/// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam>
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
@@ -240,17 +241,54 @@ namespace Discord.Commands
=> AddTypeReader(typeof(T), reader);
/// <summary>
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
/// If <paramref name="type"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> for the value type will also be added.
/// If <paramref name="type"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> for the value type will also be added.
/// If a default <see cref="TypeReader"/> exists for <paramref name="type"/>, a warning will be logged and the default <see cref="TypeReader"/> will be replaced.
/// </summary>
/// <param name="type">A <see cref="Type"/> instance for the type to be read.</param>
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
public void AddTypeReader(Type type, TypeReader reader)
{
var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>());
readers[reader.GetType()] = reader;
if (_defaultTypeReaders.ContainsKey(type))
_ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." +
$"To suppress this message, use AddTypeReader<T>(reader, true).");
AddTypeReader(type, reader, true);
}
/// <summary>
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
/// If <typeparamref name="T"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> will also be added.
/// </summary>
/// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam>
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
/// <param name="replaceDefault">If <paramref name="reader"/> should replace the default <see cref="TypeReader"/> for <typeparamref name="T"/> if one exists.</param>
public void AddTypeReader<T>(TypeReader reader, bool replaceDefault)
=> AddTypeReader(typeof(T), reader, replaceDefault);
/// <summary>
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
/// If <paramref name="type"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> for the value type will also be added.
/// </summary>
/// <param name="type">A <see cref="Type"/> instance for the type to be read.</param>
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
/// <param name="replaceDefault">If <paramref name="reader"/> should replace the default <see cref="TypeReader"/> for <paramref name="type"/> if one exists.</param>
public void AddTypeReader(Type type, TypeReader reader, bool replaceDefault)
{
if (replaceDefault && _defaultTypeReaders.ContainsKey(type))
{
_defaultTypeReaders.AddOrUpdate(type, reader, (k, v) => reader);
if (type.GetTypeInfo().IsValueType)
{
var nullableType = typeof(Nullable<>).MakeGenericType(type);
var nullableReader = NullableTypeReader.Create(type, reader);
_defaultTypeReaders.AddOrUpdate(nullableType, nullableReader, (k, v) => nullableReader);
}
}
else
{
var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>());
readers[reader.GetType()] = reader;

if (type.GetTypeInfo().IsValueType)
AddNullableTypeReader(type, reader);
if (type.GetTypeInfo().IsValueType)
AddNullableTypeReader(type, reader);
}
}
internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader)
{


+ 3
- 3
src/Discord.Net.Commands/Info/CommandInfo.cs View File

@@ -191,11 +191,11 @@ namespace Discord.Commands
switch (RunMode)
{
case RunMode.Sync: //Always sync
return await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false);
return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
case RunMode.Async: //Always async
var t2 = Task.Run(async () =>
{
await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false);
await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
});
break;
}
@@ -207,7 +207,7 @@ namespace Discord.Commands
}
}

private async Task<IResult> ExecuteAsyncInternalAsync(ICommandContext context, object[] args, IServiceProvider services)
private async Task<IResult> ExecuteInternalAsync(ICommandContext context, object[] args, IServiceProvider services)
{
await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
try


+ 15
- 6
src/Discord.Net.Core/CDN.cs View File

@@ -16,7 +16,11 @@ namespace Discord
string extension = FormatToExtension(format, avatarId);
return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}";
}
/// <summary> Returns the guild icon URL based on the guild and icon ID. </summary>
/// <summary> Returns the default user avatar URL. </summary>
public static string GetDefaultUserAvatarUrl(ushort discriminator)
{
return $"{DiscordConfig.CDNUrl}embed/avatars/{discriminator % 5}.png";
}
public static string GetGuildIconUrl(ulong guildId, string iconId)
=> iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null;
/// <summary> Returns the guild splash URL based on the guild and icon ID. </summary>
@@ -46,11 +50,16 @@ namespace Discord
format = imageId.StartsWith("a_") ? ImageFormat.Gif : ImageFormat.Png;
switch (format)
{
case ImageFormat.Gif: return "gif";
case ImageFormat.Jpeg: return "jpeg";
case ImageFormat.Png: return "png";
case ImageFormat.WebP: return "webp";
default: throw new ArgumentException(nameof(format));
case ImageFormat.Gif:
return "gif";
case ImageFormat.Jpeg:
return "jpeg";
case ImageFormat.Png:
return "png";
case ImageFormat.WebP:
return "webp";
default:
throw new ArgumentException(nameof(format));
}
}
}


+ 71
- 75
src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs View File

@@ -1,13 +1,19 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Discord
{
/// <summary> A builder for creating an <see cref="Embed"/> to be sent. </summary>
public class EmbedBuilder
{
private readonly Embed _embed;
private string _title;
private string _description;
private string _url;
private EmbedImage? _image;
private EmbedThumbnail? _thumbnail;
private List<EmbedFieldBuilder> _fields;

/// <summary> The maximum number of fields allowed by Discord. </summary>
public const int MaxFieldCount = 25;
@@ -16,92 +22,100 @@ namespace Discord
/// <summary> The maximum length of description allowed by Discord. </summary>
public const int MaxDescriptionLength = 2048;
/// <summary> The maximum length of total characters allowed by Discord. </summary>
public const int MaxEmbedLength = 6000; // user bot limit is 2000, but we don't validate that here.
public const int MaxEmbedLength = 6000;

/// <summary> Creates a new <see cref="EmbedBuilder"/>. </summary>
public EmbedBuilder()
{
_embed = new Embed(EmbedType.Rich);
Fields = new List<EmbedFieldBuilder>();
}

/// <summary> Gets or sets the title of an <see cref="Embed"/>. </summary>
public string Title
{
get => _embed.Title;
get => _title;
set
{
if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title));
_embed.Title = value;
_title = value;
}
}

/// <summary> Gets or sets the description of an <see cref="Embed"/>. </summary>
public string Description
{
get => _embed.Description;
get => _description;
set
{
if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description));
_embed.Description = value;
_description = value;
}
}

/// <summary> Gets or sets the URL of an <see cref="Embed"/>. </summary>
public string Url
{
get => _embed.Url;
get => _url;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url));
_embed.Url = value;
_url = value;
}
}
/// <summary> Gets or sets the thumbnail URL of an <see cref="Embed"/>. </summary>
public string ThumbnailUrl
{
get => _embed.Thumbnail?.Url;
get => _thumbnail?.Url;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl));
_embed.Thumbnail = new EmbedThumbnail(value, null, null, null);
_thumbnail = new EmbedThumbnail(value, null, null, null);
}
}
/// <summary> Gets or sets the image URL of an <see cref="Embed"/>. </summary>
public string ImageUrl
{
get => _embed.Image?.Url;
get => _image?.Url;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl));
_embed.Image = new EmbedImage(value, null, null, null);
_image = new EmbedImage(value, null, null, null);
}
}
/// <summary> Gets or sets the timestamp of an <see cref="Embed"/>. </summary>
public DateTimeOffset? Timestamp { get => _embed.Timestamp; set { _embed.Timestamp = value; } }
/// <summary> Gets or sets the sidebar color of an <see cref="Embed"/>. </summary>
public Color? Color { get => _embed.Color; set { _embed.Color = value; } }

/// <summary> Gets or sets the <see cref="EmbedAuthorBuilder"/> of an <see cref="Embed"/>. </summary>
public EmbedAuthorBuilder Author { get; set; }
/// <summary> Gets or sets the <see cref="EmbedFooterBuilder"/> of an <see cref="Embed"/>. </summary>
public EmbedFooterBuilder Footer { get; set; }
private List<EmbedFieldBuilder> _fields;
/// <summary> Gets or sets the list of <see cref="EmbedFieldBuilder"/> of an <see cref="Embed"/>. </summary>
public List<EmbedFieldBuilder> Fields
{
get => _fields;
set
{

if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields));
if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields));
_fields = value;
}
}

/// <summary> Sets the title of an <see cref="Embed"/>. </summary>
/// <param name="title"> The title to be set. </param>
/// <summary> Gets or sets the timestamp of an <see cref="Embed"/>. </summary>
public DateTimeOffset? Timestamp { get; set; }
/// <summary> Gets or sets the sidebar color of an <see cref="Embed"/>. </summary>
public Color? Color { get; set; }
/// <summary> Gets or sets the <see cref="EmbedAuthorBuilder"/> of an <see cref="Embed"/>. </summary>
public EmbedAuthorBuilder Author { get; set; }
/// <summary> Gets or sets the <see cref="EmbedFooterBuilder"/> of an <see cref="Embed"/>. </summary>
public EmbedFooterBuilder Footer { get; set; }

public int Length
{
get
{
int titleLength = Title?.Length ?? 0;
int authorLength = Author?.Name?.Length ?? 0;
int descriptionLength = Description?.Length ?? 0;
int footerLength = Footer?.Text?.Length ?? 0;
int fieldSum = Fields.Sum(f => f.Name.Length + f.Value.ToString().Length);

return titleLength + authorLength + descriptionLength + footerLength + fieldSum;
}
}

public EmbedBuilder WithTitle(string title)
{
Title = title;
@@ -230,7 +244,6 @@ namespace Discord
AddField(field);
return this;
}

/// <summary> Adds a field with the provided <see cref="EmbedFieldBuilder"/> to an <see cref="Embed"/>. </summary>
/// <param name="field"> The field builder class containing the field properties. </param>
public EmbedBuilder AddField(EmbedFieldBuilder field)
@@ -249,7 +262,7 @@ namespace Discord
{
var field = new EmbedFieldBuilder();
action(field);
this.AddField(field);
AddField(field);
return this;
}

@@ -257,57 +270,48 @@ namespace Discord
/// <returns> The built embed object. </returns>
public Embed Build()
{
_embed.Footer = Footer?.Build();
_embed.Author = Author?.Build();
if (Length > MaxEmbedLength)
throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}");

var fields = ImmutableArray.CreateBuilder<EmbedField>(Fields.Count);
for (int i = 0; i < Fields.Count; i++)
fields.Add(Fields[i].Build());
_embed.Fields = fields.ToImmutable();

if (_embed.Length > MaxEmbedLength)
{
throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}");
}

return _embed;
return new Embed(EmbedType.Rich, Title, Description, Url, Timestamp, Color, _image, null, Author?.Build(), Footer?.Build(), null, _thumbnail, fields.ToImmutable());
}
}

public class EmbedFieldBuilder
{
private string _name;
private string _value;
private EmbedField _field;

public const int MaxFieldNameLength = 256;
public const int MaxFieldValueLength = 1024;

public string Name
{
get => _field.Name;
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name));
if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name));
_field.Name = value;
_name = value;
}
}

public object Value
{
get => _field.Value;
get => _value;
set
{
var stringValue = value?.ToString();
if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value));
if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value));
_field.Value = stringValue;
_value = stringValue;
}
}
public bool IsInline { get => _field.Inline; set { _field.Inline = value; } }

public EmbedFieldBuilder()
{
_field = new EmbedField();
}
public bool IsInline { get; set; }

public EmbedFieldBuilder WithName(string name)
{
@@ -326,48 +330,44 @@ namespace Discord
}

public EmbedField Build()
=> _field;
=> new EmbedField(Name, Value.ToString(), IsInline);
}

public class EmbedAuthorBuilder
{
private EmbedAuthor _author;

private string _name;
private string _url;
private string _iconUrl;
public const int MaxAuthorNameLength = 256;

public string Name
{
get => _author.Name;
get => _name;
set
{
if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name));
_author.Name = value;
_name = value;
}
}
public string Url
{
get => _author.Url;
get => _url;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url));
_author.Url = value;
_url = value;
}
}
public string IconUrl
{
get => _author.IconUrl;
get => _iconUrl;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl));
_author.IconUrl = value;
_iconUrl = value;
}
}

public EmbedAuthorBuilder()
{
_author = new EmbedAuthor();
}

public EmbedAuthorBuilder WithName(string name)
{
Name = name;
@@ -385,39 +385,35 @@ namespace Discord
}

public EmbedAuthor Build()
=> _author;
=> new EmbedAuthor(Name, Url, IconUrl, null);
}

public class EmbedFooterBuilder
{
private EmbedFooter _footer;
private string _text;
private string _iconUrl;

public const int MaxFooterTextLength = 2048;

public string Text
{
get => _footer.Text;
get => _text;
set
{
if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text));
_footer.Text = value;
_text = value;
}
}
public string IconUrl
{
get => _footer.IconUrl;
get => _iconUrl;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl));
_footer.IconUrl = value;
_iconUrl = value;
}
}

public EmbedFooterBuilder()
{
_footer = new EmbedFooter();
}

public EmbedFooterBuilder WithText(string text)
{
Text = text;
@@ -430,6 +426,6 @@ namespace Discord
}

public EmbedFooter Build()
=> _footer;
=> new EmbedFooter(Text, IconUrl, null);
}
}

+ 2
- 0
src/Discord.Net.Core/Entities/Users/IUser.cs View File

@@ -9,6 +9,8 @@ namespace Discord
string AvatarId { get; }
/// <summary> Gets the url to this user's avatar. </summary>
string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128);
/// <summary> Gets the url to this user's default avatar. </summary>
string GetDefaultAvatarUrl();
/// <summary> Gets the per-username unique id for this user. </summary>
string Discriminator { get; }
/// <summary> Gets the per-username unique id for this user. </summary>


+ 4
- 1
src/Discord.Net.Rest/Entities/Users/RestUser.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Model = Discord.API.User;
@@ -60,6 +60,9 @@ namespace Discord.Rest
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format);

public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);

public override string ToString() => $"{Username}#{Discriminator}";
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})";



+ 7
- 4
src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs View File

@@ -48,18 +48,18 @@ namespace Discord.WebSocket
{
var newVal = ushort.Parse(model.Discriminator.Value);
if (newVal != DiscriminatorValue)
{
{
DiscriminatorValue = ushort.Parse(model.Discriminator.Value);
hasChanges = true;
}
}
if (model.Bot.IsSpecified && model.Bot.Value != IsBot)
{
{
IsBot = model.Bot.Value;
hasChanges = true;
}
if (model.Username.IsSpecified && model.Username.Value != Username)
{
{
Username = model.Username.Value;
hasChanges = true;
}
@@ -74,7 +74,10 @@ namespace Discord.WebSocket
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format);

/// <summary> Gets the username and the discriminator. </summary>
/// <inheritdoc />
public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);

public override string ToString() => $"{Username}#{Discriminator}";
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})";
internal SocketUser Clone() => MemberwiseClone() as SocketUser;


Loading…
Cancel
Save