|
|
@@ -0,0 +1,94 @@ |
|
|
|
# Discord.Net 3.0 Interface Design # |
|
|
|
|
|
|
|
This is mostly just a collection of notes for interface design for 3.0. As |
|
|
|
such, don't expect them to be comprehensive or up to date. |
|
|
|
|
|
|
|
## Why no `ITextChannel`, `IGuildChannel`, etc? ## |
|
|
|
|
|
|
|
Usually, it's better to design types in terms of the fields they have, rather |
|
|
|
than using a discriminated union like the current `IChannel` interface. |
|
|
|
|
|
|
|
However, the design chosen in 1.0 led to [traits](wiki-traits), which in C# was |
|
|
|
*notoriously* hard to work with, debug and expand. Most notably, in |
|
|
|
`1.0.0-beta2`, the internal implementation details were exposed in form of |
|
|
|
`SocketUserMessage`, `SocketTextChannel` etc. simply to make consumption |
|
|
|
easier. The effect of this has been that writing portable, re-usable code is |
|
|
|
very hard, and has also caused us to be much slower when implementing updates |
|
|
|
from Discord. |
|
|
|
|
|
|
|
To simplify this, the current design has gone back to a centralised `IChannel` |
|
|
|
type, a la [D#+'s DiscordChannel](dsharpplus-channel) implementation. However, |
|
|
|
we are going to be using [Nullable Reference Types](nrts) to help reduce some |
|
|
|
of the inevitable errors which will come around with a discriminated union |
|
|
|
approach. Furthermore, this design allows in the future a traits-like system to |
|
|
|
be implemented on top of it, once the design flaws have been worked out. |
|
|
|
|
|
|
|
## Future Plans ## |
|
|
|
|
|
|
|
Ideally, [shapes] will become a thing, making the previous design *much* easier |
|
|
|
to implement and maintain, on top of the current design. To give a small |
|
|
|
example: |
|
|
|
|
|
|
|
```cs |
|
|
|
shape SChannel<T> |
|
|
|
{ |
|
|
|
public ulong Id { get; } |
|
|
|
} |
|
|
|
|
|
|
|
shape SGuildChannel<T> : SChannel<T> |
|
|
|
{ |
|
|
|
public ulong GuildId { get; } |
|
|
|
public int? Position { get; } |
|
|
|
public IEnumerable<PermissionOverwrite> PermissionOverwrites { get; } |
|
|
|
public string Name { get; } |
|
|
|
} |
|
|
|
|
|
|
|
shape STextChannel<T> : SChannel<T> |
|
|
|
{ |
|
|
|
public ulong? LastMessageId { get; } |
|
|
|
public DateTimeOffset LastPinTimestamp { get; } |
|
|
|
} |
|
|
|
|
|
|
|
shape SGuildTextChannel<T> : SGuildChannel<T>, STextChannel<T> |
|
|
|
{ |
|
|
|
public string? Topic { get; } |
|
|
|
public bool IsNsfw { get; } |
|
|
|
public int? RateLimit { get; } |
|
|
|
} |
|
|
|
|
|
|
|
public ValueTask<bool> DeleteLastMessageAsync<T>(T channel) |
|
|
|
where T : STextChannel<T> |
|
|
|
{ |
|
|
|
return channel.LastMessageId switch { |
|
|
|
null => new ValueTask<bool>(false), |
|
|
|
0 => new ValueTask<bool>(false), |
|
|
|
_ => new ValueTask<bool>(DeleteMessageAsync(channel.LastMessageId)) |
|
|
|
}; |
|
|
|
|
|
|
|
static async Task<bool> DeleteMessage(ulong messageId) |
|
|
|
{ |
|
|
|
var message = await GetMessageAsync(messageId); |
|
|
|
|
|
|
|
if (message == null) |
|
|
|
return false; |
|
|
|
|
|
|
|
await message?.DeleteAsync(); |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
Internally, `DeleteLastMessageAsync` will work on any channel, but as long |
|
|
|
as that channel exposes the correct APIs, the above method will function as the |
|
|
|
user expects. |
|
|
|
|
|
|
|
Additionally, this provides a huge advantage in that the above code is |
|
|
|
extremely easy to unit test; any type which fulfils the contract of |
|
|
|
`STextChannel<T>` can be used, even if it doesn't implement the `IChannel` |
|
|
|
interface. |
|
|
|
|
|
|
|
|
|
|
|
[wiki-traits]: https://en.wikipedia.org/wiki/Trait_(computer_programming) |
|
|
|
[dsharpplus-channel]: https://dsharpplus.emzi0767.com/api/DSharpPlus.Entities.DiscordChannel.html |
|
|
|
[nrts]: https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references |
|
|
|
[shapes]: https://github.com/dotnet/csharplang/issues/164 |