| @@ -0,0 +1,16 @@ | |||||
| # Instructions for Building Documentation | |||||
| The documentation for the Discord.NET library uses [DocFX][docfx-main]. [Instructions for installing this tool can be found here.][docfx-installing] | |||||
| 1. Navigate to the root of the repository. | |||||
| 2. (Optional) If you intend to target a specific version, ensure that you | |||||
| have the correct version checked out. | |||||
| 3. Build the library. Run `dotnet build` in the root of this repository. | |||||
| Ensure that the build passes without errors. | |||||
| 4. Build the docs using `docfx .\docs\docfx.json`. Add the `--serve` parameter | |||||
| to preview the site locally. Some elements of the page may appear incorrect | |||||
| when not hosted by a server. | |||||
| - Remarks: According to the docfx website, this tool does work on Linux under mono. | |||||
| [docfx-main]: https://dotnet.github.io/docfx/ | |||||
| [docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html | |||||
| @@ -56,7 +56,7 @@ | |||||
| "default" | "default" | ||||
| ], | ], | ||||
| "globalMetadata":{ | "globalMetadata":{ | ||||
| "_appFooter":"Discord.Net (c) 2015-2018", | |||||
| "_appFooter":"Discord.Net (c) 2015-2018 2.0.0-beta", | |||||
| "_enableSearch":true | "_enableSearch":true | ||||
| }, | }, | ||||
| "noLangKeyword":false, | "noLangKeyword":false, | ||||
| @@ -13,6 +13,8 @@ namespace Discord | |||||
| public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); | public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); | ||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for voice channels. </summary> | /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for voice channels. </summary> | ||||
| public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001); | public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001); | ||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for category channels. </summary> | |||||
| public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); | |||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for direct message channels. </summary> | /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for direct message channels. </summary> | ||||
| public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000); | public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000); | ||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for group channels. </summary> | /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for group channels. </summary> | ||||
| @@ -24,6 +26,7 @@ namespace Discord | |||||
| { | { | ||||
| case ITextChannel _: return Text; | case ITextChannel _: return Text; | ||||
| case IVoiceChannel _: return Voice; | case IVoiceChannel _: return Voice; | ||||
| case ICategoryChannel _: return Category; | |||||
| case IDMChannel _: return DM; | case IDMChannel _: return DM; | ||||
| case IGroupChannel _: return Group; | case IGroupChannel _: return Group; | ||||
| default: throw new ArgumentException("Unknown channel type", nameof(channel)); | default: throw new ArgumentException("Unknown channel type", nameof(channel)); | ||||
| @@ -46,5 +46,8 @@ namespace Discord | |||||
| return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | ||||
| } | } | ||||
| #endif | #endif | ||||
| public static Task BanAsync(this IGuildUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) | |||||
| => user.Guild.AddBanAsync(user, pruneDays, reason, options); | |||||
| } | } | ||||
| } | } | ||||
| @@ -30,28 +30,24 @@ namespace Discord.API.Rest | |||||
| { | { | ||||
| var d = new Dictionary<string, object>(); | var d = new Dictionary<string, object>(); | ||||
| d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat")); | d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat")); | ||||
| var payload = new Dictionary<string, object>(); | |||||
| if (Content.IsSpecified) | if (Content.IsSpecified) | ||||
| d["content"] = Content.Value; | |||||
| payload["content"] = Content.Value; | |||||
| if (IsTTS.IsSpecified) | if (IsTTS.IsSpecified) | ||||
| d["tts"] = IsTTS.Value.ToString(); | |||||
| payload["tts"] = IsTTS.Value.ToString(); | |||||
| if (Nonce.IsSpecified) | if (Nonce.IsSpecified) | ||||
| d["nonce"] = Nonce.Value; | |||||
| payload["nonce"] = Nonce.Value; | |||||
| if (Embed.IsSpecified) | if (Embed.IsSpecified) | ||||
| { | |||||
| var payload = new StringBuilder(); | |||||
| using (var text = new StringWriter(payload)) | |||||
| using (var writer = new JsonTextWriter(text)) | |||||
| { | |||||
| var map = new Dictionary<string, object>() | |||||
| { | |||||
| ["embed"] = Embed.Value, | |||||
| }; | |||||
| _serializer.Serialize(writer, map); | |||||
| } | |||||
| d["payload_json"] = payload.ToString(); | |||||
| } | |||||
| payload["embed"] = Embed.Value; | |||||
| var json = new StringBuilder(); | |||||
| using (var text = new StringWriter(json)) | |||||
| using (var writer = new JsonTextWriter(text)) | |||||
| _serializer.Serialize(writer, payload); | |||||
| d["payload_json"] = json.ToString(); | |||||
| return d; | return d; | ||||
| } | } | ||||
| } | } | ||||
| @@ -470,7 +470,7 @@ namespace Discord.API | |||||
| if (!args.Embed.IsSpecified || args.Embed.Value == null) | if (!args.Embed.IsSpecified || args.Embed.Value == null) | ||||
| Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | ||||
| if (args.Content.Length > DiscordConfig.MaxMessageSize) | |||||
| if (args.Content?.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| @@ -487,7 +487,7 @@ namespace Discord.API | |||||
| if (!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) | if (!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) | ||||
| Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | ||||
| if (args.Content.Length > DiscordConfig.MaxMessageSize) | |||||
| if (args.Content?.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| @@ -568,7 +568,7 @@ namespace Discord.API | |||||
| { | { | ||||
| if (!args.Embed.IsSpecified) | if (!args.Embed.IsSpecified) | ||||
| Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | ||||
| if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) | |||||
| if (args.Content.Value?.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | ||||
| } | } | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| @@ -180,7 +180,7 @@ namespace Discord.Rest | |||||
| public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
| Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | ||||
| { | { | ||||
| var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() }; | |||||
| var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed != null ? embed.ToModel() : Optional<API.Embed>.Unspecified }; | |||||
| var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
| return RestUserMessage.Create(client, channel, client.CurrentUser, model); | return RestUserMessage.Create(client, channel, client.CurrentUser, model); | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| using Discord.API.Rest; | |||||
| using Discord.API.Rest; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| @@ -13,6 +13,9 @@ namespace Discord.Rest | |||||
| public static async Task<Model> ModifyAsync(IMessage msg, BaseDiscordClient client, Action<MessageProperties> func, | public static async Task<Model> ModifyAsync(IMessage msg, BaseDiscordClient client, Action<MessageProperties> func, | ||||
| RequestOptions options) | RequestOptions options) | ||||
| { | { | ||||
| if (msg.Author.Id != client.CurrentUser.Id) | |||||
| throw new InvalidOperationException("Only the author of a message may change it."); | |||||
| var args = new MessageProperties(); | var args = new MessageProperties(); | ||||
| func(args); | func(args); | ||||
| var apiArgs = new API.Rest.ModifyMessageParams | var apiArgs = new API.Rest.ModifyMessageParams | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| @@ -15,10 +15,12 @@ namespace Discord.WebSocket | |||||
| public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel | public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel | ||||
| { | { | ||||
| public override IReadOnlyCollection<SocketGuildUser> Users | public override IReadOnlyCollection<SocketGuildUser> Users | ||||
| => Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray(); | |||||
| => Guild.Users.Where(x => Permissions.GetValue( | |||||
| Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), | |||||
| ChannelPermission.ViewChannel)).ToImmutableArray(); | |||||
| public IReadOnlyCollection<SocketGuildChannel> Channels | public IReadOnlyCollection<SocketGuildChannel> Channels | ||||
| => Guild.Channels.Where(x => x.CategoryId == CategoryId).ToImmutableArray(); | |||||
| => Guild.Channels.Where(x => x.CategoryId == Id).ToImmutableArray(); | |||||
| internal SocketCategoryChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | internal SocketCategoryChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | ||||
| : base(discord, id, guild) | : base(discord, id, guild) | ||||
| @@ -31,14 +33,28 @@ namespace Discord.WebSocket | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| //Users | |||||
| public override SocketGuildUser GetUser(ulong id) | |||||
| { | |||||
| var user = Guild.GetUser(id); | |||||
| if (user != null) | |||||
| { | |||||
| var guildPerms = Permissions.ResolveGuild(Guild, user); | |||||
| var channelPerms = Permissions.ResolveChannel(Guild, user, this, guildPerms); | |||||
| if (Permissions.GetValue(channelPerms, ChannelPermission.ViewChannel)) | |||||
| return user; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| private string DebuggerDisplay => $"{Name} ({Id}, Category)"; | private string DebuggerDisplay => $"{Name} ({Id}, Category)"; | ||||
| internal new SocketCategoryChannel Clone() => MemberwiseClone() as SocketCategoryChannel; | internal new SocketCategoryChannel Clone() => MemberwiseClone() as SocketCategoryChannel; | ||||
| // IGuildChannel | // IGuildChannel | ||||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
| => throw new NotSupportedException(); | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | |||||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => throw new NotSupportedException(); | |||||
| => Task.FromResult<IGuildUser>(GetUser(id)); | |||||
| Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) | Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) | ||||
| => throw new NotSupportedException(); | => throw new NotSupportedException(); | ||||
| Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync(RequestOptions options) | Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync(RequestOptions options) | ||||
| @@ -46,8 +62,8 @@ namespace Discord.WebSocket | |||||
| //IChannel | //IChannel | ||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
| => throw new NotSupportedException(); | |||||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| => throw new NotSupportedException(); | |||||
| => Task.FromResult<IUser>(GetUser(id)); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| using Discord.Rest; | |||||
| using Discord.Rest; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||