Browse Source

Initial 1.0-alpha1 design changes

tags/docs-0.9
RogueException 9 years ago
parent
commit
27d7e9915b
72 changed files with 1245 additions and 1710 deletions
  1. +3
    -35
      src/Discord.Net.Audio/AudioClient.cs
  2. +2
    -2
      src/Discord.Net.Audio/AudioExtensions.cs
  3. +3
    -3
      src/Discord.Net.Audio/AudioService.cs
  4. +2
    -4
      src/Discord.Net.Audio/IAudioClient.cs
  5. +1
    -1
      src/Discord.Net.Audio/Net/VoiceSocket.cs
  6. +2
    -3
      src/Discord.Net.Audio/VirtualClient.cs
  7. +2
    -2
      src/Discord.Net.Audio/project.json
  8. +1
    -1
      src/Discord.Net.Commands/Command.cs
  9. +2
    -2
      src/Discord.Net.Commands/CommandBuilder.cs
  10. +1
    -2
      src/Discord.Net.Commands/CommandEventArgs.cs
  11. +1
    -1
      src/Discord.Net.Commands/CommandMap.cs
  12. +7
    -29
      src/Discord.Net.Commands/CommandService.cs
  13. +3
    -3
      src/Discord.Net.Commands/GenericPermissionChecker.cs
  14. +1
    -1
      src/Discord.Net.Commands/IPermissionChecker.cs
  15. +0
    -24
      src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs
  16. +0
    -29
      src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs
  17. +0
    -23
      src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs
  18. +0
    -18
      src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs
  19. +0
    -48
      src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs
  20. +0
    -13
      src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs
  21. +0
    -52
      src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs
  22. +0
    -18
      src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs
  23. +0
    -48
      src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs
  24. +0
    -13
      src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs
  25. +0
    -21
      src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs
  26. +0
    -21
      src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs
  27. +0
    -21
      src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs
  28. +0
    -21
      src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs
  29. +2
    -2
      src/Discord.Net.Commands/project.json
  30. +4
    -2
      src/Discord.Net.Modules/ModuleChecker.cs
  31. +52
    -90
      src/Discord.Net.Modules/ModuleManager.cs
  32. +3
    -3
      src/Discord.Net.Modules/project.json
  33. +12
    -0
      src/Discord.Net.Net45/Discord.Net.csproj
  34. +2
    -0
      src/Discord.Net/API/Client/Common/Channel.cs
  35. +2
    -2
      src/Discord.Net/API/Client/Rest/AddChannelPermission.cs
  36. +1
    -1
      src/Discord.Net/API/Client/Rest/CreateChannel.cs
  37. +2
    -0
      src/Discord.Net/API/Client/Rest/UpdateChannel.cs
  38. +6
    -6
      src/Discord.Net/DiscordClient.Events.cs
  39. +71
    -112
      src/Discord.Net/DiscordClient.cs
  40. +42
    -0
      src/Discord.Net/EnumConverters.cs
  41. +7
    -31
      src/Discord.Net/Enums/ChannelType.cs
  42. +3
    -30
      src/Discord.Net/Enums/PermissionTarget.cs
  43. +2
    -4
      src/Discord.Net/Events/ChannelEventArgs.cs
  44. +3
    -5
      src/Discord.Net/Events/ChannelUpdatedEventArgs.cs
  45. +1
    -1
      src/Discord.Net/Events/MessageEventArgs.cs
  46. +1
    -1
      src/Discord.Net/Events/MessageUpdatedEventArgs.cs
  47. +3
    -3
      src/Discord.Net/Events/TypingEventArgs.cs
  48. +11
    -0
      src/Discord.Net/IModel.cs
  49. +0
    -81
      src/Discord.Net/InternalExtensions.cs
  50. +54
    -65
      src/Discord.Net/MessageQueue.cs
  51. +27
    -574
      src/Discord.Net/Models/Channel.cs
  52. +0
    -22
      src/Discord.Net/Models/Color.cs
  53. +21
    -0
      src/Discord.Net/Models/IChannel.cs
  54. +6
    -0
      src/Discord.Net/Models/IPrivateChannel.cs
  55. +6
    -0
      src/Discord.Net/Models/IPublicChannel.cs
  56. +17
    -0
      src/Discord.Net/Models/ITextChannel.cs
  57. +6
    -0
      src/Discord.Net/Models/IVoiceChannel.cs
  58. +4
    -6
      src/Discord.Net/Models/Invite.cs
  59. +154
    -0
      src/Discord.Net/Models/Managers/MessageManager.cs
  60. +223
    -0
      src/Discord.Net/Models/Managers/PermissionManager.cs
  61. +41
    -62
      src/Discord.Net/Models/Message.cs
  62. +20
    -12
      src/Discord.Net/Models/Permissions.cs
  63. +66
    -0
      src/Discord.Net/Models/PrivateChannel.cs
  64. +1
    -1
      src/Discord.Net/Models/Profile.cs
  65. +109
    -0
      src/Discord.Net/Models/PublicChannel.cs
  66. +2
    -0
      src/Discord.Net/Models/Region.cs
  67. +2
    -4
      src/Discord.Net/Models/Role.cs
  68. +41
    -73
      src/Discord.Net/Models/Server.cs
  69. +88
    -0
      src/Discord.Net/Models/TextChannel.cs
  70. +34
    -54
      src/Discord.Net/Models/User.cs
  71. +60
    -0
      src/Discord.Net/Models/VoiceChannel.cs
  72. +2
    -4
      src/Discord.Net/project.json

+ 3
- 35
src/Discord.Net.Audio/AudioClient.cs View File

@@ -7,7 +7,6 @@ using Newtonsoft.Json;
using Nito.AsyncEx; using Nito.AsyncEx;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


@@ -15,35 +14,6 @@ namespace Discord.Audio
{ {
internal class AudioClient : IAudioClient internal class AudioClient : IAudioClient
{ {
private class OutStream : Stream
{
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;

private readonly AudioClient _client;

internal OutStream(AudioClient client)
{
_client = client;
}

public override long Length { get { throw new InvalidOperationException(); } }
public override long Position
{
get { throw new InvalidOperationException(); }
set { throw new InvalidOperationException(); }
}
public override void Flush() { throw new InvalidOperationException(); }
public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); }
public override void SetLength(long value) { throw new InvalidOperationException(); }
public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException(); }
public override void Write(byte[] buffer, int offset, int count)
{
_client.Send(buffer, offset, count);
}
}

private readonly DiscordConfig _config; private readonly DiscordConfig _config;
private readonly AsyncLock _connectionLock; private readonly AsyncLock _connectionLock;
private readonly TaskManager _taskManager; private readonly TaskManager _taskManager;
@@ -58,14 +28,13 @@ namespace Discord.Audio
public GatewaySocket GatewaySocket { get; } public GatewaySocket GatewaySocket { get; }
public VoiceSocket VoiceSocket { get; } public VoiceSocket VoiceSocket { get; }
public JsonSerializer Serializer { get; } public JsonSerializer Serializer { get; }
public Stream OutputStream { get; }
public CancellationToken CancelToken { get; private set; } public CancellationToken CancelToken { get; private set; }
public string SessionId => GatewaySocket.SessionId; public string SessionId => GatewaySocket.SessionId;


public ConnectionState State => VoiceSocket.State; public ConnectionState State => VoiceSocket.State;
public Server Server => VoiceSocket.Server; public Server Server => VoiceSocket.Server;
public Channel Channel => VoiceSocket.Channel;
public VoiceChannel Channel => VoiceSocket.Channel;


public AudioClient(DiscordClient client, Server server, int id) public AudioClient(DiscordClient client, Server server, int id)
{ {
@@ -121,7 +90,6 @@ namespace Discord.Audio
GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e);
VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}")); VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}"));
VoiceSocket.Server = server; VoiceSocket.Server = server;
OutputStream = new OutStream(this);
} }


public async Task Connect() public async Task Connect()
@@ -216,7 +184,7 @@ namespace Discord.Audio
_gatewayState = (int)ConnectionState.Disconnected; _gatewayState = (int)ConnectionState.Disconnected;
} }


public async Task Join(Channel channel)
public async Task Join(VoiceChannel channel)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));
if (channel.Type != ChannelType.Voice) if (channel.Type != ChannelType.Voice)
@@ -248,7 +216,7 @@ namespace Discord.Audio
await Disconnect().ConfigureAwait(false); await Disconnect().ConfigureAwait(false);
else else
{ {
var channel = Service.Client.GetChannel(data.ChannelId.Value);
var channel = Service.Client.GetChannel(data.ChannelId.Value) as VoiceChannel;
if (channel != null) if (channel != null)
VoiceSocket.Channel = channel; VoiceSocket.Channel = channel;
else else


+ 2
- 2
src/Discord.Net.Audio/AudioExtensions.cs View File

@@ -18,8 +18,8 @@ namespace Discord.Audio
return client; return client;
} }


public static Task<IAudioClient> JoinAudio(this Channel channel) => channel.Client.GetService<AudioService>().Join(channel);
public static Task LeaveAudio(this Channel channel) => channel.Client.GetService<AudioService>().Leave(channel);
public static Task<IAudioClient> JoinAudio(this VoiceChannel channel) => channel.Client.GetService<AudioService>().Join(channel);
public static Task LeaveAudio(this VoiceChannel channel) => channel.Client.GetService<AudioService>().Leave(channel);
public static Task LeaveAudio(this Server server) => server.Client.GetService<AudioService>().Leave(server); public static Task LeaveAudio(this Server server) => server.Client.GetService<AudioService>().Leave(server);
public static IAudioClient GetAudioClient(this Server server) => server.Client.GetService<AudioService>().GetClient(server); public static IAudioClient GetAudioClient(this Server server) => server.Client.GetService<AudioService>().GetClient(server);
} }


+ 3
- 3
src/Discord.Net.Audio/AudioService.cs View File

@@ -113,7 +113,7 @@ namespace Discord.Audio
} }
} }


public async Task<IAudioClient> Join(Channel channel)
public async Task<IAudioClient> Join(VoiceChannel channel)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));
@@ -163,8 +163,8 @@ namespace Discord.Audio
} }


public Task Leave(Server server) => Leave(server, null); public Task Leave(Server server) => Leave(server, null);
public Task Leave(Channel channel) => Leave(channel.Server, channel);
private async Task Leave(Server server, Channel channel)
public Task Leave(VoiceChannel channel) => Leave(channel.Server, channel);
private async Task Leave(Server server, VoiceChannel channel)
{ {
if (server == null) throw new ArgumentNullException(nameof(server)); if (server == null) throw new ArgumentNullException(nameof(server));




+ 2
- 4
src/Discord.Net.Audio/IAudioClient.cs View File

@@ -15,11 +15,9 @@ namespace Discord.Audio
/// <summary> Gets the current state of this client. </summary> /// <summary> Gets the current state of this client. </summary>
ConnectionState State { get; } ConnectionState State { get; }
/// <summary> Gets the channel this client is currently a member of. </summary> /// <summary> Gets the channel this client is currently a member of. </summary>
Channel Channel { get; }
VoiceChannel Channel { get; }
/// <summary> Gets the server this client is bound to. </summary> /// <summary> Gets the server this client is bound to. </summary>
Server Server { get; } Server Server { get; }
/// <summary> Gets a stream object that wraps the Send() function. </summary>
Stream OutputStream { get; }
/// <summary> Gets a cancellation token that triggers when the client is manually disconnected. </summary> /// <summary> Gets a cancellation token that triggers when the client is manually disconnected. </summary>
CancellationToken CancelToken { get; } CancellationToken CancelToken { get; }


@@ -31,7 +29,7 @@ namespace Discord.Audio
VoiceSocket VoiceSocket { get; } VoiceSocket VoiceSocket { get; }


/// <summary> Moves the client to another channel on the same server. </summary> /// <summary> Moves the client to another channel on the same server. </summary>
Task Join(Channel channel);
Task Join(VoiceChannel channel);
/// <summary> Disconnects from the Discord server, canceling any pending requests. </summary> /// <summary> Disconnects from the Discord server, canceling any pending requests. </summary>
Task Disconnect(); Task Disconnect();




+ 1
- 1
src/Discord.Net.Audio/Net/VoiceSocket.cs View File

@@ -46,7 +46,7 @@ namespace Discord.Net.WebSockets


public string Token { get; internal set; } public string Token { get; internal set; }
public Server Server { get; internal set; } public Server Server { get; internal set; }
public Channel Channel { get; internal set; }
public VoiceChannel Channel { get; internal set; }


public int Ping => _ping; public int Ping => _ping;
internal VoiceBuffer OutputBuffer => _sendBuffer; internal VoiceBuffer OutputBuffer => _sendBuffer;


+ 2
- 3
src/Discord.Net.Audio/VirtualClient.cs View File

@@ -16,8 +16,7 @@ namespace Discord.Audio
public string SessionId => _client.Server == Server ? _client.SessionId : null; public string SessionId => _client.Server == Server ? _client.SessionId : null;


public ConnectionState State => _client.Server == Server ? _client.State : ConnectionState.Disconnected; public ConnectionState State => _client.Server == Server ? _client.State : ConnectionState.Disconnected;
public Channel Channel => _client.Server == Server ? _client.Channel : null;
public Stream OutputStream => _client.Server == Server ? _client.OutputStream : null;
public VoiceChannel Channel => _client.Server == Server ? _client.Channel : null;
public CancellationToken CancelToken => _client.Server == Server ? _client.CancelToken : CancellationToken.None; public CancellationToken CancelToken => _client.Server == Server ? _client.CancelToken : CancellationToken.None;


public RestClient ClientAPI => _client.Server == Server ? _client.ClientAPI : null; public RestClient ClientAPI => _client.Server == Server ? _client.ClientAPI : null;
@@ -31,7 +30,7 @@ namespace Discord.Audio
} }


public Task Disconnect() => _client.Service.Leave(Server); public Task Disconnect() => _client.Service.Leave(Server);
public Task Join(Channel channel) => _client.Join(channel);
public Task Join(VoiceChannel channel) => _client.Join(channel);


public void Send(byte[] data, int offset, int count) => _client.Send(data, offset, count); public void Send(byte[] data, int offset, int count) => _client.Send(data, offset, count);
public void Clear() => _client.Clear(); public void Clear() => _client.Clear();


+ 2
- 2
src/Discord.Net.Audio/project.json View File

@@ -1,5 +1,5 @@
{ {
"version": "0.9.0-rc3",
"version": "1.0.0-alpha1",
"description": "A Discord.Net extension adding voice support.", "description": "A Discord.Net extension adding voice support.",
"authors": [ "RogueException" ], "authors": [ "RogueException" ],
"tags": [ "discord", "discordapp" ], "tags": [ "discord", "discordapp" ],
@@ -18,7 +18,7 @@
}, },


"dependencies": { "dependencies": {
"Discord.Net": "0.9.0-rc3-3"
"Discord.Net": "1.0.0-alpha1"
}, },
"frameworks": { "frameworks": {
"net45": { }, "net45": { },


+ 1
- 1
src/Discord.Net.Commands/Command.cs View File

@@ -52,7 +52,7 @@ namespace Discord.Commands
_checks = checks; _checks = checks;
} }


internal bool CanRun(User user, Channel channel, out string error)
internal bool CanRun(User user, ITextChannel channel, out string error)
{ {
for (int i = 0; i < _checks.Length; i++) for (int i = 0; i < _checks.Length; i++)
{ {


+ 2
- 2
src/Discord.Net.Commands/CommandBuilder.cs View File

@@ -79,7 +79,7 @@ namespace Discord.Commands
_checks.Add(check); _checks.Add(check);
return this; return this;
} }
public CommandBuilder AddCheck(Func<Command, User, Channel, bool> checkFunc, string errorMsg = null)
public CommandBuilder AddCheck(Func<Command, User, ITextChannel, bool> checkFunc, string errorMsg = null)
{ {
_checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg));
return this; return this;
@@ -145,7 +145,7 @@ namespace Discord.Commands
{ {
_checks.Add(checker); _checks.Add(checker);
} }
public void AddCheck(Func<Command, User, Channel, bool> checkFunc, string errorMsg = null)
public void AddCheck(Func<Command, User, ITextChannel, bool> checkFunc, string errorMsg = null)
{ {
_checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg));
} }


+ 1
- 2
src/Discord.Net.Commands/CommandEventArgs.cs View File

@@ -10,8 +10,7 @@ namespace Discord.Commands
public Command Command { get; } public Command Command { get; }


public User User => Message.User; public User User => Message.User;
public Channel Channel => Message.Channel;
public Server Server => Message.Channel.Server;
public ITextChannel Channel => Message.Channel;


public CommandEventArgs(Message message, Command command, string[] args) public CommandEventArgs(Message message, Command command, string[] args)
{ {


+ 1
- 1
src/Discord.Net.Commands/CommandMap.cs View File

@@ -116,7 +116,7 @@ namespace Discord.Commands
} }
} }


