diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index 9f75d7327..d78840281 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -1,4 +1,4 @@ - + 1.0.0 rc-dev diff --git a/src/Discord.Net.Core/Entities/Users/IRelationship.cs b/src/Discord.Net.Core/Entities/Users/IRelationship.cs new file mode 100644 index 000000000..6403184e7 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Users/IRelationship.cs @@ -0,0 +1,9 @@ +namespace Discord +{ + public interface IRelationship + { + RelationshipType Type { get; } + + IUser User { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index b7e807d8e..aeb65aa9c 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -21,5 +21,12 @@ namespace Discord Task GetDMChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Returns a private message channel to this user, creating one if it does not already exist. Task CreateDMChannelAsync(RequestOptions options = null); + + /// Adds this user as a friend, this will remove a block + Task AddFriendAsync(RequestOptions options = null); + /// Blocks this user, and removes the user as a friend + Task BlockUserAsync(RequestOptions options = null); + /// Removes the relationship of this user + Task RemoveRelationshipAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Users/RelationshipType.cs b/src/Discord.Net.Core/Entities/Users/RelationshipType.cs new file mode 100644 index 000000000..dc5129aa1 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Users/RelationshipType.cs @@ -0,0 +1,11 @@ +namespace Discord +{ + public enum RelationshipType + { + None, + Friend, + Blocked, + IncomingPending, + OutgoingPending + } +} diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index 1c5ec41c1..1d3bf3df7 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -25,12 +25,14 @@ namespace Discord Task GetGuildAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); Task> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload); Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null); - + Task GetInviteAsync(string inviteId); Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); Task GetUserAsync(string username, string discriminator); + Task> GetRelationshipsAsync(); + Task> GetVoiceRegionsAsync(); Task GetVoiceRegionAsync(string id); } diff --git a/src/Discord.Net.Rest/API/Common/RelationshipType.cs b/src/Discord.Net.Rest/API/Common/RelationshipType.cs deleted file mode 100644 index 0ed99f396..000000000 --- a/src/Discord.Net.Rest/API/Common/RelationshipType.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 -namespace Discord.API -{ - internal enum RelationshipType - { - Friend = 1, - Blocked = 2, - IncomingPending = 3, - OutgoingPending = 4 - } -} diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 80c4cb598..f38b9506d 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -121,11 +121,14 @@ namespace Discord.Rest } /// public void Dispose() => Dispose(true); - + //IDiscordClient ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; ISelfUser IDiscordClient.CurrentUser => CurrentUser; - + + Task> IDiscordClient.GetRelationshipsAsync() + => Task.FromResult>(null); + Task IDiscordClient.GetApplicationInfoAsync() { throw new NotSupportedException(); } Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode) diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 456362be6..25a84802e 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -128,5 +128,11 @@ namespace Discord.Rest var models = await client.ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); return models.Select(x => RestVoiceRegion.Create(client, x)).Where(x => x.Id == id).FirstOrDefault(); } + + public static async Task> GetRelationshipsAsync(BaseDiscordClient client) + { + var models = await client.ApiClient.GetRelationshipsAsync().ConfigureAwait(false); + return models.Select(r => RestRelationship.Create(client, r)).ToImmutableArray(); + } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 9e9ac4611..3587d95f8 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -241,7 +241,7 @@ namespace Discord.API internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); - public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, + public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { options = options ?? new RequestOptions(); @@ -387,7 +387,7 @@ namespace Discord.API break; } } - + //Channel Messages public async Task 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.RegionId, nameof(args.RegionId)); options = RequestOptions.CreateOrClone(options); - + return await SendJsonAsync("POST", () => "guilds", args, new BucketIds(), options: options).ConfigureAwait(false); } public async Task DeleteGuildAsync(ulong guildId, RequestOptions options = null) @@ -886,14 +886,14 @@ namespace Discord.API { Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); options = RequestOptions.CreateOrClone(options); - + return await SendAsync("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); } public async Task AcceptInviteAsync(string inviteId, RequestOptions options = null) { Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); options = RequestOptions.CreateOrClone(options); - + 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; } } + + //Relationships + public async Task> GetRelationshipsAsync(RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + return await SendAsync>("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 public async Task GetMyUserAsync(RequestOptions options = null) @@ -1193,7 +1221,7 @@ namespace Discord.API int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); string fieldName = GetFieldName(methodArgs[argId + 1]); int? mappedId; - + mappedId = BucketIds.GetIndex(fieldName); if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash rightIndex++; diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 0ff1a4821..5163e7613 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -89,6 +89,9 @@ namespace Discord.Rest public Task GetVoiceRegionAsync(string id) => ClientHelper.GetVoiceRegionAsync(this, id); + public Task> GetRelationshipsAsync() + => ClientHelper.GetRelationshipsAsync(this); + //IDiscordClient async Task IDiscordClient.GetApplicationInfoAsync() => await GetApplicationInfoAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Users/RestRelationship.cs b/src/Discord.Net.Rest/Entities/Users/RestRelationship.cs new file mode 100644 index 000000000..7061969fe --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Users/RestRelationship.cs @@ -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); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs index ab5ec4a3b..4ebe8ee8f 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -49,5 +49,14 @@ namespace Discord.Rest var model = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); 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!"); } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index b439fb886..7e3cdb4fd 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -53,6 +53,13 @@ namespace Discord.Rest public override string ToString() => $"{Username}#{Discriminator}"; 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 Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs index e78aee008..86524b721 100644 --- a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs +++ b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs @@ -54,5 +54,12 @@ namespace Discord.Rpc => Task.FromResult(null); async Task IUser.CreateDMChannelAsync(RequestOptions options) => 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(); } } diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index a452113e2..ce61a0ba5 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -15,6 +15,7 @@ namespace Discord.WebSocket private readonly ConcurrentDictionary _dmChannels; private readonly ConcurrentDictionary _guilds; private readonly ConcurrentDictionary _users; + private readonly ConcurrentDictionary _relationships; private readonly ConcurrentHashSet _groupChannels; internal IReadOnlyCollection Channels => _channels.ToReadOnlyCollection(); @@ -22,6 +23,7 @@ namespace Discord.WebSocket internal IReadOnlyCollection GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); internal IReadOnlyCollection Guilds => _guilds.ToReadOnlyCollection(); internal IReadOnlyCollection Users => _users.ToReadOnlyCollection(); + internal IReadOnlyCollection Relationships => _relationships.ToReadOnlyCollection(); internal IReadOnlyCollection PrivateChannels => _dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat( @@ -36,6 +38,7 @@ namespace Discord.WebSocket _dmChannels = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); _guilds = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); _users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); + _relationships = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, 35); _groupChannels = new ConcurrentHashSet(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier)); } @@ -126,5 +129,22 @@ namespace Discord.WebSocket return user; 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; + } } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs index 313e661f3..d731e390a 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs @@ -221,5 +221,19 @@ namespace Discord.WebSocket remove { _recipientRemovedEvent.Remove(value); } } private readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>(); + + // relationships + public event Func RelationshipAdd + { + add { _relationshipAddedEvent.Add(value); } + remove { _relationshipAddedEvent.Remove(value); } + } + private readonly AsyncEvent> _relationshipAddedEvent = new AsyncEvent>(); + public event Func RelationshipRemoved + { + add { _relationshipRemovedEvent.Add(value); } + remove { _relationshipRemovedEvent.Remove(value); } + } + private readonly AsyncEvent> _relationshipRemovedEvent = new AsyncEvent>(); } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index fdb8b2359..c375825db 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -70,6 +70,7 @@ namespace Discord.WebSocket public IReadOnlyCollection GroupChannels => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); public IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); + public IReadOnlyCollection Relationships => State.Relationships; /// Creates a new REST/WebSocket discord client. public DiscordSocketClient() : this(new DiscordSocketConfig()) { } @@ -270,6 +271,9 @@ namespace Discord.WebSocket /// public Task GetInviteAsync(string inviteId) => ClientHelper.GetInviteAsync(this, inviteId); + + public Task> GetRelationshipsAsync() + => Task.FromResult(State.Relationships); /// public SocketUser GetUser(ulong id) @@ -484,6 +488,8 @@ namespace Discord.WebSocket } for (int i = 0; i < data.PrivateChannels.Length; i++) AddPrivateChannel(data.PrivateChannels[i], state); + for (int i = 0; i < data.Relationships.Length; i++) + AddRelationship(data.Relationships[i], state); _sessionId = data.SessionId; _unavailableGuilds = unavailableGuilds; @@ -1499,6 +1505,29 @@ namespace Discord.WebSocket } } return; + + //Relationships + case "RELATIONSHIP_ADD": + { + await _gatewayLogger.DebugAsync("Received Dispatch (RELATIONSHIP_ADD)").ConfigureAwait(false); + + var addedModel = (payload as JToken).ToObject(_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(_serializer); + var removed = RemoveRelationship(removedModel.Id); + + await _relationshipRemovedEvent.InvokeAsync(removed); + return; + } //Ignored (User only) case "CHANNEL_PINS_ACK": @@ -1650,6 +1679,21 @@ namespace Discord.WebSocket 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 ConnectionState IDiscordClient.ConnectionState => _connection.State; @@ -1692,5 +1736,8 @@ namespace Discord.WebSocket => await StartAsync().ConfigureAwait(false); async Task IDiscordClient.StopAsync() => await StopAsync().ConfigureAwait(false); + + async Task> IDiscordClient.GetRelationshipsAsync() + => await GetRelationshipsAsync(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketRelationship.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketRelationship.cs new file mode 100644 index 000000000..a7bbcb805 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketRelationship.cs @@ -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); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index 0f6d4e4f1..b7531ffc6 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -46,6 +46,15 @@ namespace Discord.WebSocket public Task ModifyAsync(Action func, RequestOptions options = null) => 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; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 574e79b6e..2b29311ff 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -21,6 +21,7 @@ namespace Discord.WebSocket public string Mention => MentionUtils.MentionUser(Id); public Game? Game => Presence.Game; public UserStatus Status => Presence.Status; + public RelationshipType Relationship => Discord.GetRelationship(Id)?.Type ?? RelationshipType.None; internal SocketUser(DiscordSocketClient discord, ulong id) : base(discord, id) @@ -55,5 +56,12 @@ namespace Discord.WebSocket => Task.FromResult(GlobalUser.DMChannel); async Task IUser.CreateDMChannelAsync(RequestOptions options) => 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); } }