| @@ -1,4 +1,4 @@ | |||||
| <Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
| <Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <VersionPrefix>1.0.0</VersionPrefix> | <VersionPrefix>1.0.0</VersionPrefix> | ||||
| <VersionSuffix Condition="'$(BuildNumber)' == ''">rc-dev</VersionSuffix> | <VersionSuffix Condition="'$(BuildNumber)' == ''">rc-dev</VersionSuffix> | ||||
| @@ -0,0 +1,9 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public interface IRelationship | |||||
| { | |||||
| RelationshipType Type { get; } | |||||
| IUser User { get; } | |||||
| } | |||||
| } | |||||
| @@ -21,5 +21,12 @@ namespace Discord | |||||
| Task<IDMChannel> GetDMChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<IDMChannel> GetDMChannelAsync(CacheMode mode = CacheMode.AllowDownload, 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> CreateDMChannelAsync(RequestOptions options = null); | Task<IDMChannel> CreateDMChannelAsync(RequestOptions options = null); | ||||
| /// <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); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,11 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public enum RelationshipType | |||||
| { | |||||
| None, | |||||
| Friend, | |||||
| Blocked, | |||||
| IncomingPending, | |||||
| OutgoingPending | |||||
| } | |||||
| } | |||||
| @@ -25,12 +25,14 @@ namespace Discord | |||||
| Task<IGuild> GetGuildAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | Task<IGuild> GetGuildAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | ||||
| Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload); | Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload); | ||||
| Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null); | Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null); | ||||
| Task<IInvite> GetInviteAsync(string inviteId); | Task<IInvite> GetInviteAsync(string inviteId); | ||||
| Task<IUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | Task<IUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | ||||
| Task<IUser> GetUserAsync(string username, string discriminator); | Task<IUser> GetUserAsync(string username, string discriminator); | ||||
| Task<IReadOnlyCollection<IRelationship>> GetRelationshipsAsync(); | |||||
| Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync(); | Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync(); | ||||
| Task<IVoiceRegion> GetVoiceRegionAsync(string id); | Task<IVoiceRegion> GetVoiceRegionAsync(string id); | ||||
| } | } | ||||
| @@ -1,11 +0,0 @@ | |||||
| #pragma warning disable CS1591 | |||||
| namespace Discord.API | |||||
| { | |||||
| internal enum RelationshipType | |||||
| { | |||||
| Friend = 1, | |||||
| Blocked = 2, | |||||
| IncomingPending = 3, | |||||
| OutgoingPending = 4 | |||||
| } | |||||
| } | |||||
| @@ -121,11 +121,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() | |||||
| => Task.FromResult<IReadOnlyCollection<IRelationship>>(null); | |||||
| Task<IApplication> IDiscordClient.GetApplicationInfoAsync() { throw new NotSupportedException(); } | Task<IApplication> IDiscordClient.GetApplicationInfoAsync() { throw new NotSupportedException(); } | ||||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode) | Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode) | ||||
| @@ -128,5 +128,11 @@ namespace Discord.Rest | |||||
| var models = await client.ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); | var models = await client.ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); | ||||
| return models.Select(x => RestVoiceRegion.Create(client, x)).Where(x => x.Id == id).FirstOrDefault(); | return models.Select(x => RestVoiceRegion.Create(client, x)).Where(x => x.Id == id).FirstOrDefault(); | ||||
| } | } | ||||
| 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)).ToImmutableArray(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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(); | ||||
| @@ -387,7 +387,7 @@ namespace Discord.API | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| //Channel Messages | //Channel Messages | ||||
| public async Task<Message> GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | public async Task<Message> GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | ||||
| { | { | ||||
| @@ -678,7 +678,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) | ||||
| @@ -886,14 +886,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); | ||||
| } | } | ||||
| @@ -1028,6 +1028,34 @@ 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) | |||||
| { | |||||
| 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)); | |||||
| 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)); | |||||
| 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)); | |||||
| 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) | ||||
| @@ -1193,7 +1221,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++; | ||||
| @@ -89,6 +89,9 @@ namespace Discord.Rest | |||||
| public Task<RestVoiceRegion> GetVoiceRegionAsync(string id) | public Task<RestVoiceRegion> GetVoiceRegionAsync(string id) | ||||
| => ClientHelper.GetVoiceRegionAsync(this, id); | => ClientHelper.GetVoiceRegionAsync(this, id); | ||||
| public Task<IReadOnlyCollection<RestRelationship>> GetRelationshipsAsync() | |||||
| => ClientHelper.GetRelationshipsAsync(this); | |||||
| //IDiscordClient | //IDiscordClient | ||||
| async Task<IApplication> IDiscordClient.GetApplicationInfoAsync() | async Task<IApplication> IDiscordClient.GetApplicationInfoAsync() | ||||
| => await GetApplicationInfoAsync().ConfigureAwait(false); | => await GetApplicationInfoAsync().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!"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -53,6 +53,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) | |||||
| => Discord.ApiClient.AddFriendAsync(Id, options); | |||||
| public Task BlockUserAsync(RequestOptions options = null) | |||||
| => Discord.ApiClient.BlockUserAsync(Id, options); | |||||
| public Task RemoveRelationshipAsync(RequestOptions options = null) | |||||
| => Discord.ApiClient.RemoveRelationshipAsync(Id, options); | |||||
| //IUser | //IUser | ||||
| Task<IDMChannel> IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) | Task<IDMChannel> IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) | ||||
| @@ -54,5 +54,12 @@ namespace Discord.Rpc | |||||
| => Task.FromResult<IDMChannel>(null); | => Task.FromResult<IDMChannel>(null); | ||||
| async Task<IDMChannel> IUser.CreateDMChannelAsync(RequestOptions options) | async Task<IDMChannel> IUser.CreateDMChannelAsync(RequestOptions options) | ||||
| => await CreateDMChannelAsync(options).ConfigureAwait(false); | => await CreateDMChannelAsync(options).ConfigureAwait(false); | ||||
| Task IUser.AddFriendAsync(RequestOptions options) | |||||
| => throw new NotSupportedException(); | |||||
| Task IUser.BlockUserAsync(RequestOptions options) | |||||
| => throw new NotSupportedException(); | |||||
| Task IUser.RemoveRelationshipAsync(RequestOptions options) | |||||
| => throw new NotSupportedException(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -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)); | ||||
| } | } | ||||
| @@ -126,5 +129,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; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -221,5 +221,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> RelationshipAdd | |||||
| { | |||||
| 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>>(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -70,6 +70,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()) { } | ||||
| @@ -270,6 +271,9 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task<RestInvite> GetInviteAsync(string inviteId) | public Task<RestInvite> GetInviteAsync(string inviteId) | ||||
| => ClientHelper.GetInviteAsync(this, inviteId); | => ClientHelper.GetInviteAsync(this, inviteId); | ||||
| public Task<IReadOnlyCollection<SocketRelationship>> GetRelationshipsAsync() | |||||
| => Task.FromResult(State.Relationships); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public SocketUser GetUser(ulong id) | public SocketUser GetUser(ulong id) | ||||
| @@ -484,6 +488,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; | ||||
| _unavailableGuilds = unavailableGuilds; | _unavailableGuilds = unavailableGuilds; | ||||
| @@ -1499,6 +1505,29 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| return; | 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); | |||||
| 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": | ||||
| @@ -1650,6 +1679,21 @@ namespace Discord.WebSocket | |||||
| return channel; | return channel; | ||||
| } | } | ||||
| 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); | |||||
| } | |||||
| //IDiscordClient | //IDiscordClient | ||||
| ConnectionState IDiscordClient.ConnectionState => _connection.State; | ConnectionState IDiscordClient.ConnectionState => _connection.State; | ||||
| @@ -1692,5 +1736,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() | |||||
| => await GetRelationshipsAsync(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,23 @@ | |||||
| using Model = Discord.API.Relationship; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| public class SocketRelationship : IRelationship | |||||
| { | |||||
| public RelationshipType Type { get; internal set; } | |||||
| public IUser User { get; internal set; } | |||||
| public SocketRelationship(RelationshipType type, IUser user) | |||||
| { | |||||
| Type = type; | |||||
| User = user; | |||||
| } | |||||
| internal static SocketRelationship Create(DiscordSocketClient discord, ClientState state, Model model) | |||||
| { | |||||
| SocketSimpleUser user = SocketSimpleUser.Create(discord, state, model.User); | |||||
| return new SocketRelationship(model.Type, user); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -46,6 +46,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; | ||||
| } | } | ||||
| } | } | ||||
| @@ -21,6 +21,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) | ||||
| @@ -55,5 +56,12 @@ namespace Discord.WebSocket | |||||
| => Task.FromResult<IDMChannel>(GlobalUser.DMChannel); | => Task.FromResult<IDMChannel>(GlobalUser.DMChannel); | ||||
| async Task<IDMChannel> IUser.CreateDMChannelAsync(RequestOptions options) | async Task<IDMChannel> IUser.CreateDMChannelAsync(RequestOptions options) | ||||
| => await CreateDMChannelAsync(options).ConfigureAwait(false); | => await CreateDMChannelAsync(options).ConfigureAwait(false); | ||||
| public async Task AddFriendAsync(RequestOptions options = null) | |||||
| => await Discord.ApiClient.AddFriendAsync(Id, options); | |||||
| public async Task BlockUserAsync(RequestOptions options = null) | |||||
| => await Discord.ApiClient.BlockUserAsync(Id, options); | |||||
| public async Task RemoveRelationshipAsync(RequestOptions options = null) | |||||
| => await Discord.ApiClient.RemoveRelationshipAsync(Id, options); | |||||
| } | } | ||||
| } | } | ||||