| @@ -0,0 +1,9 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public interface IRelationship | |||||
| { | |||||
| RelationshipType Type { get; } | |||||
| IUser User { get; } | |||||
| } | |||||
| } | |||||
| @@ -18,7 +18,13 @@ namespace Discord | |||||
| bool IsWebhook { get; } | bool IsWebhook { get; } | ||||
| /// <summary> Gets the username for this user. </summary> | /// <summary> Gets the username for this user. </summary> | ||||
| string Username { get; } | string Username { get; } | ||||
| /// <summary> Adds this user as a friend, this will remove a block </summary> | |||||
| Task AddFriendAsync(RequestOptions options = null); | |||||
| /// <summary> Blocks this user, and removes the user as a friend </summary> | |||||
| Task BlockUserAsync(RequestOptions options = null); | |||||
| /// <summary> Removes the relationship of this user </summary> | |||||
| Task RemoveRelationshipAsync(RequestOptions options = null); | |||||
| /// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | /// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | ||||
| Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null); | Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null); | ||||
| } | } | ||||
| @@ -0,0 +1,11 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public enum RelationshipType | |||||
| { | |||||
| None, | |||||
| Friend, | |||||
| Blocked, | |||||
| IncomingPending, | |||||
| OutgoingPending | |||||
| } | |||||
| } | |||||
| @@ -26,6 +26,8 @@ namespace Discord | |||||
| Task<IGuild> GetGuildAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IGuild> GetGuildAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null); | Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null); | ||||
| Task<IReadOnlyCollection<IRelationship>> GetRelationshipsAsync(RequestOptions options = null); | |||||
| Task<IInvite> GetInviteAsync(string inviteId, RequestOptions options = null); | Task<IInvite> GetInviteAsync(string inviteId, RequestOptions options = null); | ||||
| @@ -192,5 +192,7 @@ namespace Discord | |||||
| throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); | throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); | ||||
| } | } | ||||
| } | } | ||||
| public static void LoggedInAs(TokenType token, TokenType existing) { if (token != existing) throw new NotSupportedException($"Must be logged in as a {token} to use this endpoint"); } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,11 +0,0 @@ | |||||
| #pragma warning disable CS1591 | |||||
| namespace Discord.API | |||||
| { | |||||
| internal enum RelationshipType | |||||
| { | |||||
| Friend = 1, | |||||
| Blocked = 2, | |||||
| IncomingPending = 3, | |||||
| OutgoingPending = 4 | |||||
| } | |||||
| } | |||||
| @@ -122,11 +122,14 @@ namespace Discord.Rest | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public void Dispose() => Dispose(true); | public void Dispose() => Dispose(true); | ||||
| //IDiscordClient | //IDiscordClient | ||||
| ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | ||||
| ISelfUser IDiscordClient.CurrentUser => CurrentUser; | ISelfUser IDiscordClient.CurrentUser => CurrentUser; | ||||
| Task<IReadOnlyCollection<IRelationship>> IDiscordClient.GetRelationshipsAsync(RequestOptions options) | |||||
| => Task.FromResult<IReadOnlyCollection<IRelationship>>(null); | |||||
| Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) { throw new NotSupportedException(); } | Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) { throw new NotSupportedException(); } | ||||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| @@ -152,5 +152,20 @@ namespace Discord.Rest | |||||
| var models = await client.ApiClient.GetVoiceRegionsAsync(options).ConfigureAwait(false); | var models = await client.ApiClient.GetVoiceRegionsAsync(options).ConfigureAwait(false); | ||||
| return models.Select(x => RestVoiceRegion.Create(client, x)).FirstOrDefault(x => x.Id == id); | return models.Select(x => RestVoiceRegion.Create(client, x)).FirstOrDefault(x => x.Id == id); | ||||
| } | } | ||||
| public static async Task<IReadOnlyCollection<RestRelationship>> GetRelationshipsAsync(BaseDiscordClient client) | |||||
| { | |||||
| var models = await client.ApiClient.GetRelationshipsAsync().ConfigureAwait(false); | |||||
| return models.Select(r => RestRelationship.Create(client, r)).ToReadOnlyCollection(models); | |||||
| } | |||||
| public static async Task AddFriendAsync(BaseDiscordClient client, ulong user, RequestOptions options) | |||||
| => await client.ApiClient.AddFriendAsync(user, options).ConfigureAwait(false); | |||||
| public static async Task BlockUserAsync(BaseDiscordClient client, ulong user, RequestOptions options) | |||||
| => await client.ApiClient.BlockUserAsync(user, options).ConfigureAwait(false); | |||||
| public static async Task RemoveRelationshipAsync(BaseDiscordClient client, ulong user, RequestOptions options) | |||||
| => await client.ApiClient.RemoveRelationshipAsync(user, options).ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| @@ -241,7 +241,7 @@ namespace Discord.API | |||||
| internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | ||||
| ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) | ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) | ||||
| => SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | => SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | ||||
| public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, | |||||
| public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, | |||||
| string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) | string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) | ||||
| { | { | ||||
| options = options ?? new RequestOptions(); | options = options ?? new RequestOptions(); | ||||
| @@ -730,7 +730,7 @@ namespace Discord.API | |||||
| Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); | Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); | ||||
| Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); | Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| return await SendJsonAsync<Guild>("POST", () => "guilds", args, new BucketIds(), options: options).ConfigureAwait(false); | return await SendJsonAsync<Guild>("POST", () => "guilds", args, new BucketIds(), options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| public async Task<Guild> DeleteGuildAsync(ulong guildId, RequestOptions options = null) | public async Task<Guild> DeleteGuildAsync(ulong guildId, RequestOptions options = null) | ||||
| @@ -940,14 +940,14 @@ namespace Discord.API | |||||
| { | { | ||||
| Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); | Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| return await SendAsync<Invite>("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); | return await SendAsync<Invite>("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| public async Task AcceptInviteAsync(string inviteId, RequestOptions options = null) | public async Task AcceptInviteAsync(string inviteId, RequestOptions options = null) | ||||
| { | { | ||||
| Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); | Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| await SendAsync("POST", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); | await SendAsync("POST", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -1073,6 +1073,43 @@ namespace Discord.API | |||||
| } | } | ||||
| catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } | catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } | ||||
| } | } | ||||
| //Relationships | |||||
| public async Task<IReadOnlyCollection<Relationship>> GetRelationshipsAsync(RequestOptions options = null) | |||||
| { | |||||
| Preconditions.LoggedInAs(TokenType.User, AuthTokenType); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| return await SendAsync<IReadOnlyCollection<Relationship>>("GET", () => "users/@me/relationships", new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task AddFriendAsync(ulong userId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(userId, 0, nameof(userId)); | |||||
| Preconditions.LoggedInAs(TokenType.User, AuthTokenType); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| await SendJsonAsync("PUT", () => $"users/@me/relationships/{userId}", new object(), new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task BlockUserAsync(ulong userId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(userId, 0, nameof(userId)); | |||||
| Preconditions.LoggedInAs(TokenType.User, AuthTokenType); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| await SendJsonAsync("PUT", () => $"users/@me/relationships/{userId}", new { type = 2 }, new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task RemoveRelationshipAsync(ulong userId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(userId, 0, nameof(userId)); | |||||
| Preconditions.LoggedInAs(TokenType.User, AuthTokenType); | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| await SendAsync("DELETE", () => $"users/@me/relationships/{userId}", new BucketIds(), options: options).ConfigureAwait(false); | |||||
| } | |||||
| //Current User/DMs | //Current User/DMs | ||||
| public async Task<User> GetMyUserAsync(RequestOptions options = null) | public async Task<User> GetMyUserAsync(RequestOptions options = null) | ||||
| @@ -1246,7 +1283,7 @@ namespace Discord.API | |||||
| int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); | int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); | ||||
| string fieldName = GetFieldName(methodArgs[argId + 1]); | string fieldName = GetFieldName(methodArgs[argId + 1]); | ||||
| int? mappedId; | int? mappedId; | ||||
| mappedId = BucketIds.GetIndex(fieldName); | mappedId = BucketIds.GetIndex(fieldName); | ||||
| if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash | if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash | ||||
| rightIndex++; | rightIndex++; | ||||
| @@ -92,6 +92,9 @@ namespace Discord.Rest | |||||
| public Task<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null) | public Task<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null) | ||||
| => ClientHelper.GetVoiceRegionAsync(this, id, options); | => ClientHelper.GetVoiceRegionAsync(this, id, options); | ||||
| public Task<IReadOnlyCollection<RestRelationship>> GetRelationshipsAsync() | |||||
| => ClientHelper.GetRelationshipsAsync(this); | |||||
| //IDiscordClient | //IDiscordClient | ||||
| async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | ||||
| => await GetApplicationInfoAsync(options).ConfigureAwait(false); | => await GetApplicationInfoAsync(options).ConfigureAwait(false); | ||||
| @@ -0,0 +1,26 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Text; | |||||
| using Model = Discord.API.Relationship; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| public class RestRelationship : IRelationship | |||||
| { | |||||
| public RelationshipType Type { get; internal set; } | |||||
| public IUser User { get; internal set; } | |||||
| internal RestRelationship(RelationshipType type, IUser user) | |||||
| { | |||||
| Type = type; | |||||
| User = user; | |||||
| } | |||||
| internal static RestRelationship Create(BaseDiscordClient discord, Model model) | |||||
| { | |||||
| RestUser user = RestUser.Create(discord, model.User); | |||||
| return new RestRelationship(model.Type, user); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -49,5 +49,14 @@ namespace Discord.Rest | |||||
| var model = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); | var model = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); | ||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| Task IUser.AddFriendAsync(RequestOptions options) => | |||||
| throw new InvalidOperationException("You can't friend yourself!"); | |||||
| Task IUser.BlockUserAsync(RequestOptions options) => | |||||
| throw new InvalidOperationException("You can't block yourself!"); | |||||
| Task IUser.RemoveRelationshipAsync(RequestOptions options) => | |||||
| throw new InvalidOperationException("You don't have any relations with yourself!"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -63,6 +63,13 @@ namespace Discord.Rest | |||||
| public override string ToString() => $"{Username}#{Discriminator}"; | public override string ToString() => $"{Username}#{Discriminator}"; | ||||
| private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | ||||
| public Task AddFriendAsync(RequestOptions options = null) | |||||
| => ClientHelper.AddFriendAsync(Discord, Id, options); | |||||
| public Task BlockUserAsync(RequestOptions options = null) | |||||
| => ClientHelper.BlockUserAsync(Discord, Id, options); | |||||
| public Task RemoveRelationshipAsync(RequestOptions options = null) | |||||
| => ClientHelper.RemoveRelationshipAsync(Discord, Id, options); | |||||
| //IUser | //IUser | ||||
| async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) | async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) | ||||
| => await GetOrCreateDMChannelAsync(options); | => await GetOrCreateDMChannelAsync(options); | ||||
| @@ -58,7 +58,13 @@ namespace Discord.Rpc | |||||
| public override string ToString() => $"{Username}#{Discriminator}"; | public override string ToString() => $"{Username}#{Discriminator}"; | ||||
| private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | ||||
| //IUser | |||||
| Task IUser.AddFriendAsync(RequestOptions options) | |||||
| => throw new NotSupportedException(); | |||||
| Task IUser.BlockUserAsync(RequestOptions options) | |||||
| => throw new NotSupportedException(); | |||||
| Task IUser.RemoveRelationshipAsync(RequestOptions options) | |||||
| => throw new NotSupportedException(); | |||||
| async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) | async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) | ||||
| => await GetOrCreateDMChannelAsync(options); | => await GetOrCreateDMChannelAsync(options); | ||||
| } | } | ||||
| @@ -15,6 +15,7 @@ namespace Discord.WebSocket | |||||
| private readonly ConcurrentDictionary<ulong, SocketDMChannel> _dmChannels; | private readonly ConcurrentDictionary<ulong, SocketDMChannel> _dmChannels; | ||||
| private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds; | private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds; | ||||
| private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users; | private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users; | ||||
| private readonly ConcurrentDictionary<ulong, SocketRelationship> _relationships; | |||||
| private readonly ConcurrentHashSet<ulong> _groupChannels; | private readonly ConcurrentHashSet<ulong> _groupChannels; | ||||
| internal IReadOnlyCollection<SocketChannel> Channels => _channels.ToReadOnlyCollection(); | internal IReadOnlyCollection<SocketChannel> Channels => _channels.ToReadOnlyCollection(); | ||||
| @@ -22,6 +23,7 @@ namespace Discord.WebSocket | |||||
| internal IReadOnlyCollection<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); | internal IReadOnlyCollection<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); | ||||
| internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection(); | internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection(); | ||||
| internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection(); | internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection(); | ||||
| internal IReadOnlyCollection<SocketRelationship> Relationships => _relationships.ToReadOnlyCollection(); | |||||
| internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => | internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => | ||||
| _dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat( | _dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat( | ||||
| @@ -36,6 +38,7 @@ namespace Discord.WebSocket | |||||
| _dmChannels = new ConcurrentDictionary<ulong, SocketDMChannel>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); | _dmChannels = new ConcurrentDictionary<ulong, SocketDMChannel>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); | ||||
| _guilds = new ConcurrentDictionary<ulong, SocketGuild>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); | _guilds = new ConcurrentDictionary<ulong, SocketGuild>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); | ||||
| _users = new ConcurrentDictionary<ulong, SocketGlobalUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); | _users = new ConcurrentDictionary<ulong, SocketGlobalUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); | ||||
| _relationships = new ConcurrentDictionary<ulong, SocketRelationship>(ConcurrentHashSet.DefaultConcurrencyLevel, 35); | |||||
| _groupChannels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier)); | _groupChannels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier)); | ||||
| } | } | ||||
| @@ -116,5 +119,22 @@ namespace Discord.WebSocket | |||||
| return user; | return user; | ||||
| return null; | return null; | ||||
| } | } | ||||
| internal SocketRelationship GetRelationship(ulong id) | |||||
| { | |||||
| if (_relationships.TryGetValue(id, out SocketRelationship value)) | |||||
| return value; | |||||
| return null; | |||||
| } | |||||
| internal void AddRelationship(SocketRelationship relationship) | |||||
| { | |||||
| _relationships[relationship.User.Id] = relationship; | |||||
| } | |||||
| internal SocketRelationship RemoveRelationship(ulong id) | |||||
| { | |||||
| if (_relationships.TryRemove(id, out SocketRelationship value)) | |||||
| return value; | |||||
| return null; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -215,5 +215,19 @@ namespace Discord.WebSocket | |||||
| remove { _recipientRemovedEvent.Remove(value); } | remove { _recipientRemovedEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | ||||
| // relationships | |||||
| public event Func<SocketRelationship, SocketRelationship, Task> RelationshipAdded | |||||
| { | |||||
| add { _relationshipAddedEvent.Add(value); } | |||||
| remove { _relationshipAddedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<SocketRelationship, SocketRelationship, Task>> _relationshipAddedEvent = new AsyncEvent<Func<SocketRelationship, SocketRelationship, Task>>(); | |||||
| public event Func<SocketRelationship, Task> RelationshipRemoved | |||||
| { | |||||
| add { _relationshipRemovedEvent.Add(value); } | |||||
| remove { _relationshipRemovedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<SocketRelationship, Task>> _relationshipRemovedEvent = new AsyncEvent<Func<SocketRelationship, Task>>(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -68,6 +68,7 @@ namespace Discord.WebSocket | |||||
| public IReadOnlyCollection<SocketGroupChannel> GroupChannels | public IReadOnlyCollection<SocketGroupChannel> GroupChannels | ||||
| => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); | => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); | ||||
| public IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | public IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | ||||
| public IReadOnlyCollection<SocketRelationship> Relationships => State.Relationships; | |||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | ||||
| @@ -256,8 +257,11 @@ namespace Discord.WebSocket | |||||
| => ClientHelper.GetConnectionsAsync(this, new RequestOptions()); | => ClientHelper.GetConnectionsAsync(this, new RequestOptions()); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task<RestInvite> GetInviteAsync(string inviteId) | |||||
| => ClientHelper.GetInviteAsync(this, inviteId, new RequestOptions()); | |||||
| public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | |||||
| => ClientHelper.GetInviteAsync(this, inviteId, options); | |||||
| public Task<IReadOnlyCollection<SocketRelationship>> GetRelationshipsAsync() | |||||
| => Task.FromResult(State.Relationships); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public SocketUser GetUser(ulong id) | public SocketUser GetUser(ulong id) | ||||
| @@ -472,6 +476,8 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| for (int i = 0; i < data.PrivateChannels.Length; i++) | for (int i = 0; i < data.PrivateChannels.Length; i++) | ||||
| AddPrivateChannel(data.PrivateChannels[i], state); | AddPrivateChannel(data.PrivateChannels[i], state); | ||||
| for (int i = 0; i < data.Relationships.Length; i++) | |||||
| AddRelationship(data.Relationships[i], state); | |||||
| _sessionId = data.SessionId; | _sessionId = data.SessionId; | ||||
| _unavailableGuildCount = unavailableGuilds; | _unavailableGuildCount = unavailableGuilds; | ||||
| @@ -1495,7 +1501,30 @@ namespace Discord.WebSocket | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| break; | |||||
| return; | |||||
| //Relationships | |||||
| case "RELATIONSHIP_ADD": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (RELATIONSHIP_ADD)").ConfigureAwait(false); | |||||
| var addedModel = (payload as JToken).ToObject<Relationship>(_serializer); | |||||
| var before = State.GetRelationship(addedModel.Id) ?? SocketRelationship.Create(this, State, new Relationship { Id = addedModel.Id, Type = RelationshipType.None, User = addedModel.User }); | |||||
| var after = AddRelationship(addedModel, State); | |||||
| await _relationshipAddedEvent.InvokeAsync(before, after); | |||||
| return; | |||||
| } | |||||
| case "RELATIONSHIP_REMOVE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (RELATIONSHIP_REMOVE)").ConfigureAwait(false); | |||||
| var removedModel = (payload as JToken).ToObject<Relationship>(_serializer); | |||||
| var removed = RemoveRelationship(removedModel.Id); | |||||
| await _relationshipRemovedEvent.InvokeAsync(removed); | |||||
| return; | |||||
| } | |||||
| //Ignored (User only) | //Ignored (User only) | ||||
| case "CHANNEL_PINS_ACK": | case "CHANNEL_PINS_ACK": | ||||
| @@ -1798,6 +1827,21 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync($"Unsynced Guild ({details}).").ConfigureAwait(false); | await _gatewayLogger.DebugAsync($"Unsynced Guild ({details}).").ConfigureAwait(false); | ||||
| } | } | ||||
| internal SocketRelationship GetRelationship(ulong id) | |||||
| { | |||||
| return State.GetRelationship(id); | |||||
| } | |||||
| internal SocketRelationship AddRelationship(Relationship model, ClientState state) | |||||
| { | |||||
| var relationship = SocketRelationship.Create(this, state, model); | |||||
| state.AddRelationship(SocketRelationship.Create(this, state, model)); | |||||
| return relationship; | |||||
| } | |||||
| internal SocketRelationship RemoveRelationship(ulong id) | |||||
| { | |||||
| return State.RemoveRelationship(id); | |||||
| } | |||||
| internal int GetAudioId() => _nextAudioId++; | internal int GetAudioId() => _nextAudioId++; | ||||
| //IDiscordClient | //IDiscordClient | ||||
| @@ -1840,5 +1884,8 @@ namespace Discord.WebSocket | |||||
| => await StartAsync().ConfigureAwait(false); | => await StartAsync().ConfigureAwait(false); | ||||
| async Task IDiscordClient.StopAsync() | async Task IDiscordClient.StopAsync() | ||||
| => await StopAsync().ConfigureAwait(false); | => await StopAsync().ConfigureAwait(false); | ||||
| async Task<IReadOnlyCollection<IRelationship>> IDiscordClient.GetRelationshipsAsync(RequestOptions options) | |||||
| => await GetRelationshipsAsync(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,27 @@ | |||||
| using System.Diagnostics; | |||||
| using Model = Discord.API.Relationship; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| [DebuggerDisplay("{Type} - {User}")] | |||||
| public class SocketRelationship : IRelationship | |||||
| { | |||||
| public RelationshipType Type { get; internal set; } | |||||
| public IUser User { get; internal set; } | |||||
| internal SocketRelationship() | |||||
| { | |||||
| } | |||||
| internal static SocketRelationship Create(DiscordSocketClient discord, ClientState state, Model model) | |||||
| { | |||||
| SocketGlobalUser user = SocketGlobalUser.Create(discord, state, model.User); | |||||
| return new SocketRelationship | |||||
| { | |||||
| Type = model.Type, | |||||
| User = user | |||||
| }; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -57,6 +57,15 @@ namespace Discord.WebSocket | |||||
| public Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null) | public Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null) | ||||
| => UserHelper.ModifyAsync(this, Discord, func, options); | => UserHelper.ModifyAsync(this, Discord, func, options); | ||||
| Task IUser.AddFriendAsync(RequestOptions options) => | |||||
| throw new InvalidOperationException("You can't friend yourself!"); | |||||
| Task IUser.BlockUserAsync(RequestOptions options) => | |||||
| throw new InvalidOperationException("You can't block yourself!"); | |||||
| Task IUser.RemoveRelationshipAsync(RequestOptions options) => | |||||
| throw new InvalidOperationException("You don't have any relations with yourself!"); | |||||
| internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; | internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; | ||||
| } | } | ||||
| } | } | ||||
| @@ -20,6 +20,7 @@ namespace Discord.WebSocket | |||||
| public string Mention => MentionUtils.MentionUser(Id); | public string Mention => MentionUtils.MentionUser(Id); | ||||
| public Game? Game => Presence.Game; | public Game? Game => Presence.Game; | ||||
| public UserStatus Status => Presence.Status; | public UserStatus Status => Presence.Status; | ||||
| public RelationshipType Relationship => Discord.GetRelationship(Id)?.Type ?? RelationshipType.None; | |||||
| internal SocketUser(DiscordSocketClient discord, ulong id) | internal SocketUser(DiscordSocketClient discord, ulong id) | ||||
| : base(discord, id) | : base(discord, id) | ||||
| @@ -64,5 +65,12 @@ namespace Discord.WebSocket | |||||
| public override string ToString() => $"{Username}#{Discriminator}"; | public override string ToString() => $"{Username}#{Discriminator}"; | ||||
| private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | ||||
| internal SocketUser Clone() => MemberwiseClone() as SocketUser; | internal SocketUser Clone() => MemberwiseClone() as SocketUser; | ||||
| public async Task AddFriendAsync(RequestOptions options = null) | |||||
| => await ClientHelper.AddFriendAsync(Discord, Id, options); | |||||
| public async Task BlockUserAsync(RequestOptions options = null) | |||||
| => await ClientHelper.BlockUserAsync(Discord, Id, options); | |||||
| public async Task RemoveRelationshipAsync(RequestOptions options = null) | |||||
| => await ClientHelper.RemoveRelationshipAsync(Discord, Id, options); | |||||
| } | } | ||||
| } | } | ||||