public bool CanRun(User user, Channel channel, out string error)
public bool CanRun(User user, ITextChannel channel, out string error)
{ {
error = null; error = null;
if (_commands.Count > 0) if (_commands.Count > 0)


+ 7
- 29
src/Discord.Net.Commands/CommandService.cs View File

@@ -63,7 +63,7 @@ namespace Discord.Commands
.Description("Returns information about commands.") .Description("Returns information about commands.")
.Do(async e => .Do(async e =>
{ {
Channel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false);
ITextChannel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false);
if (e.Args.Length > 0) //Show command help if (e.Args.Length > 0) //Show command help
{ {
var map = _map.GetItem(string.Join(" ", e.Args)); var map = _map.GetItem(string.Join(" ", e.Args));
@@ -175,7 +175,7 @@ namespace Discord.Commands
}; };
} }


public Task ShowGeneralHelp(User user, Channel channel, Channel replyChannel = null)
public Task ShowGeneralHelp(User user, ITextChannel channel, ITextChannel replyChannel = null)
{ {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
bool isFirstCategory = true; bool isFirstCategory = true;
@@ -219,32 +219,12 @@ namespace Discord.Commands
if (output.Length == 0) if (output.Length == 0)
output.Append("There are no commands you have permission to run."); output.Append("There are no commands you have permission to run.");
else else
{
output.Append("\n\n");

//TODO: Should prefix be stated in the help message or not?
/*StringBuilder builder = new StringBuilder();
if (Config.PrefixChar != null)
{
builder.Append('`');
builder.Append(Config.PrefixChar.Value);
builder.Append('`');
}
if (Config.AllowMentionPrefix)
{
if (builder.Length > 0)
builder.Append(" or ");
builder.Append(Client.CurrentUser.Mention);
}
if (builder.Length > 0)
output.AppendLine($"Start your message with {builder.ToString()} to run a command.");*/
output.AppendLine($"Run `help <command>` for more information.");
}
output.AppendLine("\n\nRun `help <command>` for more information.");


return (replyChannel ?? channel).SendMessage(output.ToString()); return (replyChannel ?? channel).SendMessage(output.ToString());
} }


private Task ShowCommandHelp(CommandMap map, User user, Channel channel, Channel replyChannel = null)
private Task ShowCommandHelp(CommandMap map, User user, ITextChannel channel, ITextChannel replyChannel = null)
{ {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();


@@ -255,9 +235,7 @@ namespace Discord.Commands
{ {
foreach (var cmd in cmds) foreach (var cmd in cmds)
{ {
if (!cmd.CanRun(user, channel, out error)) { }
//output.AppendLine(error ?? DefaultPermissionError);
else
if (cmd.CanRun(user, channel, out error))
{ {
if (isFirstCmd) if (isFirstCmd)
isFirstCmd = false; isFirstCmd = false;
@@ -299,7 +277,7 @@ namespace Discord.Commands


return (replyChannel ?? channel).SendMessage(output.ToString()); return (replyChannel ?? channel).SendMessage(output.ToString());
} }
public Task ShowCommandHelp(Command command, User user, Channel channel, Channel replyChannel = null)
public Task ShowCommandHelp(Command command, User user, ITextChannel channel, ITextChannel replyChannel = null)
{ {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
string error; string error;
@@ -309,7 +287,7 @@ namespace Discord.Commands
ShowCommandHelpInternal(command, user, channel, output); ShowCommandHelpInternal(command, user, channel, output);
return (replyChannel ?? channel).SendMessage(output.ToString()); return (replyChannel ?? channel).SendMessage(output.ToString());
} }
private void ShowCommandHelpInternal(Command command, User user, Channel channel, StringBuilder output)
private void ShowCommandHelpInternal(Command command, User user, ITextChannel channel, StringBuilder output)
{ {
output.Append('`'); output.Append('`');
output.Append(command.Text); output.Append(command.Text);


src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs → src/Discord.Net.Commands/GenericPermissionChecker.cs View File

@@ -4,16 +4,16 @@ namespace Discord.Commands.Permissions
{ {
internal class GenericPermissionChecker : IPermissionChecker internal class GenericPermissionChecker : IPermissionChecker
{ {
private readonly Func<Command, User, Channel, bool> _checkFunc;
private readonly Func<Command, User, ITextChannel, bool> _checkFunc;
private readonly string _error; private readonly string _error;


public GenericPermissionChecker(Func<Command, User, Channel, bool> checkFunc, string error = null)
public GenericPermissionChecker(Func<Command, User, ITextChannel, bool> checkFunc, string error = null)
{ {
_checkFunc = checkFunc; _checkFunc = checkFunc;
_error = error; _error = error;
} }


public bool CanRun(Command command, User user, Channel channel, out string error)
public bool CanRun(Command command, User user, ITextChannel channel, out string error)
{ {
error = _error; error = _error;
return _checkFunc(command, user, channel); return _checkFunc(command, user, channel);

src/Discord.Net.Commands/Permissions/IPermissionChecker.cs → src/Discord.Net.Commands/IPermissionChecker.cs View File

@@ -2,6 +2,6 @@
{ {
public interface IPermissionChecker public interface IPermissionChecker
{ {
bool CanRun(Command command, User user, Channel channel, out string error);
bool CanRun(Command command, User user, ITextChannel channel, out string error);
} }
} }

+ 0
- 24
src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs View File

@@ -1,24 +0,0 @@
namespace Discord.Commands.Permissions.Levels
{
public class PermissionLevelChecker : IPermissionChecker
{
private readonly PermissionLevelService _service;
private readonly int _minPermissions;

public PermissionLevelService Service => _service;
public int MinPermissions => _minPermissions;

internal PermissionLevelChecker(DiscordClient client, int minPermissions)
{
_service = client.GetService<PermissionLevelService>(true);
_minPermissions = minPermissions;
}

public bool CanRun(Command command, User user, Channel channel, out string error)
{
error = null; //Use default error text.
int permissions = _service.GetPermissionLevel(user, channel);
return permissions >= _minPermissions;
}
}
}

+ 0
- 29
src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs View File

@@ -1,29 +0,0 @@
using System;

namespace Discord.Commands.Permissions.Levels
{
public static class PermissionLevelExtensions
{
public static DiscordClient UsingPermissionLevels(this DiscordClient client, Func<User, Channel, int> permissionResolver)
{
client.AddService(new PermissionLevelService(permissionResolver));
return client;
}

public static CommandBuilder MinPermissions(this CommandBuilder builder, int minPermissions)
{
builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions));
return builder;
}
public static CommandGroupBuilder MinPermissions(this CommandGroupBuilder builder, int minPermissions)
{
builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions));
return builder;
}
public static CommandService MinPermissions(this CommandService service, int minPermissions)
{
service.Root.AddCheck(new PermissionLevelChecker(service.Client, minPermissions));
return service;
}
}
}

+ 0
- 23
src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs View File

@@ -1,23 +0,0 @@
using System;

namespace Discord.Commands.Permissions.Levels
{
public class PermissionLevelService : IService
{
private readonly Func<User, Channel, int> _getPermissionsFunc;

private DiscordClient _client;
public DiscordClient Client => _client;

public PermissionLevelService(Func<User, Channel, int> getPermissionsFunc)
{
_getPermissionsFunc = getPermissionsFunc;
}

public void Install(DiscordClient client)
{
_client = client;
}
public int GetPermissionLevel(User user, Channel channel) => _getPermissionsFunc(user, channel);
}
}

+ 0
- 18
src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs View File

@@ -1,18 +0,0 @@
namespace Discord.Commands.Permissions.Userlist
{
public class BlacklistChecker : IPermissionChecker
{
private readonly BlacklistService _service;

internal BlacklistChecker(DiscordClient client)
{
_service = client.GetService<BlacklistService>(true);
}

public bool CanRun(Command command, User user, Channel channel, out string error)
{
error = null; //Use default error text.
return _service.CanRun(user);
}
}
}

+ 0
- 48
src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs View File

@@ -1,48 +0,0 @@
using System.Collections.Generic;

namespace Discord.Commands.Permissions.Userlist
{
public static class BlacklistExtensions
{
public static DiscordClient UsingGlobalBlacklist(this DiscordClient client, params ulong[] initialUserIds)
{
client.AddService(new BlacklistService(initialUserIds));
return client;
}

public static CommandBuilder UseGlobalBlacklist(this CommandBuilder builder)
{
builder.AddCheck(new BlacklistChecker(builder.Service.Client));
return builder;
}
public static CommandGroupBuilder UseGlobalBlacklist(this CommandGroupBuilder builder)
{
builder.AddCheck(new BlacklistChecker(builder.Service.Client));
return builder;
}
public static CommandService UseGlobalBlacklist(this CommandService service)
{
service.Root.AddCheck(new BlacklistChecker(service.Client));
return service;
}

public static IEnumerable<ulong> GetBlacklistedUserIds(this DiscordClient client)
=> client.GetService<BlacklistService>().UserIds;
public static void BlacklistUser(this DiscordClient client, User user)
{
client.GetService<BlacklistService>().Add(user.Id);
}
public static void BlacklistUser(this DiscordClient client, ulong userId)
{
client.GetService<BlacklistService>().Add(userId);
}
public static void UnBlacklistUser(this DiscordClient client, User user)
{
client.GetService<BlacklistService>().Remove(user.Id);
}
public static void UnBlacklistUser(this DiscordClient client, ulong userId)
{
client.GetService<BlacklistService>().Remove(userId);
}
}
}

+ 0
- 13
src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs View File

@@ -1,13 +0,0 @@
namespace Discord.Commands.Permissions.Userlist
{
public class BlacklistService : UserlistService
{
public BlacklistService(params ulong[] initialList)
: base(initialList)
{
}

public bool CanRun(User user)
=> !_userList.ContainsKey(user.Id);
}
}

+ 0
- 52
src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs View File

@@ -1,52 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace Discord.Commands.Permissions.Userlist
{
public class UserlistService : IService
{
protected readonly ConcurrentDictionary<ulong, bool> _userList;
private DiscordClient _client;

public DiscordClient Client => _client;
public IEnumerable<ulong> UserIds => _userList.Select(x => x.Key);

public UserlistService(params ulong[] initialUserIds)
{
_userList = new ConcurrentDictionary<ulong, bool>();
for (int i = 0; i < initialUserIds.Length; i++)
_userList.TryAdd(initialUserIds[i], true);
}

public void Add(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));

_userList[user.Id] = true;
}
public void Add(ulong userId)
{
_userList[userId] = true;
}

public bool Remove(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));

bool ignored;
return _userList.TryRemove(user.Id, out ignored);
}
public bool Remove(ulong userId)
{
bool ignored;
return _userList.TryRemove(userId, out ignored);
}

void IService.Install(DiscordClient client)
{
_client = client;
}
}
}

+ 0
- 18
src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs View File

@@ -1,18 +0,0 @@
namespace Discord.Commands.Permissions.Userlist
{
public class WhitelistChecker : IPermissionChecker
{
private readonly WhitelistService _service;

internal WhitelistChecker(DiscordClient client)
{
_service = client.GetService<WhitelistService>(true);
}

public bool CanRun(Command command, User user, Channel channel, out string error)
{
error = null; //Use default error text.
return _service.CanRun(user);
}
}
}

+ 0
- 48
src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs View File

@@ -1,48 +0,0 @@
using System.Collections.Generic;

namespace Discord.Commands.Permissions.Userlist
{
public static class WhitelistExtensions
{
public static DiscordClient UsingGlobalWhitelist(this DiscordClient client, params ulong[] initialUserIds)
{
client.AddService(new WhitelistService(initialUserIds));
return client;
}

public static CommandBuilder UseGlobalWhitelist(this CommandBuilder builder)
{
builder.AddCheck(new WhitelistChecker(builder.Service.Client));
return builder;
}
public static CommandGroupBuilder UseGlobalWhitelist(this CommandGroupBuilder builder)
{
builder.AddCheck(new WhitelistChecker(builder.Service.Client));
return builder;
}
public static CommandService UseGlobalWhitelist(this CommandService service)
{
service.Root.AddCheck(new BlacklistChecker(service.Client));
return service;
}

public static IEnumerable<ulong> GetWhitelistedUserIds(this DiscordClient client)
=> client.GetService<WhitelistService>().UserIds;
public static void WhitelistUser(this DiscordClient client, User user)
{
client.GetService<WhitelistService>().Add(user.Id);
}
public static void WhitelistUser(this DiscordClient client, ulong userId)
{
client.GetService<WhitelistService>().Add(userId);
}
public static void UnWhitelistUser(this DiscordClient client, User user)
{
client.GetService<WhitelistService>().Remove(user.Id);
}
public static void RemoveFromWhitelist(this DiscordClient client, ulong userId)
{
client.GetService<WhitelistService>().Remove(userId);
}
}
}

+ 0
- 13
src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs View File

@@ -1,13 +0,0 @@
namespace Discord.Commands.Permissions.Userlist
{
public class WhitelistService : UserlistService
{
public WhitelistService(params ulong[] initialList)
: base(initialList)
{
}

public bool CanRun(User user)
=> _userList.ContainsKey(user.Id);
}
}

+ 0
- 21
src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs View File

@@ -1,21 +0,0 @@
namespace Discord.Commands.Permissions.Visibility
{
public class PrivateChecker : IPermissionChecker
{
internal PrivateChecker() { }

public bool CanRun(Command command, User user, Channel channel, out string error)
{
if (user.Server != null)
{
error = "This command may only be run in a private chat.";
return false;
}
else
{
error = null;
return true;
}
}
}
}

+ 0
- 21
src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs View File

@@ -1,21 +0,0 @@
namespace Discord.Commands.Permissions.Visibility
{
public static class PrivateExtensions
{
public static CommandBuilder PrivateOnly(this CommandBuilder builder)
{
builder.AddCheck(new PrivateChecker());
return builder;
}
public static CommandGroupBuilder PrivateOnly(this CommandGroupBuilder builder)
{
builder.AddCheck(new PrivateChecker());
return builder;
}
public static CommandService PrivateOnly(this CommandService service)
{
service.Root.AddCheck(new PrivateChecker());
return service;
}
}
}

+ 0
- 21
src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs View File

@@ -1,21 +0,0 @@
namespace Discord.Commands.Permissions.Visibility
{
public class PublicChecker : IPermissionChecker
{
internal PublicChecker() { }

public bool CanRun(Command command, User user, Channel channel, out string error)
{
if (user.Server == null)
{
error = "This command can't be run in a private chat.";
return false;
}
else
{
error = null;
return true;
}
}
}
}

+ 0
- 21
src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs View File

@@ -1,21 +0,0 @@
namespace Discord.Commands.Permissions.Visibility
{
public static class PublicExtensions
{
public static CommandBuilder PublicOnly(this CommandBuilder builder)
{
builder.AddCheck(new PublicChecker());
return builder;
}
public static CommandGroupBuilder PublicOnly(this CommandGroupBuilder builder)
{
builder.AddCheck(new PublicChecker());
return builder;
}
public static CommandService PublicOnly(this CommandService service)
{
service.Root.AddCheck(new PublicChecker());
return service;
}
}
}

+ 2
- 2
src/Discord.Net.Commands/project.json View File

@@ -1,5 +1,5 @@
{ {
"version": "0.9.0-rc3-1",
"version": "1.0.0-alpha1",
"description": "A Discord.Net extension adding basic command support.", "description": "A Discord.Net extension adding basic command support.",
"authors": [ "RogueException" ], "authors": [ "RogueException" ],
"tags": [ "discord", "discordapp" ], "tags": [ "discord", "discordapp" ],
@@ -16,7 +16,7 @@
}, },


"dependencies": { "dependencies": {
"Discord.Net": "0.9.0-rc3-3"
"Discord.Net": "1.0.0-alpha1"
}, },
"frameworks": { "frameworks": {
"net45": { }, "net45": { },


+ 4
- 2
src/Discord.Net.Modules/ModuleChecker.cs View File

@@ -14,9 +14,11 @@ namespace Discord.Modules
_filterType = manager.FilterType; _filterType = manager.FilterType;
} }


public bool CanRun(Command command, User user, Channel channel, out string error)
public bool CanRun(Command command, User user, ITextChannel channel, out string error)
{ {
if (_filterType == ModuleFilter.None || _filterType == ModuleFilter.AlwaysAllowPrivate || _manager.HasChannel(channel))
if (_filterType == ModuleFilter.None ||
_filterType == ModuleFilter.AlwaysAllowPrivate ||
(channel.IsPublic && _manager.HasChannel(channel)))
{ {
error = null; error = null;
return true; return true;


+ 52
- 90
src/Discord.Net.Modules/ModuleManager.cs View File

@@ -20,11 +20,6 @@ namespace Discord.Modules


public class ModuleManager public class ModuleManager
{ {
public event EventHandler<ServerEventArgs> ServerEnabled = delegate { };
public event EventHandler<ServerEventArgs> ServerDisabled = delegate { };
public event EventHandler<ChannelEventArgs> ChannelEnabled = delegate { };
public event EventHandler<ChannelEventArgs> ChannelDisabled = delegate { };

public event EventHandler<ServerEventArgs> JoinedServer = delegate { }; public event EventHandler<ServerEventArgs> JoinedServer = delegate { };
public event EventHandler<ServerEventArgs> LeftServer = delegate { }; public event EventHandler<ServerEventArgs> LeftServer = delegate { };
public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { }; public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { };
@@ -43,10 +38,8 @@ namespace Discord.Modules
public event EventHandler<UserEventArgs> UserJoined = delegate { }; public event EventHandler<UserEventArgs> UserJoined = delegate { };
public event EventHandler<UserEventArgs> UserLeft = delegate { }; public event EventHandler<UserEventArgs> UserLeft = delegate { };
public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { }; public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { };
//public event EventHandler<UserEventArgs> UserPresenceUpdated = delegate { };
//public event EventHandler<UserEventArgs> UserVoiceStateUpdated = delegate { };
public event EventHandler<UserEventArgs> UserUnbanned = delegate { }; public event EventHandler<UserEventArgs> UserUnbanned = delegate { };
public event EventHandler<ChannelUserEventArgs> UserIsTyping = delegate { };
public event EventHandler<TypingEventArgs> UserIsTyping = delegate { };


public event EventHandler<MessageEventArgs> MessageReceived = delegate { }; public event EventHandler<MessageEventArgs> MessageReceived = delegate { };
public event EventHandler<MessageEventArgs> MessageSent = delegate { }; public event EventHandler<MessageEventArgs> MessageSent = delegate { };
@@ -56,7 +49,7 @@ namespace Discord.Modules
private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate;
private readonly ConcurrentDictionary<ulong, Server> _enabledServers; private readonly ConcurrentDictionary<ulong, Server> _enabledServers;
private readonly ConcurrentDictionary<ulong, Channel> _enabledChannels;
private readonly ConcurrentDictionary<ulong, IChannel> _enabledChannels;
private readonly ConcurrentDictionary<ulong, int> _indirectServers; private readonly ConcurrentDictionary<ulong, int> _indirectServers;
private readonly AsyncLock _lock; private readonly AsyncLock _lock;


@@ -67,7 +60,7 @@ namespace Discord.Modules
public ModuleFilter FilterType { get; } public ModuleFilter FilterType { get; }


public IEnumerable<Server> EnabledServers => _enabledServers.Select(x => x.Value); public IEnumerable<Server> EnabledServers => _enabledServers.Select(x => x.Value);
public IEnumerable<Channel> EnabledChannels => _enabledChannels.Select(x => x.Value);
public IEnumerable<IChannel> EnabledChannels => _enabledChannels.Select(x => x.Value);


internal ModuleManager(DiscordClient client, IModule instance, string name, ModuleFilter filterType) internal ModuleManager(DiscordClient client, IModule instance, string name, ModuleFilter filterType)
{ {
@@ -85,12 +78,17 @@ namespace Discord.Modules
_allowPrivate = filterType.HasFlag(ModuleFilter.AlwaysAllowPrivate); _allowPrivate = filterType.HasFlag(ModuleFilter.AlwaysAllowPrivate);


_enabledServers = new ConcurrentDictionary<ulong, Server>(); _enabledServers = new ConcurrentDictionary<ulong, Server>();
_enabledChannels = new ConcurrentDictionary<ulong, Channel>();
_enabledChannels = new ConcurrentDictionary<ulong, IChannel>();
_indirectServers = new ConcurrentDictionary<ulong, int>(); _indirectServers = new ConcurrentDictionary<ulong, int>();


if (_allowAll || _useServerWhitelist) //Server-only events if (_allowAll || _useServerWhitelist) //Server-only events
{ {
client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); };
client.ChannelCreated += (s, e) =>
{
var server = (e.Channel as PublicChannel)?.Server;
if (HasServer(server))
ChannelCreated(s, e);
};
//TODO: This *is* a channel update if the before/after voice channel is whitelisted //TODO: This *is* a channel update if the before/after voice channel is whitelisted
//client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); }; //client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); };
} }
@@ -116,10 +114,9 @@ namespace Discord.Modules


client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); }; client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); };
client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); }; client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); };
//TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist
client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); }; client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); };
client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); }; client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); };
//TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist
//client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); };
client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); }; client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); };
client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); };
} }
@@ -155,16 +152,7 @@ namespace Discord.Modules
EnableServerInternal(server); EnableServerInternal(server);
} }
} }
private bool EnableServerInternal(Server server)
{
if (_enabledServers.TryAdd(server.Id, server))
{
if (ServerEnabled != null)
ServerEnabled(this, new ServerEventArgs(server));
return true;
}
return false;
}
private bool EnableServerInternal(Server server) => _enabledServers.TryAdd(server.Id, server);


public bool DisableServer(Server server) public bool DisableServer(Server server)
{ {
@@ -172,34 +160,18 @@ namespace Discord.Modules
if (!_useServerWhitelist) return false; if (!_useServerWhitelist) return false;


using (_lock.Lock()) using (_lock.Lock())
{
if (_enabledServers.TryRemove(server.Id, out server))
{
if (ServerDisabled != null)
ServerDisabled(this, new ServerEventArgs(server));
return true;
}
return false;
}
return _enabledServers.TryRemove(server.Id, out server);
} }
public void DisableAllServers() public void DisableAllServers()
{ {
if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist.");
if (!_useServerWhitelist) return; if (!_useServerWhitelist) return;


using (_lock.Lock())
{
if (ServerDisabled != null)
{
foreach (var server in _enabledServers)
ServerDisabled(this, new ServerEventArgs(server.Value));
}
using (_lock.Lock())
_enabledServers.Clear(); _enabledServers.Clear();
}
} }


public bool EnableChannel(Channel channel)
public bool EnableChannel(ITextChannel channel)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));
if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist.");
@@ -207,7 +179,7 @@ namespace Discord.Modules
using (_lock.Lock()) using (_lock.Lock())
return EnableChannelInternal(channel); return EnableChannelInternal(channel);
} }
public void EnableChannels(IEnumerable<Channel> channels)
public void EnableChannels(IEnumerable<ITextChannel> channels)
{ {
if (channels == null) throw new ArgumentNullException(nameof(channels)); if (channels == null) throw new ArgumentNullException(nameof(channels));
if (channels.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(channels)); if (channels.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(channels));
@@ -219,65 +191,55 @@ namespace Discord.Modules
EnableChannelInternal(channel); EnableChannelInternal(channel);
} }
} }
private bool EnableChannelInternal(Channel channel)
private bool EnableChannelInternal(ITextChannel channel)
{ {
if (_enabledChannels.TryAdd(channel.Id, channel)) if (_enabledChannels.TryAdd(channel.Id, channel))
{
var server = channel.Server;
if (server != null)
{
int value = 0;
_indirectServers.TryGetValue(server.Id, out value);
value++;
_indirectServers[server.Id] = value;
}
if (ChannelEnabled != null)
ChannelEnabled(this, new ChannelEventArgs(channel));
{
if (channel.Type != ChannelType.Private)
{
var server = (channel as PublicChannel)?.Server;
int value = 0;
_indirectServers.TryGetValue(server.Id, out value);
value++;
_indirectServers[server.Id] = value;
}
return true; return true;
} }
return false; return false;
} }


public bool DisableChannel(Channel channel)
public bool DisableChannel(IChannel channel)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));
if (!_useChannelWhitelist) return false; if (!_useChannelWhitelist) return false;


