@@ -16,13 +16,9 @@ Our stable builds available from NuGet through the Discord.Net metapackage: | |||
The individual components may also be installed from NuGet: | |||
- [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) | |||
- [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/) | |||
- [Discord.Net.Rpc](https://www.nuget.org/packages/Discord.Net.Rpc/) | |||
- [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/) | |||
- [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/) | |||
The following provider is available for platforms not supporting .NET Standard 1.3: | |||
- [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) | |||
### Unstable (MyGet) | |||
Nightly builds are available through our MyGet feed (`https://www.myget.org/F/discord-net/api/v3/index.json`). | |||
@@ -41,5 +37,4 @@ The .NET Core workload must be selected during Visual Studio installation. | |||
## Known Issues | |||
### WebSockets (Win7 and earlier) | |||
.NET Core 1.1 does not support WebSockets on Win7 and earlier. It's recommended to use the Discord.Net.Providers.WS4Net package until this is resolved. | |||
Track the issue [here](https://github.com/dotnet/corefx/issues/9503). | |||
.NET Core 1.1 does not support WebSockets on Win7 and earlier. This issue has been fixed since the release of .NET Core 2.1. It is recommended to target .NET Core 2.1 or above for your project if you wish to run your bot on legacy platforms; alternatively, you may choose to install the [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) package. |
@@ -120,7 +120,7 @@ namespace Discord.Commands.Builders | |||
if (Name == null) | |||
Name = _aliases[0]; | |||
if (TypeInfo != null) | |||
if (TypeInfo != null && !TypeInfo.IsAbstract) | |||
{ | |||
var moduleInstance = ReflectionUtils.CreateObject<IModuleBase>(TypeInfo, service, services); | |||
moduleInstance.OnModuleBuilding(service, this); | |||
@@ -45,14 +45,7 @@ namespace Discord.Commands.Builders | |||
internal void SetType(Type type) | |||
{ | |||
var readers = Command.Module.Service.GetTypeReaders(type); | |||
if (readers != null) | |||
TypeReader = readers.FirstOrDefault().Value; | |||
else | |||
TypeReader = Command.Module.Service.GetDefaultTypeReader(type); | |||
if (TypeReader == null) | |||
throw new InvalidOperationException($"{type} does not have a TypeReader registered for it. Parameter: {Name} in {Command.PrimaryAlias}"); | |||
TypeReader = GetReader(type); | |||
if (type.GetTypeInfo().IsValueType) | |||
DefaultValue = Activator.CreateInstance(type); | |||
@@ -60,7 +53,16 @@ namespace Discord.Commands.Builders | |||
type = ParameterType.GetElementType(); | |||
ParameterType = type; | |||
} | |||
private TypeReader GetReader(Type type) | |||
{ | |||
var readers = Command.Module.Service.GetTypeReaders(type); | |||
if (readers != null) | |||
return readers.FirstOrDefault().Value; | |||
else | |||
return Command.Module.Service.GetDefaultTypeReader(type); | |||
} | |||
public ParameterBuilder WithSummary(string summary) | |||
{ | |||
Summary = summary; | |||
@@ -100,10 +102,10 @@ namespace Discord.Commands.Builders | |||
internal ParameterInfo Build(CommandInfo info) | |||
{ | |||
if (TypeReader == null) | |||
if ((TypeReader ?? (TypeReader = GetReader(ParameterType))) == null) | |||
throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified"); | |||
return new ParameterInfo(this, info, Command.Module.Service); | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -262,7 +262,7 @@ namespace Discord.Commands | |||
/// <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)) | |||
if (replaceDefault && HasDefaultTypeReader(type)) | |||
{ | |||
_defaultTypeReaders.AddOrUpdate(type, reader, (k, v) => reader); | |||
if (type.GetTypeInfo().IsValueType) | |||
@@ -281,6 +281,16 @@ namespace Discord.Commands | |||
AddNullableTypeReader(type, reader); | |||
} | |||
} | |||
internal bool HasDefaultTypeReader(Type type) | |||
{ | |||
if (_defaultTypeReaders.ContainsKey(type)) | |||
return true; | |||
var typeInfo = type.GetTypeInfo(); | |||
if (typeInfo.IsEnum) | |||
return true; | |||
return _entityTypeReaders.Any(x => type == x.Item1 || typeInfo.ImplementedInterfaces.Contains(x.Item2)); | |||
} | |||
internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) | |||
{ | |||
var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary<Type, TypeReader>()); | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
namespace Discord.Commands | |||
{ | |||
@@ -43,4 +43,4 @@ namespace Discord.Commands | |||
return false; | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
namespace Discord.Commands | |||
@@ -53,6 +54,8 @@ namespace Discord.Commands | |||
public static ParseResult FromError(CommandError error, string reason) | |||
=> new ParseResult(null, null, error, reason); | |||
public static ParseResult FromError(Exception ex) | |||
=> FromError(CommandError.Exception, ex.Message); | |||
public static ParseResult FromError(IResult result) | |||
=> new ParseResult(null, null, result.Error, result.ErrorReason); | |||
@@ -1,4 +1,5 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
namespace Discord.Commands | |||
@@ -18,6 +19,8 @@ namespace Discord.Commands | |||
=> new PreconditionGroupResult(null, null, null); | |||
public static PreconditionGroupResult FromError(string reason, ICollection<PreconditionResult> preconditions) | |||
=> new PreconditionGroupResult(CommandError.UnmetPrecondition, reason, preconditions); | |||
public static new PreconditionGroupResult FromError(Exception ex) | |||
=> new PreconditionGroupResult(CommandError.Exception, ex.Message, null); | |||
public static new PreconditionGroupResult FromError(IResult result) //needed? | |||
=> new PreconditionGroupResult(result.Error, result.ErrorReason, null); | |||
@@ -1,4 +1,5 @@ | |||
using System.Diagnostics; | |||
using System; | |||
using System.Diagnostics; | |||
namespace Discord.Commands | |||
{ | |||
@@ -20,6 +21,8 @@ namespace Discord.Commands | |||
=> new PreconditionResult(null, null); | |||
public static PreconditionResult FromError(string reason) | |||
=> new PreconditionResult(CommandError.UnmetPrecondition, reason); | |||
public static PreconditionResult FromError(Exception ex) | |||
=> new PreconditionResult(CommandError.Exception, ex.Message); | |||
public static PreconditionResult FromError(IResult result) | |||
=> new PreconditionResult(result.Error, result.ErrorReason); | |||
@@ -1,4 +1,5 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
namespace Discord.Commands | |||
@@ -26,6 +27,8 @@ namespace Discord.Commands | |||
=> new SearchResult(text, commands, null, null); | |||
public static SearchResult FromError(CommandError error, string reason) | |||
=> new SearchResult(null, null, error, reason); | |||
public static SearchResult FromError(Exception ex) | |||
=> FromError(CommandError.Exception, ex.Message); | |||
public static SearchResult FromError(IResult result) | |||
=> new SearchResult(null, null, result.Error, result.ErrorReason); | |||
@@ -50,6 +50,8 @@ namespace Discord.Commands | |||
=> new TypeReaderResult(values, null, null); | |||
public static TypeReaderResult FromError(CommandError error, string reason) | |||
=> new TypeReaderResult(null, error, reason); | |||
public static TypeReaderResult FromError(Exception ex) | |||
=> FromError(CommandError.Exception, ex.Message); | |||
public static TypeReaderResult FromError(IResult result) | |||
=> new TypeReaderResult(null, result.Error, result.ErrorReason); | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
@@ -9,7 +9,7 @@ namespace Discord | |||
/// <summary> | |||
/// Represents an entry in an audit log | |||
/// </summary> | |||
public interface IAuditLogEntry : IEntity<ulong> | |||
public interface IAuditLogEntry : ISnowflakeEntity | |||
{ | |||
/// <summary> | |||
/// The action which occured to create this entry | |||
@@ -120,6 +120,15 @@ namespace Discord | |||
/// <summary> Gets a collection of all invites to this guild. </summary> | |||
Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the vanity invite URL of this guild. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// An awaitable <see cref="Task"/> containing the partial metadata of the vanity invite found within | |||
/// this guild. | |||
/// </returns> | |||
Task<IInviteMetadata> GetVanityInviteAsync(RequestOptions options = null); | |||
/// <summary> Gets the role in this guild with the provided id, or null if not found. </summary> | |||
IRole GetRole(ulong id); | |||
@@ -140,7 +149,7 @@ namespace Discord | |||
Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); | |||
/// <summary> Gets the specified number of audit log entries for this guild. </summary> | |||
Task<IReadOnlyCollection<IAuditLogEntry>> GetAuditLogAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, | |||
Task<IReadOnlyCollection<IAuditLogEntry>> GetAuditLogsAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, | |||
CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> Gets the webhook in this guild with the provided id, or null if not found. </summary> | |||
@@ -1,5 +1,3 @@ | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public interface IInvite : IEntity<string>, IDeletable | |||
@@ -11,6 +9,8 @@ namespace Discord | |||
/// <summary> Gets the channel this invite is linked to. </summary> | |||
IChannel Channel { get; } | |||
/// <summary> Gets the type of the channel this invite is linked to. </summary> | |||
ChannelType ChannelType { get; } | |||
/// <summary> Gets the id of the channel this invite is linked to. </summary> | |||
ulong ChannelId { get; } | |||
/// <summary> Gets the name of the channel this invite is linked to. </summary> | |||
@@ -19,7 +19,7 @@ namespace Discord | |||
/// <summary> Gets the guild this invite is linked to. </summary> | |||
IGuild Guild { get; } | |||
/// <summary> Gets the id of the guild this invite is linked to. </summary> | |||
ulong GuildId { get; } | |||
ulong? GuildId { get; } | |||
/// <summary> Gets the name of the guild this invite is linked to. </summary> | |||
string GuildName { get; } | |||
/// <summary> Gets the approximated count of online members in the guild. </summary> | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
namespace Discord | |||
{ | |||
@@ -15,8 +15,8 @@ namespace Discord | |||
/// <summary> Gets the max amount of times this invite may be used, or null if there is no limit. </summary> | |||
int? MaxUses { get; } | |||
/// <summary> Gets the amount of times this invite has been used. </summary> | |||
int Uses { get; } | |||
int? Uses { get; } | |||
/// <summary> Gets when this invite was created. </summary> | |||
DateTimeOffset CreatedAt { get; } | |||
DateTimeOffset? CreatedAt { get; } | |||
} | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
namespace Discord | |||
{ | |||
@@ -30,6 +30,7 @@ namespace Discord | |||
DeafenMembers = 0x00_80_00_00, | |||
MoveMembers = 0x01_00_00_00, | |||
UseVAD = 0x02_00_00_00, | |||
PrioritySpeaker = 0x00_00_01_00, | |||
// More General | |||
ManageRoles = 0x10_00_00_00, | |||
@@ -12,7 +12,7 @@ namespace Discord | |||
/// <summary> Gets a ChannelPermissions that grants all permissions for text channels. </summary> | |||
public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); | |||
/// <summary> Gets a ChannelPermissions that grants all permissions for voice channels. </summary> | |||
public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000010000_010001); | |||
public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000010100_010001); | |||
/// <summary> Gets a ChannelPermissions that grants all permissions for category channels. </summary> | |||
public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); | |||
/// <summary> Gets a ChannelPermissions that grants all permissions for direct message channels. </summary> | |||
@@ -78,6 +78,8 @@ namespace Discord | |||
public bool MoveMembers => Permissions.GetValue(RawValue, ChannelPermission.MoveMembers); | |||
/// <summary> If True, a user may use voice-activity-detection rather than push-to-talk. </summary> | |||
public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); | |||
/// <summary> If True, a user may use priority speaker in a voice channel. </summary> | |||
public bool PrioritySpeaker => Permissions.GetValue(RawValue, ChannelPermission.PrioritySpeaker); | |||
/// <summary> If True, a user may adjust role permissions. This also implictly grants all other permissions. </summary> | |||
public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles); | |||
@@ -106,6 +108,7 @@ namespace Discord | |||
bool? deafenMembers = null, | |||
bool? moveMembers = null, | |||
bool? useVoiceActivation = null, | |||
bool? prioritySpeaker = null, | |||
bool? manageRoles = null, | |||
bool? manageWebhooks = null) | |||
{ | |||
@@ -129,6 +132,7 @@ namespace Discord | |||
Permissions.SetValue(ref value, deafenMembers, ChannelPermission.DeafenMembers); | |||
Permissions.SetValue(ref value, moveMembers, ChannelPermission.MoveMembers); | |||
Permissions.SetValue(ref value, useVoiceActivation, ChannelPermission.UseVAD); | |||
Permissions.SetValue(ref value, prioritySpeaker, ChannelPermission.PrioritySpeaker); | |||
Permissions.SetValue(ref value, manageRoles, ChannelPermission.ManageRoles); | |||
Permissions.SetValue(ref value, manageWebhooks, ChannelPermission.ManageWebhooks); | |||
@@ -155,11 +159,12 @@ namespace Discord | |||
bool deafenMembers = false, | |||
bool moveMembers = false, | |||
bool useVoiceActivation = false, | |||
bool prioritySpeaker = false, | |||
bool manageRoles = false, | |||
bool manageWebhooks = false) | |||
: this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, | |||
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, | |||
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks) | |||
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, manageRoles, manageWebhooks) | |||
{ } | |||
/// <summary> Creates a new ChannelPermissions from this one, changing the provided non-null permissions. </summary> | |||
@@ -182,6 +187,7 @@ namespace Discord | |||
bool? deafenMembers = null, | |||
bool? moveMembers = null, | |||
bool? useVoiceActivation = null, | |||
bool? prioritySpeaker = null, | |||
bool? manageRoles = null, | |||
bool? manageWebhooks = null) | |||
=> new ChannelPermissions(RawValue, | |||
@@ -203,6 +209,7 @@ namespace Discord | |||
deafenMembers, | |||
moveMembers, | |||
useVoiceActivation, | |||
prioritySpeaker, | |||
manageRoles, | |||
manageWebhooks); | |||
@@ -35,6 +35,7 @@ namespace Discord | |||
DeafenMembers = 0x00_80_00_00, | |||
MoveMembers = 0x01_00_00_00, | |||
UseVAD = 0x02_00_00_00, | |||
PrioritySpeaker = 0x00_00_01_00, | |||
// General 2 | |||
ChangeNickname = 0x04_00_00_00, | |||
@@ -12,7 +12,7 @@ namespace Discord | |||
/// <summary> Gets a GuildPermissions that grants all guild permissions for webhook users. </summary> | |||
public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); | |||
/// <summary> Gets a GuildPermissions that grants all guild permissions. </summary> | |||
public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_1111111110011_111111); | |||
public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_1111111110111_111111); | |||
/// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> | |||
public ulong RawValue { get; } | |||
@@ -69,6 +69,8 @@ namespace Discord | |||
public bool MoveMembers => Permissions.GetValue(RawValue, GuildPermission.MoveMembers); | |||
/// <summary> If True, a user may use voice-activity-detection rather than push-to-talk. </summary> | |||
public bool UseVAD => Permissions.GetValue(RawValue, GuildPermission.UseVAD); | |||
/// <summary> If True, a user may use priority speaker in a voice channel. </summary> | |||
public bool PrioritySpeaker => Permissions.GetValue(RawValue, ChannelPermission.PrioritySpeaker); | |||
/// <summary> If True, a user may change their own nickname. </summary> | |||
public bool ChangeNickname => Permissions.GetValue(RawValue, GuildPermission.ChangeNickname); | |||
@@ -108,6 +110,7 @@ namespace Discord | |||
bool? deafenMembers = null, | |||
bool? moveMembers = null, | |||
bool? useVoiceActivation = null, | |||
bool? prioritySpeaker = null, | |||
bool? changeNickname = null, | |||
bool? manageNicknames = null, | |||
bool? manageRoles = null, | |||
@@ -139,6 +142,7 @@ namespace Discord | |||
Permissions.SetValue(ref value, deafenMembers, GuildPermission.DeafenMembers); | |||
Permissions.SetValue(ref value, moveMembers, GuildPermission.MoveMembers); | |||
Permissions.SetValue(ref value, useVoiceActivation, GuildPermission.UseVAD); | |||
Permissions.SetValue(ref value, prioritySpeaker, GuildPermission.PrioritySpeaker); | |||
Permissions.SetValue(ref value, changeNickname, GuildPermission.ChangeNickname); | |||
Permissions.SetValue(ref value, manageNicknames, GuildPermission.ManageNicknames); | |||
Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles); | |||
@@ -173,6 +177,7 @@ namespace Discord | |||
bool deafenMembers = false, | |||
bool moveMembers = false, | |||
bool useVoiceActivation = false, | |||
bool prioritySpeaker = false, | |||
bool changeNickname = false, | |||
bool manageNicknames = false, | |||
bool manageRoles = false, | |||
@@ -203,6 +208,7 @@ namespace Discord | |||
deafenMembers: deafenMembers, | |||
moveMembers: moveMembers, | |||
useVoiceActivation: useVoiceActivation, | |||
prioritySpeaker: prioritySpeaker, | |||
changeNickname: changeNickname, | |||
manageNicknames: manageNicknames, | |||
manageWebhooks: manageWebhooks, | |||
@@ -234,6 +240,7 @@ namespace Discord | |||
bool? deafenMembers = null, | |||
bool? moveMembers = null, | |||
bool? useVoiceActivation = null, | |||
bool? prioritySpeaker = null, | |||
bool? changeNickname = null, | |||
bool? manageNicknames = null, | |||
bool? manageRoles = null, | |||
@@ -242,7 +249,7 @@ namespace Discord | |||
=> new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, | |||
viewAuditLog, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, | |||
readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, | |||
useVoiceActivation, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); | |||
useVoiceActivation, prioritySpeaker, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); | |||
public bool Has(GuildPermission permission) => Permissions.GetValue(RawValue, permission); | |||
@@ -0,0 +1,11 @@ | |||
namespace Discord | |||
{ | |||
public static class MessageExtensions | |||
{ | |||
public static string GetJumpUrl(this IMessage msg) | |||
{ | |||
var channel = msg.Channel; | |||
return $"https://discordapp.com/channels/{(channel is IDMChannel ? "@me" : $"{(channel as ITextChannel).GuildId}")}/{channel.Id}/{msg.Id}"; | |||
} | |||
} | |||
} |
@@ -27,7 +27,7 @@ namespace Discord | |||
Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null); | |||
Task<IInvite> GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null); | |||
Task<IInvite> GetInviteAsync(string inviteId, RequestOptions options = null); | |||
Task<IUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
Task<IUser> GetUserAsync(string username, string discriminator, RequestOptions options = null); | |||
@@ -8,7 +8,7 @@ namespace Discord.API | |||
[JsonProperty("code")] | |||
public string Code { get; set; } | |||
[JsonProperty("guild")] | |||
public InviteGuild Guild { get; set; } | |||
public Optional<InviteGuild> Guild { get; set; } | |||
[JsonProperty("channel")] | |||
public InviteChannel Channel { get; set; } | |||
[JsonProperty("approximate_presence_count")] | |||
@@ -1,4 +1,4 @@ | |||
#pragma warning disable CS1591 | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
@@ -10,6 +10,6 @@ namespace Discord.API | |||
[JsonProperty("name")] | |||
public string Name { get; set; } | |||
[JsonProperty("type")] | |||
public string Type { get; set; } | |||
public int Type { get; set; } | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
#pragma warning disable CS1591 | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
using System; | |||
@@ -9,15 +9,15 @@ namespace Discord.API | |||
[JsonProperty("inviter")] | |||
public User Inviter { get; set; } | |||
[JsonProperty("uses")] | |||
public int Uses { get; set; } | |||
public Optional<int> Uses { get; set; } | |||
[JsonProperty("max_uses")] | |||
public int MaxUses { get; set; } | |||
public Optional<int> MaxUses { get; set; } | |||
[JsonProperty("max_age")] | |||
public int MaxAge { get; set; } | |||
public Optional<int> MaxAge { get; set; } | |||
[JsonProperty("temporary")] | |||
public bool Temporary { get; set; } | |||
[JsonProperty("created_at")] | |||
public DateTimeOffset CreatedAt { get; set; } | |||
public Optional<DateTimeOffset> CreatedAt { get; set; } | |||
[JsonProperty("revoked")] | |||
public bool Revoked { get; set; } | |||
} | |||
@@ -1,7 +0,0 @@ | |||
namespace Discord.API.Rest | |||
{ | |||
internal class GetInviteParams | |||
{ | |||
public Optional<bool?> WithCounts { get; set; } | |||
} | |||
} |
@@ -148,7 +148,7 @@ namespace Discord.Rest | |||
Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IConnection>>(ImmutableArray.Create<IConnection>()); | |||
Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) | |||
Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
=> Task.FromResult<IInvite>(null); | |||
Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
@@ -51,13 +51,9 @@ namespace Discord.Rest | |||
} | |||
public static async Task<RestInviteMetadata> GetInviteAsync(BaseDiscordClient client, | |||
string inviteId, bool withCount, RequestOptions options) | |||
string inviteId, RequestOptions options) | |||
{ | |||
var args = new GetInviteParams | |||
{ | |||
WithCounts = withCount | |||
}; | |||
var model = await client.ApiClient.GetInviteAsync(inviteId, args, options).ConfigureAwait(false); | |||
var model = await client.ApiClient.GetInviteAsync(inviteId, options).ConfigureAwait(false); | |||
if (model != null) | |||
return RestInviteMetadata.Create(client, null, null, model); | |||
return null; | |||
@@ -906,7 +906,7 @@ namespace Discord.API | |||
} | |||
//Guild Invites | |||
public async Task<InviteMetadata> GetInviteAsync(string inviteId, GetInviteParams args, RequestOptions options = null) | |||
public async Task<InviteMetadata> GetInviteAsync(string inviteId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
@@ -919,14 +919,20 @@ namespace Discord.API | |||
if (index >= 0) | |||
inviteId = inviteId.Substring(index + 1); | |||
var withCounts = args.WithCounts.GetValueOrDefault(false); | |||
try | |||
{ | |||
return await SendAsync<InviteMetadata>("GET", () => $"invites/{inviteId}?with_counts={withCounts}", new BucketIds(), options: options).ConfigureAwait(false); | |||
return await SendAsync<InviteMetadata>("GET", () => $"invites/{inviteId}?with_counts=true", new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } | |||
} | |||
public async Task<InviteMetadata> GetVanityInviteAsync(ulong guildId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var ids = new BucketIds(guildId: guildId); | |||
return await SendAsync<InviteMetadata>("GET", () => $"guilds/{guildId}/vanity-url", ids, options: options).ConfigureAwait(false); | |||
} | |||
public async Task<IReadOnlyCollection<InviteMetadata>> GetGuildInvitesAsync(ulong guildId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
@@ -56,8 +56,8 @@ namespace Discord.Rest | |||
=> ClientHelper.GetConnectionsAsync(this, options); | |||
/// <inheritdoc /> | |||
public Task<RestInviteMetadata> GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) | |||
=> ClientHelper.GetInviteAsync(this, inviteId, withCount, options); | |||
public Task<RestInviteMetadata> GetInviteAsync(string inviteId, RequestOptions options = null) | |||
=> ClientHelper.GetInviteAsync(this, inviteId, options); | |||
/// <inheritdoc /> | |||
public Task<RestGuild> GetGuildAsync(ulong id, RequestOptions options = null) | |||
@@ -131,8 +131,8 @@ namespace Discord.Rest | |||
async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
=> await GetConnectionsAsync(options).ConfigureAwait(false); | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) | |||
=> await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
=> await GetInviteAsync(inviteId, options).ConfigureAwait(false); | |||
async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
@@ -0,0 +1,18 @@ | |||
namespace Discord.Rest | |||
{ | |||
public struct MemberInfo | |||
{ | |||
internal MemberInfo(string nick, bool? deaf, bool? mute, string avatar_hash) | |||
{ | |||
Nickname = nick; | |||
Deaf = deaf; | |||
Mute = mute; | |||
AvatarHash = avatar_hash; | |||
} | |||
public string Nickname { get; } | |||
public bool? Deaf { get; } | |||
public bool? Mute { get; } | |||
public string AvatarHash { get; } | |||
} | |||
} |
@@ -8,28 +8,42 @@ namespace Discord.Rest | |||
{ | |||
public class MemberUpdateAuditLogData : IAuditLogData | |||
{ | |||
private MemberUpdateAuditLogData(IUser target, string newNick, string oldNick) | |||
private MemberUpdateAuditLogData(IUser target, MemberInfo before, MemberInfo after) | |||
{ | |||
Target = target; | |||
NewNick = newNick; | |||
OldNick = oldNick; | |||
Before = before; | |||
After = after; | |||
} | |||
internal static MemberUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | |||
{ | |||
var changes = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "nick"); | |||
var changes = entry.Changes; | |||
var newNick = changes.NewValue?.ToObject<string>(); | |||
var oldNick = changes.OldValue?.ToObject<string>(); | |||
var nickModel = changes.FirstOrDefault(x => x.ChangedProperty == "nick"); | |||
var deafModel = changes.FirstOrDefault(x => x.ChangedProperty == "deaf"); | |||
var muteModel = changes.FirstOrDefault(x => x.ChangedProperty == "mute"); | |||
var avatarModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); | |||
string oldNick = nickModel?.OldValue?.ToObject<string>(), | |||
newNick = nickModel?.NewValue?.ToObject<string>(); | |||
bool? oldDeaf = deafModel?.OldValue?.ToObject<bool>(), | |||
newDeaf = deafModel?.NewValue?.ToObject<bool>(); | |||
bool? oldMute = muteModel?.OldValue?.ToObject<bool>(), | |||
newMute = muteModel?.NewValue?.ToObject<bool>(); | |||
string oldAvatar = avatarModel?.OldValue?.ToObject<string>(), | |||
newAvatar = avatarModel?.NewValue?.ToObject<string>(); | |||
var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||
var user = RestUser.Create(discord, targetInfo); | |||
return new MemberUpdateAuditLogData(user, newNick, oldNick); | |||
var before = new MemberInfo(oldNick, oldDeaf, oldMute, oldAvatar); | |||
var after = new MemberInfo(newNick, newDeaf, newMute, newAvatar); | |||
return new MemberUpdateAuditLogData(user, before, after); | |||
} | |||
public IUser Target { get; } | |||
public string NewNick { get; } | |||
public string OldNick { get; } | |||
public MemberInfo Before { get; } | |||
public MemberInfo After { get; } | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using System.Linq; | |||
using System; | |||
using System.Linq; | |||
using Model = Discord.API.AuditLog; | |||
using EntryModel = Discord.API.AuditLogEntry; | |||
@@ -26,6 +27,8 @@ namespace Discord.Rest | |||
return new RestAuditLogEntry(discord, fullLog, model, user); | |||
} | |||
/// <inheritdoc/> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
/// <inheritdoc/> | |||
public ActionType Action { get; } | |||
/// <inheritdoc/> | |||
@@ -210,6 +210,12 @@ namespace Discord.Rest | |||
var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id, options).ConfigureAwait(false); | |||
return models.Select(x => RestInviteMetadata.Create(client, guild, null, x)).ToImmutableArray(); | |||
} | |||
public static async Task<RestInviteMetadata> GetVanityInviteAsync(IGuild guild, BaseDiscordClient client, | |||
RequestOptions options) | |||
{ | |||
var model = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); | |||
return RestInviteMetadata.Create(client, guild, null, model); | |||
} | |||
//Roles | |||
public static async Task<RestRole> CreateRoleAsync(IGuild guild, BaseDiscordClient client, | |||
@@ -238,6 +238,15 @@ namespace Discord.Rest | |||
//Invites | |||
public Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) | |||
=> GuildHelper.GetInvitesAsync(this, Discord, options); | |||
/// <summary> | |||
/// Gets the vanity invite URL of this guild. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A partial metadata of the vanity invite found within this guild. | |||
/// </returns> | |||
public Task<RestInviteMetadata> GetVanityInviteAsync(RequestOptions options = null) | |||
=> GuildHelper.GetVanityInviteAsync(this, Discord, options); | |||
//Roles | |||
public RestRole GetRole(ulong id) | |||
@@ -397,6 +406,9 @@ namespace Discord.Rest | |||
async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync(RequestOptions options) | |||
=> await GetInvitesAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IInviteMetadata> IGuild.GetVanityInviteAsync(RequestOptions options) | |||
=> await GetVanityInviteAsync(options).ConfigureAwait(false); | |||
IRole IGuild.GetRole(ulong id) | |||
=> GetRole(id); | |||
@@ -433,7 +445,7 @@ namespace Discord.Rest | |||
} | |||
Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } | |||
async Task<IReadOnlyCollection<IAuditLogEntry>> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) | |||
async Task<IReadOnlyCollection<IAuditLogEntry>> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options) | |||
{ | |||
if (cacheMode == CacheMode.AllowDownload) | |||
return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); | |||
@@ -1,7 +1,6 @@ | |||
using System; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Discord.API.Rest; | |||
using Model = Discord.API.Invite; | |||
namespace Discord.Rest | |||
@@ -9,14 +8,15 @@ namespace Discord.Rest | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestInvite : RestEntity<string>, IInvite, IUpdateable | |||
{ | |||
public ChannelType ChannelType { get; private set; } | |||
public string ChannelName { get; private set; } | |||
public string GuildName { get; private set; } | |||
public int? PresenceCount { get; private set; } | |||
public int? MemberCount { get; private set; } | |||
public ulong ChannelId { get; private set; } | |||
public ulong GuildId { get; private set; } | |||
internal IChannel Channel { get; private set; } | |||
internal IGuild Guild { get; private set; } | |||
public ulong? GuildId { get; private set; } | |||
internal IChannel Channel { get; } | |||
internal IGuild Guild { get; } | |||
public string Code => Id; | |||
public string Url => $"{DiscordConfig.InviteUrl}{Code}"; | |||
@@ -35,20 +35,18 @@ namespace Discord.Rest | |||
} | |||
internal void Update(Model model) | |||
{ | |||
GuildId = model.Guild.Id; | |||
GuildId = model.Guild.IsSpecified ? model.Guild.Value.Id : default(ulong?); | |||
ChannelId = model.Channel.Id; | |||
GuildName = model.Guild.Name; | |||
GuildName = model.Guild.IsSpecified ? model.Guild.Value.Name : null; | |||
ChannelName = model.Channel.Name; | |||
MemberCount = model.MemberCount.IsSpecified ? model.MemberCount.Value : null; | |||
PresenceCount = model.PresenceCount.IsSpecified ? model.PresenceCount.Value : null; | |||
ChannelType = (ChannelType)model.Channel.Type; | |||
} | |||
public async Task UpdateAsync(RequestOptions options = null) | |||
{ | |||
var args = new GetInviteParams(); | |||
if (MemberCount != null || PresenceCount != null) | |||
args.WithCounts = true; | |||
var model = await Discord.ApiClient.GetInviteAsync(Code, args, options).ConfigureAwait(false); | |||
var model = await Discord.ApiClient.GetInviteAsync(Code, options).ConfigureAwait(false); | |||
Update(model); | |||
} | |||
public Task DeleteAsync(RequestOptions options = null) | |||
@@ -1,20 +1,20 @@ | |||
using System; | |||
using System; | |||
using Model = Discord.API.InviteMetadata; | |||
namespace Discord.Rest | |||
{ | |||
public class RestInviteMetadata : RestInvite, IInviteMetadata | |||
{ | |||
private long _createdAtTicks; | |||
private long? _createdAtTicks; | |||
public bool IsRevoked { get; private set; } | |||
public bool IsTemporary { get; private set; } | |||
public int? MaxAge { get; private set; } | |||
public int? MaxUses { get; private set; } | |||
public int Uses { get; private set; } | |||
public int? Uses { get; private set; } | |||
public RestUser Inviter { get; private set; } | |||
public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); | |||
public DateTimeOffset? CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); | |||
internal RestInviteMetadata(BaseDiscordClient discord, IGuild guild, IChannel channel, string id) | |||
: base(discord, guild, channel, id) | |||
@@ -32,10 +32,10 @@ namespace Discord.Rest | |||
Inviter = model.Inviter != null ? RestUser.Create(Discord, model.Inviter) : null; | |||
IsRevoked = model.Revoked; | |||
IsTemporary = model.Temporary; | |||
MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null; | |||
MaxUses = model.MaxUses; | |||
Uses = model.Uses; | |||
_createdAtTicks = model.CreatedAt.UtcTicks; | |||
MaxAge = model.MaxAge.IsSpecified ? model.MaxAge.Value : (int?)null; | |||
MaxUses = model.MaxUses.IsSpecified ? model.MaxUses.Value : (int?)null; | |||
Uses = model.Uses.IsSpecified ? model.Uses.Value : (int?)null; | |||
_createdAtTicks = model.CreatedAt.IsSpecified ? model.CreatedAt.Value.UtcTicks : (long?)null; | |||
} | |||
IUser IInviteMetadata.Inviter => Inviter; | |||
@@ -55,8 +55,8 @@ namespace Discord.WebSocket | |||
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | |||
=> ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); | |||
/// <inheritdoc /> | |||
public Task<RestInviteMetadata> GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) | |||
=> ClientHelper.GetInviteAsync(this, inviteId, withCount, options ?? RequestOptions.Default); | |||
public Task<RestInviteMetadata> GetInviteAsync(string inviteId, RequestOptions options = null) | |||
=> ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); | |||
// IDiscordClient | |||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
@@ -70,8 +70,8 @@ namespace Discord.WebSocket | |||
async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
=> await GetConnectionsAsync(options).ConfigureAwait(false); | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) | |||
=> await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
=> await GetInviteAsync(inviteId, options).ConfigureAwait(false); | |||
Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IGuild>(GetGuild(id)); | |||
@@ -328,8 +328,8 @@ namespace Discord.WebSocket | |||
async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
=> await GetConnectionsAsync().ConfigureAwait(false); | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) | |||
=> await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
=> await GetInviteAsync(inviteId, options).ConfigureAwait(false); | |||
Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IGuild>(GetGuild(id)); | |||
@@ -1513,6 +1513,9 @@ namespace Discord.WebSocket | |||
case "MESSAGE_ACK": | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); | |||
break; | |||
case "PRESENCES_REPLACE": | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (PRESENCES_REPLACE)").ConfigureAwait(false); | |||
break; | |||
case "USER_SETTINGS_UPDATE": | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | |||
break; | |||
@@ -1819,8 +1822,8 @@ namespace Discord.WebSocket | |||
async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
=> await GetConnectionsAsync().ConfigureAwait(false); | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) | |||
=> await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); | |||
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
=> await GetInviteAsync(inviteId, options).ConfigureAwait(false); | |||
Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IGuild>(GetGuild(id)); | |||
@@ -345,6 +345,15 @@ namespace Discord.WebSocket | |||
//Invites | |||
public Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) | |||
=> GuildHelper.GetInvitesAsync(this, Discord, options); | |||
/// <summary> | |||
/// Gets the vanity invite URL of this guild. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A partial metadata of the vanity invite found within this guild. | |||
/// </returns> | |||
public Task<RestInviteMetadata> GetVanityInviteAsync(RequestOptions options = null) | |||
=> GuildHelper.GetVanityInviteAsync(this, Discord, options); | |||
//Roles | |||
public SocketRole GetRole(ulong id) | |||
@@ -700,6 +709,9 @@ namespace Discord.WebSocket | |||
async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync(RequestOptions options) | |||
=> await GetInvitesAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
async Task<IInviteMetadata> IGuild.GetVanityInviteAsync(RequestOptions options) | |||
=> await GetVanityInviteAsync(options).ConfigureAwait(false); | |||
IRole IGuild.GetRole(ulong id) | |||
=> GetRole(id); | |||
@@ -715,7 +727,7 @@ namespace Discord.WebSocket | |||
Task<IGuildUser> IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IGuildUser>(Owner); | |||
async Task<IReadOnlyCollection<IAuditLogEntry>> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) | |||
async Task<IReadOnlyCollection<IAuditLogEntry>> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options) | |||
{ | |||
if (cacheMode == CacheMode.AllowDownload) | |||
return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); | |||