@@ -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> | |||
<VersionPrefix>1.0.0</VersionPrefix> | |||
<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); | |||
/// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | |||
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<IReadOnlyCollection<IGuild>> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload); | |||
Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null); | |||
Task<IInvite> GetInviteAsync(string inviteId); | |||
Task<IUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); | |||
Task<IUser> GetUserAsync(string username, string discriminator); | |||
Task<IReadOnlyCollection<IRelationship>> GetRelationshipsAsync(); | |||
Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync(); | |||
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 /> | |||
public void Dispose() => Dispose(true); | |||
//IDiscordClient | |||
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | |||
ISelfUser IDiscordClient.CurrentUser => CurrentUser; | |||
Task<IReadOnlyCollection<IRelationship>> IDiscordClient.GetRelationshipsAsync() | |||
=> Task.FromResult<IReadOnlyCollection<IRelationship>>(null); | |||
Task<IApplication> IDiscordClient.GetApplicationInfoAsync() { throw new NotSupportedException(); } | |||
Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode) | |||
@@ -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<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, | |||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> 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) | |||
{ | |||
options = options ?? new RequestOptions(); | |||
@@ -387,7 +387,7 @@ namespace Discord.API | |||
break; | |||
} | |||
} | |||
//Channel Messages | |||
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.RegionId, nameof(args.RegionId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
return await SendJsonAsync<Guild>("POST", () => "guilds", args, new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Guild> 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<Invite>("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<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 | |||
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)); | |||
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++; | |||
@@ -89,6 +89,9 @@ namespace Discord.Rest | |||
public Task<RestVoiceRegion> GetVoiceRegionAsync(string id) | |||
=> ClientHelper.GetVoiceRegionAsync(this, id); | |||
public Task<IReadOnlyCollection<RestRelationship>> GetRelationshipsAsync() | |||
=> ClientHelper.GetRelationshipsAsync(this); | |||
//IDiscordClient | |||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync() | |||
=> 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); | |||
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}"; | |||
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<IDMChannel> IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) | |||
@@ -54,5 +54,12 @@ namespace Discord.Rpc | |||
=> Task.FromResult<IDMChannel>(null); | |||
async Task<IDMChannel> 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(); | |||
} | |||
} |
@@ -15,6 +15,7 @@ namespace Discord.WebSocket | |||
private readonly ConcurrentDictionary<ulong, SocketDMChannel> _dmChannels; | |||
private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds; | |||
private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users; | |||
private readonly ConcurrentDictionary<ulong, SocketRelationship> _relationships; | |||
private readonly ConcurrentHashSet<ulong> _groupChannels; | |||
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<SocketGuild> Guilds => _guilds.ToReadOnlyCollection(); | |||
internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection(); | |||
internal IReadOnlyCollection<SocketRelationship> Relationships => _relationships.ToReadOnlyCollection(); | |||
internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => | |||
_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)); | |||
_guilds = new ConcurrentDictionary<ulong, SocketGuild>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * 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)); | |||
} | |||
@@ -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; | |||
} | |||
} | |||
} |
@@ -221,5 +221,19 @@ namespace Discord.WebSocket | |||
remove { _recipientRemovedEvent.Remove(value); } | |||
} | |||
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 | |||
=> State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); | |||
public IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | |||
public IReadOnlyCollection<SocketRelationship> Relationships => State.Relationships; | |||
/// <summary> Creates a new REST/WebSocket discord client. </summary> | |||
public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | |||
@@ -270,6 +271,9 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public Task<RestInvite> GetInviteAsync(string inviteId) | |||
=> ClientHelper.GetInviteAsync(this, inviteId); | |||
public Task<IReadOnlyCollection<SocketRelationship>> GetRelationshipsAsync() | |||
=> Task.FromResult(State.Relationships); | |||
/// <inheritdoc /> | |||
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<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) | |||
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<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) | |||
=> 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; | |||
} | |||
} |
@@ -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<IDMChannel>(GlobalUser.DMChannel); | |||
async Task<IDMChannel> 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); | |||
} | |||
} |