using (_lock.Lock())
IChannel ignored;
if (_enabledChannels.TryRemove(channel.Id, out ignored))
{ {
Channel ignored;
if (_enabledChannels.TryRemove(channel.Id, out ignored))
{
var server = channel.Server;
if (server != null)
{
int value = 0;
_indirectServers.TryGetValue(server.Id, out value);
value--;
if (value <= 0)
_indirectServers.TryRemove(server.Id, out value);
else
_indirectServers[server.Id] = value;
}
if (ChannelDisabled != null)
ChannelDisabled(this, new ChannelEventArgs(channel));
return true;
}
return false;
}
}
using (_lock.Lock())
{
if (channel.Type != ChannelType.Private)
{
var server = (channel as PublicChannel)?.Server;
int value = 0;
_indirectServers.TryGetValue(server.Id, out value);
value--;
if (value <= 0)
_indirectServers.TryRemove(server.Id, out value);
else
_indirectServers[server.Id] = value;
}
return true;
}
}
return false;
}
public void DisableAllChannels() public void DisableAllChannels()
{ {
if (!_useChannelWhitelist) return; if (!_useChannelWhitelist) return;


using (_lock.Lock()) using (_lock.Lock())
{ {
if (ChannelDisabled != null)
{
foreach (var channel in _enabledChannels)
ChannelDisabled(this, new ChannelEventArgs(channel.Value));
}

_enabledChannels.Clear(); _enabledChannels.Clear();
_indirectServers.Clear(); _indirectServers.Clear();
} }
@@ -293,20 +255,20 @@ namespace Discord.Modules


internal bool HasServer(Server server) => internal bool HasServer(Server server) =>
_allowAll || _allowAll ||
_useServerWhitelist && _enabledServers.ContainsKey(server.Id);
(_useServerWhitelist && _enabledServers.ContainsKey(server.Id));
internal bool HasIndirectServer(Server server) => internal bool HasIndirectServer(Server server) =>
_allowAll || _allowAll ||
(_useServerWhitelist && _enabledServers.ContainsKey(server.Id)) || (_useServerWhitelist && _enabledServers.ContainsKey(server.Id)) ||
(_useChannelWhitelist && _indirectServers.ContainsKey(server.Id)); (_useChannelWhitelist && _indirectServers.ContainsKey(server.Id));
internal bool HasChannel(Channel channel)
internal bool HasChannel(IChannel channel)
{ {
if (_allowAll) return true; if (_allowAll) return true;
if (channel.IsPrivate) return _allowPrivate;
if (channel.Type == ChannelType.Private) return _allowPrivate;


if (_useChannelWhitelist && _enabledChannels.ContainsKey(channel.Id)) return true; if (_useChannelWhitelist && _enabledChannels.ContainsKey(channel.Id)) return true;
if (_useServerWhitelist)
if (_useServerWhitelist && channel.IsPublic)
{ {
var server = channel.Server;
var server = (channel as PublicChannel).Server;
if (server == null) return false; if (server == null) return false;
if (_enabledServers.ContainsKey(server.Id)) return true; if (_enabledServers.ContainsKey(server.Id)) return true;
} }


+ 3
- 3
src/Discord.Net.Modules/project.json View File

@@ -1,5 +1,5 @@
{ {
"version": "0.9.0-rc3",
"version": "1.0.0-alpha1",
"description": "A Discord.Net extension adding basic plugin support.", "description": "A Discord.Net extension adding basic plugin support.",
"authors": [ "RogueException" ], "authors": [ "RogueException" ],
"tags": [ "discord", "discordapp" ], "tags": [ "discord", "discordapp" ],
@@ -16,8 +16,8 @@
}, },


"dependencies": { "dependencies": {
"Discord.Net": "0.9.0-rc3-3",
"Discord.Net.Commands": "0.9.0-rc3-1"
"Discord.Net": "1.0.0-alpha1",
"Discord.Net.Commands": "1.0.0-alpha1"
}, },
"frameworks": { "frameworks": {
"net45": { }, "net45": { },


+ 12
- 0
src/Discord.Net.Net45/Discord.Net.csproj View File

@@ -526,9 +526,15 @@
<Compile Include="..\Discord.Net\Models\Permissions.cs"> <Compile Include="..\Discord.Net\Models\Permissions.cs">
<Link>Models\Permissions.cs</Link> <Link>Models\Permissions.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Models\PrivateChannel.cs">
<Link>Models\PrivateChannel.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Models\Profile.cs"> <Compile Include="..\Discord.Net\Models\Profile.cs">
<Link>Models\Profile.cs</Link> <Link>Models\Profile.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Models\PublicChannel.cs">
<Link>Models\PublicChannel.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Models\Region.cs"> <Compile Include="..\Discord.Net\Models\Region.cs">
<Link>Models\Region.cs</Link> <Link>Models\Region.cs</Link>
</Compile> </Compile>
@@ -538,9 +544,15 @@
<Compile Include="..\Discord.Net\Models\Server.cs"> <Compile Include="..\Discord.Net\Models\Server.cs">
<Link>Models\Server.cs</Link> <Link>Models\Server.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Models\TextChannel.cs">
<Link>Models\TextChannel.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Models\User.cs"> <Compile Include="..\Discord.Net\Models\User.cs">
<Link>Models\User.cs</Link> <Link>Models\User.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Models\VoiceChannel.cs">
<Link>Models\VoiceChannel.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\HttpException.cs"> <Compile Include="..\Discord.Net\Net\HttpException.cs">
<Link>Net\HttpException.cs</Link> <Link>Net\HttpException.cs</Link>
</Compile> </Compile>


+ 2
- 0
src/Discord.Net/API/Client/Common/Channel.cs View File

@@ -29,5 +29,7 @@ namespace Discord.API.Client
public PermissionOverwrite[] PermissionOverwrites { get; set; } public PermissionOverwrite[] PermissionOverwrites { get; set; }
[JsonProperty("recipient")] [JsonProperty("recipient")]
public UserReference Recipient { get; set; } public UserReference Recipient { get; set; }
[JsonProperty("bitrate")]
public int Bitrate { get; set; }
} }
} }

+ 2
- 2
src/Discord.Net/API/Client/Rest/AddChannelPermission.cs View File

@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public class AddChannelPermissionsRequest : IRestRequest
public class AddOrUpdateChannelPermissionsRequest : IRestRequest
{ {
string IRestRequest.Method => "PUT"; string IRestRequest.Method => "PUT";
string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}";
@@ -21,7 +21,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("deny")] [JsonProperty("deny")]
public uint Deny { get; set; } public uint Deny { get; set; }


public AddChannelPermissionsRequest(ulong channelId)
public AddOrUpdateChannelPermissionsRequest(ulong channelId)
{ {
ChannelId = channelId; ChannelId = channelId;
} }


+ 1
- 1
src/Discord.Net/API/Client/Rest/CreateChannel.cs View File

@@ -14,7 +14,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; }
public ChannelType Type { get; set; }


public CreateChannelRequest(ulong guildId) public CreateChannelRequest(ulong guildId)
{ {


+ 2
- 0
src/Discord.Net/API/Client/Rest/UpdateChannel.cs View File

@@ -17,6 +17,8 @@ namespace Discord.API.Client.Rest
public string Topic { get; set; } public string Topic { get; set; }
[JsonProperty("position")] [JsonProperty("position")]
public int Position { get; set; } public int Position { get; set; }
[JsonProperty("bitrate")]
public int Bitrate { get; set; }


public UpdateChannelRequest(ulong channelId) public UpdateChannelRequest(ulong channelId)
{ {


+ 6
- 6
src/Discord.Net/DiscordClient.Events.cs View File

@@ -25,7 +25,7 @@ namespace Discord
public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { }; public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { };
public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { }; public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { };
public event EventHandler<UserEventArgs> UserBanned = delegate { }; public event EventHandler<UserEventArgs> UserBanned = delegate { };
public event EventHandler<ChannelUserEventArgs> UserIsTyping = delegate { };
public event EventHandler<TypingEventArgs> UserIsTyping = delegate { };
public event EventHandler<UserEventArgs> UserJoined = delegate { }; public event EventHandler<UserEventArgs> UserJoined = delegate { };
public event EventHandler<UserEventArgs> UserLeft = delegate { }; public event EventHandler<UserEventArgs> UserLeft = delegate { };
public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { }; public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { };
@@ -36,11 +36,11 @@ namespace Discord
/*private void OnLoggedOut(bool wasUnexpected, Exception ex) /*private void OnLoggedOut(bool wasUnexpected, Exception ex)
=> OnEvent(LoggedOut, new DisconnectedEventArgs(wasUnexpected, ex));*/ => OnEvent(LoggedOut, new DisconnectedEventArgs(wasUnexpected, ex));*/


private void OnChannelCreated(Channel channel)
private void OnChannelCreated(IChannel channel)
=> OnEvent(ChannelCreated, new ChannelEventArgs(channel)); => OnEvent(ChannelCreated, new ChannelEventArgs(channel));
private void OnChannelDestroyed(Channel channel)
private void OnChannelDestroyed(IChannel channel)
=> OnEvent(ChannelDestroyed, new ChannelEventArgs(channel)); => OnEvent(ChannelDestroyed, new ChannelEventArgs(channel));
private void OnChannelUpdated(Channel before, Channel after)
private void OnChannelUpdated(IChannel before, IChannel after)
=> OnEvent(ChannelUpdated, new ChannelUpdatedEventArgs(before, after)); => OnEvent(ChannelUpdated, new ChannelUpdatedEventArgs(before, after));
private void OnMessageAcknowledged(Message msg) private void OnMessageAcknowledged(Message msg)
@@ -77,8 +77,8 @@ namespace Discord


private void OnUserBanned(User user) private void OnUserBanned(User user)
=> OnEvent(UserBanned, new UserEventArgs(user)); => OnEvent(UserBanned, new UserEventArgs(user));
private void OnUserIsTypingUpdated(Channel channel, User user)
=> OnEvent(UserIsTyping, new ChannelUserEventArgs(channel, user));
private void OnUserIsTypingUpdated(ITextChannel channel, User user)
=> OnEvent(UserIsTyping, new TypingEventArgs(channel, user));
private void OnUserJoined(User user) private void OnUserJoined(User user)
=> OnEvent(UserJoined, new UserEventArgs(user)); => OnEvent(UserJoined, new UserEventArgs(user));
private void OnUserLeft(User user) private void OnUserLeft(User user)


+ 71
- 112
src/Discord.Net/DiscordClient.cs View File

@@ -1,4 +1,5 @@
using Discord.API.Client.GatewaySocket;
using APIChannel = Discord.API.Client.Channel;
using Discord.API.Client.GatewaySocket;
using Discord.API.Client.Rest; using Discord.API.Client.Rest;
using Discord.Logging; using Discord.Logging;
using Discord.Net; using Discord.Net;
@@ -29,8 +30,8 @@ namespace Discord
private readonly TaskManager _taskManager; private readonly TaskManager _taskManager;
private readonly ServiceCollection _services; private readonly ServiceCollection _services;
private ConcurrentDictionary<ulong, Server> _servers; private ConcurrentDictionary<ulong, Server> _servers;
private ConcurrentDictionary<ulong, Channel> _channels;
private ConcurrentDictionary<ulong, Channel> _privateChannels; //Key = RecipientId
private ConcurrentDictionary<ulong, IChannel> _channels;
private ConcurrentDictionary<ulong, PrivateChannel> _privateChannels; //Key = RecipientId
private Dictionary<string, Region> _regions; private Dictionary<string, Region> _regions;
private Stopwatch _connectionStopwatch; private Stopwatch _connectionStopwatch;


@@ -71,7 +72,7 @@ namespace Discord
/// <summary> Gets a collection of all servers this client is a member of. </summary> /// <summary> Gets a collection of all servers this client is a member of. </summary>
public IEnumerable<Server> Servers => _servers.Select(x => x.Value); public IEnumerable<Server> Servers => _servers.Select(x => x.Value);
/// <summary> Gets a collection of all private channels this client is a member of. </summary> /// <summary> Gets a collection of all private channels this client is a member of. </summary>
public IEnumerable<Channel> PrivateChannels => _privateChannels.Select(x => x.Value);
public IEnumerable<PrivateChannel> PrivateChannels => _privateChannels.Select(x => x.Value);
/// <summary> Gets a collection of all voice regions currently offered by Discord. </summary> /// <summary> Gets a collection of all voice regions currently offered by Discord. </summary>
public IEnumerable<Region> Regions => _regions.Select(x => x.Value); public IEnumerable<Region> Regions => _regions.Select(x => x.Value);


@@ -123,8 +124,8 @@ namespace Discord
//Cache //Cache
//ConcurrentLevel = 2 (only REST and WebSocket can add/remove) //ConcurrentLevel = 2 (only REST and WebSocket can add/remove)
_servers = new ConcurrentDictionary<ulong, Server>(2, 0); _servers = new ConcurrentDictionary<ulong, Server>(2, 0);
_channels = new ConcurrentDictionary<ulong, Channel>(2, 0);
_privateChannels = new ConcurrentDictionary<ulong, Channel>(2, 0);
_channels = new ConcurrentDictionary<ulong, IChannel>(2, 0);
_privateChannels = new ConcurrentDictionary<ulong, PrivateChannel>(2, 0);


//Serialization //Serialization
Serializer = new JsonSerializer(); Serializer = new JsonSerializer();
@@ -335,45 +336,47 @@ namespace Discord
} }


#region Channels #region Channels
internal void AddChannel(Channel channel)
internal void AddChannel(IChannel channel)
{ {
_channels.GetOrAdd(channel.Id, channel); _channels.GetOrAdd(channel.Id, channel);
} }
private Channel RemoveChannel(ulong id)
private IChannel RemoveChannel(ulong id)
{ {
Channel channel;
IChannel channel;
if (_channels.TryRemove(id, out channel)) if (_channels.TryRemove(id, out channel))
{ {
if (channel.IsPrivate) if (channel.IsPrivate)
_privateChannels.TryRemove(channel.Recipient.Id, out channel);
{
PrivateChannel removed;
_privateChannels.TryRemove((channel as PrivateChannel).Recipient.Id, out removed);
}
else else
channel.Server.RemoveChannel(id);
(channel as PublicChannel).Server.RemoveChannel(id);
} }
return channel; return channel;
} }
public Channel GetChannel(ulong id)
public IChannel GetChannel(ulong id)
{ {
Channel channel;
IChannel channel;
_channels.TryGetValue(id, out channel); _channels.TryGetValue(id, out channel);
return channel; return channel;
} }


private Channel AddPrivateChannel(ulong id, ulong recipientId)
private PrivateChannel AddPrivateChannel(APIChannel model)
{ {
Channel channel;
if (_channels.TryGetOrAdd(id, x => new Channel(this, x, new User(this, recipientId, null)), out channel))
_privateChannels[recipientId] = channel;
return channel;
IChannel channel;
if (_channels.TryGetOrAdd(model.Id, x => new PrivateChannel(x, new User(this, model.Recipient.Id, null), model), out channel))
_privateChannels[model.Recipient.Id] = channel as PrivateChannel;
return channel as PrivateChannel;
} }
internal Channel GetPrivateChannel(ulong recipientId)
internal PrivateChannel GetPrivateChannel(ulong recipientId)
{ {
Channel channel;
PrivateChannel channel;
_privateChannels.TryGetValue(recipientId, out channel); _privateChannels.TryGetValue(recipientId, out channel);
return channel; return channel;
} }
internal Task<Channel> CreatePMChannel(User user)
=> CreatePrivateChannel(user.Id);
public async Task<Channel> CreatePrivateChannel(ulong userId)
public Task<PrivateChannel> CreatePrivateChannel(User user) => CreatePrivateChannel(user.Id);
public async Task<PrivateChannel> CreatePrivateChannel(ulong userId)
{ {
var channel = GetPrivateChannel(userId); var channel = GetPrivateChannel(userId);
if (channel != null) return channel; if (channel != null) return channel;
@@ -381,9 +384,7 @@ namespace Discord
var request = new CreatePrivateChannelRequest() { RecipientId = userId }; var request = new CreatePrivateChannelRequest() { RecipientId = userId };
var response = await ClientAPI.Send(request).ConfigureAwait(false); var response = await ClientAPI.Send(request).ConfigureAwait(false);


channel = AddPrivateChannel(response.Id, userId);
channel.Update(response);
return channel;
return AddPrivateChannel(response);
} }
#endregion #endregion


@@ -429,8 +430,7 @@ namespace Discord
#endregion #endregion


#region Servers #region Servers
private Server AddServer(ulong id)
=> _servers.GetOrAdd(id, x => new Server(this, x));
private Server AddServer(ulong id) => _servers.GetOrAdd(id, x => new Server(this, x));
private Server RemoveServer(ulong id) private Server RemoveServer(ulong id)
{ {
Server server; Server server;
@@ -448,11 +448,6 @@ namespace Discord
_servers.TryGetValue(id, out server); _servers.TryGetValue(id, out server);
return server; return server;
} }
public IEnumerable<Server> FindServers(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));
return _servers.Select(x => x.Value).Find(name);
}


/// <summary> Creates a new server with the provided name and region. </summary> /// <summary> Creates a new server with the provided name and region. </summary>
public async Task<Server> CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null) public async Task<Server> CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null)
@@ -494,8 +489,8 @@ namespace Discord


//ConcurrencyLevel = 2 (only REST and WebSocket can add/remove) //ConcurrencyLevel = 2 (only REST and WebSocket can add/remove)
_servers = new ConcurrentDictionary<ulong, Server>(2, (int)(data.Guilds.Length * 1.05)); _servers = new ConcurrentDictionary<ulong, Server>(2, (int)(data.Guilds.Length * 1.05));
_channels = new ConcurrentDictionary<ulong, Channel>(2, (int)(channelCount * 1.05));
_privateChannels = new ConcurrentDictionary<ulong, Channel>(2, (int)(data.PrivateChannels.Length * 1.05));
_channels = new ConcurrentDictionary<ulong, IChannel>(2, (int)(channelCount * 1.05));
_privateChannels = new ConcurrentDictionary<ulong, PrivateChannel>(2, (int)(data.PrivateChannels.Length * 1.05));
List<ulong> largeServers = new List<ulong>(); List<ulong> largeServers = new List<ulong>();


SessionId = data.SessionId; SessionId = data.SessionId;
@@ -516,11 +511,7 @@ namespace Discord
} }
} }
for (int i = 0; i < data.PrivateChannels.Length; i++) for (int i = 0; i < data.PrivateChannels.Length; i++)
{
var model = data.PrivateChannels[i];
var channel = AddPrivateChannel(model.Id, model.Recipient.Id);
channel.Update(model);
}
AddPrivateChannel(data.PrivateChannels[i]);
if (largeServers.Count > 0) if (largeServers.Count > 0)
GatewaySocket.SendRequestMembers(largeServers, "", 0); GatewaySocket.SendRequestMembers(largeServers, "", 0);
else else
@@ -538,9 +529,9 @@ namespace Discord
server.Update(data); server.Update(data);
if (data.Unavailable != false) if (data.Unavailable != false)
Logger.Info($"GUILD_CREATE: {server.Path}");
Logger.Info($"GUILD_CREATE: {server}");
else else
Logger.Info($"GUILD_AVAILABLE: {server.Path}");
Logger.Info($"GUILD_AVAILABLE: {server}");


