Compare commits
No commits in common. "a10e513ef2233789cb111bd4a2c080baeaccce1b" and "776e2157f59a777f6147b1b25bcee48cc3f54741" have entirely different histories.
a10e513ef2
...
776e2157f5
1436 changed files with 53 additions and 125561 deletions
63
.gitattributes
vendored
63
.gitattributes
vendored
|
@ -1,63 +0,0 @@
|
|||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"editor.rulers": [
|
||||
120
|
||||
],
|
||||
"editor.insertSpaces": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/obj": true,
|
||||
"**/bin": true,
|
||||
"samples/": true,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Cacheable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Downloader/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Emoji/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=libsodium/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=NSFW/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ratelimit/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=seeked/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=sharded/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Spotify/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unban/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uncache/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=webhook/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=webhooks/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=webhook_0027s/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ZWSP/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
|
@ -1,3 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Discord.Net.Tests")]
|
|
@ -1,55 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace RegexAnalyzer
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class ConfigureAwaitAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
public const string DiagnosticId = "ConfigureAwait";
|
||||
internal const string Title = "ConfigureAwait was not specified";
|
||||
internal const string MessageFormat = "ConfigureAwait error {0}";
|
||||
internal const string Description = "ConfigureAwait(false) should be used.";
|
||||
internal const string Category = "Usage";
|
||||
internal static DiagnosticDescriptor Rule =
|
||||
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat,
|
||||
Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression);
|
||||
}
|
||||
|
||||
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
/*var invocationExpr = (InvocationExpressionSyntax)context.Node;
|
||||
var memberAccessExpr = invocationExpr.Expression as MemberAccessExpressionSyntax;
|
||||
if (memberAccessExpr?.Name.ToString() != "Match") return;
|
||||
var memberSymbol = context.SemanticModel.GetSymbolInfo(memberAccessExpr).Symbol as IMethodSymbol;
|
||||
if (!memberSymbol?.ToString().StartsWith("System.Text.RegularExpressions.Regex.Match") ?? true) return;
|
||||
var argumentList = invocationExpr.ArgumentList as ArgumentListSyntax;
|
||||
if ((argumentList?.Arguments.Count ?? 0) < 2) return;
|
||||
var regexLiteral = argumentList.Arguments[1].Expression as LiteralExpressionSyntax;
|
||||
if (regexLiteral == null) return;
|
||||
var regexOpt = context.SemanticModel.GetConstantValue(regexLiteral);
|
||||
if (!regexOpt.HasValue) return;
|
||||
var regex = regexOpt.Value as string;
|
||||
if (regex == null) return;
|
||||
try
|
||||
{
|
||||
System.Text.RegularExpressions.Regex.Match("", regex);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
var diagnostic = Diagnostic.Create(Rule, regexLiteral.GetLocation(), e.Message);
|
||||
context.ReportDiagnostic(diagnostic);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../../Discord.Net.targets" />
|
||||
<PropertyGroup>
|
||||
<AssemblyName>Discord.Net.Analyzers</AssemblyName>
|
||||
<RootNamespace>Discord.Analyzers</RootNamespace>
|
||||
<Description>A Discord.Net extension adding compile-time analysis.</Description>
|
||||
<TargetFrameworks>netstandard1.3</TargetFrameworks>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win81</PackageTargetFallback>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis" Version="2.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,278 +0,0 @@
|
|||
using Discord.Overrides;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an override that can be loaded.
|
||||
/// </summary>
|
||||
public sealed class Override
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the ID of the override.
|
||||
/// </summary>
|
||||
public Guid Id { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the override.
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the override.
|
||||
/// </summary>
|
||||
public string Description { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date this override was created.
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedAt { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date the override was last modified.
|
||||
/// </summary>
|
||||
public DateTimeOffset LastUpdated { get; internal set; }
|
||||
|
||||
internal static Override FromJson(string json)
|
||||
{
|
||||
var result = new Override();
|
||||
|
||||
using (var textReader = new StringReader(json))
|
||||
using (var reader = new JsonTextReader(textReader))
|
||||
{
|
||||
var obj = JObject.ReadFrom(reader);
|
||||
result.Id = obj["id"].ToObject<Guid>();
|
||||
result.Name = obj["name"].ToObject<string>();
|
||||
result.Description = obj["description"].ToObject<string>();
|
||||
result.CreatedAt = obj["created_at"].ToObject<DateTimeOffset>();
|
||||
result.LastUpdated = obj["last_updated"].ToObject<DateTimeOffset>();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a loaded override instance.
|
||||
/// </summary>
|
||||
public sealed class LoadedOverride
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the assembly containing the overrides definition.
|
||||
/// </summary>
|
||||
public Assembly Assembly { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of the override.
|
||||
/// </summary>
|
||||
public IOverride Instance { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the overrides type.
|
||||
/// </summary>
|
||||
public Type Type { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class BuildOverrides
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when an override logs a message.
|
||||
/// </summary>
|
||||
public static event Func<Override, string, Task> Log
|
||||
{
|
||||
add => _logEvents.Add(value);
|
||||
remove => _logEvents.Remove(value);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only dictionary containing the currently loaded overrides.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<Override, IReadOnlyCollection<LoadedOverride>> LoadedOverrides
|
||||
=> _loadedOverrides.Select(x => new KeyValuePair<Override, IReadOnlyCollection<LoadedOverride>>(x.Key, x.Value)).ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
private static AssemblyLoadContext _overrideDomain;
|
||||
private static List<Func<Override, string, Task>> _logEvents = new();
|
||||
private static ConcurrentDictionary<Override, List<LoadedOverride>> _loadedOverrides = new ConcurrentDictionary<Override, List<LoadedOverride>>();
|
||||
|
||||
private const string ApiUrl = "https://overrides.discordnet.dev";
|
||||
|
||||
static BuildOverrides()
|
||||
{
|
||||
_overrideDomain = new AssemblyLoadContext("Discord.Net.Overrides.Runtime");
|
||||
|
||||
_overrideDomain.Resolving += _overrideDomain_Resolving;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets details about a specific override.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Note:</b> This method does not load an override, it simply retrieves the info about it.
|
||||
/// </remarks>
|
||||
/// <param name="name">The name of the override to get.</param>
|
||||
/// <returns>
|
||||
/// A task representing the asynchronous get operation. The tasks result is an <see cref="Override"/>
|
||||
/// if it exists; otherwise <see langword="null"/>.
|
||||
/// </returns>
|
||||
public static async Task<Override> GetOverrideAsync(string name)
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var result = await client.GetAsync($"{ApiUrl}/overrides/{name}");
|
||||
|
||||
if (result.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await result.Content.ReadAsStringAsync();
|
||||
|
||||
return Override.FromJson(content);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an override to the current Discord.Net instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The override initialization is non-blocking, any errors that occur within
|
||||
/// the overrides initialization procedure will be sent in the <see cref="Log"/> event.
|
||||
/// </remarks>
|
||||
/// <param name="name">The name of the override to add.</param>
|
||||
/// <returns>
|
||||
/// A task representing the asynchronous add operation. The tasks result is a boolean
|
||||
/// determining if the add operation was successful.
|
||||
/// </returns>
|
||||
public static async Task<bool> AddOverrideAsync(string name)
|
||||
{
|
||||
var ovrride = await GetOverrideAsync(name);
|
||||
|
||||
if (ovrride == null)
|
||||
return false;
|
||||
|
||||
return await AddOverrideAsync(ovrride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an override to the current Discord.Net instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The override initialization is non-blocking, any errors that occur within
|
||||
/// the overrides initialization procedure will be sent in the <see cref="Log"/> event.
|
||||
/// </remarks>
|
||||
/// <param name="ovrride">The override to add.</param>
|
||||
/// <returns>
|
||||
/// A task representing the asynchronous add operation. The tasks result is a boolean
|
||||
/// determining if the add operation was successful.
|
||||
/// </returns>
|
||||
public static async Task<bool> AddOverrideAsync(Override ovrride)
|
||||
{
|
||||
// download it
|
||||
var ms = new MemoryStream();
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var result = await client.GetAsync($"{ApiUrl}/overrides/download/{ovrride.Id}");
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
return false;
|
||||
|
||||
await (await result.Content.ReadAsStreamAsync()).CopyToAsync(ms);
|
||||
}
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
// load the assembly
|
||||
//var test = Assembly.Load(ms.ToArray());
|
||||
var asm = _overrideDomain.LoadFromStream(ms);
|
||||
|
||||
// find out IOverride
|
||||
var overrides = asm.GetTypes().Where(x => x.GetInterfaces().Any(x => x == typeof(IOverride)));
|
||||
|
||||
List<LoadedOverride> loaded = new();
|
||||
|
||||
var context = new OverrideContext((m) => HandleLog(ovrride, m), ovrride);
|
||||
|
||||
foreach (var ovr in overrides)
|
||||
{
|
||||
var inst = (IOverride)Activator.CreateInstance(ovr);
|
||||
|
||||
inst.RegisterPackageLookupHandler((s) =>
|
||||
{
|
||||
return GetDependencyAsync(ovrride.Id, s);
|
||||
});
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await inst.InitializeAsync(context);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
HandleLog(ovrride, $"Failed to initialize build override: {x}");
|
||||
}
|
||||
});
|
||||
|
||||
loaded.Add(new LoadedOverride()
|
||||
{
|
||||
Assembly = asm,
|
||||
Instance = inst,
|
||||
Type = ovr
|
||||
});
|
||||
}
|
||||
|
||||
return _loadedOverrides.AddOrUpdate(ovrride, loaded, (_, __) => loaded) != null;
|
||||
}
|
||||
|
||||
internal static void HandleLog(Override ovr, string msg)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
foreach (var item in _logEvents)
|
||||
{
|
||||
await item.Invoke(ovr, msg).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Assembly _overrideDomain_Resolving(AssemblyLoadContext arg1, AssemblyName arg2)
|
||||
{
|
||||
// resolve the override id
|
||||
var v = _loadedOverrides.FirstOrDefault(x => x.Value.Any(x => x.Assembly.FullName == arg1.Assemblies.First().FullName));
|
||||
|
||||
return GetDependencyAsync(v.Key.Id, $"{arg2}").GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static async Task<Assembly> GetDependencyAsync(Guid id, string name)
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var result = await client.PostAsync($"{ApiUrl}/overrides/{id}/dependency", new StringContent($"{{ \"info\": \"{name}\"}}", Encoding.UTF8, "application/json"));
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
throw new Exception("Failed to get dependency");
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
var innerStream = await result.Content.ReadAsStreamAsync();
|
||||
await innerStream.CopyToAsync(ms);
|
||||
ms.Position = 0;
|
||||
return _overrideDomain.LoadFromStream(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<AssemblyName>Discord.Net.BuildOverrides</AssemblyName>
|
||||
<RootNamespace>Discord.BuildOverrides</RootNamespace>
|
||||
<Description>A Discord.Net extension adding a way to add build overrides for testing.</Description>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net6.0;net5.0;</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net6.0;net5.0;</TargetFrameworks>
|
||||
<IsTrimmable>false</IsTrimmable>
|
||||
<IsAotCompatible>false</IsAotCompatible>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Overrides
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a generic build override for Discord.Net
|
||||
/// </summary>
|
||||
public interface IOverride
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the override.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is called by the <see cref="BuildOverrides"/> class
|
||||
/// and should not be called externally from it.
|
||||
/// </remarks>
|
||||
/// <param name="context">Context used by an override to initialize.</param>
|
||||
/// <returns>
|
||||
/// A task representing the asynchronous initialization operation.
|
||||
/// </returns>
|
||||
Task InitializeAsync(OverrideContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a callback to load a dependency for this override.
|
||||
/// </summary>
|
||||
/// <param name="func">The callback to load an external dependency.</param>
|
||||
void RegisterPackageLookupHandler(Func<string, Task<Assembly>> func);
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Overrides
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents context that's passed to an override in the initialization step.
|
||||
/// </summary>
|
||||
public sealed class OverrideContext
|
||||
{
|
||||
/// <summary>
|
||||
/// A callback used to log messages.
|
||||
/// </summary>
|
||||
public Action<string> Log { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The info about the override.
|
||||
/// </summary>
|
||||
public Override Info { get; private set; }
|
||||
|
||||
internal OverrideContext(Action<string> log, Override info)
|
||||
{
|
||||
Log = log;
|
||||
Info = info;
|
||||
}
|
||||
}
|
||||
}
|
2
experiment/Discord.Net.BuildOverrides/Program.cs
Normal file
2
experiment/Discord.Net.BuildOverrides/Program.cs
Normal file
|
@ -0,0 +1,2 @@
|
|||
// See https://aka.ms/new-console-template for more information
|
||||
Console.WriteLine("Hello, World!");
|
|
@ -1,20 +0,0 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using System;
|
||||
|
||||
namespace Discord.Relay
|
||||
{
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
public static void UseDiscordRelay(this IApplicationBuilder app, Action<RelayServer> configAction = null)
|
||||
{
|
||||
var server = new RelayServer(configAction);
|
||||
server.StartAsync();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.WebSockets.IsWebSocketRequest)
|
||||
await server.AcceptAsync(context);
|
||||
await next();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Discord.Net.Tests")]
|
|
@ -1,18 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../../Discord.Net.targets" />
|
||||
<PropertyGroup>
|
||||
<AssemblyName>Discord.Net.Relay</AssemblyName>
|
||||
<RootNamespace>Discord.Relay</RootNamespace>
|
||||
<Description>A core Discord.Net library containing the Relay server.</Description>
|
||||
<TargetFrameworks>netstandard1.3</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
|
||||
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" />
|
||||
<ProjectReference Include="..\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="1.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,79 +0,0 @@
|
|||
using Discord.API;
|
||||
using Discord.API.Gateway;
|
||||
using Discord.Logging;
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using WebSocketClient = System.Net.WebSockets.WebSocket;
|
||||
|
||||
namespace Discord.Relay
|
||||
{
|
||||
public class RelayConnection
|
||||
{
|
||||
private readonly RelayServer _server;
|
||||
private readonly WebSocketClient _socket;
|
||||
private readonly CancellationTokenSource _cancelToken;
|
||||
private readonly byte[] _inBuffer, _outBuffer;
|
||||
private readonly Logger _logger;
|
||||
|
||||
internal RelayConnection(RelayServer server, WebSocketClient socket, int id)
|
||||
{
|
||||
_server = server;
|
||||
_socket = socket;
|
||||
_cancelToken = new CancellationTokenSource();
|
||||
_inBuffer = new byte[4000];
|
||||
_outBuffer = new byte[4000];
|
||||
_logger = server.LogManager.CreateLogger($"Client #{id}");
|
||||
}
|
||||
|
||||
internal async Task RunAsync()
|
||||
{
|
||||
await _logger.InfoAsync($"Connected");
|
||||
var token = _cancelToken.Token;
|
||||
try
|
||||
{
|
||||
var segment = new ArraySegment<byte>(_inBuffer);
|
||||
|
||||
//Send HELLO
|
||||
await SendAsync(GatewayOpCode.Hello, new HelloEvent { HeartbeatInterval = 15000 }).ConfigureAwait(false);
|
||||
|
||||
while (_socket.State == WebSocketState.Open)
|
||||
{
|
||||
var result = await _socket.ReceiveAsync(segment, token).ConfigureAwait(false);
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
await _logger.WarningAsync($"Received Close {result.CloseStatus} ({result.CloseStatusDescription ?? "No Reason"})").ConfigureAwait(false);
|
||||
else
|
||||
await _logger.InfoAsync($"Received {result.Count} bytes");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
try { await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None).ConfigureAwait(false); }
|
||||
catch { }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { await _socket.CloseAsync(WebSocketCloseStatus.InternalServerError, ex.Message, CancellationToken.None).ConfigureAwait(false); }
|
||||
catch { }
|
||||
}
|
||||
finally
|
||||
{
|
||||
await _logger.InfoAsync($"Disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
internal void Stop()
|
||||
{
|
||||
_cancelToken.Cancel();
|
||||
}
|
||||
|
||||
private async Task SendAsync(GatewayOpCode opCode, object payload)
|
||||
{
|
||||
var frame = new SocketFrame { Operation = (int)opCode, Payload = payload };
|
||||
var bytes = _server.Serialize(frame, _outBuffer);
|
||||
var segment = new ArraySegment<byte>(_outBuffer, 0, bytes);
|
||||
await _socket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
using Discord.API;
|
||||
using Discord.Logging;
|
||||
using Discord.Net.Rest;
|
||||
using Discord.Net.WebSockets;
|
||||
using Discord.Rest;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using WebSocketClient = System.Net.WebSockets.WebSocket;
|
||||
|
||||
namespace Discord.Relay
|
||||
{
|
||||
public class RelayServer
|
||||
{
|
||||
public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } }
|
||||
internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>();
|
||||
|
||||
private readonly HashSet<RelayConnection> _connections;
|
||||
private readonly SemaphoreSlim _lock;
|
||||
private readonly JsonSerializer _serializer;
|
||||
private readonly DiscordSocketApiClient _discord;
|
||||
private int _nextId;
|
||||
|
||||
internal LogManager LogManager { get; }
|
||||
|
||||
internal RelayServer(Action<RelayServer> configAction)
|
||||
{
|
||||
_connections = new HashSet<RelayConnection>();
|
||||
_lock = new SemaphoreSlim(1, 1);
|
||||
_serializer = new JsonSerializer();
|
||||
_discord = new DiscordSocketApiClient(
|
||||
DefaultRestClientProvider.Instance,
|
||||
DefaultWebSocketProvider.Instance,
|
||||
DiscordRestConfig.UserAgent);
|
||||
configAction?.Invoke(this);
|
||||
|
||||
LogManager = new LogManager(LogSeverity.Debug);
|
||||
LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task AcceptAsync(HttpContext context)
|
||||
{
|
||||
WebSocketClient socket;
|
||||
try
|
||||
{
|
||||
socket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch { return; }
|
||||
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
var conn = new RelayConnection(this, socket, Interlocked.Increment(ref _nextId));
|
||||
await AddConnection(conn).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await conn.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally { await RemoveConnection(conn).ConfigureAwait(false); }
|
||||
});
|
||||
}
|
||||
|
||||
internal void StartAsync()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await _discord.ConnectAsync().ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
internal async Task AddConnection(RelayConnection conn)
|
||||
{
|
||||
await _lock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_connections.Add(conn);
|
||||
}
|
||||
finally { _lock.Release(); }
|
||||
}
|
||||
internal async Task RemoveConnection(RelayConnection conn)
|
||||
{
|
||||
await _lock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_connections.Remove(conn);
|
||||
}
|
||||
finally { _lock.Release(); }
|
||||
}
|
||||
|
||||
internal int Serialize(object obj, byte[] buffer)
|
||||
{
|
||||
using (var stream = new MemoryStream(buffer))
|
||||
using (var writer = new StreamWriter(stream))
|
||||
{
|
||||
_serializer.Serialize(writer, obj);
|
||||
return (int)stream.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class AuthenticateParams
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class AuthenticateResponse
|
||||
{
|
||||
[JsonProperty("application")]
|
||||
public Application Application { get; set; }
|
||||
[JsonProperty("expires")]
|
||||
public DateTimeOffset Expires { get; set; }
|
||||
[JsonProperty("user")]
|
||||
public User User { get; set; }
|
||||
[JsonProperty("scopes")]
|
||||
public string[] Scopes { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class AuthorizeParams
|
||||
{
|
||||
[JsonProperty("client_id")]
|
||||
public string ClientId { get; set; }
|
||||
[JsonProperty("scopes")]
|
||||
public IReadOnlyCollection<string> Scopes { get; set; }
|
||||
[JsonProperty("rpc_token")]
|
||||
public Optional<string> RpcToken { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class AuthorizeResponse
|
||||
{
|
||||
[JsonProperty("code")]
|
||||
public string Code { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class Channel
|
||||
{
|
||||
//Shared
|
||||
[JsonProperty("id")]
|
||||
public ulong Id { get; set; }
|
||||
[JsonProperty("type")]
|
||||
public ChannelType Type { get; set; }
|
||||
|
||||
//GuildChannel
|
||||
[JsonProperty("guild_id")]
|
||||
public Optional<ulong> GuildId { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public Optional<string> Name { get; set; }
|
||||
[JsonProperty("position")]
|
||||
public Optional<int> Position { get; set; }
|
||||
|
||||
//IMessageChannel
|
||||
[JsonProperty("messages")]
|
||||
public Message[] Messages { get; set; }
|
||||
|
||||
//VoiceChannel
|
||||
[JsonProperty("bitrate")]
|
||||
public Optional<int> Bitrate { get; set; }
|
||||
[JsonProperty("user_limit")]
|
||||
public Optional<int> UserLimit { get; set; }
|
||||
[JsonProperty("voice_states")]
|
||||
public ExtendedVoiceState[] VoiceStates { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class ChannelSubscriptionParams
|
||||
{
|
||||
[JsonProperty("channel_id")]
|
||||
public ulong ChannelId { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class ChannelSummary
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public ulong Id { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonProperty("type")]
|
||||
public ChannelType Type { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class ErrorEvent
|
||||
{
|
||||
[JsonProperty("code")]
|
||||
public int Code { get; set; }
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class ExtendedVoiceState
|
||||
{
|
||||
[JsonProperty("user")]
|
||||
public User User { get; set; }
|
||||
[JsonProperty("voice_state")]
|
||||
public Optional<VoiceState> VoiceState { get; set; }
|
||||
[JsonProperty("nick")]
|
||||
public Optional<string> Nickname { get; set; }
|
||||
[JsonProperty("volume")]
|
||||
public Optional<int> Volume { get; set; }
|
||||
[JsonProperty("mute")]
|
||||
public Optional<bool> Mute { get; set; }
|
||||
[JsonProperty("pan")]
|
||||
public Optional<Pan> Pan { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GetChannelParams
|
||||
{
|
||||
[JsonProperty("channel_id")]
|
||||
public ulong ChannelId { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GetChannelsParams
|
||||
{
|
||||
[JsonProperty("guild_id")]
|
||||
public ulong GuildId { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GetChannelsResponse
|
||||
{
|
||||
[JsonProperty("channels")]
|
||||
public IReadOnlyCollection<ChannelSummary> Channels { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GetGuildParams
|
||||
{
|
||||
[JsonProperty("guild_id")]
|
||||
public ulong GuildId { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GetGuildsParams
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GetGuildsResponse
|
||||
{
|
||||
[JsonProperty("guilds")]
|
||||
public GuildSummary[] Guilds { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class Guild
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public ulong Id { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonProperty("icon_url")]
|
||||
public string IconUrl { get; set; }
|
||||
[JsonProperty("members")]
|
||||
public IEnumerable<GuildMember> Members { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GuildMember
|
||||
{
|
||||
[JsonProperty("user")]
|
||||
public User User { get; set; }
|
||||
[JsonProperty("status")]
|
||||
public UserStatus Status { get; set; }
|
||||
/*[JsonProperty("activity")]
|
||||
public object Activity { get; set; }*/
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GuildStatusEvent
|
||||
{
|
||||
[JsonProperty("guild")]
|
||||
public Guild Guild { get; set; }
|
||||
[JsonProperty("online")]
|
||||
public int Online { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GuildSubscriptionParams
|
||||
{
|
||||
[JsonProperty("guild_id")]
|
||||
public ulong GuildId { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class GuildSummary
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public ulong Id { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class Message : Discord.API.Message
|
||||
{
|
||||
[JsonProperty("blocked")]
|
||||
public Optional<bool> IsBlocked { get; }
|
||||
[JsonProperty("content_parsed")]
|
||||
public Optional<object[]> ContentParsed { get; }
|
||||
[JsonProperty("author_color")]
|
||||
public Optional<string> AuthorColor { get; } //#Hex
|
||||
|
||||
[JsonProperty("mentions")]
|
||||
public new Optional<ulong[]> UserMentions { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class MessageEvent
|
||||
{
|
||||
[JsonProperty("channel_id")]
|
||||
public ulong ChannelId { get; set; }
|
||||
[JsonProperty("message")]
|
||||
public Message Message { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class Pan
|
||||
{
|
||||
[JsonProperty("left")]
|
||||
public float Left { get; set; }
|
||||
[JsonProperty("right")]
|
||||
public float Right { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class ReadyEvent
|
||||
{
|
||||
[JsonProperty("v")]
|
||||
public int Version { get; set; }
|
||||
[JsonProperty("config")]
|
||||
public RpcConfig Config { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class RpcConfig
|
||||
{
|
||||
[JsonProperty("cdn_host")]
|
||||
public string CdnHost { get; set; }
|
||||
[JsonProperty("api_endpoint")]
|
||||
public string ApiEndpoint { get; set; }
|
||||
[JsonProperty("environment")]
|
||||
public string Environment { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class SelectChannelParams
|
||||
{
|
||||
[JsonProperty("channel_id")]
|
||||
public ulong? ChannelId { get; set; }
|
||||
[JsonProperty("force")]
|
||||
public Optional<bool> Force { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class SetLocalVolumeParams
|
||||
{
|
||||
[JsonProperty("volume")]
|
||||
public int Volume { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class SetLocalVolumeResponse
|
||||
{
|
||||
[JsonProperty("user_id")]
|
||||
public ulong UserId { get; set; }
|
||||
[JsonProperty("volume")]
|
||||
public int Volume { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class SpeakingEvent
|
||||
{
|
||||
[JsonProperty("user_id")]
|
||||
public ulong UserId { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class SubscriptionResponse
|
||||
{
|
||||
[JsonProperty("evt")]
|
||||
public string Event { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class UserVoiceSettings
|
||||
{
|
||||
[JsonProperty("userId")]
|
||||
internal ulong UserId { get; set; }
|
||||
[JsonProperty("pan")]
|
||||
public Optional<Pan> Pan { get; set; }
|
||||
[JsonProperty("volume")]
|
||||
public Optional<int> Volume { get; set; }
|
||||
[JsonProperty("mute")]
|
||||
public Optional<bool> Mute { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class VoiceDevice
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class VoiceDeviceSettings
|
||||
{
|
||||
[JsonProperty("device_id")]
|
||||
public Optional<string> DeviceId { get; set; }
|
||||
[JsonProperty("volume")]
|
||||
public Optional<float> Volume { get; set; }
|
||||
[JsonProperty("available_devices")]
|
||||
public Optional<VoiceDevice[]> AvailableDevices { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class VoiceMode
|
||||
{
|
||||
[JsonProperty("type")]
|
||||
public Optional<string> Type { get; set; }
|
||||
[JsonProperty("auto_threshold")]
|
||||
public Optional<bool> AutoThreshold { get; set; }
|
||||
[JsonProperty("threshold")]
|
||||
public Optional<float> Threshold { get; set; }
|
||||
[JsonProperty("shortcut")]
|
||||
public Optional<VoiceShortcut[]> Shortcut { get; set; }
|
||||
[JsonProperty("delay")]
|
||||
public Optional<float> Delay { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class VoiceSettings
|
||||
{
|
||||
[JsonProperty("input")]
|
||||
public VoiceDeviceSettings Input { get; set; }
|
||||
[JsonProperty("output")]
|
||||
public VoiceDeviceSettings Output { get; set; }
|
||||
[JsonProperty("mode")]
|
||||
public VoiceMode Mode { get; set; }
|
||||
[JsonProperty("automatic_gain_control")]
|
||||
public Optional<bool> AutomaticGainControl { get; set; }
|
||||
[JsonProperty("echo_cancellation")]
|
||||
public Optional<bool> EchoCancellation { get; set; }
|
||||
[JsonProperty("noise_suppression")]
|
||||
public Optional<bool> NoiseSuppression { get; set; }
|
||||
[JsonProperty("qos")]
|
||||
public Optional<bool> QualityOfService { get; set; }
|
||||
[JsonProperty("silence_warning")]
|
||||
public Optional<bool> SilenceWarning { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
using Discord.Rpc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class VoiceShortcut
|
||||
{
|
||||
[JsonProperty("type")]
|
||||
public Optional<VoiceShortcutType> Type { get; set; }
|
||||
[JsonProperty("code")]
|
||||
public Optional<int> Code { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public Optional<string> Name { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
internal class RpcFrame
|
||||
{
|
||||
[JsonProperty("cmd")]
|
||||
public string Cmd { get; set; }
|
||||
[JsonProperty("nonce")]
|
||||
public Optional<Guid?> Nonce { get; set; }
|
||||
[JsonProperty("evt")]
|
||||
public Optional<string> Event { get; set; }
|
||||
[JsonProperty("data")]
|
||||
public Optional<object> Data { get; set; }
|
||||
[JsonProperty("args")]
|
||||
public object Args { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Discord.Net.Tests")]
|
|
@ -1,29 +0,0 @@
|
|||
using Discord.Rpc;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
public class RpcCommandContext : ICommandContext
|
||||
{
|
||||
public DiscordRpcClient Client { get; }
|
||||
public IMessageChannel Channel { get; }
|
||||
public RpcUser User { get; }
|
||||
public RpcUserMessage Message { get; }
|
||||
|
||||
public bool IsPrivate => Channel is IPrivateChannel;
|
||||
|
||||
public RpcCommandContext(DiscordRpcClient client, RpcUserMessage msg)
|
||||
{
|
||||
Client = client;
|
||||
Channel = msg.Channel;
|
||||
User = msg.Author;
|
||||
Message = msg;
|
||||
}
|
||||
|
||||
//ICommandContext
|
||||
IDiscordClient ICommandContext.Client => Client;
|
||||
IGuild ICommandContext.Guild => null;
|
||||
IMessageChannel ICommandContext.Channel => Channel;
|
||||
IUser ICommandContext.User => User;
|
||||
IUserMessage ICommandContext.Message => Message;
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../../Discord.Net.targets" />
|
||||
<PropertyGroup>
|
||||
<AssemblyName>Discord.Net.Rpc</AssemblyName>
|
||||
<RootNamespace>Discord.Rpc</RootNamespace>
|
||||
<Description>A core Discord.Net library containing the RPC client and models.</Description>
|
||||
<TargetFrameworks>net45;netstandard1.1;netstandard1.3</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Discord.Net.WebSocket\Net\DefaultWebSocketClient.cs">
|
||||
<Link>Net\DefaultWebSocketClient.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net.WebSocket\ConnectionManager.cs">
|
||||
<Link>ConnectionManager.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
|
||||
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,397 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
using Discord.API.Rpc;
|
||||
using Discord.Net.Queue;
|
||||
using Discord.Net.Rest;
|
||||
using Discord.Net.WebSockets;
|
||||
using Discord.Rpc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.API
|
||||
{
|
||||
internal class DiscordRpcApiClient : DiscordRestApiClient, IDisposable
|
||||
{
|
||||
private abstract class RpcRequest
|
||||
{
|
||||
public abstract Task SetResultAsync(JToken data, JsonSerializer serializer);
|
||||
public abstract Task SetExceptionAsync(JToken data, JsonSerializer serializer);
|
||||
}
|
||||
private class RpcRequest<T> : RpcRequest
|
||||
{
|
||||
public TaskCompletionSource<T> Promise { get; set; }
|
||||
|
||||
public RpcRequest(RequestOptions options)
|
||||
{
|
||||
Promise = new TaskCompletionSource<T>();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(options?.Timeout ?? 15000).ConfigureAwait(false);
|
||||
Promise.TrySetCanceled(); //Doesn't need to be async, we're already in a separate task
|
||||
});
|
||||
}
|
||||
public override Task SetResultAsync(JToken data, JsonSerializer serializer)
|
||||
{
|
||||
return Promise.TrySetResultAsync(data.ToObject<T>(serializer));
|
||||
}
|
||||
public override Task SetExceptionAsync(JToken data, JsonSerializer serializer)
|
||||
{
|
||||
var error = data.ToObject<ErrorEvent>(serializer);
|
||||
return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private object _eventLock = new object();
|
||||
|
||||
public event Func<string, Task> SentRpcMessage { add { _sentRpcMessageEvent.Add(value); } remove { _sentRpcMessageEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<string, Task>> _sentRpcMessageEvent = new AsyncEvent<Func<string, Task>>();
|
||||
|
||||
public event Func<string, Optional<string>, Optional<object>, Task> ReceivedRpcEvent { add { _receivedRpcEvent.Add(value); } remove { _receivedRpcEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<string, Optional<string>, Optional<object>, Task>> _receivedRpcEvent = new AsyncEvent<Func<string, Optional<string>, Optional<object>, Task>>();
|
||||
public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, RpcRequest> _requests;
|
||||
private readonly IWebSocketClient _webSocketClient;
|
||||
private readonly SemaphoreSlim _connectionLock;
|
||||
private readonly string _clientId;
|
||||
private CancellationTokenSource _stateCancelToken;
|
||||
private string _origin;
|
||||
|
||||
public ConnectionState ConnectionState { get; private set; }
|
||||
|
||||
public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider,
|
||||
RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null)
|
||||
: base(restClientProvider, userAgent, defaultRetryMode, serializer)
|
||||
{
|
||||
_connectionLock = new SemaphoreSlim(1, 1);
|
||||
_clientId = clientId;
|
||||
_origin = origin;
|
||||
|
||||
_requests = new ConcurrentDictionary<Guid, RpcRequest>();
|
||||
|
||||
_webSocketClient = webSocketProvider();
|
||||
//_webSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+)
|
||||
_webSocketClient.SetHeader("origin", _origin);
|
||||
_webSocketClient.BinaryMessage += async (data, index, count) =>
|
||||
{
|
||||
using (var compressed = new MemoryStream(data, index + 2, count - 2))
|
||||
using (var decompressed = new MemoryStream())
|
||||
{
|
||||
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress))
|
||||
zlib.CopyTo(decompressed);
|
||||
decompressed.Position = 0;
|
||||
using (var reader = new StreamReader(decompressed))
|
||||
using (var jsonReader = new JsonTextReader(reader))
|
||||
{
|
||||
var msg = _serializer.Deserialize<API.Rpc.RpcFrame>(jsonReader);
|
||||
await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false);
|
||||
if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue)
|
||||
ProcessMessage(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
_webSocketClient.TextMessage += async text =>
|
||||
{
|
||||
using (var reader = new StringReader(text))
|
||||
using (var jsonReader = new JsonTextReader(reader))
|
||||
{
|
||||
var msg = _serializer.Deserialize<API.Rpc.RpcFrame>(jsonReader);
|
||||
await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false);
|
||||
if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue)
|
||||
ProcessMessage(msg);
|
||||
}
|
||||
};
|
||||
_webSocketClient.Closed += async ex =>
|
||||
{
|
||||
await DisconnectAsync().ConfigureAwait(false);
|
||||
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
internal override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_stateCancelToken?.Dispose();
|
||||
(_webSocketClient as IDisposable)?.Dispose();
|
||||
}
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await ConnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
internal override async Task ConnectInternalAsync()
|
||||
{
|
||||
/*if (LoginState != LoginState.LoggedIn)
|
||||
throw new InvalidOperationException("Client is not logged in.");*/
|
||||
|
||||
ConnectionState = ConnectionState.Connecting;
|
||||
try
|
||||
{
|
||||
_stateCancelToken = new CancellationTokenSource();
|
||||
if (_webSocketClient != null)
|
||||
_webSocketClient.SetCancelToken(_stateCancelToken.Token);
|
||||
|
||||
bool success = false;
|
||||
int port;
|
||||
string uuid = Guid.NewGuid().ToString();
|
||||
|
||||
for ( port = DiscordRpcConfig.PortRangeStart; port <= DiscordRpcConfig.PortRangeEnd; port++)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"wss://{uuid}.discordapp.io:{port}/?v={DiscordRpcConfig.RpcAPIVersion}&client_id={_clientId}";
|
||||
await _webSocketClient.ConnectAsync(url).ConfigureAwait(false);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
throw new Exception("Unable to connect to the RPC server.");
|
||||
|
||||
SetBaseUrl($"https://{uuid}.discordapp.io:{port}/");
|
||||
ConnectionState = ConnectionState.Connected;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
internal override async Task DisconnectInternalAsync()
|
||||
{
|
||||
if (_webSocketClient == null)
|
||||
throw new NotSupportedException("This client is not configured with WebSocket support.");
|
||||
|
||||
if (ConnectionState == ConnectionState.Disconnected) return;
|
||||
ConnectionState = ConnectionState.Disconnecting;
|
||||
|
||||
try { _stateCancelToken?.Cancel(false); }
|
||||
catch { }
|
||||
|
||||
await _webSocketClient.DisconnectAsync().ConfigureAwait(false);
|
||||
|
||||
ConnectionState = ConnectionState.Disconnected;
|
||||
}
|
||||
|
||||
//Core
|
||||
public async Task<TResponse> SendRpcAsync<TResponse>(string cmd, object payload, Optional<string> evt = default(Optional<string>), RequestOptions options = null)
|
||||
where TResponse : class
|
||||
{
|
||||
return await SendRpcAsyncInternal<TResponse>(cmd, payload, evt, options).ConfigureAwait(false);
|
||||
}
|
||||
private async Task<TResponse> SendRpcAsyncInternal<TResponse>(string cmd, object payload, Optional<string> evt, RequestOptions options)
|
||||
where TResponse : class
|
||||
{
|
||||
byte[] bytes = null;
|
||||
var guid = Guid.NewGuid();
|
||||
payload = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = payload, Nonce = guid };
|
||||
if (payload != null)
|
||||
{
|
||||
var json = SerializeJson(payload);
|
||||
bytes = Encoding.UTF8.GetBytes(json);
|
||||
}
|
||||
|
||||
var requestTracker = new RpcRequest<TResponse>(options);
|
||||
_requests[guid] = requestTracker;
|
||||
|
||||
await RequestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, bytes, true, options)).ConfigureAwait(false);
|
||||
await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false);
|
||||
return await requestTracker.Promise.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Rpc
|
||||
public async Task<AuthenticateResponse> SendAuthenticateAsync(RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new AuthenticateParams
|
||||
{
|
||||
AccessToken = AuthToken
|
||||
};
|
||||
options.IgnoreState = true;
|
||||
return await SendRpcAsync<AuthenticateResponse>("AUTHENTICATE", msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<AuthorizeResponse> SendAuthorizeAsync(IReadOnlyCollection<string> scopes, string rpcToken = null, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new AuthorizeParams
|
||||
{
|
||||
ClientId = _clientId,
|
||||
Scopes = scopes,
|
||||
RpcToken = rpcToken != null ? rpcToken : Optional.Create<string>()
|
||||
};
|
||||
if (options.Timeout == null)
|
||||
options.Timeout = 60000; //This requires manual input on the user's end, lets give them more time
|
||||
options.IgnoreState = true;
|
||||
return await SendRpcAsync<AuthorizeResponse>("AUTHORIZE", msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<GetGuildsResponse> SendGetGuildsAsync(RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
return await SendRpcAsync<GetGuildsResponse>("GET_GUILDS", null, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Rpc.Guild> SendGetGuildAsync(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new GetGuildParams
|
||||
{
|
||||
GuildId = guildId
|
||||
};
|
||||
return await SendRpcAsync<Rpc.Guild>("GET_GUILD", msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<GetChannelsResponse> SendGetChannelsAsync(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new GetChannelsParams
|
||||
{
|
||||
GuildId = guildId
|
||||
};
|
||||
return await SendRpcAsync<GetChannelsResponse>("GET_CHANNELS", msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Rpc.Channel> SendGetChannelAsync(ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new GetChannelParams
|
||||
{
|
||||
ChannelId = channelId
|
||||
};
|
||||
return await SendRpcAsync<Rpc.Channel>("GET_CHANNEL", msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<Rpc.Channel> SendSelectTextChannelAsync(ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new SelectChannelParams
|
||||
{
|
||||
ChannelId = channelId
|
||||
};
|
||||
return await SendRpcAsync<Rpc.Channel>("SELECT_TEXT_CHANNEL", msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<Rpc.Channel> SendSelectVoiceChannelAsync(ulong channelId, bool force = false, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new SelectChannelParams
|
||||
{
|
||||
ChannelId = channelId,
|
||||
Force = force
|
||||
};
|
||||
return await SendRpcAsync<Rpc.Channel>("SELECT_VOICE_CHANNEL", msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<SubscriptionResponse> SendGlobalSubscribeAsync(string evt, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
return await SendRpcAsync<SubscriptionResponse>("SUBSCRIBE", null, evt: evt, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<SubscriptionResponse> SendGlobalUnsubscribeAsync(string evt, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
return await SendRpcAsync<SubscriptionResponse>("UNSUBSCRIBE", null, evt: evt, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<SubscriptionResponse> SendGuildSubscribeAsync(string evt, ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new GuildSubscriptionParams
|
||||
{
|
||||
GuildId = guildId
|
||||
};
|
||||
return await SendRpcAsync<SubscriptionResponse>("SUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<SubscriptionResponse> SendGuildUnsubscribeAsync(string evt, ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new GuildSubscriptionParams
|
||||
{
|
||||
GuildId = guildId
|
||||
};
|
||||
return await SendRpcAsync<SubscriptionResponse>("UNSUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<SubscriptionResponse> SendChannelSubscribeAsync(string evt, ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new ChannelSubscriptionParams
|
||||
{
|
||||
ChannelId = channelId
|
||||
};
|
||||
return await SendRpcAsync<SubscriptionResponse>("SUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<SubscriptionResponse> SendChannelUnsubscribeAsync(string evt, ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
var msg = new ChannelSubscriptionParams
|
||||
{
|
||||
ChannelId = channelId
|
||||
};
|
||||
return await SendRpcAsync<SubscriptionResponse>("UNSUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<API.Rpc.VoiceSettings> GetVoiceSettingsAsync(RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
return await SendRpcAsync<API.Rpc.VoiceSettings>("GET_VOICE_SETTINGS", null, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task SetVoiceSettingsAsync(API.Rpc.VoiceSettings settings, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
await SendRpcAsync<API.Rpc.VoiceSettings>("SET_VOICE_SETTINGS", settings, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task SetUserVoiceSettingsAsync(ulong userId, API.Rpc.UserVoiceSettings settings, RequestOptions options = null)
|
||||
{
|
||||
options = RequestOptions.CreateOrClone(options);
|
||||
settings.UserId = userId;
|
||||
await SendRpcAsync<API.Rpc.UserVoiceSettings>("SET_USER_VOICE_SETTINGS", settings, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private bool ProcessMessage(API.Rpc.RpcFrame msg)
|
||||
{
|
||||
if (_requests.TryGetValue(msg.Nonce.Value.Value, out RpcRequest requestTracker))
|
||||
{
|
||||
if (msg.Event.GetValueOrDefault("") == "ERROR")
|
||||
{
|
||||
var _ = requestTracker.SetExceptionAsync(msg.Data.GetValueOrDefault() as JToken, _serializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
var _ = requestTracker.SetResultAsync(msg.Data.GetValueOrDefault() as JToken, _serializer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public partial class DiscordRpcClient
|
||||
{
|
||||
//General
|
||||
public event Func<Task> Connected
|
||||
{
|
||||
add { _connectedEvent.Add(value); }
|
||||
remove { _connectedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
|
||||
public event Func<Exception, Task> Disconnected
|
||||
{
|
||||
add { _disconnectedEvent.Add(value); }
|
||||
remove { _disconnectedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
||||
public event Func<Task> Ready
|
||||
{
|
||||
add { _readyEvent.Add(value); }
|
||||
remove { _readyEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<Task>> _readyEvent = new AsyncEvent<Func<Task>>();
|
||||
|
||||
//Channel
|
||||
public event Func<RpcChannelSummary, Task> ChannelCreated
|
||||
{
|
||||
add { _channelCreatedEvent.Add(value); }
|
||||
remove { _channelCreatedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<RpcChannelSummary, Task>> _channelCreatedEvent = new AsyncEvent<Func<RpcChannelSummary, Task>>();
|
||||
|
||||
//Guild
|
||||
public event Func<RpcGuildSummary, Task> GuildCreated
|
||||
{
|
||||
add { _guildCreatedEvent.Add(value); }
|
||||
remove { _guildCreatedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<RpcGuildSummary, Task>> _guildCreatedEvent = new AsyncEvent<Func<RpcGuildSummary, Task>>();
|
||||
public event Func<RpcGuildStatus, Task> GuildStatusUpdated
|
||||
{
|
||||
add { _guildStatusUpdatedEvent.Add(value); }
|
||||
remove { _guildStatusUpdatedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<RpcGuildStatus, Task>> _guildStatusUpdatedEvent = new AsyncEvent<Func<RpcGuildStatus, Task>>();
|
||||
|
||||
//Voice
|
||||
public event Func<RpcVoiceState, Task> VoiceStateCreated
|
||||
{
|
||||
add { _voiceStateCreatedEvent.Add(value); }
|
||||
remove { _voiceStateCreatedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<RpcVoiceState, Task>> _voiceStateCreatedEvent = new AsyncEvent<Func<RpcVoiceState, Task>>();
|
||||
|
||||
public event Func<RpcVoiceState, Task> VoiceStateUpdated
|
||||
{
|
||||
add { _voiceStateUpdatedEvent.Add(value); }
|
||||
remove { _voiceStateUpdatedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<RpcVoiceState, Task>> _voiceStateUpdatedEvent = new AsyncEvent<Func<RpcVoiceState, Task>>();
|
||||
|
||||
public event Func<RpcVoiceState, Task> VoiceStateDeleted
|
||||
{
|
||||
add { _voiceStateDeletedEvent.Add(value); }
|
||||
remove { _voiceStateDeletedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<RpcVoiceState, Task>> _voiceStateDeletedEvent = new AsyncEvent<Func<RpcVoiceState, Task>>();
|
||||
|
||||
public event Func<ulong, Task> SpeakingStarted
|
||||
{
|
||||
add { _speakingStartedEvent.Add(value); }
|
||||
remove { _speakingStartedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<ulong, Task>> _speakingStartedEvent = new AsyncEvent<Func<ulong, Task>>();
|
||||
public event Func<ulong, Task> SpeakingStopped
|
||||
{
|
||||
add { _speakingStoppedEvent.Add(value); }
|
||||
remove { _speakingStoppedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<ulong, Task>> _speakingStoppedEvent = new AsyncEvent<Func<ulong, Task>>();
|
||||
|
||||
public event Func<VoiceSettings, Task> VoiceSettingsUpdated
|
||||
{
|
||||
add { _voiceSettingsUpdated.Add(value); }
|
||||
remove { _voiceSettingsUpdated.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<VoiceSettings, Task>> _voiceSettingsUpdated = new AsyncEvent<Func<VoiceSettings, Task>>();
|
||||
|
||||
//Messages
|
||||
public event Func<RpcMessage, Task> MessageReceived
|
||||
{
|
||||
add { _messageReceivedEvent.Add(value); }
|
||||
remove { _messageReceivedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<RpcMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<RpcMessage, Task>>();
|
||||
public event Func<RpcMessage, Task> MessageUpdated
|
||||
{
|
||||
add { _messageUpdatedEvent.Add(value); }
|
||||
remove { _messageUpdatedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<RpcMessage, Task>> _messageUpdatedEvent = new AsyncEvent<Func<RpcMessage, Task>>();
|
||||
public event Func<ulong, ulong, Task> MessageDeleted
|
||||
{
|
||||
add { _messageDeletedEvent.Add(value); }
|
||||
remove { _messageDeletedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<ulong, ulong, Task>> _messageDeletedEvent = new AsyncEvent<Func<ulong, ulong, Task>>();
|
||||
}
|
||||
}
|
|
@ -1,478 +0,0 @@
|
|||
using Discord.API.Rpc;
|
||||
using Discord.Logging;
|
||||
using Discord.Net.Converters;
|
||||
using Discord.Rest;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient
|
||||
{
|
||||
private readonly JsonSerializer _serializer;
|
||||
private readonly ConnectionManager _connection;
|
||||
private readonly Logger _rpcLogger;
|
||||
private readonly SemaphoreSlim _stateLock, _authorizeLock;
|
||||
|
||||
public ConnectionState ConnectionState { get; private set; }
|
||||
public IReadOnlyCollection<string> Scopes { get; private set; }
|
||||
public DateTimeOffset TokenExpiresAt { get; private set; }
|
||||
|
||||
internal new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient;
|
||||
public new RestSelfUser CurrentUser { get { return base.CurrentUser as RestSelfUser; } private set { base.CurrentUser = value; } }
|
||||
public RestApplication ApplicationInfo { get; private set; }
|
||||
|
||||
/// <summary> Creates a new RPC discord client. </summary>
|
||||
public DiscordRpcClient(string clientId, string origin)
|
||||
: this(clientId, origin, new DiscordRpcConfig()) { }
|
||||
/// <summary> Creates a new RPC discord client. </summary>
|
||||
public DiscordRpcClient(string clientId, string origin, DiscordRpcConfig config)
|
||||
: base(config, CreateApiClient(clientId, origin, config))
|
||||
{
|
||||
_stateLock = new SemaphoreSlim(1, 1);
|
||||
_authorizeLock = new SemaphoreSlim(1, 1);
|
||||
_rpcLogger = LogManager.CreateLogger("RPC");
|
||||
_connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout,
|
||||
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x);
|
||||
_connection.Connected += () => _connectedEvent.InvokeAsync();
|
||||
_connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex);
|
||||
|
||||
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
||||
_serializer.Error += (s, e) =>
|
||||
{
|
||||
_rpcLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult();
|
||||
e.ErrorContext.Handled = true;
|
||||
};
|
||||
|
||||
ApiClient.SentRpcMessage += async opCode => await _rpcLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false);
|
||||
ApiClient.ReceivedRpcEvent += ProcessMessageAsync;
|
||||
}
|
||||
|
||||
private static API.DiscordRpcApiClient CreateApiClient(string clientId, string origin, DiscordRpcConfig config)
|
||||
=> new API.DiscordRpcApiClient(clientId, DiscordRestConfig.UserAgent, origin, config.RestClientProvider, config.WebSocketProvider);
|
||||
internal override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
StopAsync().GetAwaiter().GetResult();
|
||||
ApiClient.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync() => _connection.StartAsync();
|
||||
public Task StopAsync() => _connection.StopAsync();
|
||||
|
||||
private async Task OnConnectingAsync()
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false);
|
||||
await ApiClient.ConnectAsync().ConfigureAwait(false);
|
||||
|
||||
await _connection.WaitAsync().ConfigureAwait(false);
|
||||
}
|
||||
private async Task OnDisconnectingAsync(Exception ex)
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false);
|
||||
await ApiClient.DisconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<string> AuthorizeAsync(string[] scopes, string rpcToken = null, RequestOptions options = null)
|
||||
{
|
||||
await _authorizeLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _connection.StartAsync().ConfigureAwait(false);
|
||||
await _connection.WaitAsync().ConfigureAwait(false);
|
||||
var result = await ApiClient.SendAuthorizeAsync(scopes, rpcToken, options).ConfigureAwait(false);
|
||||
await _connection.StopAsync().ConfigureAwait(false);
|
||||
return result.Code;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_authorizeLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SubscribeGlobal(RpcGlobalEvent evnt, RequestOptions options = null)
|
||||
{
|
||||
await ApiClient.SendGlobalSubscribeAsync(GetEventName(evnt), options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task UnsubscribeGlobal(RpcGlobalEvent evnt, RequestOptions options = null)
|
||||
{
|
||||
await ApiClient.SendGlobalUnsubscribeAsync(GetEventName(evnt), options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task SubscribeGuild(ulong guildId, RpcChannelEvent evnt, RequestOptions options = null)
|
||||
{
|
||||
await ApiClient.SendGuildSubscribeAsync(GetEventName(evnt), guildId, options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task UnsubscribeGuild(ulong guildId, RpcChannelEvent evnt, RequestOptions options = null)
|
||||
{
|
||||
await ApiClient.SendGuildUnsubscribeAsync(GetEventName(evnt), guildId, options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task SubscribeChannel(ulong channelId, RpcChannelEvent evnt, RequestOptions options = null)
|
||||
{
|
||||
await ApiClient.SendChannelSubscribeAsync(GetEventName(evnt), channelId).ConfigureAwait(false);
|
||||
}
|
||||
public async Task UnsubscribeChannel(ulong channelId, RpcChannelEvent evnt, RequestOptions options = null)
|
||||
{
|
||||
await ApiClient.SendChannelUnsubscribeAsync(GetEventName(evnt), channelId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<RpcGuild> GetRpcGuildAsync(ulong id, RequestOptions options = null)
|
||||
{
|
||||
var model = await ApiClient.SendGetGuildAsync(id, options).ConfigureAwait(false);
|
||||
return RpcGuild.Create(this, model);
|
||||
}
|
||||
public async Task<IReadOnlyCollection<RpcGuildSummary>> GetRpcGuildsAsync(RequestOptions options = null)
|
||||
{
|
||||
var models = await ApiClient.SendGetGuildsAsync(options).ConfigureAwait(false);
|
||||
return models.Guilds.Select(x => RpcGuildSummary.Create(x)).ToImmutableArray();
|
||||
}
|
||||
public async Task<RpcChannel> GetRpcChannelAsync(ulong id, RequestOptions options = null)
|
||||
{
|
||||
var model = await ApiClient.SendGetChannelAsync(id, options).ConfigureAwait(false);
|
||||
return RpcChannel.Create(this, model);
|
||||
}
|
||||
public async Task<IReadOnlyCollection<RpcChannelSummary>> GetRpcChannelsAsync(ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
var models = await ApiClient.SendGetChannelsAsync(guildId, options).ConfigureAwait(false);
|
||||
return models.Channels.Select(x => RpcChannelSummary.Create(x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public async Task<IMessageChannel> SelectTextChannelAsync(IChannel channel, RequestOptions options = null)
|
||||
{
|
||||
var model = await ApiClient.SendSelectTextChannelAsync(channel.Id, options).ConfigureAwait(false);
|
||||
return RpcChannel.Create(this, model) as IMessageChannel;
|
||||
}
|
||||
public async Task<IMessageChannel> SelectTextChannelAsync(RpcChannelSummary channel, RequestOptions options = null)
|
||||
{
|
||||
var model = await ApiClient.SendSelectTextChannelAsync(channel.Id, options).ConfigureAwait(false);
|
||||
return RpcChannel.Create(this, model) as IMessageChannel;
|
||||
}
|
||||
public async Task<IMessageChannel> SelectTextChannelAsync(ulong channelId, RequestOptions options = null)
|
||||
{
|
||||
var model = await ApiClient.SendSelectTextChannelAsync(channelId, options).ConfigureAwait(false);
|
||||
return RpcChannel.Create(this, model) as IMessageChannel;
|
||||
}
|
||||
|
||||
public async Task<IRpcAudioChannel> SelectVoiceChannelAsync(IChannel channel, bool force = false, RequestOptions options = null)
|
||||
{
|
||||
var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force, options).ConfigureAwait(false);
|
||||
return RpcChannel.Create(this, model) as IRpcAudioChannel;
|
||||
}
|
||||
public async Task<IRpcAudioChannel> SelectVoiceChannelAsync(RpcChannelSummary channel, bool force = false, RequestOptions options = null)
|
||||
{
|
||||
var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force, options).ConfigureAwait(false);
|
||||
return RpcChannel.Create(this, model) as IRpcAudioChannel;
|
||||
}
|
||||
public async Task<IRpcAudioChannel> SelectVoiceChannelAsync(ulong channelId, bool force = false, RequestOptions options = null)
|
||||
{
|
||||
var model = await ApiClient.SendSelectVoiceChannelAsync(channelId, force, options).ConfigureAwait(false);
|
||||
return RpcChannel.Create(this, model) as IRpcAudioChannel;
|
||||
}
|
||||
|
||||
public async Task<VoiceSettings> GetVoiceSettingsAsync(RequestOptions options = null)
|
||||
{
|
||||
var model = await ApiClient.GetVoiceSettingsAsync(options).ConfigureAwait(false);
|
||||
return VoiceSettings.Create(model);
|
||||
}
|
||||
public async Task SetVoiceSettingsAsync(Action<VoiceProperties> func, RequestOptions options = null)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
|
||||
var settings = new VoiceProperties();
|
||||
settings.Input = new VoiceDeviceProperties();
|
||||
settings.Output = new VoiceDeviceProperties();
|
||||
settings.Mode = new VoiceModeProperties();
|
||||
func(settings);
|
||||
|
||||
var model = new API.Rpc.VoiceSettings
|
||||
{
|
||||
AutomaticGainControl = settings.AutomaticGainControl,
|
||||
EchoCancellation = settings.EchoCancellation,
|
||||
NoiseSuppression = settings.NoiseSuppression,
|
||||
QualityOfService = settings.QualityOfService,
|
||||
SilenceWarning = settings.SilenceWarning
|
||||
};
|
||||
model.Input = new API.Rpc.VoiceDeviceSettings
|
||||
{
|
||||
DeviceId = settings.Input.DeviceId,
|
||||
Volume = settings.Input.Volume
|
||||
};
|
||||
model.Output = new API.Rpc.VoiceDeviceSettings
|
||||
{
|
||||
DeviceId = settings.Output.DeviceId,
|
||||
Volume = settings.Output.Volume
|
||||
};
|
||||
model.Mode = new API.Rpc.VoiceMode
|
||||
{
|
||||
AutoThreshold = settings.Mode.AutoThreshold,
|
||||
Delay = settings.Mode.Delay,
|
||||
Threshold = settings.Mode.Threshold,
|
||||
Type = settings.Mode.Type
|
||||
};
|
||||
|
||||
if (settings.Input.AvailableDevices.IsSpecified)
|
||||
model.Input.AvailableDevices = settings.Input.AvailableDevices.Value.Select(x => x.ToModel()).ToArray();
|
||||
if (settings.Output.AvailableDevices.IsSpecified)
|
||||
model.Output.AvailableDevices = settings.Output.AvailableDevices.Value.Select(x => x.ToModel()).ToArray();
|
||||
if (settings.Mode.Shortcut.IsSpecified)
|
||||
model.Mode.Shortcut = settings.Mode.Shortcut.Value.Select(x => x.ToModel()).ToArray();
|
||||
|
||||
await ApiClient.SetVoiceSettingsAsync(model, options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task SetUserVoiceSettingsAsync(ulong userId, Action<UserVoiceProperties> func, RequestOptions options = null)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
|
||||
var settings = new UserVoiceProperties();
|
||||
func(settings);
|
||||
|
||||
var model = new API.Rpc.UserVoiceSettings
|
||||
{
|
||||
Mute = settings.Mute,
|
||||
UserId = settings.UserId,
|
||||
Volume = settings.Volume
|
||||
};
|
||||
if (settings.Pan.IsSpecified)
|
||||
model.Pan = settings.Pan.Value.ToModel();
|
||||
await ApiClient.SetUserVoiceSettingsAsync(userId, model, options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string GetEventName(RpcGlobalEvent rpcEvent)
|
||||
{
|
||||
switch (rpcEvent)
|
||||
{
|
||||
case RpcGlobalEvent.ChannelCreated: return "CHANNEL_CREATE";
|
||||
case RpcGlobalEvent.GuildCreated: return "GUILD_CREATE";
|
||||
case RpcGlobalEvent.VoiceSettingsUpdated: return "VOICE_SETTINGS_UPDATE";
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown RPC Global Event: {rpcEvent}");
|
||||
}
|
||||
}
|
||||
private static string GetEventName(RpcGuildEvent rpcEvent)
|
||||
{
|
||||
switch (rpcEvent)
|
||||
{
|
||||
case RpcGuildEvent.GuildStatus: return "GUILD_STATUS";
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown RPC Guild Event: {rpcEvent}");
|
||||
}
|
||||
}
|
||||
private static string GetEventName(RpcChannelEvent rpcEvent)
|
||||
{
|
||||
switch (rpcEvent)
|
||||
{
|
||||
case RpcChannelEvent.MessageCreate: return "MESSAGE_CREATE";
|
||||
case RpcChannelEvent.MessageUpdate: return "MESSAGE_UPDATE";
|
||||
case RpcChannelEvent.MessageDelete: return "MESSAGE_DELETE";
|
||||
case RpcChannelEvent.SpeakingStart: return "SPEAKING_START";
|
||||
case RpcChannelEvent.SpeakingStop: return "SPEAKING_STOP";
|
||||
case RpcChannelEvent.VoiceStateCreate: return "VOICE_STATE_CREATE";
|
||||
case RpcChannelEvent.VoiceStateUpdate: return "VOICE_STATE_UPDATE";
|
||||
case RpcChannelEvent.VoiceStateDelete: return "VOICE_STATE_DELETE";
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown RPC Channel Event: {rpcEvent}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessMessageAsync(string cmd, Optional<string> evnt, Optional<object> payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case "DISPATCH":
|
||||
switch (evnt.Value)
|
||||
{
|
||||
//Connection
|
||||
case "READY":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<ReadyEvent>(_serializer);
|
||||
|
||||
RequestOptions options = new RequestOptions
|
||||
{
|
||||
//CancellationToken = _cancelToken //TODO: Implement
|
||||
};
|
||||
|
||||
if (ApiClient.LoginState == LoginState.LoggedIn)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false);
|
||||
CurrentUser = RestSelfUser.Create(this, response.User);
|
||||
ApiClient.CurrentUserId = CurrentUser.Id;
|
||||
ApplicationInfo = RestApplication.Create(this, response.Application);
|
||||
Scopes = response.Scopes;
|
||||
TokenExpiresAt = response.Expires;
|
||||
|
||||
var __ = _connection.CompleteAsync();
|
||||
await _rpcLogger.InfoAsync("Ready").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _rpcLogger.ErrorAsync($"Error handling {cmd}{(evnt.IsSpecified ? $" ({evnt})" : "")}", ex).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var _ = _connection.CompleteAsync();
|
||||
await _rpcLogger.InfoAsync("Ready").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
//Channels
|
||||
case "CHANNEL_CREATE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<ChannelSummary>(_serializer);
|
||||
var channel = RpcChannelSummary.Create(data);
|
||||
|
||||
await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
|
||||
//Guilds
|
||||
case "GUILD_CREATE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<GuildSummary>(_serializer);
|
||||
var guild = RpcGuildSummary.Create(data);
|
||||
|
||||
await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case "GUILD_STATUS":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (GUILD_STATUS)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<GuildStatusEvent>(_serializer);
|
||||
var guildStatus = RpcGuildStatus.Create(data);
|
||||
|
||||
await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
|
||||
//Voice
|
||||
case "VOICE_STATE_CREATE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer);
|
||||
var voiceState = RpcVoiceState.Create(this, data);
|
||||
|
||||
await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case "VOICE_STATE_UPDATE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer);
|
||||
var voiceState = RpcVoiceState.Create(this, data);
|
||||
|
||||
await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case "VOICE_STATE_DELETE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer);
|
||||
var voiceState = RpcVoiceState.Create(this, data);
|
||||
|
||||
await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case "SPEAKING_START":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<SpeakingEvent>(_serializer);
|
||||
|
||||
await _speakingStartedEvent.InvokeAsync(data.UserId).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case "SPEAKING_STOP":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<SpeakingEvent>(_serializer);
|
||||
|
||||
await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case "VOICE_SETTINGS_UPDATE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_SETTINGS_UPDATE)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<API.Rpc.VoiceSettings>(_serializer);
|
||||
var settings = VoiceSettings.Create(data);
|
||||
|
||||
await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
|
||||
//Messages
|
||||
case "MESSAGE_CREATE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
|
||||
var msg = RpcMessage.Create(this, data.ChannelId, data.Message);
|
||||
|
||||
await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case "MESSAGE_UPDATE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
|
||||
var msg = RpcMessage.Create(this, data.ChannelId, data.Message);
|
||||
|
||||
await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case "MESSAGE_DELETE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false);
|
||||
var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
|
||||
|
||||
await _messageDeletedEvent.InvokeAsync(data.ChannelId, data.Message.Id).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
|
||||
//Others
|
||||
default:
|
||||
await _rpcLogger.WarningAsync($"Unknown Dispatch ({evnt})").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
/*default: //Other opcodes are used for command responses
|
||||
await _rpcLogger.WarningAsync($"Unknown OpCode ({cmd})").ConfigureAwait(false);
|
||||
return;*/
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _rpcLogger.ErrorAsync($"Error handling {cmd}{(evnt.IsSpecified ? $" ({evnt})" : "")}", ex).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//IDiscordClient
|
||||
ConnectionState IDiscordClient.ConnectionState => _connection.State;
|
||||
|
||||
Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => Task.FromResult<IApplication>(ApplicationInfo);
|
||||
|
||||
async Task IDiscordClient.StartAsync()
|
||||
=> await StartAsync().ConfigureAwait(false);
|
||||
async Task IDiscordClient.StopAsync()
|
||||
=> await StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
using Discord.Net.WebSockets;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public class DiscordRpcConfig : DiscordRestConfig
|
||||
{
|
||||
public const int RpcAPIVersion = 1;
|
||||
|
||||
public const int PortRangeStart = 6463;
|
||||
public const int PortRangeEnd = 6472;
|
||||
|
||||
/// <summary> Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. </summary>
|
||||
public int ConnectionTimeout { get; set; } = 30000;
|
||||
|
||||
/// <summary> Gets or sets the provider used to generate new WebSocket connections. </summary>
|
||||
public WebSocketProvider WebSocketProvider { get; set; }
|
||||
|
||||
public DiscordRpcConfig()
|
||||
{
|
||||
#if FILESYSTEM
|
||||
WebSocketProvider = () => new DefaultWebSocketClient();
|
||||
#else
|
||||
WebSocketProvider = () =>
|
||||
{
|
||||
throw new InvalidOperationException("The default WebSocket provider is not supported on this platform.\n" +
|
||||
"You must specify a WebSocketProvider or target a runtime supporting .NET Standard 1.3, such as .NET Framework 4.6+.");
|
||||
};
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public interface IRpcAudioChannel : IAudioChannel
|
||||
{
|
||||
IReadOnlyCollection<RpcVoiceState> VoiceStates { get; }
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public interface IRpcMessageChannel : IMessageChannel
|
||||
{
|
||||
IReadOnlyCollection<RpcMessage> CachedMessages { get; }
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
namespace Discord.Rpc
|
||||
{
|
||||
public interface IRpcPrivateChannel
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Rpc.Channel;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcCategoryChannel : RpcGuildChannel, ICategoryChannel
|
||||
{
|
||||
public IReadOnlyCollection<RpcMessage> CachedMessages { get; private set; }
|
||||
|
||||
public string Mention => MentionUtils.MentionChannel(Id);
|
||||
|
||||
internal RpcCategoryChannel(DiscordRpcClient discord, ulong id, ulong guildId)
|
||||
: base(discord, id, guildId)
|
||||
{
|
||||
}
|
||||
internal new static RpcCategoryChannel Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
var entity = new RpcCategoryChannel(discord, model.Id, model.GuildId.Value);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
CachedMessages = model.Messages.Select(x => RpcMessage.Create(Discord, Id, x)).ToImmutableArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
using Discord.Rest;
|
||||
using System;
|
||||
|
||||
using Model = Discord.API.Rpc.Channel;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public class RpcChannel : RpcEntity<ulong>
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
|
||||
|
||||
internal RpcChannel(DiscordRpcClient discord, ulong id)
|
||||
: base(discord, id)
|
||||
{
|
||||
}
|
||||
internal static RpcChannel Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
if (model.GuildId.IsSpecified)
|
||||
return RpcGuildChannel.Create(discord, model);
|
||||
else
|
||||
return CreatePrivate(discord, model);
|
||||
}
|
||||
internal static RpcChannel CreatePrivate(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
switch (model.Type)
|
||||
{
|
||||
case ChannelType.DM:
|
||||
return RpcDMChannel.Create(discord, model);
|
||||
case ChannelType.Group:
|
||||
return RpcGroupChannel.Create(discord, model);
|
||||
default:
|
||||
throw new InvalidOperationException($"Unexpected channel type: {model.Type}");
|
||||
}
|
||||
}
|
||||
internal virtual void Update(Model model)
|
||||
{
|
||||
if (model.Name.IsSpecified)
|
||||
Name = model.Name.Value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using Model = Discord.API.Rpc.ChannelSummary;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcChannelSummary
|
||||
{
|
||||
public ulong Id { get; }
|
||||
public string Name { get; private set; }
|
||||
public ChannelType Type { get; private set; }
|
||||
|
||||
internal RpcChannelSummary(ulong id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
internal static RpcChannelSummary Create(Model model)
|
||||
{
|
||||
var entity = new RpcChannelSummary(model.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
{
|
||||
Name = model.Name;
|
||||
Type = model.Type;
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
private string DebuggerDisplay => $"{Name} ({Id}, {Type})";
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Rpc.Channel;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcDMChannel : RpcChannel, IRpcMessageChannel, IRpcPrivateChannel, IDMChannel
|
||||
{
|
||||
public IReadOnlyCollection<RpcMessage> CachedMessages { get; private set; }
|
||||
|
||||
internal RpcDMChannel(DiscordRpcClient discord, ulong id)
|
||||
: base(discord, id)
|
||||
{
|
||||
}
|
||||
internal static new RpcDMChannel Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
var entity = new RpcDMChannel(discord, model.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
CachedMessages = model.Messages.Select(x => RpcMessage.Create(Discord, Id, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public Task CloseAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.DeleteAsync(this, Discord, options);
|
||||
|
||||
//TODO: Use RPC cache
|
||||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
|
||||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
|
||||
|
||||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
|
||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
|
||||
#if FILESYSTEM
|
||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null)
|
||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
|
||||
#endif
|
||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
|
||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);
|
||||
|
||||
public Task TriggerTypingAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
|
||||
public IDisposable EnterTypingState(RequestOptions options = null)
|
||||
=> ChannelHelper.EnterTypingState(this, Discord, options);
|
||||
|
||||
public override string ToString() => Id.ToString();
|
||||
private string DebuggerDisplay => $"({Id}, DM)";
|
||||
|
||||
//IDMChannel
|
||||
IUser IDMChannel.Recipient { get { throw new NotSupportedException(); } }
|
||||
|
||||
//IPrivateChannel
|
||||
IReadOnlyCollection<IUser> IPrivateChannel.Recipients { get { throw new NotSupportedException(); } }
|
||||
|
||||
//IMessageChannel
|
||||
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return await GetMessageAsync(id, options).ConfigureAwait(false);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return GetMessagesAsync(limit, options);
|
||||
else
|
||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return GetMessagesAsync(fromMessageId, dir, limit, options);
|
||||
else
|
||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return GetMessagesAsync(fromMessage, dir, limit, options);
|
||||
else
|
||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
|
||||
}
|
||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
|
||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
|
||||
|
||||
#if FILESYSTEM
|
||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options)
|
||||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false);
|
||||
#endif
|
||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
|
||||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false);
|
||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options)
|
||||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
|
||||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
|
||||
=> EnterTypingState(options);
|
||||
|
||||
//IChannel
|
||||
string IChannel.Name { get { throw new NotSupportedException(); } }
|
||||
|
||||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
using Discord.Audio;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Rpc.Channel;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcGroupChannel : RpcChannel, IRpcMessageChannel, IRpcAudioChannel, IRpcPrivateChannel, IGroupChannel
|
||||
{
|
||||
public IReadOnlyCollection<RpcMessage> CachedMessages { get; private set; }
|
||||
public IReadOnlyCollection<RpcVoiceState> VoiceStates { get; private set; }
|
||||
|
||||
internal RpcGroupChannel(DiscordRpcClient discord, ulong id)
|
||||
: base(discord, id)
|
||||
{
|
||||
}
|
||||
internal new static RpcGroupChannel Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
var entity = new RpcGroupChannel(discord, model.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
CachedMessages = model.Messages.Select(x => RpcMessage.Create(Discord, Id, x)).ToImmutableArray();
|
||||
VoiceStates = model.VoiceStates.Select(x => RpcVoiceState.Create(Discord, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public Task LeaveAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.DeleteAsync(this, Discord, options);
|
||||
|
||||
//TODO: Use RPC cache
|
||||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
|
||||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
|
||||
|
||||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
|
||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
|
||||
#if FILESYSTEM
|
||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null)
|
||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
|
||||
#endif
|
||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
|
||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);
|
||||
|
||||
public Task TriggerTypingAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
|
||||
public IDisposable EnterTypingState(RequestOptions options = null)
|
||||
=> ChannelHelper.EnterTypingState(this, Discord, options);
|
||||
|
||||
public override string ToString() => Id.ToString();
|
||||
private string DebuggerDisplay => $"({Id}, Group)";
|
||||
|
||||
//IPrivateChannel
|
||||
IReadOnlyCollection<IUser> IPrivateChannel.Recipients { get { throw new NotSupportedException(); } }
|
||||
|
||||
//IMessageChannel
|
||||
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return await GetMessageAsync(id, options).ConfigureAwait(false);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return GetMessagesAsync(limit, options);
|
||||
else
|
||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return GetMessagesAsync(fromMessageId, dir, limit, options);
|
||||
else
|
||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return GetMessagesAsync(fromMessage, dir, limit, options);
|
||||
else
|
||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
|
||||
}
|
||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
|
||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
|
||||
|
||||
#if FILESYSTEM
|
||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options)
|
||||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false);
|
||||
#endif
|
||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
|
||||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false);
|
||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options)
|
||||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
|
||||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
|
||||
=> EnterTypingState(options);
|
||||
|
||||
//IAudioChannel
|
||||
Task<IAudioClient> IAudioChannel.ConnectAsync(Action<IAudioClient> configAction) { throw new NotSupportedException(); }
|
||||
|
||||
//IChannel
|
||||
string IChannel.Name { get { throw new NotSupportedException(); } }
|
||||
|
||||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Rpc.Channel;
|
||||
using Discord.Rest;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public class RpcGuildChannel : RpcChannel, IGuildChannel
|
||||
{
|
||||
public ulong GuildId { get; }
|
||||
public int Position { get; private set; }
|
||||
public ulong? CategoryId { get; private set; }
|
||||
|
||||
internal RpcGuildChannel(DiscordRpcClient discord, ulong id, ulong guildId)
|
||||
: base(discord, id)
|
||||
{
|
||||
GuildId = guildId;
|
||||
}
|
||||
internal new static RpcGuildChannel Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
switch (model.Type)
|
||||
{
|
||||
case ChannelType.Text:
|
||||
return RpcTextChannel.Create(discord, model);
|
||||
case ChannelType.Voice:
|
||||
return RpcVoiceChannel.Create(discord, model);
|
||||
default:
|
||||
throw new InvalidOperationException("Unknown guild channel type");
|
||||
}
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
if (model.Position.IsSpecified)
|
||||
Position = model.Position.Value;
|
||||
}
|
||||
|
||||
public Task ModifyAsync(Action<GuildChannelProperties> func, RequestOptions options = null)
|
||||
=> ChannelHelper.ModifyAsync(this, Discord, func, options);
|
||||
public Task DeleteAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.DeleteAsync(this, Discord, options);
|
||||
|
||||
public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, RequestOptions options = null)
|
||||
=> ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms, options);
|
||||
public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, RequestOptions options = null)
|
||||
=> ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms, options);
|
||||
public Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
|
||||
=> ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options);
|
||||
public Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
|
||||
=> ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options);
|
||||
|
||||
public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
|
||||
=> await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
|
||||
public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
|
||||
=> await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
//IGuildChannel
|
||||
public Task<ICategoryChannel> GetCategoryAsync()
|
||||
{
|
||||
//Always fails
|
||||
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
|
||||
}
|
||||
|
||||
IGuild IGuildChannel.Guild
|
||||
{
|
||||
get
|
||||
{
|
||||
//Always fails
|
||||
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
|
||||
}
|
||||
}
|
||||
|
||||
async Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync(RequestOptions options)
|
||||
=> await GetInvitesAsync(options).ConfigureAwait(false);
|
||||
async Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
|
||||
=> await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
|
||||
|
||||
IReadOnlyCollection<Overwrite> IGuildChannel.PermissionOverwrites { get { throw new NotSupportedException(); } }
|
||||
OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
//IChannel
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Rpc.Channel;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcTextChannel : RpcGuildChannel, IRpcMessageChannel, ITextChannel
|
||||
{
|
||||
public IReadOnlyCollection<RpcMessage> CachedMessages { get; private set; }
|
||||
|
||||
public string Mention => MentionUtils.MentionChannel(Id);
|
||||
// TODO: Check if RPC includes the 'nsfw' field on Channel models
|
||||
public bool IsNsfw => ChannelHelper.IsNsfw(this);
|
||||
|
||||
internal RpcTextChannel(DiscordRpcClient discord, ulong id, ulong guildId)
|
||||
: base(discord, id, guildId)
|
||||
{
|
||||
}
|
||||
internal new static RpcVoiceChannel Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
var entity = new RpcVoiceChannel(discord, model.Id, model.GuildId.Value);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
CachedMessages = model.Messages.Select(x => RpcMessage.Create(Discord, Id, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
|
||||
=> ChannelHelper.ModifyAsync(this, Discord, func, options);
|
||||
|
||||
//TODO: Use RPC cache
|
||||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
|
||||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
|
||||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
|
||||
|
||||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
|
||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
|
||||
#if FILESYSTEM
|
||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null)
|
||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
|
||||
#endif
|
||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
|
||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);
|
||||
|
||||
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
|
||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
|
||||
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
|
||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);
|
||||
|
||||
public Task TriggerTypingAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
|
||||
public IDisposable EnterTypingState(RequestOptions options = null)
|
||||
=> ChannelHelper.EnterTypingState(this, Discord, options);
|
||||
|
||||
//Webhooks
|
||||
public Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
|
||||
=> ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options);
|
||||
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
|
||||
=> ChannelHelper.GetWebhookAsync(this, Discord, id, options);
|
||||
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.GetWebhooksAsync(this, Discord, options);
|
||||
|
||||
private string DebuggerDisplay => $"{Name} ({Id}, Text)";
|
||||
|
||||
//ITextChannel
|
||||
string ITextChannel.Topic { get { throw new NotSupportedException(); } }
|
||||
async Task<IWebhook> ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options)
|
||||
=> await CreateWebhookAsync(name, avatar, options);
|
||||
async Task<IWebhook> ITextChannel.GetWebhookAsync(ulong id, RequestOptions options)
|
||||
=> await GetWebhookAsync(id, options);
|
||||
async Task<IReadOnlyCollection<IWebhook>> ITextChannel.GetWebhooksAsync(RequestOptions options)
|
||||
=> await GetWebhooksAsync(options);
|
||||
|
||||
//IMessageChannel
|
||||
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return await GetMessageAsync(id, options).ConfigureAwait(false);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return GetMessagesAsync(limit, options);
|
||||
else
|
||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return GetMessagesAsync(fromMessageId, dir, limit, options);
|
||||
else
|
||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
|
||||
}
|
||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
if (mode == CacheMode.AllowDownload)
|
||||
return GetMessagesAsync(fromMessage, dir, limit, options);
|
||||
else
|
||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
|
||||
}
|
||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
|
||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
|
||||
|
||||
#if FILESYSTEM
|
||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options)
|
||||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false);
|
||||
#endif
|
||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
|
||||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false);
|
||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options)
|
||||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
|
||||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
|
||||
=> EnterTypingState(options);
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
using Discord.Audio;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Rpc.Channel;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcVoiceChannel : RpcGuildChannel, IRpcAudioChannel, IVoiceChannel
|
||||
{
|
||||
public int Bitrate { get; private set; }
|
||||
public int? UserLimit { get; private set; }
|
||||
public IReadOnlyCollection<RpcVoiceState> VoiceStates { get; private set; }
|
||||
|
||||
internal RpcVoiceChannel(DiscordRpcClient discord, ulong id, ulong guildId)
|
||||
: base(discord, id, guildId)
|
||||
{
|
||||
}
|
||||
internal new static RpcVoiceChannel Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
var entity = new RpcVoiceChannel(discord, model.Id, model.GuildId.Value);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
if (model.UserLimit.IsSpecified)
|
||||
UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null;
|
||||
if (model.Bitrate.IsSpecified)
|
||||
Bitrate = model.Bitrate.Value;
|
||||
VoiceStates = model.VoiceStates.Select(x => RpcVoiceState.Create(Discord, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null)
|
||||
=> ChannelHelper.ModifyAsync(this, Discord, func, options);
|
||||
|
||||
private string DebuggerDisplay => $"{Name} ({Id}, Voice)";
|
||||
|
||||
//IAudioChannel
|
||||
Task<IAudioClient> IAudioChannel.ConnectAsync(Action<IAudioClient> configAction) { throw new NotSupportedException(); }
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Model = Discord.API.Rpc.Guild;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcGuild : RpcEntity<ulong>
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public string IconUrl { get; private set; }
|
||||
public IReadOnlyCollection<RpcGuildUser> Users { get; private set; }
|
||||
|
||||
internal RpcGuild(DiscordRpcClient discord, ulong id)
|
||||
: base(discord, id)
|
||||
{
|
||||
}
|
||||
internal static RpcGuild Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
var entity = new RpcGuild(discord, model.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
{
|
||||
Name = model.Name;
|
||||
IconUrl = model.IconUrl;
|
||||
Users = model.Members.Select(x => RpcGuildUser.Create(Discord, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
private string DebuggerDisplay => $"{Name} ({Id})";
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using Model = Discord.API.Rpc.GuildStatusEvent;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcGuildStatus
|
||||
{
|
||||
public RpcGuildSummary Guild { get; }
|
||||
public int Online { get; private set; }
|
||||
|
||||
internal RpcGuildStatus(ulong guildId)
|
||||
{
|
||||
Guild = new RpcGuildSummary(guildId);
|
||||
}
|
||||
internal static RpcGuildStatus Create(Model model)
|
||||
{
|
||||
var entity = new RpcGuildStatus(model.Guild.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
{
|
||||
Online = model.Online;
|
||||
}
|
||||
|
||||
public override string ToString() => Guild.Name;
|
||||
private string DebuggerDisplay => $"{Guild.Name} ({Guild.Id}, {Online} Online)";
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using Model = Discord.API.Rpc.GuildSummary;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcGuildSummary
|
||||
{
|
||||
public ulong Id { get; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
internal RpcGuildSummary(ulong id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
internal static RpcGuildSummary Create(Model model)
|
||||
{
|
||||
var entity = new RpcGuildSummary(model.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
{
|
||||
Name = model.Name;
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
private string DebuggerDisplay => $"{Name} ({Id})";
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Rpc.Message;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public abstract class RpcMessage : RpcEntity<ulong>, IMessage
|
||||
{
|
||||
private long _timestampTicks;
|
||||
|
||||
public IMessageChannel Channel { get; }
|
||||
public RpcUser Author { get; }
|
||||
public MessageSource Source { get; }
|
||||
|
||||
public string Content { get; private set; }
|
||||
public Color AuthorColor { get; private set; }
|
||||
|
||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
|
||||
public virtual bool IsTTS => false;
|
||||
public virtual bool IsPinned => false;
|
||||
public virtual bool IsBlocked => false;
|
||||
public virtual DateTimeOffset? EditedTimestamp => null;
|
||||
public virtual IReadOnlyCollection<Attachment> Attachments => ImmutableArray.Create<Attachment>();
|
||||
public virtual IReadOnlyCollection<Embed> Embeds => ImmutableArray.Create<Embed>();
|
||||
public virtual IReadOnlyCollection<ulong> MentionedChannelIds => ImmutableArray.Create<ulong>();
|
||||
public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>();
|
||||
public virtual IReadOnlyCollection<ulong> MentionedUserIds => ImmutableArray.Create<ulong>();
|
||||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>();
|
||||
public virtual ulong? WebhookId => null;
|
||||
public bool IsWebhook => WebhookId != null;
|
||||
|
||||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks);
|
||||
|
||||
internal RpcMessage(DiscordRpcClient discord, ulong id, RestVirtualMessageChannel channel, RpcUser author, MessageSource source)
|
||||
: base(discord, id)
|
||||
{
|
||||
Channel = channel;
|
||||
Author = author;
|
||||
Source = source;
|
||||
}
|
||||
internal static RpcMessage Create(DiscordRpcClient discord, ulong channelId, Model model)
|
||||
{
|
||||
//model.ChannelId is always 0, needs to be passed from the event
|
||||
if (model.Type == MessageType.Default)
|
||||
return RpcUserMessage.Create(discord, channelId, model);
|
||||
else
|
||||
return RpcSystemMessage.Create(discord, channelId, model);
|
||||
}
|
||||
internal virtual void Update(Model model)
|
||||
{
|
||||
if (model.Timestamp.IsSpecified)
|
||||
_timestampTicks = model.Timestamp.Value.UtcTicks;
|
||||
|
||||
if (model.Content.IsSpecified)
|
||||
Content = model.Content.Value;
|
||||
if (model.AuthorColor.IsSpecified)
|
||||
AuthorColor = new Color(Convert.ToUInt32(model.AuthorColor.Value.Substring(1), 16));
|
||||
}
|
||||
|
||||
public Task DeleteAsync(RequestOptions options = null)
|
||||
=> MessageHelper.DeleteAsync(this, Discord, options);
|
||||
|
||||
public override string ToString() => Content;
|
||||
|
||||
//IMessage
|
||||
IMessageChannel IMessage.Channel => Channel;
|
||||
MessageType IMessage.Type => MessageType.Default;
|
||||
IUser IMessage.Author => Author;
|
||||
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments;
|
||||
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds;
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
using Discord.Rest;
|
||||
using System.Diagnostics;
|
||||
using Model = Discord.API.Rpc.Message;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcSystemMessage : RpcMessage, ISystemMessage
|
||||
{
|
||||
public MessageType Type { get; private set; }
|
||||
|
||||
internal RpcSystemMessage(DiscordRpcClient discord, ulong id, RestVirtualMessageChannel channel, RpcUser author)
|
||||
: base(discord, id, channel, author, MessageSource.System)
|
||||
{
|
||||
}
|
||||
internal new static RpcSystemMessage Create(DiscordRpcClient discord, ulong channelId, Model model)
|
||||
{
|
||||
var entity = new RpcSystemMessage(discord, model.Id,
|
||||
RestVirtualMessageChannel.Create(discord, channelId),
|
||||
RpcUser.Create(discord, model.Author.Value, model.WebhookId.ToNullable()));
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
|
||||
Type = model.Type;
|
||||
}
|
||||
|
||||
private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})";
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Rpc.Message;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcUserMessage : RpcMessage, IUserMessage
|
||||
{
|
||||
private bool _isMentioningEveryone, _isTTS, _isPinned, _isBlocked;
|
||||
private long? _editedTimestampTicks;
|
||||
private ulong? _webhookId;
|
||||
private ImmutableArray<Attachment> _attachments;
|
||||
private ImmutableArray<Embed> _embeds;
|
||||
private ImmutableArray<ITag> _tags;
|
||||
|
||||
public override bool IsTTS => _isTTS;
|
||||
public override bool IsPinned => _isPinned;
|
||||
public override bool IsBlocked => _isBlocked;
|
||||
public override ulong? WebhookId => _webhookId;
|
||||
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks);
|
||||
public override IReadOnlyCollection<Attachment> Attachments => _attachments;
|
||||
public override IReadOnlyCollection<Embed> Embeds => _embeds;
|
||||
public override IReadOnlyCollection<ulong> MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags);
|
||||
public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags);
|
||||
public override IReadOnlyCollection<ulong> MentionedUserIds => MessageHelper.FilterTagsByKey(TagType.UserMention, _tags);
|
||||
public override IReadOnlyCollection<ITag> Tags => _tags;
|
||||
public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => ImmutableDictionary.Create<IEmote, ReactionMetadata>();
|
||||
|
||||
internal RpcUserMessage(DiscordRpcClient discord, ulong id, RestVirtualMessageChannel channel, RpcUser author, MessageSource source)
|
||||
: base(discord, id, channel, author, source)
|
||||
{
|
||||
}
|
||||
internal new static RpcUserMessage Create(DiscordRpcClient discord, ulong channelId, Model model)
|
||||
{
|
||||
var entity = new RpcUserMessage(discord, model.Id,
|
||||
RestVirtualMessageChannel.Create(discord, channelId),
|
||||
RpcUser.Create(discord, model.Author.Value, model.WebhookId.ToNullable()),
|
||||
MessageHelper.GetSource(model));
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
|
||||
internal override void Update(Model model)
|
||||
{
|
||||
base.Update(model);
|
||||
|
||||
if (model.IsTextToSpeech.IsSpecified)
|
||||
_isTTS = model.IsTextToSpeech.Value;
|
||||
if (model.Pinned.IsSpecified)
|
||||
_isPinned = model.Pinned.Value;
|
||||
if (model.EditedTimestamp.IsSpecified)
|
||||
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks;
|
||||
if (model.MentionEveryone.IsSpecified)
|
||||
_isMentioningEveryone = model.MentionEveryone.Value;
|
||||
if (model.WebhookId.IsSpecified)
|
||||
_webhookId = model.WebhookId.Value;
|
||||
|
||||
if (model.IsBlocked.IsSpecified)
|
||||
_isBlocked = model.IsBlocked.Value;
|
||||
|
||||
if (model.Attachments.IsSpecified)
|
||||
{
|
||||
var value = model.Attachments.Value;
|
||||
if (value.Length > 0)
|
||||
{
|
||||
var attachments = ImmutableArray.CreateBuilder<Attachment>(value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
attachments.Add(Attachment.Create(value[i]));
|
||||
_attachments = attachments.ToImmutable();
|
||||
}
|
||||
else
|
||||
_attachments = ImmutableArray.Create<Attachment>();
|
||||
}
|
||||
|
||||
if (model.Embeds.IsSpecified)
|
||||
{
|
||||
var value = model.Embeds.Value;
|
||||
if (value.Length > 0)
|
||||
{
|
||||
var embeds = ImmutableArray.CreateBuilder<Embed>(value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
embeds.Add(value[i].ToEntity());
|
||||
_embeds = embeds.ToImmutable();
|
||||
}
|
||||
else
|
||||
_embeds = ImmutableArray.Create<Embed>();
|
||||
}
|
||||
|
||||
if (model.Content.IsSpecified)
|
||||
{
|
||||
var text = model.Content.Value;
|
||||
_tags = MessageHelper.ParseTags(text, null, null, ImmutableArray.Create<IUser>());
|
||||
model.Content = text;
|
||||
}
|
||||
}
|
||||
|
||||
public Task ModifyAsync(Action<MessageProperties> func, RequestOptions options)
|
||||
=> MessageHelper.ModifyAsync(this, Discord, func, options);
|
||||
|
||||
public Task AddReactionAsync(IEmote emote, RequestOptions options = null)
|
||||
=> MessageHelper.AddReactionAsync(this, emote, Discord, options);
|
||||
public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null)
|
||||
=> MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options);
|
||||
public Task RemoveAllReactionsAsync(RequestOptions options = null)
|
||||
=> MessageHelper.RemoveAllReactionsAsync(this, Discord, options);
|
||||
public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit = 100, ulong? afterUserId = null, RequestOptions options = null)
|
||||
=> MessageHelper.GetReactionUsersAsync(this, emote, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create<ulong>(); }, Discord, options);
|
||||
|
||||
public Task PinAsync(RequestOptions options)
|
||||
=> MessageHelper.PinAsync(this, Discord, options);
|
||||
public Task UnpinAsync(RequestOptions options)
|
||||
=> MessageHelper.UnpinAsync(this, Discord, options);
|
||||
|
||||
public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
|
||||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
|
||||
=> MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
|
||||
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
|
||||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
|
||||
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
|
||||
|
||||
private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})";
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public abstract class RpcEntity<T> : IEntity<T>
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
internal DiscordRpcClient Discord { get; }
|
||||
public T Id { get; }
|
||||
|
||||
internal RpcEntity(DiscordRpcClient discord, T id)
|
||||
{
|
||||
Discord = discord;
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public class UserVoiceProperties
|
||||
{
|
||||
[JsonProperty("userId")]
|
||||
internal ulong UserId { get; set; }
|
||||
[JsonProperty("pan")]
|
||||
public Optional<Pan> Pan { get; set; }
|
||||
[JsonProperty("volume")]
|
||||
public Optional<int> Volume { get; set; }
|
||||
[JsonProperty("mute")]
|
||||
public Optional<bool> Mute { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using Model = Discord.API.Rpc.Pan;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public struct Pan
|
||||
{
|
||||
public float Left { get; }
|
||||
public float Right { get; }
|
||||
|
||||
public Pan(float left, float right)
|
||||
{
|
||||
Left = left;
|
||||
Right = right;
|
||||
}
|
||||
internal static Pan Create(Model model)
|
||||
{
|
||||
return new Pan(model.Left, model.Right);
|
||||
}
|
||||
|
||||
public override string ToString() => $"Left = {Left}, Right = {Right}";
|
||||
private string DebuggerDisplay => $"Left = {Left}, Right = {Right}";
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
using Model = Discord.API.Rpc.GuildMember;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public class RpcGuildUser : RpcUser
|
||||
{
|
||||
private UserStatus _status;
|
||||
|
||||
public override UserStatus Status => _status;
|
||||
//public object Acitivity { get; private set; }
|
||||
|
||||
internal RpcGuildUser(DiscordRpcClient discord, ulong id)
|
||||
: base(discord, id)
|
||||
{
|
||||
}
|
||||
internal static RpcGuildUser Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
var entity = new RpcGuildUser(discord, model.User.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
{
|
||||
base.Update(model.User);
|
||||
_status = model.Status;
|
||||
//Activity = model.Activity;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.User;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcUser : RpcEntity<ulong>, IUser
|
||||
{
|
||||
public bool IsBot { get; private set; }
|
||||
public string Username { get; private set; }
|
||||
public ushort DiscriminatorValue { get; private set; }
|
||||
public string AvatarId { get; private set; }
|
||||
|
||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
|
||||
public string Discriminator => DiscriminatorValue.ToString("D4");
|
||||
public string Mention => MentionUtils.MentionUser(Id);
|
||||
public virtual bool IsWebhook => false;
|
||||
public virtual IActivity Activity => null;
|
||||
public virtual UserStatus Status => UserStatus.Offline;
|
||||
|
||||
internal RpcUser(DiscordRpcClient discord, ulong id)
|
||||
: base(discord, id)
|
||||
{
|
||||
}
|
||||
internal static RpcUser Create(DiscordRpcClient discord, Model model)
|
||||
=> Create(discord, model, null);
|
||||
internal static RpcUser Create(DiscordRpcClient discord, Model model, ulong? webhookId)
|
||||
{
|
||||
RpcUser entity;
|
||||
if (webhookId.HasValue)
|
||||
entity = new RpcWebhookUser(discord, model.Id, webhookId.Value);
|
||||
else
|
||||
entity = new RpcUser(discord, model.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal virtual void Update(Model model)
|
||||
{
|
||||
if (model.Avatar.IsSpecified)
|
||||
AvatarId = model.Avatar.Value;
|
||||
if (model.Discriminator.IsSpecified)
|
||||
DiscriminatorValue = ushort.Parse(model.Discriminator.Value);
|
||||
if (model.Bot.IsSpecified)
|
||||
IsBot = model.Bot.Value;
|
||||
if (model.Username.IsSpecified)
|
||||
Username = model.Username.Value;
|
||||
}
|
||||
|
||||
public Task<RestDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null)
|
||||
=> UserHelper.CreateDMChannelAsync(this, Discord, options);
|
||||
|
||||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
|
||||
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format);
|
||||
|
||||
public override string ToString() => $"{Username}#{Discriminator}";
|
||||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})";
|
||||
|
||||
//IUser
|
||||
async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options)
|
||||
=> await GetOrCreateDMChannelAsync(options);
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using Model = Discord.API.Rpc.ExtendedVoiceState;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcVoiceState : IVoiceState
|
||||
{
|
||||
[Flags]
|
||||
private enum Flags : byte
|
||||
{
|
||||
Normal = 0x00,
|
||||
Suppressed = 0x01,
|
||||
Muted = 0x02,
|
||||
Deafened = 0x04,
|
||||
SelfMuted = 0x08,
|
||||
SelfDeafened = 0x10,
|
||||
}
|
||||
|
||||
private Flags _voiceStates;
|
||||
|
||||
public RpcUser User { get; }
|
||||
public string Nickname { get; private set; }
|
||||
public int Volume { get; private set; }
|
||||
public bool IsMuted2 { get; private set; }
|
||||
public Pan Pan { get; private set; }
|
||||
|
||||
public bool IsMuted => (_voiceStates & Flags.Muted) != 0;
|
||||
public bool IsDeafened => (_voiceStates & Flags.Deafened) != 0;
|
||||
public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0;
|
||||
public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0;
|
||||
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0;
|
||||
|
||||
internal RpcVoiceState(DiscordRpcClient discord, ulong userId)
|
||||
{
|
||||
User = new RpcUser(discord, userId);
|
||||
}
|
||||
internal static RpcVoiceState Create(DiscordRpcClient discord, Model model)
|
||||
{
|
||||
var entity = new RpcVoiceState(discord, model.User.Id);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
{
|
||||
if (model.VoiceState.IsSpecified)
|
||||
{
|
||||
Flags voiceStates = Flags.Normal;
|
||||
if (model.VoiceState.Value.Mute)
|
||||
voiceStates |= Flags.Muted;
|
||||
if (model.VoiceState.Value.Deaf)
|
||||
voiceStates |= Flags.Deafened;
|
||||
if (model.VoiceState.Value.SelfMute)
|
||||
voiceStates |= Flags.SelfMuted;
|
||||
if (model.VoiceState.Value.SelfDeaf)
|
||||
voiceStates |= Flags.SelfDeafened;
|
||||
if (model.VoiceState.Value.Suppress)
|
||||
voiceStates |= Flags.Suppressed;
|
||||
_voiceStates = voiceStates;
|
||||
}
|
||||
User.Update(model.User);
|
||||
if (model.Nickname.IsSpecified)
|
||||
Nickname = model.Nickname.Value;
|
||||
if (model.Volume.IsSpecified)
|
||||
Volume = model.Volume.Value;
|
||||
if (model.Mute.IsSpecified)
|
||||
IsMuted2 = model.Mute.Value;
|
||||
if (model.Pan.IsSpecified)
|
||||
Pan = Pan.Create(model.Pan.Value);
|
||||
}
|
||||
|
||||
public override string ToString() => User.ToString();
|
||||
private string DebuggerDisplay => $"{User} ({_voiceStates})";
|
||||
|
||||
string IVoiceState.VoiceSessionId { get { throw new NotSupportedException(); } }
|
||||
IVoiceChannel IVoiceState.VoiceChannel { get { throw new NotSupportedException(); } }
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using Model = Discord.API.User;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class RpcWebhookUser : RpcUser
|
||||
{
|
||||
public ulong WebhookId { get; }
|
||||
|
||||
public override bool IsWebhook => true;
|
||||
|
||||
internal RpcWebhookUser(DiscordRpcClient discord, ulong id, ulong webhookId)
|
||||
: base(discord, id)
|
||||
{
|
||||
WebhookId = webhookId;
|
||||
}
|
||||
internal static RpcWebhookUser Create(DiscordRpcClient discord, Model model, ulong webhookId)
|
||||
{
|
||||
var entity = new RpcWebhookUser(discord, model.Id, webhookId);
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using Model = Discord.API.Rpc.VoiceDevice;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public struct VoiceDevice
|
||||
{
|
||||
public string Id { get; }
|
||||
public string Name { get; }
|
||||
|
||||
internal VoiceDevice(string id, string name)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
internal static VoiceDevice Create(Model model)
|
||||
{
|
||||
return new VoiceDevice(model.Id, model.Name);
|
||||
}
|
||||
|
||||
public override string ToString() => $"{Name}";
|
||||
private string DebuggerDisplay => $"{Name} ({Id})";
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace Discord.Rpc
|
||||
{
|
||||
public class VoiceDeviceProperties
|
||||
{
|
||||
public Optional<string> DeviceId { get; set; }
|
||||
public Optional<float> Volume { get; set; }
|
||||
public Optional<VoiceDevice[]> AvailableDevices { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
namespace Discord.Rpc
|
||||
{
|
||||
public class VoiceModeProperties
|
||||
{
|
||||
public Optional<string> Type { get; set; }
|
||||
public Optional<bool> AutoThreshold { get; set; }
|
||||
public Optional<float> Threshold { get; set; }
|
||||
public Optional<VoiceShortcut[]> Shortcut { get; set; }
|
||||
public Optional<float> Delay { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
namespace Discord.Rpc
|
||||
{
|
||||
public class VoiceProperties
|
||||
{
|
||||
public VoiceDeviceProperties Input { get; set; }
|
||||
public VoiceDeviceProperties Output { get; set; }
|
||||
public VoiceModeProperties Mode { get; set; }
|
||||
public Optional<bool> AutomaticGainControl { get; set; }
|
||||
public Optional<bool> EchoCancellation { get; set; }
|
||||
public Optional<bool> NoiseSuppression { get; set; }
|
||||
public Optional<bool> QualityOfService { get; set; }
|
||||
public Optional<bool> SilenceWarning { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Model = Discord.API.Rpc.VoiceSettings;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
public class VoiceSettings
|
||||
{
|
||||
public string InputDeviceId { get; private set; }
|
||||
public float InputVolume { get; private set; }
|
||||
public IReadOnlyCollection<VoiceDevice> AvailableInputDevices { get; private set; }
|
||||
|
||||
public string OutputDeviceId { get; private set; }
|
||||
public float OutputVolume { get; private set; }
|
||||
public IReadOnlyCollection<VoiceDevice> AvailableOutputDevices { get; private set; }
|
||||
|
||||
public bool AutomaticGainControl { get; private set; }
|
||||
public bool EchoCancellation { get; private set; }
|
||||
public bool NoiseSuppression { get; private set; }
|
||||
public bool QualityOfService { get; private set; }
|
||||
public bool SilenceWarning { get; private set; }
|
||||
|
||||
public string ActivationMode { get; private set; }
|
||||
public bool AutoThreshold { get; private set; }
|
||||
public float Threshold { get; private set; }
|
||||
public IReadOnlyCollection<VoiceShortcut> Shortcuts { get; private set; }
|
||||
public float Delay { get; private set; }
|
||||
|
||||
internal VoiceSettings() { }
|
||||
internal static VoiceSettings Create(Model model)
|
||||
{
|
||||
var entity = new VoiceSettings();
|
||||
entity.Update(model);
|
||||
return entity;
|
||||
}
|
||||
internal void Update(Model model)
|
||||
{
|
||||
if (model.AutomaticGainControl.IsSpecified)
|
||||
AutomaticGainControl = model.AutomaticGainControl.Value;
|
||||
if (model.EchoCancellation.IsSpecified)
|
||||
EchoCancellation = model.EchoCancellation.Value;
|
||||
if (model.NoiseSuppression.IsSpecified)
|
||||
NoiseSuppression = model.NoiseSuppression.Value;
|
||||
if (model.QualityOfService.IsSpecified)
|
||||
QualityOfService = model.QualityOfService.Value;
|
||||
if (model.SilenceWarning.IsSpecified)
|
||||
SilenceWarning = model.SilenceWarning.Value;
|
||||
|
||||
if (model.Input.DeviceId.IsSpecified)
|
||||
InputDeviceId = model.Input.DeviceId.Value;
|
||||
if (model.Input.Volume.IsSpecified)
|
||||
InputVolume = model.Input.Volume.Value;
|
||||
if (model.Input.AvailableDevices.IsSpecified)
|
||||
AvailableInputDevices = model.Input.AvailableDevices.Value.Select(x => VoiceDevice.Create(x)).ToImmutableArray();
|
||||
|
||||
if (model.Output.DeviceId.IsSpecified)
|
||||
OutputDeviceId = model.Output.DeviceId.Value;
|
||||
if (model.Output.Volume.IsSpecified)
|
||||
OutputVolume = model.Output.Volume.Value;
|
||||
if (model.Output.AvailableDevices.IsSpecified)
|
||||
AvailableInputDevices = model.Output.AvailableDevices.Value.Select(x => VoiceDevice.Create(x)).ToImmutableArray();
|
||||
|
||||
if (model.Mode.Type.IsSpecified)
|
||||
ActivationMode = model.Mode.Type.Value;
|
||||
if (model.Mode.AutoThreshold.IsSpecified)
|
||||
AutoThreshold = model.Mode.AutoThreshold.Value;
|
||||
if (model.Mode.Threshold.IsSpecified)
|
||||
Threshold = model.Mode.Threshold.Value;
|
||||
if (model.Mode.Shortcut.IsSpecified)
|
||||
Shortcuts = model.Mode.Shortcut.Value.Select(x => VoiceShortcut.Create(x)).ToImmutableArray();
|
||||
if (model.Mode.Delay.IsSpecified)
|
||||
Delay = model.Mode.Delay.Value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using Model = Discord.API.Rpc.VoiceShortcut;
|
||||
|
||||
namespace Discord.Rpc
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public struct VoiceShortcut
|
||||
{
|
||||
public VoiceShortcutType Type { get; }
|
||||
public int Code { get; }
|
||||
public string Name { get; }
|
||||
|
||||
internal VoiceShortcut(VoiceShortcutType type, int code, string name)
|
||||
{
|
||||
Type = type;
|
||||
Code = code;
|
||||
Name = name;
|
||||
}
|
||||
internal static VoiceShortcut Create(Model model)
|
||||
{
|
||||
return new VoiceShortcut(model.Type.Value, model.Code.Value, model.Name.Value);
|
||||
}
|
||||
|
||||
public override string ToString() => $"{Name}";
|
||||
private string DebuggerDisplay => $"{Name} ({Code}, {Type})";
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
namespace Discord.Rpc
|
||||
{
|
||||
public enum VoiceShortcutType
|
||||
{
|
||||
KeyboardKey = 0,
|
||||
MouseButton = 1,
|
||||
KeyboardModifierKey = 2,
|
||||
GamepadButton = 3
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
namespace Discord.Rpc
|
||||
{
|
||||
internal static class EntityExtensions
|
||||
{
|
||||
public static API.Rpc.Pan ToModel(this Pan entity)
|
||||
{
|
||||
return new API.Rpc.Pan
|
||||
{
|
||||
Left = entity.Left,
|
||||
Right = entity.Right
|
||||
};
|
||||
}
|
||||
public static API.Rpc.VoiceDevice ToModel(this VoiceDevice entity)
|
||||
{
|
||||
return new API.Rpc.VoiceDevice
|
||||
{
|
||||
Id = entity.Id,
|
||||
Name = entity.Name
|
||||
};
|
||||
}
|
||||
public static API.Rpc.VoiceShortcut ToModel(this VoiceShortcut entity)
|
||||
{
|
||||
return new API.Rpc.VoiceShortcut
|
||||
{
|
||||
Code = entity.Code,
|
||||
Name = entity.Name,
|
||||
Type = entity.Type
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
namespace Discord.Rpc
|
||||
{
|
||||
public enum RpcChannelEvent
|
||||
{
|
||||
VoiceStateCreate,
|
||||
VoiceStateUpdate,
|
||||
VoiceStateDelete,
|
||||
SpeakingStart,
|
||||
SpeakingStop,
|
||||
MessageCreate,
|
||||
MessageUpdate,
|
||||
MessageDelete
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace Discord.Rpc
|
||||
{
|
||||
public enum RpcGlobalEvent
|
||||
{
|
||||
ChannelCreated,
|
||||
GuildCreated,
|
||||
VoiceSettingsUpdated
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
namespace Discord.Rpc
|
||||
{
|
||||
public enum RpcGuildEvent
|
||||
{
|
||||
GuildStatus
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Discord.Net.Tests")]
|
||||
[assembly: InternalsVisibleTo("Discord.Net.Tests.Unit")]
|
|
@ -1,41 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks the aliases for a command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute allows a command to have one or multiple aliases. In other words, the base command can have
|
||||
/// multiple aliases when triggering the command itself, giving the end-user more freedom of choices when giving
|
||||
/// hot-words to trigger the desired command. See the example for a better illustration.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// In the following example, the command can be triggered with the base name, "stats", or either "stat" or
|
||||
/// "info".
|
||||
/// <code language="cs">
|
||||
/// [Command("stats")]
|
||||
/// [Alias("stat", "info")]
|
||||
/// public <see langword="async"/> Task GetStatsAsync(IUser user)
|
||||
/// {
|
||||
/// // ...pull stats
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class AliasAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the aliases which have been defined for the command.
|
||||
/// </summary>
|
||||
public string[] Aliases { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="AliasAttribute" /> with the given aliases.
|
||||
/// </summary>
|
||||
public AliasAttribute(params string[] aliases)
|
||||
{
|
||||
Aliases = aliases;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks the execution information for a command.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class CommandAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the text that has been set to be recognized as a command.
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
/// <summary>
|
||||
/// Specifies the <see cref="RunMode" /> of the command. This affects how the command is executed.
|
||||
/// </summary>
|
||||
public RunMode RunMode { get; set; } = RunMode.Default;
|
||||
public bool? IgnoreExtraArgs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a summary to your command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="Summary"/> overrides the value of this property if present.
|
||||
/// </remarks>
|
||||
public string Summary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Marks the aliases for a command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="AliasAttribute"/> extends the base value of this if present.
|
||||
/// </remarks>
|
||||
public string[] Aliases { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attaches remarks to your commands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="RemainderAttribute"/> overrides the value of this property if present.
|
||||
/// </remarks>
|
||||
public string Remarks { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public CommandAttribute()
|
||||
{
|
||||
Text = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="CommandAttribute" /> attribute with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="text">The name of the command.</param>
|
||||
public CommandAttribute(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
public CommandAttribute(string text, bool ignoreExtraArgs)
|
||||
{
|
||||
Text = text;
|
||||
IgnoreExtraArgs = ignoreExtraArgs;
|
||||
}
|
||||
|
||||
public CommandAttribute(string text, bool ignoreExtraArgs, string summary = default, string[] aliases = default, string remarks = default)
|
||||
{
|
||||
Text = text;
|
||||
IgnoreExtraArgs = ignoreExtraArgs;
|
||||
Summary = summary;
|
||||
Aliases = aliases;
|
||||
Remarks = remarks;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents the marked module from being loaded automatically.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute tells <see cref="CommandService" /> to ignore the marked module from being loaded
|
||||
/// automatically (e.g. the <see cref="CommandService.AddModulesAsync" /> method). If a non-public module marked
|
||||
/// with this attribute is attempted to be loaded manually, the loading process will also fail.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class DontAutoLoadAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents the marked property from being injected into a module.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute prevents the marked member from being injected into its parent module. Useful when you have a
|
||||
/// public property that you do not wish to invoke the library's dependency injection service.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// In the following example, <c>DatabaseService</c> will not be automatically injected into the module and will
|
||||
/// not throw an error message if the dependency fails to be resolved.
|
||||
/// <code language="cs">
|
||||
/// public class MyModule : ModuleBase
|
||||
/// {
|
||||
/// [DontInject]
|
||||
/// public DatabaseService DatabaseService;
|
||||
/// public MyModule()
|
||||
/// {
|
||||
/// DatabaseService = DatabaseFactory.Generate();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public class DontInjectAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue