Restructured to better merge REST and WebSocket entities
This commit is contained in:
14
README.md
14
README.md
@@ -5,9 +5,6 @@ An unofficial .Net API Wrapper for the Discord client (http://discordapp.com).
|
||||
|
||||
Check out the [documentation](http://rtd.discord.foxbot.me/en/docs-dev/index.html) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx).
|
||||
|
||||
##### Warning: Some of the documentation is outdated.
|
||||
It's current being rewritten. Until that's done, feel free to use my [DiscordBot](https://github.com/RogueException/DiscordBot) repo for reference.
|
||||
|
||||
### Installation
|
||||
You can download Discord.Net and its extensions from NuGet:
|
||||
- [Discord.Net](https://www.nuget.org/packages/Discord.Net/)
|
||||
@@ -16,9 +13,10 @@ You can download Discord.Net and its extensions from NuGet:
|
||||
- [Discord.Net.Audio](https://www.nuget.org/packages/Discord.Net.Audio/)
|
||||
|
||||
### Compiling
|
||||
In order to compile Discord.Net, you require at least the following:
|
||||
- [Visual Studio 2015](https://www.visualstudio.com/downloads/download-visual-studio-vs)
|
||||
- [Visual Studio 2015 Update 2](https://www.visualstudio.com/en-us/news/vs2015-update2-vs.aspx)
|
||||
- [Visual Studio .Net Core Plugin](https://www.microsoft.com/net/core#windows)
|
||||
In order to compile Discord.Net, you require the following:
|
||||
#### Visual Studio 2015
|
||||
- [VS2015 Update 2](https://www.visualstudio.com/en-us/news/vs2015-update2-vs.aspx)
|
||||
- [.Net Core SDK + VS Plugin](https://www.microsoft.com/net/core#windows)
|
||||
- NuGet 3.3+ (available through Visual Studio)
|
||||
|
||||
#### CLI
|
||||
- [.Net Core SDK](https://www.microsoft.com/net/core#windows)
|
||||
@@ -15,6 +15,6 @@ namespace Discord.API
|
||||
public bool Revoked { get; set; }
|
||||
|
||||
[JsonProperty("integrations")]
|
||||
public IEnumerable<ulong> Integrations { get; set; }
|
||||
public IReadOnlyCollection<ulong> Integrations { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ namespace Discord.API
|
||||
[JsonProperty("url")]
|
||||
public string StreamUrl { get; set; }
|
||||
[JsonProperty("type")]
|
||||
public StreamType StreamType { get; set; }
|
||||
public StreamType? StreamType { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
14
src/Discord.Net/API/Common/Presence.cs
Normal file
14
src/Discord.Net/API/Common/Presence.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API
|
||||
{
|
||||
public class Presence
|
||||
{
|
||||
[JsonProperty("user")]
|
||||
public User User { get; set; }
|
||||
[JsonProperty("status")]
|
||||
public UserStatus Status { get; set; }
|
||||
[JsonProperty("game")]
|
||||
public Game Game { get; set; }
|
||||
}
|
||||
}
|
||||
14
src/Discord.Net/API/Common/Relationship.cs
Normal file
14
src/Discord.Net/API/Common/Relationship.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API
|
||||
{
|
||||
public class Relationship
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public ulong Id { get; set; }
|
||||
[JsonProperty("user")]
|
||||
public User User { get; set; }
|
||||
[JsonProperty("type")]
|
||||
public RelationshipType Type { get; set; }
|
||||
}
|
||||
}
|
||||
9
src/Discord.Net/API/Common/RelationshipType.cs
Normal file
9
src/Discord.Net/API/Common/RelationshipType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Discord.API
|
||||
{
|
||||
public enum RelationshipType
|
||||
{
|
||||
Friend = 1,
|
||||
Blocked = 2,
|
||||
Pending = 4
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ namespace Discord.API
|
||||
{
|
||||
public class VoiceState
|
||||
{
|
||||
[JsonProperty("guild_id")]
|
||||
public ulong? GuildId { get; set; }
|
||||
[JsonProperty("channel_id")]
|
||||
public ulong ChannelId { get; set; }
|
||||
[JsonProperty("user_id")]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Discord.API.Gateway;
|
||||
using Discord.API.Rest;
|
||||
using Discord.Extensions;
|
||||
using Discord.Net;
|
||||
using Discord.Net.Converters;
|
||||
using Discord.Net.Queue;
|
||||
@@ -91,26 +92,17 @@ namespace Discord.API
|
||||
}
|
||||
}
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
public async Task Login(LoginParams args)
|
||||
|
||||
public async Task Login(TokenType tokenType, string token, RequestOptions options = null)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LoginInternal(TokenType.User, null, args, true).ConfigureAwait(false);
|
||||
await LoginInternal(tokenType, token, options).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
public async Task Login(TokenType tokenType, string token)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LoginInternal(tokenType, token, null, false).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task LoginInternal(TokenType tokenType, string token, LoginParams args, bool doLogin)
|
||||
private async Task LoginInternal(TokenType tokenType, string token, RequestOptions options = null)
|
||||
{
|
||||
if (LoginState != LoginState.LoggedOut)
|
||||
await LogoutInternal().ConfigureAwait(false);
|
||||
@@ -126,12 +118,6 @@ namespace Discord.API
|
||||
await _requestQueue.SetCancelToken(_loginCancelToken.Token).ConfigureAwait(false);
|
||||
_restClient.SetCancelToken(_loginCancelToken.Token);
|
||||
|
||||
if (doLogin)
|
||||
{
|
||||
var response = await Send<LoginResponse>("POST", "auth/login", args, GlobalBucket.Login).ConfigureAwait(false);
|
||||
token = response.Token;
|
||||
}
|
||||
|
||||
AuthTokenType = tokenType;
|
||||
_authToken = token;
|
||||
switch (tokenType)
|
||||
@@ -249,54 +235,71 @@ namespace Discord.API
|
||||
}
|
||||
|
||||
//Core
|
||||
public Task Send(string method, string endpoint, GlobalBucket bucket = GlobalBucket.General)
|
||||
=> SendInternal(method, endpoint, null, true, bucket);
|
||||
public Task Send(string method, string endpoint, object payload, GlobalBucket bucket = GlobalBucket.General)
|
||||
=> SendInternal(method, endpoint, payload, true, bucket);
|
||||
public Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary<string, string> multipartArgs, GlobalBucket bucket = GlobalBucket.General)
|
||||
=> SendInternal(method, endpoint, multipartArgs, true, bucket);
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, GlobalBucket bucket = GlobalBucket.General)
|
||||
public Task Send(string method, string endpoint,
|
||||
GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null)
|
||||
=> SendInternal(method, endpoint, null, true, bucket, options);
|
||||
public Task Send(string method, string endpoint, object payload,
|
||||
GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null)
|
||||
=> SendInternal(method, endpoint, payload, true, bucket, options);
|
||||
public Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary<string, string> multipartArgs,
|
||||
GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null)
|
||||
=> SendInternal(method, endpoint, multipartArgs, true, bucket, options);
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint,
|
||||
GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null)
|
||||
where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, null, false, bucket).ConfigureAwait(false));
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, object payload, GlobalBucket bucket = GlobalBucket.General)
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, null, false, bucket, options).ConfigureAwait(false));
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, object payload, GlobalBucket bucket =
|
||||
GlobalBucket.GeneralRest, RequestOptions options = null)
|
||||
where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, payload, false, bucket).ConfigureAwait(false));
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, Stream file, IReadOnlyDictionary<string, string> multipartArgs, GlobalBucket bucket = GlobalBucket.General)
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, payload, false, bucket, options).ConfigureAwait(false));
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, Stream file, IReadOnlyDictionary<string, string> multipartArgs,
|
||||
GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null)
|
||||
where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, multipartArgs, false, bucket).ConfigureAwait(false));
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, multipartArgs, false, bucket, options).ConfigureAwait(false));
|
||||
|
||||
public Task Send(string method, string endpoint, GuildBucket bucket, ulong guildId)
|
||||
=> SendInternal(method, endpoint, null, true, bucket, guildId);
|
||||
public Task Send(string method, string endpoint, object payload, GuildBucket bucket, ulong guildId)
|
||||
=> SendInternal(method, endpoint, payload, true, bucket, guildId);
|
||||
public Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary<string, string> multipartArgs, GuildBucket bucket, ulong guildId)
|
||||
=> SendInternal(method, endpoint, multipartArgs, true, bucket, guildId);
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, GuildBucket bucket, ulong guildId)
|
||||
public Task Send(string method, string endpoint,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
=> SendInternal(method, endpoint, null, true, bucket, guildId, options);
|
||||
public Task Send(string method, string endpoint, object payload,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
=> SendInternal(method, endpoint, payload, true, bucket, guildId, options);
|
||||
public Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary<string, string> multipartArgs,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
=> SendInternal(method, endpoint, multipartArgs, true, bucket, guildId, options);
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, null, false, bucket, guildId).ConfigureAwait(false));
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, object payload, GuildBucket bucket, ulong guildId)
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, null, false, bucket, guildId, options).ConfigureAwait(false));
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, object payload,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, payload, false, bucket, guildId).ConfigureAwait(false));
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, Stream file, IReadOnlyDictionary<string, string> multipartArgs, GuildBucket bucket, ulong guildId)
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, payload, false, bucket, guildId, options).ConfigureAwait(false));
|
||||
public async Task<TResponse> Send<TResponse>(string method, string endpoint, Stream file, IReadOnlyDictionary<string, string> multipartArgs,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, multipartArgs, false, bucket, guildId).ConfigureAwait(false));
|
||||
=> DeserializeJson<TResponse>(await SendInternal(method, endpoint, multipartArgs, false, bucket, guildId, options).ConfigureAwait(false));
|
||||
|
||||
private Task<Stream> SendInternal(string method, string endpoint, object payload, bool headerOnly, GlobalBucket bucket)
|
||||
=> SendInternal(method, endpoint, payload, headerOnly, BucketGroup.Global, (int)bucket, 0);
|
||||
private Task<Stream> SendInternal(string method, string endpoint, object payload, bool headerOnly, GuildBucket bucket, ulong guildId)
|
||||
=> SendInternal(method, endpoint, payload, headerOnly, BucketGroup.Guild, (int)bucket, guildId);
|
||||
private Task<Stream> SendInternal(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, bool headerOnly, GlobalBucket bucket)
|
||||
=> SendInternal(method, endpoint, multipartArgs, headerOnly, BucketGroup.Global, (int)bucket, 0);
|
||||
private Task<Stream> SendInternal(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, bool headerOnly, GuildBucket bucket, ulong guildId)
|
||||
=> SendInternal(method, endpoint, multipartArgs, headerOnly, BucketGroup.Guild, (int)bucket, guildId);
|
||||
private Task<Stream> SendInternal(string method, string endpoint, object payload, bool headerOnly,
|
||||
GlobalBucket bucket, RequestOptions options)
|
||||
=> SendInternal(method, endpoint, payload, headerOnly, BucketGroup.Global, (int)bucket, 0, options);
|
||||
private Task<Stream> SendInternal(string method, string endpoint, object payload, bool headerOnly,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options)
|
||||
=> SendInternal(method, endpoint, payload, headerOnly, BucketGroup.Guild, (int)bucket, guildId, options);
|
||||
private Task<Stream> SendInternal(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, bool headerOnly,
|
||||
GlobalBucket bucket, RequestOptions options)
|
||||
=> SendInternal(method, endpoint, multipartArgs, headerOnly, BucketGroup.Global, (int)bucket, 0, options);
|
||||
private Task<Stream> SendInternal(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, bool headerOnly,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options)
|
||||
=> SendInternal(method, endpoint, multipartArgs, headerOnly, BucketGroup.Guild, (int)bucket, guildId, options);
|
||||
|
||||
private async Task<Stream> SendInternal(string method, string endpoint, object payload, bool headerOnly, BucketGroup group, int bucketId, ulong guildId)
|
||||
private async Task<Stream> SendInternal(string method, string endpoint, object payload, bool headerOnly,
|
||||
BucketGroup group, int bucketId, ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
string json = null;
|
||||
if (payload != null)
|
||||
json = SerializeJson(payload);
|
||||
var responseStream = await _requestQueue.Send(new RestRequest(_restClient, method, endpoint, json, headerOnly), group, bucketId, guildId).ConfigureAwait(false);
|
||||
var responseStream = await _requestQueue.Send(new RestRequest(_restClient, method, endpoint, json, headerOnly, options), group, bucketId, guildId).ConfigureAwait(false);
|
||||
stopwatch.Stop();
|
||||
|
||||
double milliseconds = ToMilliseconds(stopwatch);
|
||||
@@ -304,10 +307,11 @@ namespace Discord.API
|
||||
|
||||
return responseStream;
|
||||
}
|
||||
private async Task<Stream> SendInternal(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, bool headerOnly, BucketGroup group, int bucketId, ulong guildId)
|
||||
private async Task<Stream> SendInternal(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, bool headerOnly,
|
||||
BucketGroup group, int bucketId, ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var responseStream = await _requestQueue.Send(new RestRequest(_restClient, method, endpoint, multipartArgs, headerOnly), group, bucketId, guildId).ConfigureAwait(false);
|
||||
var responseStream = await _requestQueue.Send(new RestRequest(_restClient, method, endpoint, multipartArgs, headerOnly, options), group, bucketId, guildId).ConfigureAwait(false);
|
||||
int bytes = headerOnly ? 0 : (int)responseStream.Length;
|
||||
stopwatch.Stop();
|
||||
|
||||
@@ -317,36 +321,36 @@ namespace Discord.API
|
||||
return responseStream;
|
||||
}
|
||||
|
||||
public Task SendGateway(GatewayOpCodes opCode, object payload, GlobalBucket bucket = GlobalBucket.Gateway)
|
||||
=> SendGateway((int)opCode, payload, BucketGroup.Global, (int)bucket, 0);
|
||||
public Task SendGateway(VoiceOpCodes opCode, object payload, GlobalBucket bucket = GlobalBucket.Gateway)
|
||||
=> SendGateway((int)opCode, payload, BucketGroup.Global, (int)bucket, 0);
|
||||
public Task SendGateway(GatewayOpCodes opCode, object payload, GuildBucket bucket, ulong guildId)
|
||||
=> SendGateway((int)opCode, payload, BucketGroup.Guild, (int)bucket, guildId);
|
||||
public Task SendGateway(VoiceOpCodes opCode, object payload, GuildBucket bucket, ulong guildId)
|
||||
=> SendGateway((int)opCode, payload, BucketGroup.Guild, (int)bucket, guildId);
|
||||
private async Task SendGateway(int opCode, object payload, BucketGroup group, int bucketId, ulong guildId)
|
||||
public Task SendGateway(GatewayOpCodes opCode, object payload,
|
||||
GlobalBucket bucket = GlobalBucket.GeneralGateway, RequestOptions options = null)
|
||||
=> SendGateway(opCode, payload, BucketGroup.Global, (int)bucket, 0, options);
|
||||
public Task SendGateway(GatewayOpCodes opCode, object payload,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
=> SendGateway(opCode, payload, BucketGroup.Guild, (int)bucket, guildId, options);
|
||||
private async Task SendGateway(GatewayOpCodes opCode, object payload,
|
||||
BucketGroup group, int bucketId, ulong guildId, RequestOptions options)
|
||||
{
|
||||
//TODO: Add ETF
|
||||
byte[] bytes = null;
|
||||
payload = new WebSocketMessage { Operation = opCode, Payload = payload };
|
||||
payload = new WebSocketMessage { Operation = (int)opCode, Payload = payload };
|
||||
if (payload != null)
|
||||
bytes = Encoding.UTF8.GetBytes(SerializeJson(payload));
|
||||
await _requestQueue.Send(new WebSocketRequest(_gatewayClient, bytes, true), group, bucketId, guildId).ConfigureAwait(false);
|
||||
await _requestQueue.Send(new WebSocketRequest(_gatewayClient, bytes, true, options), group, bucketId, guildId).ConfigureAwait(false);
|
||||
await SentGatewayMessage.Raise((int)opCode).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Auth
|
||||
public async Task ValidateToken()
|
||||
public async Task ValidateToken(RequestOptions options = null)
|
||||
{
|
||||
await Send("GET", "auth/login").ConfigureAwait(false);
|
||||
await Send("GET", "auth/login", options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Gateway
|
||||
public async Task<GetGatewayResponse> GetGateway()
|
||||
public async Task<GetGatewayResponse> GetGateway(RequestOptions options = null)
|
||||
{
|
||||
return await Send<GetGatewayResponse>("GET", "gateway").ConfigureAwait(false);
|
||||
return await Send<GetGatewayResponse>("GET", "gateway", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task SendIdentify(int largeThreshold = 100, bool useCompression = true)
|
||||
public async Task SendIdentify(int largeThreshold = 100, bool useCompression = true, RequestOptions options = null)
|
||||
{
|
||||
var props = new Dictionary<string, string>
|
||||
{
|
||||
@@ -359,74 +363,74 @@ namespace Discord.API
|
||||
LargeThreshold = largeThreshold,
|
||||
UseCompression = useCompression
|
||||
};
|
||||
await SendGateway(GatewayOpCodes.Identify, msg).ConfigureAwait(false);
|
||||
await SendGateway(GatewayOpCodes.Identify, msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Channels
|
||||
public async Task<Channel> GetChannel(ulong channelId)
|
||||
public async Task<Channel> GetChannel(ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
|
||||
try
|
||||
{
|
||||
return await Send<Channel>("GET", $"channels/{channelId}").ConfigureAwait(false);
|
||||
return await Send<Channel>("GET", $"channels/{channelId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; }
|
||||
}
|
||||
public async Task<Channel> GetChannel(ulong guildId, ulong channelId)
|
||||
public async Task<Channel> GetChannel(ulong guildId, ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
|
||||
try
|
||||
{
|
||||
var model = await Send<Channel>("GET", $"channels/{channelId}").ConfigureAwait(false);
|
||||
var model = await Send<Channel>("GET", $"channels/{channelId}", options: options).ConfigureAwait(false);
|
||||
if (model.GuildId != guildId)
|
||||
return null;
|
||||
return model;
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; }
|
||||
}
|
||||
public async Task<IEnumerable<Channel>> GetGuildChannels(ulong guildId)
|
||||
public async Task<IReadOnlyCollection<Channel>> GetGuildChannels(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<IEnumerable<Channel>>("GET", $"guilds/{guildId}/channels").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<Channel>>("GET", $"guilds/{guildId}/channels", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Channel> CreateGuildChannel(ulong guildId, CreateGuildChannelParams args)
|
||||
public async Task<Channel> CreateGuildChannel(ulong guildId, CreateGuildChannelParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate));
|
||||
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));
|
||||
|
||||
return await Send<Channel>("POST", $"guilds/{guildId}/channels", args).ConfigureAwait(false);
|
||||
return await Send<Channel>("POST", $"guilds/{guildId}/channels", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Channel> DeleteChannel(ulong channelId)
|
||||
public async Task<Channel> DeleteChannel(ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
|
||||
return await Send<Channel>("DELETE", $"channels/{channelId}").ConfigureAwait(false);
|
||||
return await Send<Channel>("DELETE", $"channels/{channelId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Channel> ModifyGuildChannel(ulong channelId, ModifyGuildChannelParams args)
|
||||
public async Task<Channel> ModifyGuildChannel(ulong channelId, ModifyGuildChannelParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.AtLeast(args.Position, 0, nameof(args.Position));
|
||||
Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name));
|
||||
|
||||
return await Send<Channel>("PATCH", $"channels/{channelId}", args).ConfigureAwait(false);
|
||||
return await Send<Channel>("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Channel> ModifyGuildChannel(ulong channelId, ModifyTextChannelParams args)
|
||||
public async Task<Channel> ModifyGuildChannel(ulong channelId, ModifyTextChannelParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.AtLeast(args.Position, 0, nameof(args.Position));
|
||||
Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name));
|
||||
|
||||
return await Send<Channel>("PATCH", $"channels/{channelId}", args).ConfigureAwait(false);
|
||||
return await Send<Channel>("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Channel> ModifyGuildChannel(ulong channelId, ModifyVoiceChannelParams args)
|
||||
public async Task<Channel> ModifyGuildChannel(ulong channelId, ModifyVoiceChannelParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
@@ -435,9 +439,9 @@ namespace Discord.API
|
||||
Preconditions.AtLeast(args.Position, 0, nameof(args.Position));
|
||||
Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name));
|
||||
|
||||
return await Send<Channel>("PATCH", $"channels/{channelId}", args).ConfigureAwait(false);
|
||||
return await Send<Channel>("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task ModifyGuildChannels(ulong guildId, IEnumerable<ModifyGuildChannelsParams> args)
|
||||
public async Task ModifyGuildChannels(ulong guildId, IEnumerable<ModifyGuildChannelsParams> args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
@@ -451,55 +455,55 @@ namespace Discord.API
|
||||
await ModifyGuildChannel(channels[0].Id, new ModifyGuildChannelParams { Position = channels[0].Position }).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
await Send("PATCH", $"guilds/{guildId}/channels", channels).ConfigureAwait(false);
|
||||
await Send("PATCH", $"guilds/{guildId}/channels", channels, options: options).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Channel Permissions
|
||||
public async Task ModifyChannelPermissions(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args)
|
||||
public async Task ModifyChannelPermissions(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
|
||||
await Send("PUT", $"channels/{channelId}/permissions/{targetId}", args).ConfigureAwait(false);
|
||||
await Send("PUT", $"channels/{channelId}/permissions/{targetId}", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task DeleteChannelPermission(ulong channelId, ulong targetId)
|
||||
public async Task DeleteChannelPermission(ulong channelId, ulong targetId, RequestOptions options = null)
|
||||
{
|
||||
await Send("DELETE", $"channels/{channelId}/permissions/{targetId}").ConfigureAwait(false);
|
||||
await Send("DELETE", $"channels/{channelId}/permissions/{targetId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Guilds
|
||||
public async Task<Guild> GetGuild(ulong guildId)
|
||||
public async Task<Guild> GetGuild(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
try
|
||||
{
|
||||
return await Send<Guild>("GET", $"guilds/{guildId}").ConfigureAwait(false);
|
||||
return await Send<Guild>("GET", $"guilds/{guildId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; }
|
||||
}
|
||||
public async Task<Guild> CreateGuild(CreateGuildParams args)
|
||||
public async Task<Guild> CreateGuild(CreateGuildParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));
|
||||
Preconditions.NotNullOrWhitespace(args.Region, nameof(args.Region));
|
||||
|
||||
return await Send<Guild>("POST", "guilds", args).ConfigureAwait(false);
|
||||
return await Send<Guild>("POST", "guilds", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Guild> DeleteGuild(ulong guildId)
|
||||
public async Task<Guild> DeleteGuild(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<Guild>("DELETE", $"guilds/{guildId}").ConfigureAwait(false);
|
||||
return await Send<Guild>("DELETE", $"guilds/{guildId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Guild> LeaveGuild(ulong guildId)
|
||||
public async Task<Guild> LeaveGuild(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<Guild>("DELETE", $"users/@me/guilds/{guildId}").ConfigureAwait(false);
|
||||
return await Send<Guild>("DELETE", $"users/@me/guilds/{guildId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Guild> ModifyGuild(ulong guildId, ModifyGuildParams args)
|
||||
public async Task<Guild> ModifyGuild(ulong guildId, ModifyGuildParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
@@ -510,91 +514,91 @@ namespace Discord.API
|
||||
Preconditions.NotNull(args.Region, nameof(args.Region));
|
||||
Preconditions.AtLeast(args.VerificationLevel, 0, nameof(args.VerificationLevel));
|
||||
|
||||
return await Send<Guild>("PATCH", $"guilds/{guildId}", args).ConfigureAwait(false);
|
||||
return await Send<Guild>("PATCH", $"guilds/{guildId}", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<GetGuildPruneCountResponse> BeginGuildPrune(ulong guildId, GuildPruneParams args)
|
||||
public async Task<GetGuildPruneCountResponse> BeginGuildPrune(ulong guildId, GuildPruneParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.AtLeast(args.Days, 0, nameof(args.Days));
|
||||
|
||||
return await Send<GetGuildPruneCountResponse>("POST", $"guilds/{guildId}/prune", args).ConfigureAwait(false);
|
||||
return await Send<GetGuildPruneCountResponse>("POST", $"guilds/{guildId}/prune", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<GetGuildPruneCountResponse> GetGuildPruneCount(ulong guildId, GuildPruneParams args)
|
||||
public async Task<GetGuildPruneCountResponse> GetGuildPruneCount(ulong guildId, GuildPruneParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.AtLeast(args.Days, 0, nameof(args.Days));
|
||||
|
||||
return await Send<GetGuildPruneCountResponse>("GET", $"guilds/{guildId}/prune", args).ConfigureAwait(false);
|
||||
return await Send<GetGuildPruneCountResponse>("GET", $"guilds/{guildId}/prune", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Guild Bans
|
||||
public async Task<IEnumerable<User>> GetGuildBans(ulong guildId)
|
||||
public async Task<IReadOnlyCollection<User>> GetGuildBans(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<IEnumerable<User>>("GET", $"guilds/{guildId}/bans").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<User>>("GET", $"guilds/{guildId}/bans", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task CreateGuildBan(ulong guildId, ulong userId, CreateGuildBanParams args)
|
||||
public async Task CreateGuildBan(ulong guildId, ulong userId, CreateGuildBanParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(userId, 0, nameof(userId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.AtLeast(args.PruneDays, 0, nameof(args.PruneDays));
|
||||
|
||||
await Send("PUT", $"guilds/{guildId}/bans/{userId}", args).ConfigureAwait(false);
|
||||
await Send("PUT", $"guilds/{guildId}/bans/{userId}", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task RemoveGuildBan(ulong guildId, ulong userId)
|
||||
public async Task RemoveGuildBan(ulong guildId, ulong userId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(userId, 0, nameof(userId));
|
||||
|
||||
await Send("DELETE", $"guilds/{guildId}/bans/{userId}").ConfigureAwait(false);
|
||||
await Send("DELETE", $"guilds/{guildId}/bans/{userId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Guild Embeds
|
||||
public async Task<GuildEmbed> GetGuildEmbed(ulong guildId)
|
||||
public async Task<GuildEmbed> GetGuildEmbed(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
try
|
||||
{
|
||||
return await Send<GuildEmbed>("GET", $"guilds/{guildId}/embed").ConfigureAwait(false);
|
||||
return await Send<GuildEmbed>("GET", $"guilds/{guildId}/embed", options: options).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; }
|
||||
}
|
||||
public async Task<GuildEmbed> ModifyGuildEmbed(ulong guildId, ModifyGuildEmbedParams args)
|
||||
public async Task<GuildEmbed> ModifyGuildEmbed(ulong guildId, ModifyGuildEmbedParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<GuildEmbed>("PATCH", $"guilds/{guildId}/embed", args).ConfigureAwait(false);
|
||||
return await Send<GuildEmbed>("PATCH", $"guilds/{guildId}/embed", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Guild Integrations
|
||||
public async Task<IEnumerable<Integration>> GetGuildIntegrations(ulong guildId)
|
||||
public async Task<IReadOnlyCollection<Integration>> GetGuildIntegrations(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<IEnumerable<Integration>>("GET", $"guilds/{guildId}/integrations").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<Integration>>("GET", $"guilds/{guildId}/integrations", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Integration> CreateGuildIntegration(ulong guildId, CreateGuildIntegrationParams args)
|
||||
public async Task<Integration> CreateGuildIntegration(ulong guildId, CreateGuildIntegrationParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.NotEqual(args.Id, 0, nameof(args.Id));
|
||||
|
||||
return await Send<Integration>("POST", $"guilds/{guildId}/integrations").ConfigureAwait(false);
|
||||
return await Send<Integration>("POST", $"guilds/{guildId}/integrations", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Integration> DeleteGuildIntegration(ulong guildId, ulong integrationId)
|
||||
public async Task<Integration> DeleteGuildIntegration(ulong guildId, ulong integrationId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(integrationId, 0, nameof(integrationId));
|
||||
|
||||
return await Send<Integration>("DELETE", $"guilds/{guildId}/integrations/{integrationId}").ConfigureAwait(false);
|
||||
return await Send<Integration>("DELETE", $"guilds/{guildId}/integrations/{integrationId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Integration> ModifyGuildIntegration(ulong guildId, ulong integrationId, ModifyGuildIntegrationParams args)
|
||||
public async Task<Integration> ModifyGuildIntegration(ulong guildId, ulong integrationId, ModifyGuildIntegrationParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(integrationId, 0, nameof(integrationId));
|
||||
@@ -602,74 +606,74 @@ namespace Discord.API
|
||||
Preconditions.AtLeast(args.ExpireBehavior, 0, nameof(args.ExpireBehavior));
|
||||
Preconditions.AtLeast(args.ExpireGracePeriod, 0, nameof(args.ExpireGracePeriod));
|
||||
|
||||
return await Send<Integration>("PATCH", $"guilds/{guildId}/integrations/{integrationId}", args).ConfigureAwait(false);
|
||||
return await Send<Integration>("PATCH", $"guilds/{guildId}/integrations/{integrationId}", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Integration> SyncGuildIntegration(ulong guildId, ulong integrationId)
|
||||
public async Task<Integration> SyncGuildIntegration(ulong guildId, ulong integrationId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(integrationId, 0, nameof(integrationId));
|
||||
|
||||
return await Send<Integration>("POST", $"guilds/{guildId}/integrations/{integrationId}/sync").ConfigureAwait(false);
|
||||
return await Send<Integration>("POST", $"guilds/{guildId}/integrations/{integrationId}/sync", options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Guild Invites
|
||||
public async Task<Invite> GetInvite(string inviteIdOrXkcd)
|
||||
public async Task<Invite> GetInvite(string inviteIdOrXkcd, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNullOrEmpty(inviteIdOrXkcd, nameof(inviteIdOrXkcd));
|
||||
|
||||
try
|
||||
{
|
||||
return await Send<Invite>("GET", $"invites/{inviteIdOrXkcd}").ConfigureAwait(false);
|
||||
return await Send<Invite>("GET", $"invites/{inviteIdOrXkcd}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; }
|
||||
}
|
||||
public async Task<IEnumerable<InviteMetadata>> GetGuildInvites(ulong guildId)
|
||||
public async Task<IReadOnlyCollection<InviteMetadata>> GetGuildInvites(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<IEnumerable<InviteMetadata>>("GET", $"guilds/{guildId}/invites").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<InviteMetadata>>("GET", $"guilds/{guildId}/invites", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<InviteMetadata[]> GetChannelInvites(ulong channelId)
|
||||
public async Task<InviteMetadata[]> GetChannelInvites(ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
|
||||
return await Send<InviteMetadata[]>("GET", $"channels/{channelId}/invites").ConfigureAwait(false);
|
||||
return await Send<InviteMetadata[]>("GET", $"channels/{channelId}/invites", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<InviteMetadata> CreateChannelInvite(ulong channelId, CreateChannelInviteParams args)
|
||||
public async Task<InviteMetadata> CreateChannelInvite(ulong channelId, CreateChannelInviteParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.AtLeast(args.MaxAge, 0, nameof(args.MaxAge));
|
||||
Preconditions.AtLeast(args.MaxUses, 0, nameof(args.MaxUses));
|
||||
|
||||
return await Send<InviteMetadata>("POST", $"channels/{channelId}/invites", args).ConfigureAwait(false);
|
||||
return await Send<InviteMetadata>("POST", $"channels/{channelId}/invites", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Invite> DeleteInvite(string inviteCode)
|
||||
public async Task<Invite> DeleteInvite(string inviteCode, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNullOrEmpty(inviteCode, nameof(inviteCode));
|
||||
|
||||
return await Send<Invite>("DELETE", $"invites/{inviteCode}").ConfigureAwait(false);
|
||||
return await Send<Invite>("DELETE", $"invites/{inviteCode}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task AcceptInvite(string inviteCode)
|
||||
public async Task AcceptInvite(string inviteCode, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNullOrEmpty(inviteCode, nameof(inviteCode));
|
||||
|
||||
await Send("POST", $"invites/{inviteCode}").ConfigureAwait(false);
|
||||
await Send("POST", $"invites/{inviteCode}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Guild Members
|
||||
public async Task<GuildMember> GetGuildMember(ulong guildId, ulong userId)
|
||||
public async Task<GuildMember> GetGuildMember(ulong guildId, ulong userId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(userId, 0, nameof(userId));
|
||||
|
||||
try
|
||||
{
|
||||
return await Send<GuildMember>("GET", $"guilds/{guildId}/members/{userId}").ConfigureAwait(false);
|
||||
return await Send<GuildMember>("GET", $"guilds/{guildId}/members/{userId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; }
|
||||
}
|
||||
public async Task<IEnumerable<GuildMember>> GetGuildMembers(ulong guildId, GetGuildMembersParams args)
|
||||
public async Task<IReadOnlyCollection<GuildMember>> GetGuildMembers(ulong guildId, GetGuildMembersParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
@@ -689,7 +693,7 @@ namespace Discord.API
|
||||
{
|
||||
int runLimit = (limit >= DiscordConfig.MaxUsersPerBatch) ? DiscordConfig.MaxUsersPerBatch : limit;
|
||||
string endpoint = $"guilds/{guildId}/members?limit={runLimit}&offset={offset}";
|
||||
var models = await Send<GuildMember[]>("GET", endpoint).ConfigureAwait(false);
|
||||
var models = await Send<GuildMember[]>("GET", endpoint, options: options).ConfigureAwait(false);
|
||||
|
||||
//Was this an empty batch?
|
||||
if (models.Length == 0) break;
|
||||
@@ -704,49 +708,49 @@ namespace Discord.API
|
||||
}
|
||||
|
||||
if (result.Count > 1)
|
||||
return result.SelectMany(x => x);
|
||||
return result.SelectMany(x => x).ToImmutableArray();
|
||||
else if (result.Count == 1)
|
||||
return result[0];
|
||||
else
|
||||
return Array.Empty<GuildMember>();
|
||||
return ImmutableArray.Create<GuildMember>();
|
||||
}
|
||||
public async Task RemoveGuildMember(ulong guildId, ulong userId)
|
||||
public async Task RemoveGuildMember(ulong guildId, ulong userId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(userId, 0, nameof(userId));
|
||||
|
||||
await Send("DELETE", $"guilds/{guildId}/members/{userId}").ConfigureAwait(false);
|
||||
await Send("DELETE", $"guilds/{guildId}/members/{userId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task ModifyGuildMember(ulong guildId, ulong userId, ModifyGuildMemberParams args)
|
||||
public async Task ModifyGuildMember(ulong guildId, ulong userId, ModifyGuildMemberParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(userId, 0, nameof(userId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
|
||||
await Send("PATCH", $"guilds/{guildId}/members/{userId}", args, GuildBucket.ModifyMember, guildId).ConfigureAwait(false);
|
||||
await Send("PATCH", $"guilds/{guildId}/members/{userId}", args, GuildBucket.ModifyMember, guildId, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Guild Roles
|
||||
public async Task<IEnumerable<Role>> GetGuildRoles(ulong guildId)
|
||||
public async Task<IReadOnlyCollection<Role>> GetGuildRoles(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<IEnumerable<Role>>("GET", $"guilds/{guildId}/roles").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<Role>>("GET", $"guilds/{guildId}/roles", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Role> CreateGuildRole(ulong guildId)
|
||||
public async Task<Role> CreateGuildRole(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<Role>("POST", $"guilds/{guildId}/roles").ConfigureAwait(false);
|
||||
return await Send<Role>("POST", $"guilds/{guildId}/roles", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task DeleteGuildRole(ulong guildId, ulong roleId)
|
||||
public async Task DeleteGuildRole(ulong guildId, ulong roleId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(roleId, 0, nameof(roleId));
|
||||
|
||||
await Send("DELETE", $"guilds/{guildId}/roles/{roleId}").ConfigureAwait(false);
|
||||
await Send("DELETE", $"guilds/{guildId}/roles/{roleId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Role> ModifyGuildRole(ulong guildId, ulong roleId, ModifyGuildRoleParams args)
|
||||
public async Task<Role> ModifyGuildRole(ulong guildId, ulong roleId, ModifyGuildRoleParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotEqual(roleId, 0, nameof(roleId));
|
||||
@@ -755,9 +759,9 @@ namespace Discord.API
|
||||
Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name));
|
||||
Preconditions.AtLeast(args.Position, 0, nameof(args.Position));
|
||||
|
||||
return await Send<Role>("PATCH", $"guilds/{guildId}/roles/{roleId}", args).ConfigureAwait(false);
|
||||
return await Send<Role>("PATCH", $"guilds/{guildId}/roles/{roleId}", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<IEnumerable<Role>> ModifyGuildRoles(ulong guildId, IEnumerable<ModifyGuildRolesParams> args)
|
||||
public async Task<IReadOnlyCollection<Role>> ModifyGuildRoles(ulong guildId, IEnumerable<ModifyGuildRolesParams> args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
@@ -766,16 +770,31 @@ namespace Discord.API
|
||||
switch (roles.Length)
|
||||
{
|
||||
case 0:
|
||||
return Array.Empty<Role>();
|
||||
return ImmutableArray.Create<Role>();
|
||||
case 1:
|
||||
return ImmutableArray.Create(await ModifyGuildRole(guildId, roles[0].Id, roles[0]).ConfigureAwait(false));
|
||||
default:
|
||||
return await Send<IEnumerable<Role>>("PATCH", $"guilds/{guildId}/roles", args).ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<Role>>("PATCH", $"guilds/{guildId}/roles", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
//Messages
|
||||
public async Task<IEnumerable<Message>> GetChannelMessages(ulong channelId, GetChannelMessagesParams args)
|
||||
public async Task<Message> GetChannelMessage(ulong channelId, ulong messageId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotEqual(messageId, 0, nameof(messageId));
|
||||
|
||||
//TODO: Improve when Discord adds support
|
||||
var msgs = await GetChannelMessages(channelId, new GetChannelMessagesParams { Limit = 1, RelativeDirection = Direction.Before, RelativeMessageId = messageId + 1 }).ConfigureAwait(false);
|
||||
return msgs.FirstOrDefault();
|
||||
|
||||
/*try
|
||||
{
|
||||
return await Send<Message>("GET", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; }*/
|
||||
}
|
||||
public async Task<IReadOnlyCollection<Message>> GetChannelMessages(ulong channelId, GetChannelMessagesParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
@@ -798,7 +817,7 @@ namespace Discord.API
|
||||
endpoint = $"channels/{channelId}/messages?limit={runCount}&{relativeDir}={relativeId}";
|
||||
else
|
||||
endpoint = $"channels/{channelId}/messages?limit={runCount}";
|
||||
var models = await Send<Message[]>("GET", endpoint).ConfigureAwait(false);
|
||||
var models = await Send<Message[]>("GET", endpoint, options: options).ConfigureAwait(false);
|
||||
|
||||
//Was this an empty batch?
|
||||
if (models.Length == 0) break;
|
||||
@@ -813,26 +832,26 @@ namespace Discord.API
|
||||
if (i > 1)
|
||||
{
|
||||
if (args.RelativeDirection == Direction.Before)
|
||||
return result.Take(i).SelectMany(x => x);
|
||||
return result.Take(i).SelectMany(x => x).ToImmutableArray();
|
||||
else
|
||||
return result.Take(i).Reverse().SelectMany(x => x);
|
||||
return result.Take(i).Reverse().SelectMany(x => x).ToImmutableArray();
|
||||
}
|
||||
else if (i == 1)
|
||||
return result[0];
|
||||
else
|
||||
return Array.Empty<Message>();
|
||||
return ImmutableArray.Create<Message>();
|
||||
}
|
||||
public Task<Message> CreateMessage(ulong guildId, ulong channelId, CreateMessageParams args)
|
||||
public Task<Message> CreateMessage(ulong guildId, ulong channelId, CreateMessageParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return CreateMessageInternal(guildId, channelId, args);
|
||||
}
|
||||
public Task<Message> CreateDMMessage(ulong channelId, CreateMessageParams args)
|
||||
public Task<Message> CreateDMMessage(ulong channelId, CreateMessageParams args, RequestOptions options = null)
|
||||
{
|
||||
return CreateMessageInternal(0, channelId, args);
|
||||
}
|
||||
public async Task<Message> CreateMessageInternal(ulong guildId, ulong channelId, CreateMessageParams args)
|
||||
public async Task<Message> CreateMessageInternal(ulong guildId, ulong channelId, CreateMessageParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
@@ -841,21 +860,21 @@ namespace Discord.API
|
||||
throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content));
|
||||
|
||||
if (guildId != 0)
|
||||
return await Send<Message>("POST", $"channels/{channelId}/messages", args, GuildBucket.SendEditMessage, guildId).ConfigureAwait(false);
|
||||
return await Send<Message>("POST", $"channels/{channelId}/messages", args, GuildBucket.SendEditMessage, guildId, options: options).ConfigureAwait(false);
|
||||
else
|
||||
return await Send<Message>("POST", $"channels/{channelId}/messages", args, GlobalBucket.DirectMessage).ConfigureAwait(false);
|
||||
return await Send<Message>("POST", $"channels/{channelId}/messages", args, GlobalBucket.DirectMessage, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public Task<Message> UploadFile(ulong guildId, ulong channelId, Stream file, UploadFileParams args)
|
||||
public Task<Message> UploadFile(ulong guildId, ulong channelId, Stream file, UploadFileParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return UploadFileInternal(guildId, channelId, file, args);
|
||||
}
|
||||
public Task<Message> UploadDMFile(ulong channelId, Stream file, UploadFileParams args)
|
||||
public Task<Message> UploadDMFile(ulong channelId, Stream file, UploadFileParams args, RequestOptions options = null)
|
||||
{
|
||||
return UploadFileInternal(0, channelId, file, args);
|
||||
}
|
||||
private async Task<Message> UploadFileInternal(ulong guildId, ulong channelId, Stream file, UploadFileParams args)
|
||||
private async Task<Message> UploadFileInternal(ulong guildId, ulong channelId, Stream file, UploadFileParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
@@ -867,47 +886,49 @@ namespace Discord.API
|
||||
}
|
||||
|
||||
if (guildId != 0)
|
||||
return await Send<Message>("POST", $"channels/{channelId}/messages", file, args.ToDictionary(), GuildBucket.SendEditMessage, guildId).ConfigureAwait(false);
|
||||
return await Send<Message>("POST", $"channels/{channelId}/messages", file, args.ToDictionary(), GuildBucket.SendEditMessage, guildId, options: options).ConfigureAwait(false);
|
||||
else
|
||||
return await Send<Message>("POST", $"channels/{channelId}/messages", file, args.ToDictionary(), GlobalBucket.DirectMessage).ConfigureAwait(false);
|
||||
return await Send<Message>("POST", $"channels/{channelId}/messages", file, args.ToDictionary(), GlobalBucket.DirectMessage, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public Task DeleteMessage(ulong guildId, ulong channelId, ulong messageId)
|
||||
public Task DeleteMessage(ulong guildId, ulong channelId, ulong messageId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return DeleteMessageInternal(guildId, channelId, messageId);
|
||||
}
|
||||
public Task DeleteDMMessage(ulong channelId, ulong messageId)
|
||||
public Task DeleteDMMessage(ulong channelId, ulong messageId, RequestOptions options = null)
|
||||
{
|
||||
return DeleteMessageInternal(0, channelId, messageId);
|
||||
}
|
||||
private async Task DeleteMessageInternal(ulong guildId, ulong channelId, ulong messageId)
|
||||
private async Task DeleteMessageInternal(ulong guildId, ulong channelId, ulong messageId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotEqual(messageId, 0, nameof(messageId));
|
||||
|
||||
if (guildId != 0)
|
||||
await Send("DELETE", $"channels/{channelId}/messages/{messageId}", GuildBucket.DeleteMessage, guildId).ConfigureAwait(false);
|
||||
await Send("DELETE", $"channels/{channelId}/messages/{messageId}", GuildBucket.DeleteMessage, guildId, options: options).ConfigureAwait(false);
|
||||
else
|
||||
await Send("DELETE", $"channels/{channelId}/messages/{messageId}").ConfigureAwait(false);
|
||||
await Send("DELETE", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public Task DeleteMessages(ulong guildId, ulong channelId, DeleteMessagesParam args)
|
||||
public Task DeleteMessages(ulong guildId, ulong channelId, DeleteMessagesParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return DeleteMessagesInternal(guildId, channelId, args);
|
||||
}
|
||||
public Task DeleteDMMessages(ulong channelId, DeleteMessagesParam args)
|
||||
public Task DeleteDMMessages(ulong channelId, DeleteMessagesParams args, RequestOptions options = null)
|
||||
{
|
||||
return DeleteMessagesInternal(0, channelId, args);
|
||||
}
|
||||
private async Task DeleteMessagesInternal(ulong guildId, ulong channelId, DeleteMessagesParam args)
|
||||
private async Task DeleteMessagesInternal(ulong guildId, ulong channelId, DeleteMessagesParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.NotNull(args.MessageIds, nameof(args.MessageIds));
|
||||
|
||||
var messageIds = args.MessageIds.ToArray();
|
||||
var messageIds = args.MessageIds?.ToArray();
|
||||
Preconditions.NotNull(args.MessageIds, nameof(args.MessageIds));
|
||||
Preconditions.AtMost(messageIds.Length, 100, nameof(messageIds.Length));
|
||||
|
||||
switch (messageIds.Length)
|
||||
{
|
||||
case 0:
|
||||
@@ -917,23 +938,23 @@ namespace Discord.API
|
||||
break;
|
||||
default:
|
||||
if (guildId != 0)
|
||||
await Send("POST", $"channels/{channelId}/messages/bulk_delete", args, GuildBucket.DeleteMessages, guildId).ConfigureAwait(false);
|
||||
await Send("POST", $"channels/{channelId}/messages/bulk_delete", args, GuildBucket.DeleteMessages, guildId, options: options).ConfigureAwait(false);
|
||||
else
|
||||
await Send("POST", $"channels/{channelId}/messages/bulk_delete", args).ConfigureAwait(false);
|
||||
await Send("POST", $"channels/{channelId}/messages/bulk_delete", args, options: options).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
public Task<Message> ModifyMessage(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args)
|
||||
public Task<Message> ModifyMessage(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return ModifyMessageInternal(guildId, channelId, messageId, args);
|
||||
}
|
||||
public Task<Message> ModifyDMMessage(ulong channelId, ulong messageId, ModifyMessageParams args)
|
||||
public Task<Message> ModifyDMMessage(ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null)
|
||||
{
|
||||
return ModifyMessageInternal(0, channelId, messageId, args);
|
||||
}
|
||||
private async Task<Message> ModifyMessageInternal(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args)
|
||||
private async Task<Message> ModifyMessageInternal(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotEqual(messageId, 0, nameof(messageId));
|
||||
@@ -946,104 +967,103 @@ namespace Discord.API
|
||||
}
|
||||
|
||||
if (guildId != 0)
|
||||
return await Send<Message>("PATCH", $"channels/{channelId}/messages/{messageId}", args, GuildBucket.SendEditMessage, guildId).ConfigureAwait(false);
|
||||
return await Send<Message>("PATCH", $"channels/{channelId}/messages/{messageId}", args, GuildBucket.SendEditMessage, guildId, options: options).ConfigureAwait(false);
|
||||
else
|
||||
return await Send<Message>("PATCH", $"channels/{channelId}/messages/{messageId}", args).ConfigureAwait(false);
|
||||
return await Send<Message>("PATCH", $"channels/{channelId}/messages/{messageId}", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task AckMessage(ulong channelId, ulong messageId)
|
||||
public async Task AckMessage(ulong channelId, ulong messageId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
Preconditions.NotEqual(messageId, 0, nameof(messageId));
|
||||
|
||||
await Send("POST", $"channels/{channelId}/messages/{messageId}/ack").ConfigureAwait(false);
|
||||
await Send("POST", $"channels/{channelId}/messages/{messageId}/ack", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task TriggerTypingIndicator(ulong channelId)
|
||||
public async Task TriggerTypingIndicator(ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
|
||||
await Send("POST", $"channels/{channelId}/typing").ConfigureAwait(false);
|
||||
await Send("POST", $"channels/{channelId}/typing", options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Users
|
||||
public async Task<User> GetUser(ulong userId)
|
||||
public async Task<User> GetUser(ulong userId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(userId, 0, nameof(userId));
|
||||
|
||||
try
|
||||
{
|
||||
return await Send<User>("GET", $"users/{userId}").ConfigureAwait(false);
|
||||
return await Send<User>("GET", $"users/{userId}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; }
|
||||
}
|
||||
public async Task<User> GetUser(string username, ushort discriminator)
|
||||
public async Task<User> GetUser(string username, ushort discriminator, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNullOrEmpty(username, nameof(username));
|
||||
|
||||
try
|
||||
{
|
||||
var models = await QueryUsers($"{username}#{discriminator}", 1).ConfigureAwait(false);
|
||||
var models = await QueryUsers($"{username}#{discriminator}", 1, options: options).ConfigureAwait(false);
|
||||
return models.FirstOrDefault();
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; }
|
||||
}
|
||||
public async Task<IEnumerable<User>> QueryUsers(string query, int limit)
|
||||
public async Task<IReadOnlyCollection<User>> QueryUsers(string query, int limit, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNullOrEmpty(query, nameof(query));
|
||||
Preconditions.AtLeast(limit, 0, nameof(limit));
|
||||
|
||||
return await Send<IEnumerable<User>>("GET", $"users?q={Uri.EscapeDataString(query)}&limit={limit}").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<User>>("GET", $"users?q={Uri.EscapeDataString(query)}&limit={limit}", options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Current User/DMs
|
||||
public async Task<User> GetCurrentUser()
|
||||
public async Task<User> GetCurrentUser(RequestOptions options = null)
|
||||
{
|
||||
return await Send<User>("GET", "users/@me").ConfigureAwait(false);
|
||||
return await Send<User>("GET", "users/@me", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<IEnumerable<Connection>> GetCurrentUserConnections()
|
||||
public async Task<IReadOnlyCollection<Connection>> GetCurrentUserConnections(RequestOptions options = null)
|
||||
{
|
||||
return await Send<IEnumerable<Connection>>("GET", "users/@me/connections").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<Connection>>("GET", "users/@me/connections", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<IEnumerable<Channel>> GetCurrentUserDMs()
|
||||
public async Task<IReadOnlyCollection<Channel>> GetCurrentUserDMs(RequestOptions options = null)
|
||||
{
|
||||
return await Send<IEnumerable<Channel>>("GET", "users/@me/channels").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<Channel>>("GET", "users/@me/channels", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<IEnumerable<UserGuild>> GetCurrentUserGuilds()
|
||||
public async Task<IReadOnlyCollection<UserGuild>> GetCurrentUserGuilds(RequestOptions options = null)
|
||||
{
|
||||
return await Send<IEnumerable<UserGuild>>("GET", "users/@me/guilds").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<UserGuild>>("GET", "users/@me/guilds", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<User> ModifyCurrentUser(ModifyCurrentUserParams args)
|
||||
public async Task<User> ModifyCurrentUser(ModifyCurrentUserParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.NotNullOrEmpty(args.Email, nameof(args.Email));
|
||||
Preconditions.NotNullOrEmpty(args.Username, nameof(args.Username));
|
||||
|
||||
return await Send<User>("PATCH", "users/@me", args).ConfigureAwait(false);
|
||||
return await Send<User>("PATCH", "users/@me", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task ModifyCurrentUserNick(ulong guildId, ModifyCurrentUserNickParams args)
|
||||
public async Task ModifyCurrentUserNick(ulong guildId, ModifyCurrentUserNickParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.NotEmpty(args.Nickname, nameof(args.Nickname));
|
||||
|
||||
await Send("PATCH", $"guilds/{guildId}/members/@me/nick", args).ConfigureAwait(false);
|
||||
await Send("PATCH", $"guilds/{guildId}/members/@me/nick", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Channel> CreateDMChannel(CreateDMChannelParams args)
|
||||
public async Task<Channel> CreateDMChannel(CreateDMChannelParams args, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.NotEqual(args.RecipientId, 0, nameof(args.RecipientId));
|
||||
|
||||
return await Send<Channel>("POST", $"users/@me/channels", args).ConfigureAwait(false);
|
||||
return await Send<Channel>("POST", $"users/@me/channels", args, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Voice Regions
|
||||
public async Task<IEnumerable<VoiceRegion>> GetVoiceRegions()
|
||||
public async Task<IReadOnlyCollection<VoiceRegion>> GetVoiceRegions(RequestOptions options = null)
|
||||
{
|
||||
return await Send<IEnumerable<VoiceRegion>>("GET", "voice/regions").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<VoiceRegion>>("GET", "voice/regions", options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<IEnumerable<VoiceRegion>> GetGuildVoiceRegions(ulong guildId)
|
||||
public async Task<IReadOnlyCollection<VoiceRegion>> GetGuildVoiceRegions(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||
|
||||
return await Send<IEnumerable<VoiceRegion>>("GET", $"guilds/{guildId}/regions").ConfigureAwait(false);
|
||||
return await Send<IReadOnlyCollection<VoiceRegion>>("GET", $"guilds/{guildId}/regions", options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Helpers
|
||||
|
||||
24
src/Discord.Net/API/Gateway/Common/ExtendedGuild.cs
Normal file
24
src/Discord.Net/API/Gateway/Common/ExtendedGuild.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
{
|
||||
public class ExtendedGuild : Guild
|
||||
{
|
||||
[JsonProperty("unavailable")]
|
||||
public bool? Unavailable { get; set; }
|
||||
[JsonProperty("member_count")]
|
||||
public int MemberCount { get; set; }
|
||||
[JsonProperty("large")]
|
||||
public bool Large { get; set; }
|
||||
|
||||
[JsonProperty("presences")]
|
||||
public Presence[] Presences { get; set; }
|
||||
[JsonProperty("members")]
|
||||
public GuildMember[] Members { get; set; }
|
||||
[JsonProperty("channels")]
|
||||
public Channel[] Channels { get; set; }
|
||||
[JsonProperty("joined_at")]
|
||||
public DateTime JoinedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -12,13 +12,19 @@
|
||||
StatusUpdate = 3,
|
||||
/// <summary> C→S - Used to join a particular voice channel. </summary>
|
||||
VoiceStateUpdate = 4,
|
||||
/// <summary> C→S - Used to ensure the server's voice server is alive. Only send this if voice connection fails or suddenly drops. </summary>
|
||||
/// <summary> C→S - Used to ensure the guild's voice server is alive. </summary>
|
||||
VoiceServerPing = 5,
|
||||
/// <summary> C→S - Used to resume a connection after a redirect occurs. </summary>
|
||||
Resume = 6,
|
||||
/// <summary> C←S - Used to notify a client that they must reconnect to another gateway. </summary>
|
||||
Reconnect = 7,
|
||||
/// <summary> C→S - Used to request all members that were withheld by large_threshold </summary>
|
||||
RequestGuildMembers = 8
|
||||
RequestGuildMembers = 8,
|
||||
/// <summary> S→C - Used to notify the client that their session has expired and cannot be resumed. </summary>
|
||||
InvalidSession = 9,
|
||||
/// <summary> S→C - Used to provide information to the client immediately on connection. </summary>
|
||||
Hello = 10,
|
||||
/// <summary> S→C - Used to reply to a client's heartbeat. </summary>
|
||||
HeartbeatAck = 11
|
||||
}
|
||||
}
|
||||
|
||||
10
src/Discord.Net/API/Gateway/GuildBanEvent.cs
Normal file
10
src/Discord.Net/API/Gateway/GuildBanEvent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
{
|
||||
public class GuildBanEvent : User
|
||||
{
|
||||
[JsonProperty("guild_id")]
|
||||
public ulong GuildId { get; set; }
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/API/Gateway/GuildRoleDeleteEvent.cs
Normal file
12
src/Discord.Net/API/Gateway/GuildRoleDeleteEvent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
{
|
||||
public class GuildRoleDeleteEvent
|
||||
{
|
||||
[JsonProperty("guild_id")]
|
||||
public ulong GuildId { get; set; }
|
||||
[JsonProperty("role_id")]
|
||||
public ulong RoleId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,13 @@ namespace Discord.API.Gateway
|
||||
[JsonProperty("read_state")]
|
||||
public ReadState[] ReadStates { get; set; }
|
||||
[JsonProperty("guilds")]
|
||||
public Guild[] Guilds { get; set; }
|
||||
public ExtendedGuild[] Guilds { get; set; }
|
||||
[JsonProperty("private_channels")]
|
||||
public Channel[] PrivateChannels { get; set; }
|
||||
[JsonProperty("heartbeat_interval")]
|
||||
public int HeartbeatInterval { get; set; }
|
||||
[JsonProperty("relationships")]
|
||||
public Relationship[] Relationships { get; set; }
|
||||
|
||||
//Ignored
|
||||
[JsonProperty("user_settings")]
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Discord.API.Rest
|
||||
{
|
||||
public class DeleteMessagesParam
|
||||
public class DeleteMessagesParams
|
||||
{
|
||||
[JsonProperty("messages")]
|
||||
public IEnumerable<ulong> MessageIds { get; set; }
|
||||
@@ -1,12 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rest
|
||||
{
|
||||
public class LoginParams
|
||||
{
|
||||
[JsonProperty("email")]
|
||||
public string Email { get; set; }
|
||||
[JsonProperty("password")]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rest
|
||||
{
|
||||
public class LoginResponse
|
||||
{
|
||||
[JsonProperty("token")]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Discord.Net.Converters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace Discord.API.Rest
|
||||
@@ -8,12 +7,6 @@ namespace Discord.API.Rest
|
||||
{
|
||||
[JsonProperty("username")]
|
||||
public Optional<string> Username { get; set; }
|
||||
[JsonProperty("email")]
|
||||
public Optional<string> Email { get; set; }
|
||||
[JsonProperty("password")]
|
||||
public Optional<string> Password { get; set; }
|
||||
[JsonProperty("new_password")]
|
||||
public Optional<string> NewPassword { get; set; }
|
||||
[JsonProperty("avatar"), Image]
|
||||
public Optional<Stream> Avatar { get; set; }
|
||||
}
|
||||
|
||||
4
src/Discord.Net/Data/DataStoreProvider.cs
Normal file
4
src/Discord.Net/Data/DataStoreProvider.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
namespace Discord.Data
|
||||
{
|
||||
public delegate DataStore DataStoreProvider(int shardId, int totalShards, int guildCount, int dmCount);
|
||||
}
|
||||
90
src/Discord.Net/Data/DefaultDataStore.cs
Normal file
90
src/Discord.Net/Data/DefaultDataStore.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Discord.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord.Data
|
||||
{
|
||||
public class DefaultDataStore : DataStore
|
||||
{
|
||||
private const double AverageChannelsPerGuild = 10.22; //Source: Googie2149
|
||||
private const double AverageUsersPerGuild = 47.78; //Source: Googie2149
|
||||
private const double CollectionMultiplier = 1.05; //Add buffer to handle growth
|
||||
private const double CollectionConcurrencyLevel = 1; //WebSocket updater/event handler. //TODO: Needs profiling, increase to 2?
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, ICachedChannel> _channels;
|
||||
private readonly ConcurrentDictionary<ulong, CachedGuild> _guilds;
|
||||
private readonly ConcurrentDictionary<ulong, CachedPublicUser> _users;
|
||||
|
||||
internal override IReadOnlyCollection<ICachedChannel> Channels => _channels.ToReadOnlyCollection();
|
||||
internal override IReadOnlyCollection<CachedGuild> Guilds => _guilds.ToReadOnlyCollection();
|
||||
internal override IReadOnlyCollection<CachedPublicUser> Users => _users.ToReadOnlyCollection();
|
||||
|
||||
public DefaultDataStore(int guildCount, int dmChannelCount)
|
||||
{
|
||||
double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount;
|
||||
double estimatedUsersCount = guildCount * AverageUsersPerGuild;
|
||||
_channels = new ConcurrentDictionary<ulong, ICachedChannel>(1, (int)(estimatedChannelCount * CollectionMultiplier));
|
||||
_guilds = new ConcurrentDictionary<ulong, CachedGuild>(1, (int)(guildCount * CollectionMultiplier));
|
||||
_users = new ConcurrentDictionary<ulong, CachedPublicUser>(1, (int)(estimatedUsersCount * CollectionMultiplier));
|
||||
}
|
||||
|
||||
internal override ICachedChannel GetChannel(ulong id)
|
||||
{
|
||||
ICachedChannel channel;
|
||||
if (_channels.TryGetValue(id, out channel))
|
||||
return channel;
|
||||
return null;
|
||||
}
|
||||
internal override void AddChannel(ICachedChannel channel)
|
||||
{
|
||||
_channels[channel.Id] = channel;
|
||||
}
|
||||
internal override ICachedChannel RemoveChannel(ulong id)
|
||||
{
|
||||
ICachedChannel channel;
|
||||
if (_channels.TryRemove(id, out channel))
|
||||
return channel;
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override CachedGuild GetGuild(ulong id)
|
||||
{
|
||||
CachedGuild guild;
|
||||
if (_guilds.TryGetValue(id, out guild))
|
||||
return guild;
|
||||
return null;
|
||||
}
|
||||
internal override void AddGuild(CachedGuild guild)
|
||||
{
|
||||
_guilds[guild.Id] = guild;
|
||||
}
|
||||
internal override CachedGuild RemoveGuild(ulong id)
|
||||
{
|
||||
CachedGuild guild;
|
||||
if (_guilds.TryRemove(id, out guild))
|
||||
return guild;
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override CachedPublicUser GetUser(ulong id)
|
||||
{
|
||||
CachedPublicUser user;
|
||||
if (_users.TryGetValue(id, out user))
|
||||
return user;
|
||||
return null;
|
||||
}
|
||||
internal override CachedPublicUser GetOrAddUser(ulong id, Func<ulong, CachedPublicUser> userFactory)
|
||||
{
|
||||
return _users.GetOrAdd(id, userFactory);
|
||||
}
|
||||
internal override CachedPublicUser RemoveUser(ulong id)
|
||||
{
|
||||
CachedPublicUser user;
|
||||
if (_users.TryRemove(id, out user))
|
||||
return user;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/Discord.Net/Data/IDataStore.cs
Normal file
24
src/Discord.Net/Data/IDataStore.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.Data
|
||||
{
|
||||
public abstract class DataStore
|
||||
{
|
||||
internal abstract IReadOnlyCollection<ICachedChannel> Channels { get; }
|
||||
internal abstract IReadOnlyCollection<CachedGuild> Guilds { get; }
|
||||
internal abstract IReadOnlyCollection<CachedPublicUser> Users { get; }
|
||||
|
||||
internal abstract ICachedChannel GetChannel(ulong id);
|
||||
internal abstract void AddChannel(ICachedChannel channel);
|
||||
internal abstract ICachedChannel RemoveChannel(ulong id);
|
||||
|
||||
internal abstract CachedGuild GetGuild(ulong id);
|
||||
internal abstract void AddGuild(CachedGuild guild);
|
||||
internal abstract CachedGuild RemoveGuild(ulong id);
|
||||
|
||||
internal abstract CachedPublicUser GetUser(ulong id);
|
||||
internal abstract CachedPublicUser GetOrAddUser(ulong userId, Func<ulong, CachedPublicUser> userFactory);
|
||||
internal abstract CachedPublicUser RemoveUser(ulong id);
|
||||
}
|
||||
}
|
||||
11
src/Discord.Net/Data/SharedDataStore.cs
Normal file
11
src/Discord.Net/Data/SharedDataStore.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Discord.Data
|
||||
{
|
||||
//TODO: Implement
|
||||
//TODO: CachedPublicUser's GuildCount system is not at all multi-writer threadsafe!
|
||||
//TODO: CachedPublicUser's Update method is not multi-writer threadsafe!
|
||||
//TODO: Are there other multiwriters across shards?
|
||||
|
||||
/*public class SharedDataStore
|
||||
{
|
||||
}*/
|
||||
}
|
||||
@@ -1,43 +1,38 @@
|
||||
using Discord.API.Rest;
|
||||
using Discord.Extensions;
|
||||
using Discord.Logging;
|
||||
using Discord.Net;
|
||||
using Discord.Net.Queue;
|
||||
using Discord.Net.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
//TODO: Docstrings
|
||||
//TODO: Log Internal/External REST Rate Limits, 502s
|
||||
//TODO: Log Logins/Logouts
|
||||
public sealed class DiscordClient : IDiscordClient, IDisposable
|
||||
public class DiscordClient : IDiscordClient
|
||||
{
|
||||
public event Func<LogMessage, Task> Log;
|
||||
public event Func<Task> LoggedIn, LoggedOut;
|
||||
|
||||
private readonly Logger _discordLogger, _restLogger;
|
||||
private readonly SemaphoreSlim _connectionLock;
|
||||
private readonly RestClientProvider _restClientProvider;
|
||||
private readonly LogManager _log;
|
||||
private readonly RequestQueue _requestQueue;
|
||||
private bool _isDisposed;
|
||||
private SelfUser _currentUser;
|
||||
internal readonly Logger _discordLogger, _restLogger;
|
||||
internal readonly SemaphoreSlim _connectionLock;
|
||||
internal readonly LogManager _log;
|
||||
internal readonly RequestQueue _requestQueue;
|
||||
internal bool _isDisposed;
|
||||
internal SelfUser _currentUser;
|
||||
|
||||
public LoginState LoginState { get; private set; }
|
||||
public API.DiscordApiClient ApiClient { get; private set; }
|
||||
|
||||
public IRequestQueue RequestQueue => _requestQueue;
|
||||
|
||||
public DiscordClient(DiscordConfig config = null)
|
||||
{
|
||||
if (config == null)
|
||||
config = new DiscordConfig();
|
||||
|
||||
|
||||
_log = new LogManager(config.LogLevel);
|
||||
_log.Message += async msg => await Log.Raise(msg).ConfigureAwait(false);
|
||||
_discordLogger = _log.CreateLogger("Discord");
|
||||
@@ -49,26 +44,17 @@ namespace Discord.Rest
|
||||
ApiClient = new API.DiscordApiClient(config.RestClientProvider, requestQueue: _requestQueue);
|
||||
ApiClient.SentRequest += async (method, endpoint, millis) => await _log.Verbose("Rest", $"{method} {endpoint}: {millis} ms").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task Login(string email, string password)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LoginInternal(TokenType.User, null, email, password, true, false).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
|
||||
public async Task Login(TokenType tokenType, string token, bool validateToken = true)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LoginInternal(tokenType, token, null, null, false, validateToken).ConfigureAwait(false);
|
||||
await LoginInternal(tokenType, token, validateToken).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task LoginInternal(TokenType tokenType, string token, string email, string password, bool useEmail, bool validateToken)
|
||||
private async Task LoginInternal(TokenType tokenType, string token, bool validateToken)
|
||||
{
|
||||
if (LoginState != LoginState.LoggedOut)
|
||||
await LogoutInternal().ConfigureAwait(false);
|
||||
@@ -76,13 +62,7 @@ namespace Discord.Rest
|
||||
|
||||
try
|
||||
{
|
||||
if (useEmail)
|
||||
{
|
||||
var args = new LoginParams { Email = email, Password = password };
|
||||
await ApiClient.Login(args).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ApiClient.Login(tokenType, token).ConfigureAwait(false);
|
||||
await ApiClient.Login(tokenType, token).ConfigureAwait(false);
|
||||
|
||||
if (validateToken)
|
||||
{
|
||||
@@ -96,6 +76,8 @@ namespace Discord.Rest
|
||||
}
|
||||
}
|
||||
|
||||
await OnLogin().ConfigureAwait(false);
|
||||
|
||||
LoginState = LoginState.LoggedIn;
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -106,6 +88,7 @@ namespace Discord.Rest
|
||||
|
||||
await LoggedIn.Raise().ConfigureAwait(false);
|
||||
}
|
||||
protected virtual Task OnLogin() => Task.CompletedTask;
|
||||
|
||||
public async Task Logout()
|
||||
{
|
||||
@@ -122,6 +105,8 @@ namespace Discord.Rest
|
||||
LoginState = LoginState.LoggingOut;
|
||||
|
||||
await ApiClient.Logout().ConfigureAwait(false);
|
||||
|
||||
await OnLogout().ConfigureAwait(false);
|
||||
|
||||
_currentUser = null;
|
||||
|
||||
@@ -129,14 +114,15 @@ namespace Discord.Rest
|
||||
|
||||
await LoggedOut.Raise().ConfigureAwait(false);
|
||||
}
|
||||
protected virtual Task OnLogout() => Task.CompletedTask;
|
||||
|
||||
public async Task<IEnumerable<Connection>> GetConnections()
|
||||
public async Task<IReadOnlyCollection<IConnection>> GetConnections()
|
||||
{
|
||||
var models = await ApiClient.GetCurrentUserConnections().ConfigureAwait(false);
|
||||
return models.Select(x => new Connection(x));
|
||||
return models.Select(x => new Connection(x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public async Task<IChannel> GetChannel(ulong id)
|
||||
public virtual async Task<IChannel> GetChannel(ulong id)
|
||||
{
|
||||
var model = await ApiClient.GetChannel(id).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
@@ -151,17 +137,17 @@ namespace Discord.Rest
|
||||
}
|
||||
}
|
||||
else
|
||||
return new DMChannel(this, model);
|
||||
return new DMChannel(this, new User(this, model.Recipient), model);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public async Task<IEnumerable<DMChannel>> GetDMChannels()
|
||||
public virtual async Task<IReadOnlyCollection<IDMChannel>> GetDMChannels()
|
||||
{
|
||||
var models = await ApiClient.GetCurrentUserDMs().ConfigureAwait(false);
|
||||
return models.Select(x => new DMChannel(this, x));
|
||||
return models.Select(x => new DMChannel(this, new User(this, x.Recipient), x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public async Task<Invite> GetInvite(string inviteIdOrXkcd)
|
||||
public virtual async Task<IInvite> GetInvite(string inviteIdOrXkcd)
|
||||
{
|
||||
var model = await ApiClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
@@ -169,48 +155,48 @@ namespace Discord.Rest
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<Guild> GetGuild(ulong id)
|
||||
public virtual async Task<IGuild> GetGuild(ulong id)
|
||||
{
|
||||
var model = await ApiClient.GetGuild(id).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
return new Guild(this, model);
|
||||
return null;
|
||||
}
|
||||
public async Task<GuildEmbed> GetGuildEmbed(ulong id)
|
||||
public virtual async Task<GuildEmbed?> GetGuildEmbed(ulong id)
|
||||
{
|
||||
var model = await ApiClient.GetGuildEmbed(id).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
return new GuildEmbed(model);
|
||||
return null;
|
||||
}
|
||||
public async Task<IEnumerable<UserGuild>> GetGuilds()
|
||||
public virtual async Task<IReadOnlyCollection<IUserGuild>> GetGuilds()
|
||||
{
|
||||
var models = await ApiClient.GetCurrentUserGuilds().ConfigureAwait(false);
|
||||
return models.Select(x => new UserGuild(this, x));
|
||||
return models.Select(x => new UserGuild(this, x)).ToImmutableArray();
|
||||
|
||||
}
|
||||
public async Task<Guild> CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null)
|
||||
public virtual async Task<IGuild> CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null)
|
||||
{
|
||||
var args = new CreateGuildParams();
|
||||
var model = await ApiClient.CreateGuild(args).ConfigureAwait(false);
|
||||
return new Guild(this, model);
|
||||
}
|
||||
|
||||
public async Task<User> GetUser(ulong id)
|
||||
public virtual async Task<IUser> GetUser(ulong id)
|
||||
{
|
||||
var model = await ApiClient.GetUser(id).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
return new PublicUser(this, model);
|
||||
return new User(this, model);
|
||||
return null;
|
||||
}
|
||||
public async Task<User> GetUser(string username, ushort discriminator)
|
||||
public virtual async Task<IUser> GetUser(string username, ushort discriminator)
|
||||
{
|
||||
var model = await ApiClient.GetUser(username, discriminator).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
return new PublicUser(this, model);
|
||||
return new User(this, model);
|
||||
return null;
|
||||
}
|
||||
public async Task<SelfUser> GetCurrentUser()
|
||||
public virtual async Task<ISelfUser> GetCurrentUser()
|
||||
{
|
||||
var user = _currentUser;
|
||||
if (user == null)
|
||||
@@ -221,60 +207,32 @@ namespace Discord.Rest
|
||||
}
|
||||
return user;
|
||||
}
|
||||
public async Task<IEnumerable<User>> QueryUsers(string query, int limit)
|
||||
public virtual async Task<IReadOnlyCollection<IUser>> QueryUsers(string query, int limit)
|
||||
{
|
||||
var models = await ApiClient.QueryUsers(query, limit).ConfigureAwait(false);
|
||||
return models.Select(x => new PublicUser(this, x));
|
||||
return models.Select(x => new User(this, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<VoiceRegion>> GetVoiceRegions()
|
||||
public virtual async Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegions()
|
||||
{
|
||||
var models = await ApiClient.GetVoiceRegions().ConfigureAwait(false);
|
||||
return models.Select(x => new VoiceRegion(x));
|
||||
return models.Select(x => new VoiceRegion(x)).ToImmutableArray();
|
||||
}
|
||||
public async Task<VoiceRegion> GetVoiceRegion(string id)
|
||||
public virtual async Task<IVoiceRegion> GetVoiceRegion(string id)
|
||||
{
|
||||
var models = await ApiClient.GetVoiceRegions().ConfigureAwait(false);
|
||||
return models.Select(x => new VoiceRegion(x)).Where(x => x.Id == id).FirstOrDefault();
|
||||
}
|
||||
|
||||
void Dispose(bool disposing)
|
||||
internal void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
_isDisposed = true;
|
||||
}
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
|
||||
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected;
|
||||
WebSocket.Data.IDataStore IDiscordClient.DataStore => null;
|
||||
|
||||
Task IDiscordClient.Connect() { return Task.FromException(new NotSupportedException("This client does not support websocket connections.")); }
|
||||
Task IDiscordClient.Disconnect() { return Task.FromException(new NotSupportedException("This client does not support websocket connections.")); }
|
||||
async Task<IChannel> IDiscordClient.GetChannel(ulong id)
|
||||
=> await GetChannel(id).ConfigureAwait(false);
|
||||
async Task<IEnumerable<IDMChannel>> IDiscordClient.GetDMChannels()
|
||||
=> await GetDMChannels().ConfigureAwait(false);
|
||||
async Task<IEnumerable<IConnection>> IDiscordClient.GetConnections()
|
||||
=> await GetConnections().ConfigureAwait(false);
|
||||
async Task<IInvite> IDiscordClient.GetInvite(string inviteIdOrXkcd)
|
||||
=> await GetInvite(inviteIdOrXkcd).ConfigureAwait(false);
|
||||
async Task<IGuild> IDiscordClient.GetGuild(ulong id)
|
||||
=> await GetGuild(id).ConfigureAwait(false);
|
||||
async Task<IEnumerable<IUserGuild>> IDiscordClient.GetGuilds()
|
||||
=> await GetGuilds().ConfigureAwait(false);
|
||||
async Task<IGuild> IDiscordClient.CreateGuild(string name, IVoiceRegion region, Stream jpegIcon)
|
||||
=> await CreateGuild(name, region, jpegIcon).ConfigureAwait(false);
|
||||
async Task<IUser> IDiscordClient.GetUser(ulong id)
|
||||
=> await GetUser(id).ConfigureAwait(false);
|
||||
async Task<IUser> IDiscordClient.GetUser(string username, ushort discriminator)
|
||||
=> await GetUser(username, discriminator).ConfigureAwait(false);
|
||||
async Task<ISelfUser> IDiscordClient.GetCurrentUser()
|
||||
=> await GetCurrentUser().ConfigureAwait(false);
|
||||
async Task<IEnumerable<IUser>> IDiscordClient.QueryUsers(string query, int limit)
|
||||
=> await QueryUsers(query, limit).ConfigureAwait(false);
|
||||
async Task<IEnumerable<IVoiceRegion>> IDiscordClient.GetVoiceRegions()
|
||||
=> await GetVoiceRegions().ConfigureAwait(false);
|
||||
async Task<IVoiceRegion> IDiscordClient.GetVoiceRegion(string id)
|
||||
=> await GetVoiceRegion(id).ConfigureAwait(false);
|
||||
Task IDiscordClient.Connect() { throw new NotSupportedException(); }
|
||||
Task IDiscordClient.Disconnect() { throw new NotSupportedException(); }
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace Discord
|
||||
public static string Version { get; } = typeof(DiscordConfig).GetTypeInfo().Assembly?.GetName().Version.ToString(3) ?? "Unknown";
|
||||
public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})";
|
||||
|
||||
public const int GatewayAPIVersion = 3; //TODO: Upgrade to 4
|
||||
public const int GatewayAPIVersion = 5;
|
||||
public const string GatewayEncoding = "json";
|
||||
|
||||
public const string ClientAPIUrl = "https://discordapp.com/api/";
|
||||
|
||||
708
src/Discord.Net/DiscordSocketClient.cs
Normal file
708
src/Discord.Net/DiscordSocketClient.cs
Normal file
@@ -0,0 +1,708 @@
|
||||
using Discord.API;
|
||||
using Discord.API.Gateway;
|
||||
using Discord.Data;
|
||||
using Discord.Extensions;
|
||||
using Discord.Logging;
|
||||
using Discord.Net.Converters;
|
||||
using Discord.Net.WebSockets;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
//TODO: Remove unnecessary `as` casts
|
||||
//TODO: Add docstrings
|
||||
public class DiscordSocketClient : DiscordClient, IDiscordClient
|
||||
{
|
||||
public event Func<Task> Connected, Disconnected;
|
||||
public event Func<Task> Ready;
|
||||
//public event Func<Channel> VoiceConnected, VoiceDisconnected;
|
||||
/*public event Func<IChannel, Task> ChannelCreated, ChannelDestroyed;
|
||||
public event Func<IChannel, IChannel, Task> ChannelUpdated;
|
||||
public event Func<IMessage, Task> MessageReceived, MessageDeleted;
|
||||
public event Func<IMessage, IMessage, Task> MessageUpdated;
|
||||
public event Func<IRole, Task> RoleCreated, RoleDeleted;
|
||||
public event Func<IRole, IRole, Task> RoleUpdated;
|
||||
public event Func<IGuild, Task> JoinedGuild, LeftGuild, GuildAvailable, GuildUnavailable;
|
||||
public event Func<IGuild, IGuild, Task> GuildUpdated;
|
||||
public event Func<IUser, Task> UserJoined, UserLeft, UserBanned, UserUnbanned;
|
||||
public event Func<IUser, IUser, Task> UserUpdated;
|
||||
public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated;
|
||||
public event Func<IChannel, IUser, Task> UserIsTyping;*/
|
||||
|
||||
private readonly ConcurrentQueue<ulong> _largeGuilds;
|
||||
private readonly Logger _gatewayLogger;
|
||||
private readonly DataStoreProvider _dataStoreProvider;
|
||||
private readonly JsonSerializer _serializer;
|
||||
private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay;
|
||||
private readonly bool _enablePreUpdateEvents;
|
||||
private readonly int _largeThreshold;
|
||||
private readonly int _totalShards;
|
||||
private ImmutableDictionary<string, VoiceRegion> _voiceRegions;
|
||||
private string _sessionId;
|
||||
|
||||
public int ShardId { get; }
|
||||
public ConnectionState ConnectionState { get; private set; }
|
||||
public IWebSocketClient GatewaySocket { get; private set; }
|
||||
internal int MessageCacheSize { get; private set; }
|
||||
//internal bool UsePermissionCache { get; private set; }
|
||||
internal DataStore DataStore { get; private set; }
|
||||
|
||||
internal CachedSelfUser CurrentUser => _currentUser as CachedSelfUser;
|
||||
internal IReadOnlyCollection<CachedGuild> Guilds
|
||||
{
|
||||
get
|
||||
{
|
||||
var guilds = DataStore.Guilds;
|
||||
return guilds.Select(x => x as CachedGuild).ToReadOnlyCollection(guilds);
|
||||
}
|
||||
}
|
||||
internal IReadOnlyCollection<CachedDMChannel> DMChannels
|
||||
{
|
||||
get
|
||||
{
|
||||
var users = DataStore.Users;
|
||||
return users.Select(x => (x as CachedPublicUser).DMChannel).Where(x => x != null).ToReadOnlyCollection(users);
|
||||
}
|
||||
}
|
||||
internal IReadOnlyCollection<VoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection();
|
||||
|
||||
public DiscordSocketClient(DiscordSocketConfig config = null)
|
||||
{
|
||||
if (config == null)
|
||||
config = new DiscordSocketConfig();
|
||||
|
||||
ShardId = config.ShardId;
|
||||
_totalShards = config.TotalShards;
|
||||
|
||||
_connectionTimeout = config.ConnectionTimeout;
|
||||
_reconnectDelay = config.ReconnectDelay;
|
||||
_failedReconnectDelay = config.FailedReconnectDelay;
|
||||
_dataStoreProvider = config.DataStoreProvider;
|
||||
|
||||
MessageCacheSize = config.MessageCacheSize;
|
||||
//UsePermissionCache = config.UsePermissionsCache;
|
||||
_enablePreUpdateEvents = config.EnablePreUpdateEvents;
|
||||
_largeThreshold = config.LargeThreshold;
|
||||
|
||||
_gatewayLogger = _log.CreateLogger("Gateway");
|
||||
|
||||
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
||||
|
||||
ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.Verbose($"Sent Op {opCode}");
|
||||
ApiClient.ReceivedGatewayEvent += ProcessMessage;
|
||||
GatewaySocket = config.WebSocketProvider();
|
||||
|
||||
_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
|
||||
_largeGuilds = new ConcurrentQueue<ulong>();
|
||||
}
|
||||
|
||||
protected override async Task OnLogin()
|
||||
{
|
||||
var voiceRegions = await ApiClient.GetVoiceRegions().ConfigureAwait(false);
|
||||
_voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id);
|
||||
}
|
||||
protected override async Task OnLogout()
|
||||
{
|
||||
if (ConnectionState != ConnectionState.Disconnected)
|
||||
await DisconnectInternal().ConfigureAwait(false);
|
||||
|
||||
_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
|
||||
}
|
||||
|
||||
public async Task Connect()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await ConnectInternal().ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task ConnectInternal()
|
||||
{
|
||||
if (LoginState != LoginState.LoggedIn)
|
||||
throw new InvalidOperationException("You must log in before connecting.");
|
||||
|
||||
ConnectionState = ConnectionState.Connecting;
|
||||
try
|
||||
{
|
||||
await ApiClient.Connect().ConfigureAwait(false);
|
||||
|
||||
ConnectionState = ConnectionState.Connected;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await DisconnectInternal().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
await Connected.Raise().ConfigureAwait(false);
|
||||
}
|
||||
public async Task Disconnect()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DisconnectInternal().ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task DisconnectInternal()
|
||||
{
|
||||
ulong guildId;
|
||||
|
||||
if (ConnectionState == ConnectionState.Disconnected) return;
|
||||
ConnectionState = ConnectionState.Disconnecting;
|
||||
|
||||
await ApiClient.Disconnect().ConfigureAwait(false);
|
||||
while (_largeGuilds.TryDequeue(out guildId)) { }
|
||||
|
||||
ConnectionState = ConnectionState.Disconnected;
|
||||
|
||||
await Disconnected.Raise().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override Task<IVoiceRegion> GetVoiceRegion(string id)
|
||||
{
|
||||
VoiceRegion region;
|
||||
if (_voiceRegions.TryGetValue(id, out region))
|
||||
return Task.FromResult<IVoiceRegion>(region);
|
||||
return Task.FromResult<IVoiceRegion>(null);
|
||||
}
|
||||
|
||||
public override Task<IGuild> GetGuild(ulong id)
|
||||
{
|
||||
return Task.FromResult<IGuild>(DataStore.GetGuild(id));
|
||||
}
|
||||
internal CachedGuild AddCachedGuild(API.Gateway.ExtendedGuild model, DataStore dataStore = null)
|
||||
{
|
||||
var guild = new CachedGuild(this, model);
|
||||
for (int i = 0; i < model.Channels.Length; i++)
|
||||
AddCachedChannel(model.Channels[i], dataStore);
|
||||
DataStore.AddGuild(guild);
|
||||
if (model.Large)
|
||||
_largeGuilds.Enqueue(model.Id);
|
||||
return guild;
|
||||
}
|
||||
internal CachedGuild RemoveCachedGuild(ulong id, DataStore dataStore = null)
|
||||
{
|
||||
var guild = DataStore.RemoveGuild(id) as CachedGuild;
|
||||
foreach (var channel in guild.Channels)
|
||||
guild.RemoveCachedChannel(channel.Id);
|
||||
foreach (var user in guild.Members)
|
||||
guild.RemoveCachedUser(user.Id);
|
||||
return guild;
|
||||
}
|
||||
internal CachedGuild GetCachedGuild(ulong id) => DataStore.GetGuild(id) as CachedGuild;
|
||||
|
||||
public override Task<IChannel> GetChannel(ulong id)
|
||||
{
|
||||
return Task.FromResult<IChannel>(DataStore.GetChannel(id));
|
||||
}
|
||||
internal ICachedChannel AddCachedChannel(API.Channel model, DataStore dataStore = null)
|
||||
{
|
||||
if (model.IsPrivate)
|
||||
{
|
||||
var recipient = AddCachedUser(model.Recipient);
|
||||
return recipient.SetDMChannel(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
var guild = GetCachedGuild(model.GuildId.Value);
|
||||
return guild.AddCachedChannel(model);
|
||||
}
|
||||
}
|
||||
internal ICachedChannel RemoveCachedChannel(ulong id, DataStore dataStore = null)
|
||||
{
|
||||
var channel = DataStore.RemoveChannel(id) as ICachedChannel;
|
||||
var dmChannel = channel as CachedDMChannel;
|
||||
if (dmChannel != null)
|
||||
{
|
||||
var recipient = dmChannel.Recipient;
|
||||
recipient.RemoveDMChannel(id);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
internal ICachedChannel GetCachedChannel(ulong id) => DataStore.GetChannel(id) as ICachedChannel;
|
||||
|
||||
public override Task<IUser> GetUser(ulong id)
|
||||
{
|
||||
return Task.FromResult<IUser>(DataStore.GetUser(id));
|
||||
}
|
||||
public override Task<IUser> GetUser(string username, ushort discriminator)
|
||||
{
|
||||
return Task.FromResult<IUser>(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault());
|
||||
}
|
||||
internal CachedPublicUser AddCachedUser(API.User model, DataStore dataStore = null)
|
||||
{
|
||||
var user = DataStore.GetOrAddUser(model.Id, _ => new CachedPublicUser(this, model)) as CachedPublicUser;
|
||||
user.AddRef();
|
||||
return user;
|
||||
}
|
||||
internal CachedPublicUser RemoveCachedUser(ulong id, DataStore dataStore = null)
|
||||
{
|
||||
var user = DataStore.GetUser(id) as CachedPublicUser;
|
||||
user.RemoveRef();
|
||||
return user;
|
||||
}
|
||||
|
||||
private async Task ProcessMessage(GatewayOpCodes opCode, string type, JToken payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (opCode)
|
||||
{
|
||||
case GatewayOpCodes.Dispatch:
|
||||
switch (type)
|
||||
{
|
||||
//Global
|
||||
case "READY":
|
||||
{
|
||||
//TODO: Make downloading large guilds optional
|
||||
var data = payload.ToObject<ReadyEvent>(_serializer);
|
||||
var dataStore = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length);
|
||||
|
||||
_currentUser = new CachedSelfUser(this,data.User);
|
||||
|
||||
for (int i = 0; i < data.Guilds.Length; i++)
|
||||
AddCachedGuild(data.Guilds[i], dataStore);
|
||||
for (int i = 0; i < data.PrivateChannels.Length; i++)
|
||||
AddCachedChannel(data.PrivateChannels[i], dataStore);
|
||||
|
||||
_sessionId = data.SessionId;
|
||||
DataStore = dataStore;
|
||||
|
||||
await Ready().ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
|
||||
//Guilds
|
||||
/*case "GUILD_CREATE":
|
||||
{
|
||||
var data = payload.ToObject<ExtendedGuild>(_serializer);
|
||||
var guild = new CachedGuild(this, data);
|
||||
DataStore.AddGuild(guild);
|
||||
|
||||
if (data.Unavailable == false)
|
||||
type = "GUILD_AVAILABLE";
|
||||
else
|
||||
await JoinedGuild.Raise(guild).ConfigureAwait(false);
|
||||
|
||||
if (!data.Large)
|
||||
await GuildAvailable.Raise(guild);
|
||||
else
|
||||
_largeGuilds.Enqueue(data.Id);
|
||||
}
|
||||
break;
|
||||
case "GUILD_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Guild>(_serializer);
|
||||
var guild = DataStore.GetGuild(data.Id);
|
||||
if (guild != null)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? guild.Clone() : null;
|
||||
guild.Update(data);
|
||||
await GuildUpdated.Raise(before, guild);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_UPDATE referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
case "GUILD_DELETE":
|
||||
{
|
||||
var data = payload.ToObject<ExtendedGuild>(_serializer);
|
||||
var guild = DataStore.RemoveGuild(data.Id);
|
||||
if (guild != null)
|
||||
{
|
||||
if (data.Unavailable == true)
|
||||
type = "GUILD_UNAVAILABLE";
|
||||
|
||||
await GuildUnavailable.Raise(guild);
|
||||
if (data.Unavailable != true)
|
||||
await LeftGuild.Raise(guild);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_DELETE referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
|
||||
//Channels
|
||||
case "CHANNEL_CREATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Channel>(_serializer);
|
||||
|
||||
IChannel channel = null;
|
||||
if (data.GuildId != null)
|
||||
{
|
||||
var guild = GetCachedGuild(data.GuildId.Value);
|
||||
if (guild != null)
|
||||
channel = guild.AddCachedChannel(data.Id, true);
|
||||
else
|
||||
await _gatewayLogger.Warning("CHANNEL_CREATE referenced an unknown guild.");
|
||||
}
|
||||
else
|
||||
channel = AddCachedPrivateChannel(data.Id, data.Recipient.Id);
|
||||
if (channel != null)
|
||||
{
|
||||
channel.Update(data);
|
||||
await ChannelCreated.Raise(channel);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "CHANNEL_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Channel>(_serializer);
|
||||
var channel = DataStore.GetChannel(data.Id) as Channel;
|
||||
if (channel != null)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? channel.Clone() : null;
|
||||
channel.Update(data);
|
||||
await ChannelUpdated.Raise(before, channel);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("CHANNEL_UPDATE referenced an unknown channel.");
|
||||
}
|
||||
break;
|
||||
case "CHANNEL_DELETE":
|
||||
{
|
||||
var data = payload.ToObject<API.Channel>(_serializer);
|
||||
var channel = RemoveCachedChannel(data.Id);
|
||||
if (channel != null)
|
||||
await ChannelDestroyed.Raise(channel);
|
||||
else
|
||||
await _gatewayLogger.Warning("CHANNEL_DELETE referenced an unknown channel.");
|
||||
}
|
||||
break;
|
||||
|
||||
//Members
|
||||
case "GUILD_MEMBER_ADD":
|
||||
{
|
||||
var data = payload.ToObject<API.GuildMember>(_serializer);
|
||||
var guild = GetGuild(data.GuildId.Value);
|
||||
if (guild != null)
|
||||
{
|
||||
var user = guild.AddCachedUser(data.User.Id, true, true);
|
||||
user.Update(data);
|
||||
user.UpdateActivity();
|
||||
UserJoined.Raise(user);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_MEMBER_ADD referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
case "GUILD_MEMBER_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.GuildMember>(_serializer);
|
||||
var guild = GetGuild(data.GuildId.Value);
|
||||
if (guild != null)
|
||||
{
|
||||
var user = guild.GetCachedUser(data.User.Id);
|
||||
if (user != null)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? user.Clone() : null;
|
||||
user.Update(data);
|
||||
await UserUpdated.Raise(before, user);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user.");
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
case "GUILD_MEMBER_REMOVE":
|
||||
{
|
||||
var data = payload.ToObject<API.GuildMember>(_serializer);
|
||||
var guild = GetGuild(data.GuildId.Value);
|
||||
if (guild != null)
|
||||
{
|
||||
var user = guild.RemoveCachedUser(data.User.Id);
|
||||
if (user != null)
|
||||
{
|
||||
user.GlobalUser.RemoveGuild();
|
||||
if (user.GuildCount == 0 && user.DMChannel == null)
|
||||
DataStore.RemoveUser(user.Id);
|
||||
await UserLeft.Raise(user);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user.");
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
case "GUILD_MEMBERS_CHUNK":
|
||||
{
|
||||
var data = payload.ToObject<GuildMembersChunkEvent>(_serializer);
|
||||
var guild = GetCachedGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
{
|
||||
foreach (var memberData in data.Members)
|
||||
{
|
||||
var user = guild.AddCachedUser(memberData.User.Id, true, false);
|
||||
user.Update(memberData);
|
||||
}
|
||||
|
||||
if (guild.CurrentUserCount >= guild.UserCount) //Finished downloading for there
|
||||
await GuildAvailable.Raise(guild);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
|
||||
//Roles
|
||||
case "GUILD_ROLE_CREATE":
|
||||
{
|
||||
var data = payload.ToObject<GuildRoleCreateEvent>(_serializer);
|
||||
var guild = GetCachedGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
{
|
||||
var role = guild.AddCachedRole(data.Data.Id);
|
||||
role.Update(data.Data, false);
|
||||
RoleCreated.Raise(role);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_ROLE_CREATE referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
case "GUILD_ROLE_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<GuildRoleUpdateEvent>(_serializer);
|
||||
var guild = GetCachedGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
{
|
||||
var role = guild.GetRole(data.Data.Id);
|
||||
if (role != null)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? role.Clone() : null;
|
||||
role.Update(data.Data, true);
|
||||
RoleUpdated.Raise(before, role);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown role.");
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
case "GUILD_ROLE_DELETE":
|
||||
{
|
||||
var data = payload.ToObject<GuildRoleDeleteEvent>(_serializer);
|
||||
var guild = DataStore.GetGuild(data.GuildId) as CachedGuild;
|
||||
if (guild != null)
|
||||
{
|
||||
var role = guild.RemoveRole(data.RoleId);
|
||||
if (role != null)
|
||||
RoleDeleted.Raise(role);
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown role.");
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
|
||||
//Bans
|
||||
case "GUILD_BAN_ADD":
|
||||
{
|
||||
var data = payload.ToObject<GuildBanEvent>(_serializer);
|
||||
var guild = GetCachedGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
await UserBanned.Raise(new User(this, data));
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
case "GUILD_BAN_REMOVE":
|
||||
{
|
||||
var data = payload.ToObject<GuildBanEvent>(_serializer);
|
||||
var guild = GetCachedGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
await UserUnbanned.Raise(new User(this, data));
|
||||
else
|
||||
await _gatewayLogger.Warning("GUILD_BAN_REMOVE referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
|
||||
//Messages
|
||||
case "MESSAGE_CREATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Message>(_serializer);
|
||||
|
||||
var channel = DataStore.GetChannel(data.ChannelId);
|
||||
if (channel != null)
|
||||
{
|
||||
var user = channel.GetUser(data.Author.Id);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
bool isAuthor = data.Author.Id == CurrentUser.Id;
|
||||
var msg = channel.AddMessage(data.Id, user, data.Timestamp.Value);
|
||||
|
||||
msg.Update(data);
|
||||
|
||||
MessageReceived.Raise(msg);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown user.");
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown channel.");
|
||||
}
|
||||
break;
|
||||
case "MESSAGE_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Message>(_serializer);
|
||||
var channel = GetCachedChannel(data.ChannelId);
|
||||
if (channel != null)
|
||||
{
|
||||
var msg = channel.GetMessage(data.Id, data.Author?.Id);
|
||||
var before = _enablePreUpdateEvents ? msg.Clone() : null;
|
||||
msg.Update(data);
|
||||
MessageUpdated.Raise(before, msg);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("MESSAGE_UPDATE referenced an unknown channel.");
|
||||
}
|
||||
break;
|
||||
case "MESSAGE_DELETE":
|
||||
{
|
||||
var data = payload.ToObject<API.Message>(_serializer);
|
||||
var channel = GetCachedChannel(data.ChannelId);
|
||||
if (channel != null)
|
||||
{
|
||||
var msg = channel.RemoveMessage(data.Id);
|
||||
MessageDeleted.Raise(msg);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("MESSAGE_DELETE referenced an unknown channel.");
|
||||
}
|
||||
break;
|
||||
|
||||
//Statuses
|
||||
case "PRESENCE_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Presence>(_serializer);
|
||||
User user;
|
||||
Guild guild;
|
||||
if (data.GuildId == null)
|
||||
{
|
||||
guild = null;
|
||||
user = GetPrivateChannel(data.User.Id)?.Recipient;
|
||||
}
|
||||
else
|
||||
{
|
||||
guild = GetGuild(data.GuildId.Value);
|
||||
if (guild == null)
|
||||
{
|
||||
await _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown guild.");
|
||||
break;
|
||||
}
|
||||
else
|
||||
user = guild.GetUser(data.User.Id);
|
||||
}
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? user.Clone() : null;
|
||||
user.Update(data);
|
||||
UserUpdated.Raise(before, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Occurs when a user leaves a guild
|
||||
//await _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown user.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "TYPING_START":
|
||||
{
|
||||
var data = payload.ToObject<TypingStartEvent>(_serializer);
|
||||
var channel = GetCachedChannel(data.ChannelId);
|
||||
if (channel != null)
|
||||
{
|
||||
var user = channel.GetUser(data.UserId);
|
||||
if (user != null)
|
||||
{
|
||||
await UserIsTyping.Raise(channel, user);
|
||||
user.UpdateActivity();
|
||||
}
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("TYPING_START referenced an unknown channel.");
|
||||
}
|
||||
break;
|
||||
|
||||
//Voice
|
||||
case "VOICE_STATE_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.VoiceState>(_serializer);
|
||||
var guild = GetGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
{
|
||||
var user = guild.GetUser(data.UserId);
|
||||
if (user != null)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? user.Clone() : null;
|
||||
user.Update(data);
|
||||
UserUpdated.Raise(before, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Occurs when a user leaves a guild
|
||||
//await _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown user.");
|
||||
}
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown guild.");
|
||||
}
|
||||
break;
|
||||
|
||||
//Settings
|
||||
case "USER_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<SelfUser>(_serializer);
|
||||
if (data.Id == CurrentUser.Id)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? CurrentUser.Clone() : null;
|
||||
CurrentUser.Update(data);
|
||||
await CurrentUserUpdated.Raise(before, CurrentUser).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
break;*/
|
||||
|
||||
//Ignored
|
||||
case "USER_SETTINGS_UPDATE":
|
||||
case "MESSAGE_ACK": //TODO: Add (User only)
|
||||
case "GUILD_EMOJIS_UPDATE": //TODO: Add
|
||||
case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add
|
||||
case "VOICE_SERVER_UPDATE": //TODO: Add
|
||||
case "RESUMED": //TODO: Add
|
||||
await _gatewayLogger.Debug($"Ignored message {opCode}{(type != null ? $" ({type})" : "")}").ConfigureAwait(false);
|
||||
return;
|
||||
|
||||
//Others
|
||||
default:
|
||||
await _gatewayLogger.Warning($"Unknown message {opCode}{(type != null ? $" ({type})" : "")}").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _gatewayLogger.Error($"Error handling msg {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await _gatewayLogger.Debug($"Received {opCode}{(type != null ? $" ({type})" : "")}").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Discord.Net.WebSockets;
|
||||
using Discord.WebSocket.Data;
|
||||
using Discord.Data;
|
||||
using Discord.Net.WebSockets;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
namespace Discord
|
||||
{
|
||||
public class DiscordSocketConfig : DiscordConfig
|
||||
{
|
||||
@@ -15,15 +15,15 @@ namespace Discord.WebSocket
|
||||
/// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary>
|
||||
public int ReconnectDelay { get; set; } = 1000;
|
||||
/// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary>
|
||||
public int FailedReconnectDelay { get; set; } = 15000;
|
||||
public int FailedReconnectDelay { get; set; } = 15000;
|
||||
|
||||
/// <summary> Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. </summary>
|
||||
public int MessageCacheSize { get; set; } = 100;
|
||||
/// <summary>
|
||||
/*/// <summary>
|
||||
/// Gets or sets whether the permissions cache should be used.
|
||||
/// This makes operations such as User.GetPermissions(Channel), User.GuildPermissions, Channel.GetUser, and Channel.Members much faster while increasing memory usage.
|
||||
/// This makes operations such as User.GetPermissions(Channel), User.GuildPermissions, Channel.GetUser, and Channel.Members much faster at the expense of increased memory usage.
|
||||
/// </summary>
|
||||
public bool UsePermissionsCache { get; set; } = true;
|
||||
public bool UsePermissionsCache { get; set; } = false;*/
|
||||
/// <summary> Gets or sets whether the a copy of a model is generated on an update event to allow you to check which properties changed. </summary>
|
||||
public bool EnablePreUpdateEvents { get; set; } = true;
|
||||
/// <summary>
|
||||
126
src/Discord.Net/Entities/Channels/DMChannel.cs
Normal file
126
src/Discord.Net/Entities/Channels/DMChannel.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using Discord.API.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
internal class DMChannel : SnowflakeEntity, IDMChannel
|
||||
{
|
||||
public override DiscordClient Discord { get; }
|
||||
public User Recipient { get; private set; }
|
||||
|
||||
public virtual IReadOnlyCollection<IMessage> CachedMessages => ImmutableArray.Create<IMessage>();
|
||||
|
||||
public DMChannel(DiscordClient discord, User recipient, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Discord = discord;
|
||||
Recipient = recipient;
|
||||
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
protected void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
Recipient.Update(model.Recipient, UpdateSource.Rest);
|
||||
}
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
if (IsAttached) throw new NotSupportedException();
|
||||
|
||||
var model = await Discord.ApiClient.GetChannel(Id).ConfigureAwait(false);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
public async Task Close()
|
||||
{
|
||||
await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public virtual async Task<IUser> GetUser(ulong id)
|
||||
{
|
||||
var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false);
|
||||
if (id == Recipient.Id)
|
||||
return Recipient;
|
||||
else if (id == currentUser.Id)
|
||||
return currentUser;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public virtual async Task<IReadOnlyCollection<IUser>> GetUsers()
|
||||
{
|
||||
var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false);
|
||||
return ImmutableArray.Create<IUser>(currentUser, Recipient);
|
||||
}
|
||||
public virtual async Task<IReadOnlyCollection<IUser>> GetUsers(int limit, int offset)
|
||||
{
|
||||
var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false);
|
||||
return new IUser[] { currentUser, Recipient }.Skip(offset).Take(limit).ToImmutableArray();
|
||||
}
|
||||
|
||||
public async Task<IMessage> SendMessage(string text, bool isTTS)
|
||||
{
|
||||
var args = new CreateMessageParams { Content = text, IsTTS = isTTS };
|
||||
var model = await Discord.ApiClient.CreateDMMessage(Id, args).ConfigureAwait(false);
|
||||
return new Message(this, new User(Discord, model.Author), model);
|
||||
}
|
||||
public async Task<IMessage> SendFile(string filePath, string text, bool isTTS)
|
||||
{
|
||||
string filename = Path.GetFileName(filePath);
|
||||
using (var file = File.OpenRead(filePath))
|
||||
{
|
||||
var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS };
|
||||
var model = await Discord.ApiClient.UploadDMFile(Id, file, args).ConfigureAwait(false);
|
||||
return new Message(this, new User(Discord, model.Author), model);
|
||||
}
|
||||
}
|
||||
public async Task<IMessage> SendFile(Stream stream, string filename, string text, bool isTTS)
|
||||
{
|
||||
var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS };
|
||||
var model = await Discord.ApiClient.UploadDMFile(Id, stream, args).ConfigureAwait(false);
|
||||
return new Message(this, new User(Discord, model.Author), model);
|
||||
}
|
||||
public virtual async Task<IMessage> GetMessage(ulong id)
|
||||
{
|
||||
var model = await Discord.ApiClient.GetChannelMessage(Id, id).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
return new Message(this, new User(Discord, model.Author), model);
|
||||
return null;
|
||||
}
|
||||
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessages(int limit)
|
||||
{
|
||||
var args = new GetChannelMessagesParams { Limit = limit };
|
||||
var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false);
|
||||
return models.Select(x => new Message(this, new User(Discord, x.Author), x)).ToImmutableArray();
|
||||
}
|
||||
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessages(ulong fromMessageId, Direction dir, int limit)
|
||||
{
|
||||
var args = new GetChannelMessagesParams { Limit = limit };
|
||||
var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false);
|
||||
return models.Select(x => new Message(this, new User(Discord, x.Author), x)).ToImmutableArray();
|
||||
}
|
||||
public async Task DeleteMessages(IEnumerable<IMessage> messages)
|
||||
{
|
||||
await Discord.ApiClient.DeleteDMMessages(Id, new DeleteMessagesParams { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task TriggerTyping()
|
||||
{
|
||||
await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override string ToString() => '@' + Recipient.ToString();
|
||||
private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)";
|
||||
|
||||
IUser IDMChannel.Recipient => Recipient;
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => null;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,39 @@
|
||||
using Discord.API.Rest;
|
||||
using Discord.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
public abstract class GuildChannel : IGuildChannel
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
internal abstract class GuildChannel : SnowflakeEntity, IGuildChannel
|
||||
{
|
||||
private ConcurrentDictionary<ulong, Overwrite> _overwrites;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ulong Id { get; }
|
||||
/// <summary> Gets the guild this channel is a member of. </summary>
|
||||
public Guild Guild { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int Position { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<ulong, Overwrite> PermissionOverwrites => _overwrites;
|
||||
internal DiscordClient Discord => Guild.Discord;
|
||||
public Guild Guild { get; private set; }
|
||||
|
||||
internal GuildChannel(Guild guild, Model model)
|
||||
public override DiscordClient Discord => Guild.Discord;
|
||||
|
||||
public GuildChannel(Guild guild, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Id = model.Id;
|
||||
Guild = guild;
|
||||
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
internal virtual void Update(Model model)
|
||||
protected virtual void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
Name = model.Name;
|
||||
Position = model.Position;
|
||||
|
||||
@@ -49,6 +46,13 @@ namespace Discord.Rest
|
||||
_overwrites = newOverwrites;
|
||||
}
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
if (IsAttached) throw new NotSupportedException();
|
||||
|
||||
var model = await Discord.ApiClient.GetChannel(Id).ConfigureAwait(false);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
public async Task Modify(Action<ModifyGuildChannelParams> func)
|
||||
{
|
||||
if (func != null) throw new NullReferenceException(nameof(func));
|
||||
@@ -56,69 +60,23 @@ namespace Discord.Rest
|
||||
var args = new ModifyGuildChannelParams();
|
||||
func(args);
|
||||
var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public OverwritePermissions? GetPermissionOverwrite(IUser user)
|
||||
public async Task Delete()
|
||||
{
|
||||
Overwrite value;
|
||||
if (_overwrites.TryGetValue(Id, out value))
|
||||
return value.Permissions;
|
||||
return null;
|
||||
await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public OverwritePermissions? GetPermissionOverwrite(IRole role)
|
||||
{
|
||||
Overwrite value;
|
||||
if (_overwrites.TryGetValue(Id, out value))
|
||||
return value.Permissions;
|
||||
return null;
|
||||
}
|
||||
/// <summary> Downloads a collection of all invites to this channel. </summary>
|
||||
public async Task<IEnumerable<InviteMetadata>> GetInvites()
|
||||
|
||||
public abstract Task<IGuildUser> GetUser(ulong id);
|
||||
public abstract Task<IReadOnlyCollection<IGuildUser>> GetUsers();
|
||||
public abstract Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset);
|
||||
|
||||
public async Task<IReadOnlyCollection<IInviteMetadata>> GetInvites()
|
||||
{
|
||||
var models = await Discord.ApiClient.GetChannelInvites(Id).ConfigureAwait(false);
|
||||
return models.Select(x => new InviteMetadata(Discord, x));
|
||||
return models.Select(x => new InviteMetadata(Discord, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms)
|
||||
{
|
||||
var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue };
|
||||
await Discord.ApiClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false);
|
||||
_overwrites[user.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User });
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms)
|
||||
{
|
||||
var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue };
|
||||
await Discord.ApiClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false);
|
||||
_overwrites[role.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role });
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task RemovePermissionOverwrite(IUser user)
|
||||
{
|
||||
await Discord.ApiClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false);
|
||||
|
||||
Overwrite value;
|
||||
_overwrites.TryRemove(user.Id, out value);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task RemovePermissionOverwrite(IRole role)
|
||||
{
|
||||
await Discord.ApiClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false);
|
||||
|
||||
Overwrite value;
|
||||
_overwrites.TryRemove(role.Id, out value);
|
||||
}
|
||||
|
||||
/// <summary> Creates a new invite to this channel. </summary>
|
||||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param>
|
||||
/// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param>
|
||||
/// <param name="isTemporary"> If true, a user accepting this invite will be kicked from the guild after closing their client. </param>
|
||||
/// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param>
|
||||
public async Task<InviteMetadata> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false)
|
||||
public async Task<IInviteMetadata> CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd)
|
||||
{
|
||||
var args = new CreateChannelInviteParams
|
||||
{
|
||||
@@ -131,39 +89,56 @@ namespace Discord.Rest
|
||||
return new InviteMetadata(Discord, model);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete()
|
||||
public OverwritePermissions? GetPermissionOverwrite(IUser user)
|
||||
{
|
||||
await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false);
|
||||
Overwrite value;
|
||||
if (_overwrites.TryGetValue(Id, out value))
|
||||
return value.Permissions;
|
||||
return null;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task Update()
|
||||
public OverwritePermissions? GetPermissionOverwrite(IRole role)
|
||||
{
|
||||
var model = await Discord.ApiClient.GetChannel(Id).ConfigureAwait(false);
|
||||
Update(model);
|
||||
Overwrite value;
|
||||
if (_overwrites.TryGetValue(Id, out value))
|
||||
return value.Permissions;
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms)
|
||||
{
|
||||
var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue };
|
||||
await Discord.ApiClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false);
|
||||
_overwrites[user.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User });
|
||||
}
|
||||
public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms)
|
||||
{
|
||||
var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue };
|
||||
await Discord.ApiClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false);
|
||||
_overwrites[role.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role });
|
||||
}
|
||||
public async Task RemovePermissionOverwrite(IUser user)
|
||||
{
|
||||
await Discord.ApiClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false);
|
||||
|
||||
/// <inheritdoc />
|
||||
Overwrite value;
|
||||
_overwrites.TryRemove(user.Id, out value);
|
||||
}
|
||||
public async Task RemovePermissionOverwrite(IRole role)
|
||||
{
|
||||
await Discord.ApiClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false);
|
||||
|
||||
Overwrite value;
|
||||
_overwrites.TryRemove(role.Id, out value);
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
protected abstract Task<GuildUser> GetUserInternal(ulong id);
|
||||
protected abstract Task<IEnumerable<GuildUser>> GetUsersInternal();
|
||||
protected abstract Task<IEnumerable<GuildUser>> GetUsersInternal(int limit, int offset);
|
||||
|
||||
private string DebuggerDisplay => $"{Name} ({Id})";
|
||||
|
||||
IGuild IGuildChannel.Guild => Guild;
|
||||
async Task<IInviteMetadata> IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd)
|
||||
=> await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false);
|
||||
async Task<IEnumerable<IInviteMetadata>> IGuildChannel.GetInvites()
|
||||
=> await GetInvites().ConfigureAwait(false);
|
||||
async Task<IEnumerable<IGuildUser>> IGuildChannel.GetUsers()
|
||||
=> await GetUsersInternal().ConfigureAwait(false);
|
||||
async Task<IEnumerable<IUser>> IChannel.GetUsers()
|
||||
=> await GetUsersInternal().ConfigureAwait(false);
|
||||
async Task<IEnumerable<IUser>> IChannel.GetUsers(int limit, int offset)
|
||||
=> await GetUsersInternal(limit, offset).ConfigureAwait(false);
|
||||
async Task<IGuildUser> IGuildChannel.GetUser(ulong id)
|
||||
=> await GetUserInternal(id).ConfigureAwait(false);
|
||||
async Task<IUser> IChannel.GetUser(ulong id)
|
||||
=> await GetUserInternal(id).ConfigureAwait(false);
|
||||
IReadOnlyCollection<Overwrite> IGuildChannel.PermissionOverwrites => _overwrites.ToReadOnlyCollection();
|
||||
|
||||
async Task<IUser> IChannel.GetUser(ulong id) => await GetUser(id).ConfigureAwait(false);
|
||||
async Task<IReadOnlyCollection<IUser>> IChannel.GetUsers() => await GetUsers().ConfigureAwait(false);
|
||||
async Task<IReadOnlyCollection<IUser>> IChannel.GetUsers(int limit, int offset) => await GetUsers(limit, offset).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ namespace Discord
|
||||
public interface IChannel : ISnowflakeEntity
|
||||
{
|
||||
/// <summary> Gets a collection of all users in this channel. </summary>
|
||||
Task<IEnumerable<IUser>> GetUsers();
|
||||
Task<IReadOnlyCollection<IUser>> GetUsers();
|
||||
/// <summary> Gets a paginated collection of all users in this channel. </summary>
|
||||
Task<IEnumerable<IUser>> GetUsers(int limit, int offset = 0);
|
||||
Task<IReadOnlyCollection<IUser>> GetUsers(int limit, int offset = 0);
|
||||
/// <summary> Gets a user in this channel with the provided id.</summary>
|
||||
Task<IUser> GetUser(ulong id);
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ namespace Discord
|
||||
/// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param>
|
||||
Task<IInviteMetadata> CreateInvite(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, bool withXkcd = false);
|
||||
/// <summary> Returns a collection of all invites to this channel. </summary>
|
||||
Task<IEnumerable<IInviteMetadata>> GetInvites();
|
||||
Task<IReadOnlyCollection<IInviteMetadata>> GetInvites();
|
||||
|
||||
/// <summary> Gets a collection of permission overwrites for this channel. </summary>
|
||||
IReadOnlyDictionary<ulong, Overwrite> PermissionOverwrites { get; }
|
||||
|
||||
IReadOnlyCollection<Overwrite> PermissionOverwrites { get; }
|
||||
|
||||
/// <summary> Modifies this guild channel. </summary>
|
||||
Task Modify(Action<ModifyGuildChannelParams> func);
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Discord
|
||||
Task AddPermissionOverwrite(IUser user, OverwritePermissions permissions);
|
||||
|
||||
/// <summary> Gets a collection of all users in this channel. </summary>
|
||||
new Task<IEnumerable<IGuildUser>> GetUsers();
|
||||
new Task<IReadOnlyCollection<IGuildUser>> GetUsers();
|
||||
/// <summary> Gets a user in this channel with the provided id.</summary>
|
||||
new Task<IGuildUser> GetUser(ulong id);
|
||||
}
|
||||
|
||||
@@ -7,25 +7,25 @@ namespace Discord
|
||||
public interface IMessageChannel : IChannel
|
||||
{
|
||||
/// <summary> Gets all messages in this channel's cache. </summary>
|
||||
IEnumerable<IMessage> CachedMessages { get; }
|
||||
IReadOnlyCollection<IMessage> CachedMessages { get; }
|
||||
|
||||
/// <summary> Gets the message from this channel's cache with the given id, or null if none was found. </summary>
|
||||
Task<IMessage> GetCachedMessage(ulong id);
|
||||
|
||||
/// <summary> Gets the last N messages from this message channel. </summary>
|
||||
Task<IEnumerable<IMessage>> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch);
|
||||
/// <summary> Gets a collection of messages in this channel. </summary>
|
||||
Task<IEnumerable<IMessage>> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch);
|
||||
|
||||
/// <summary> Sends a message to this text channel. </summary>
|
||||
/// <summary> Sends a message to this message channel. </summary>
|
||||
Task<IMessage> SendMessage(string text, bool isTTS = false);
|
||||
/// <summary> Sends a file to this text channel, with an optional caption. </summary>
|
||||
Task<IMessage> SendFile(string filePath, string text = null, bool isTTS = false);
|
||||
/// <summary> Sends a file to this text channel, with an optional caption. </summary>
|
||||
Task<IMessage> SendFile(Stream stream, string filename, string text = null, bool isTTS = false);
|
||||
|
||||
/// <summary> Gets a message from this message channel with the given id, or null if not found. </summary>
|
||||
Task<IMessage> GetMessage(ulong id);
|
||||
/// <summary> Gets the message from this channel's cache with the given id, or null if not found. </summary>
|
||||
IMessage GetCachedMessage(ulong id);
|
||||
/// <summary> Gets the last N messages from this message channel. </summary>
|
||||
Task<IReadOnlyCollection<IMessage>> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch);
|
||||
/// <summary> Gets a collection of messages in this channel. </summary>
|
||||
Task<IReadOnlyCollection<IMessage>> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch);
|
||||
/// <summary> Bulk deletes multiple messages. </summary>
|
||||
Task DeleteMessages(IEnumerable<IMessage> messages);
|
||||
|
||||
|
||||
/// <summary> Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds.</summary>
|
||||
Task TriggerTyping();
|
||||
|
||||
116
src/Discord.Net/Entities/Channels/TextChannel.cs
Normal file
116
src/Discord.Net/Entities/Channels/TextChannel.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using Discord.API.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
internal class TextChannel : GuildChannel, ITextChannel
|
||||
{
|
||||
public string Topic { get; private set; }
|
||||
|
||||
public string Mention => MentionUtils.Mention(this);
|
||||
public virtual IReadOnlyCollection<IMessage> CachedMessages => ImmutableArray.Create<IMessage>();
|
||||
|
||||
public TextChannel(Guild guild, Model model)
|
||||
: base(guild, model)
|
||||
{
|
||||
}
|
||||
protected override void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
Topic = model.Topic;
|
||||
base.Update(model, UpdateSource.Rest);
|
||||
}
|
||||
|
||||
public async Task Modify(Action<ModifyTextChannelParams> func)
|
||||
{
|
||||
if (func != null) throw new NullReferenceException(nameof(func));
|
||||
|
||||
var args = new ModifyTextChannelParams();
|
||||
func(args);
|
||||
var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
|
||||
public override async Task<IGuildUser> GetUser(ulong id)
|
||||
{
|
||||
var user = await Guild.GetUser(id).ConfigureAwait(false);
|
||||
if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages))
|
||||
return user;
|
||||
return null;
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IGuildUser>> GetUsers()
|
||||
{
|
||||
var users = await Guild.GetUsers().ConfigureAwait(false);
|
||||
return users.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray();
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset)
|
||||
{
|
||||
var users = await Guild.GetUsers(limit, offset).ConfigureAwait(false);
|
||||
return users.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public async Task<IMessage> SendMessage(string text, bool isTTS)
|
||||
{
|
||||
var args = new CreateMessageParams { Content = text, IsTTS = isTTS };
|
||||
var model = await Discord.ApiClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false);
|
||||
return new Message(this, new User(Discord, model.Author), model);
|
||||
}
|
||||
public async Task<IMessage> SendFile(string filePath, string text, bool isTTS)
|
||||
{
|
||||
string filename = Path.GetFileName(filePath);
|
||||
using (var file = File.OpenRead(filePath))
|
||||
{
|
||||
var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS };
|
||||
var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false);
|
||||
return new Message(this, new User(Discord, model.Author), model);
|
||||
}
|
||||
}
|
||||
public async Task<IMessage> SendFile(Stream stream, string filename, string text, bool isTTS)
|
||||
{
|
||||
var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS };
|
||||
var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false);
|
||||
return new Message(this, new User(Discord, model.Author), model);
|
||||
}
|
||||
public virtual async Task<IMessage> GetMessage(ulong id)
|
||||
{
|
||||
var model = await Discord.ApiClient.GetChannelMessage(Id, id).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
return new Message(this, new User(Discord, model.Author), model);
|
||||
return null;
|
||||
}
|
||||
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessages(int limit)
|
||||
{
|
||||
var args = new GetChannelMessagesParams { Limit = limit };
|
||||
var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false);
|
||||
return models.Select(x => new Message(this, new User(Discord, x.Author), x)).ToImmutableArray();
|
||||
}
|
||||
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessages(ulong fromMessageId, Direction dir, int limit)
|
||||
{
|
||||
var args = new GetChannelMessagesParams { Limit = limit };
|
||||
var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false);
|
||||
return models.Select(x => new Message(this, new User(Discord, x.Author), x)).ToImmutableArray();
|
||||
}
|
||||
public async Task DeleteMessages(IEnumerable<IMessage> messages)
|
||||
{
|
||||
await Discord.ApiClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParams { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task TriggerTyping()
|
||||
{
|
||||
await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string DebuggerDisplay => $"{Name} ({Id}, Text)";
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => null;
|
||||
}
|
||||
}
|
||||
@@ -5,28 +5,27 @@ using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class VoiceChannel : GuildChannel, IVoiceChannel
|
||||
internal class VoiceChannel : GuildChannel, IVoiceChannel
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Bitrate { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int UserLimit { get; private set; }
|
||||
|
||||
internal VoiceChannel(Guild guild, Model model)
|
||||
public VoiceChannel(Guild guild, Model model)
|
||||
: base(guild, model)
|
||||
{
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
protected override void Update(Model model, UpdateSource source)
|
||||
{
|
||||
base.Update(model);
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
base.Update(model, UpdateSource.Rest);
|
||||
Bitrate = model.Bitrate;
|
||||
UserLimit = model.UserLimit;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public async Task Modify(Action<ModifyVoiceChannelParams> func)
|
||||
{
|
||||
if (func != null) throw new NullReferenceException(nameof(func));
|
||||
@@ -34,12 +33,21 @@ namespace Discord.Rest
|
||||
var args = new ModifyVoiceChannelParams();
|
||||
func(args);
|
||||
var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false);
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
|
||||
protected override Task<GuildUser> GetUserInternal(ulong id) { throw new NotSupportedException(); }
|
||||
protected override Task<IEnumerable<GuildUser>> GetUsersInternal() { throw new NotSupportedException(); }
|
||||
protected override Task<IEnumerable<GuildUser>> GetUsersInternal(int limit, int offset) { throw new NotSupportedException(); }
|
||||
public override Task<IGuildUser> GetUser(ulong id)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private string DebuggerDisplay => $"{Name} ({Id}, Voice)";
|
||||
}
|
||||
16
src/Discord.Net/Entities/Entity.cs
Normal file
16
src/Discord.Net/Entities/Entity.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Discord
|
||||
{
|
||||
internal abstract class Entity<T> : IEntity<T>
|
||||
{
|
||||
public T Id { get; }
|
||||
|
||||
public abstract DiscordClient Discord { get; }
|
||||
|
||||
public bool IsAttached => this is ICachedEntity<T>;
|
||||
|
||||
public Entity(T id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Discord
|
||||
public bool RequireColons { get; }
|
||||
public IImmutableList<ulong> RoleIds { get; }
|
||||
|
||||
internal Emoji(Model model)
|
||||
public Emoji(Model model)
|
||||
{
|
||||
Id = model.Id;
|
||||
Name = model.Name;
|
||||
|
||||
@@ -1,77 +1,60 @@
|
||||
using Discord.API.Rest;
|
||||
using Discord.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Guild;
|
||||
using EmbedModel = Discord.API.GuildEmbed;
|
||||
using Model = Discord.API.Guild;
|
||||
using RoleModel = Discord.API.Role;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
/// <summary> Represents a Discord guild (called a server in the official client). </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class Guild : IGuild
|
||||
internal class Guild : SnowflakeEntity, IGuild
|
||||
{
|
||||
private ConcurrentDictionary<ulong, Role> _roles;
|
||||
private string _iconId, _splashId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ulong Id { get; }
|
||||
internal DiscordClient Discord { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected ConcurrentDictionary<ulong, Role> _roles;
|
||||
protected string _iconId, _splashId;
|
||||
|
||||
public string Name { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int AFKTimeout { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsEmbeddable { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int VerificationLevel { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ulong? AFKChannelId { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public ulong? EmbedChannelId { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public ulong OwnerId { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string VoiceRegionId { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Emoji> Emojis { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<string> Features { get; private set; }
|
||||
public override DiscordClient Discord { get; }
|
||||
public ImmutableArray<Emoji> Emojis { get; protected set; }
|
||||
public ImmutableArray<string> Features { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
/// <inheritdoc />
|
||||
public ulong DefaultChannelId => Id;
|
||||
public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId);
|
||||
/// <inheritdoc />
|
||||
public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, _splashId);
|
||||
/// <inheritdoc />
|
||||
public ulong DefaultChannelId => Id;
|
||||
/// <inheritdoc />
|
||||
public Role EveryoneRole => GetRole(Id);
|
||||
/// <summary> Gets a collection of all roles in this guild. </summary>
|
||||
public IEnumerable<Role> Roles => _roles?.Select(x => x.Value) ?? Enumerable.Empty<Role>();
|
||||
|
||||
internal Guild(DiscordClient discord, Model model)
|
||||
public Role EveryoneRole => GetRole(Id);
|
||||
public IReadOnlyCollection<IRole> Roles => _roles.ToReadOnlyCollection();
|
||||
|
||||
public Guild(DiscordClient discord, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Id = model.Id;
|
||||
Discord = discord;
|
||||
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
private void Update(Model model)
|
||||
public void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
AFKChannelId = model.AFKChannelId;
|
||||
AFKTimeout = model.AFKTimeout;
|
||||
EmbedChannelId = model.EmbedChannelId;
|
||||
AFKTimeout = model.AFKTimeout;
|
||||
IsEmbeddable = model.EmbedEnabled;
|
||||
Features = model.Features;
|
||||
Features = model.Features.ToImmutableArray();
|
||||
_iconId = model.Icon;
|
||||
Name = model.Name;
|
||||
OwnerId = model.OwnerId;
|
||||
@@ -84,10 +67,10 @@ namespace Discord.Rest
|
||||
var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length);
|
||||
for (int i = 0; i < model.Emojis.Length; i++)
|
||||
emojis.Add(new Emoji(model.Emojis[i]));
|
||||
Emojis = emojis.ToArray();
|
||||
Emojis = emojis.ToImmutableArray();
|
||||
}
|
||||
else
|
||||
Emojis = Array.Empty<Emoji>();
|
||||
Emojis = ImmutableArray.Create<Emoji>();
|
||||
|
||||
var roles = new ConcurrentDictionary<ulong, Role>(1, model.Roles?.Length ?? 0);
|
||||
if (model.Roles != null)
|
||||
@@ -97,28 +80,32 @@ namespace Discord.Rest
|
||||
}
|
||||
_roles = roles;
|
||||
}
|
||||
private void Update(EmbedModel model)
|
||||
public void Update(EmbedModel model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
IsEmbeddable = model.Enabled;
|
||||
EmbedChannelId = model.ChannelId;
|
||||
}
|
||||
private void Update(IEnumerable<RoleModel> models)
|
||||
public void Update(IEnumerable<RoleModel> models, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
Role role;
|
||||
foreach (var model in models)
|
||||
{
|
||||
if (_roles.TryGetValue(model.Id, out role))
|
||||
role.Update(model);
|
||||
role.Update(model, UpdateSource.Rest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
if (IsAttached) throw new NotSupportedException();
|
||||
|
||||
var response = await Discord.ApiClient.GetGuild(Id).ConfigureAwait(false);
|
||||
Update(response);
|
||||
Update(response, UpdateSource.Rest);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task Modify(Action<ModifyGuildParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
@@ -126,9 +113,8 @@ namespace Discord.Rest
|
||||
var args = new ModifyGuildParams();
|
||||
func(args);
|
||||
var model = await Discord.ApiClient.ModifyGuild(Id, args).ConfigureAwait(false);
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task ModifyEmbed(Action<ModifyGuildEmbedParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
@@ -136,68 +122,57 @@ namespace Discord.Rest
|
||||
var args = new ModifyGuildEmbedParams();
|
||||
func(args);
|
||||
var model = await Discord.ApiClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false);
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task ModifyChannels(IEnumerable<ModifyGuildChannelsParams> args)
|
||||
{
|
||||
//TODO: Update channels
|
||||
await Discord.ApiClient.ModifyGuildChannels(Id, args).ConfigureAwait(false);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task ModifyRoles(IEnumerable<ModifyGuildRolesParams> args)
|
||||
{
|
||||
var models = await Discord.ApiClient.ModifyGuildRoles(Id, args).ConfigureAwait(false);
|
||||
Update(models);
|
||||
Update(models, UpdateSource.Rest);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task Leave()
|
||||
{
|
||||
await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task Delete()
|
||||
{
|
||||
await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<User>> GetBans()
|
||||
|
||||
public async Task<IReadOnlyCollection<IUser>> GetBans()
|
||||
{
|
||||
var models = await Discord.ApiClient.GetGuildBans(Id).ConfigureAwait(false);
|
||||
return models.Select(x => new PublicUser(Discord, x));
|
||||
return models.Select(x => new User(Discord, x)).ToImmutableArray();
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public Task AddBan(IUser user, int pruneDays = 0) => AddBan(user, pruneDays);
|
||||
/// <inheritdoc />
|
||||
public async Task AddBan(ulong userId, int pruneDays = 0)
|
||||
{
|
||||
var args = new CreateGuildBanParams() { PruneDays = pruneDays };
|
||||
await Discord.ApiClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public Task RemoveBan(IUser user) => RemoveBan(user.Id);
|
||||
/// <inheritdoc />
|
||||
public async Task RemoveBan(ulong userId)
|
||||
{
|
||||
await Discord.ApiClient.RemoveGuildBan(Id, userId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary> Gets the channel in this guild with the provided id, or null if not found. </summary>
|
||||
public async Task<GuildChannel> GetChannel(ulong id)
|
||||
|
||||
public virtual async Task<IGuildChannel> GetChannel(ulong id)
|
||||
{
|
||||
var model = await Discord.ApiClient.GetChannel(Id, id).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
return ToChannel(model);
|
||||
return null;
|
||||
}
|
||||
/// <summary> Gets a collection of all channels in this guild. </summary>
|
||||
public async Task<IEnumerable<GuildChannel>> GetChannels()
|
||||
public virtual async Task<IReadOnlyCollection<IGuildChannel>> GetChannels()
|
||||
{
|
||||
var models = await Discord.ApiClient.GetGuildChannels(Id).ConfigureAwait(false);
|
||||
return models.Select(x => ToChannel(x));
|
||||
return models.Select(x => ToChannel(x)).ToImmutableArray();
|
||||
}
|
||||
/// <summary> Creates a new text channel. </summary>
|
||||
public async Task<TextChannel> CreateTextChannel(string name)
|
||||
public async Task<ITextChannel> CreateTextChannel(string name)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -205,8 +180,7 @@ namespace Discord.Rest
|
||||
var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false);
|
||||
return new TextChannel(this, model);
|
||||
}
|
||||
/// <summary> Creates a new voice channel. </summary>
|
||||
public async Task<VoiceChannel> CreateVoiceChannel(string name)
|
||||
public async Task<IVoiceChannel> CreateVoiceChannel(string name)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -214,29 +188,25 @@ namespace Discord.Rest
|
||||
var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false);
|
||||
return new VoiceChannel(this, model);
|
||||
}
|
||||
|
||||
/// <summary> Gets a collection of all integrations attached to this guild. </summary>
|
||||
public async Task<IEnumerable<GuildIntegration>> GetIntegrations()
|
||||
|
||||
public async Task<IReadOnlyCollection<IGuildIntegration>> GetIntegrations()
|
||||
{
|
||||
var models = await Discord.ApiClient.GetGuildIntegrations(Id).ConfigureAwait(false);
|
||||
return models.Select(x => new GuildIntegration(this, x));
|
||||
return models.Select(x => new GuildIntegration(this, x)).ToImmutableArray();
|
||||
}
|
||||
/// <summary> Creates a new integration for this guild. </summary>
|
||||
public async Task<GuildIntegration> CreateIntegration(ulong id, string type)
|
||||
public async Task<IGuildIntegration> CreateIntegration(ulong id, string type)
|
||||
{
|
||||
var args = new CreateGuildIntegrationParams { Id = id, Type = type };
|
||||
var model = await Discord.ApiClient.CreateGuildIntegration(Id, args).ConfigureAwait(false);
|
||||
return new GuildIntegration(this, model);
|
||||
}
|
||||
|
||||
/// <summary> Gets a collection of all invites to this guild. </summary>
|
||||
public async Task<IEnumerable<InviteMetadata>> GetInvites()
|
||||
|
||||
public async Task<IReadOnlyCollection<IInviteMetadata>> GetInvites()
|
||||
{
|
||||
var models = await Discord.ApiClient.GetGuildInvites(Id).ConfigureAwait(false);
|
||||
return models.Select(x => new InviteMetadata(Discord, x));
|
||||
return models.Select(x => new InviteMetadata(Discord, x)).ToImmutableArray();
|
||||
}
|
||||
/// <summary> Creates a new invite to this guild. </summary>
|
||||
public async Task<InviteMetadata> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false)
|
||||
public async Task<IInviteMetadata> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false)
|
||||
{
|
||||
if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge));
|
||||
if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses));
|
||||
@@ -251,18 +221,15 @@ namespace Discord.Rest
|
||||
var model = await Discord.ApiClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false);
|
||||
return new InviteMetadata(Discord, model);
|
||||
}
|
||||
|
||||
/// <summary> Gets the role in this guild with the provided id, or null if not found. </summary>
|
||||
|
||||
public Role GetRole(ulong id)
|
||||
{
|
||||
Role result = null;
|
||||
if (_roles?.TryGetValue(id, out result) == true)
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary> Creates a new role. </summary>
|
||||
public async Task<Role> CreateRole(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false)
|
||||
}
|
||||
public async Task<IRole> CreateRole(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -280,34 +247,30 @@ namespace Discord.Rest
|
||||
return role;
|
||||
}
|
||||
|
||||
/// <summary> Gets a collection of all users in this guild. </summary>
|
||||
public async Task<IEnumerable<GuildUser>> GetUsers()
|
||||
{
|
||||
var args = new GetGuildMembersParams();
|
||||
var models = await Discord.ApiClient.GetGuildMembers(Id, args).ConfigureAwait(false);
|
||||
return models.Select(x => new GuildUser(this, x));
|
||||
}
|
||||
/// <summary> Gets a paged collection of all users in this guild. </summary>
|
||||
public async Task<IEnumerable<GuildUser>> GetUsers(int limit, int offset)
|
||||
{
|
||||
var args = new GetGuildMembersParams { Limit = limit, Offset = offset };
|
||||
var models = await Discord.ApiClient.GetGuildMembers(Id, args).ConfigureAwait(false);
|
||||
return models.Select(x => new GuildUser(this, x));
|
||||
}
|
||||
/// <summary> Gets the user in this guild with the provided id, or null if not found. </summary>
|
||||
public async Task<GuildUser> GetUser(ulong id)
|
||||
public virtual async Task<IGuildUser> GetUser(ulong id)
|
||||
{
|
||||
var model = await Discord.ApiClient.GetGuildMember(Id, id).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
return new GuildUser(this, model);
|
||||
return new GuildUser(this, new User(Discord, model.User), model);
|
||||
return null;
|
||||
}
|
||||
/// <summary> Gets a the current user. </summary>
|
||||
public async Task<GuildUser> GetCurrentUser()
|
||||
public virtual async Task<IGuildUser> GetCurrentUser()
|
||||
{
|
||||
var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false);
|
||||
return await GetUser(currentUser.Id).ConfigureAwait(false);
|
||||
}
|
||||
public virtual async Task<IReadOnlyCollection<IGuildUser>> GetUsers()
|
||||
{
|
||||
var args = new GetGuildMembersParams();
|
||||
var models = await Discord.ApiClient.GetGuildMembers(Id, args).ConfigureAwait(false);
|
||||
return models.Select(x => new GuildUser(this, new User(Discord, x.User), x)).ToImmutableArray();
|
||||
}
|
||||
public virtual async Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset)
|
||||
{
|
||||
var args = new GetGuildMembersParams { Limit = limit, Offset = offset };
|
||||
var models = await Discord.ApiClient.GetGuildMembers(Id, args).ConfigureAwait(false);
|
||||
return models.Select(x => new GuildUser(this, new User(Discord, x.User), x)).ToImmutableArray();
|
||||
}
|
||||
public async Task<int> PruneUsers(int days = 30, bool simulate = false)
|
||||
{
|
||||
var args = new GuildPruneParams() { Days = days };
|
||||
@@ -324,45 +287,22 @@ namespace Discord.Rest
|
||||
switch (model.Type)
|
||||
{
|
||||
case ChannelType.Text:
|
||||
default:
|
||||
return new TextChannel(this, model);
|
||||
case ChannelType.Voice:
|
||||
return new VoiceChannel(this, model);
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown channel type: {model.Type}");
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
private string DebuggerDisplay => $"{Name} ({Id})";
|
||||
|
||||
IEnumerable<Emoji> IGuild.Emojis => Emojis;
|
||||
ulong IGuild.EveryoneRoleId => EveryoneRole.Id;
|
||||
IEnumerable<string> IGuild.Features => Features;
|
||||
IRole IGuild.EveryoneRole => EveryoneRole;
|
||||
IReadOnlyCollection<Emoji> IGuild.Emojis => Emojis;
|
||||
IReadOnlyCollection<string> IGuild.Features => Features;
|
||||
|
||||
async Task<IEnumerable<IUser>> IGuild.GetBans()
|
||||
=> await GetBans().ConfigureAwait(false);
|
||||
async Task<IGuildChannel> IGuild.GetChannel(ulong id)
|
||||
=> await GetChannel(id).ConfigureAwait(false);
|
||||
async Task<IEnumerable<IGuildChannel>> IGuild.GetChannels()
|
||||
=> await GetChannels().ConfigureAwait(false);
|
||||
async Task<IInviteMetadata> IGuild.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd)
|
||||
=> await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false);
|
||||
async Task<IRole> IGuild.CreateRole(string name, GuildPermissions? permissions, Color? color, bool isHoisted)
|
||||
=> await CreateRole(name, permissions, color, isHoisted).ConfigureAwait(false);
|
||||
async Task<ITextChannel> IGuild.CreateTextChannel(string name)
|
||||
=> await CreateTextChannel(name).ConfigureAwait(false);
|
||||
async Task<IVoiceChannel> IGuild.CreateVoiceChannel(string name)
|
||||
=> await CreateVoiceChannel(name).ConfigureAwait(false);
|
||||
async Task<IEnumerable<IInviteMetadata>> IGuild.GetInvites()
|
||||
=> await GetInvites().ConfigureAwait(false);
|
||||
Task<IRole> IGuild.GetRole(ulong id)
|
||||
=> Task.FromResult<IRole>(GetRole(id));
|
||||
Task<IEnumerable<IRole>> IGuild.GetRoles()
|
||||
=> Task.FromResult<IEnumerable<IRole>>(Roles);
|
||||
async Task<IGuildUser> IGuild.GetUser(ulong id)
|
||||
=> await GetUser(id).ConfigureAwait(false);
|
||||
async Task<IGuildUser> IGuild.GetCurrentUser()
|
||||
=> await GetCurrentUser().ConfigureAwait(false);
|
||||
async Task<IEnumerable<IGuildUser>> IGuild.GetUsers()
|
||||
=> await GetUsers().ConfigureAwait(false);
|
||||
IRole IGuild.GetRole(ulong id) => GetRole(id);
|
||||
}
|
||||
}
|
||||
18
src/Discord.Net/Entities/Guilds/GuildEmbed.cs
Normal file
18
src/Discord.Net/Entities/Guilds/GuildEmbed.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Model = Discord.API.GuildEmbed;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public struct GuildEmbed
|
||||
{
|
||||
public bool IsEnabled { get; private set; }
|
||||
public ulong? ChannelId { get; private set; }
|
||||
|
||||
public GuildEmbed(bool isEnabled, ulong? channelId)
|
||||
{
|
||||
ChannelId = channelId;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
internal GuildEmbed(Model model)
|
||||
: this(model.Enabled, model.ChannelId) { }
|
||||
}
|
||||
}
|
||||
@@ -4,47 +4,37 @@ using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Integration;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class GuildIntegration : IGuildIntegration
|
||||
internal class GuildIntegration : Entity<ulong>, IGuildIntegration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ulong Id { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string Name { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string Type { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsSyncing { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public ulong ExpireBehavior { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public ulong ExpireGracePeriod { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public DateTime SyncedAt { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guild Guild { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public Role Role { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public User User { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public IntegrationAccount Account { get; private set; }
|
||||
internal DiscordClient Discord => Guild.Discord;
|
||||
|
||||
internal GuildIntegration(Guild guild, Model model)
|
||||
|
||||
public override DiscordClient Discord => Guild.Discord;
|
||||
|
||||
public GuildIntegration(Guild guild, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Guild = guild;
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
|
||||
private void Update(Model model)
|
||||
private void Update(Model model, UpdateSource source)
|
||||
{
|
||||
Id = model.Id;
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
Name = model.Name;
|
||||
Type = model.Type;
|
||||
IsEnabled = model.Enabled;
|
||||
@@ -53,16 +43,14 @@ namespace Discord.Rest
|
||||
ExpireGracePeriod = model.ExpireGracePeriod;
|
||||
SyncedAt = model.SyncedAt;
|
||||
|
||||
Role = Guild.GetRole(model.RoleId);
|
||||
User = new PublicUser(Discord, model.User);
|
||||
Role = Guild.GetRole(model.RoleId) as Role;
|
||||
User = new User(Discord, model.User);
|
||||
}
|
||||
|
||||
/// <summary> </summary>
|
||||
|
||||
public async Task Delete()
|
||||
{
|
||||
await Discord.ApiClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false);
|
||||
}
|
||||
/// <summary> </summary>
|
||||
public async Task Modify(Action<ModifyGuildIntegrationParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
@@ -71,9 +59,8 @@ namespace Discord.Rest
|
||||
func(args);
|
||||
var model = await Discord.ApiClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false);
|
||||
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
/// <summary> </summary>
|
||||
public async Task Sync()
|
||||
{
|
||||
await Discord.ApiClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false);
|
||||
@@ -83,8 +70,7 @@ namespace Discord.Rest
|
||||
private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})";
|
||||
|
||||
IGuild IGuildIntegration.Guild => Guild;
|
||||
IRole IGuildIntegration.Role => Role;
|
||||
IUser IGuildIntegration.User => User;
|
||||
IntegrationAccount IGuildIntegration.Account => Account;
|
||||
IRole IGuildIntegration.Role => Role;
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,17 @@ namespace Discord
|
||||
{
|
||||
public interface IGuild : IDeletable, ISnowflakeEntity, IUpdateable
|
||||
{
|
||||
/// <summary> Gets the name of this guild. </summary>
|
||||
string Name { get; }
|
||||
/// <summary> Gets the amount of time (in seconds) a user must be inactive in a voice channel for until they are automatically moved to the AFK voice channel, if one is set. </summary>
|
||||
int AFKTimeout { get; }
|
||||
/// <summary> Returns true if this guild is embeddable (e.g. widget) </summary>
|
||||
bool IsEmbeddable { get; }
|
||||
/// <summary> Gets the name of this guild. </summary>
|
||||
string Name { get; }
|
||||
int VerificationLevel { get; }
|
||||
/// <summary> Returns the url to this guild's icon, or null if one is not set. </summary>
|
||||
string IconUrl { get; }
|
||||
/// <summary> Returns the url to this guild's splash image, or null if one is not set. </summary>
|
||||
string SplashUrl { get; }
|
||||
|
||||
/// <summary> Gets the id of the AFK voice channel for this guild if set, or null if not. </summary>
|
||||
ulong? AFKChannelId { get; }
|
||||
@@ -21,22 +25,19 @@ namespace Discord
|
||||
ulong DefaultChannelId { get; }
|
||||
/// <summary> Gets the id of the embed channel for this guild if set, or null if not. </summary>
|
||||
ulong? EmbedChannelId { get; }
|
||||
/// <summary> Gets the id of the role containing all users in this guild. </summary>
|
||||
ulong EveryoneRoleId { get; }
|
||||
/// <summary> Gets the id of the user that created this guild. </summary>
|
||||
ulong OwnerId { get; }
|
||||
/// <summary> Gets the id of the server region hosting this guild's voice channels. </summary>
|
||||
/// <summary> Gets the id of the region hosting this guild's voice channels. </summary>
|
||||
string VoiceRegionId { get; }
|
||||
|
||||
/// <summary> Returns the url to this server's icon, or null if one is not set. </summary>
|
||||
string IconUrl { get; }
|
||||
/// <summary> Returns the url to this server's splash image, or null if one is not set. </summary>
|
||||
string SplashUrl { get; }
|
||||
|
||||
/// <summary> Gets the built-in role containing all users in this guild. </summary>
|
||||
IRole EveryoneRole { get; }
|
||||
/// <summary> Gets a collection of all custom emojis for this guild. </summary>
|
||||
IEnumerable<Emoji> Emojis { get; }
|
||||
IReadOnlyCollection<Emoji> Emojis { get; }
|
||||
/// <summary> Gets a collection of all extra features added to this guild. </summary>
|
||||
IEnumerable<string> Features { get; }
|
||||
IReadOnlyCollection<string> Features { get; }
|
||||
/// <summary> Gets a collection of all roles in this guild. </summary>
|
||||
IReadOnlyCollection<IRole> Roles { get; }
|
||||
|
||||
/// <summary> Modifies this guild. </summary>
|
||||
Task Modify(Action<ModifyGuildParams> func);
|
||||
@@ -50,7 +51,7 @@ namespace Discord
|
||||
Task Leave();
|
||||
|
||||
/// <summary> Gets a collection of all users banned on this guild. </summary>
|
||||
Task<IEnumerable<IUser>> GetBans();
|
||||
Task<IReadOnlyCollection<IUser>> GetBans();
|
||||
/// <summary> Bans the provided user from this guild and optionally prunes their recent messages. </summary>
|
||||
Task AddBan(IUser user, int pruneDays = 0);
|
||||
/// <summary> Bans the provided user id from this guild and optionally prunes their recent messages. </summary>
|
||||
@@ -61,7 +62,7 @@ namespace Discord
|
||||
Task RemoveBan(ulong userId);
|
||||
|
||||
/// <summary> Gets a collection of all channels in this guild. </summary>
|
||||
Task<IEnumerable<IGuildChannel>> GetChannels();
|
||||
Task<IReadOnlyCollection<IGuildChannel>> GetChannels();
|
||||
/// <summary> Gets the channel in this guild with the provided id, or null if not found. </summary>
|
||||
Task<IGuildChannel> GetChannel(ulong id);
|
||||
/// <summary> Creates a new text channel. </summary>
|
||||
@@ -70,7 +71,7 @@ namespace Discord
|
||||
Task<IVoiceChannel> CreateVoiceChannel(string name);
|
||||
|
||||
/// <summary> Gets a collection of all invites to this guild. </summary>
|
||||
Task<IEnumerable<IInviteMetadata>> GetInvites();
|
||||
Task<IReadOnlyCollection<IInviteMetadata>> GetInvites();
|
||||
/// <summary> Creates a new invite to this guild. </summary>
|
||||
/// <param name="maxAge"> The time (in seconds) until the invite expires. Set to null to never expire. </param>
|
||||
/// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param>
|
||||
@@ -78,15 +79,13 @@ namespace Discord
|
||||
/// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param>
|
||||
Task<IInviteMetadata> CreateInvite(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, bool withXkcd = false);
|
||||
|
||||
/// <summary> Gets a collection of all roles in this guild. </summary>
|
||||
Task<IEnumerable<IRole>> GetRoles();
|
||||
/// <summary> Gets the role in this guild with the provided id, or null if not found. </summary>
|
||||
Task<IRole> GetRole(ulong id);
|
||||
IRole GetRole(ulong id);
|
||||
/// <summary> Creates a new role. </summary>
|
||||
Task<IRole> CreateRole(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false);
|
||||
|
||||
/// <summary> Gets a collection of all users in this guild. </summary>
|
||||
Task<IEnumerable<IGuildUser>> GetUsers();
|
||||
Task<IReadOnlyCollection<IGuildUser>> GetUsers();
|
||||
/// <summary> Gets the user in this guild with the provided id, or null if not found. </summary>
|
||||
Task<IGuildUser> GetUser(ulong id);
|
||||
/// <summary> Gets the current user for this guild. </summary>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Discord
|
||||
{
|
||||
public interface IGuildEmbed : ISnowflakeEntity
|
||||
{
|
||||
bool IsEnabled { get; }
|
||||
ulong? ChannelId { get; }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
{
|
||||
/// <summary> Gets the name of this guild. </summary>
|
||||
string Name { get; }
|
||||
/// <summary> Returns the url to this server's icon, or null if one is not set. </summary>
|
||||
/// <summary> Returns the url to this guild's icon, or null if one is not set. </summary>
|
||||
string IconUrl { get; }
|
||||
/// <summary> Returns true if the current user owns this guild. </summary>
|
||||
bool IsOwner { get; }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace Discord
|
||||
{
|
||||
public interface IVoiceRegion : IEntity<string>
|
||||
public interface IVoiceRegion
|
||||
{
|
||||
/// <summary> Gets the unique identifier for this voice region. </summary>
|
||||
string Id { get; }
|
||||
/// <summary> Gets the name of this voice region. </summary>
|
||||
string Name { get; }
|
||||
/// <summary> Returns true if this voice region is exclusive to VIP accounts. </summary>
|
||||
|
||||
@@ -5,10 +5,7 @@ namespace Discord
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
public struct IntegrationAccount
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Id { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name { get; private set; }
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
@@ -1,50 +1,42 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.UserGuild;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class UserGuild : IUserGuild
|
||||
internal class UserGuild : SnowflakeEntity, IUserGuild
|
||||
{
|
||||
private string _iconId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ulong Id { get; }
|
||||
internal IDiscordClient Discord { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public string Name { get; private set; }
|
||||
public bool IsOwner { get; private set; }
|
||||
public GuildPermissions Permissions { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
/// <inheritdoc />
|
||||
public override DiscordClient Discord { get; }
|
||||
|
||||
public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId);
|
||||
|
||||
internal UserGuild(IDiscordClient discord, Model model)
|
||||
public UserGuild(DiscordClient discord, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Discord = discord;
|
||||
Id = model.Id;
|
||||
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
private void Update(Model model)
|
||||
private void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
_iconId = model.Icon;
|
||||
IsOwner = model.Owner;
|
||||
Name = model.Name;
|
||||
Permissions = new GuildPermissions(model.Permissions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Leave()
|
||||
{
|
||||
await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task Delete()
|
||||
{
|
||||
await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false);
|
||||
@@ -4,22 +4,16 @@ using Model = Discord.API.VoiceRegion;
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
public class VoiceRegion : IVoiceRegion
|
||||
internal class VoiceRegion : IVoiceRegion
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Id { get; }
|
||||
/// <inheritdoc />
|
||||
public string Name { get; }
|
||||
/// <inheritdoc />
|
||||
public bool IsVip { get; }
|
||||
/// <inheritdoc />
|
||||
public bool IsOptimal { get; }
|
||||
/// <inheritdoc />
|
||||
public string SampleHostname { get; }
|
||||
/// <inheritdoc />
|
||||
public int SamplePort { get; }
|
||||
|
||||
internal VoiceRegion(Model model)
|
||||
public VoiceRegion(Model model)
|
||||
{
|
||||
Id = model.Id;
|
||||
Name = model.Name;
|
||||
|
||||
@@ -4,5 +4,9 @@ namespace Discord
|
||||
{
|
||||
/// <summary> Gets the unique identifier for this object. </summary>
|
||||
TId Id { get; }
|
||||
|
||||
//TODO: What do we do when an object is destroyed due to reconnect? This summary isn't correct.
|
||||
/// <summary> Returns true if this object is getting live updates from the DiscordClient. </summary>
|
||||
bool IsAttached { get;}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Discord
|
||||
{
|
||||
public interface IUpdateable
|
||||
{
|
||||
/// <summary> Ensures this objects's cached properties reflect its current state on the Discord server. </summary>
|
||||
/// <summary> Updates this object's properties with its current state. </summary>
|
||||
Task Update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Discord
|
||||
/// <summary> Gets the id of the guild this invite is linked to. </summary>
|
||||
ulong GuildId { get; }
|
||||
|
||||
/// <summary> Accepts this invite and joins the target server. This will fail on bot accounts. </summary>
|
||||
/// <summary> Accepts this invite and joins the target guild. This will fail on bot accounts. </summary>
|
||||
Task Accept();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,38 +5,31 @@ using Model = Discord.API.Invite;
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class Invite : IInvite
|
||||
internal class Invite : Entity<string>, IInvite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Code { get; }
|
||||
internal IDiscordClient Discord { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ulong GuildId { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public ulong ChannelId { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string XkcdCode { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string GuildName { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string ChannelName { get; private set; }
|
||||
public string GuildName { get; private set; }
|
||||
public string XkcdCode { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ulong ChannelId { get; private set; }
|
||||
public ulong GuildId { get; private set; }
|
||||
public override DiscordClient Discord { get; }
|
||||
|
||||
public string Code => Id;
|
||||
public string Url => $"{DiscordConfig.InviteUrl}/{XkcdCode ?? Code}";
|
||||
/// <inheritdoc />
|
||||
public string XkcdUrl => XkcdCode != null ? $"{DiscordConfig.InviteUrl}/{XkcdCode}" : null;
|
||||
|
||||
|
||||
internal Invite(IDiscordClient discord, Model model)
|
||||
public Invite(DiscordClient discord, Model model)
|
||||
: base(model.Code)
|
||||
{
|
||||
Discord = discord;
|
||||
Code = model.Code;
|
||||
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
protected virtual void Update(Model model)
|
||||
protected void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
XkcdCode = model.XkcdPass;
|
||||
GuildId = model.Guild.Id;
|
||||
ChannelId = model.Channel.Id;
|
||||
@@ -44,22 +37,16 @@ namespace Discord
|
||||
ChannelName = model.Channel.Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Accept()
|
||||
{
|
||||
await Discord.ApiClient.AcceptInvite(Code).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete()
|
||||
{
|
||||
await Discord.ApiClient.DeleteInvite(Code).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => XkcdUrl ?? Url;
|
||||
private string DebuggerDisplay => $"{XkcdUrl ?? Url} ({GuildName} / {ChannelName})";
|
||||
|
||||
string IEntity<string>.Id => Code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,26 +2,23 @@
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class InviteMetadata : Invite, IInviteMetadata
|
||||
internal class InviteMetadata : Invite, IInviteMetadata
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool IsRevoked { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsTemporary { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int? MaxAge { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int? MaxUses { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int Uses { get; private set; }
|
||||
|
||||
internal InviteMetadata(IDiscordClient client, Model model)
|
||||
public InviteMetadata(DiscordClient client, Model model)
|
||||
: base(client, model)
|
||||
{
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
private void Update(Model model)
|
||||
private void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
IsRevoked = model.Revoked;
|
||||
IsTemporary = model.Temporary;
|
||||
MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null;
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public struct Embed
|
||||
internal class Embed : IEmbed
|
||||
{
|
||||
public string Url { get; }
|
||||
public string Type { get; }
|
||||
public string Title { get; }
|
||||
public string Description { get; }
|
||||
public string Url { get; }
|
||||
public string Title { get; }
|
||||
public string Type { get; }
|
||||
public EmbedProvider Provider { get; }
|
||||
public EmbedThumbnail Thumbnail { get; }
|
||||
|
||||
internal Embed(Model model)
|
||||
public Embed(Model model)
|
||||
{
|
||||
Url = model.Url;
|
||||
Type = model.Type;
|
||||
|
||||
@@ -7,10 +7,12 @@ namespace Discord
|
||||
public string Name { get; }
|
||||
public string Url { get; }
|
||||
|
||||
internal EmbedProvider(Model model)
|
||||
public EmbedProvider(string name, string url)
|
||||
{
|
||||
Name = model.Name;
|
||||
Url = model.Url;
|
||||
Name = name;
|
||||
Url = url;
|
||||
}
|
||||
internal EmbedProvider(Model model)
|
||||
: this(model.Name, model.Url) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@ namespace Discord
|
||||
public int? Height { get; }
|
||||
public int? Width { get; }
|
||||
|
||||
internal EmbedThumbnail(Model model)
|
||||
public EmbedThumbnail(string url, string proxyUrl, int? height, int? width)
|
||||
{
|
||||
Url = model.Url;
|
||||
ProxyUrl = model.ProxyUrl;
|
||||
Height = model.Height;
|
||||
Width = model.Width;
|
||||
Url = url;
|
||||
ProxyUrl = proxyUrl;
|
||||
Height = height;
|
||||
Width = width;
|
||||
}
|
||||
|
||||
internal EmbedThumbnail(Model model)
|
||||
: this(model.Url, model.ProxyUrl, model.Height, model.Width) { }
|
||||
}
|
||||
}
|
||||
|
||||
12
src/Discord.Net/Entities/Messages/IEmbed.cs
Normal file
12
src/Discord.Net/Entities/Messages/IEmbed.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Discord
|
||||
{
|
||||
public interface IEmbed
|
||||
{
|
||||
string Url { get; }
|
||||
string Type { get; }
|
||||
string Title { get; }
|
||||
string Description { get; }
|
||||
EmbedProvider Provider { get; }
|
||||
EmbedThumbnail Thumbnail { get; }
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public interface IMessage : IDeletable, ISnowflakeEntity
|
||||
public interface IMessage : IDeletable, ISnowflakeEntity, IUpdateable
|
||||
{
|
||||
/// <summary> Gets the time of this message's last edit, if any. </summary>
|
||||
DateTime? EditedTimestamp { get; }
|
||||
@@ -16,23 +16,22 @@ namespace Discord
|
||||
/// <summary> Returns the text for this message after mention processing. </summary>
|
||||
string Text { get; }
|
||||
/// <summary> Gets the time this message was sent. </summary>
|
||||
DateTime Timestamp { get; } //TODO: Is this different from IHasSnowflake.CreatedAt?
|
||||
DateTime Timestamp { get; }
|
||||
|
||||
/// <summary> Gets the channel this message was sent to. </summary>
|
||||
IMessageChannel Channel { get; }
|
||||
/// <summary> Gets the author of this message. </summary>
|
||||
IUser Author { get; }
|
||||
|
||||
/// <summary> Returns a collection of all attachments included in this message. </summary>
|
||||
IReadOnlyList<Attachment> Attachments { get; }
|
||||
IReadOnlyCollection<Attachment> Attachments { get; }
|
||||
/// <summary> Returns a collection of all embeds included in this message. </summary>
|
||||
IReadOnlyList<Embed> Embeds { get; }
|
||||
IReadOnlyCollection<IEmbed> Embeds { get; }
|
||||
/// <summary> Returns a collection of channel ids mentioned in this message. </summary>
|
||||
IReadOnlyList<ulong> MentionedChannelIds { get; }
|
||||
IReadOnlyCollection<ulong> MentionedChannelIds { get; }
|
||||
/// <summary> Returns a collection of role ids mentioned in this message. </summary>
|
||||
IReadOnlyList<ulong> MentionedRoleIds { get; }
|
||||
IReadOnlyCollection<ulong> MentionedRoleIds { get; }
|
||||
/// <summary> Returns a collection of user ids mentioned in this message. </summary>
|
||||
IReadOnlyList<IUser> MentionedUsers { get; }
|
||||
IReadOnlyCollection<IUser> MentionedUsers { get; }
|
||||
|
||||
/// <summary> Modifies this message. </summary>
|
||||
Task Modify(Action<ModifyMessageParams> func);
|
||||
|
||||
@@ -6,55 +6,40 @@ using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Message;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class Message : IMessage
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ulong Id { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
internal class Message : SnowflakeEntity, IMessage
|
||||
{
|
||||
public DateTime? EditedTimestamp { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsTTS { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string RawText { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string Text { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public DateTime Timestamp { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public IMessageChannel Channel { get; }
|
||||
/// <inheritdoc />
|
||||
public IUser Author { get; }
|
||||
|
||||
public ImmutableArray<Attachment> Attachments { get; private set; }
|
||||
public ImmutableArray<Embed> Embeds { get; private set; }
|
||||
public ImmutableArray<ulong> MentionedChannelIds { get; private set; }
|
||||
public ImmutableArray<ulong> MentionedRoleIds { get; private set; }
|
||||
public ImmutableArray<User> MentionedUsers { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Attachment> Attachments { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Embed> Embeds { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<IUser> MentionedUsers { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ulong> MentionedChannelIds { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ulong> MentionedRoleIds { get; private set; }
|
||||
public override DiscordClient Discord => (Channel as Entity<ulong>).Discord;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
internal DiscordClient Discord => (Channel as TextChannel)?.Discord ?? (Channel as DMChannel).Discord;
|
||||
|
||||
internal Message(IMessageChannel channel, Model model)
|
||||
public Message(IMessageChannel channel, IUser author, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Id = model.Id;
|
||||
Channel = channel;
|
||||
Author = new PublicUser(Discord, model.Author);
|
||||
Author = author;
|
||||
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
private void Update(Model model)
|
||||
private void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
var guildChannel = Channel as GuildChannel;
|
||||
var guild = guildChannel?.Guild;
|
||||
var discord = Discord;
|
||||
@@ -72,7 +57,7 @@ namespace Discord.Rest
|
||||
Attachments = ImmutableArray.Create(attachments);
|
||||
}
|
||||
else
|
||||
Attachments = Array.Empty<Attachment>();
|
||||
Attachments = ImmutableArray.Create<Attachment>();
|
||||
|
||||
if (model.Embeds.Length > 0)
|
||||
{
|
||||
@@ -82,17 +67,17 @@ namespace Discord.Rest
|
||||
Embeds = ImmutableArray.Create(embeds);
|
||||
}
|
||||
else
|
||||
Embeds = Array.Empty<Embed>();
|
||||
Embeds = ImmutableArray.Create<Embed>();
|
||||
|
||||
if (guildChannel != null && model.Mentions.Length > 0)
|
||||
{
|
||||
var mentions = new PublicUser[model.Mentions.Length];
|
||||
var mentions = new User[model.Mentions.Length];
|
||||
for (int i = 0; i < model.Mentions.Length; i++)
|
||||
mentions[i] = new PublicUser(discord, model.Mentions[i]);
|
||||
mentions[i] = new User(discord, model.Mentions[i]);
|
||||
MentionedUsers = ImmutableArray.Create(mentions);
|
||||
}
|
||||
else
|
||||
MentionedUsers = Array.Empty<PublicUser>();
|
||||
MentionedUsers = ImmutableArray.Create<User>();
|
||||
|
||||
if (guildChannel != null)
|
||||
{
|
||||
@@ -105,14 +90,20 @@ namespace Discord.Rest
|
||||
}
|
||||
else
|
||||
{
|
||||
MentionedChannelIds = Array.Empty<ulong>();
|
||||
MentionedRoleIds = Array.Empty<ulong>();
|
||||
MentionedChannelIds = ImmutableArray.Create<ulong>();
|
||||
MentionedRoleIds = ImmutableArray.Create<ulong>();
|
||||
}
|
||||
|
||||
Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Update()
|
||||
{
|
||||
if (IsAttached) throw new NotSupportedException();
|
||||
|
||||
var model = await Discord.ApiClient.GetChannelMessage(Channel.Id, Id).ConfigureAwait(false);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
public async Task Modify(Action<ModifyMessageParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
@@ -126,10 +117,8 @@ namespace Discord.Rest
|
||||
model = await Discord.ApiClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false);
|
||||
else
|
||||
model = await Discord.ApiClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false);
|
||||
Update(model);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
public async Task Delete()
|
||||
{
|
||||
var guildChannel = Channel as GuildChannel;
|
||||
@@ -140,9 +129,12 @@ namespace Discord.Rest
|
||||
}
|
||||
|
||||
public override string ToString() => Text;
|
||||
private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}";
|
||||
private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Length > 0 ? $" [{Attachments.Length} Attachments]" : "")}";
|
||||
|
||||
IUser IMessage.Author => Author;
|
||||
IReadOnlyList<IUser> IMessage.MentionedUsers => MentionedUsers;
|
||||
IReadOnlyCollection<Attachment> IMessage.Attachments => Attachments;
|
||||
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds;
|
||||
IReadOnlyCollection<ulong> IMessage.MentionedChannelIds => MentionedChannelIds;
|
||||
IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoleIds;
|
||||
IReadOnlyCollection<IUser> IMessage.MentionedUsers => MentionedUsers;
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ namespace Discord
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
|
||||
public override string ToString() => RawValue.ToString();
|
||||
private string DebuggerDisplay => $"{RawValue} ({string.Join(", ", ToList())})";
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Discord
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
|
||||
public override string ToString() => RawValue.ToString();
|
||||
private string DebuggerDisplay => $"{RawValue} ({string.Join(", ", ToList())})";
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@ namespace Discord
|
||||
public OverwritePermissions Permissions { get; }
|
||||
|
||||
/// <summary> Creates a new Overwrite with provided target information and modified permissions. </summary>
|
||||
internal Overwrite(Model model)
|
||||
public Overwrite(ulong targetId, PermissionTarget targetType, OverwritePermissions permissions)
|
||||
{
|
||||
TargetId = model.TargetId;
|
||||
TargetType = model.TargetType;
|
||||
Permissions = new OverwritePermissions(model.Allow, model.Deny);
|
||||
TargetId = targetId;
|
||||
TargetType = targetType;
|
||||
Permissions = permissions;
|
||||
}
|
||||
|
||||
internal Overwrite(Model model)
|
||||
: this(model.TargetId, model.TargetType, new OverwritePermissions(model.Allow, model.Deny)) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace Discord
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
|
||||
public override string ToString() => $"Allow {AllowValue}, Deny {DenyValue}";
|
||||
private string DebuggerDisplay =>
|
||||
$"Allow {AllowValue} ({string.Join(", ", ToAllowList())})\n" +
|
||||
|
||||
@@ -90,8 +90,8 @@ namespace Discord
|
||||
{
|
||||
var roles = user.Roles;
|
||||
ulong newPermissions = 0;
|
||||
for (int i = 0; i < roles.Count; i++)
|
||||
newPermissions |= roles[i].Permissions.RawValue;
|
||||
foreach (var role in roles)
|
||||
newPermissions |= role.Permissions.RawValue;
|
||||
return newPermissions;
|
||||
}
|
||||
|
||||
@@ -110,25 +110,26 @@ namespace Discord
|
||||
{
|
||||
//Start with this user's guild permissions
|
||||
resolvedPermissions = guildPermissions;
|
||||
var overwrites = channel.PermissionOverwrites;
|
||||
|
||||
Overwrite entry;
|
||||
OverwritePermissions? perms;
|
||||
var roles = user.Roles;
|
||||
if (roles.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < roles.Count; i++)
|
||||
ulong deniedPermissions = 0UL, allowedPermissions = 0UL;
|
||||
foreach (var role in roles)
|
||||
{
|
||||
if (overwrites.TryGetValue(roles[i].Id, out entry))
|
||||
resolvedPermissions &= ~entry.Permissions.DenyValue;
|
||||
}
|
||||
for (int i = 0; i < roles.Count; i++)
|
||||
{
|
||||
if (overwrites.TryGetValue(roles[i].Id, out entry))
|
||||
resolvedPermissions |= entry.Permissions.AllowValue;
|
||||
perms = channel.GetPermissionOverwrite(role);
|
||||
if (perms != null)
|
||||
{
|
||||
deniedPermissions |= perms.Value.DenyValue;
|
||||
allowedPermissions |= perms.Value.AllowValue;
|
||||
}
|
||||
}
|
||||
resolvedPermissions = (resolvedPermissions & ~deniedPermissions) | allowedPermissions;
|
||||
}
|
||||
if (overwrites.TryGetValue(user.Id, out entry))
|
||||
resolvedPermissions = (resolvedPermissions & ~entry.Permissions.DenyValue) | entry.Permissions.AllowValue;
|
||||
perms = channel.GetPermissionOverwrite(user);
|
||||
if (perms != null)
|
||||
resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue;
|
||||
|
||||
#if CSHARP7
|
||||
switch (channel)
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Discord
|
||||
Color Color { get; }
|
||||
/// <summary> Returns true if users of this role are separated in the user list. </summary>
|
||||
bool IsHoisted { get; }
|
||||
/// <summary> Returns true if this role is automatically managed by the Discord server. </summary>
|
||||
/// <summary> Returns true if this role is automatically managed by Discord. </summary>
|
||||
bool IsManaged { get; }
|
||||
/// <summary> Gets the name of this role. </summary>
|
||||
string Name { get; }
|
||||
@@ -25,8 +25,5 @@ namespace Discord
|
||||
|
||||
/// <summary> Modifies this role. </summary>
|
||||
Task Modify(Action<ModifyGuildRoleParams> func);
|
||||
|
||||
/// <summary> Returns a collection of all users that have been assigned this role. </summary>
|
||||
Task<IEnumerable<IGuildUser>> GetUsers();
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,41 @@
|
||||
using Discord.API.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Role;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class Role : IRole, IMentionable
|
||||
internal class Role : SnowflakeEntity, IRole, IMentionable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ulong Id { get; }
|
||||
/// <summary> Returns the guild this role belongs to. </summary>
|
||||
public Guild Guild { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public Color Color { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsHoisted { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsManaged { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string Name { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public GuildPermissions Permissions { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int Position { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
/// <inheritdoc />
|
||||
|
||||
public bool IsEveryone => Id == Guild.Id;
|
||||
/// <inheritdoc />
|
||||
public string Mention => MentionUtils.Mention(this);
|
||||
internal DiscordClient Discord => Guild.Discord;
|
||||
public override DiscordClient Discord => Guild.Discord;
|
||||
|
||||
internal Role(Guild guild, Model model)
|
||||
public Role(Guild guild, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Id = model.Id;
|
||||
Guild = guild;
|
||||
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
internal void Update(Model model)
|
||||
public void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
Name = model.Name;
|
||||
IsHoisted = model.Hoist.Value;
|
||||
IsManaged = model.Managed.Value;
|
||||
@@ -53,7 +43,7 @@ namespace Discord.Rest
|
||||
Color = new Color(model.Color.Value);
|
||||
Permissions = new GuildPermissions(model.Permissions.Value);
|
||||
}
|
||||
/// <summary> Modifies the properties of this role. </summary>
|
||||
|
||||
public async Task Modify(Action<ModifyGuildRoleParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
@@ -61,23 +51,16 @@ namespace Discord.Rest
|
||||
var args = new ModifyGuildRoleParams();
|
||||
func(args);
|
||||
var response = await Discord.ApiClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false);
|
||||
Update(response);
|
||||
Update(response, UpdateSource.Rest);
|
||||
}
|
||||
/// <summary> Deletes this message. </summary>
|
||||
public async Task Delete()
|
||||
=> await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false);
|
||||
|
||||
/// <inheritdoc />
|
||||
{
|
||||
await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
private string DebuggerDisplay => $"{Name} ({Id})";
|
||||
|
||||
ulong IRole.GuildId => Guild.Id;
|
||||
|
||||
async Task<IEnumerable<IGuildUser>> IRole.GetUsers()
|
||||
{
|
||||
//TODO: Rethink this, it isn't paginated or anything...
|
||||
var models = await Discord.ApiClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false);
|
||||
return models.Where(x => x.Roles.Contains(Id)).Select(x => new GuildUser(Guild, x));
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/Discord.Net/Entities/SnowflakeEntity.cs
Normal file
15
src/Discord.Net/Entities/SnowflakeEntity.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal abstract class SnowflakeEntity : Entity<ulong>, ISnowflakeEntity
|
||||
{
|
||||
//TODO: Candidate for Extension Property. Lets us remove this class.
|
||||
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
|
||||
public SnowflakeEntity(ulong id)
|
||||
: base(id)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Discord.Net/Entities/UpdateSource.cs
Normal file
9
src/Discord.Net/Entities/UpdateSource.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Discord
|
||||
{
|
||||
internal enum UpdateSource
|
||||
{
|
||||
Creation,
|
||||
Rest,
|
||||
WebSocket
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,18 @@ using Model = Discord.API.Connection;
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class Connection : IConnection
|
||||
internal class Connection : IConnection
|
||||
{
|
||||
public string Id { get; }
|
||||
public string Type { get; }
|
||||
public string Name { get; }
|
||||
public bool IsRevoked { get; }
|
||||
|
||||
public IEnumerable<ulong> IntegrationIds { get; }
|
||||
public IReadOnlyCollection<ulong> IntegrationIds { get; }
|
||||
|
||||
public Connection(Model model)
|
||||
{
|
||||
Id = model.Id;
|
||||
|
||||
Type = model.Type;
|
||||
Name = model.Name;
|
||||
IsRevoked = model.Revoked;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Discord
|
||||
using Model = Discord.API.Game;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public struct Game
|
||||
{
|
||||
@@ -6,17 +8,15 @@
|
||||
public string StreamUrl { get; }
|
||||
public StreamType StreamType { get; }
|
||||
|
||||
public Game(string name)
|
||||
{
|
||||
Name = name;
|
||||
StreamUrl = null;
|
||||
StreamType = StreamType.NotStreaming;
|
||||
}
|
||||
public Game(string name, string streamUrl, StreamType type)
|
||||
{
|
||||
Name = name;
|
||||
StreamUrl = streamUrl;
|
||||
StreamType = type;
|
||||
}
|
||||
public Game(string name)
|
||||
: this(name, null, StreamType.NotStreaming) { }
|
||||
internal Game(Model model)
|
||||
: this(model.Name, model.StreamUrl, model.StreamType ?? StreamType.NotStreaming) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,70 +6,64 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.GuildMember;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
public class GuildUser : User, IGuildUser
|
||||
internal class GuildUser : IGuildUser, ISnowflakeEntity
|
||||
{
|
||||
private ImmutableArray<Role> _roles;
|
||||
|
||||
public Guild Guild { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDeaf { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsMute { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public DateTime JoinedAt { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string Nickname { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public GuildPermissions GuildPermissions { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Role> Roles => _roles;
|
||||
internal override DiscordClient Discord => Guild.Discord;
|
||||
public Guild Guild { get; private set; }
|
||||
public User User { get; private set; }
|
||||
public ImmutableArray<Role> Roles { get; private set; }
|
||||
|
||||
internal GuildUser(Guild guild, Model model)
|
||||
: base(model.User)
|
||||
public ulong Id => User.Id;
|
||||
public string AvatarUrl => User.AvatarUrl;
|
||||
public DateTime CreatedAt => User.CreatedAt;
|
||||
public ushort Discriminator => User.Discriminator;
|
||||
public Game? Game => User.Game;
|
||||
public bool IsAttached => User.IsAttached;
|
||||
public bool IsBot => User.IsBot;
|
||||
public string Mention => User.Mention;
|
||||
public UserStatus Status => User.Status;
|
||||
public string Username => User.Username;
|
||||
|
||||
public DiscordClient Discord => Guild.Discord;
|
||||
|
||||
public GuildUser(Guild guild, User user, Model model)
|
||||
{
|
||||
Guild = guild;
|
||||
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
internal void Update(Model model)
|
||||
private void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
IsDeaf = model.Deaf;
|
||||
IsMute = model.Mute;
|
||||
JoinedAt = model.JoinedAt.Value;
|
||||
Nickname = model.Nick;
|
||||
|
||||
var roles = ImmutableArray.CreateBuilder<Role>(model.Roles.Length + 1);
|
||||
roles.Add(Guild.EveryoneRole);
|
||||
roles.Add(Guild.EveryoneRole as Role);
|
||||
for (int i = 0; i < model.Roles.Length; i++)
|
||||
roles.Add(Guild.GetRole(model.Roles[i]));
|
||||
_roles = roles.ToImmutable();
|
||||
roles.Add(Guild.GetRole(model.Roles[i]) as Role);
|
||||
Roles = roles.ToImmutable();
|
||||
|
||||
GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this));
|
||||
}
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
if (IsAttached) throw new NotSupportedException();
|
||||
|
||||
var model = await Discord.ApiClient.GetGuildMember(Guild.Id, Id).ConfigureAwait(false);
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
|
||||
public async Task Kick()
|
||||
{
|
||||
await Discord.ApiClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public ChannelPermissions GetPermissions(IGuildChannel channel)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
return new ChannelPermissions(Permissions.ResolveChannel(this, channel, GuildPermissions.RawValue));
|
||||
}
|
||||
|
||||
public async Task Modify(Action<ModifyGuildMemberParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
@@ -82,7 +76,7 @@ namespace Discord.Rest
|
||||
{
|
||||
var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value ?? "" };
|
||||
await Discord.ApiClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false);
|
||||
args.Nickname = new API.Optional<string>(); //Remove
|
||||
args.Nickname = new Optional<string>(); //Remove
|
||||
}
|
||||
|
||||
if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified)
|
||||
@@ -95,18 +89,24 @@ namespace Discord.Rest
|
||||
if (args.Nickname.IsSpecified)
|
||||
Nickname = args.Nickname.Value ?? "";
|
||||
if (args.Roles.IsSpecified)
|
||||
_roles = args.Roles.Value.Select(x => Guild.GetRole(x)).Where(x => x != null).ToImmutableArray();
|
||||
Roles = args.Roles.Value.Select(x => Guild.GetRole(x) as Role).Where(x => x != null).ToImmutableArray();
|
||||
}
|
||||
}
|
||||
public async Task Kick()
|
||||
{
|
||||
await Discord.ApiClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public ChannelPermissions GetPermissions(IGuildChannel channel)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
return new ChannelPermissions(Permissions.ResolveChannel(this, channel, GuildPermissions.RawValue));
|
||||
}
|
||||
|
||||
public Task<IDMChannel> CreateDMChannel() => User.CreateDMChannel();
|
||||
|
||||
IGuild IGuildUser.Guild => Guild;
|
||||
IReadOnlyList<IRole> IGuildUser.Roles => Roles;
|
||||
IReadOnlyCollection<IRole> IGuildUser.Roles => Roles;
|
||||
IVoiceChannel IGuildUser.VoiceChannel => null;
|
||||
|
||||
GuildPermissions IGuildUser.GetGuildPermissions()
|
||||
=> GuildPermissions;
|
||||
ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel)
|
||||
=> GetPermissions(channel);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,6 @@ namespace Discord
|
||||
string Name { get; }
|
||||
bool IsRevoked { get; }
|
||||
|
||||
IEnumerable<ulong> IntegrationIds { get; }
|
||||
IReadOnlyCollection<ulong> IntegrationIds { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,16 @@ namespace Discord
|
||||
DateTime JoinedAt { get; }
|
||||
/// <summary> Gets the nickname for this user. </summary>
|
||||
string Nickname { get; }
|
||||
/// <summary> Gets the guild-level permissions granted to this user by their roles. </summary>
|
||||
GuildPermissions GuildPermissions { get; }
|
||||
|
||||
/// <summary> Gets the guild for this guild-user pair. </summary>
|
||||
IGuild Guild { get; }
|
||||
/// <summary> Returns a collection of the roles this user is a member of in this guild, including the guild's @everyone role. </summary>
|
||||
IReadOnlyList<IRole> Roles { get; }
|
||||
IReadOnlyCollection<IRole> Roles { get; }
|
||||
/// <summary> Gets the voice channel this user is currently in, if any. </summary>
|
||||
IVoiceChannel VoiceChannel { get; }
|
||||
|
||||
/// <summary> Gets the guild-level permissions granted to this user by their roles. </summary>
|
||||
GuildPermissions GetGuildPermissions();
|
||||
/// <summary> Gets the channel-level permissions granted to this user for a given channel. </summary>
|
||||
ChannelPermissions GetPermissions(IGuildChannel channel);
|
||||
|
||||
@@ -34,4 +34,4 @@ namespace Discord
|
||||
/// <summary> Modifies this user's properties in this guild. </summary>
|
||||
Task Modify(Action<ModifyGuildMemberParams> func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
src/Discord.Net/Entities/Users/IPresence.cs
Normal file
10
src/Discord.Net/Entities/Users/IPresence.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Discord
|
||||
{
|
||||
public interface IPresence
|
||||
{
|
||||
/// <summary> Gets the game this user is currently playing, if any. </summary>
|
||||
Game? Game { get; }
|
||||
/// <summary> Gets the current status of this user. </summary>
|
||||
UserStatus Status { get; }
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,14 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public interface IUser : ISnowflakeEntity, IMentionable
|
||||
public interface IUser : ISnowflakeEntity, IMentionable, IPresence
|
||||
{
|
||||
/// <summary> Gets the url to this user's avatar. </summary>
|
||||
string AvatarUrl { get; }
|
||||
/// <summary> Gets the game this user is currently playing, if any. </summary>
|
||||
Game? CurrentGame { get; }
|
||||
/// <summary> Gets the per-username unique id for this user. </summary>
|
||||
ushort Discriminator { get; }
|
||||
/// <summary> Returns true if this user is a bot account. </summary>
|
||||
bool IsBot { get; }
|
||||
/// <summary> Gets the current status of this user. </summary>
|
||||
UserStatus Status { get; }
|
||||
/// <summary> Gets the username for this user. </summary>
|
||||
string Username { get; }
|
||||
|
||||
|
||||
@@ -3,38 +3,34 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.User;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
public class SelfUser : User, ISelfUser
|
||||
{
|
||||
internal override DiscordClient Discord { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
internal class SelfUser : User, ISelfUser
|
||||
{
|
||||
public string Email { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsVerified { get; private set; }
|
||||
|
||||
internal SelfUser(DiscordClient discord, Model model)
|
||||
: base(model)
|
||||
public SelfUser(DiscordClient discord, Model model)
|
||||
: base(discord, model)
|
||||
{
|
||||
Discord = discord;
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
public override void Update(Model model, UpdateSource source)
|
||||
{
|
||||
base.Update(model);
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
base.Update(model, source);
|
||||
|
||||
Email = model.Email;
|
||||
IsVerified = model.IsVerified;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
var model = await Discord.ApiClient.GetCurrentUser().ConfigureAwait(false);
|
||||
Update(model);
|
||||
}
|
||||
if (IsAttached) throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
var model = await Discord.ApiClient.GetCurrentUser().ConfigureAwait(false);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
public async Task Modify(Action<ModifyCurrentUserParams> func)
|
||||
{
|
||||
if (func != null) throw new NullReferenceException(nameof(func));
|
||||
@@ -42,7 +38,7 @@ namespace Discord.Rest
|
||||
var args = new ModifyCurrentUserParams();
|
||||
func(args);
|
||||
var model = await Discord.ApiClient.ModifyCurrentUser(args).ConfigureAwait(false);
|
||||
Update(model);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,52 @@
|
||||
using Discord.API.Rest;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.User;
|
||||
|
||||
namespace Discord.Rest
|
||||
namespace Discord
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
public abstract class User : IUser
|
||||
internal class User : SnowflakeEntity, IUser
|
||||
{
|
||||
private string _avatarId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ulong Id { get; }
|
||||
internal abstract DiscordClient Discord { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public ushort Discriminator { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsBot { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string Username { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DiscordClient Discord { get; }
|
||||
|
||||
public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId);
|
||||
/// <inheritdoc />
|
||||
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id);
|
||||
/// <inheritdoc />
|
||||
public virtual Game? Game => null;
|
||||
public string Mention => MentionUtils.Mention(this, false);
|
||||
/// <inheritdoc />
|
||||
public string NicknameMention => MentionUtils.Mention(this, true);
|
||||
public virtual UserStatus Status => UserStatus.Unknown;
|
||||
|
||||
internal User(Model model)
|
||||
public User(DiscordClient discord, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Id = model.Id;
|
||||
|
||||
Update(model);
|
||||
Discord = discord;
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
internal virtual void Update(Model model)
|
||||
public virtual void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
_avatarId = model.Avatar;
|
||||
Discriminator = model.Discriminator;
|
||||
IsBot = model.Bot;
|
||||
Username = model.Username;
|
||||
}
|
||||
|
||||
protected virtual async Task<DMChannel> CreateDMChannelInternal()
|
||||
public async Task<IDMChannel> CreateDMChannel()
|
||||
{
|
||||
var args = new CreateDMChannelParams { RecipientId = Id };
|
||||
var model = await Discord.ApiClient.CreateDMChannel(args).ConfigureAwait(false);
|
||||
|
||||
return new DMChannel(Discord, model);
|
||||
return new DMChannel(Discord, this, model);
|
||||
}
|
||||
|
||||
public override string ToString() => $"{Username}#{Discriminator}";
|
||||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})";
|
||||
|
||||
/// <inheritdoc />
|
||||
Game? IUser.CurrentGame => null;
|
||||
/// <inheritdoc />
|
||||
UserStatus IUser.Status => UserStatus.Unknown;
|
||||
|
||||
/// <inheritdoc />
|
||||
async Task<IDMChannel> IUser.CreateDMChannel()
|
||||
=> await CreateDMChannelInternal().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
70
src/Discord.Net/Entities/WebSocket/CachedDMChannel.cs
Normal file
70
src/Discord.Net/Entities/WebSocket/CachedDMChannel.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MessageModel = Discord.API.Message;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal class CachedDMChannel : DMChannel, IDMChannel, ICachedChannel, ICachedMessageChannel
|
||||
{
|
||||
private readonly MessageCache _messages;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new CachedPublicUser Recipient => base.Recipient as CachedPublicUser;
|
||||
public IReadOnlyCollection<IUser> Members => ImmutableArray.Create<IUser>(Discord.CurrentUser, Recipient);
|
||||
|
||||
public CachedDMChannel(DiscordSocketClient discord, CachedPublicUser recipient, Model model)
|
||||
: base(discord, recipient, model)
|
||||
{
|
||||
_messages = new MessageCache(Discord, this);
|
||||
}
|
||||
|
||||
public override Task<IUser> GetUser(ulong id) => Task.FromResult(GetCachedUser(id));
|
||||
public override Task<IReadOnlyCollection<IUser>> GetUsers() => Task.FromResult(Members);
|
||||
public override Task<IReadOnlyCollection<IUser>> GetUsers(int limit, int offset)
|
||||
=> Task.FromResult<IReadOnlyCollection<IUser>>(Members.Skip(offset).Take(limit).ToImmutableArray());
|
||||
public IUser GetCachedUser(ulong id)
|
||||
{
|
||||
var currentUser = Discord.CurrentUser;
|
||||
if (id == Recipient.Id)
|
||||
return Recipient;
|
||||
else if (id == currentUser.Id)
|
||||
return currentUser;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async Task<IMessage> GetMessage(ulong id)
|
||||
{
|
||||
return await _messages.Download(id).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessages(int limit)
|
||||
{
|
||||
return await _messages.Download(null, Direction.Before, limit).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessages(ulong fromMessageId, Direction dir, int limit)
|
||||
{
|
||||
return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false);
|
||||
}
|
||||
public CachedMessage AddCachedMessage(IUser author, MessageModel model)
|
||||
{
|
||||
var msg = new CachedMessage(this, author, model);
|
||||
_messages.Add(msg);
|
||||
return msg;
|
||||
}
|
||||
public CachedMessage GetCachedMessage(ulong id)
|
||||
{
|
||||
return _messages.Get(id);
|
||||
}
|
||||
public CachedMessage RemoveCachedMessage(ulong id)
|
||||
{
|
||||
return _messages.Remove(id);
|
||||
}
|
||||
|
||||
public CachedDMChannel Clone() => MemberwiseClone() as CachedDMChannel;
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id);
|
||||
}
|
||||
}
|
||||
171
src/Discord.Net/Entities/WebSocket/CachedGuild.cs
Normal file
171
src/Discord.Net/Entities/WebSocket/CachedGuild.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using Discord.Data;
|
||||
using Discord.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ChannelModel = Discord.API.Channel;
|
||||
using ExtendedModel = Discord.API.Gateway.ExtendedGuild;
|
||||
using MemberModel = Discord.API.GuildMember;
|
||||
using Model = Discord.API.Guild;
|
||||
using PresenceModel = Discord.API.Presence;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal class CachedGuild : Guild, ICachedEntity<ulong>
|
||||
{
|
||||
private ConcurrentHashSet<ulong> _channels;
|
||||
private ConcurrentDictionary<ulong, CachedGuildUser> _members;
|
||||
private ConcurrentDictionary<ulong, Presence> _presences;
|
||||
private int _userCount;
|
||||
|
||||
public bool Available { get; private set; } //TODO: Add to IGuild
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public CachedGuildUser CurrentUser => GetCachedUser(Discord.CurrentUser.Id);
|
||||
public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetCachedChannel(x)).ToReadOnlyCollection(_channels);
|
||||
public IReadOnlyCollection<CachedGuildUser> Members => _members.ToReadOnlyCollection();
|
||||
|
||||
public CachedGuild(DiscordSocketClient discord, Model model) : base(discord, model)
|
||||
{
|
||||
}
|
||||
|
||||
public void Update(ExtendedModel model, UpdateSource source, DataStore dataStore)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
Available = !(model.Unavailable ?? false);
|
||||
if (!Available)
|
||||
{
|
||||
if (_channels == null)
|
||||
_channels = new ConcurrentHashSet<ulong>();
|
||||
if (_members == null)
|
||||
_members = new ConcurrentDictionary<ulong, CachedGuildUser>();
|
||||
if (_presences == null)
|
||||
_presences = new ConcurrentDictionary<ulong, Presence>();
|
||||
if (_roles == null)
|
||||
_roles = new ConcurrentDictionary<ulong, Role>();
|
||||
if (Emojis == null)
|
||||
Emojis = ImmutableArray.Create<Emoji>();
|
||||
if (Features == null)
|
||||
Features = ImmutableArray.Create<string>();
|
||||
return;
|
||||
}
|
||||
|
||||
base.Update(model as Model, source);
|
||||
|
||||
_userCount = model.MemberCount;
|
||||
|
||||
var channels = new ConcurrentHashSet<ulong>();
|
||||
if (model.Channels != null)
|
||||
{
|
||||
for (int i = 0; i < model.Channels.Length; i++)
|
||||
AddCachedChannel(model.Channels[i], channels, dataStore);
|
||||
}
|
||||
_channels = channels;
|
||||
|
||||
var presences = new ConcurrentDictionary<ulong, Presence>();
|
||||
if (model.Presences != null)
|
||||
{
|
||||
for (int i = 0; i < model.Presences.Length; i++)
|
||||
AddCachedPresence(model.Presences[i], presences);
|
||||
}
|
||||
_presences = presences;
|
||||
|
||||
var members = new ConcurrentDictionary<ulong, CachedGuildUser>();
|
||||
if (model.Members != null)
|
||||
{
|
||||
for (int i = 0; i < model.Members.Length; i++)
|
||||
AddCachedUser(model.Members[i], members, dataStore);
|
||||
}
|
||||
_members = members;
|
||||
}
|
||||
|
||||
public override Task<IGuildChannel> GetChannel(ulong id) => Task.FromResult<IGuildChannel>(GetCachedChannel(id));
|
||||
public override Task<IReadOnlyCollection<IGuildChannel>> GetChannels() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels);
|
||||
public ICachedGuildChannel AddCachedChannel(ChannelModel model, ConcurrentHashSet<ulong> channels = null, DataStore dataStore = null)
|
||||
{
|
||||
var channel = ToChannel(model);
|
||||
(dataStore ?? Discord.DataStore).AddChannel(channel);
|
||||
(channels ?? _channels).TryAdd(model.Id);
|
||||
return channel;
|
||||
}
|
||||
public ICachedGuildChannel GetCachedChannel(ulong id)
|
||||
{
|
||||
return Discord.DataStore.GetChannel(id) as ICachedGuildChannel;
|
||||
}
|
||||
public ICachedGuildChannel RemoveCachedChannel(ulong id, ConcurrentHashSet<ulong> channels = null, DataStore dataStore = null)
|
||||
{
|
||||
(channels ?? _channels).TryRemove(id);
|
||||
return (dataStore ?? Discord.DataStore).RemoveChannel(id) as ICachedGuildChannel;
|
||||
}
|
||||
|
||||
public Presence AddCachedPresence(PresenceModel model, ConcurrentDictionary<ulong, Presence> presences = null)
|
||||
{
|
||||
var game = model.Game != null ? new Game(model.Game) : (Game?)null;
|
||||
var presence = new Presence(model.Status, game);
|
||||
(presences ?? _presences)[model.User.Id] = presence;
|
||||
return presence;
|
||||
}
|
||||
public Presence? GetCachedPresence(ulong id)
|
||||
{
|
||||
Presence presence;
|
||||
if (_presences.TryGetValue(id, out presence))
|
||||
return presence;
|
||||
return null;
|
||||
}
|
||||
public Presence? RemoveCachedPresence(ulong id)
|
||||
{
|
||||
Presence presence;
|
||||
if (_presences.TryRemove(id, out presence))
|
||||
return presence;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override Task<IGuildUser> GetUser(ulong id) => Task.FromResult<IGuildUser>(GetCachedUser(id));
|
||||
public override Task<IGuildUser> GetCurrentUser()
|
||||
=> Task.FromResult<IGuildUser>(CurrentUser);
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers()
|
||||
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members);
|
||||
//TODO: Is there a better way of exposing pagination?
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset)
|
||||
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.OrderBy(x => x.Id).Skip(offset).Take(limit).ToImmutableArray());
|
||||
public CachedGuildUser AddCachedUser(MemberModel model, ConcurrentDictionary<ulong, CachedGuildUser> members = null, DataStore dataStore = null)
|
||||
{
|
||||
var user = Discord.AddCachedUser(model.User);
|
||||
var member = new CachedGuildUser(this, user, model);
|
||||
(members ?? _members)[user.Id] = member;
|
||||
user.AddRef();
|
||||
return member;
|
||||
}
|
||||
public CachedGuildUser GetCachedUser(ulong id)
|
||||
{
|
||||
CachedGuildUser member;
|
||||
if (_members.TryGetValue(id, out member))
|
||||
return member;
|
||||
return null;
|
||||
}
|
||||
public CachedGuildUser RemoveCachedUser(ulong id)
|
||||
{
|
||||
CachedGuildUser member;
|
||||
if (_members.TryRemove(id, out member))
|
||||
return member;
|
||||
return null;
|
||||
}
|
||||
|
||||
new internal ICachedGuildChannel ToChannel(ChannelModel model)
|
||||
{
|
||||
switch (model.Type)
|
||||
{
|
||||
case ChannelType.Text:
|
||||
return new CachedTextChannel(this, model);
|
||||
case ChannelType.Voice:
|
||||
return new CachedVoiceChannel(this, model);
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown channel type: {model.Type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Discord.Net/Entities/WebSocket/CachedGuildUser.cs
Normal file
16
src/Discord.Net/Entities/WebSocket/CachedGuildUser.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Model = Discord.API.GuildMember;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal class CachedGuildUser : GuildUser, ICachedEntity<ulong>
|
||||
{
|
||||
public VoiceChannel VoiceChannel { get; private set; }
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
|
||||
public CachedGuildUser(CachedGuild guild, CachedPublicUser user, Model model)
|
||||
: base(guild, user, model)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/Discord.Net/Entities/WebSocket/CachedMessage.cs
Normal file
17
src/Discord.Net/Entities/WebSocket/CachedMessage.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Model = Discord.API.Message;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal class CachedMessage : Message, ICachedEntity<ulong>
|
||||
{
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new ICachedMessageChannel Channel => base.Channel as ICachedMessageChannel;
|
||||
|
||||
public CachedMessage(ICachedMessageChannel channel, IUser author, Model model)
|
||||
: base(channel, author, model)
|
||||
{
|
||||
}
|
||||
|
||||
public CachedMessage Clone() => MemberwiseClone() as CachedMessage;
|
||||
}
|
||||
}
|
||||
58
src/Discord.Net/Entities/WebSocket/CachedPublicUser.cs
Normal file
58
src/Discord.Net/Entities/WebSocket/CachedPublicUser.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using ChannelModel = Discord.API.Channel;
|
||||
using Model = Discord.API.User;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal class CachedPublicUser : User, ICachedEntity<ulong>
|
||||
{
|
||||
private int _references;
|
||||
|
||||
public CachedDMChannel DMChannel { get; private set; }
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
|
||||
public CachedPublicUser(DiscordSocketClient discord, Model model)
|
||||
: base(discord, model)
|
||||
{
|
||||
}
|
||||
|
||||
public CachedDMChannel SetDMChannel(ChannelModel model)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var channel = new CachedDMChannel(Discord, this, model);
|
||||
DMChannel = channel;
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
public CachedDMChannel RemoveDMChannel(ulong id)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var channel = DMChannel;
|
||||
if (channel.Id == id)
|
||||
{
|
||||
DMChannel = null;
|
||||
return channel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRef()
|
||||
{
|
||||
lock (this)
|
||||
_references++;
|
||||
}
|
||||
public void RemoveRef()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (--_references == 0 && DMChannel == null)
|
||||
Discord.RemoveCachedUser(Id);
|
||||
}
|
||||
}
|
||||
|
||||
public CachedPublicUser Clone() => MemberwiseClone() as CachedPublicUser;
|
||||
}
|
||||
}
|
||||
16
src/Discord.Net/Entities/WebSocket/CachedSelfUser.cs
Normal file
16
src/Discord.Net/Entities/WebSocket/CachedSelfUser.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Model = Discord.API.User;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal class CachedSelfUser : SelfUser, ICachedEntity<ulong>
|
||||
{
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
|
||||
public CachedSelfUser(DiscordSocketClient discord, Model model)
|
||||
: base(discord, model)
|
||||
{
|
||||
}
|
||||
|
||||
public CachedSelfUser Clone() => MemberwiseClone() as CachedSelfUser;
|
||||
}
|
||||
}
|
||||
73
src/Discord.Net/Entities/WebSocket/CachedTextChannel.cs
Normal file
73
src/Discord.Net/Entities/WebSocket/CachedTextChannel.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MessageModel = Discord.API.Message;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal class CachedTextChannel : TextChannel, ICachedGuildChannel, ICachedMessageChannel
|
||||
{
|
||||
private readonly MessageCache _messages;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new CachedGuild Guild => base.Guild as CachedGuild;
|
||||
|
||||
public IReadOnlyCollection<IGuildUser> Members
|
||||
=> Guild.Members.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray();
|
||||
|
||||
public CachedTextChannel(CachedGuild guild, Model model)
|
||||
: base(guild, model)
|
||||
{
|
||||
_messages = new MessageCache(Discord, this);
|
||||
}
|
||||
|
||||
public override Task<IGuildUser> GetUser(ulong id) => Task.FromResult(GetCachedUser(id));
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers() => Task.FromResult(Members);
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset)
|
||||
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.Skip(offset).Take(limit).ToImmutableArray());
|
||||
public IGuildUser GetCachedUser(ulong id)
|
||||
{
|
||||
var user = Guild.GetCachedUser(id);
|
||||
if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages))
|
||||
return user;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async Task<IMessage> GetMessage(ulong id)
|
||||
{
|
||||
return await _messages.Download(id).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
|
||||
{
|
||||
return await _messages.Download(null, Direction.Before, limit).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
|
||||
{
|
||||
return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public CachedMessage AddCachedMessage(IUser author, MessageModel model)
|
||||
{
|
||||
var msg = new CachedMessage(this, author, model);
|
||||
_messages.Add(msg);
|
||||
return msg;
|
||||
}
|
||||
public CachedMessage GetCachedMessage(ulong id)
|
||||
{
|
||||
return _messages.Get(id);
|
||||
}
|
||||
public CachedMessage RemoveCachedMessage(ulong id)
|
||||
{
|
||||
return _messages.Remove(id);
|
||||
}
|
||||
|
||||
public CachedTextChannel Clone() => MemberwiseClone() as CachedTextChannel;
|
||||
|
||||
IReadOnlyCollection<IUser> ICachedMessageChannel.Members => Members;
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id);
|
||||
IUser ICachedMessageChannel.GetCachedUser(ulong id) => GetCachedUser(id);
|
||||
}
|
||||
}
|
||||
38
src/Discord.Net/Entities/WebSocket/CachedVoiceChannel.cs
Normal file
38
src/Discord.Net/Entities/WebSocket/CachedVoiceChannel.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal class CachedVoiceChannel : VoiceChannel, ICachedGuildChannel
|
||||
{
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new CachedGuild Guild => base.Guild as CachedGuild;
|
||||
|
||||
public IReadOnlyCollection<IGuildUser> Members
|
||||
=> Guild.Members.Where(x => x.VoiceChannel.Id == Id).ToImmutableArray();
|
||||
|
||||
public CachedVoiceChannel(CachedGuild guild, Model model)
|
||||
: base(guild, model)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<IGuildUser> GetUser(ulong id)
|
||||
=> Task.FromResult(GetCachedUser(id));
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers()
|
||||
=> Task.FromResult(Members);
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset)
|
||||
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.OrderBy(x => x.Id).Skip(offset).Take(limit).ToImmutableArray());
|
||||
public IGuildUser GetCachedUser(ulong id)
|
||||
{
|
||||
var user = Guild.GetCachedUser(id);
|
||||
if (user != null && user.VoiceChannel.Id == Id)
|
||||
return user;
|
||||
return null;
|
||||
}
|
||||
|
||||
public CachedVoiceChannel Clone() => MemberwiseClone() as CachedVoiceChannel;
|
||||
}
|
||||
}
|
||||
6
src/Discord.Net/Entities/WebSocket/ICachedChannel.cs
Normal file
6
src/Discord.Net/Entities/WebSocket/ICachedChannel.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Discord
|
||||
{
|
||||
internal interface ICachedChannel : IChannel, ICachedEntity<ulong>
|
||||
{
|
||||
}
|
||||
}
|
||||
7
src/Discord.Net/Entities/WebSocket/ICachedEntity.cs
Normal file
7
src/Discord.Net/Entities/WebSocket/ICachedEntity.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Discord
|
||||
{
|
||||
interface ICachedEntity<T> : IEntity<T>
|
||||
{
|
||||
DiscordSocketClient Discord { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Discord
|
||||
{
|
||||
internal interface ICachedGuildChannel : ICachedChannel, IGuildChannel
|
||||
{
|
||||
}
|
||||
}
|
||||
16
src/Discord.Net/Entities/WebSocket/ICachedMessageChannel.cs
Normal file
16
src/Discord.Net/Entities/WebSocket/ICachedMessageChannel.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using MessageModel = Discord.API.Message;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal interface ICachedMessageChannel : ICachedChannel, IMessageChannel
|
||||
{
|
||||
IReadOnlyCollection<IUser> Members { get; }
|
||||
|
||||
CachedMessage AddCachedMessage(IUser author, MessageModel model);
|
||||
new CachedMessage GetCachedMessage(ulong id);
|
||||
CachedMessage RemoveCachedMessage(ulong id);
|
||||
|
||||
IUser GetCachedUser(ulong id);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Model = Discord.API.MemberVoiceState;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
public class VoiceState
|
||||
internal class VoiceState : IVoiceState
|
||||
{
|
||||
[Flags]
|
||||
private enum VoiceStates : byte
|
||||
@@ -22,7 +22,7 @@ namespace Discord.WebSocket
|
||||
public ulong UserId { get; }
|
||||
|
||||
/// <summary> Gets this user's current voice channel. </summary>
|
||||
public VoiceChannel VoiceChannel { get; internal set; }
|
||||
public VoiceChannel VoiceChannel { get; set; }
|
||||
|
||||
/// <summary> Returns true if this user has marked themselves as muted. </summary>
|
||||
public bool IsSelfMuted => (_voiceStates & VoiceStates.SelfMuted) != 0;
|
||||
@@ -35,13 +35,13 @@ namespace Discord.WebSocket
|
||||
/// <summary> Returns true if the guild is temporarily blocking audio to/from this user. </summary>
|
||||
public bool IsSuppressed => (_voiceStates & VoiceStates.Suppressed) != 0;
|
||||
|
||||
internal VoiceState(ulong userId, Guild guild)
|
||||
public VoiceState(ulong userId, Guild guild)
|
||||
{
|
||||
UserId = userId;
|
||||
Guild = guild;
|
||||
}
|
||||
|
||||
internal void Update(Model model)
|
||||
private void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (model.IsMuted == true)
|
||||
_voiceStates |= VoiceStates.Muted;
|
||||
14
src/Discord.Net/Entities/WebSocket/Presence.cs
Normal file
14
src/Discord.Net/Entities/WebSocket/Presence.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Discord
|
||||
{
|
||||
internal struct Presence : IPresence
|
||||
{
|
||||
public UserStatus Status { get; }
|
||||
public Game? Game { get; }
|
||||
|
||||
public Presence(UserStatus status, Game? game)
|
||||
{
|
||||
Status = status;
|
||||
Game = game;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Discord.Net/Extensions/CollectionExtensions.cs
Normal file
31
src/Discord.Net/Extensions/CollectionExtensions.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord.Extensions
|
||||
{
|
||||
internal static class CollectionExtensions
|
||||
{
|
||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> source)
|
||||
=> new ConcurrentDictionaryWrapper<TValue, KeyValuePair<TKey, TValue>>(source, source.Select(x => x.Value));
|
||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue, TSource>(this IEnumerable<TValue> query, IReadOnlyCollection<TSource> source)
|
||||
=> new ConcurrentDictionaryWrapper<TValue, TSource>(source, query);
|
||||
}
|
||||
|
||||
internal struct ConcurrentDictionaryWrapper<TValue, TSource> : IReadOnlyCollection<TValue>
|
||||
{
|
||||
private readonly IReadOnlyCollection<TSource> _source;
|
||||
private readonly IEnumerable<TValue> _query;
|
||||
|
||||
public int Count => _source.Count;
|
||||
|
||||
public ConcurrentDictionaryWrapper(IReadOnlyCollection<TSource> source, IEnumerable<TValue> query)
|
||||
{
|
||||
_source = source;
|
||||
_query = query;
|
||||
}
|
||||
|
||||
public IEnumerator<TValue> GetEnumerator() => _query.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => _query.GetEnumerator();
|
||||
}
|
||||
}
|
||||
14
src/Discord.Net/Extensions/DiscordClientExtensions.cs
Normal file
14
src/Discord.Net/Extensions/DiscordClientExtensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Extensions
|
||||
{
|
||||
public static class DiscordClientExtensions
|
||||
{
|
||||
public static async Task<IVoiceRegion> GetOptimalVoiceRegion(this DiscordClient discord)
|
||||
{
|
||||
var regions = await discord.GetVoiceRegions().ConfigureAwait(false);
|
||||
return regions.FirstOrDefault(x => x.IsOptimal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
namespace Discord.Extensions
|
||||
{
|
||||
internal static class EventExtensions
|
||||
{
|
||||
12
src/Discord.Net/Extensions/GuildExtensions.cs
Normal file
12
src/Discord.Net/Extensions/GuildExtensions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Extensions
|
||||
{
|
||||
public static class GuildExtensions
|
||||
{
|
||||
public static async Task<ITextChannel> GetTextChannel(this IGuild guild, ulong id)
|
||||
=> await guild.GetChannel(id).ConfigureAwait(false) as ITextChannel;
|
||||
public static async Task<IVoiceChannel> GetVoiceChannel(this IGuild guild, ulong id)
|
||||
=> await guild.GetChannel(id).ConfigureAwait(false) as IVoiceChannel;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using Discord.API;
|
||||
using Discord.Net.Queue;
|
||||
using Discord.WebSocket.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
@@ -14,10 +12,7 @@ namespace Discord
|
||||
ConnectionState ConnectionState { get; }
|
||||
|
||||
DiscordApiClient ApiClient { get; }
|
||||
IRequestQueue RequestQueue { get; }
|
||||
IDataStore DataStore { get; }
|
||||
|
||||
Task Login(string email, string password);
|
||||
|
||||
Task Login(TokenType tokenType, string token, bool validateToken = true);
|
||||
Task Logout();
|
||||
|
||||
@@ -25,12 +20,12 @@ namespace Discord
|
||||
Task Disconnect();
|
||||
|
||||
Task<IChannel> GetChannel(ulong id);
|
||||
Task<IEnumerable<IDMChannel>> GetDMChannels();
|
||||
Task<IReadOnlyCollection<IDMChannel>> GetDMChannels();
|
||||
|
||||
Task<IEnumerable<IConnection>> GetConnections();
|
||||
Task<IReadOnlyCollection<IConnection>> GetConnections();
|
||||
|
||||
Task<IGuild> GetGuild(ulong id);
|
||||
Task<IEnumerable<IUserGuild>> GetGuilds();
|
||||
Task<IReadOnlyCollection<IUserGuild>> GetGuilds();
|
||||
Task<IGuild> CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null);
|
||||
|
||||
Task<IInvite> GetInvite(string inviteIdOrXkcd);
|
||||
@@ -38,9 +33,9 @@ namespace Discord
|
||||
Task<IUser> GetUser(ulong id);
|
||||
Task<IUser> GetUser(string username, ushort discriminator);
|
||||
Task<ISelfUser> GetCurrentUser();
|
||||
Task<IEnumerable<IUser>> QueryUsers(string query, int limit);
|
||||
Task<IReadOnlyCollection<IUser>> QueryUsers(string query, int limit);
|
||||
|
||||
Task<IEnumerable<IVoiceRegion>> GetVoiceRegions();
|
||||
Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegions();
|
||||
Task<IVoiceRegion> GetVoiceRegion(string id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Discord.Extensions;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Logging
|
||||
@@ -9,7 +10,7 @@ namespace Discord.Logging
|
||||
|
||||
public event Func<LogMessage, Task> Message;
|
||||
|
||||
internal LogManager(LogSeverity minSeverity)
|
||||
public LogManager(LogSeverity minSeverity)
|
||||
{
|
||||
Level = minSeverity;
|
||||
}
|
||||
@@ -110,6 +111,6 @@ namespace Discord.Logging
|
||||
Task ILogger.Debug(Exception ex)
|
||||
=> Log(LogSeverity.Debug, "Discord", ex);
|
||||
|
||||
internal Logger CreateLogger(string name) => new Logger(this, name);
|
||||
public Logger CreateLogger(string name) => new Logger(this, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Discord.Logging
|
||||
public string Name { get; }
|
||||
public LogSeverity Level => _manager.Level;
|
||||
|
||||
internal Logger(LogManager manager, string name)
|
||||
public Logger(LogManager manager, string name)
|
||||
{
|
||||
_manager = manager;
|
||||
Name = name;
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace Discord.Net.Converters
|
||||
public class DiscordContractResolver : DefaultContractResolver
|
||||
{
|
||||
private static readonly TypeInfo _ienumerable = typeof(IEnumerable<ulong[]>).GetTypeInfo();
|
||||
|
||||
private static readonly MethodInfo _shouldSerialize = typeof(DiscordContractResolver).GetTypeInfo().GetDeclaredMethod("ShouldSerialize");
|
||||
|
||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
||||
{
|
||||
var property = base.CreateProperty(member, memberSerialization);
|
||||
@@ -54,12 +55,15 @@ namespace Discord.Net.Converters
|
||||
converter = ImageConverter.Instance;
|
||||
else if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>))
|
||||
{
|
||||
var lambda = (Func<object, bool>)propInfo.GetMethod.CreateDelegate(typeof(Func<object, bool>));
|
||||
/*var parentArg = Expression.Parameter(typeof(object));
|
||||
var optional = Expression.Property(Expression.Convert(parentArg, property.DeclaringType), member as PropertyInfo);
|
||||
var isSpecified = Expression.Property(optional, OptionalConverter.IsSpecifiedProperty);
|
||||
var lambda = Expression.Lambda<Func<object, bool>>(isSpecified, parentArg).Compile();*/
|
||||
property.ShouldSerialize = x => lambda(x);
|
||||
var typeInput = propInfo.DeclaringType;
|
||||
var typeOutput = propInfo.PropertyType;
|
||||
|
||||
var getter = typeof(Func<,>).MakeGenericType(typeInput, typeOutput);
|
||||
var getterDelegate = propInfo.GetMethod.CreateDelegate(getter);
|
||||
var shouldSerialize = _shouldSerialize.MakeGenericMethod(typeInput, typeOutput);
|
||||
var shouldSerializeDelegate = (Func<object, Delegate, bool>)shouldSerialize.CreateDelegate(typeof(Func<object, Delegate, bool>));
|
||||
|
||||
property.ShouldSerialize = x => shouldSerializeDelegate(x, getterDelegate);
|
||||
converter = OptionalConverter.Instance;
|
||||
}
|
||||
}
|
||||
@@ -73,5 +77,11 @@ namespace Discord.Net.Converters
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
private static bool ShouldSerialize<TOwner, TValue>(object owner, Delegate getter)
|
||||
where TValue : IOptional
|
||||
{
|
||||
return (getter as Func<TOwner, TValue>)((TOwner)owner).IsSpecified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using Discord.API;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Discord.Net.Converters
|
||||
{
|
||||
public class OptionalConverter : JsonConverter
|
||||
{
|
||||
public static readonly OptionalConverter Instance = new OptionalConverter();
|
||||
internal static readonly PropertyInfo IsSpecifiedProperty = typeof(IOptional).GetTypeInfo().GetDeclaredProperty(nameof(IOptional.IsSpecified));
|
||||
|
||||
public override bool CanConvert(Type objectType) => true;
|
||||
public override bool CanRead => false;
|
||||
|
||||
@@ -6,11 +6,13 @@ namespace Discord.Net
|
||||
public class HttpException : Exception
|
||||
{
|
||||
public HttpStatusCode StatusCode { get; }
|
||||
public string Reason { get; }
|
||||
|
||||
public HttpException(HttpStatusCode statusCode)
|
||||
: base($"The server responded with error {(int)statusCode} ({statusCode})")
|
||||
public HttpException(HttpStatusCode statusCode, string reason = null)
|
||||
: base($"The server responded with error {(int)statusCode} ({statusCode}){(reason != null ? $": \"{reason}\"" : "")}")
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
Reason = reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
src/Discord.Net/Net/Queue/BucketDefinition.cs
Normal file
16
src/Discord.Net/Net/Queue/BucketDefinition.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Discord.Net.Queue
|
||||
{
|
||||
internal struct BucketDefinition
|
||||
{
|
||||
public int WindowCount { get; }
|
||||
public int WindowSeconds { get; }
|
||||
public GlobalBucket? Parent { get; }
|
||||
|
||||
public BucketDefinition(int windowCount, int windowSeconds, GlobalBucket? parent = null)
|
||||
{
|
||||
WindowCount = windowCount;
|
||||
WindowSeconds = windowSeconds;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Discord.Net.Queue
|
||||
{
|
||||
internal enum BucketGroup
|
||||
public enum BucketGroup
|
||||
{
|
||||
Global,
|
||||
Guild
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
{
|
||||
public enum GlobalBucket
|
||||
{
|
||||
General,
|
||||
Login,
|
||||
GeneralRest,
|
||||
DirectMessage,
|
||||
SendEditMessage,
|
||||
Gateway,
|
||||
GeneralGateway,
|
||||
UpdateStatus
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user