if (data.Unavailable != false) if (data.Unavailable != false)
OnJoinedServer(server); OnJoinedServer(server);
@@ -556,7 +547,7 @@ namespace Discord
{ {
var before = Config.EnablePreUpdateEvents ? server.Clone() : null; var before = Config.EnablePreUpdateEvents ? server.Clone() : null;
server.Update(data); server.Update(data);
Logger.Info($"GUILD_UPDATE: {server.Path}");
Logger.Info($"GUILD_UPDATE: {server}");
OnServerUpdated(before, server); OnServerUpdated(before, server);
} }
else else
@@ -570,9 +561,9 @@ namespace Discord
if (server != null) if (server != null)
{ {
if (data.Unavailable != true) if (data.Unavailable != true)
Logger.Info($"GUILD_DELETE: {server.Path}");
Logger.Info($"GUILD_DELETE: {server}");
else else
Logger.Info($"GUILD_UNAVAILABLE: {server.Path}");
Logger.Info($"GUILD_UNAVAILABLE: {server}");


OnServerUnavailable(server); OnServerUnavailable(server);
if (data.Unavailable != true) if (data.Unavailable != true)
@@ -588,23 +579,22 @@ namespace Discord
{ {
var data = e.Payload.ToObject<ChannelCreateEvent>(Serializer); var data = e.Payload.ToObject<ChannelCreateEvent>(Serializer);


Channel channel = null;
IChannel channel = null;
if (data.GuildId != null) if (data.GuildId != null)
{ {
var server = GetServer(data.GuildId.Value); var server = GetServer(data.GuildId.Value);
if (server != null) if (server != null)
channel = server.AddChannel(data.Id, true);
channel = server.AddChannel(data, true);
else else
{
Logger.Warning("CHANNEL_CREATE referenced an unknown guild."); Logger.Warning("CHANNEL_CREATE referenced an unknown guild.");
break;
}
} }
else else
channel = AddPrivateChannel(data.Id, data.Recipient.Id);
if (channel != null)
{
channel.Update(data);
Logger.Info($"CHANNEL_CREATE: {channel.Path}");
OnChannelCreated(channel);
}
channel = AddPrivateChannel(data);
Logger.Info($"CHANNEL_CREATE: {channel}");
OnChannelCreated(channel);
} }
break; break;
case "CHANNEL_UPDATE": case "CHANNEL_UPDATE":
@@ -613,9 +603,9 @@ namespace Discord
var channel = GetChannel(data.Id); var channel = GetChannel(data.Id);
if (channel != null) if (channel != null)
{ {
var before = Config.EnablePreUpdateEvents ? channel.Clone() : null;
channel.Update(data);
Logger.Info($"CHANNEL_UPDATE: {channel.Path}");
var before = Config.EnablePreUpdateEvents ? (channel as Channel).Clone() : null;
(channel as Channel).Update(data);
Logger.Info($"CHANNEL_UPDATE: {channel}");
OnChannelUpdated(before, channel); OnChannelUpdated(before, channel);
} }
else else
@@ -628,7 +618,7 @@ namespace Discord
var channel = RemoveChannel(data.Id); var channel = RemoveChannel(data.Id);
if (channel != null) if (channel != null)
{ {
Logger.Info($"CHANNEL_DELETE: {channel.Path}");
Logger.Info($"CHANNEL_DELETE: {channel}");
OnChannelDestroyed(channel); OnChannelDestroyed(channel);
} }
else else
@@ -646,7 +636,7 @@ namespace Discord
var user = server.AddUser(data.User.Id, true, true); var user = server.AddUser(data.User.Id, true, true);
user.Update(data); user.Update(data);
user.UpdateActivity(); user.UpdateActivity();
Logger.Info($"GUILD_MEMBER_ADD: {user.Path}");
Logger.Info($"GUILD_MEMBER_ADD: {user}");
OnUserJoined(user); OnUserJoined(user);
} }
else else
@@ -664,7 +654,7 @@ namespace Discord
{ {
var before = Config.EnablePreUpdateEvents ? user.Clone() : null; var before = Config.EnablePreUpdateEvents ? user.Clone() : null;
user.Update(data); user.Update(data);
Logger.Info($"GUILD_MEMBER_UPDATE: {user.Path}");
Logger.Info($"GUILD_MEMBER_UPDATE: {user}");
OnUserUpdated(before, user); OnUserUpdated(before, user);
} }
else else
@@ -683,7 +673,7 @@ namespace Discord
var user = server.RemoveUser(data.User.Id); var user = server.RemoveUser(data.User.Id);
if (user != null) if (user != null)
{ {
Logger.Info($"GUILD_MEMBER_REMOVE: {user.Path}");
Logger.Info($"GUILD_MEMBER_REMOVE: {user}");
OnUserLeft(user); OnUserLeft(user);
} }
else else
@@ -732,7 +722,7 @@ namespace Discord
{ {
var role = server.AddRole(data.Data.Id); var role = server.AddRole(data.Data.Id);
role.Update(data.Data, false); role.Update(data.Data, false);
Logger.Info($"GUILD_ROLE_CREATE: {role.Path}");
Logger.Info($"GUILD_ROLE_CREATE: {role}");
OnRoleCreated(role); OnRoleCreated(role);
} }
else else
@@ -750,7 +740,7 @@ namespace Discord
{ {
var before = Config.EnablePreUpdateEvents ? role.Clone() : null; var before = Config.EnablePreUpdateEvents ? role.Clone() : null;
role.Update(data.Data, true); role.Update(data.Data, true);
Logger.Info($"GUILD_ROLE_UPDATE: {role.Path}");
Logger.Info($"GUILD_ROLE_UPDATE: {role}");
OnRoleUpdated(before, role); OnRoleUpdated(before, role);
} }
else else
@@ -769,7 +759,7 @@ namespace Discord
var role = server.RemoveRole(data.RoleId); var role = server.RemoveRole(data.RoleId);
if (role != null) if (role != null)
{ {
Logger.Info($"GUILD_ROLE_DELETE: {role.Path}");
Logger.Info($"GUILD_ROLE_DELETE: {role}");
OnRoleDeleted(role); OnRoleDeleted(role);
} }
else else
@@ -790,7 +780,7 @@ namespace Discord
var user = server.GetUser(data.User.Id); var user = server.GetUser(data.User.Id);
if (user != null) if (user != null)
{ {
Logger.Info($"GUILD_BAN_ADD: {user.Path}");
Logger.Info($"GUILD_BAN_ADD: {user}");
OnUserBanned(user); OnUserBanned(user);
} }
else else
@@ -808,7 +798,7 @@ namespace Discord
{ {
var user = new User(this, data.User.Id, server); var user = new User(this, data.User.Id, server);
user.Update(data.User); user.Update(data.User);
Logger.Info($"GUILD_BAN_REMOVE: {user.Path}");
Logger.Info($"GUILD_BAN_REMOVE: {user}");
OnUserUnbanned(user); OnUserUnbanned(user);
} }
else else
@@ -821,40 +811,20 @@ namespace Discord
{ {
var data = e.Payload.ToObject<MessageCreateEvent>(Serializer); var data = e.Payload.ToObject<MessageCreateEvent>(Serializer);


Channel channel = GetChannel(data.ChannelId);
var channel = GetChannel(data.ChannelId) as ITextChannel;
if (channel != null) if (channel != null)
{ {
var user = channel.GetUserFast(data.Author.Id);
var user = (channel as Channel).GetUser(data.Author.Id);


if (user != null) if (user != null)
{ {
Message msg = null; Message msg = null;
bool isAuthor = data.Author.Id == CurrentUser.Id;
//ulong nonce = 0;

/*if (data.Author.Id == _privateUser.Id && Config.UseMessageQueue)
{
if (data.Nonce != null && ulong.TryParse(data.Nonce, out nonce))
msg = _messages[nonce];
}*/
if (msg == null)
{
msg = channel.AddMessage(data.Id, user, data.Timestamp.Value);
//nonce = 0;
}

//Remapped queued message
/*if (nonce != 0)
{
msg = _messages.Remap(nonce, data.Id);
msg.Id = data.Id;
RaiseMessageSent(msg);
}*/
msg = (channel as Channel).MessageManager.Add(data.Id, user, data.Timestamp.Value);


msg.Update(data); msg.Update(data);
user.UpdateActivity(); user.UpdateActivity();
Logger.Verbose($"MESSAGE_CREATE: {channel.Path} ({user.Name ?? "Unknown"})");
Logger.Verbose($"MESSAGE_CREATE: {channel} ({user})");
OnMessageReceived(msg); OnMessageReceived(msg);
} }
else else
@@ -867,13 +837,13 @@ namespace Discord
case "MESSAGE_UPDATE": case "MESSAGE_UPDATE":
{ {
var data = e.Payload.ToObject<MessageUpdateEvent>(Serializer); var data = e.Payload.ToObject<MessageUpdateEvent>(Serializer);
var channel = GetChannel(data.ChannelId);
var channel = GetChannel(data.ChannelId) as ITextChannel;
if (channel != null) if (channel != null)
{ {
var msg = channel.GetMessage(data.Id, data.Author?.Id);
var msg = (channel as Channel).MessageManager.Get(data.Id, data.Author?.Id);
var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; var before = Config.EnablePreUpdateEvents ? msg.Clone() : null;
msg.Update(data); msg.Update(data);
Logger.Verbose($"MESSAGE_UPDATE: {channel.Path} ({data.Author?.Username ?? "Unknown"})");
Logger.Verbose($"MESSAGE_UPDATE: {channel} ({data.Author?.Username ?? "Unknown"})");
OnMessageUpdated(before, msg); OnMessageUpdated(before, msg);
} }
else else
@@ -883,11 +853,11 @@ namespace Discord
case "MESSAGE_DELETE": case "MESSAGE_DELETE":
{ {
var data = e.Payload.ToObject<MessageDeleteEvent>(Serializer); var data = e.Payload.ToObject<MessageDeleteEvent>(Serializer);
var channel = GetChannel(data.ChannelId);
var channel = GetChannel(data.ChannelId) as ITextChannel;
if (channel != null) if (channel != null)
{ {
var msg = channel.RemoveMessage(data.Id);
Logger.Verbose($"MESSAGE_DELETE: {channel.Path} ({msg.User?.Name ?? "Unknown"})");
var msg = (channel as Channel).MessageManager.Remove(data.Id);
Logger.Verbose($"MESSAGE_DELETE: {channel} ({msg.User?.Name ?? "Unknown"})");
OnMessageDeleted(msg); OnMessageDeleted(msg);
} }
else else
@@ -921,7 +891,7 @@ namespace Discord
if (user != null) if (user != null)
{ {
if (Config.LogLevel == LogSeverity.Debug) if (Config.LogLevel == LogSeverity.Debug)
Logger.Debug($"PRESENCE_UPDATE: {user.Path}");
Logger.Debug($"PRESENCE_UPDATE: {user}");
var before = Config.EnablePreUpdateEvents ? user.Clone() : null; var before = Config.EnablePreUpdateEvents ? user.Clone() : null;
user.Update(data); user.Update(data);
OnUserUpdated(before, user); OnUserUpdated(before, user);
@@ -933,29 +903,18 @@ namespace Discord
case "TYPING_START": case "TYPING_START":
{ {
var data = e.Payload.ToObject<TypingStartEvent>(Serializer); var data = e.Payload.ToObject<TypingStartEvent>(Serializer);
var channel = GetChannel(data.ChannelId);
var channel = GetChannel(data.ChannelId) as ITextChannel;
if (channel != null) if (channel != null)
{ {
User user;
if (channel.IsPrivate)
{
if (channel.Recipient.Id == data.UserId)
user = channel.Recipient;
else
break;
}
else
user = channel.Server.GetUser(data.UserId);
User user = (channel as Channel).GetUser(data.UserId);
if (user != null) if (user != null)
{ {
if (Config.LogLevel == LogSeverity.Debug) if (Config.LogLevel == LogSeverity.Debug)
Logger.Debug($"TYPING_START: {channel.Path} ({user.Name})");
Logger.Debug($"TYPING_START: {user.ToString(channel)}");
OnUserIsTypingUpdated(channel, user); OnUserIsTypingUpdated(channel, user);
user.UpdateActivity(); user.UpdateActivity();
} }
} }
else
Logger.Warning("TYPING_START referenced an unknown channel.");
} }
break; break;


@@ -970,7 +929,7 @@ namespace Discord
if (user != null) if (user != null)
{ {
if (Config.LogLevel == LogSeverity.Debug) if (Config.LogLevel == LogSeverity.Debug)
Logger.Debug($"VOICE_STATE_UPDATE: {user.Path}");
Logger.Debug($"VOICE_STATE_UPDATE: {user}");
var before = Config.EnablePreUpdateEvents ? user.Clone() : null; var before = Config.EnablePreUpdateEvents ? user.Clone() : null;
user.Update(data); user.Update(data);
//Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); //Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}");


+ 42
- 0
src/Discord.Net/EnumConverters.cs View File

@@ -0,0 +1,42 @@
using System;

namespace Discord
{
public static class EnumConverters
{
public static ChannelType ToChannelType(string value)
{
switch (value)
{
case "text": return ChannelType.Text;
case "voice": return ChannelType.Voice;
default: throw new ArgumentException("Unknown channel type", nameof(value));
}
}
public static string ToString(ChannelType value)
{
if ((value & ChannelType.Text) != 0) return "text";
if ((value & ChannelType.Voice) != 0) return "voice";
throw new ArgumentException("Invalid channel tType", nameof(value));
}

public static PermissionTarget ToPermissionTarget(string value)
{
switch (value)
{
case "member": return PermissionTarget.User;
case "role": return PermissionTarget.Role;
default: throw new ArgumentException("Unknown permission target", nameof(value));
}
}
public static string ToString(PermissionTarget value)
{
switch (value)
{
case PermissionTarget.User: return "member";
case PermissionTarget.Role: return "role";
default: throw new ArgumentException("Invalid permission target", nameof(value));
}
}
}
}

+ 7
- 31
src/Discord.Net/Enums/ChannelType.cs View File

@@ -2,36 +2,12 @@


namespace Discord namespace Discord
{ {
public class ChannelType : StringEnum, IEquatable<ChannelType>
{
/// <summary> A text-only channel. </summary>
public static ChannelType Text { get; } = new ChannelType("text");
/// <summary> A voice-only channel. </summary>
public static ChannelType Voice { get; } = new ChannelType("voice");
private ChannelType(string value)
: base(value) { }

public static ChannelType FromString(string value)
{
switch (value)
{
case null:
return null;
case "text":
return Text;
case "voice":
return Voice;
default:
return new ChannelType(value);
}
}

public static implicit operator ChannelType(string value) => FromString(value);
public static bool operator ==(ChannelType a, ChannelType b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false);
public static bool operator !=(ChannelType a, ChannelType b) => !(a == b);
public override int GetHashCode() => Value.GetHashCode();
public override bool Equals(object obj) => (obj as ChannelType)?.Equals(this) ?? false;
public bool Equals(ChannelType type) => type != null && type.Value == Value;
[Flags]
public enum ChannelType : byte
{
Public = 0x01,
Private = 0x02,
Text = 0x10,
Voice = 0x20
} }
} }

+ 3
- 30
src/Discord.Net/Enums/PermissionTarget.cs View File

@@ -1,35 +1,8 @@
namespace Discord namespace Discord
{ {
public class PermissionTarget : StringEnum
public enum PermissionTarget : byte
{ {
/// <summary> A text-only channel. </summary>
public static PermissionTarget Role { get; } = new PermissionTarget("role");
/// <summary> A voice-only channel. </summary>
public static PermissionTarget User { get; } = new PermissionTarget("member");

private PermissionTarget(string value)
: base(value) { }

public static PermissionTarget FromString(string value)
{
switch (value)
{
case null:
return null;
case "role":
return Role;
case "member":
return User;
default:
return new PermissionTarget(value);
}
}

public static implicit operator PermissionTarget(string value) => FromString(value);
public static bool operator ==(PermissionTarget a, PermissionTarget b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false);
public static bool operator !=(PermissionTarget a, PermissionTarget b) => !(a == b);
public override int GetHashCode() => Value.GetHashCode();
public override bool Equals(object obj) => (obj as PermissionTarget)?.Equals(this) ?? false;
public bool Equals(PermissionTarget type) => type != null && type.Value == Value;
User,
Role
} }
} }

+ 2
- 4
src/Discord.Net/Events/ChannelEventArgs.cs View File

@@ -4,10 +4,8 @@ namespace Discord
{ {
public class ChannelEventArgs : EventArgs public class ChannelEventArgs : EventArgs
{ {
public Channel Channel { get; }
public IChannel Channel { get; }


public Server Server => Channel.Server;

public ChannelEventArgs(Channel channel) { Channel = channel; }
public ChannelEventArgs(IChannel channel) { Channel = channel; }
} }
} }

+ 3
- 5
src/Discord.Net/Events/ChannelUpdatedEventArgs.cs View File

@@ -4,12 +4,10 @@ namespace Discord
{ {
public class ChannelUpdatedEventArgs : EventArgs public class ChannelUpdatedEventArgs : EventArgs
{ {
public Channel Before { get; }
public Channel After { get; }
public IChannel Before { get; }
public IChannel After { get; }


public Server Server => After.Server;

public ChannelUpdatedEventArgs(Channel before, Channel after)
public ChannelUpdatedEventArgs(IChannel before, IChannel after)
{ {
Before = before; Before = before;
After = after; After = after;


+ 1
- 1
src/Discord.Net/Events/MessageEventArgs.cs View File

@@ -7,7 +7,7 @@ namespace Discord
public Message Message { get; } public Message Message { get; }


public User User => Message.User; public User User => Message.User;
public Channel Channel => Message.Channel;
public ITextChannel Channel => Message.Channel;
public Server Server => Message.Server; public Server Server => Message.Server;


public MessageEventArgs(Message msg) { Message = msg; } public MessageEventArgs(Message msg) { Message = msg; }


+ 1
- 1
src/Discord.Net/Events/MessageUpdatedEventArgs.cs View File

@@ -8,7 +8,7 @@ namespace Discord
public Message After { get; } public Message After { get; }


public User User => After.User; public User User => After.User;
public Channel Channel => After.Channel;
public ITextChannel Channel => After.Channel;
public Server Server => After.Server; public Server Server => After.Server;


public MessageUpdatedEventArgs(Message before, Message after) public MessageUpdatedEventArgs(Message before, Message after)


src/Discord.Net/Events/ChannelUserEventArgs.cs → src/Discord.Net/Events/TypingEventArgs.cs View File

@@ -1,11 +1,11 @@
namespace Discord namespace Discord
{ {
public class ChannelUserEventArgs
public class TypingEventArgs
{ {
public Channel Channel { get; }
public ITextChannel Channel { get; }
public User User { get; } public User User { get; }


public ChannelUserEventArgs(Channel channel, User user)
public TypingEventArgs(ITextChannel channel, User user)
{ {
Channel = channel; Channel = channel;
User = user; User = user;

+ 11
- 0
src/Discord.Net/IModel.cs View File

@@ -0,0 +1,11 @@
using System.Threading.Tasks;

namespace Discord
{
public interface IModel
{
ulong Id { get; }

Task Save();
}
}

+ 0
- 81
src/Discord.Net/InternalExtensions.cs View File

@@ -1,9 +1,7 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;


namespace Discord namespace Discord
@@ -63,85 +61,6 @@ namespace Discord
} }
} }


public static IEnumerable<Channel> Find(this IEnumerable<Channel> channels, string name, ChannelType type = null, bool exactMatch = false)
{
//Search by name
var query = channels
.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));

if (!exactMatch)
{
if (name.Length >= 2 && name[0] == '<' && name[1] == '#' && name[name.Length - 1] == '>') //Search by raw mention
{
ulong id;
if (name.Substring(2, name.Length - 3).TryToId(out id))
{
var channel = channels.Where(x => x.Id == id).FirstOrDefault();
if (channel != null)
query = query.Concat(new Channel[] { channel });
}
}
if (name.Length >= 1 && name[0] == '#' && (type == null || type == ChannelType.Text)) //Search by clean mention
{
string name2 = name.Substring(1);
query = query.Concat(channels
.Where(x => x.Type == ChannelType.Text && string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)));
}
}

if (type != null)
query = query.Where(x => x.Type == type);
return query;
}

public static IEnumerable<User> Find(this IEnumerable<User> users,
string name, ushort? discriminator = null, bool exactMatch = false)
{
//Search by name
var query = users
.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));

if (!exactMatch)
{
if (name.Length >= 2 && name[0] == '<' && name[1] == '@' && name[name.Length - 1] == '>') //Search by raw mention
{
ulong id;
if (name.Substring(2, name.Length - 3).TryToId(out id))
{
var user = users.Where(x => x.Id == id).FirstOrDefault();
if (user != null)
query = query.Concat(new User[] { user });
}
}
if (name.Length >= 1 && name[0] == '@') //Search by clean mention
{
string name2 = name.Substring(1);
query = query.Concat(users
.Where(x => string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)));
}
}

if (discriminator != null)
query = query.Where(x => x.Discriminator == discriminator.Value);
return query;
}

public static IEnumerable<Role> Find(this IEnumerable<Role> roles, string name, bool exactMatch = false)
{
// if (name.StartsWith("@"))
// {
// string name2 = name.Substring(1);
// return _roles.Where(x => x.Server.Id == server.Id &&
// string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) ||
// string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase));
// }
// else
return roles.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
}

public static IEnumerable<Server> Find(this IEnumerable<Server> servers, string name, bool exactMatch = false)
=> servers.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));

public static string Base64(this Stream stream, ImageType type, string existingId) public static string Base64(this Stream stream, ImageType type, string existingId)
{ {
if (type == ImageType.None) if (type == ImageType.None)


+ 54
- 65
src/Discord.Net/MessageQueue.cs View File

@@ -57,12 +57,12 @@ namespace Discord.Net
_pendingSendsByNonce = new ConcurrentDictionary<int, string>(); _pendingSendsByNonce = new ConcurrentDictionary<int, string>();
} }


internal Message QueueSend(Channel channel, string text, bool isTTS)
internal Message QueueSend(ITextChannel channel, string text, bool isTTS)
{ {
Message msg = new Message(0, channel, channel.IsPrivate ? channel.Client.PrivateUser : channel.Server.CurrentUser);
Message msg = new Message(0, channel, (channel as Channel).CurrentUser);
msg.IsTTS = isTTS; msg.IsTTS = isTTS;
msg.RawText = text; msg.RawText = text;
msg.Text = msg.Resolve(text);
msg.Text = Message.ResolveMentions(msg.Channel, msg.Text);
msg.Nonce = GenerateNonce(); msg.Nonce = GenerateNonce();
if (_pendingSendsByNonce.TryAdd(msg.Nonce, text)) if (_pendingSendsByNonce.TryAdd(msg.Nonce, text))
{ {
@@ -111,110 +111,99 @@ namespace Discord.Net
{ {
return Task.Run(async () => return Task.Run(async () =>
{ {
try
while (!cancelToken.IsCancellationRequested)
{ {
while (!cancelToken.IsCancellationRequested)
Message msg;
while (_pendingSends.TryDequeue(out msg))
{ {
Message msg;
while (_pendingSends.TryDequeue(out msg))
DecrementCount();
string text;
if (_pendingSendsByNonce.TryRemove(msg.Nonce, out text)) //If it was deleted from queue, this will fail
{ {
DecrementCount();
string text;
if (_pendingSendsByNonce.TryRemove(msg.Nonce, out text)) //If it was deleted from queue, this will fail
try
{ {
try
//msg.RawText = text;
//msg.Text = Message.ResolveMentions(msg.Channel, text);
var request = new SendMessageRequest(msg.Channel.Id)
{ {
msg.RawText = text;
msg.Text = msg.Resolve(text);
var request = new SendMessageRequest(msg.Channel.Id)
{
Content = msg.RawText,
Nonce = msg.Nonce.ToString(),
IsTTS = msg.IsTTS
};
var response = await _rest.Send(request).ConfigureAwait(false);
msg.State = MessageState.Normal;
msg.Id = response.Id;
msg.Update(response);
}
catch (Exception ex)
{
msg.State = MessageState.Failed;
_logger.Error($"Failed to send message to {msg.Channel.Path}", ex);
}
Content = text,
Nonce = msg.Nonce.ToString(),
IsTTS = msg.IsTTS
};
var response = await _rest.Send(request).ConfigureAwait(false);
msg.Id = response.Id;
msg.State = MessageState.Normal;
msg.Update(response);
}
catch (Exception ex)
{
msg.State = MessageState.Failed;
_logger.Error($"Failed to send message to {msg.Channel}", ex);
} }
} }
await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false);
} }
await Task.Delay(DiscordConfig.MessageQueueInterval).ConfigureAwait(false);
} }
catch (OperationCanceledException) { }
}); });
} }
private Task RunEditQueue(CancellationToken cancelToken) private Task RunEditQueue(CancellationToken cancelToken)
{ {
return Task.Run(async () => return Task.Run(async () =>
{ {
try
while (!cancelToken.IsCancellationRequested)
{ {
while (!cancelToken.IsCancellationRequested)
MessageEdit edit;
while (_pendingEdits.TryPeek(out edit) && edit.Message.State != MessageState.Queued)
{ {
MessageEdit edit;
while (_pendingEdits.TryPeek(out edit) && edit.Message.State != MessageState.Queued)
if (_pendingEdits.TryDequeue(out edit))
{ {
if (_pendingEdits.TryDequeue(out edit))
DecrementCount();
if (edit.Message.State == MessageState.Normal)
{ {
DecrementCount();
if (edit.Message.State == MessageState.Normal)
try
{ {
try
var request = new UpdateMessageRequest(edit.Message.Channel.Id, edit.Message.Id)
{ {
var request = new UpdateMessageRequest(edit.Message.Channel.Id, edit.Message.Id)
{
Content = edit.NewText
};
await _rest.Send(request).ConfigureAwait(false);
}
catch (Exception ex) { _logger.Error($"Failed to edit message {edit.Message.Path}", ex); }
Content = edit.NewText
};
await _rest.Send(request).ConfigureAwait(false);
} }
catch (Exception ex) { _logger.Error($"Failed to edit message {edit.Message}", ex); }
} }
} }
await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false);
} }
await Task.Delay(DiscordConfig.MessageQueueInterval).ConfigureAwait(false);
} }
catch (OperationCanceledException) { }
}); });
} }
private Task RunDeleteQueue(CancellationToken cancelToken) private Task RunDeleteQueue(CancellationToken cancelToken)
{ {
return Task.Run(async () => return Task.Run(async () =>
{ {
try
while (!cancelToken.IsCancellationRequested)
{ {
while (!cancelToken.IsCancellationRequested)
Message msg;
while (_pendingDeletes.TryPeek(out msg) && msg.State != MessageState.Queued)
{ {
Message msg;
while (_pendingDeletes.TryPeek(out msg) && msg.State != MessageState.Queued)
if (_pendingDeletes.TryDequeue(out msg))
{ {
if (_pendingDeletes.TryDequeue(out msg))
DecrementCount();
if (msg.State == MessageState.Normal)
{ {
DecrementCount();
if (msg.State == MessageState.Normal)
try
{ {
try
{
var request = new DeleteMessageRequest(msg.Channel.Id, msg.Id);
await _rest.Send(request).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } //Ignore
catch (Exception ex) { _logger.Error($"Failed to delete message {msg.Path}", ex); }
var request = new DeleteMessageRequest(msg.Channel.Id, msg.Id);
await _rest.Send(request).ConfigureAwait(false);
msg.State = MessageState.Deleted;
} }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } //Ignore
catch (Exception ex) { _logger.Error($"Failed to delete message {msg}", ex); }
} }
} }

await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false);
} }

await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false);
} }
catch (OperationCanceledException) { }
}); });
} }




+ 27
- 574
src/Discord.Net/Models/Channel.cs View File

@@ -1,602 +1,55 @@
using Discord.API.Client;
using Discord.API.Client.Rest;
using Discord.Net;
using System;
using System.Collections.Concurrent;
using APIChannel = Discord.API.Client.Channel;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using APIChannel = Discord.API.Client.Channel;


namespace Discord namespace Discord
{ {
public class Channel : IMentionable
public abstract class Channel : IChannel
{ {
private readonly static Action<Channel, Channel> _cloner = DynamicIL.CreateCopyMethod<Channel>();

private struct Member
{
public User User { get; }
public ChannelPermissions Permissions { get; }

public Member(User user, ChannelPermissions permissions)
{
User = user;
Permissions = permissions;
}
}

public class PermissionOverwrite
/// <summary> An entry in a public channel's permissions that gives or takes permissions from a specific role or user. </summary>
public class PermissionRule
{ {
/// <summary> The type of object TargetId is referring to. </summary>
public PermissionTarget TargetType { get; } public PermissionTarget TargetType { get; }
/// <summary> The Id of an object, whos type is specified by TargetType, that is the target of permissions being added or taken away. </summary>
public ulong TargetId { get; } public ulong TargetId { get; }
public ChannelPermissionOverrides Permissions { get; }
/// <summary> A collection of permissions that are added or taken away from the target. </summary>
public ChannelTriStatePermissions Permissions { get; }


internal PermissionOverwrite(PermissionTarget targetType, ulong targetId, uint allow, uint deny)
internal PermissionRule(PermissionTarget targetType, ulong targetId, uint allow, uint deny)
{ {
TargetType = targetType; TargetType = targetType;
TargetId = targetId; TargetId = targetId;
Permissions = new ChannelPermissionOverrides(allow, deny);
Permissions = new ChannelTriStatePermissions(allow, deny);
} }
} }


private readonly ConcurrentDictionary<ulong, Member> _users;
private readonly ConcurrentDictionary<ulong, Message> _messages;
private Dictionary<ulong, PermissionOverwrite> _permissionOverwrites;

public DiscordClient Client { get; }

/// <summary> Gets the unique identifier for this channel. </summary> /// <summary> Gets the unique identifier for this channel. </summary>
public ulong Id { get; } public ulong Id { get; }
/// <summary> Gets the server owning this channel, if this is a public chat. </summary>
public Server Server { get; }
/// <summary> Gets the target user, if this is a private chat. </summary>
public User Recipient { get; }

/// <summary> Gets the name of this channel. </summary>
public string Name { get; private set; }
/// <summary> Gets the topic of this channel. </summary>
public string Topic { get; private set; }
/// <summary> Gets the position of this channel relative to other channels in this server. </summary>
public int Position { get; private set; }
/// <summary> Gets the type of this channel). </summary>
public ChannelType Type { get; private set; }

/// <summary> Gets the path to this object. </summary>
internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}";
/// <summary> Gets true if this is a private chat with another user. </summary>
public bool IsPrivate => Recipient != null;
/// <summary> Gets the string used to mention this channel. </summary>
public string Mention => $"<#{Id}>";
/// <summary> Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. </summary>
public IEnumerable<Message> Messages => _messages?.Values ?? Enumerable.Empty<Message>();
/// <summary> Gets a collection of all custom permissions used for this channel. </summary>
public IEnumerable<PermissionOverwrite> PermissionOverwrites => _permissionOverwrites.Select(x => x.Value);
/// <summary> Gets a collection of all users with read access to this channel. </summary>
public IEnumerable<User> Users
{
get
{
if (Client.Config.UsePermissionsCache)
{
if (IsPrivate)
return new User[] { Client.PrivateUser, Recipient };
else if (Type == ChannelType.Text)
return _users.Values.Where(x => x.Permissions.ReadMessages == true).Select(x => x.User);
else if (Type == ChannelType.Voice)
return _users.Values.Select(x => x.User).Where(x => x.VoiceChannel == this);
}
else
{
if (IsPrivate)
return new User[] { Client.PrivateUser, Recipient };
else if (Type == ChannelType.Text)
{
ChannelPermissions perms = new ChannelPermissions();
return Server.Users.Where(x =>
{
UpdatePermissions(x, ref perms);
return perms.ReadMessages == true;
});
}
else if (Type == ChannelType.Voice)
return Server.Users.Where(x => x.VoiceChannel == this);
}
return Enumerable.Empty<User>();
}
}

internal Channel(DiscordClient client, ulong id, Server server)
: this(client, id)
{
Server = server;
if (client.Config.UsePermissionsCache)
_users = new ConcurrentDictionary<ulong, Member>(2, (int)(server.UserCount * 1.05));
}
internal Channel(DiscordClient client, ulong id, User recipient)
: this(client, id)
{
Recipient = recipient;
Type = ChannelType.Text; //Discord doesn't give us a type for private channels
}
private Channel(DiscordClient client, ulong id)
{
Client = client;
Id = id;

_permissionOverwrites = new Dictionary<ulong, PermissionOverwrite>();
if (client.Config.MessageCacheSize > 0)
_messages = new ConcurrentDictionary<ulong, Message>(2, (int)(client.Config.MessageCacheSize * 1.05));
}
internal void Update(APIChannel model)
{
if (!IsPrivate && model.Name != null)
Name = model.Name;
if (model.Type != null)
Type = model.Type;
if (model.Position != null)
Position = model.Position.Value;
if (model.Topic != null)
Topic = model.Topic;
if (model.Recipient != null)
{
Recipient.Update(model.Recipient);
Name = $"@{Recipient}";
}

if (model.PermissionOverwrites != null)
{
_permissionOverwrites = model.PermissionOverwrites
.Select(x => new PermissionOverwrite(PermissionTarget.FromString(x.Type), x.Id, x.Allow, x.Deny))
.ToDictionary(x => x.TargetId);
UpdatePermissions();
}
}

/// <summary> Edits this channel, changing only non-null attributes. </summary>
public async Task Edit(string name = null, string topic = null, int? position = null)
{
if (name != null || topic != null)
{
var request = new UpdateChannelRequest(Id)
{
Name = name ?? Name,
Topic = topic ?? Topic,
Position = Position
};
await Client.ClientAPI.Send(request).ConfigureAwait(false);
}


if (position != null)
{
Channel[] channels = Server.AllChannels.Where(x => x.Type == Type).OrderBy(x => x.Position).ToArray();
int oldPos = Array.IndexOf(channels, this);
var newPosChannel = channels.Where(x => x.Position > position).FirstOrDefault();
int newPos = (newPosChannel != null ? Array.IndexOf(channels, newPosChannel) : channels.Length) - 1;
if (newPos < 0)
newPos = 0;
int minPos;
public abstract DiscordClient Client { get; }
/// <summary> Gets the type of this channel. </summary>
public abstract ChannelType Type { get; }
public bool IsText => (Type & ChannelType.Text) != 0;
public bool IsVoice => (Type & ChannelType.Voice) != 0;
public bool IsPrivate => (Type & ChannelType.Private) != 0;
public bool IsPublic => (Type & ChannelType.Public) != 0;


if (oldPos < newPos) //Moving Down
{
minPos = oldPos;
for (int i = oldPos; i < newPos; i++)
channels[i] = channels[i + 1];
channels[newPos] = this;
}
else //(oldPos > newPos) Moving Up
{
minPos = newPos;
for (int i = oldPos; i > newPos; i--)
channels[i] = channels[i - 1];
channels[newPos] = this;
}
Channel after = minPos > 0 ? channels.Skip(minPos - 1).FirstOrDefault() : null;
await Server.ReorderChannels(channels.Skip(minPos), after).ConfigureAwait(false);
}
}
public async Task Delete()
{
try { await Client.ClientAPI.Send(new DeleteChannelRequest(Id)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}
public abstract User CurrentUser { get; }
/// <summary> Gets a collection of all users in this channel. </summary>
public abstract IEnumerable<User> Users { get; }


#region Invites
/// <summary> Gets all active (non-expired) invites to this server. </summary>
public async Task<IEnumerable<Invite>> GetInvites()
=> (await Server.GetInvites().ConfigureAwait(false)).Where(x => x.Channel.Id == Id);
internal abstract MessageManager MessageManager { get; }
internal abstract PermissionManager PermissionManager { get; }


/// <summary> Creates a new invite to this channel. </summary>
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param>
/// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param>
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param>
/// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param>
public async Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false)
protected Channel(ulong id)
{ {
if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge));
if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses));

var request = new CreateInviteRequest(Id)
{
MaxAge = maxAge ?? 0,
MaxUses = maxUses ?? 0,
IsTemporary = tempMembership,
WithXkcdPass = withXkcd
};

var response = await Client.ClientAPI.Send(request).ConfigureAwait(false);
var invite = new Invite(Client, response.Code, response.XkcdPass);
return invite;
}
#endregion

#region Messages
internal Message AddMessage(ulong id, User user, DateTime timestamp)
{
Message message = new Message(id, this, user);
message.State = MessageState.Normal;
var cacheLength = Client.Config.MessageCacheSize;
if (cacheLength > 0)
{
var oldestIds = _messages
.Where(x => x.Value.Timestamp < timestamp)
.Select(x => x.Key).OrderBy(x => x)
.Take(_messages.Count - cacheLength);
Message removed;
foreach (var removeId in oldestIds)
_messages.TryRemove(removeId, out removed);
return _messages.GetOrAdd(message.Id, message);
}
return message;
}
internal Message RemoveMessage(ulong id)
{
if (Client.Config.MessageCacheSize > 0)
{
Message msg;
if (_messages.TryRemove(id, out msg))
return msg;
}
return new Message(id, this, null);
}

public Message GetMessage(ulong id)
=> GetMessage(id, null);
internal Message GetMessage(ulong id, ulong? userId)
{
if (Client.Config.MessageCacheSize > 0)
{
Message result;
if (_messages.TryGetValue(id, out result))
return result;
}
return new Message(id, this, userId != null ? GetUserFast(userId.Value) : null);
}

public async Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null,
Relative relativeDir = Relative.Before, bool useCache = true)
{
if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit));
if (limit == 0 || Type != ChannelType.Text) return new Message[0];
try
{
var request = new GetMessagesRequest(Id)
{
Limit = limit,
RelativeDir = relativeMessageId.HasValue ? relativeDir == Relative.Before ? "before" : "after" : null,
RelativeId = relativeMessageId ?? 0
};
var msgs = await Client.ClientAPI.Send(request).ConfigureAwait(false);
return msgs.Select(x =>
{
Message msg = null;
if (useCache)
{
msg = AddMessage(x.Id, GetUserFast(x.Author.Id), x.Timestamp.Value);
var user = msg.User;
if (user != null)
user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp);
}
else
msg = new Message(x.Id, this, GetUserFast(x.Author.Id));
msg.Update(x);
return msg;
})
.ToArray();
}
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
return new Message[0];
}
}

/// <summary> Returns all members of this channel with the specified name. </summary>
/// <remarks> Name formats supported: Name, @Name and &lt;@Id&gt;. Search is case-insensitive if exactMatch is false.</remarks>
public IEnumerable<User> FindUsers(string name, bool exactMatch = false)
{
if (name == null) throw new ArgumentNullException(nameof(name));
return Users.Find(name, exactMatch: exactMatch);
}

public Task<Message> SendMessage(string text) => SendMessageInternal(text, false);
public Task<Message> SendTTSMessage(string text) => SendMessageInternal(text, true);
private Task<Message> SendMessageInternal(string text, bool isTTS)
{
if (text == null) throw new ArgumentNullException(nameof(text));
if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text));
if (text.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less.");
return Task.FromResult(Client.MessageQueue.QueueSend(this, text, isTTS));
}

public async Task<Message> SendFile(string filePath)
{
using (var stream = File.OpenRead(filePath))
return await SendFile(System.IO.Path.GetFileName(filePath), stream).ConfigureAwait(false);
}
public async Task<Message> SendFile(string filename, Stream stream)
{
if (filename == null) throw new ArgumentNullException(nameof(filename));
if (stream == null) throw new ArgumentNullException(nameof(stream));

var request = new SendFileRequest(Id)
{
Filename = filename,
Stream = stream
};
var model = await Client.ClientAPI.Send(request).ConfigureAwait(false);

var msg = AddMessage(model.Id, IsPrivate ? Client.PrivateUser : Server.CurrentUser, model.Timestamp.Value);
msg.Update(model);
return msg;
}

public Task SendIsTyping() => Client.ClientAPI.Send(new SendIsTypingRequest(Id));
#endregion

#region Permissions
internal void UpdatePermissions()
{
if (!Client.Config.UsePermissionsCache) return;

foreach (var pair in _users)
{
var member = pair.Value;
var perms = member.Permissions;
if (UpdatePermissions(member.User, ref perms))
_users[pair.Key] = new Member(member.User, perms);
}
}
internal void UpdatePermissions(User user)
{
if (!Client.Config.UsePermissionsCache) return;

Member member;
if (_users.TryGetValue(user.Id, out member))
{
var perms = member.Permissions;
if (UpdatePermissions(member.User, ref perms))
_users[user.Id] = new Member(member.User, perms);
}
}
internal bool UpdatePermissions(User user, ref ChannelPermissions permissions)
{
uint newPermissions = 0;
var server = Server;

//Load the mask of all permissions supported by this channel type
var mask = ChannelPermissions.All(this).RawValue;

if (server != null)
{
//Start with this user's server permissions
newPermissions = server.GetPermissions(user).RawValue;

if (IsPrivate || user == Server.Owner)
newPermissions = mask; //Owners always have all permissions
else
{
var channelOverwrites = PermissionOverwrites;

var roles = user.Roles;
foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.DenyValue != 0 && roles.Any(y => y.Id == x.TargetId)))
newPermissions &= ~denyRole.Permissions.DenyValue;
foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.AllowValue != 0 && roles.Any(y => y.Id == x.TargetId)))
newPermissions |= allowRole.Permissions.AllowValue;
foreach (var denyUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id && x.Permissions.DenyValue != 0))
newPermissions &= ~denyUser.Permissions.DenyValue;
foreach (var allowUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id && x.Permissions.AllowValue != 0))
newPermissions |= allowUser.Permissions.AllowValue;

if (newPermissions.HasBit((byte)PermissionBits.ManageRolesOrPermissions))
newPermissions = mask; //ManageRolesOrPermissions gives all permisions
else if (Type == ChannelType.Text && !newPermissions.HasBit((byte)PermissionBits.ReadMessages))
newPermissions = 0; //No read permission on a text channel removes all other permissions
else if (Type == ChannelType.Voice && !newPermissions.HasBit((byte)PermissionBits.Connect))
newPermissions = 0; //No connect permissions on a voice channel removes all other permissions
else
newPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from serverPerms, for example)
}
}
else
newPermissions = mask; //Private messages always have all permissions

if (newPermissions != permissions.RawValue)
{
permissions = new ChannelPermissions(newPermissions);
return true;
}
return false;
}
internal ChannelPermissions GetPermissions(User user)
{
if (Client.Config.UsePermissionsCache)
{
Member member;
if (_users.TryGetValue(user.Id, out member))
return member.Permissions;
else
return ChannelPermissions.None;
}
else
{
ChannelPermissions perms = new ChannelPermissions();
UpdatePermissions(user, ref perms);
return perms;
}
}
public ChannelPermissionOverrides GetPermissionsRule(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));

return PermissionOverwrites
.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id)
.Select(x => x.Permissions)
.FirstOrDefault();
}
public ChannelPermissionOverrides GetPermissionsRule(Role role)
{
if (role == null) throw new ArgumentNullException(nameof(role));

return PermissionOverwrites
.Where(x => x.TargetType == PermissionTarget.Role && x.TargetId == role.Id)
.Select(x => x.Permissions)
.FirstOrDefault();
}
public Task AddPermissionsRule(User user, ChannelPermissions allow, ChannelPermissions deny)
{
if (user == null) throw new ArgumentNullException(nameof(user));

return AddPermissionsRule(user.Id, PermissionTarget.User, allow.RawValue, deny.RawValue);
}
public Task AddPermissionsRule(User user, ChannelPermissionOverrides permissions)
{
if (user == null) throw new ArgumentNullException(nameof(user));

return AddPermissionsRule(user.Id, PermissionTarget.User, permissions.AllowValue, permissions.DenyValue);
}
public Task AddPermissionsRule(Role role, ChannelPermissions allow, ChannelPermissions deny)
{
if (role == null) throw new ArgumentNullException(nameof(role));

return AddPermissionsRule(role.Id, PermissionTarget.Role, allow.RawValue, deny.RawValue);
}
public Task AddPermissionsRule(Role role, ChannelPermissionOverrides permissions)
{
if (role == null) throw new ArgumentNullException(nameof(role));

return AddPermissionsRule(role.Id, PermissionTarget.Role, permissions.AllowValue, permissions.DenyValue);
}
private Task AddPermissionsRule(ulong targetId, PermissionTarget targetType, uint allow, uint deny)
{
var request = new AddChannelPermissionsRequest(Id)
{
TargetId = targetId,
TargetType = targetType.Value,
Allow = allow,
Deny = deny
};
return Client.ClientAPI.Send(request);
}
public Task RemovePermissionsRule(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
return RemovePermissionsRule(user.Id, PermissionTarget.User);
}
public Task RemovePermissionsRule(Role role)
{
if (role == null) throw new ArgumentNullException(nameof(role));
return RemovePermissionsRule(role.Id, PermissionTarget.Role);
}
private async Task RemovePermissionsRule(ulong userOrRoleId, PermissionTarget targetType)
{
try
{
var perms = PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).FirstOrDefault();
await Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(Id, userOrRoleId)).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}
#endregion

