Skip to content

Commit

Permalink
v3 api: first pass
Browse files Browse the repository at this point in the history
  • Loading branch information
foxbot committed Jul 1, 2019
1 parent 3ca31cc commit 0cf14da
Show file tree
Hide file tree
Showing 73 changed files with 1,356 additions and 698 deletions.
25 changes: 21 additions & 4 deletions Discord.Net.sln
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
# Visual Studio Version 16
VisualStudioVersion = 16.0.29021.104
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{424AA4CA-E283-4B83-B288-B0181516D1F9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{279F3738-D35E-402B-BC99-CBE44821C468}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Tests.Unit", "test\Discord.Tests.Unit\Discord.Tests.Unit.csproj", "{97B3208E-FBB3-43D8-8944-EE06F6FE4032}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Tests.Integration", "test\Discord.Tests.Integration\Discord.Tests.Integration.csproj", "{D2A17BA6-C6A5-42DB-A4BC-F97C07904BA2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{279F3738-D35E-402B-BC99-CBE44821C468}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net", "src\Discord.Net\Discord.Net.csproj", "{65F1C45D-6D82-4E24-9F56-2840D2E6EFB9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{942F56B3-26D5-428A-AE1A-37480D26B516}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net", "src\Discord.Net\Discord.Net.csproj", "{65F1C45D-6D82-4E24-9F56-2840D2E6EFB9}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "examples\0_Using_the_Client\Sample.csproj", "{E8E39B45-F718-443B-B6BD-2ED092047B57}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -59,6 +63,18 @@ Global
{65F1C45D-6D82-4E24-9F56-2840D2E6EFB9}.Release|x64.Build.0 = Release|Any CPU
{65F1C45D-6D82-4E24-9F56-2840D2E6EFB9}.Release|x86.ActiveCfg = Release|Any CPU
{65F1C45D-6D82-4E24-9F56-2840D2E6EFB9}.Release|x86.Build.0 = Release|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Debug|x64.ActiveCfg = Debug|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Debug|x64.Build.0 = Debug|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Debug|x86.ActiveCfg = Debug|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Debug|x86.Build.0 = Debug|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Release|Any CPU.Build.0 = Release|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Release|x64.ActiveCfg = Release|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Release|x64.Build.0 = Release|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Release|x86.ActiveCfg = Release|Any CPU
{E8E39B45-F718-443B-B6BD-2ED092047B57}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -67,6 +83,7 @@ Global
{97B3208E-FBB3-43D8-8944-EE06F6FE4032} = {424AA4CA-E283-4B83-B288-B0181516D1F9}
{D2A17BA6-C6A5-42DB-A4BC-F97C07904BA2} = {424AA4CA-E283-4B83-B288-B0181516D1F9}
{65F1C45D-6D82-4E24-9F56-2840D2E6EFB9} = {279F3738-D35E-402B-BC99-CBE44821C468}
{E8E39B45-F718-443B-B6BD-2ED092047B57} = {942F56B3-26D5-428A-AE1A-37480D26B516}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1F07E0CE-26FE-4660-A0C3-4F9CC2BD7B72}
Expand Down
47 changes: 17 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
# Discord.Net
[![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net)
[![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net)
[![Build status](https://ci.appveyor.com/api/projects/status/5sb7n8a09w9clute/branch/dev?svg=true)](https://ci.appveyor.com/project/RogueException/discord-net/branch/dev)
[![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/jkrBmQR)

An unofficial .NET API Wrapper for the Discord client (http://discordapp.com).
An unofficial .NET API Wrapper for the Discord client (https://discordapp.com).

Check out the [documentation](https://discord.foxbot.me/docs/) or join the [Discord API Chat](https://discord.gg/jkrBmQR).
This branch is an unstable reference design of the next major release, Discord.Net v3. Currently, this branch
does not offer any functionality, and is only being used for API design.

## Installation
### Stable (NuGet)
Our stable builds available from NuGet through the Discord.Net metapackage:
- [Discord.Net](https://www.nuget.org/packages/Discord.Net/)
## Contributing

The individual components may also be installed from NuGet:
- [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/)
- [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/)
- [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/)
- [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/)
This branch is being developed on preview releases of .NET Core and Visual Studio; the following features
are necessary to contribute:
- C# 8 with Nullable Reference Types and IAsyncEnumerable
- .NET Core 3 with ValueTask support

### Unstable (MyGet)
Nightly builds are available through our MyGet feed (`https://www.myget.org/F/discord-net/api/v3/index.json`).
The following configurations are known to work:
- Visual Studio 2019 16.2 Preview 3
- .NET Core SDK 3.0.100-preview6-012264

## Compiling
In order to compile Discord.Net, you require the following:
### Documentation

### Using Visual Studio
- [Visual Studio 2017](https://www.microsoft.com/net/core#windowsvs2017)
- [.NET Core SDK](https://www.microsoft.com/net/download/core)
Documentation Strings are currently being left out, primarily because I find them extremely distracting when
trying to read over long interfaces, but also because this design will likely be iterated on before being finalized,
and it would be annoying to have to move docstrings around.

The .NET Core workload must be selected during Visual Studio installation.
Please leave docstrings out of contributions until the implementation round is finished.

### Using Command Line
- [.NET Core SDK](https://www.microsoft.com/net/download/core)

## Known Issues

### WebSockets (Win7 and earlier)
.NET Core 1.1 does not support WebSockets on Win7 and earlier. This issue has been fixed since the release of .NET Core 2.1. It is recommended to target .NET Core 2.1 or above for your project if you wish to run your bot on legacy platforms; alternatively, you may choose to install the [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) package.
Usability documentation is being provided in sample solutions.
53 changes: 53 additions & 0 deletions examples/0_Using_the_Client/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Threading.Tasks;
using Discord;

namespace Sample
{
public class Program
{
static void Main()
=> new Program().MainAsync().GetAwaiter().GetResult();
public async Task MainAsync()
{
string token = "abc";
var client = DiscordClientBuilder.FromConfig(new DiscordClientConfig
{
Token = token,
DefaultStateBehavior = StateBehavior.SyncOnly
});
await client.StartAndWaitAsync();

client.MessageCreated += msg => Task.Run(() => OnMessageCreated(msg)).Observe();

await Task.Delay(-1);
}
public async Task OnMessageCreated(IMessage msg)
{
if (!(msg is IUserMessage message))
return;
var guild = await msg.GetGuildAsync();
if (guild == null)
return;
var owner = await guild.GetOwnerAsync();
var dm = await (await owner.GetUserAsync()).GetOrCreateDMChannelAsync();
await dm.SendMessageAsync($"{msg.Author} said: {msg.Content}");
}
}

public static class TaskExtensions
{
public static void Observe(this Task task)
{
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
var flattened = t.Exception.Flatten();
foreach (var ex in flattened.InnerExceptions)
Console.WriteLine(ex.ToString());
}
});
}
}
}
8 changes: 8 additions & 0 deletions examples/0_Using_the_Client/Sample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>

</Project>
13 changes: 11 additions & 2 deletions src/Discord.Net/Discord.Net.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.3</LangVersion>
<TargetFramework>netcoreapp3.0</TargetFramework> <!-- TODO: netstandard3/.NET 5/whatever is mainstream when we release -->
<LangVersion>8.0</LangVersion>
<RootNamespace>Discord</RootNamespace>
<Nullable>Enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.2" />
<PackageReference Include="Wumpus.Net.Gateway" Version="0.2.2-build-00031" />
<PackageReference Include="Wumpus.Net.Rest" Version="0.2.2-build-00031" />
</ItemGroup>

<ItemGroup>
<Folder Include="Models\Emotes\" />
<Folder Include="Models\Guilds\" />
<Folder Include="Models\Roles\" />
</ItemGroup>

</Project>
39 changes: 38 additions & 1 deletion src/Discord.Net/DiscordClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ namespace Discord
internal class DiscordClient : IDiscordClient, IDisposable
{
public event Action Ready;
public event Action Connected;
public event Action Disconnected;
public event Action<IChannel> ChannelCreated;
public event Action<IChannel> ChannelDeleted;
public event Action<IMessage> MessageCreated;

private int _shard, _totalShards;
private readonly int _shard, _totalShards;
private TaskCompletionSource<bool>? _readyPromise;

public WumpusGatewayClient Gateway { get; }
public WumpusRestClient Rest { get; }
Expand All @@ -23,28 +29,59 @@ public DiscordClient(DiscordClientConfig config)
Gateway = new WumpusGatewayClient();
Rest = new WumpusRestClient();

if (config.Token == null)
throw new ArgumentNullException(nameof(config.Token), "The client token must be set.");
var auth = new AuthenticationHeaderValue("", config.Token);
// todo: port ChrisJ's token validator
Gateway.Authorization = auth;
Rest.Authorization = auth;

RegisterEvents();
}

public async Task StartAsync()
{
if (Gateway.State != ConnectionState.Disconnected)
await StopAsync().ConfigureAwait(false);

var gateway = await Rest.GetBotGatewayAsync().ConfigureAwait(false);
await Gateway.RunAsync(gateway.Url.ToString(), _shard, _totalShards).ConfigureAwait(false);
}

public async Task StartAndWaitAsync()
{
if (Gateway.State != ConnectionState.Disconnected)
await StopAsync().ConfigureAwait(false);

_readyPromise = new TaskCompletionSource<bool>();
await StartAsync().ConfigureAwait(false);
await _readyPromise.Task; // todo: need a timeout or cancellation token
_readyPromise = null;
}

public async Task StopAsync()
{
await Gateway.StopAsync().ConfigureAwait(false);
}

private void RegisterEvents()
{
Gateway.Connected += OnConnected;
Gateway.Disconnected += OnDisconnected;
Gateway.Ready += OnReady;
}
private void OnConnected()
{
Connected?.Invoke();
}
private void OnDisconnected(Exception e)
{
Disconnected?.Invoke();
}
private void OnReady(ReadyEvent args)
{
if (_readyPromise != null)
_readyPromise.TrySetResult(true);
// TODO: Cache
Ready?.Invoke();
}
Expand Down
3 changes: 2 additions & 1 deletion src/Discord.Net/DiscordClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ namespace Discord
{
public sealed class DiscordClientBuilder
{
public IDiscordClient FromConfig(DiscordClientConfig config)
private DiscordClientBuilder() { }
public static IDiscordClient FromConfig(DiscordClientConfig config)
{
return new DiscordClient(config);
}
Expand Down
5 changes: 4 additions & 1 deletion src/Discord.Net/DiscordClientConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ namespace Discord
{
public class DiscordClientConfig
{
public string Token { get; set; }
public string? Token { get; set; }

public int Shard { get; set; } = 0;
public int TotalShards { get; set; } = 1;

IStateProvider StateProvider { get; set; } = new ConcurrentMemoryStateProvider(); // todo: use a factory so the state is never exposed
public StateBehavior DefaultStateBehavior { get; set; } = StateBehavior.Default;
}
}
10 changes: 10 additions & 0 deletions src/Discord.Net/Entities/Activities/IActivity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// todo: meth
// todo: props
// todo: docs
namespace Discord
{
public interface IActivity
{
string Name { get; }
}
}
28 changes: 28 additions & 0 deletions src/Discord.Net/Entities/Channels/GuildChannelProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// todo: docs
using Voltaic;
using Wumpus;
using Model = Wumpus.Requests.ModifyChannelParams;

namespace Discord
{
public class GuildChannelProperties
{
public Optional<string> Name { get; set; }
public Optional<int> Position { get; set; }
public Optional<SnowflakeOrEntity<ICategoryChannel>?> Category { get; set; }

internal virtual Model ToWumpus()
{
var model = new Model();
if (Name.IsSpecified)
model.Name = new Utf8String(Name.Value);
model.Position = Position;
if (Category.IsSpecified)
{
model.ParentId = Category.Value.HasValue ? Category.Value.Value.Id : (Snowflake?)null;
}

return model;
}
}
}
8 changes: 8 additions & 0 deletions src/Discord.Net/Entities/Channels/ICategoryChannel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// todo: docs
namespace Discord
{
// review: another marker interface 😕
public interface ICategoryChannel : IGuildChannel
{
}
}
14 changes: 14 additions & 0 deletions src/Discord.Net/Entities/Channels/IChannel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// TODO: docs
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord
{
public interface IChannel : ISnowflakeEntity
{
string Name { get; }

ValueTask<IUser> GetUserAsync(ulong id, StateBehavior stateBehavior = default, RequestOptions? options = default);
IAsyncEnumerable<IUser> GetUsersAsync(StateBehavior stateBehavior = default, RequestOptions? options = default);
}
}
13 changes: 13 additions & 0 deletions src/Discord.Net/Entities/Channels/IDMChannel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// todo: docs
using System.Threading.Tasks;

namespace Discord
{
public interface IDMChannel : IMessageChannel
{
ValueTask<IUser> GetRecipientAsync(StateBehavior stateBehavior, RequestOptions? options = default);
ulong RecipientId { get; }

// not applicable: CloseAsync
}
}
Loading

0 comments on commit 0cf14da

Please sign in to comment.