| @@ -56,14 +56,17 @@ | |||||
| <Compile Include="..\Discord.Net.Audio\IAudioClient.cs"> | <Compile Include="..\Discord.Net.Audio\IAudioClient.cs"> | ||||
| <Link>IAudioClient.cs</Link> | <Link>IAudioClient.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\Net\WebSockets\VoiceWebSocket.cs"> | |||||
| <Link>Net\WebSockets\VoiceWebSocket.cs</Link> | |||||
| <Compile Include="..\Discord.Net.Audio\InternalFrameEventArgs.cs"> | |||||
| <Link>InternalFrameEventArgs.cs</Link> | |||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\Net\WebSockets\VoiceWebSocket.Events.cs"> | |||||
| <Link>Net\WebSockets\VoiceWebSocket.Events.cs</Link> | |||||
| <Compile Include="..\Discord.Net.Audio\InternalIsSpeakingEventArgs.cs"> | |||||
| <Link>InternalIsSpeakingEventArgs.cs</Link> | |||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\Opus\Enums.cs"> | |||||
| <Link>Opus\Enums.cs</Link> | |||||
| <Compile Include="..\Discord.Net.Audio\Net\VoiceWebSocket.cs"> | |||||
| <Link>Net\VoiceWebSocket.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net.Audio\Opus\OpusConverter.cs"> | |||||
| <Link>Opus\OpusConverter.cs</Link> | |||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\Opus\OpusDecoder.cs"> | <Compile Include="..\Discord.Net.Audio\Opus\OpusDecoder.cs"> | ||||
| <Link>Opus\OpusDecoder.cs</Link> | <Link>Opus\OpusDecoder.cs</Link> | ||||
| @@ -74,9 +77,6 @@ | |||||
| <Compile Include="..\Discord.Net.Audio\SimpleAudioClient.cs"> | <Compile Include="..\Discord.Net.Audio\SimpleAudioClient.cs"> | ||||
| <Link>SimpleAudioClient.cs</Link> | <Link>SimpleAudioClient.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\Sodium.cs"> | |||||
| <Link>Sodium.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> | <Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> | ||||
| <Link>Sodium\SecretBox.cs</Link> | <Link>Sodium\SecretBox.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -89,16 +89,9 @@ | |||||
| <Compile Include="..\Discord.Net.Audio\VoiceDisconnectedEventArgs.cs"> | <Compile Include="..\Discord.Net.Audio\VoiceDisconnectedEventArgs.cs"> | ||||
| <Link>VoiceDisconnectedEventArgs.cs</Link> | <Link>VoiceDisconnectedEventArgs.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\VoicePacketEventArgs.cs"> | |||||
| <Link>VoicePacketEventArgs.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\Discord.Net.Commands.Net45\Discord.Net.Commands.csproj"> | |||||
| <Project>{1b5603b4-6f8f-4289-b945-7baae523d740}</Project> | |||||
| <Name>Discord.Net.Commands</Name> | |||||
| </ProjectReference> | |||||
| <ProjectReference Include="..\Discord.Net.Net45\Discord.Net.csproj"> | <ProjectReference Include="..\Discord.Net.Net45\Discord.Net.csproj"> | ||||
| <Project>{8d71a857-879a-4a10-859e-5ff824ed6688}</Project> | <Project>{8d71a857-879a-4a10-859e-5ff824ed6688}</Project> | ||||
| <Name>Discord.Net</Name> | <Name>Discord.Net</Name> | ||||
| @@ -2,6 +2,11 @@ | |||||
| { | { | ||||
| public static class AudioExtensions | public static class AudioExtensions | ||||
| { | { | ||||
| public static DiscordClient UsingAudio(this DiscordClient client, AudioServiceConfig config = null) | |||||
| { | |||||
| client.Services.Add(new AudioService(config)); | |||||
| return client; | |||||
| } | |||||
| public static AudioService Audio(this DiscordClient client, bool required = true) | public static AudioService Audio(this DiscordClient client, bool required = true) | ||||
| => client.Services.Get<AudioService>(required); | => client.Services.Get<AudioService>(required); | ||||
| } | } | ||||
| @@ -17,15 +17,12 @@ namespace Discord.Audio | |||||
| public event EventHandler Connected = delegate { }; | public event EventHandler Connected = delegate { }; | ||||
| public event EventHandler<VoiceDisconnectedEventArgs> Disconnected = delegate { }; | public event EventHandler<VoiceDisconnectedEventArgs> Disconnected = delegate { }; | ||||
| public event EventHandler<VoicePacketEventArgs> PacketReceived = delegate { }; | |||||
| public event EventHandler<UserIsSpeakingEventArgs> UserIsSpeakingUpdated = delegate { }; | public event EventHandler<UserIsSpeakingEventArgs> UserIsSpeakingUpdated = delegate { }; | ||||
| private void OnConnected() | private void OnConnected() | ||||
| => Connected(this, EventArgs.Empty); | => Connected(this, EventArgs.Empty); | ||||
| private void OnDisconnected(ulong serverId, bool wasUnexpected, Exception ex) | private void OnDisconnected(ulong serverId, bool wasUnexpected, Exception ex) | ||||
| => Disconnected(this, new VoiceDisconnectedEventArgs(serverId, wasUnexpected, ex)); | => Disconnected(this, new VoiceDisconnectedEventArgs(serverId, wasUnexpected, ex)); | ||||
| internal void OnPacketReceived(VoicePacketEventArgs e) | |||||
| => PacketReceived(this, e); | |||||
| private void OnUserIsSpeakingUpdated(User user, bool isSpeaking) | private void OnUserIsSpeakingUpdated(User user, bool isSpeaking) | ||||
| => UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking)); | => UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking)); | ||||
| @@ -7,7 +7,7 @@ | |||||
| <Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> | <Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> | ||||
| <PropertyGroup Label="Globals"> | <PropertyGroup Label="Globals"> | ||||
| <ProjectGuid>dff7afe3-ca77-4109-bade-b4b49a4f6648</ProjectGuid> | <ProjectGuid>dff7afe3-ca77-4109-bade-b4b49a4f6648</ProjectGuid> | ||||
| <RootNamespace>Discord</RootNamespace> | |||||
| <RootNamespace>Discord.Audio</RootNamespace> | |||||
| <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> | <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> | ||||
| <OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> | <OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| @@ -2,7 +2,7 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public class VoicePacketEventArgs : EventArgs | |||||
| internal class InternalFrameEventArgs : EventArgs | |||||
| { | { | ||||
| public ulong UserId { get; } | public ulong UserId { get; } | ||||
| public ulong ChannelId { get; } | public ulong ChannelId { get; } | ||||
| @@ -10,7 +10,7 @@ namespace Discord | |||||
| public int Offset { get; } | public int Offset { get; } | ||||
| public int Count { get; } | public int Count { get; } | ||||
| public VoicePacketEventArgs(ulong userId, ulong channelId, byte[] buffer, int offset, int count) | |||||
| public InternalFrameEventArgs(ulong userId, ulong channelId, byte[] buffer, int offset, int count) | |||||
| { | { | ||||
| UserId = userId; | UserId = userId; | ||||
| ChannelId = channelId; | ChannelId = channelId; | ||||
| @@ -0,0 +1,14 @@ | |||||
| namespace Discord.Audio | |||||
| { | |||||
| internal class InternalIsSpeakingEventArgs | |||||
| { | |||||
| public ulong UserId { get; } | |||||
| public bool IsSpeaking { get; } | |||||
| public InternalIsSpeakingEventArgs(ulong userId, bool isSpeaking) | |||||
| { | |||||
| UserId = userId; | |||||
| IsSpeaking = isSpeaking; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -49,6 +49,14 @@ namespace Discord.Net.WebSockets | |||||
| public int Ping => _ping; | public int Ping => _ping; | ||||
| internal VoiceBuffer OutputBuffer => _sendBuffer; | internal VoiceBuffer OutputBuffer => _sendBuffer; | ||||
| internal event EventHandler<InternalIsSpeakingEventArgs> UserIsSpeaking = delegate { }; | |||||
| internal event EventHandler<InternalFrameEventArgs> FrameReceived = delegate { }; | |||||
| private void OnUserIsSpeaking(ulong userId, bool isSpeaking) | |||||
| => UserIsSpeaking(this, new InternalIsSpeakingEventArgs(userId, isSpeaking)); | |||||
| internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count) | |||||
| => FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count)); | |||||
| internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, JsonSerializer serializer, Logger logger) | internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, JsonSerializer serializer, Logger logger) | ||||
| : base(client, serializer, logger) | : base(client, serializer, logger) | ||||
| { | { | ||||
| @@ -58,7 +66,7 @@ namespace Discord.Net.WebSockets | |||||
| _targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames | _targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames | ||||
| _encodingBuffer = new byte[MaxOpusSize]; | _encodingBuffer = new byte[MaxOpusSize]; | ||||
| _ssrcMapping = new ConcurrentDictionary<uint, ulong>(); | _ssrcMapping = new ConcurrentDictionary<uint, ulong>(); | ||||
| _encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.Audio); | |||||
| _encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.MusicOrMixed); | |||||
| _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); | _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); | ||||
| } | } | ||||
| @@ -223,7 +231,7 @@ namespace Discord.Net.WebSockets | |||||
| ulong userId; | ulong userId; | ||||
| if (_ssrcMapping.TryGetValue(ssrc, out userId)) | if (_ssrcMapping.TryGetValue(ssrc, out userId)) | ||||
| RaiseOnPacket(userId, Channel.Id, result, resultOffset, resultLength); | |||||
| OnFrameReceived(userId, Channel.Id, result, resultOffset, resultLength); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -440,7 +448,7 @@ namespace Discord.Net.WebSockets | |||||
| case OpCodes.Speaking: | case OpCodes.Speaking: | ||||
| { | { | ||||
| var payload = (msg.Payload as JToken).ToObject<SpeakingEvent>(_serializer); | var payload = (msg.Payload as JToken).ToObject<SpeakingEvent>(_serializer); | ||||
| RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); | |||||
| OnUserIsSpeaking(payload.UserId, payload.IsSpeaking); | |||||
| } | } | ||||
| break; | break; | ||||
| default: | default: | ||||
| @@ -449,9 +457,9 @@ namespace Discord.Net.WebSockets | |||||
| } | } | ||||
| } | } | ||||
| public void SendPCMFrames(byte[] data, int bytes) | |||||
| public void SendPCMFrames(byte[] data, int offset, int count) | |||||
| { | { | ||||
| _sendBuffer.Push(data, bytes, CancelToken); | |||||
| _sendBuffer.Push(data, offset, count, CancelToken); | |||||
| } | } | ||||
| public void ClearPCMFrames() | public void ClearPCMFrames() | ||||
| { | { | ||||
| @@ -1,33 +0,0 @@ | |||||
| using Discord.Audio; | |||||
| using System; | |||||
| namespace Discord.Net.WebSockets | |||||
| { | |||||
| internal sealed class IsTalkingEventArgs : EventArgs | |||||
| { | |||||
| public readonly ulong UserId; | |||||
| public readonly bool IsSpeaking; | |||||
| internal IsTalkingEventArgs(ulong userId, bool isTalking) | |||||
| { | |||||
| UserId = userId; | |||||
| IsSpeaking = isTalking; | |||||
| } | |||||
| } | |||||
| public partial class VoiceWebSocket | |||||
| { | |||||
| internal event EventHandler<IsTalkingEventArgs> IsSpeaking; | |||||
| private void RaiseIsSpeaking(ulong userId, bool isSpeaking) | |||||
| { | |||||
| if (IsSpeaking != null) | |||||
| IsSpeaking(this, new IsTalkingEventArgs(userId, isSpeaking)); | |||||
| } | |||||
| internal event EventHandler<VoicePacketEventArgs> OnPacket; | |||||
| internal void RaiseOnPacket(ulong userId, ulong channelId, byte[] buffer, int offset, int count) | |||||
| { | |||||
| if (OnPacket != null) | |||||
| OnPacket(this, new VoicePacketEventArgs(userId, channelId, buffer, offset, count)); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,49 +0,0 @@ | |||||
| namespace Discord.Audio.Opus | |||||
| { | |||||
| internal enum OpusCtl : int | |||||
| { | |||||
| SetBitrateRequest = 4002, | |||||
| GetBitrateRequest = 4003, | |||||
| SetInbandFECRequest = 4012, | |||||
| GetInbandFECRequest = 4013 | |||||
| } | |||||
| /// <summary>Supported coding modes.</summary> | |||||
| internal enum OpusApplication : int | |||||
| { | |||||
| /// <summary> | |||||
| /// Gives best quality at a given bitrate for voice signals. It enhances the input signal by high-pass filtering and emphasizing formants and harmonics. | |||||
| /// Optionally it includes in-band forward error correction to protect against packet loss. Use this mode for typical VoIP applications. | |||||
| /// Because of the enhancement, even at high bitrates the output may sound different from the input. | |||||
| /// </summary> | |||||
| Voip = 2048, | |||||
| /// <summary> | |||||
| /// Gives best quality at a given bitrate for most non-voice signals like music. | |||||
| /// Use this mode for music and mixed (music/voice) content, broadcast, and applications requiring less than 15 ms of coding delay. | |||||
| /// </summary> | |||||
| Audio = 2049, | |||||
| /// <summary> Low-delay mode that disables the speech-optimized mode in exchange for slightly reduced delay. </summary> | |||||
| Restricted_LowLatency = 2051 | |||||
| } | |||||
| internal enum OpusError : int | |||||
| { | |||||
| /// <summary> No error. </summary> | |||||
| OK = 0, | |||||
| /// <summary> One or more invalid/out of range arguments. </summary> | |||||
| BadArg = -1, | |||||
| /// <summary> The mode struct passed is invalid. </summary> | |||||
| BufferToSmall = -2, | |||||
| /// <summary> An internal error was detected. </summary> | |||||
| InternalError = -3, | |||||
| /// <summary> The compressed data passed is corrupted. </summary> | |||||
| InvalidPacket = -4, | |||||
| /// <summary> Invalid/unsupported request number. </summary> | |||||
| Unimplemented = -5, | |||||
| /// <summary> An encoder or decoder structure is invalid or already freed. </summary> | |||||
| InvalidState = -6, | |||||
| /// <summary> Memory allocation has failed. </summary> | |||||
| AllocFail = -7 | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,109 @@ | |||||
| using System; | |||||
| using System.Runtime.InteropServices; | |||||
| using System.Security; | |||||
| namespace Discord.Audio.Opus | |||||
| { | |||||
| public enum OpusApplication : int | |||||
| { | |||||
| Voice = 2048, | |||||
| MusicOrMixed = 2049, | |||||
| LowLatency = 2051 | |||||
| } | |||||
| public enum OpusError : int | |||||
| { | |||||
| OK = 0, | |||||
| BadArg = -1, | |||||
| BufferToSmall = -2, | |||||
| InternalError = -3, | |||||
| InvalidPacket = -4, | |||||
| Unimplemented = -5, | |||||
| InvalidState = -6, | |||||
| AllocFail = -7 | |||||
| } | |||||
| public abstract class OpusConverter : IDisposable | |||||
| { | |||||
| protected enum Ctl : int | |||||
| { | |||||
| SetBitrateRequest = 4002, | |||||
| GetBitrateRequest = 4003, | |||||
| SetInbandFECRequest = 4012, | |||||
| GetInbandFECRequest = 4013 | |||||
| } | |||||
| #if NET45 | |||||
| [SuppressUnmanagedCodeSecurity] | |||||
| #endif | |||||
| protected unsafe static class UnsafeNativeMethods | |||||
| { | |||||
| [DllImport("opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out OpusError error); | |||||
| [DllImport("opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern void DestroyEncoder(IntPtr encoder); | |||||
| [DllImport("opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte[] data, int max_data_bytes); | |||||
| [DllImport("opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern int EncoderCtl(IntPtr st, Ctl request, int value); | |||||
| [DllImport("opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern IntPtr CreateDecoder(int Fs, int channels, out OpusError error); | |||||
| [DllImport("opus", EntryPoint = "opus_decoder_destroy", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern void DestroyDecoder(IntPtr decoder); | |||||
| [DllImport("opus", EntryPoint = "opus_decode", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern int Decode(IntPtr st, byte* data, int len, byte[] pcm, int frame_size, int decode_fec); | |||||
| } | |||||
| protected IntPtr _ptr; | |||||
| /// <summary> Gets the bit rate of this converter. </summary> | |||||
| public const int BitsPerSample = 16; | |||||
| /// <summary> Gets the input sampling rate of this converter. </summary> | |||||
| public int InputSamplingRate { get; } | |||||
| /// <summary> Gets the number of channels of this converter. </summary> | |||||
| public int InputChannels { get; } | |||||
| /// <summary> Gets the milliseconds per frame. </summary> | |||||
| public int FrameLength { get; } | |||||
| /// <summary> Gets the number of samples per frame. </summary> | |||||
| public int SamplesPerFrame { get; } | |||||
| /// <summary> Gets the bytes per frame. </summary> | |||||
| public int FrameSize { get; } | |||||
| /// <summary> Gets the bytes per sample. </summary> | |||||
| public int SampleSize { get; } | |||||
| protected OpusConverter(int samplingRate, int channels, int frameLength) | |||||
| { | |||||
| if (samplingRate != 8000 && samplingRate != 12000 && | |||||
| samplingRate != 16000 && samplingRate != 24000 && | |||||
| samplingRate != 48000) | |||||
| throw new ArgumentOutOfRangeException(nameof(samplingRate)); | |||||
| if (channels != 1 && channels != 2) | |||||
| throw new ArgumentOutOfRangeException(nameof(channels)); | |||||
| InputSamplingRate = samplingRate; | |||||
| InputChannels = channels; | |||||
| FrameLength = frameLength; | |||||
| SampleSize = (BitsPerSample / 8) * channels; | |||||
| SamplesPerFrame = samplingRate / 1000 * FrameLength; | |||||
| FrameSize = SamplesPerFrame * SampleSize; | |||||
| } | |||||
| #region IDisposable Support | |||||
| private bool disposedValue = false; // To detect redundant calls | |||||
| protected virtual void Dispose(bool disposing) | |||||
| { | |||||
| if (!disposedValue) | |||||
| disposedValue = true; | |||||
| } | |||||
| ~OpusConverter() { | |||||
| Dispose(false); | |||||
| } | |||||
| public void Dispose() | |||||
| { | |||||
| Dispose(true); | |||||
| GC.SuppressFinalize(this); | |||||
| } | |||||
| #endregion | |||||
| } | |||||
| } | |||||
| @@ -1,64 +1,16 @@ | |||||
| using System; | using System; | ||||
| using System.Runtime.InteropServices; | |||||
| using System.Security; | |||||
| namespace Discord.Audio.Opus | namespace Discord.Audio.Opus | ||||
| { | { | ||||
| /// <summary> Opus codec wrapper. </summary> | /// <summary> Opus codec wrapper. </summary> | ||||
| internal class OpusDecoder : IDisposable | |||||
| internal class OpusDecoder : OpusConverter | |||||
| { | { | ||||
| #if NET45 | |||||
| [SuppressUnmanagedCodeSecurity] | |||||
| #endif | |||||
| private unsafe static class UnsafeNativeMethods | |||||
| { | |||||
| [DllImport("opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern IntPtr CreateDecoder(int Fs, int channels, out OpusError error); | |||||
| [DllImport("opus", EntryPoint = "opus_decoder_destroy", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern void DestroyDecoder(IntPtr decoder); | |||||
| [DllImport("opus", EntryPoint = "opus_decode", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern int Decode(IntPtr st, byte* data, int len, byte[] pcm, int frame_size, int decode_fec); | |||||
| } | |||||
| private readonly IntPtr _ptr; | |||||
| /// <summary> Gets the bit rate of the encoder. </summary> | |||||
| public const int BitRate = 16; | |||||
| /// <summary> Gets the input sampling rate of the encoder. </summary> | |||||
| public int InputSamplingRate { get; private set; } | |||||
| /// <summary> Gets the number of channels of the encoder. </summary> | |||||
| public int InputChannels { get; private set; } | |||||
| /// <summary> Gets the milliseconds per frame. </summary> | |||||
| public int FrameLength { get; private set; } | |||||
| /// <summary> Gets the number of samples per frame. </summary> | |||||
| public int SamplesPerFrame { get; private set; } | |||||
| /// <summary> Gets the bytes per sample. </summary> | |||||
| public int SampleSize { get; private set; } | |||||
| /// <summary> Gets the bytes per frame. </summary> | |||||
| public int FrameSize { get; private set; } | |||||
| /// <summary> Creates a new Opus decoder. </summary> | |||||
| /// <param name="samplingRate">Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000.</param> | |||||
| /// <param name="channels">Number of channels (1 or 2) in input signal.</param> | |||||
| /// <param name="frameLength">Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60</param> | |||||
| /// <param name="application">Coding mode.</param> | |||||
| /// <returns>A new <c>OpusEncoder</c></returns> | |||||
| public OpusDecoder(int samplingRate, int channels, int frameLength) | |||||
| /// <summary> Creates a new Opus decoder. </summary> | |||||
| /// <param name="samplingRate">Sampling rate of the input PCM (in Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000</param> | |||||
| /// <param name="frameLength">Length, in milliseconds, of each frame. Supported Values: 2.5, 5, 10, 20, 40, or 60</param> | |||||
| public OpusDecoder(int samplingRate, int channels, int frameLength) | |||||
| : base(samplingRate, channels, frameLength) | |||||
| { | { | ||||
| if (samplingRate != 8000 && samplingRate != 12000 && | |||||
| samplingRate != 16000 && samplingRate != 24000 && | |||||
| samplingRate != 48000) | |||||
| throw new ArgumentOutOfRangeException(nameof(samplingRate)); | |||||
| if (channels != 1 && channels != 2) | |||||
| throw new ArgumentOutOfRangeException(nameof(channels)); | |||||
| InputSamplingRate = samplingRate; | |||||
| InputChannels = channels; | |||||
| FrameLength = frameLength; | |||||
| SampleSize = (BitRate / 8) * channels; | |||||
| SamplesPerFrame = samplingRate / 1000 * FrameLength; | |||||
| FrameSize = SamplesPerFrame * SampleSize; | |||||
| OpusError error; | OpusError error; | ||||
| _ptr = UnsafeNativeMethods.CreateDecoder(samplingRate, channels, out error); | _ptr = UnsafeNativeMethods.CreateDecoder(samplingRate, channels, out error); | ||||
| if (error != OpusError.OK) | if (error != OpusError.OK) | ||||
| @@ -69,39 +21,24 @@ namespace Discord.Audio.Opus | |||||
| /// <param name="input">PCM samples to decode.</param> | /// <param name="input">PCM samples to decode.</param> | ||||
| /// <param name="inputOffset">Offset of the frame in input.</param> | /// <param name="inputOffset">Offset of the frame in input.</param> | ||||
| /// <param name="output">Buffer to store the decoded frame.</param> | /// <param name="output">Buffer to store the decoded frame.</param> | ||||
| /// <returns>Length of the frame contained in output.</returns> | |||||
| public unsafe int DecodeFrame(byte[] input, int inputOffset, byte[] output) | |||||
| { | |||||
| if (disposed) | |||||
| throw new ObjectDisposedException(nameof(OpusDecoder)); | |||||
| public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output) | |||||
| { | |||||
| int result = 0; | int result = 0; | ||||
| fixed (byte* inPtr = input) | fixed (byte* inPtr = input) | ||||
| result = UnsafeNativeMethods.Decode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length, 0); | |||||
| result = UnsafeNativeMethods.Decode(_ptr, inPtr + inputOffset, inputCount, output, SamplesPerFrame, 0); | |||||
| if (result < 0) | if (result < 0) | ||||
| throw new Exception("Decoding failed: " + ((OpusError)result).ToString()); | |||||
| throw new Exception(((OpusError)result).ToString()); | |||||
| return result; | return result; | ||||
| } | } | ||||
| #region IDisposable | |||||
| private bool disposed; | |||||
| public void Dispose() | |||||
| { | |||||
| if (disposed) | |||||
| return; | |||||
| GC.SuppressFinalize(this); | |||||
| if (_ptr != IntPtr.Zero) | |||||
| UnsafeNativeMethods.DestroyDecoder(_ptr); | |||||
| disposed = true; | |||||
| } | |||||
| ~OpusDecoder() | |||||
| { | |||||
| Dispose(); | |||||
| } | |||||
| #endregion | |||||
| protected override void Dispose(bool disposing) | |||||
| { | |||||
| if (_ptr != IntPtr.Zero) | |||||
| { | |||||
| UnsafeNativeMethods.DestroyDecoder(_ptr); | |||||
| _ptr = IntPtr.Zero; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,76 +1,31 @@ | |||||
| using System; | using System; | ||||
| using System.Runtime.InteropServices; | |||||
| using System.Security; | |||||
| namespace Discord.Audio.Opus | namespace Discord.Audio.Opus | ||||
| { | { | ||||
| /// <summary> Opus codec wrapper. </summary> | /// <summary> Opus codec wrapper. </summary> | ||||
| internal class OpusEncoder : IDisposable | |||||
| internal class OpusEncoder : OpusConverter | |||||
| { | { | ||||
| #if NET45 | |||||
| [SuppressUnmanagedCodeSecurity] | |||||
| #endif | |||||
| private unsafe static class UnsafeNativeMethods | |||||
| { | |||||
| [DllImport("opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out OpusError error); | |||||
| [DllImport("opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern void DestroyEncoder(IntPtr encoder); | |||||
| [DllImport("opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte[] data, int max_data_bytes); | |||||
| [DllImport("opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)] | |||||
| public static extern int EncoderCtl(IntPtr st, OpusCtl request, int value); | |||||
| } | |||||
| private readonly IntPtr _ptr; | |||||
| /// <summary> Gets the bit rate of the encoder. </summary> | |||||
| public const int BitsPerSample = 16; | |||||
| /// <summary> Gets the input sampling rate of the encoder. </summary> | |||||
| public int InputSamplingRate { get; } | |||||
| /// <summary> Gets the number of channels of the encoder. </summary> | |||||
| public int InputChannels { get; } | |||||
| /// <summary> Gets the milliseconds per frame. </summary> | |||||
| public int FrameLength { get; } | |||||
| /// <summary> Gets the number of samples per frame. </summary> | |||||
| public int SamplesPerFrame { get; } | |||||
| /// <summary> Gets the bytes per sample. </summary> | |||||
| public int SampleSize { get; } | |||||
| /// <summary> Gets the bytes per frame. </summary> | |||||
| public int FrameSize { get; } | |||||
| /// <summary> Gets the bit rate in kbit/s. </summary> | |||||
| public int? BitRate { get; } | |||||
| /// <summary> Gets the bit rate in kbit/s. </summary> | |||||
| public int? BitRate { get; } | |||||
| /// <summary> Gets the coding mode of the encoder. </summary> | /// <summary> Gets the coding mode of the encoder. </summary> | ||||
| public OpusApplication Application { get; } | public OpusApplication Application { get; } | ||||
| /// <summary> Creates a new Opus encoder. </summary> | |||||
| /// <param name="samplingRate">Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000.</param> | |||||
| /// <param name="channels">Number of channels (1 or 2) in input signal.</param> | |||||
| /// <param name="frameLength">Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60</param> | |||||
| /// <param name="bitrate">Bitrate (kbit/s) used for this encoder. Supported Values: 1-512. Null will use the recommended bitrate. </param> | |||||
| /// <param name="application">Coding mode.</param> | |||||
| /// <returns>A new <c>OpusEncoder</c></returns> | |||||
| public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application) | |||||
| /// <summary> Creates a new Opus encoder. </summary> | |||||
| /// <param name="samplingRate">Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000</param> | |||||
| /// <param name="channels">Number of channels in input signal. Supported Values: 1 or 2</param> | |||||
| /// <param name="frameLength">Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60</param> | |||||
| /// <param name="bitrate">Bitrate (kbit/s) used for this encoder. Supported Values: 1-512. Null will use the recommended bitrate. </param> | |||||
| /// <param name="application">Coding mode.</param> | |||||
| public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application) | |||||
| : base(samplingRate, channels, frameLength) | |||||
| { | { | ||||
| if (samplingRate != 8000 && samplingRate != 12000 && | |||||
| samplingRate != 16000 && samplingRate != 24000 && | |||||
| samplingRate != 48000) | |||||
| throw new ArgumentOutOfRangeException(nameof(samplingRate)); | |||||
| if (channels != 1 && channels != 2) | |||||
| throw new ArgumentOutOfRangeException(nameof(channels)); | |||||
| if (bitrate != null && (bitrate < 1 || bitrate > 512)) | if (bitrate != null && (bitrate < 1 || bitrate > 512)) | ||||
| throw new ArgumentOutOfRangeException(nameof(bitrate)); | throw new ArgumentOutOfRangeException(nameof(bitrate)); | ||||
| InputSamplingRate = samplingRate; | |||||
| InputChannels = channels; | |||||
| Application = application; | |||||
| FrameLength = frameLength; | |||||
| SampleSize = (BitsPerSample / 8) * channels; | |||||
| SamplesPerFrame = samplingRate / 1000 * FrameLength; | |||||
| FrameSize = SamplesPerFrame * SampleSize; | |||||
| BitRate = bitrate; | BitRate = bitrate; | ||||
| Application = application; | |||||
| OpusError error; | |||||
| OpusError error; | |||||
| _ptr = UnsafeNativeMethods.CreateEncoder(samplingRate, channels, (int)application, out error); | _ptr = UnsafeNativeMethods.CreateEncoder(samplingRate, channels, (int)application, out error); | ||||
| if (error != OpusError.OK) | if (error != OpusError.OK) | ||||
| throw new InvalidOperationException($"Error occured while creating encoder: {error}"); | throw new InvalidOperationException($"Error occured while creating encoder: {error}"); | ||||
| @@ -86,59 +41,39 @@ namespace Discord.Audio.Opus | |||||
| /// <param name="output">Buffer to store the encoded frame.</param> | /// <param name="output">Buffer to store the encoded frame.</param> | ||||
| /// <returns>Length of the frame contained in outputBuffer.</returns> | /// <returns>Length of the frame contained in outputBuffer.</returns> | ||||
| public unsafe int EncodeFrame(byte[] input, int inputOffset, byte[] output) | public unsafe int EncodeFrame(byte[] input, int inputOffset, byte[] output) | ||||
| { | |||||
| if (disposed) | |||||
| throw new ObjectDisposedException(nameof(OpusEncoder)); | |||||
| { | |||||
| int result = 0; | int result = 0; | ||||
| fixed (byte* inPtr = input) | fixed (byte* inPtr = input) | ||||
| result = UnsafeNativeMethods.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length); | result = UnsafeNativeMethods.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length); | ||||
| if (result < 0) | if (result < 0) | ||||
| throw new Exception("Encoding failed: " + ((OpusError)result).ToString()); | |||||
| throw new Exception(((OpusError)result).ToString()); | |||||
| return result; | return result; | ||||
| } | } | ||||
| /// <summary> Gets or sets whether Forward Error Correction is enabled. </summary> | /// <summary> Gets or sets whether Forward Error Correction is enabled. </summary> | ||||
| public void SetForwardErrorCorrection(bool value) | public void SetForwardErrorCorrection(bool value) | ||||
| { | { | ||||
| if (disposed) | |||||
| throw new ObjectDisposedException(nameof(OpusEncoder)); | |||||
| var result = UnsafeNativeMethods.EncoderCtl(_ptr, OpusCtl.SetInbandFECRequest, value ? 1 : 0); | |||||
| var result = UnsafeNativeMethods.EncoderCtl(_ptr, Ctl.SetInbandFECRequest, value ? 1 : 0); | |||||
| if (result < 0) | if (result < 0) | ||||
| throw new Exception("Encoder error: " + ((OpusError)result).ToString()); | |||||
| throw new Exception(((OpusError)result).ToString()); | |||||
| } | } | ||||
| /// <summary> Gets or sets whether Forward Error Correction is enabled. </summary> | /// <summary> Gets or sets whether Forward Error Correction is enabled. </summary> | ||||
| public void SetBitrate(int value) | public void SetBitrate(int value) | ||||
| { | { | ||||
| if (disposed) | |||||
| throw new ObjectDisposedException(nameof(OpusEncoder)); | |||||
| var result = UnsafeNativeMethods.EncoderCtl(_ptr, OpusCtl.SetBitrateRequest, value * 1000); | |||||
| var result = UnsafeNativeMethods.EncoderCtl(_ptr, Ctl.SetBitrateRequest, value * 1000); | |||||
| if (result < 0) | if (result < 0) | ||||
| throw new Exception("Encoder error: " + ((OpusError)result).ToString()); | |||||
| throw new Exception(((OpusError)result).ToString()); | |||||
| } | } | ||||
| #region IDisposable | |||||
| private bool disposed; | |||||
| public void Dispose() | |||||
| { | |||||
| if (disposed) | |||||
| return; | |||||
| GC.SuppressFinalize(this); | |||||
| if (_ptr != IntPtr.Zero) | |||||
| UnsafeNativeMethods.DestroyEncoder(_ptr); | |||||
| disposed = true; | |||||
| } | |||||
| ~OpusEncoder() | |||||
| { | |||||
| Dispose(); | |||||
| } | |||||
| #endregion | |||||
| protected override void Dispose(bool disposing) | |||||
| { | |||||
| if (_ptr != IntPtr.Zero) | |||||
| { | |||||
| UnsafeNativeMethods.DestroyEncoder(_ptr); | |||||
| _ptr = IntPtr.Zero; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +0,0 @@ | |||||
| using System.Runtime.InteropServices; | |||||
| namespace Discord.Audio | |||||
| { | |||||
| } | |||||
| @@ -2,7 +2,12 @@ | |||||
| { | { | ||||
| public static class CommandExtensions | public static class CommandExtensions | ||||
| { | { | ||||
| public static CommandService Commands(this DiscordClient client, bool required = true) | |||||
| public static DiscordClient UsingCommands(this DiscordClient client, CommandServiceConfig config = null) | |||||
| { | |||||
| client.Services.Add(new CommandService(config)); | |||||
| return client; | |||||
| } | |||||
| public static CommandService Commands(this DiscordClient client, bool required = true) | |||||
| => client.Services.Get<CommandService>(required); | => client.Services.Get<CommandService>(required); | ||||
| } | } | ||||
| } | } | ||||
| @@ -21,13 +21,13 @@ namespace Discord.Commands | |||||
| //Groups store all commands by their module, used for more informative help | //Groups store all commands by their module, used for more informative help | ||||
| internal IEnumerable<CommandMap> Categories => _categories.Values; | internal IEnumerable<CommandMap> Categories => _categories.Values; | ||||
| public event EventHandler<CommandEventArgs> Command = delegate { }; | |||||
| public event EventHandler<CommandErrorEventArgs> CommandError = delegate { }; | |||||
| public event EventHandler<CommandEventArgs> CommandExecuted = delegate { }; | |||||
| public event EventHandler<CommandErrorEventArgs> CommandErrored = delegate { }; | |||||
| private void OnCommand(CommandEventArgs args) | private void OnCommand(CommandEventArgs args) | ||||
| => Command(this, args); | |||||
| => CommandExecuted(this, args); | |||||
| private void OnCommandError(CommandErrorType errorType, CommandEventArgs args, Exception ex = null) | private void OnCommandError(CommandErrorType errorType, CommandEventArgs args, Exception ex = null) | ||||
| => CommandError(this, new CommandErrorEventArgs(errorType, args, ex)); | |||||
| => CommandErrored(this, new CommandErrorEventArgs(errorType, args, ex)); | |||||
| public CommandService(CommandServiceConfig config) | public CommandService(CommandServiceConfig config) | ||||
| { | { | ||||
| @@ -1,8 +1,16 @@ | |||||
| namespace Discord.Commands.Permissions.Levels | |||||
| using System; | |||||
| namespace Discord.Commands.Permissions.Levels | |||||
| { | { | ||||
| public static class PermissionLevelExtensions | public static class PermissionLevelExtensions | ||||
| { | { | ||||
| public static CommandBuilder MinPermissions(this CommandBuilder builder, int minPermissions) | |||||
| public static DiscordClient UsingPermissionLevels(this DiscordClient client, Func<User, Channel, int> permissionResolver) | |||||
| { | |||||
| client.Services.Add(new PermissionLevelService(permissionResolver)); | |||||
| return client; | |||||
| } | |||||
| public static CommandBuilder MinPermissions(this CommandBuilder builder, int minPermissions) | |||||
| { | { | ||||
| builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); | builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); | ||||
| return builder; | return builder; | ||||
| @@ -1,8 +1,14 @@ | |||||
| namespace Discord.Commands.Permissions.Userlist | namespace Discord.Commands.Permissions.Userlist | ||||
| { | { | ||||
| public static class BlacklistExtensions | public static class BlacklistExtensions | ||||
| { | |||||
| public static CommandBuilder UseGlobalBlacklist(this CommandBuilder builder) | |||||
| { | |||||
| public static DiscordClient UsingGlobalBlacklist(this DiscordClient client, params ulong[] initialUserIds) | |||||
| { | |||||
| client.Services.Add(new BlacklistService(initialUserIds)); | |||||
| return client; | |||||
| } | |||||
| public static CommandBuilder UseGlobalBlacklist(this CommandBuilder builder) | |||||
| { | { | ||||
| builder.AddCheck(new BlacklistChecker(builder.Service.Client)); | builder.AddCheck(new BlacklistChecker(builder.Service.Client)); | ||||
| return builder; | return builder; | ||||
| @@ -1,17 +1,13 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| { | { | ||||
| public class BlacklistService : UserlistService | public class BlacklistService : UserlistService | ||||
| { | { | ||||
| public BlacklistService(IEnumerable<ulong> initialList = null) | |||||
| public BlacklistService(params ulong[] initialList) | |||||
| : base(initialList) | : base(initialList) | ||||
| { | { | ||||
| } | } | ||||
| public bool CanRun(User user) | public bool CanRun(User user) | ||||
| { | |||||
| return !_userList.ContainsKey(user.Id); | |||||
| } | |||||
| => !_userList.ContainsKey(user.Id); | |||||
| } | } | ||||
| } | } | ||||
| @@ -13,12 +13,11 @@ namespace Discord.Commands.Permissions.Userlist | |||||
| public DiscordClient Client => _client; | public DiscordClient Client => _client; | ||||
| public IEnumerable<ulong> UserIds => _userList.Select(x => x.Key); | public IEnumerable<ulong> UserIds => _userList.Select(x => x.Key); | ||||
| public UserlistService(IEnumerable<ulong> initialList = null) | |||||
| public UserlistService(params ulong[] initialUserIds) | |||||
| { | { | ||||
| if (initialList != null) | |||||
| _userList = new ConcurrentDictionary<ulong, bool>(initialList.Select(x => new KeyValuePair<ulong, bool>(x, true))); | |||||
| else | |||||
| _userList = new ConcurrentDictionary<ulong, bool>(); | |||||
| _userList = new ConcurrentDictionary<ulong, bool>(); | |||||
| for (int i = 0; i < initialUserIds.Length; i++) | |||||
| _userList.TryAdd(initialUserIds[i], true); | |||||
| } | } | ||||
| public void Add(User user) | public void Add(User user) | ||||
| @@ -31,6 +30,7 @@ namespace Discord.Commands.Permissions.Userlist | |||||
| { | { | ||||
| _userList[userId] = true; | _userList[userId] = true; | ||||
| } | } | ||||
| public bool Remove(User user) | public bool Remove(User user) | ||||
| { | { | ||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | if (user == null) throw new ArgumentNullException(nameof(user)); | ||||
| @@ -44,7 +44,7 @@ namespace Discord.Commands.Permissions.Userlist | |||||
| return _userList.TryRemove(userId, out ignored); | return _userList.TryRemove(userId, out ignored); | ||||
| } | } | ||||
| public void Install(DiscordClient client) | |||||
| void IService.Install(DiscordClient client) | |||||
| { | { | ||||
| _client = client; | _client = client; | ||||
| } | } | ||||
| @@ -1,8 +1,14 @@ | |||||
| namespace Discord.Commands.Permissions.Userlist | namespace Discord.Commands.Permissions.Userlist | ||||
| { | { | ||||
| public static class WhitelistExtensions | public static class WhitelistExtensions | ||||
| { | |||||
| public static CommandBuilder UseGlobalWhitelist(this CommandBuilder builder) | |||||
| { | |||||
| public static DiscordClient UsingGlobalWhitelist(this DiscordClient client, params ulong[] initialUserIds) | |||||
| { | |||||
| client.Services.Add(new WhitelistService(initialUserIds)); | |||||
| return client; | |||||
| } | |||||
| public static CommandBuilder UseGlobalWhitelist(this CommandBuilder builder) | |||||
| { | { | ||||
| builder.AddCheck(new WhitelistChecker(builder.Service.Client)); | builder.AddCheck(new WhitelistChecker(builder.Service.Client)); | ||||
| return builder; | return builder; | ||||
| @@ -1,17 +1,13 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| { | { | ||||
| public class WhitelistService : UserlistService | public class WhitelistService : UserlistService | ||||
| { | { | ||||
| public WhitelistService(IEnumerable<ulong> initialList = null) | |||||
| public WhitelistService(params ulong[] initialList) | |||||
| : base(initialList) | : base(initialList) | ||||
| { | { | ||||
| } | } | ||||
| public bool CanRun(User user) | public bool CanRun(User user) | ||||
| { | |||||
| return _userList.ContainsKey(user.Id); | |||||
| } | |||||
| => _userList.ContainsKey(user.Id); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,8 +1,8 @@ | |||||
| namespace Discord.Commands.Permissions.Visibility | namespace Discord.Commands.Permissions.Visibility | ||||
| { | { | ||||
| public static class PrivateExtensions | public static class PrivateExtensions | ||||
| { | |||||
| public static CommandBuilder PrivateOnly(this CommandBuilder builder) | |||||
| { | |||||
| public static CommandBuilder PrivateOnly(this CommandBuilder builder) | |||||
| { | { | ||||
| builder.AddCheck(new PrivateChecker()); | builder.AddCheck(new PrivateChecker()); | ||||
| return builder; | return builder; | ||||
| @@ -50,15 +50,15 @@ | |||||
| <Compile Include="..\Discord.Net.Modules\ModuleExtensions.cs"> | <Compile Include="..\Discord.Net.Modules\ModuleExtensions.cs"> | ||||
| <Link>ModuleExtensions.cs</Link> | <Link>ModuleExtensions.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Modules\ModuleFilter.cs"> | |||||
| <Link>ModuleFilter.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net.Modules\ModuleManager.cs"> | <Compile Include="..\Discord.Net.Modules\ModuleManager.cs"> | ||||
| <Link>ModuleManager.cs</Link> | <Link>ModuleManager.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Modules\ModuleService.cs"> | <Compile Include="..\Discord.Net.Modules\ModuleService.cs"> | ||||
| <Link>ModuleService.cs</Link> | <Link>ModuleService.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Modules\ModuleType.cs"> | |||||
| <Link>ModuleType.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| @@ -6,7 +6,7 @@ namespace Discord.Modules | |||||
| public class ModuleChecker : IPermissionChecker | public class ModuleChecker : IPermissionChecker | ||||
| { | { | ||||
| private readonly ModuleManager _manager; | private readonly ModuleManager _manager; | ||||
| private readonly FilterType _filterType; | |||||
| private readonly ModuleFilter _filterType; | |||||
| internal ModuleChecker(ModuleManager manager) | internal ModuleChecker(ModuleManager manager) | ||||
| { | { | ||||
| @@ -16,7 +16,7 @@ namespace Discord.Modules | |||||
| public bool CanRun(Command command, User user, Channel channel, out string error) | public bool CanRun(Command command, User user, Channel channel, out string error) | ||||
| { | { | ||||
| if (_filterType == FilterType.Unrestricted || _filterType == FilterType.AllowPrivate || _manager.HasChannel(channel)) | |||||
| if (_filterType == ModuleFilter.None || _filterType == ModuleFilter.AlwaysAllowPrivate || _manager.HasChannel(channel)) | |||||
| { | { | ||||
| error = null; | error = null; | ||||
| return true; | return true; | ||||
| @@ -2,7 +2,25 @@ | |||||
| { | { | ||||
| public static class ModuleExtensions | public static class ModuleExtensions | ||||
| { | { | ||||
| public static ModuleService Modules(this DiscordClient client, bool required = true) | |||||
| public static DiscordClient UsingModules(this DiscordClient client) | |||||
| { | |||||
| client.Services.Add(new ModuleService()); | |||||
| return client; | |||||
| } | |||||
| public static DiscordClient AddModule<T>(this DiscordClient client, T instance, string name = null, ModuleFilter filter = ModuleFilter.None) | |||||
| where T : class, IModule | |||||
| { | |||||
| client.Modules().Add(instance, name ?? nameof(T), filter); | |||||
| return client; | |||||
| } | |||||
| public static DiscordClient AddModule<T>(this DiscordClient client, string name = null, ModuleFilter filter = ModuleFilter.None) | |||||
| where T : class, IModule, new() | |||||
| { | |||||
| client.Modules().Add(new T(), name ?? nameof(T), filter); | |||||
| return client; | |||||
| } | |||||
| public static ModuleService Modules(this DiscordClient client, bool required = true) | |||||
| => client.Services.Get<ModuleService>(required); | => client.Services.Get<ModuleService>(required); | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,15 +3,15 @@ | |||||
| namespace Discord.Modules | namespace Discord.Modules | ||||
| { | { | ||||
| [Flags] | [Flags] | ||||
| public enum FilterType | |||||
| public enum ModuleFilter | |||||
| { | { | ||||
| /// <summary> Disables the event and command filtesr. </summary> | |||||
| Unrestricted = 0x0, | |||||
| /// <summary> Disables the event and command filters. </summary> | |||||
| None = 0x0, | |||||
| /// <summary> Uses the server whitelist to filter events and commands. </summary> | /// <summary> Uses the server whitelist to filter events and commands. </summary> | ||||
| ServerWhitelist = 0x1, | ServerWhitelist = 0x1, | ||||
| /// <summary> Uses the channel whitelist to filter events and commands. </summary> | /// <summary> Uses the channel whitelist to filter events and commands. </summary> | ||||
| ChannelWhitelist = 0x2, | ChannelWhitelist = 0x2, | ||||
| /// <summary> Enables this module in all private messages. </summary> | /// <summary> Enables this module in all private messages. </summary> | ||||
| AllowPrivate = 0x4 | |||||
| AlwaysAllowPrivate = 0x4 | |||||
| } | } | ||||
| } | } | ||||
| @@ -41,10 +41,7 @@ namespace Discord.Modules | |||||
| public event EventHandler<MessageEventArgs> MessageDeleted = delegate { }; | public event EventHandler<MessageEventArgs> MessageDeleted = delegate { }; | ||||
| public event EventHandler<MessageEventArgs> MessageUpdated = delegate { }; | public event EventHandler<MessageEventArgs> MessageUpdated = delegate { }; | ||||
| public event EventHandler<MessageEventArgs> MessageReadRemotely = delegate { }; | public event EventHandler<MessageEventArgs> MessageReadRemotely = delegate { }; | ||||
| private readonly DiscordClient _client; | |||||
| private readonly string _name, _id; | |||||
| private readonly FilterType _filterType; | |||||
| 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, Channel> _enabledChannels; | ||||
| @@ -55,12 +52,12 @@ namespace Discord.Modules | |||||
| public IModule Instance { get; } | public IModule Instance { get; } | ||||
| public string Name { get; } | public string Name { get; } | ||||
| public string Id { get; } | public string Id { get; } | ||||
| public FilterType 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<Channel> EnabledChannels => _enabledChannels.Select(x => x.Value); | ||||
| internal ModuleManager(DiscordClient client, IModule instance, string name, FilterType filterType) | |||||
| internal ModuleManager(DiscordClient client, IModule instance, string name, ModuleFilter filterType) | |||||
| { | { | ||||
| Client = client; | Client = client; | ||||
| Instance = instance; | Instance = instance; | ||||
| @@ -70,10 +67,10 @@ namespace Discord.Modules | |||||
| Id = name.ToLowerInvariant(); | Id = name.ToLowerInvariant(); | ||||
| _lock = new AsyncLock(); | _lock = new AsyncLock(); | ||||
| _allowAll = filterType == FilterType.Unrestricted; | |||||
| _useServerWhitelist = filterType.HasFlag(FilterType.ServerWhitelist); | |||||
| _useChannelWhitelist = filterType.HasFlag(FilterType.ChannelWhitelist); | |||||
| _allowPrivate = filterType.HasFlag(FilterType.AllowPrivate); | |||||
| _allowAll = filterType == ModuleFilter.None; | |||||
| _useServerWhitelist = filterType.HasFlag(ModuleFilter.ServerWhitelist); | |||||
| _useChannelWhitelist = filterType.HasFlag(ModuleFilter.ChannelWhitelist); | |||||
| _allowPrivate = filterType.HasFlag(ModuleFilter.AlwaysAllowPrivate); | |||||
| _enabledServers = new ConcurrentDictionary<ulong, Server>(); | _enabledServers = new ConcurrentDictionary<ulong, Server>(); | ||||
| _enabledChannels = new ConcurrentDictionary<ulong, Channel>(); | _enabledChannels = new ConcurrentDictionary<ulong, Channel>(); | ||||
| @@ -115,10 +112,10 @@ namespace Discord.Modules | |||||
| public void CreateCommands(string prefix, Action<CommandGroupBuilder> config) | public void CreateCommands(string prefix, Action<CommandGroupBuilder> config) | ||||
| { | { | ||||
| var commandService = _client.Commands(true); | |||||
| var commandService = Client.Commands(true); | |||||
| commandService.CreateGroup(prefix, x => | commandService.CreateGroup(prefix, x => | ||||
| { | { | ||||
| x.Category(_name); | |||||
| x.Category(Name); | |||||
| x.AddCheck(new ModuleChecker(this)); | x.AddCheck(new ModuleChecker(this)); | ||||
| config(x); | config(x); | ||||
| }); | }); | ||||
| @@ -20,7 +20,7 @@ namespace Discord.Modules | |||||
| Client = client; | Client = client; | ||||
| } | } | ||||
| public void Install<T>(T module, string name, FilterType type) | |||||
| public T Add<T>(T module, string name, ModuleFilter type) | |||||
| where T : class, IModule | where T : class, IModule | ||||
| { | { | ||||
| if (module == null) throw new ArgumentNullException(nameof(module)); | if (module == null) throw new ArgumentNullException(nameof(module)); | ||||
| @@ -33,6 +33,7 @@ namespace Discord.Modules | |||||
| var manager = new ModuleManager(Client, module, name, type); | var manager = new ModuleManager(Client, module, name, type); | ||||
| _modules.Add(module, manager); | _modules.Add(module, manager); | ||||
| module.Install(manager); | module.Install(manager); | ||||
| return module; | |||||
| } | } | ||||
| public ModuleManager GetManager(IModule module) | public ModuleManager GetManager(IModule module) | ||||
| @@ -8,6 +8,22 @@ using System.Runtime.CompilerServices; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public static class DiscordClientExtensions | |||||
| { | |||||
| public static DiscordClient AddService<T>(this DiscordClient client, T instance) | |||||
| where T : class, IService | |||||
| { | |||||
| client.Services.Add(instance); | |||||
| return client; | |||||
| } | |||||
| public static DiscordClient AddService<T>(this DiscordClient client) | |||||
| where T : class, IService, new() | |||||
| { | |||||
| client.Services.Add(new T()); | |||||
| return client; | |||||
| } | |||||
| } | |||||
| internal static class InternalExtensions | internal static class InternalExtensions | ||||
| { | { | ||||
| internal static readonly IFormatProvider _format = CultureInfo.InvariantCulture; | internal static readonly IFormatProvider _format = CultureInfo.InvariantCulture; | ||||