#region Users
internal void AddUser(User user)
{
if (!Client.Config.UsePermissionsCache) return;

var perms = new ChannelPermissions();
UpdatePermissions(user, ref perms);
var member = new Member(user, ChannelPermissions.None);
_users[user.Id] = new Member(user, ChannelPermissions.None);
Id = id;
} }
internal void RemoveUser(ulong id)
{
if (!Client.Config.UsePermissionsCache) return;


Member ignored;
_users.TryRemove(id, out ignored);
}
public User GetUser(ulong id)
{
if (IsPrivate)
{
if (id == Recipient.Id)
return Recipient;
else if (id == Client.PrivateUser.Id)
return Client.PrivateUser;
}
else if (!Client.Config.UsePermissionsCache)
{
var user = Server.GetUser(id);
if (user != null)
{
ChannelPermissions perms = new ChannelPermissions();
UpdatePermissions(user, ref perms);
if (perms.ReadMessages)
return user;
}
}
else
{
Member result;
if (_users.TryGetValue(id, out result))
return result.User;
}
return null;
}
internal User GetUserFast(ulong id)
{
//Can return users not in this channel, but that's usually okay
if (IsPrivate)
{
if (id == Recipient.Id)
return Recipient;
else if (id == Client.PrivateUser.Id)
return Client.PrivateUser;
}
else
return Server.GetUser(id);
return null;
}
#endregion
internal abstract void Update(APIChannel model);


internal Channel Clone()
{
var result = new Channel();
_cloner(this, result);
return result;
}
private Channel() { }
internal abstract User GetUser(ulong id);


public override string ToString() => Name ?? Id.ToIdString();
internal abstract Channel Clone();
} }
} }

+ 0
- 22
src/Discord.Net/Models/Color.cs View File

@@ -3,28 +3,6 @@
public class Color public class Color
{ {
public static readonly Color Default = new Color(0); public static readonly Color Default = new Color(0);

public static readonly Color Teal = new Color(0x1ABC9C);
public static readonly Color DarkTeal = new Color(0x11806A);
public static readonly Color Green = new Color(0x2ECC71);
public static readonly Color DarkGreen = new Color(0x1F8B4C);
public static readonly Color Blue = new Color(0x3498DB);
public static readonly Color DarkBlue = new Color(0x206694);
public static readonly Color Purple = new Color(0x9B59B6);
public static readonly Color DarkPurple = new Color(0x71368A);
public static readonly Color Magenta = new Color(0xE91E63);
public static readonly Color DarkMagenta = new Color(0xAD1457);
public static readonly Color Gold = new Color(0xF1C40F);
public static readonly Color DarkGold = new Color(0xC27C0E);
public static readonly Color Orange = new Color(0xE67E22);
public static readonly Color DarkOrange = new Color(0xA84300);
public static readonly Color Red = new Color(0xE74C3C);
public static readonly Color DarkRed = new Color(0x992D22);

public static readonly Color LighterGrey = new Color(0x95A5A6);
public static readonly Color DarkGrey = new Color(0x607D8B);
public static readonly Color LightGrey = new Color(0x979C9F);
public static readonly Color DarkerGrey = new Color(0x546E7A);
public uint RawValue { get; } public uint RawValue { get; }


+ 21
- 0
src/Discord.Net/Models/IChannel.cs View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;

namespace Discord
{
public interface IChannel
{
/// <summary> Gets the unique identifier for this channel. </summary>
ulong Id { get; }
DiscordClient Client { get; }

/// <summary> Gets the type of this channel. </summary>
ChannelType Type { get; }
bool IsText { get; }
bool IsVoice { get; }
bool IsPrivate { get; }
bool IsPublic { get; }

/// <summary> Gets a collection of all users in this channel. </summary>
IEnumerable<User> Users { get; }
}
}

+ 6
- 0
src/Discord.Net/Models/IPrivateChannel.cs View File

@@ -0,0 +1,6 @@
namespace Discord
{
public interface IPrivateChannel : IChannel
{
}
}

+ 6
- 0
src/Discord.Net/Models/IPublicChannel.cs View File

@@ -0,0 +1,6 @@
namespace Discord
{
public interface IPublicChannel : IChannel
{
}
}

+ 17
- 0
src/Discord.Net/Models/ITextChannel.cs View File

@@ -0,0 +1,17 @@
using System.IO;
using System.Threading.Tasks;

namespace Discord
{
public interface ITextChannel : IChannel
{
Message GetMessage(ulong id);
Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before);

Task<Message> SendMessage(string text, bool isTTS = false);
Task<Message> SendFile(string filePath);
Task<Message> SendFile(string filename, Stream stream);

Task SendIsTyping();
}
}

+ 6
- 0
src/Discord.Net/Models/IVoiceChannel.cs View File

@@ -0,0 +1,6 @@
namespace Discord
{
public interface IVoiceChannel : IChannel
{
}
}

+ 4
- 6
src/Discord.Net/Models/Invite.cs View File

@@ -1,10 +1,10 @@
using Discord.API.Client;
using APIInvite = Discord.API.Client.Invite;
using Discord.API.Client;
using Discord.API.Client.Rest; using Discord.API.Client.Rest;
using Discord.Net; using Discord.Net;
using System; using System;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using APIInvite = Discord.API.Client.Invite;


namespace Discord namespace Discord
{ {
@@ -84,9 +84,7 @@ namespace Discord
public bool IsTemporary { get; private set; } public bool IsTemporary { get; private set; }
/// <summary> Gets when this invite was created. </summary> /// <summary> Gets when this invite was created. </summary>
public DateTime CreatedAt { get; private set; } public DateTime CreatedAt { get; private set; }

/// <summary> Gets the path to this object. </summary>
internal string Path => $"{Server?.Name ?? "[Private]"}/{Code}";
/// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary> /// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary>
public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; public string Url => $"{DiscordConfig.InviteUrl}/{Code}";


@@ -138,6 +136,6 @@ namespace Discord
} }
private Invite() { } //Used for cloning private Invite() { } //Used for cloning


public override string ToString() => XkcdCode ?? Code;
public override string ToString() => $"{Server}/{XkcdCode ?? Code}";
} }
} }

+ 154
- 0
src/Discord.Net/Models/Managers/MessageManager.cs View File

@@ -0,0 +1,154 @@
using Discord.API.Client.Rest;
using Discord.Net;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace Discord
{
internal class MessageManager : IEnumerable<Message>
{
private readonly ITextChannel _channel;
private readonly int _size;
private readonly ConcurrentDictionary<ulong, Message> _messages;
private readonly ConcurrentQueue<ulong> _orderedMessages;

public MessageManager(ITextChannel channel, int size = 0)
{
_channel = channel;
_size = size;
if (size > 0)
{
_messages = new ConcurrentDictionary<ulong, Message>(2, size);
_orderedMessages = new ConcurrentQueue<ulong>();
}
}

internal Message Add(ulong id, User user, DateTime timestamp)
{
Message message = new Message(id, _channel, user);
message.State = MessageState.Normal;
if (_size > 0)
{
if (_messages.TryAdd(id, message))
{
_orderedMessages.Enqueue(id);

ulong msgId;
while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId))
{
Message msg;
if (_messages.TryRemove(msgId, out msg))
msg.State = MessageState.Detached;
}
}
}
return message;
}
internal Message Remove(ulong id)
{
if (_size > 0)
{
Message msg;
if (_messages.TryRemove(id, out msg))
return msg;
}
return new Message(id, _channel, null) { State = MessageState.Deleted };
}
public Message Get(ulong id, ulong? userId = null)
{
if (_messages != null)
{
Message result;
if (_messages.TryGetValue(id, out result))
return result;
}
return new Message(id, _channel, userId != null ? (_channel as Channel).GetUser(userId.Value) : null) { State = MessageState.Detached };
}

public async Task<Message[]> Download(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before)
{
if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit));
if (limit == 0) return new Message[0];

try
{
var request = new GetMessagesRequest(_channel.Id)
{
Limit = limit,
RelativeDir = relativeMessageId.HasValue ? relativeDir == Relative.Before ? "before" : "after" : null,
RelativeId = relativeMessageId ?? 0
};
var msgs = await _channel.Client.ClientAPI.Send(request).ConfigureAwait(false);
var server = (_channel as PublicChannel)?.Server;

return msgs.Select(x =>
{
Message msg = null;
ulong id = x.Author.Id;
var user = server?.GetUser(id) ?? (_channel as Channel).GetUser(id);
/*if (useCache)
{
msg = Add(x.Id, user, x.Timestamp.Value);
if (user != null)
user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp);
}
else*/
msg = new Message(x.Id, _channel, user);
msg.Update(x);
return msg;
}).ToArray();
}
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
return new Message[0];
}
}

public Task<Message> Send(string text, bool isTTS)
{
if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text));
if (text.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less.");
return Task.FromResult(_channel.Client.MessageQueue.QueueSend(_channel, text, isTTS));
}
public async Task<Message> SendFile(string filePath)
{
using (var stream = File.OpenRead(filePath))
return await SendFile(Path.GetFileName(filePath), stream).ConfigureAwait(false);
}
public async Task<Message> SendFile(string filename, Stream stream)
{
var request = new SendFileRequest(_channel.Id)
{
Filename = filename,
Stream = stream
};
var model = await _channel.Client.ClientAPI.Send(request).ConfigureAwait(false);

var msg = Add(model.Id, (_channel as Channel).CurrentUser, model.Timestamp.Value);
msg.Update(model);
return msg;
}

public IEnumerator<Message> GetEnumerator()
{
return _orderedMessages
.Select(x =>
{
Message msg;
if (_messages.TryGetValue(x, out msg))
return msg;
return null;
})
.Where(x => x != null).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

+ 223
- 0
src/Discord.Net/Models/Managers/PermissionManager.cs View File

@@ -0,0 +1,223 @@
using Discord.API.Client.Rest;
using Discord.Net;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using APIChannel = Discord.API.Client.Channel;

namespace Discord
{
internal class PermissionManager
{
public struct Member
{
public User User { get; }
public ChannelPermissions Permissions { get; }

public Member(User user, ChannelPermissions permissions)
{
User = user;
Permissions = permissions;
}
}

private readonly PublicChannel _channel;
private readonly ConcurrentDictionary<ulong, Member> _users;
private Dictionary<ulong, Channel.PermissionRule> _rules;

public IEnumerable<Member> Users => _users.Select(x => x.Value);
public IEnumerable<Channel.PermissionRule> Rules => _rules.Values;

public PermissionManager(PublicChannel channel, APIChannel model, int initialSize = -1)
{
_channel = channel;
if (initialSize >= 0)
_users = new ConcurrentDictionary<ulong, Member>(2, initialSize);
Update(model);
}

public void Update(APIChannel model)
{
_rules = model.PermissionOverwrites
.Select(x => new Channel.PermissionRule(EnumConverters.ToPermissionTarget(x.Type), x.Id, x.Allow, x.Deny))
.ToDictionary(x => x.TargetId);
UpdatePermissions();
}

public ChannelTriStatePermissions? GetOverwrite(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));

Channel.PermissionRule rule;
if (_rules.TryGetValue(user.Id, out rule))
return rule.Permissions;
return null;
}
public ChannelTriStatePermissions? GetOverwrite(Role role)
{
if (role == null) throw new ArgumentNullException(nameof(role));

Channel.PermissionRule rule;
if (_rules.TryGetValue(role.Id, out rule))
return rule.Permissions;
return null;
}
public Task AddOrUpdateOverwrite(User user, ChannelTriStatePermissions permissions)
{
if (user == null) throw new ArgumentNullException(nameof(user));
return AddOrUpdateOverwrite(user.Id, PermissionTarget.User, permissions);
}
public Task AddOrUpdateOverwrite(Role role, ChannelTriStatePermissions permissions)
{
if (role == null) throw new ArgumentNullException(nameof(role));
return AddOrUpdateOverwrite(role.Id, PermissionTarget.Role, permissions);
}
private Task AddOrUpdateOverwrite(ulong id, PermissionTarget type, ChannelTriStatePermissions permissions)
{
var request = new AddOrUpdateChannelPermissionsRequest(id)
{
TargetId = id,
TargetType = EnumConverters.ToString(type),
Allow = permissions.AllowValue,
Deny = permissions.DenyValue
};
return _channel.Client.ClientAPI.Send(request);
}
public Task RemoveOverwrite(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
return RemoveOverwrite(user.Id);
}
public Task RemoveOverwrite(Role role)
{
if (role == null) throw new ArgumentNullException(nameof(role));
return RemoveOverwrite(role.Id);
}
private async Task RemoveOverwrite(ulong id)
{
try { await _channel.Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(_channel.Id, id)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}

public ChannelPermissions GetPermissions(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));

if (_users != null)
{
Member member;
if (_users.TryGetValue(user.Id, out member))
return member.Permissions;
else
return ChannelPermissions.None;
}
else
{
ChannelPermissions perms = new ChannelPermissions();
ResolvePermissions(user, ref perms);
return perms;
}
}
public void UpdatePermissions()
{
if (_users != null)
{
foreach (var pair in _users)
{
var member = pair.Value;
var perms = member.Permissions;
if (ResolvePermissions(member.User, ref perms))
_users[pair.Key] = new Member(member.User, perms);
}
}
}
public void UpdatePermissions(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));

if (_users != null)
{
Member member;
if (_users.TryGetValue(user.Id, out member))
{
var perms = member.Permissions;
if (ResolvePermissions(member.User, ref perms))
_users[user.Id] = new Member(member.User, perms);
}
}
}
public bool ResolvePermissions(User user, ref ChannelPermissions permissions)
{
if (user == null) throw new ArgumentNullException(nameof(user));

uint newPermissions = 0;
var server = user.Server;

var mask = ChannelPermissions.All(_channel.Type).RawValue;
if (_channel.IsPrivate || user.IsOwner)
newPermissions = mask; //Private messages and owners always have all permissions
else
{
//Start with this user's server permissions
newPermissions = server.GetPermissions(user).RawValue;
var rules = _rules;

Channel.PermissionRule rule;
var roles = user.Roles.ToArray();
if (roles.Length > 0)
{
for (int i = 0; i < roles.Length; i++)
{
if (rules.TryGetValue(roles[i].Id, out rule))
newPermissions &= ~rule.Permissions.DenyValue;
}
for (int i = 0; i < roles.Length; i++)
{
if (rules.TryGetValue(roles[i].Id, out rule))
newPermissions |= rule.Permissions.AllowValue;
}
}
if (rules.TryGetValue(user.Id, out rule))
newPermissions = (newPermissions & ~rule.Permissions.DenyValue) | rule.Permissions.AllowValue;

if (newPermissions.HasBit((byte)PermissionBits.ManageRolesOrPermissions))
newPermissions = mask; //ManageRolesOrPermissions gives all permisions
else if (_channel.IsText && !newPermissions.HasBit((byte)PermissionBits.ReadMessages))
newPermissions = 0; //No read permission on a text channel removes all other permissions
else if (_channel.IsVoice && !newPermissions.HasBit((byte)PermissionBits.Connect))
newPermissions = 0; //No connect permissions on a voice channel removes all other permissions
else
newPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from serverPerms, for example)
}

if (newPermissions != permissions.RawValue)
{
permissions = new ChannelPermissions(newPermissions);
return true;
}
return false;
}

public void AddUser(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));

if (_users != null)
{
var perms = new ChannelPermissions();
ResolvePermissions(user, ref perms);
var member = new Member(user, ChannelPermissions.None);
_users[user.Id] = new Member(user, ChannelPermissions.None);
}
}
public void RemoveUser(ulong id)
{
Member ignored;
if (_users != null)
_users.TryRemove(id, out ignored);
}
}
}

+ 41
- 62
src/Discord.Net/Models/Message.cs View File

@@ -13,10 +13,14 @@ namespace Discord
Normal = 0, Normal = 0,
/// <summary> Message is current queued. </summary> /// <summary> Message is current queued. </summary>
Queued, Queued,
/// <summary> Message was deleted. </summary>
Deleted,
/// <summary> Message was deleted before it was sent. </summary> /// <summary> Message was deleted before it was sent. </summary>
Aborted, Aborted,
/// <summary> Message failed to be sent. </summary> /// <summary> Message failed to be sent. </summary>
Failed
Failed,
/// <summary> Message has been removed from cache and will no longer receive updates. </summary>
Detached
} }


public class Message public class Message
@@ -29,14 +33,14 @@ namespace Discord
private static readonly Attachment[] _initialAttachments = new Attachment[0]; private static readonly Attachment[] _initialAttachments = new Attachment[0];
private static readonly Embed[] _initialEmbeds = new Embed[0]; private static readonly Embed[] _initialEmbeds = new Embed[0];


internal static string CleanUserMentions(Channel channel, string text, List<User> users = null)
internal static string CleanUserMentions(PublicChannel channel, string text, List<User> users = null)
{ {
return _userRegex.Replace(text, new MatchEvaluator(e => return _userRegex.Replace(text, new MatchEvaluator(e =>
{ {
ulong id; ulong id;
if (e.Value.Substring(2, e.Value.Length - 3).TryToId(out id)) if (e.Value.Substring(2, e.Value.Length - 3).TryToId(out id))
{ {
var user = channel.GetUserFast(id);
var user = channel.GetUser(id);
if (user != null) if (user != null)
{ {
if (users != null) if (users != null)
@@ -47,7 +51,7 @@ namespace Discord
return e.Value; //User not found or parse failed return e.Value; //User not found or parse failed
})); }));
} }
internal static string CleanChannelMentions(Channel channel, string text, List<Channel> channels = null)
internal static string CleanChannelMentions(PublicChannel channel, string text, List<PublicChannel> channels = null)
{ {
var server = channel.Server; var server = channel.Server;
if (server == null) return text; if (server == null) return text;
@@ -81,34 +85,21 @@ namespace Discord
})); }));
}*/ }*/
//TODO: Move this somewhere
private static string Resolve(Channel channel, string text)
internal static string ResolveMentions(IChannel channel, string text)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel));
if (text == null) throw new ArgumentNullException(nameof(text)); if (text == null) throw new ArgumentNullException(nameof(text));


var client = channel.Client;
text = CleanUserMentions(channel, text);
text = CleanChannelMentions(channel, text);
//text = CleanRoleMentions(Channel, text);
var publicChannel = channel as PublicChannel;
if (publicChannel != null)
{
text = CleanUserMentions(publicChannel, text);
text = CleanChannelMentions(publicChannel, text);
//text = CleanRoleMentions(publicChannel, text);
}
return text; return text;
} }


/*internal class ImportResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (member is PropertyInfo)
{
if (member.Name == nameof(ChannelId) || !(member as PropertyInfo).CanWrite)
return null;

property.Writable = true; //Handles private setters
}
return property;
}
}*/

public class Attachment : File public class Attachment : File
{ {
/// <summary> Unique identifier for this file. </summary> /// <summary> Unique identifier for this file. </summary>
@@ -172,7 +163,7 @@ namespace Discord
/// <summary> Returns the unique identifier for this message. </summary> /// <summary> Returns the unique identifier for this message. </summary>
public ulong Id { get; internal set; } public ulong Id { get; internal set; }
/// <summary> Returns the channel this message was sent to. </summary> /// <summary> Returns the channel this message was sent to. </summary>
public Channel Channel { get; }
public ITextChannel Channel { get; }
/// <summary> Returns the author of this message. </summary> /// <summary> Returns the author of this message. </summary>
public User User { get; } public User User { get; }


@@ -196,20 +187,18 @@ namespace Discord
/// <summary> Returns a collection of all users mentioned in this message. </summary> /// <summary> Returns a collection of all users mentioned in this message. </summary>
public IEnumerable<User> MentionedUsers { get; internal set; } public IEnumerable<User> MentionedUsers { get; internal set; }
/// <summary> Returns a collection of all channels mentioned in this message. </summary> /// <summary> Returns a collection of all channels mentioned in this message. </summary>
public IEnumerable<Channel> MentionedChannels { get; internal set; }
public IEnumerable<PublicChannel> MentionedChannels { get; internal set; }
/// <summary> Returns a collection of all roles mentioned in this message. </summary> /// <summary> Returns a collection of all roles mentioned in this message. </summary>
public IEnumerable<Role> MentionedRoles { get; internal set; } public IEnumerable<Role> MentionedRoles { get; internal set; }


internal int Nonce { get; set; } internal int Nonce { get; set; }

/// <summary> Gets the path to this object. </summary>
internal string Path => $"{Server?.Name ?? "[Private]"}/{Id}";
/// <summary> Returns the server containing the channel this message was sent to. </summary> /// <summary> Returns the server containing the channel this message was sent to. </summary>
public Server Server => Channel.Server;
public Server Server => (Channel as PublicChannel)?.Server;
/// <summary> Returns if this message was sent from the logged-in accounts. </summary> /// <summary> Returns if this message was sent from the logged-in accounts. </summary>
public bool IsAuthor => User != null && User.Id == Client.CurrentUser?.Id; public bool IsAuthor => User != null && User.Id == Client.CurrentUser?.Id;


internal Message(ulong id, Channel channel, User user)
internal Message(ulong id, ITextChannel channel, User user)
{ {
Id = id; Id = id;
Channel = channel; Channel = channel;
@@ -222,7 +211,6 @@ namespace Discord
internal void Update(APIMessage model) internal void Update(APIMessage model)
{ {
var channel = Channel; var channel = Channel;
var server = channel.Server;
if (model.Attachments != null) if (model.Attachments != null)
{ {
Attachments = model.Attachments Attachments = model.Attachments
@@ -277,36 +265,34 @@ namespace Discord
if (model.Mentions != null) if (model.Mentions != null)
{ {
MentionedUsers = model.Mentions MentionedUsers = model.Mentions
.Select(x => Channel.GetUserFast(x.Id))
.Select(x => (Channel as Channel).GetUser(x.Id))
.Where(x => x != null) .Where(x => x != null)
.ToArray(); .ToArray();
} }
if (model.IsMentioningEveryone != null) if (model.IsMentioningEveryone != null)
{
if (model.IsMentioningEveryone.Value && User != null && User.GetPermissions(channel).MentionEveryone)
MentionedRoles = new Role[] { Server.EveryoneRole };
else
MentionedRoles = new Role[0];
{
var server = (channel as PublicChannel).Server;
if (model.IsMentioningEveryone.Value && server != null)
MentionedRoles = new Role[] { server.EveryoneRole };
else
MentionedRoles = Enumerable.Empty<Role>();
} }
if (model.Content != null) if (model.Content != null)
{ {
string text = model.Content; string text = model.Content;
RawText = text; RawText = text;


//var mentionedUsers = new List<User>();
var mentionedChannels = new List<Channel>();
//var mentionedRoles = new List<Role>();
text = CleanUserMentions(Channel, text/*, mentionedUsers*/);
if (server != null)
{
text = CleanChannelMentions(Channel, text, mentionedChannels);
//text = CleanRoleMentions(_client, User, channel, text, mentionedRoles);
}
Text = text;

//MentionedUsers = mentionedUsers;
MentionedChannels = mentionedChannels;
//MentionedRoles = mentionedRoles;
List<PublicChannel> mentionedChannels = null;
if (Channel.IsPublic)
mentionedChannels = new List<PublicChannel>();

text = CleanUserMentions(Channel as PublicChannel, text);
text = CleanChannelMentions(Channel as PublicChannel, text, mentionedChannels);

if (Channel.IsPublic)
MentionedChannels = mentionedChannels;

Text = text;
} }
} }


@@ -341,13 +327,6 @@ namespace Discord
return MentionedUsers?.Contains(me) ?? false; return MentionedUsers?.Contains(me) ?? false;
} }


/// <summary>Resolves all mentions in a provided string to those users, channels or roles' names.</summary>
public string Resolve(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
return Resolve(Channel, text);
}

internal Message Clone() internal Message Clone()
{ {
var result = new Message(); var result = new Message();
@@ -356,6 +335,6 @@ namespace Discord
} }
private Message() { } //Used for cloning private Message() { } //Used for cloning


public override string ToString() => $"{User?.Name ?? "Unknown User"}: {RawText}";
public override string ToString() => $"{User}: {RawText}";
} }
} }

+ 20
- 12
src/Discord.Net/Models/Permissions.cs View File

@@ -97,6 +97,8 @@ namespace Discord
RawValue = value; RawValue = value;
} }
public ServerPermissions(uint rawValue) { RawValue = rawValue; } public ServerPermissions(uint rawValue) { RawValue = rawValue; }

public override string ToString() => Convert.ToString(RawValue, 2);
} }


public struct ChannelPermissions public struct ChannelPermissions
@@ -105,13 +107,15 @@ namespace Discord
public static ChannelPermissions TextOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000111111110000011001", 2)); public static ChannelPermissions TextOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000111111110000011001", 2));
public static ChannelPermissions PrivateOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000011100110000000000", 2)); public static ChannelPermissions PrivateOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000011100110000000000", 2));
public static ChannelPermissions VoiceOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000011111100000000000000011001", 2)); public static ChannelPermissions VoiceOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000011111100000000000000011001", 2));
public static ChannelPermissions All(Channel channel) => All(channel.Type, channel.IsPrivate);
public static ChannelPermissions All(ChannelType channelType, bool isPrivate)
public static ChannelPermissions All(ChannelType channelType)
{ {
if (isPrivate) return PrivateOnly;
else if (channelType == ChannelType.Text) return TextOnly;
else if (channelType == ChannelType.Voice) return VoiceOnly;
else return None;
switch (channelType)
{
case ChannelType.Text: return TextOnly;
case ChannelType.Voice: return VoiceOnly;
case ChannelType.Private: return PrivateOnly;
default: return None;
}
} }


public uint RawValue { get; } public uint RawValue { get; }
@@ -191,11 +195,13 @@ namespace Discord
RawValue = value; RawValue = value;
} }
public ChannelPermissions(uint rawValue) { RawValue = rawValue; } public ChannelPermissions(uint rawValue) { RawValue = rawValue; }

public override string ToString() => Convert.ToString(RawValue, 2);
} }


public struct ChannelPermissionOverrides
public struct ChannelTriStatePermissions
{ {
public static ChannelPermissionOverrides InheritAll { get; } = new ChannelPermissionOverrides();
public static ChannelTriStatePermissions InheritAll { get; } = new ChannelTriStatePermissions();


public uint AllowValue { get; } public uint AllowValue { get; }
public uint DenyValue { get; } public uint DenyValue { get; }
@@ -236,16 +242,16 @@ namespace Discord
/// <summary> If True, a user may use voice activation rather than push-to-talk. </summary> /// <summary> If True, a user may use voice activation rather than push-to-talk. </summary>
public PermValue UseVoiceActivation => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.UseVoiceActivation); public PermValue UseVoiceActivation => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.UseVoiceActivation);


public ChannelPermissionOverrides(PermValue? createInstantInvite = null, PermValue? managePermissions = null,
public ChannelTriStatePermissions(PermValue? createInstantInvite = null, PermValue? managePermissions = null,
PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null,
PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null,
PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null,
PermValue? moveMembers = null, PermValue? useVoiceActivation = null) PermValue? moveMembers = null, PermValue? useVoiceActivation = null)
: this(new ChannelPermissionOverrides(), createInstantInvite, managePermissions, manageChannel, readMessages, sendMessages, sendTTSMessages,
: this(new ChannelTriStatePermissions(), createInstantInvite, managePermissions, manageChannel, readMessages, sendMessages, sendTTSMessages,
manageMessages, embedLinks, attachFiles, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation) manageMessages, embedLinks, attachFiles, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation)
{ {
} }
public ChannelPermissionOverrides(ChannelPermissionOverrides basePerms, PermValue? createInstantInvite = null, PermValue? managePermissions = null,
public ChannelTriStatePermissions(ChannelTriStatePermissions basePerms, PermValue? createInstantInvite = null, PermValue? managePermissions = null,
PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null,
PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null,
PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null,
@@ -274,11 +280,13 @@ namespace Discord
AllowValue = allow; AllowValue = allow;
DenyValue = deny; DenyValue = deny;
} }
public ChannelPermissionOverrides(uint allow = 0, uint deny = 0)
public ChannelTriStatePermissions(uint allow = 0, uint deny = 0)
{ {
AllowValue = allow; AllowValue = allow;
DenyValue = deny; DenyValue = deny;
} }

public override string ToString() => $"Allow: {Convert.ToString(AllowValue, 2)}, Deny: {Convert.ToString(DenyValue, 2)}";
} }
internal static class PermissionsHelper internal static class PermissionsHelper
{ {


+ 66
- 0
src/Discord.Net/Models/PrivateChannel.cs View File

@@ -0,0 +1,66 @@
using APIChannel = Discord.API.Client.Channel;
using Discord.API.Client.Rest;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace Discord
{
public class PrivateChannel : Channel, IPrivateChannel, ITextChannel
{
private readonly static Action<PrivateChannel, PrivateChannel> _cloner = DynamicIL.CreateCopyMethod<PrivateChannel>();

private readonly MessageManager _messages;
/// <summary> Gets the target user, if this is a private chat. </summary>
public User Recipient { get; }

public override DiscordClient Client => Recipient.Client;

public override ChannelType Type => ChannelType.Private;
public override User CurrentUser => Client.PrivateUser;
public override IEnumerable<User> Users => new User[] { Client.PrivateUser, Recipient };

internal override MessageManager MessageManager => _messages;
internal override PermissionManager PermissionManager => null;

internal PrivateChannel(ulong id, User recipient, APIChannel model)
: this(id, recipient)
{
_messages = new MessageManager(this, Client.Config.MessageCacheSize);
Update(model);
}
private PrivateChannel(ulong id, User recipient)
:base(id)
{
Recipient = recipient;
}

internal override User GetUser(ulong id)
{
if (id == Recipient.Id) return Recipient;
else if (id == Client.CurrentUser.Id) return Client.PrivateUser;
else return null;
}

public Message GetMessage(ulong id) => _messages.Get(id);
public Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before)
=> _messages.Download(limit, relativeMessageId, relativeDir);

public Task<Message> SendMessage(string text, bool isTTS = false) => _messages.Send(text, isTTS);
public Task<Message> SendFile(string filePath) => _messages.SendFile(filePath);
public Task<Message> SendFile(string filename, Stream stream) => _messages.SendFile(filename, stream);
public Task SendIsTyping() => Client.ClientAPI.Send(new SendIsTypingRequest(Id));

public override string ToString() => $"@{Recipient.Name}";

internal override void Update(APIChannel model) { }
internal override Channel Clone()
{
var result = new PrivateChannel(Id, Recipient);
_cloner(this, result);
return result;
}
}
}

+ 1
- 1
src/Discord.Net/Models/Profile.cs View File

@@ -83,6 +83,6 @@ namespace Discord
} }
private Profile() { } //Used for cloning private Profile() { } //Used for cloning


public override string ToString() => Id.ToIdString();
public override string ToString() => Name;
} }
} }

+ 109
- 0
src/Discord.Net/Models/PublicChannel.cs View File

@@ -0,0 +1,109 @@
using APIChannel = Discord.API.Client.Channel;
using Discord.API.Client.Rest;
using Discord.Net;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;

namespace Discord
{
/// <summary> A public Discord channel </summary>
public abstract class PublicChannel : Channel, IModel, IMentionable
{
internal readonly PermissionManager _permissions;
/// <summary> Gets the server owning this channel. </summary>
public Server Server { get; }

/// <summary> Gets or sets the name of this channel. </summary>
public string Name { get; set; }
/// <summary> Getsor sets the position of this channel relative to other channels of the same type in this server. </summary>
public int Position { get; set; }

/// <summary> Gets the DiscordClient that created this model. </summary>
public override DiscordClient Client => Server.Client;
public override User CurrentUser => Server.CurrentUser;
/// <summary> Gets the string used to mention this channel. </summary>
public string Mention => $"<#{Id}>";
/// <summary> Gets a collection of all custom permissions used for this channel. </summary>
public IEnumerable<PermissionRule> PermissionRules => _permissions.Rules;

internal PublicChannel(APIChannel model, Server server)
: this(model.Id, server)
{
_permissions = new PermissionManager(this, model, server.Client.Config.UsePermissionsCache ? (int)(server.UserCount * 1.05) : -1);
Update(model);
}
protected PublicChannel(ulong id, Server server)
: base(id)
{
Server = server;
}

internal override void Update(APIChannel model)
{
if (model.Name != null) Name = model.Name;
if (model.Position != null) Position = model.Position.Value;

if (model.PermissionOverwrites != null)
_permissions.Update(model);
}

public async Task Delete()
{
try { await Client.ClientAPI.Send(new DeleteChannelRequest(Id)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}
public abstract Task Save();

internal override User GetUser(ulong id) => Server.GetUser(id);

public ChannelTriStatePermissions? GetPermissionsRule(User user) => _permissions.GetOverwrite(user);
public ChannelTriStatePermissions? GetPermissionsRule(Role role) => _permissions.GetOverwrite(role);
public Task AddOrUpdatePermissionsRule(User user, ChannelTriStatePermissions permissions) => _permissions.AddOrUpdateOverwrite(user, permissions);
public Task AddOrUpdatePermissionsRule(Role role, ChannelTriStatePermissions permissions) => _permissions.AddOrUpdateOverwrite(role, permissions);
public Task RemovePermissionsRule(User user) => _permissions.RemoveOverwrite(user);
public async Task RemovePermissionsRule(Role role)
{
if (role == null) throw new ArgumentNullException(nameof(role));
try { await Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(Id, role.Id)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}

internal ChannelPermissions GetPermissions(User user) => _permissions.GetPermissions(user);
internal void UpdatePermissions() => _permissions.UpdatePermissions();
internal void UpdatePermissions(User user) => _permissions.UpdatePermissions(user);
internal bool ResolvePermissions(User user, ref ChannelPermissions permissions) => _permissions.ResolvePermissions(user, ref permissions);

internal override PermissionManager PermissionManager => null;

/// <summary> Creates a new invite to this channel. </summary>
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param>
/// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param>
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param>
/// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param>
public async Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false)
{
if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge));
if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses));

var request = new CreateInviteRequest(Id)
{
MaxAge = maxAge ?? 0,
MaxUses = maxUses ?? 0,
IsTemporary = tempMembership,
WithXkcdPass = withXkcd
};

var response = await Client.ClientAPI.Send(request).ConfigureAwait(false);
var invite = new Invite(Client, response.Code, response.XkcdPass);
return invite;
}

internal void AddUser(User user) => _permissions.AddUser(user);
internal void RemoveUser(ulong id) => _permissions.RemoveUser(id);
public override string ToString() => $"{Server}/{Name ?? Id.ToIdString()}";
}
}

+ 2
- 0
src/Discord.Net/Models/Region.cs View File

@@ -16,5 +16,7 @@
Port = port; Port = port;
Vip = vip; Vip = vip;
} }

public override string ToString() => Name;
} }
} }

+ 2
- 4
src/Discord.Net/Models/Role.cs View File

@@ -32,9 +32,7 @@ namespace Discord
public ServerPermissions Permissions { get; private set; } public ServerPermissions Permissions { get; private set; }
/// <summary> Gets the color of this role. </summary> /// <summary> Gets the color of this role. </summary>
public Color Color { get; private set; } public Color Color { get; private set; }

/// <summary> Gets the path to this object. </summary>
internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}";
/// <summary> Gets true if this is the role representing all users in a server. </summary> /// <summary> Gets true if this is the role representing all users in a server. </summary>
public bool IsEveryone => Id == Server.Id; public bool IsEveryone => Id == Server.Id;
/// <summary> Gets a list of all members in this role. </summary> /// <summary> Gets a list of all members in this role. </summary>
@@ -132,6 +130,6 @@ namespace Discord
} }
private Role() { } //Used for cloning private Role() { } //Used for cloning


public override string ToString() => Name ?? Id.ToIdString();
public override string ToString() => $"{Server}/{Name ?? Id.ToString()}";
} }
} }

+ 41
- 73
src/Discord.Net/Models/Server.cs View File

@@ -1,4 +1,5 @@
using Discord.API.Client;
using APIChannel = Discord.API.Client.Channel;
using Discord.API.Client;
using Discord.API.Client.Rest; using Discord.API.Client.Rest;
using Discord.Net; using Discord.Net;
using System; using System;
@@ -48,7 +49,7 @@ namespace Discord


private ConcurrentDictionary<ulong, Role> _roles; private ConcurrentDictionary<ulong, Role> _roles;
private ConcurrentDictionary<ulong, Member> _users; private ConcurrentDictionary<ulong, Member> _users;
private ConcurrentDictionary<ulong, Channel> _channels;
private ConcurrentDictionary<ulong, PublicChannel> _channels;
private ulong _ownerId; private ulong _ownerId;
private ulong? _afkChannelId; private ulong? _afkChannelId;
private int _userCount; private int _userCount;
@@ -59,34 +60,31 @@ namespace Discord
public ulong Id { get; } public ulong Id { get; }


/// <summary> Gets the name of this server. </summary> /// <summary> Gets the name of this server. </summary>
public string Name { get; private set; }
public string Name { get; set; }
/// <summary> Gets the voice region for this server. </summary> /// <summary> Gets the voice region for this server. </summary>
public Region Region { get; private set; }
/// <summary> Gets the unique identifier for this user's current avatar. </summary>
public string IconId { get; private set; }
/// <summary> Gets the unique identifier for this server's custom splash image. </summary>
public string SplashId { get; private set; }
public Region Region { get; set; }
/// <summary> Gets the AFK voice channel for this server. </summary>
public VoiceChannel AFKChannel { get; set; }
/// <summary> Gets the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK voice channel, if one is set. </summary> /// <summary> Gets the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK voice channel, if one is set. </summary>
public int AFKTimeout { get; private set; }
public int AFKTimeout { get; set; }

/// <summary> Gets the date and time you joined this server. </summary> /// <summary> Gets the date and time you joined this server. </summary>
public DateTime JoinedAt { get; private set; } public DateTime JoinedAt { get; private set; }
/// <summary> Gets the default channel for this server. </summary>
public Channel DefaultChannel { get; private set; }
/// <summary> Gets the the role representing all users in a server. </summary> /// <summary> Gets the the role representing all users in a server. </summary>
public Role EveryoneRole { get; private set; } public Role EveryoneRole { get; private set; }
/// <summary> Gets all extra features added to this server. </summary> /// <summary> Gets all extra features added to this server. </summary>
public IEnumerable<string> Features { get; private set; } public IEnumerable<string> Features { get; private set; }
/// <summary> Gets all custom emojis on this server. </summary> /// <summary> Gets all custom emojis on this server. </summary>
public IEnumerable<Emoji> CustomEmojis { get; private set; } public IEnumerable<Emoji> CustomEmojis { get; private set; }

/// <summary> Gets the path to this object. </summary>
internal string Path => Name;
/// <summary> Gets the unique identifier for this user's current avatar. </summary>
public string IconId { get; private set; }
/// <summary> Gets the unique identifier for this server's custom splash image. </summary>
public string SplashId { get; private set; }
/// <summary> Gets the user that created this server. </summary> /// <summary> Gets the user that created this server. </summary>
public User Owner => GetUser(_ownerId); public User Owner => GetUser(_ownerId);
/// <summary> Returns true if the current user owns this server. </summary>
public bool IsOwner => _ownerId == Client.CurrentUser.Id;
/// <summary> Gets the AFK voice channel for this server. </summary>
public Channel AFKChannel => _afkChannelId != null ? GetChannel(_afkChannelId.Value) : null;
/// <summary> Gets the default channel for this server. </summary>
public TextChannel DefaultChannel => _channels[Id] as TextChannel;
/// <summary> Gets the current user in this server. </summary> /// <summary> Gets the current user in this server. </summary>
public User CurrentUser => GetUser(Client.CurrentUser.Id); public User CurrentUser => GetUser(Client.CurrentUser.Id);
/// <summary> Gets the URL to this server's current icon. </summary> /// <summary> Gets the URL to this server's current icon. </summary>
@@ -95,11 +93,11 @@ namespace Discord
public string SplashUrl => GetSplashUrl(Id, SplashId); public string SplashUrl => GetSplashUrl(Id, SplashId);


/// <summary> Gets a collection of all channels in this server. </summary> /// <summary> Gets a collection of all channels in this server. </summary>
public IEnumerable<Channel> AllChannels => _channels.Select(x => x.Value);
public IEnumerable<PublicChannel> AllChannels => _channels.Select(x => x.Value);
/// <summary> Gets a collection of text channels in this server. </summary> /// <summary> Gets a collection of text channels in this server. </summary>
public IEnumerable<Channel> TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text);
public IEnumerable<TextChannel> TextChannels => _channels.Where(x => x.Value.IsText).Select(x => x.Value as TextChannel);
/// <summary> Gets a collection of voice channels in this server. </summary> /// <summary> Gets a collection of voice channels in this server. </summary>
public IEnumerable<Channel> VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice);
public IEnumerable<VoiceChannel> VoiceChannels => _channels.Where(x => x.Value.IsVoice).Select(x => x.Value as VoiceChannel);
/// <summary> Gets a collection of all members in this server. </summary> /// <summary> Gets a collection of all members in this server. </summary>
public IEnumerable<User> Users => _users.Select(x => x.Value.User); public IEnumerable<User> Users => _users.Select(x => x.Value.User);
/// <summary> Gets a collection of all roles in this server. </summary> /// <summary> Gets a collection of all roles in this server. </summary>
@@ -168,10 +166,9 @@ namespace Discord
//Only channels or members should have AddXXX(cachePerms: true), not both //Only channels or members should have AddXXX(cachePerms: true), not both
if (model.Channels != null) if (model.Channels != null)
{ {
_channels = new ConcurrentDictionary<ulong, Channel>(2, (int)(model.Channels.Length * 1.05));
_channels = new ConcurrentDictionary<ulong, PublicChannel>(2, (int)(model.Channels.Length * 1.05));
foreach (var subModel in model.Channels) foreach (var subModel in model.Channels)
AddChannel(subModel.Id, false).Update(subModel);
DefaultChannel = _channels[Id];
AddChannel(subModel, false);
} }
if (model.MemberCount != null) if (model.MemberCount != null)
{ {
@@ -257,67 +254,53 @@ namespace Discord
#endregion #endregion


#region Channels #region Channels
internal Channel AddChannel(ulong id, bool cachePerms)
internal PublicChannel AddChannel(APIChannel model, bool cachePerms)
{ {
var channel = new Channel(Client, id, this);
PublicChannel channel;
ChannelType type = EnumConverters.ToChannelType(model.Type);
if (type == ChannelType.Voice)
channel = new VoiceChannel(model, this);
else
channel = new TextChannel(model, this);

if (cachePerms && Client.Config.UsePermissionsCache) if (cachePerms && Client.Config.UsePermissionsCache)
{ {
foreach (var user in Users) foreach (var user in Users)
channel.AddUser(user); channel.AddUser(user);
} }
Client.AddChannel(channel); Client.AddChannel(channel);
return _channels.GetOrAdd(id, x => channel);
return _channels.GetOrAdd(model.Id, x => channel);
} }
internal Channel RemoveChannel(ulong id)
internal PublicChannel RemoveChannel(ulong id)
{ {
Channel channel;
PublicChannel channel;
_channels.TryRemove(id, out channel); _channels.TryRemove(id, out channel);
return channel; return channel;
} }


/// <summary> Gets the channel with the provided id and owned by this server, or null if not found. </summary> /// <summary> Gets the channel with the provided id and owned by this server, or null if not found. </summary>
public Channel GetChannel(ulong id)
public PublicChannel GetChannel(ulong id)
{ {
Channel result;
PublicChannel result;
_channels.TryGetValue(id, out result); _channels.TryGetValue(id, out result);
return result; return result;
} }

/// <summary> Returns all channels with the specified server and name. </summary>
/// <remarks> Name formats supported: Name, #Name and &lt;#Id&gt;. Search is case-insensitive if exactMatch is false.</remarks>
public IEnumerable<Channel> FindChannels(string name, ChannelType type = null, bool exactMatch = false)
{
if (name == null) throw new ArgumentNullException(nameof(name));

return _channels.Select(x => x.Value).Find(name, type, exactMatch);
}
public TextChannel GetTextChannel(ulong id) => GetChannel(id) as TextChannel;
public VoiceChannel GetVoiceChannel(ulong id) => GetChannel(id) as VoiceChannel;


/// <summary> Creates a new channel. </summary> /// <summary> Creates a new channel. </summary>
public async Task<Channel> CreateChannel(string name, ChannelType type)
public async Task<PublicChannel> CreateChannel(string name, ChannelType type)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));
if (type == null) throw new ArgumentNullException(nameof(type));
if (type != ChannelType.Text && type != ChannelType.Voice) throw new ArgumentException("Invalid channel type", nameof(type));


var request = new CreateChannelRequest(Id) { Name = name, Type = type.Value };
var request = new CreateChannelRequest(Id) { Name = name, Type = type };
var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); var response = await Client.ClientAPI.Send(request).ConfigureAwait(false);


var channel = AddChannel(response.Id, true);
var channel = AddChannel(response, true);
channel.Update(response); channel.Update(response);
return channel; return channel;
} }

/// <summary> Reorders the provided channels and optionally places them after a certain channel. </summary>
public Task ReorderChannels(IEnumerable<Channel> channels, Channel after = null)
{
if (channels == null) throw new ArgumentNullException(nameof(channels));

var request = new ReorderChannelsRequest(Id)
{
ChannelIds = channels.Select(x => x.Id).ToArray(),
StartPos = after != null ? after.Position + 1 : channels.Min(x => x.Position)
};
return Client.ClientAPI.Send(request);
}
#endregion #endregion


#region Invites #region Invites
@@ -359,13 +342,6 @@ namespace Discord
_roles.TryGetValue(id, out result); _roles.TryGetValue(id, out result);
return result; return result;
} }
/// <summary> Returns all roles with the specified server and name. </summary>
/// <remarks> Search is case-insensitive if exactMatch is false.</remarks>
public IEnumerable<Role> FindRoles(string name, bool exactMatch = false)
{
if (name == null) throw new ArgumentNullException(nameof(name));
return _roles.Select(x => x.Value).Find(name, exactMatch);
}
/// <summary> Creates a new role. </summary> /// <summary> Creates a new role. </summary>
public async Task<Role> CreateRole(string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false) public async Task<Role> CreateRole(string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false)
@@ -504,15 +480,7 @@ namespace Discord
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));


return _users.Select(x => x.Value.User).Find(name, discriminator: discriminator, exactMatch: false).FirstOrDefault();
}
/// <summary> Returns all members of this server with the specified name. </summary>
/// <remarks> Name formats supported: Name, @Name and &lt;@Id&gt;. Search is case-insensitive if exactMatch is false.</remarks>
public IEnumerable<User> FindUsers(string name, bool exactMatch = false)
{
if (name == null) throw new ArgumentNullException(nameof(name));

return _users.Select(x => x.Value.User).Find(name, exactMatch: exactMatch);
return _users.Select(x => x.Value.User).Where(x => x.Discriminator == discriminator && x.Name == name).SingleOrDefault();
} }


/// <summary> Kicks all users with an inactivity greater or equal to the provided number of days. </summary> /// <summary> Kicks all users with an inactivity greater or equal to the provided number of days. </summary>


+ 88
- 0
src/Discord.Net/Models/TextChannel.cs View File

@@ -0,0 +1,88 @@
using Discord.API.Client.Rest;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using APIChannel = Discord.API.Client.Channel;

namespace Discord
{
public class TextChannel : PublicChannel, IPublicChannel, ITextChannel
{
private readonly static Action<TextChannel, TextChannel> _cloner = DynamicIL.CreateCopyMethod<TextChannel>();

private readonly MessageManager _messages;

/// <summary> Gets or sets the topic of this channel. </summary>
public string Topic { get; set; }

public override ChannelType Type => ChannelType.Text;
/// <summary> Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. </summary>
public IEnumerable<Message> Messages => _messages != null ? _messages : Enumerable.Empty<Message>();
/// <summary> Gets a collection of all users with read access to this channel. </summary>
public override IEnumerable<User> Users
{
get
{
if (Client.Config.UsePermissionsCache)
return _permissions.Users.Where(x => x.Permissions.ReadMessages == true).Select(x => x.User);
else
{
ChannelPermissions perms = new ChannelPermissions();
return Server.Users.Where(x =>
{
_permissions.ResolvePermissions(x, ref perms);
return perms.ReadMessages == true;
});
}
}
}

internal override MessageManager MessageManager => _messages;

internal TextChannel(APIChannel model, Server server)
: base(model, server)
{
if (Client.Config.MessageCacheSize > 0)
_messages = new MessageManager(this, (int)(Client.Config.MessageCacheSize * 1.05));
}
private TextChannel(ulong id, Server server)
: base(id, server)
{
}

internal override void Update(APIChannel model)
{
base.Update(model);
if (model.Topic != null) Topic = model.Topic;
}
/// <summary> Save all changes to this channel. </summary>
public override async Task Save()
{
var request = new UpdateChannelRequest(Id)
{
Name = Name,
Topic = Topic,
Position = Position
};
await Client.ClientAPI.Send(request).ConfigureAwait(false);
}

public Message GetMessage(ulong id) => _messages.Get(id);
public Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before)
=> _messages.Download(limit, relativeMessageId, relativeDir);

public Task<Message> SendMessage(string text, bool isTTS = false) => _messages.Send(text, isTTS);
public Task<Message> SendFile(string filePath) => _messages.SendFile(filePath);
public Task<Message> SendFile(string filename, Stream stream) => _messages.SendFile(filename, stream);
public Task SendIsTyping() => Client.ClientAPI.Send(new SendIsTypingRequest(Id));
internal override Channel Clone()
{
var result = new TextChannel(Id, Server);
_cloner(this, result);
return result;
}
}
}

+ 34
- 54
src/Discord.Net/Models/User.cs View File

@@ -73,12 +73,11 @@ namespace Discord
// /// <summary> Gets this user's voice token. </summary> // /// <summary> Gets this user's voice token. </summary>
// public string Token { get; private set; } // public string Token { get; private set; }


/// <summary> Gets the path to this object. </summary>
internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}";
/// <summary> Gets the current private channel for this user if one exists. </summary> /// <summary> Gets the current private channel for this user if one exists. </summary>
public Channel PrivateChannel => Client.GetPrivateChannel(Id);
public PrivateChannel PrivateChannel => Client.GetPrivateChannel(Id);
/// <summary> Returns the string used to mention this user. </summary> /// <summary> Returns the string used to mention this user. </summary>
public string Mention => $"<@{Id}>"; public string Mention => $"<@{Id}>";
public bool IsOwner => Server == null ? false : this == Server.Owner;
/// <summary> Returns true if this user has marked themselves as muted. </summary> /// <summary> Returns true if this user has marked themselves as muted. </summary>
public bool IsSelfMuted => (_voiceState & VoiceState.SelfMuted) != 0; public bool IsSelfMuted => (_voiceState & VoiceState.SelfMuted) != 0;
/// <summary> Returns true if this user has marked themselves as deafened. </summary> /// <summary> Returns true if this user has marked themselves as deafened. </summary>
@@ -92,14 +91,15 @@ namespace Discord
/// <summary> Returns the time this user was last seen online in this server. </summary> /// <summary> Returns the time this user was last seen online in this server. </summary>
public DateTime? LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; public DateTime? LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline;
/// <summary> Gets this user's current voice channel. </summary> /// <summary> Gets this user's current voice channel. </summary>
public Channel VoiceChannel => _voiceChannelId != null ? Server.GetChannel(_voiceChannelId.Value) : null;
public VoiceChannel VoiceChannel => _voiceChannelId != null ? Server.GetVoiceChannel(_voiceChannelId.Value) : null;
/// <summary> Gets the URL to this user's current avatar. </summary> /// <summary> Gets the URL to this user's current avatar. </summary>
public string AvatarUrl => GetAvatarUrl(Id, AvatarId); public string AvatarUrl => GetAvatarUrl(Id, AvatarId);
/// <summary> Gets all roles that have been assigned to this user, including the everyone role. </summary> /// <summary> Gets all roles that have been assigned to this user, including the everyone role. </summary>
public IEnumerable<Role> Roles => _roles.Select(x => x.Value); public IEnumerable<Role> Roles => _roles.Select(x => x.Value);
public ServerPermissions ServerPermissions => Server.GetPermissions(this);


/// <summary> Returns a collection of all channels this user has permissions to join on this server. </summary> /// <summary> Returns a collection of all channels this user has permissions to join on this server. </summary>
public IEnumerable<Channel> Channels
public IEnumerable<IChannel> Channels
{ {
get get
{ {
@@ -108,8 +108,8 @@ namespace Discord
if (Client.Config.UsePermissionsCache) if (Client.Config.UsePermissionsCache)
{ {
return Server.AllChannels.Where(x => return Server.AllChannels.Where(x =>
(x.Type == ChannelType.Text && x.GetPermissions(this).ReadMessages) ||
(x.Type == ChannelType.Voice && x.GetPermissions(this).Connect));
(x.IsText && x.GetPermissions(this).ReadMessages) ||
(x.IsVoice && x.GetPermissions(this).Connect));
} }
else else
{ {
@@ -117,7 +117,7 @@ namespace Discord
return Server.AllChannels return Server.AllChannels
.Where(x => .Where(x =>
{ {
x.UpdatePermissions(this, ref perms);
x.ResolvePermissions(this, ref perms);
return (x.Type == ChannelType.Text && perms.ReadMessages) || return (x.Type == ChannelType.Text && perms.ReadMessages) ||
(x.Type == ChannelType.Voice && perms.Connect); (x.Type == ChannelType.Voice && perms.Connect);
}); });
@@ -131,9 +131,9 @@ namespace Discord
{ {
var privateChannel = Client.GetPrivateChannel(Id); var privateChannel = Client.GetPrivateChannel(Id);
if (privateChannel != null) if (privateChannel != null)
return new Channel[] { privateChannel };
return new IChannel[] { privateChannel };
else else
return new Channel[0];
return new IChannel[0];
} }
} }
} }
@@ -266,47 +266,15 @@ namespace Discord
var request = new KickMemberRequest(Server.Id, Id); var request = new KickMemberRequest(Server.Id, Id);
return Client.ClientAPI.Send(request); return Client.ClientAPI.Send(request);
} }

#region Permissions
public ServerPermissions ServerPermissions => Server.GetPermissions(this);
public ChannelPermissions GetPermissions(Channel channel)
public ChannelPermissions GetPermissions(PublicChannel channel)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));
return channel.GetPermissions(this); return channel.GetPermissions(this);
} }
#endregion

#region Channels
public Task<Channel> CreatePMChannel()
=> Client.CreatePMChannel(this);
#endregion

#region Messages
public async Task<Message> SendMessage(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));

var channel = await CreatePMChannel().ConfigureAwait(false);
return await channel.SendMessage(text).ConfigureAwait(false);
}
public async Task<Message> SendFile(string filePath)
{
if (filePath == null) throw new ArgumentNullException(nameof(filePath));

var channel = await CreatePMChannel().ConfigureAwait(false);
return await channel.SendFile(filePath).ConfigureAwait(false);
}
public async Task<Message> SendFile(string filename, Stream stream)
{
if (filename == null) throw new ArgumentNullException(nameof(filename));
if (stream == null) throw new ArgumentNullException(nameof(stream));

var channel = await CreatePMChannel().ConfigureAwait(false);
return await channel.SendFile(filename, stream).ConfigureAwait(false);
}
#endregion

#region Roles
public Task<PrivateChannel> CreatePMChannel() => Client.CreatePrivateChannel(this);
private void UpdateRoles(IEnumerable<Role> roles) private void UpdateRoles(IEnumerable<Role> roles)
{ {
bool updated = false; bool updated = false;
@@ -348,11 +316,10 @@ namespace Discord
return _roles.ContainsKey(role.Id); return _roles.ContainsKey(role.Id);
} }


public Task AddRoles(params Role[] roles)
=> Edit(roles: Roles.Concat(roles));
public Task RemoveRoles(params Role[] roles)
=> Edit(roles: Roles.Except(roles));
#endregion
public Task AddRoles(params Role[] roles) => Edit(roles: Roles.Concat(roles));
public Task AddRoles(IEnumerable<Role> roles) => Edit(roles: Roles.Concat(roles));
public Task RemoveRoles(params Role[] roles) => Edit(roles: Roles.Except(roles));
public Task RemoveRoles(IEnumerable<Role> roles) => Edit(roles: Roles.Except(roles));


internal User Clone() internal User Clone()
{ {
@@ -362,6 +329,19 @@ namespace Discord
} }
private User() { } //Used for cloning private User() { } //Used for cloning


public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : Id.ToIdString();
}
public override string ToString()
{
if (Name != null)
return $"{Server?.Name ?? "[Private]"}/{Name}#{Discriminator}";
else
return $"{Server?.Name ?? "[Private]"}/{Id}";
}
internal string ToString(IChannel channel)
{
if (Name != null)
return $"{channel}/{Name}#{Discriminator}";
else
return $"{channel}/{Id}";
}
}
} }

+ 60
- 0
src/Discord.Net/Models/VoiceChannel.cs View File

@@ -0,0 +1,60 @@
using APIChannel = Discord.API.Client.Channel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord.API.Client.Rest;

namespace Discord
{
public class VoiceChannel : PublicChannel, IPublicChannel, IVoiceChannel
{
private readonly static Action<VoiceChannel, VoiceChannel> _cloner = DynamicIL.CreateCopyMethod<VoiceChannel>();

public int Bitrate { get; set; }

public override ChannelType Type => ChannelType.Public | ChannelType.Voice;
/// <summary> Gets a collection of all users currently in this voice channel. </summary>
public override IEnumerable<User> Users
{
get
{
if (Client.Config.UsePermissionsCache)
return _permissions.Users.Select(x => x.User).Where(x => x.VoiceChannel == this);
else
return Server.Users.Where(x => x.VoiceChannel == this);
}
}

internal override MessageManager MessageManager => null;

internal VoiceChannel(APIChannel model, Server server)
: base(model, server)
{
}
private VoiceChannel(ulong id, Server server)
: base(id, server)
{
}


/// <summary> Save all changes to this channel. </summary>
public override async Task Save()
{
var request = new UpdateChannelRequest(Id)
{
Name = Name,
Position = Position,
Bitrate = Bitrate
};
await Client.ClientAPI.Send(request).ConfigureAwait(false);
}

internal override Channel Clone()
{
var result = new VoiceChannel(Id, Server);
_cloner(this, result);
return result;
}
}
}

+ 2
- 4
src/Discord.Net/project.json View File

@@ -1,9 +1,7 @@
{ {
"version": "0.9.0-rc3-3",
"version": "1.0.0-alpha1",
"description": "An unofficial .Net API wrapper for the Discord client.", "description": "An unofficial .Net API wrapper for the Discord client.",
"authors": [
"RogueException"
],
"authors": [ "RogueException" ],
"tags": [ "tags": [
"discord", "discord",
"discordapp" "discordapp"


Loading…
Cancel
Save