diff --git a/.gitignore b/.gitignore index 9eb2e047..a0409be7 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ .vs/ .vscode/ .idea/ +[Pp]roperties/ # Build results [Dd]ebug/ diff --git a/.gitmodules b/.gitmodules index 3d0f5fc4..ca8763b0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "TS3Client/Declarations"] - path = TS3Client/Declarations +[submodule "TSLib/Declarations"] + path = TSLib/Declarations url = https://github.com/ReSpeak/tsdeclarations diff --git a/Directory.Build.targets b/Directory.Build.targets index 77337080..09d17b02 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,6 +1,8 @@ - - + + + + diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000..063f877f --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,23 @@ +assembly-versioning-scheme: MajorMinorPatch +mode: Mainline +branches: + master: + tag: '' + increment: Patch + prevent-increment-of-merged-branch-version: true + track-merge-target: false + regex: ^master$ + tracks-release-branches: false + is-release-branch: false + is-mainline: true + develop: + tag: alpha + increment: Minor + prevent-increment-of-merged-branch-version: false + track-merge-target: true + regex: ^develop$ + tracks-release-branches: true + is-release-branch: false +ignore: + sha: [] +merge-message-formats: {} diff --git a/README.md b/README.md index 530cc523..1144a0bf 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ Translations need to be manually approved and will then be automatically built a This project is licensed under [OSL-3.0](https://opensource.org/licenses/OSL-3.0). Why OSL-3.0: -- OSL allows you to link to our libraries without needing to disclose your own project, which might be useful if you want to use the TS3Client as a library. +- OSL allows you to link to our libraries without needing to disclose your own project, which might be useful if you want to use the TSLib as a library. - If you create plugins you do not have to make them public like in GPL. (Although we would be happy if you shared them :) - With OSL we want to allow you providing the TS3AB as a service (even commercially). We do not want the software to be sold but the service. We want this software to be free for everyone. - TL; DR? https://tldrlegal.com/license/open-software-licence-3.0 diff --git a/TS3ABotUnitTests/BotCommandTests.cs b/TS3ABotUnitTests/BotCommandTests.cs index 4bc37874..14376c5f 100644 --- a/TS3ABotUnitTests/BotCommandTests.cs +++ b/TS3ABotUnitTests/BotCommandTests.cs @@ -1,48 +1,31 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading; +using TS3AudioBot; +using TS3AudioBot.Algorithm; +using TS3AudioBot.CommandSystem; +using TS3AudioBot.CommandSystem.Ast; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.CommandSystem.Commands; +using TS3AudioBot.Dependency; +using TSLib; namespace TS3ABotUnitTests { - using NUnit.Framework; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Reflection; - using System.Threading; - using TS3AudioBot; - using TS3AudioBot.Dependency; - using TS3AudioBot.Algorithm; - using TS3AudioBot.Audio; - using TS3AudioBot.CommandSystem; - using TS3AudioBot.CommandSystem.Ast; - using TS3AudioBot.CommandSystem.CommandResults; - using TS3AudioBot.CommandSystem.Commands; - [TestFixture] public class BotCommandTests { - private readonly CommandManager cmdMgr; - - public BotCommandTests() - { - cmdMgr = new CommandManager(null); - cmdMgr.RegisterCollection(MainCommands.Bag); - } - [Test] public void BotCommandTest() { var execInfo = Utils.GetExecInfo("ic3"); string CallCommand(string command) { - return cmdMgr.CommandSystem.ExecuteCommand(execInfo, command); + return CommandManager.ExecuteCommand(execInfo, command); } var output = CallCommand("!help"); @@ -93,6 +76,23 @@ string CallCommand(string command) Assert.Throws(() => CallCommand("!if a == b text (!)")); } + [Test] + public void TailStringTest() + { + var execInfo = Utils.GetExecInfo("ic3"); + var group = execInfo.GetModule().RootGroup; + group.AddCommand("cmd", new FunctionCommand(s => s)); + string CallCommand(string command) + { + return CommandManager.ExecuteCommand(execInfo, command); + } + + Assert.AreEqual("a", CallCommand("!cmd a")); + Assert.AreEqual("a b", CallCommand("!cmd a b")); + Assert.AreEqual("a", CallCommand("!cmd a \" b")); + Assert.AreEqual("a b 1", CallCommand("!cmd a b 1")); + } + [Test] public void XCommandSystemFilterTest() { @@ -145,32 +145,30 @@ public void XCommandSystemFilterTest() [Test] public void XCommandSystemTest() { - var execInfo = Utils.GetExecInfo("ic3"); - var commandSystem = new XCommandSystem(); - var group = commandSystem.RootCommand; + var execInfo = Utils.GetExecInfo("ic3", false); + var group = execInfo.GetModule().RootGroup; group.AddCommand("one", new FunctionCommand(() => "ONE")); group.AddCommand("two", new FunctionCommand(() => "TWO")); group.AddCommand("echo", new FunctionCommand(s => s)); group.AddCommand("optional", new FunctionCommand(GetType().GetMethod(nameof(OptionalFunc), BindingFlags.NonPublic | BindingFlags.Static))); // Basic tests - Assert.AreEqual("ONE", ((StringCommandResult)commandSystem.Execute(execInfo, - new ICommand[] { new StringCommand("one") })).Content); - Assert.AreEqual("ONE", commandSystem.ExecuteCommand(execInfo, "!one")); - Assert.AreEqual("TWO", commandSystem.ExecuteCommand(execInfo, "!t")); - Assert.AreEqual("TEST", commandSystem.ExecuteCommand(execInfo, "!e TEST")); - Assert.AreEqual("ONE", commandSystem.ExecuteCommand(execInfo, "!o")); + Assert.AreEqual("ONE", CommandManager.ExecuteCommand(execInfo, new ICommand[] { new ResultCommand(new PrimitiveResult("one")) })); + Assert.AreEqual("ONE", CommandManager.ExecuteCommand(execInfo, "!one")); + Assert.AreEqual("TWO", CommandManager.ExecuteCommand(execInfo, "!t")); + Assert.AreEqual("TEST", CommandManager.ExecuteCommand(execInfo, "!e TEST")); + Assert.AreEqual("ONE", CommandManager.ExecuteCommand(execInfo, "!o")); // Optional parameters - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!e")); - Assert.AreEqual("NULL", commandSystem.ExecuteCommand(execInfo, "!op")); - Assert.AreEqual("NOT NULL", commandSystem.ExecuteCommand(execInfo, "!op 1")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!e")); + Assert.AreEqual("NULL", CommandManager.ExecuteCommand(execInfo, "!op")); + Assert.AreEqual("NOT NULL", CommandManager.ExecuteCommand(execInfo, "!op 1")); // Command chaining - Assert.AreEqual("TEST", commandSystem.ExecuteCommand(execInfo, "!e (!e TEST)")); - Assert.AreEqual("TWO", commandSystem.ExecuteCommand(execInfo, "!e (!t)")); - Assert.AreEqual("NOT NULL", commandSystem.ExecuteCommand(execInfo, "!op (!e TEST)")); - Assert.AreEqual("ONE", commandSystem.ExecuteCommand(execInfo, "!(!e on)")); + Assert.AreEqual("TEST", CommandManager.ExecuteCommand(execInfo, "!e (!e TEST)")); + Assert.AreEqual("TWO", CommandManager.ExecuteCommand(execInfo, "!e (!t)")); + Assert.AreEqual("NOT NULL", CommandManager.ExecuteCommand(execInfo, "!op (!e TEST)")); + Assert.AreEqual("ONE", CommandManager.ExecuteCommand(execInfo, "!(!e on)")); // Command overloading var intCom = new Func(_ => "INT"); @@ -180,17 +178,16 @@ public void XCommandSystemTest() new FunctionCommand(strCom.Method, strCom.Target) })); - Assert.AreEqual("INT", commandSystem.ExecuteCommand(execInfo, "!overlord 1")); - Assert.AreEqual("STRING", commandSystem.ExecuteCommand(execInfo, "!overlord a")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!overlord")); + Assert.AreEqual("INT", CommandManager.ExecuteCommand(execInfo, "!overlord 1")); + Assert.AreEqual("STRING", CommandManager.ExecuteCommand(execInfo, "!overlord a")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!overlord")); } [Test] public void XCommandSystemTest2() { var execInfo = Utils.GetExecInfo("exact"); - var commandSystem = new XCommandSystem(); - var group = commandSystem.RootCommand; + var group = execInfo.GetModule().RootGroup; var o1 = new OverloadedFunctionCommand(); o1.AddCommand(new FunctionCommand(new Action((_) => { }))); @@ -204,25 +201,25 @@ public void XCommandSystemTest2() o2.AddCommand("b", new FunctionCommand(new Action(() => { }))); group.AddCommand("three", o2); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!one")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!one \"\"")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!one (!print \"\")")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!one string")); - Assert.DoesNotThrow(() => commandSystem.ExecuteCommand(execInfo, "!one 42")); - Assert.DoesNotThrow(() => commandSystem.ExecuteCommand(execInfo, "!one 4200000000000")); - - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!two")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!two \"\"")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!two (!print \"\")")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!two 42")); - Assert.DoesNotThrow(() => commandSystem.ExecuteCommand(execInfo, "!two None")); - - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!three")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!three \"\"")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!three (!print \"\")")); - Assert.Throws(() => commandSystem.ExecuteCommand(execInfo, "!three c")); - Assert.DoesNotThrow(() => commandSystem.ExecuteCommand(execInfo, "!three a")); - Assert.DoesNotThrow(() => commandSystem.ExecuteCommand(execInfo, "!three b")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!one")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!one \"\"")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!one (!print \"\")")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!one string")); + Assert.DoesNotThrow(() => CommandManager.ExecuteCommand(execInfo, "!one 42")); + Assert.DoesNotThrow(() => CommandManager.ExecuteCommand(execInfo, "!one 4200000000000")); + + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!two")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!two \"\"")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!two (!print \"\")")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!two 42")); + Assert.DoesNotThrow(() => CommandManager.ExecuteCommand(execInfo, "!two None")); + + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!three")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!three \"\"")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!three (!print \"\")")); + Assert.Throws(() => CommandManager.ExecuteCommand(execInfo, "!three c")); + Assert.DoesNotThrow(() => CommandManager.ExecuteCommand(execInfo, "!three a")); + Assert.DoesNotThrow(() => CommandManager.ExecuteCommand(execInfo, "!three b")); } [Test] @@ -231,10 +228,16 @@ public void EnsureAllCommandsHaveEnglishDocumentationEntry() Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en"); Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en"); + var execInfo = Utils.GetExecInfo("exact"); + var cmdMgr = execInfo.GetModule(); + var errors = new List(); foreach (var cmd in cmdMgr.AllCommands) { - Assert.IsFalse(string.IsNullOrEmpty(cmd.Description), $"Command {cmd.FullQualifiedName} has no documentation"); + if (string.IsNullOrEmpty(cmd.Description)) + errors.Add($"Command {cmd.FullQualifiedName} has no documentation"); } + if (errors.Count > 0) + Assert.Fail(string.Join("\n", errors)); } [Test] @@ -269,19 +272,15 @@ public static void TestStringParsing(string inp, string outp) internal static class Utils { - private static readonly CommandManager cmdMgr; - - static Utils() + public static ExecutionInformation GetExecInfo(string matcher, bool addMainCommands = true) { - cmdMgr = new CommandManager(null); - cmdMgr.RegisterCollection(MainCommands.Bag); - } + var cmdMgr = new CommandManager(null); + if (addMainCommands) + cmdMgr.RegisterCollection(MainCommands.Bag); - public static ExecutionInformation GetExecInfo(string matcher) - { var execInfo = new ExecutionInformation(); execInfo.AddModule(new CallerInfo(false) { SkipRightsChecks = true, CommandComplexityMax = int.MaxValue }); - execInfo.AddModule(new InvokerData("InvokerUid")); + execInfo.AddModule(new InvokerData((Uid)"InvokerUid")); execInfo.AddModule(Filter.GetFilterByName(matcher)); execInfo.AddModule(cmdMgr); return execInfo; diff --git a/TS3ABotUnitTests/M3uParserTests.cs b/TS3ABotUnitTests/M3uParserTests.cs index f20ae08a..c4d132c0 100644 --- a/TS3ABotUnitTests/M3uParserTests.cs +++ b/TS3ABotUnitTests/M3uParserTests.cs @@ -1,12 +1,12 @@ +using NUnit.Framework; +using System.IO; +using System.Text; +using TS3AudioBot.ResourceFactories.AudioTags; + namespace TS3ABotUnitTests { - using NUnit.Framework; - using System.IO; - using System.Text; - using TS3AudioBot.ResourceFactories.AudioTags; - [TestFixture] - class M3uParserTests + internal class M3uParserTests { [Test] public void SimpleListTest() diff --git a/TS3ABotUnitTests/RingQueueTest.cs b/TS3ABotUnitTests/RingQueueTest.cs index 9162e4d4..36398cc2 100644 --- a/TS3ABotUnitTests/RingQueueTest.cs +++ b/TS3ABotUnitTests/RingQueueTest.cs @@ -1,11 +1,11 @@ +using NUnit.Framework; +using System; +using TSLib.Full; + namespace TS3ABotUnitTests { - using NUnit.Framework; - using System; - using TS3Client.Full; - [TestFixture] - class RingQueueTest + internal class RingQueueTest { [Test] diff --git a/TS3ABotUnitTests/TS3ABotUnitTests.csproj b/TS3ABotUnitTests/TS3ABotUnitTests.csproj index 42d4c87f..b2a6d5cd 100644 --- a/TS3ABotUnitTests/TS3ABotUnitTests.csproj +++ b/TS3ABotUnitTests/TS3ABotUnitTests.csproj @@ -2,7 +2,7 @@ Library - netcoreapp2.2;net472 + netcoreapp2.2;netcoreapp3.1;net472 7.3 AnyCPU @@ -11,13 +11,16 @@ - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + diff --git a/TS3ABotUnitTests/TS3MessageParserTests.cs b/TS3ABotUnitTests/TS3MessageParserTests.cs index ff567684..2a992bd7 100644 --- a/TS3ABotUnitTests/TS3MessageParserTests.cs +++ b/TS3ABotUnitTests/TS3MessageParserTests.cs @@ -1,12 +1,12 @@ +using NUnit.Framework; +using System.Collections; +using System.Reflection; +using System.Text; +using TSLib; +using TSLib.Messages; + namespace TS3ABotUnitTests { - using NUnit.Framework; - using System.Collections; - using System.Reflection; - using System.Text; - using TS3Client; - using TS3Client.Messages; - [TestFixture] public class TS3MessageParserTests { @@ -20,7 +20,7 @@ public void Deserializer1Test() var notifv = notif.Value; Assert.AreEqual(notifv.Length, 1); var notifs = notifv[0]; - AssertEx.PropertyValuesAreEquals(notifs, new ChannelChanged() { ChannelId = 6 }); + AssertEx.PropertyValuesAreEquals(notifs, new ChannelChanged() { ChannelId = (ChannelId)6 }); } [Test] @@ -31,7 +31,7 @@ public void Deserializer2Test() var notifv = notif.Value; Assert.AreEqual(notifv.Length, 1); var notifs = notifv[0]; - AssertEx.PropertyValuesAreEquals(notifs, new ClientChatComposing() { ClientId = 42, ClientUid = "asdfe/rvt==" }); + AssertEx.PropertyValuesAreEquals(notifs, new ClientChatComposing() { ClientId = (ClientId)42, ClientUid = (Uid)"asdfe/rvt==" }); } [Test] @@ -41,8 +41,8 @@ public void Deserializer3Test() Assert.True(notif.Ok); var notifv = notif.Value; Assert.AreEqual(notifv.Length, 2); - AssertEx.PropertyValuesAreEquals(notifv[0], new ChannelChanged() { ChannelId = 5 }); - AssertEx.PropertyValuesAreEquals(notifv[1], new ChannelChanged() { ChannelId = 4 }); + AssertEx.PropertyValuesAreEquals(notifv[0], new ChannelChanged() { ChannelId = (ChannelId)5 }); + AssertEx.PropertyValuesAreEquals(notifv[1], new ChannelChanged() { ChannelId = (ChannelId)4 }); } [Test] @@ -52,8 +52,8 @@ public void Deserializer4Test() Assert.True(notif.Ok); var notifv = notif.Value; Assert.AreEqual(notifv.Length, 2); - AssertEx.PropertyValuesAreEquals(notifv[0], new ClientChatComposing() { ClientId = 42, ClientUid = "asdfe/rvt==" }); - AssertEx.PropertyValuesAreEquals(notifv[1], new ClientChatComposing() { ClientId = 1337, ClientUid = "asdfe/rvt==" }); + AssertEx.PropertyValuesAreEquals(notifv[0], new ClientChatComposing() { ClientId = (ClientId)42, ClientUid = (Uid)"asdfe/rvt==" }); + AssertEx.PropertyValuesAreEquals(notifv[1], new ClientChatComposing() { ClientId = (ClientId)1337, ClientUid = (Uid)"asdfe/rvt==" }); } [Test] @@ -68,10 +68,10 @@ public void Deserializer5Test() Assert.True(notif.Ok); var notifv = notif.Value; Assert.AreEqual(notifv.Length, 4); - AssertEx.PropertyValuesAreEquals(notifv[0], new ClientList() { ClientId = 1, ChannelId = 1, DatabaseId = 2, Name = "TestBob1", ClientType = ClientType.Full, Uid = "u/dFMOFFipxS9fJ8HKv0KH6WVzA=" }); - AssertEx.PropertyValuesAreEquals(notifv[1], new ClientList() { ClientId = 2, ChannelId = 4, DatabaseId = 2, Name = "TestBob", ClientType = ClientType.Full, Uid = "u/dFMOFFipxS9fJ8HKv0KH6WVzA=" }); - AssertEx.PropertyValuesAreEquals(notifv[2], new ClientList() { ClientId = 3, ChannelId = 4, DatabaseId = 6, Name = "Splamy", ClientType = ClientType.Full, Uid = "uA0U7t4PBxdJ5TLnarsOHQh4/tY=" }); - AssertEx.PropertyValuesAreEquals(notifv[3], new ClientList() { ClientId = 4, ChannelId = 4, DatabaseId = 7, Name = "AudioBud", ClientType = ClientType.Full, Uid = "b+P0CqXms5I0C+A66HZ4Sbu/PNw=" }); + AssertEx.PropertyValuesAreEquals(notifv[0], new ClientList() { ClientId = (ClientId)1, ChannelId = (ChannelId)1, DatabaseId = (ClientDbId)2, Name = "TestBob1", ClientType = ClientType.Full, Uid = (Uid)"u/dFMOFFipxS9fJ8HKv0KH6WVzA=" }); + AssertEx.PropertyValuesAreEquals(notifv[1], new ClientList() { ClientId = (ClientId)2, ChannelId = (ChannelId)4, DatabaseId = (ClientDbId)2, Name = "TestBob", ClientType = ClientType.Full, Uid = (Uid)"u/dFMOFFipxS9fJ8HKv0KH6WVzA=" }); + AssertEx.PropertyValuesAreEquals(notifv[2], new ClientList() { ClientId = (ClientId)3, ChannelId = (ChannelId)4, DatabaseId = (ClientDbId)6, Name = "Splamy", ClientType = ClientType.Full, Uid = (Uid)"uA0U7t4PBxdJ5TLnarsOHQh4/tY=" }); + AssertEx.PropertyValuesAreEquals(notifv[3], new ClientList() { ClientId = (ClientId)4, ChannelId = (ChannelId)4, DatabaseId = (ClientDbId)7, Name = "AudioBud", ClientType = ClientType.Full, Uid = (Uid)"b+P0CqXms5I0C+A66HZ4Sbu/PNw=" }); } [Test] diff --git a/TS3ABotUnitTests/UnitTests.cs b/TS3ABotUnitTests/UnitTests.cs index 4fa1fb7f..ede3526b 100644 --- a/TS3ABotUnitTests/UnitTests.cs +++ b/TS3ABotUnitTests/UnitTests.cs @@ -1,31 +1,24 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . +using NUnit.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using TS3AudioBot; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TS3AudioBot.History; +using TS3AudioBot.Playlists.Shuffle; +using TS3AudioBot.ResourceFactories; +using TS3AudioBot.ResourceFactories.Youtube; +using TSLib; +using TSLib.Full; +using TSLib.Messages; namespace TS3ABotUnitTests { - using NUnit.Framework; - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text.RegularExpressions; - using System.Threading; - using TS3AudioBot; - using TS3AudioBot.Config; - using TS3AudioBot.Helper; - using TS3AudioBot.History; - using TS3AudioBot.Playlists.Shuffle; - using TS3AudioBot.ResourceFactories; - using TS3Client.Full; - using TS3Client.Messages; - [TestFixture] public class UnitTests { @@ -39,8 +32,8 @@ public void HistoryFileIntergrityTest() string testFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "history.test"); if (File.Exists(testFile)) File.Delete(testFile); - var inv1 = new ClientList { ClientId = 10, Uid = "Uid1", Name = "Invoker1" }; - var inv2 = new ClientList { ClientId = 20, Uid = "Uid2", Name = "Invoker2" }; + var inv1 = new ClientList { ClientId = (ClientId)10, Uid = (Uid)"Uid1", Name = "Invoker1" }; + var inv2 = new ClientList { ClientId = (ClientId)20, Uid = (Uid)"Uid2", Name = "Invoker2" }; var ar1 = new AudioResource("asdf", "sc_ar1", "soundcloud"); var ar2 = new AudioResource("./File.mp3", "me_ar2", "media"); @@ -48,7 +41,7 @@ public void HistoryFileIntergrityTest() var data1 = new HistorySaveData(ar1, inv1.Uid); var data2 = new HistorySaveData(ar2, inv2.Uid); - var data3 = new HistorySaveData(ar3, "Uid3"); + var data3 = new HistorySaveData(ar3, (Uid)"Uid3"); var confHistory = ConfigTable.CreateRoot(); confHistory.FillDeletedIds.Value = false; @@ -100,7 +93,7 @@ void CreateDbStore() var ale1 = hf.FindEntryByResource(ar1); hf.RenameEntry(ale1, "sc_ar1X"); - hf.LogAudioResourceDelayed(new HistorySaveData(ale1.AudioResource, "Uid4")); + hf.LogAudioResourceDelayed(new HistorySaveData(ale1.AudioResource, (Uid)"Uid4")); db.Dispose(); @@ -114,14 +107,14 @@ void CreateDbStore() var ale2 = hf.FindEntryByResource(ar2); hf.RenameEntry(ale2, "me_ar2_loong1"); - hf.LogAudioResourceDelayed(new HistorySaveData(ale2.AudioResource, "Uid4")); + hf.LogAudioResourceDelayed(new HistorySaveData(ale2.AudioResource, (Uid)"Uid4")); ale1 = hf.FindEntryByResource(ar1); hf.RenameEntry(ale1, "sc_ar1X_loong1"); - hf.LogAudioResourceDelayed(new HistorySaveData(ale1.AudioResource, "Uid4")); + hf.LogAudioResourceDelayed(new HistorySaveData(ale1.AudioResource, (Uid)"Uid4")); hf.RenameEntry(ale2, "me_ar2_exxxxxtra_loong1"); - hf.LogAudioResourceDelayed(new HistorySaveData(ale2.AudioResource, "Uid4")); + hf.LogAudioResourceDelayed(new HistorySaveData(ale2.AudioResource, (Uid)"Uid4")); db.Dispose(); @@ -183,22 +176,28 @@ public void UtilSeedTest() [Test] public void NormalOrderTest() { - TestShuffleAlgorithm(new NormalOrder()); + TestShuffleAlgorithmBiDir(new NormalOrder()); } [Test] public void ListedShuffleTest() { - TestShuffleAlgorithm(new ListedShuffle()); + TestShuffleAlgorithmBiDir(new ListedShuffle()); } [Test] public void LinearFeedbackShiftRegisterTest() { - TestShuffleAlgorithm(new LinearFeedbackShiftRegister()); + TestShuffleAlgorithmBiDir(new LinearFeedbackShiftRegister()); + } + + private static void TestShuffleAlgorithmBiDir(IShuffleAlgorithm algo) + { + TestShuffleAlgorithm(algo, true); + TestShuffleAlgorithm(algo, false); } - private static void TestShuffleAlgorithm(IShuffleAlgorithm algo) + private static void TestShuffleAlgorithm(IShuffleAlgorithm algo, bool forward) { for (int i = 1; i < 1000; i++) { @@ -209,7 +208,8 @@ private static void TestShuffleAlgorithm(IShuffleAlgorithm algo) for (int j = 0; j < i; j++) { - algo.Next(); + if (forward) algo.Next(); + else algo.Prev(); int shufNum = algo.Index; if (checkNumbers.Get(shufNum)) Assert.Fail("Duplicate number"); @@ -223,25 +223,26 @@ private static void TestShuffleAlgorithm(IShuffleAlgorithm algo) [Test] public void Factory_YoutubeFactoryTest() { - using (IResourceFactory rfac = new YoutubeFactory()) + var ctx = new ResolveContext(null, null); + using (IResourceResolver rfac = new YoutubeResolver(new ConfFactories())) { // matching links - Assert.AreEqual(rfac.MatchResource(@"https://www.youtube.com/watch?v=robqdGEhQWo"), MatchCertainty.Always); - Assert.AreEqual(rfac.MatchResource(@"https://youtu.be/robqdGEhQWo"), MatchCertainty.Always); - Assert.AreEqual(rfac.MatchResource(@"https://discarded-ideas.org/sites/discarded-ideas.org/files/music/darkforestkeep_symphonic.mp3"), MatchCertainty.Never); - Assert.AreNotEqual(rfac.MatchResource(@"http://splamy.de/youtube.com/youtu.be/fake.mp3"), MatchCertainty.Always); + Assert.AreEqual(rfac.MatchResource(ctx, @"https://www.youtube.com/watch?v=robqdGEhQWo"), MatchCertainty.Always); + Assert.AreEqual(rfac.MatchResource(ctx, @"https://youtu.be/robqdGEhQWo"), MatchCertainty.Always); + Assert.AreEqual(rfac.MatchResource(ctx, @"https://discarded-ideas.org/sites/discarded-ideas.org/files/music/darkforestkeep_symphonic.mp3"), MatchCertainty.Never); + Assert.AreNotEqual(rfac.MatchResource(ctx, @"http://splamy.de/youtube.com/youtu.be/fake.mp3"), MatchCertainty.Always); // restoring links - Assert.AreEqual("https://youtu.be/robqdGEhQWo", rfac.RestoreLink(new AudioResource { ResourceId = "robqdGEhQWo" })); + Assert.AreEqual("https://youtu.be/robqdGEhQWo", rfac.RestoreLink(ctx, new AudioResource { ResourceId = "robqdGEhQWo" })); } } - /* ======================= TS3Client Tests ========================*/ + /* ======================= TSLib Tests ========================*/ [Test] public void VersionSelfCheck() { - Ts3Crypt.VersionSelfCheck(); + TsCrypt.VersionSelfCheck(); } } diff --git a/TS3AudioBot.ruleset b/TS3AudioBot.ruleset index dbfef148..ce3d5cd3 100644 --- a/TS3AudioBot.ruleset +++ b/TS3AudioBot.ruleset @@ -1,5 +1,5 @@ - - + + @@ -83,7 +83,7 @@ - + @@ -93,4 +93,4 @@ - \ No newline at end of file + diff --git a/TS3AudioBot.sln b/TS3AudioBot.sln index 53cc1fad..4b9159f3 100644 --- a/TS3AudioBot.sln +++ b/TS3AudioBot.sln @@ -6,12 +6,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TS3AudioBot", "TS3AudioBot\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TS3ABotUnitTests", "TS3ABotUnitTests\TS3ABotUnitTests.csproj", "{20B6F767-5396-41D9-83D8-98B5730C6E2E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TS3Client", "TS3Client\TS3Client.csproj", "{0EB99E9D-87E5-4534-A100-55D231C2B6A6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TSLib", "TSLib\TSLib.csproj", "{0EB99E9D-87E5-4534-A100-55D231C2B6A6}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{ADAA5A65-0CE1-45FA-91F4-3D8D39073AB0}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig Directory.Build.targets = Directory.Build.targets + GitVersion.yml = GitVersion.yml README.md = README.md TS3AudioBot.ruleset = TS3AudioBot.ruleset EndProjectSection diff --git a/TS3AudioBot/Algorithm/IFilterAlgorithm.cs b/TS3AudioBot/Algorithm/IFilterAlgorithm.cs index 2ee75994..b813988e 100644 --- a/TS3AudioBot/Algorithm/IFilterAlgorithm.cs +++ b/TS3AudioBot/Algorithm/IFilterAlgorithm.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; +using System.Linq; + namespace TS3AudioBot.Algorithm { - using System.Collections.Generic; - using System.Linq; - public interface IFilter { IEnumerable> Filter(IEnumerable> list, string filter); diff --git a/TS3AudioBot/Algorithm/LruCache.cs b/TS3AudioBot/Algorithm/LruCache.cs index 03738f05..69e57906 100644 --- a/TS3AudioBot/Algorithm/LruCache.cs +++ b/TS3AudioBot/Algorithm/LruCache.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; + namespace TS3AudioBot.Algorithm { - using System.Collections.Generic; - public class LruCache { private readonly int maxCapacity; diff --git a/TS3AudioBot/Algorithm/TimedCache.cs b/TS3AudioBot/Algorithm/TimedCache.cs index a833577a..6be37bcd 100644 --- a/TS3AudioBot/Algorithm/TimedCache.cs +++ b/TS3AudioBot/Algorithm/TimedCache.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Concurrent; +using System.Linq; +using TSLib.Helper; + namespace TS3AudioBot.Algorithm { - using System; - using System.Collections.Concurrent; - using System.Linq; - using TS3AudioBot.Helper; - public class TimedCache { public TimeSpan Timeout { get; } @@ -30,7 +30,7 @@ public TimedCache(TimeSpan timeout) public bool TryGetValue(TK key, out TV value) { if (!cachedData.TryGetValue(key, out var data) - || Util.GetNow() - Timeout > data.Timestamp) + || Tools.Now - Timeout > data.Timestamp) { CleanCache(); value = default; @@ -42,7 +42,7 @@ public bool TryGetValue(TK key, out TV value) public void Set(TK key, TV value) { - cachedData[key] = new TimedData { Data = value, Timestamp = Util.GetNow() }; + cachedData[key] = new TimedData { Data = value, Timestamp = Tools.Now }; } public void Clear() @@ -52,7 +52,7 @@ public void Clear() private void CleanCache() { - var now = Util.GetNow() - Timeout; + var now = Tools.Now - Timeout; foreach (var item in cachedData.Where(kvp => now > kvp.Value.Timestamp).ToList()) { cachedData.TryRemove(item.Key, out _); diff --git a/TS3AudioBot/Audio/AudioValues.cs b/TS3AudioBot/Audio/AudioValues.cs index 2a11f4d3..53601bf3 100644 --- a/TS3AudioBot/Audio/AudioValues.cs +++ b/TS3AudioBot/Audio/AudioValues.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TSLib.Helper; + namespace TS3AudioBot.Audio { - using Helper; - using System; - public static class AudioValues { public const float MinVolume = 0; @@ -33,7 +33,7 @@ public static float HumanVolumeToFactor(float value) value = (value - MinVolume) / (MaxVolume - MinVolume); // Scale the value logarithmically - return Util.Clamp((float)(fact_a * Math.Exp(fact_b * value)) - fact_a, 0, 1); + return Tools.Clamp((float)(fact_a * Math.Exp(fact_b * value)) - fact_a, 0, 1); } public static float FactorToHumanVolume(float value) @@ -42,7 +42,7 @@ public static float FactorToHumanVolume(float value) if (value > 1) return MaxVolume; // Undo logarithmical scale - value = Util.Clamp((float)(Math.Log((value + fact_a) / fact_a) / fact_b), 0, 1); + value = Tools.Clamp((float)(Math.Log((value + fact_a) / fact_a) / fact_b), 0, 1); // Map input values from [0, 1] to [MinVolume, MaxVolume] return (value * (MaxVolume - MinVolume)) + MinVolume; diff --git a/TS3AudioBot/Audio/CustomTargetPipe.cs b/TS3AudioBot/Audio/CustomTargetPipe.cs index ac2d3e57..1f5c2104 100644 --- a/TS3AudioBot/Audio/CustomTargetPipe.cs +++ b/TS3AudioBot/Audio/CustomTargetPipe.cs @@ -7,16 +7,15 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using TSLib; +using TSLib.Audio; +using TSLib.Full; + namespace TS3AudioBot.Audio { - using Helper; - using System; - using System.Collections.Generic; - using System.Linq; - using TS3Client; - using TS3Client.Audio; - using TS3Client.Full; - internal class CustomTargetPipe : IVoiceTarget, IAudioPassiveConsumer { public TargetSendMode SendMode { get; set; } = TargetSendMode.None; @@ -24,11 +23,11 @@ internal class CustomTargetPipe : IVoiceTarget, IAudioPassiveConsumer public GroupWhisperType GroupWhisperType { get; private set; } public GroupWhisperTarget GroupWhisperTarget { get; private set; } - public IReadOnlyCollection WhisperClients + public IReadOnlyCollection WhisperClients { get { lock (subscriptionLockObj) { return clientSubscriptionsSetup.ToArray(); } } } - public IReadOnlyCollection WhisperChannel + public IReadOnlyCollection WhisperChannel { get { lock (subscriptionLockObj) { return channelSubscriptionsSetup.Keys.ToArray(); } } } @@ -50,20 +49,18 @@ public bool Active } } - private readonly Dictionary channelSubscriptionsSetup; - private readonly HashSet clientSubscriptionsSetup; - private ulong[] channelSubscriptionsCache; - private ushort[] clientSubscriptionsCache; + private readonly Dictionary channelSubscriptionsSetup = new Dictionary(); + private readonly HashSet clientSubscriptionsSetup = new HashSet(); + private ChannelId[] channelSubscriptionsCache; + private ClientId[] clientSubscriptionsCache; private bool subscriptionSetupChanged; private readonly object subscriptionLockObj = new object(); - private readonly Ts3FullClient client; + private readonly TsFullClient client; - public CustomTargetPipe(Ts3FullClient client) + public CustomTargetPipe(TsFullClient client) { this.client = client; - Util.Init(out channelSubscriptionsSetup); - Util.Init(out clientSubscriptionsSetup); subscriptionSetupChanged = true; } @@ -99,7 +96,7 @@ public void SetGroupWhisper(GroupWhisperType type, GroupWhisperTarget target, ul GroupWhisperTargetId = targetId; } - public void WhisperChannelSubscribe(bool temp, params ulong[] channels) + public void WhisperChannelSubscribe(bool temp, params ChannelId[] channels) { lock (subscriptionLockObj) { @@ -118,7 +115,7 @@ public void WhisperChannelSubscribe(bool temp, params ulong[] channels) } } - public void WhisperChannelUnsubscribe(bool temp, params ulong[] channels) + public void WhisperChannelUnsubscribe(bool temp, params ChannelId[] channels) { lock (subscriptionLockObj) { @@ -140,7 +137,7 @@ public void WhisperChannelUnsubscribe(bool temp, params ulong[] channels) } } - public void WhisperClientSubscribe(params ushort[] userId) + public void WhisperClientSubscribe(params ClientId[] userId) { lock (subscriptionLockObj) { @@ -149,7 +146,7 @@ public void WhisperClientSubscribe(params ushort[] userId) } } - public void WhisperClientUnsubscribe(params ushort[] userId) + public void WhisperClientUnsubscribe(params ClientId[] userId) { lock (subscriptionLockObj) { @@ -162,7 +159,7 @@ public void ClearTemporary() { lock (subscriptionLockObj) { - ulong[] removeList = channelSubscriptionsSetup + var removeList = channelSubscriptionsSetup .Where(kvp => kvp.Value) .Select(kvp => kvp.Key) .ToArray(); diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs index be18ead5..f9343560 100644 --- a/TS3AudioBot/Audio/FfmpegProducer.cs +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -7,22 +7,22 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TSLib.Audio; +using TSLib.Helper; + namespace TS3AudioBot.Audio { - using Config; - using Helper; - using System; - using System.ComponentModel; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Text; - using System.Text.RegularExpressions; - using System.Threading; - using TS3Client.Audio; - using TS3Client.Helper; - - public class FfmpegProducer : IAudioPassiveProducer, ISampleInfo, IDisposable + public class FfmpegProducer : IPlayerSource, ISampleInfo, IDisposable { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private readonly Id id; @@ -31,7 +31,7 @@ public class FfmpegProducer : IAudioPassiveProducer, ISampleInfo, IDisposable private const string PreLinkConf = "-hide_banner -nostats -threads 1 -i \""; private const string PostLinkConf = "\" -ac 2 -ar 48000 -f s16le -acodec pcm_s16le pipe:1"; private const string LinkConfIcy = "-hide_banner -nostats -threads 1 -i pipe:0 -ac 2 -ar 48000 -f s16le -acodec pcm_s16le pipe:1"; - private readonly TimeSpan retryOnDropBeforeEnd = TimeSpan.FromSeconds(10); + private static readonly TimeSpan retryOnDropBeforeEnd = TimeSpan.FromSeconds(10); private readonly ConfToolsFfmpeg config; @@ -50,7 +50,7 @@ public FfmpegProducer(ConfToolsFfmpeg config, Id id) this.id = id; } - public E AudioStart(string url) => StartFfmpegProcess(url, TimeSpan.Zero); + public E AudioStart(string url, TimeSpan? startOff = null) => StartFfmpegProcess(url, startOff ?? TimeSpan.Zero); public E AudioStartIcy(string url) => StartFfmpegProcessIcy(url); @@ -184,15 +184,16 @@ private R SetPosition(TimeSpan value) return StartFfmpegProcess(lastLink, value); } - private R StartFfmpegProcess(string url, TimeSpan offset) + private R StartFfmpegProcess(string url, TimeSpan? offsetOpt) { StopFfmpegProcess(); Log.Trace("Start request {0}", url); string arguments; + var offset = offsetOpt ?? TimeSpan.Zero; if (offset > TimeSpan.Zero) { - var seek = $"-ss {offset.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture)}"; + var seek = string.Format(CultureInfo.InvariantCulture, @"-ss {0:hh\:mm\:ss\.fff}", offset); arguments = string.Concat(seek, " ", PreLinkConf, url, PostLinkConf, " ", seek); } else @@ -393,7 +394,7 @@ public void FfmpegProcess_ErrorDataReceived(object sender, DataReceivedEventArgs public void ReadStreamLoop(Id id) { - Util.SetLogId(id.ToString()); + Tools.SetLogId(id.ToString()); const int IcyMaxMeta = 255 * 16; const int ReadBufferSize = 4096; diff --git a/TS3AudioBot/Audio/IPlayerConnection.cs b/TS3AudioBot/Audio/IPlayerSource.cs similarity index 58% rename from TS3AudioBot/Audio/IPlayerConnection.cs rename to TS3AudioBot/Audio/IPlayerSource.cs index 01d23b72..2af20f1d 100644 --- a/TS3AudioBot/Audio/IPlayerConnection.cs +++ b/TS3AudioBot/Audio/IPlayerSource.cs @@ -1,4 +1,4 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 // Copyright (C) 2017 TS3AudioBot contributors // // This program is free software: you can redistribute it and/or modify @@ -7,24 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TSLib.Audio; + namespace TS3AudioBot.Audio { - using System; - using TS3AudioBot.ResourceFactories; - - /// Slim interface to control the audio player. - public interface IPlayerConnection : IDisposable + public interface IPlayerSource : IAudioPassiveProducer { event EventHandler OnSongEnd; event EventHandler OnSongUpdated; - float Volume { get; set; } - TimeSpan Position { get; set; } - bool Paused { get; set; } TimeSpan Length { get; } - bool Playing { get; } - - E AudioStart(PlayResource url); - E AudioStop(); + TimeSpan Position { get; set; } } } diff --git a/TS3AudioBot/Audio/IVoiceTarget.cs b/TS3AudioBot/Audio/IVoiceTarget.cs index 5e9747a2..ea9a4cf7 100644 --- a/TS3AudioBot/Audio/IVoiceTarget.cs +++ b/TS3AudioBot/Audio/IVoiceTarget.cs @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; +using TSLib; +using TSLib.Audio; + namespace TS3AudioBot.Audio { - using System.Collections.Generic; - using TS3Client; - using TS3Client.Audio; - /// Used to specify playing mode and active targets to send to. public interface IVoiceTarget { @@ -22,21 +22,21 @@ public interface IVoiceTarget GroupWhisperTarget GroupWhisperTarget { get; } void SetGroupWhisper(GroupWhisperType type, GroupWhisperTarget target, ulong targetId); - IReadOnlyCollection WhisperClients { get; } - IReadOnlyCollection WhisperChannel { get; } + IReadOnlyCollection WhisperClients { get; } + IReadOnlyCollection WhisperChannel { get; } /// Adds a channel to the audio streaming list. /// The id of the channel. /// When set to true this channel will be cleared with /// the next call (unless overwritten with false). - void WhisperChannelSubscribe(bool temp, params ulong[] channel); + void WhisperChannelSubscribe(bool temp, params ChannelId[] channel); /// Removes a channel from the audio streaming list. /// The id of the channel. /// When set to true this channel will be cleared with /// the next call (unless overwritten with false). - void WhisperChannelUnsubscribe(bool temp, params ulong[] channel); + void WhisperChannelUnsubscribe(bool temp, params ChannelId[] channel); void ClearTemporary(); - void WhisperClientSubscribe(params ushort[] userId); - void WhisperClientUnsubscribe(params ushort[] userId); + void WhisperClientSubscribe(params ClientId[] userId); + void WhisperClientUnsubscribe(params ClientId[] userId); } } diff --git a/TS3AudioBot/Audio/MetaData.cs b/TS3AudioBot/Audio/MetaData.cs index 40727f6b..bfc12043 100644 --- a/TS3AudioBot/Audio/MetaData.cs +++ b/TS3AudioBot/Audio/MetaData.cs @@ -7,19 +7,21 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TSLib; + namespace TS3AudioBot.Audio { public sealed class MetaData { /// Defaults to: invoker.Uid - Can be set if the owner of a song differs from the invoker. - public string ResourceOwnerUid { get; set; } - /// Default: false - Indicates whether the song has been requested from a playlist. - public PlaySource From { get; set; } = PlaySource.PlayRequest; + public Uid ResourceOwnerUid { get; set; } + /// + public TimeSpan? StartOffset { get; set; } - public MetaData Clone() => new MetaData + public MetaData(TimeSpan? startOffset = null) { - ResourceOwnerUid = ResourceOwnerUid, - From = From, - }; + StartOffset = startOffset; + } } } diff --git a/TS3AudioBot/Audio/PlayInfoEventArgs.cs b/TS3AudioBot/Audio/PlayInfoEventArgs.cs index 3ee931ea..a3d1c560 100644 --- a/TS3AudioBot/Audio/PlayInfoEventArgs.cs +++ b/TS3AudioBot/Audio/PlayInfoEventArgs.cs @@ -7,24 +7,23 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TS3AudioBot.ResourceFactories; + namespace TS3AudioBot.Audio { - using System; - using TS3AudioBot.ResourceFactories; - public sealed class PlayInfoEventArgs : EventArgs { public InvokerData Invoker { get; } public PlayResource PlayResource { get; } public AudioResource ResourceData => PlayResource.BaseData; - public MetaData MetaData { get; } + public MetaData MetaData => PlayResource.Meta; public string SourceLink { get; } - public PlayInfoEventArgs(InvokerData invoker, PlayResource playResource, MetaData meta, string sourceLink) + public PlayInfoEventArgs(InvokerData invoker, PlayResource playResource, string sourceLink) { Invoker = invoker; PlayResource = playResource; - MetaData = meta; SourceLink = sourceLink; } } diff --git a/TS3AudioBot/Audio/PlayManager.cs b/TS3AudioBot/Audio/PlayManager.cs index 0e8ce027..6b57a7ae 100644 --- a/TS3AudioBot/Audio/PlayManager.cs +++ b/TS3AudioBot/Audio/PlayManager.cs @@ -7,26 +7,29 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using TS3AudioBot.Config; +using TS3AudioBot.Environment; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists; +using TS3AudioBot.ResourceFactories; +using TSLib.Helper; + namespace TS3AudioBot.Audio { - using Config; - using Localization; - using Playlists; - using ResourceFactories; - using System; - using System.Collections.Generic; - using System.Linq; - using TS3AudioBot.Helper; - - /// Provides a convenient inferface for enqueing, playing and registering song events. + /// Provides a convenient inferface for enqueing, playing and registering song events. public class PlayManager { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private readonly ConfBot confBot; - private readonly IPlayerConnection playerConnection; + private readonly Player playerConnection; private readonly PlaylistManager playlistManager; - private readonly ResourceFactory resourceFactory; + private readonly ResolveContext resourceResolver; + private readonly Stats stats; public PlayInfoEventArgs CurrentPlayData { get; private set; } public bool IsPlaying => CurrentPlayData != null; @@ -34,48 +37,54 @@ public class PlayManager public event EventHandler OnResourceUpdated; public event EventHandler BeforeResourceStarted; public event EventHandler AfterResourceStarted; - public event EventHandler BeforeResourceStopped; - public event EventHandler AfterResourceStopped; + public event EventHandler ResourceStopped; + public event EventHandler PlaybackStopped; - public PlayManager(ConfBot config, IPlayerConnection playerConnection, PlaylistManager playlistManager, ResourceFactory resourceFactory) + public PlayManager(ConfBot config, Player playerConnection, PlaylistManager playlistManager, ResolveContext resourceResolver, Stats stats) { confBot = config; this.playerConnection = playerConnection; this.playlistManager = playlistManager; - this.resourceFactory = resourceFactory; + this.resourceResolver = resourceResolver; + this.stats = stats; } - public E Enqueue(InvokerData invoker, AudioResource ar) => Enqueue(invoker, new PlaylistItem(ar)); - public E Enqueue(InvokerData invoker, string message, string audioType = null) + public E Enqueue(InvokerData invoker, AudioResource ar, MetaData meta = null) => Enqueue(invoker, new PlaylistItem(ar, meta)); + public E Enqueue(InvokerData invoker, string message, string audioType = null, MetaData meta = null) { - var result = resourceFactory.Load(message, audioType); + var result = resourceResolver.Load(message, audioType); if (!result) + { + stats.TrackSongLoad(audioType, false, true); return result.Error; - return Enqueue(invoker, new PlaylistItem(result.Value.BaseData)); + } + return Enqueue(invoker, new PlaylistItem(result.Value.BaseData, meta)); } public E Enqueue(InvokerData invoker, IEnumerable items) { + var startOff = playlistManager.CurrentList.Items.Count; playlistManager.Queue(items.Select(x => UpdateItem(invoker, x))); - return PostEnqueue(invoker); + return PostEnqueue(invoker, startOff); } public E Enqueue(InvokerData invoker, PlaylistItem item) { + var startOff = playlistManager.CurrentList.Items.Count; playlistManager.Queue(UpdateItem(invoker, item)); - return PostEnqueue(invoker); + return PostEnqueue(invoker, startOff); } private static PlaylistItem UpdateItem(InvokerData invoker, PlaylistItem item) { + item.Meta = item.Meta ?? new MetaData(); item.Meta.ResourceOwnerUid = invoker.ClientUid; - item.Meta.From = PlaySource.FromQueue; return item; } - private E PostEnqueue(InvokerData invoker) + private E PostEnqueue(InvokerData invoker, int startIndex) { if (IsPlaying) return R.Ok; - playlistManager.Index = 0; + playlistManager.Index = startIndex; return StartCurrent(invoker); } @@ -89,9 +98,12 @@ public E Play(InvokerData invoker, AudioResource ar, MetaData meta = n if (ar is null) throw new ArgumentNullException(nameof(ar)); - var result = resourceFactory.Load(ar); + var result = resourceResolver.Load(ar); if (!result) + { + stats.TrackSongLoad(ar.AudioType, false, true); return result.Error; + } return Play(invoker, result.Value, meta); } @@ -103,10 +115,13 @@ public E Play(InvokerData invoker, AudioResource ar, MetaData meta = n /// Ok if successful, or an error message otherwise. public E Play(InvokerData invoker, string link, string audioType = null, MetaData meta = null) { - var result = resourceFactory.Load(link, audioType); + var result = resourceResolver.Load(link, audioType); if (!result) + { + stats.TrackSongLoad(audioType, false, true); return result.Error; - return Play(invoker, result.Value, meta ?? new MetaData()); + } + return Play(invoker, result.Value, meta); } public E Play(InvokerData invoker, IEnumerable items, int index = 0) @@ -122,15 +137,16 @@ public E Play(InvokerData invoker, PlaylistItem item) if (item is null) throw new ArgumentNullException(nameof(item)); - if (item.Resource is null) + if (item.AudioResource is null) throw new Exception("Invalid playlist item"); - playlistManager.Clear(); playlistManager.Queue(item); playlistManager.Index = 0; return StartResource(invoker, item); } + public E Play(InvokerData invoker) => StartCurrent(invoker); + /// Plays the passed /// The invoker of this resource. Used for responses and association. /// The associated resource type string to a factory. @@ -142,25 +158,24 @@ public E Play(InvokerData invoker, PlayResource play, MetaData meta = playlistManager.Clear(); playlistManager.Queue(new PlaylistItem(play.BaseData, meta)); playlistManager.Index = 0; + stats.TrackSongLoad(play.BaseData.AudioType, true, true); return StartResource(invoker, play, meta); } private E StartResource(InvokerData invoker, PlaylistItem item) { - var result = resourceFactory.Load(item.Resource); + var result = resourceResolver.Load(item.AudioResource); + stats.TrackSongLoad(item.AudioResource.AudioType, result.Ok, false); if (!result) return result.Error; - return StartResource(invoker, result.Value, item.Meta); } - private E StartResource(InvokerData invoker, PlayResource play, MetaData meta) + private E StartResource(InvokerData invoker, PlayResource play, MetaData meta = null) { - if (meta.From != PlaySource.FromPlaylist) - meta.ResourceOwnerUid = invoker.ClientUid; - - var sourceLink = resourceFactory.RestoreLink(play.BaseData).OkOr(null); - var playInfo = new PlayInfoEventArgs(invoker, play, meta, sourceLink); + play.Meta = meta ?? play.Meta ?? new MetaData(); + var sourceLink = resourceResolver.RestoreLink(play.BaseData).OkOr(null); + var playInfo = new PlayInfoEventArgs(invoker, play, sourceLink); BeforeResourceStarted?.Invoke(this, playInfo); if (string.IsNullOrWhiteSpace(play.PlayUri)) @@ -170,14 +185,14 @@ private E StartResource(InvokerData invoker, PlayResource play, MetaDa } Log.Debug("AudioResource start: {0}", play); - var result = playerConnection.AudioStart(play); + var result = playerConnection.Play(play); if (!result) { Log.Error("Error return from player: {0}", result.Error); return new LocalStr(strings.error_playmgr_internal_error); } - playerConnection.Volume = Util.Clamp(playerConnection.Volume, confBot.Audio.Volume.Min, confBot.Audio.Volume.Max); + playerConnection.Volume = Tools.Clamp(playerConnection.Volume, confBot.Audio.Volume.Min, confBot.Audio.Volume.Max); CurrentPlayData = playInfo; // TODO meta as readonly AfterResourceStarted?.Invoke(this, playInfo); @@ -192,7 +207,7 @@ private E StartCurrent(InvokerData invoker, bool manually = true) var result = StartResource(invoker, pli); if (result.Ok) return result; - Log.Warn("Skipping: {0} because {1}", pli.DisplayString, result.Error.Str); + Log.Warn("Skipping: {0} because {1}", pli, result.Error.Str); return Next(invoker, manually); } @@ -205,7 +220,7 @@ public E Next(InvokerData invoker, bool manually = true) var result = StartResource(invoker, pli); if (result.Ok) return result; - Log.Warn("Skipping: {0} because {1}", pli.DisplayString, result.Error.Str); + Log.Warn("Skipping: {0} because {1}", pli, result.Error.Str); } if (pli is null) return new LocalStr(strings.info_playmgr_no_next_song); @@ -215,25 +230,14 @@ public E Next(InvokerData invoker, bool manually = true) public E Previous(InvokerData invoker, bool manually = true) { - bool skipPrev = CurrentPlayData?.MetaData.From != PlaySource.FromPlaylist; PlaylistItem pli = null; for (int i = 0; i < 10; i++) { - if (skipPrev) - { - pli = playlistManager.Current; - skipPrev = false; - } - else - { - pli = playlistManager.Previous(manually); - } - if (pli is null) break; - + if ((pli = playlistManager.Previous(manually)) is null) break; var result = StartResource(invoker, pli); if (result.Ok) return result; - Log.Warn("Skipping: {0} because {1}", pli.DisplayString, result.Error.Str); + Log.Warn("Skipping: {0} because {1}", pli, result.Error.Str); } if (pli is null) return new LocalStr(strings.info_playmgr_no_previous_song); @@ -247,7 +251,7 @@ public E Previous(InvokerData invoker, bool manually = true) private void StopInternal(bool songEndedByCallback) { - BeforeResourceStopped?.Invoke(this, new SongEndEventArgs(songEndedByCallback)); + ResourceStopped?.Invoke(this, new SongEndEventArgs(songEndedByCallback)); if (songEndedByCallback) { @@ -258,11 +262,11 @@ private void StopInternal(bool songEndedByCallback) } else { - playerConnection.AudioStop(); + playerConnection.Stop(); } CurrentPlayData = null; - AfterResourceStopped?.Invoke(this, EventArgs.Empty); + PlaybackStopped?.Invoke(this, EventArgs.Empty); } public void Update(SongInfoChanged newInfo) @@ -275,5 +279,21 @@ public void Update(SongInfoChanged newInfo) // further properties... OnResourceUpdated?.Invoke(this, data); } + + public static MetaData ParseAttributes(string[] attrs) + { + if (attrs is null || attrs.Length == 0) + return null; + + var meta = new MetaData(); + foreach (var attr in attrs) + { + if (attr.StartsWith("@")) + { + meta.StartOffset = TextUtil.ParseTime(attr.Substring(1)); + } + } + return meta; + } } } diff --git a/TS3AudioBot/Audio/Player.cs b/TS3AudioBot/Audio/Player.cs new file mode 100644 index 00000000..64b53048 --- /dev/null +++ b/TS3AudioBot/Audio/Player.cs @@ -0,0 +1,167 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TS3AudioBot.ResourceFactories; +using TSLib; +using TSLib.Audio; +using TSLib.Helper; + +namespace TS3AudioBot.Audio +{ + public class Player : IDisposable + { + private const Codec SendCodec = Codec.OpusMusic; + + public IPlayerSource CurrentPlayerSource { get; private set; } + public StallCheckPipe StallCheckPipe { get; } + public VolumePipe VolumePipe { get; } + public FfmpegProducer FfmpegProducer { get; } + public PreciseTimedPipe TimePipe { get; } + public PassiveMergePipe MergePipe { get; } + public EncoderPipe EncoderPipe { get; } + public IAudioPassiveConsumer PlayerSink { get; private set; } + + public Player(ConfBot config, Id id) + { + FfmpegProducer = new FfmpegProducer(config.GetParent().Tools.Ffmpeg, id); + StallCheckPipe = new StallCheckPipe(); + VolumePipe = new VolumePipe(); + Volume = config.Audio.Volume.Default; + EncoderPipe = new EncoderPipe(SendCodec) { Bitrate = ScaleBitrate(config.Audio.Bitrate) }; + TimePipe = new PreciseTimedPipe { ReadBufferSize = EncoderPipe.PacketSize }; + TimePipe.Initialize(EncoderPipe, id); + MergePipe = new PassiveMergePipe(); + + config.Audio.Bitrate.Changed += (s, e) => EncoderPipe.Bitrate = ScaleBitrate(e.NewValue); + + MergePipe.Into(TimePipe).Chain().Chain(StallCheckPipe).Chain(VolumePipe).Chain(EncoderPipe); + } + + public void SetTarget(IAudioPassiveConsumer target) + { + PlayerSink = target; + EncoderPipe.Chain(target); + } + + private static int ScaleBitrate(int value) => Tools.Clamp(value, 1, 255) * 1000; + + public event EventHandler OnSongEnd; + public event EventHandler OnSongUpdated; + + private void TriggerSongEnd(object o, EventArgs e) => OnSongEnd?.Invoke(this, EventArgs.Empty); + private void TriggerSongUpdated(object o, SongInfoChanged e) => OnSongUpdated?.Invoke(this, e); + + public E Play(PlayResource res) + { + E result; + if (res is MediaPlayResource mres && mres.IsIcyStream) + result = FfmpegProducer.AudioStartIcy(res.PlayUri); + else + result = FfmpegProducer.AudioStart(res.PlayUri, res.Meta?.StartOffset); + if (result) + Play(FfmpegProducer); + return result; + } + + public void Play(IPlayerSource source) + { + var oldSource = CurrentPlayerSource; + if (oldSource != source) + { + // Clean up old + CleanSource(oldSource); + // Set events + source.OnSongEnd += TriggerSongEnd; + source.OnSongUpdated += TriggerSongUpdated; + // Update pipes + MergePipe.Add(source); + CurrentPlayerSource = source; + } + // Start Ticker + TimePipe.AudioTimer.Reset(); + TimePipe.Paused = false; + } + + private void CleanSource(IPlayerSource source) + { + if (source is null) + return; + source.OnSongEnd -= TriggerSongEnd; + source.OnSongUpdated -= TriggerSongUpdated; + MergePipe.Remove(source); + source.Dispose(); + } + + public void Stop() + { + CurrentPlayerSource?.Dispose(); + if (MergePipe.Count <= 1) + TimePipe.Paused = true; + } + + public void StopAll() + { + Stop(); + TimePipe.Paused = true; + MergePipe.Dispose(); + } + + public TimeSpan Length => CurrentPlayerSource?.Length ?? TimeSpan.Zero; + + public TimeSpan Position + { + get => CurrentPlayerSource?.Position ?? TimeSpan.Zero; + set + { + if (CurrentPlayerSource != null) + CurrentPlayerSource.Position = value; + } + } + + public float Volume + { + get => AudioValues.FactorToHumanVolume(VolumePipe.Volume); + set => VolumePipe.Volume = AudioValues.HumanVolumeToFactor(value); + } + + public bool Paused + { + get => TimePipe.Paused; + set => TimePipe.Paused = value; + } + + // Extras + + public void SetStall() => StallCheckPipe.SetStall(); + + [Obsolete(AttributeStrings.UnderDevelopment)] + public void MixInStreamOnce(IPlayerSource producer) + { + producer.OnSongEnd += (s, e) => + { + MergePipe.Remove(producer); + producer.Dispose(); + }; + MergePipe.Add(producer); + TimePipe.Paused = false; + } + + public void Dispose() + { + StopAll(); + CleanSource(CurrentPlayerSource); + TimePipe.Dispose(); + FfmpegProducer.Dispose(); + EncoderPipe.Dispose(); + } + } +} diff --git a/TS3AudioBot/Audio/SongEndEventArgs.cs b/TS3AudioBot/Audio/SongEndEventArgs.cs index 6b3d8b6f..00c69bb4 100644 --- a/TS3AudioBot/Audio/SongEndEventArgs.cs +++ b/TS3AudioBot/Audio/SongEndEventArgs.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.Audio { - using System; - public class SongEndEventArgs : EventArgs { public bool SongEndedByCallback { get; } diff --git a/TS3AudioBot/Audio/SongInfoChanged.cs b/TS3AudioBot/Audio/SongInfoChanged.cs index 5d710bed..9b5e02c5 100644 --- a/TS3AudioBot/Audio/SongInfoChanged.cs +++ b/TS3AudioBot/Audio/SongInfoChanged.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.Audio { - using System; - public class SongInfoChanged : EventArgs { public string Title { get; set; } diff --git a/TS3AudioBot/Audio/StallCheckPipe.cs b/TS3AudioBot/Audio/StallCheckPipe.cs index 52d56ef3..dfa34eb5 100644 --- a/TS3AudioBot/Audio/StallCheckPipe.cs +++ b/TS3AudioBot/Audio/StallCheckPipe.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TSLib.Audio; + namespace TS3AudioBot.Audio { - using System; - using TS3Client.Audio; - public class StallCheckPipe : IAudioPipe { private const uint StallCountInterval = 10; diff --git a/TS3AudioBot/Audio/StreamAudioPlayerSource.cs b/TS3AudioBot/Audio/StreamAudioPlayerSource.cs new file mode 100644 index 00000000..369af6d7 --- /dev/null +++ b/TS3AudioBot/Audio/StreamAudioPlayerSource.cs @@ -0,0 +1,56 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using TSLib.Audio; + +namespace TS3AudioBot.Audio +{ + public class StreamAudioPlayerSource : IPlayerSource, IAudioActiveConsumer + { + private bool hasFired = false; + + public IAudioPassiveProducer InStream { get; set; } + public TimeSpan Length => TimeSpan.Zero; + public TimeSpan Position { get => TimeSpan.Zero; set { } } + + public event EventHandler OnSongEnd; + event EventHandler IPlayerSource.OnSongUpdated { add { } remove { } } + + public StreamAudioPlayerSource() { } + + public StreamAudioPlayerSource(IAudioPassiveProducer stream) : this() + { + InStream = stream; + } + + public int Read(byte[] buffer, int offset, int length, out Meta meta) + { + var stream = InStream; + if (stream is null) + { + meta = default; + return 0; + } + + var read = stream.Read(buffer, offset, length, out meta); + if (read == 0 && !hasFired) + { + hasFired = true; + OnSongEnd?.Invoke(this, EventArgs.Empty); + return 0; + } + return read; + } + + public void Reset() => hasFired = false; + + public void Dispose() => InStream?.Dispose(); + } +} diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index e0c22eb7..7e0082e6 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -7,31 +7,32 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using TS3AudioBot.Algorithm; +using TS3AudioBot.Audio; +using TS3AudioBot.CommandSystem; +using TS3AudioBot.CommandSystem.Text; +using TS3AudioBot.Config; +using TS3AudioBot.Dependency; +using TS3AudioBot.Environment; +using TS3AudioBot.Helper; +using TS3AudioBot.History; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists; +using TS3AudioBot.Plugins; +using TS3AudioBot.ResourceFactories; +using TS3AudioBot.Sessions; +using TSLib; +using TSLib.Full; +using TSLib.Helper; +using TSLib.Messages; + namespace TS3AudioBot { - using Audio; - using Algorithm; - using CommandSystem; - using CommandSystem.CommandResults; - using CommandSystem.Text; - using Config; - using Dependency; - using Helper; - using History; - using Localization; - using Playlists; - using Plugins; - using Sessions; - using System; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using TS3AudioBot.ResourceFactories; - using TS3Client; - using TS3Client.Messages; - using TS3Client.Helper; - using TS3Client.Full; - /// Core class managing all bots and utility modules. public sealed class Bot : IDisposable { @@ -39,6 +40,7 @@ public sealed class Bot : IDisposable private readonly ConfBot config; private TickWorker idleTickWorker; + private TickWorker aloneTickWorker; internal object SyncRoot { get; } = new object(); internal bool IsDisposed { get; private set; } @@ -49,22 +51,20 @@ public sealed class Bot : IDisposable public string Name => config.Name; public bool QuizMode { get; set; } - private readonly ResourceFactory resourceFactory; - private readonly CommandManager commandManager; - private Ts3Client clientConnection; - private Ts3FullClient tsFullClient; + private ResolveContext resourceResolver; + private Ts3Client ts3client; + private TsFullClient ts3FullClient; private SessionManager sessionManager; private PlayManager playManager; private IVoiceTarget targetManager; - private IPlayerConnection playerConnection; + private Player player; + private Stats stats; - public Bot(Id id, ConfBot config, BotInjector injector, ResourceFactory resourceFactory, CommandManager commandManager) + public Bot(Id id, ConfBot config, BotInjector injector) { this.Id = id; this.config = config; this.Injector = injector; - this.resourceFactory = resourceFactory; - this.commandManager = commandManager; } public E InitializeBot() @@ -78,7 +78,7 @@ public E InitializeBot() if (!langResult.Ok) Log.Error("Failed to load language file ({0})", langResult.Error); }; - config.Events.IdleTime.Changed += (s, e) => EnableIdleTickWorker(); + config.Events.IdleDelay.Changed += (s, e) => EnableIdleTickWorker(); config.Events.OnIdle.Changed += (s, e) => EnableIdleTickWorker(); var builder = new DependencyBuilder(Injector); @@ -89,11 +89,17 @@ public E InitializeBot() builder.RequestModule(); builder.RequestModule(); builder.AddModule(Id); - builder.AddModule(new Ts3FullClient()); - builder.RequestModule(); + builder.AddModule(new TsFullClient()); + builder.RequestModule(); builder.RequestModule(); - builder.RequestModule(); + builder.RequestModule(); + builder.RequestModule(); + builder.RequestModule(); builder.RequestModule(); + builder.RequestModule(); + if (!builder.TryCreate(out var commandManager)) + return "Failed to create commandManager"; + builder.AddModule(commandManager); if (config.History.Enabled) { builder.AddModule(config.History); @@ -107,46 +113,61 @@ public E InitializeBot() return "Could not load all bot modules"; } - tsFullClient = Injector.GetModule(); - clientConnection = Injector.GetModule(); - playerConnection = clientConnection; - Injector.AddModule(clientConnection.TargetPipe); - Injector.AddModule(tsFullClient.Book); + resourceResolver = Injector.GetModule(); + ts3FullClient = Injector.GetModule(); + ts3client = Injector.GetModule(); + player = Injector.GetModule(); + player.SetTarget(Injector.GetModule()); + Injector.AddModule(ts3FullClient.Book); playManager = Injector.GetModule(); targetManager = Injector.GetModule(); sessionManager = Injector.GetModule(); + stats = Injector.GetModule(); - playerConnection.OnSongEnd += playManager.SongStoppedEvent; - playerConnection.OnSongUpdated += (s, e) => playManager.Update(e); + player.OnSongEnd += playManager.SongStoppedEvent; + player.OnSongUpdated += (s, e) => playManager.Update(e); // Update idle status events playManager.BeforeResourceStarted += (s, e) => DisableIdleTickWorker(); - playManager.AfterResourceStopped += (s, e) => EnableIdleTickWorker(); + playManager.PlaybackStopped += (s, e) => EnableIdleTickWorker(); // Used for the voice_mode script playManager.BeforeResourceStarted += BeforeResourceStarted; // Update the own status text to the current song title playManager.AfterResourceStarted += LoggedUpdateBotStatus; - playManager.AfterResourceStopped += LoggedUpdateBotStatus; + playManager.PlaybackStopped += LoggedUpdateBotStatus; playManager.OnResourceUpdated += LoggedUpdateBotStatus; // Log our resource in the history if (Injector.TryGet(out var historyManager)) - playManager.AfterResourceStarted += (s, e) => historyManager.LogAudioResource(new HistorySaveData(e.PlayResource.BaseData, e.Invoker.ClientUid)); + playManager.AfterResourceStarted += (s, e) => historyManager.LogAudioResource(new HistorySaveData(e.PlayResource.BaseData, e.MetaData.ResourceOwnerUid)); // Update our thumbnail playManager.AfterResourceStarted += GenerateStatusImage; - playManager.AfterResourceStopped += GenerateStatusImage; + playManager.PlaybackStopped += GenerateStatusImage; + // Stats + playManager.AfterResourceStarted += (s, e) => stats.TrackSongStart(Id, e.ResourceData.AudioType); + playManager.ResourceStopped += (s, e) => stats.TrackSongStop(Id); // Register callback for all messages happening - clientConnection.OnMessageReceived += OnMessageReceived; + ts3client.OnMessageReceived += OnMessageReceived; // Register callback to remove open private sessions, when user disconnects - tsFullClient.OnEachClientLeftView += OnClientLeftView; - clientConnection.OnBotConnected += OnBotConnected; - clientConnection.OnBotDisconnect += OnBotDisconnect; - + ts3FullClient.OnEachClientLeftView += OnClientLeftView; + ts3client.OnBotConnected += OnBotConnected; + ts3client.OnBotDisconnect += OnBotDisconnect; + // Alone mode + ts3client.OnAloneChanged += OnAloneChanged; + // Whisper stall + ts3client.OnWhisperNoTarget += (s, e) => player.SetStall(); + + commandManager.RegisterCollection(MainCommands.Bag); + // TODO remove after plugin rework + var pluginManager = Injector.GetModule(); + foreach (var plugin in pluginManager.Plugins) + if (plugin.Type == PluginType.CorePlugin || plugin.Type == PluginType.Commands) + commandManager.RegisterCollection(plugin.CorePlugin.Bag); // Restore all alias from the config foreach (var alias in config.Commands.Alias.GetAllItems()) commandManager.RegisterAlias(alias.Key, alias.Value).UnwrapToLog(Log); - // Connect the query after everyting is set up - return clientConnection.Connect(); + // Connect after everyting is set up + return ts3client.Connect(); } private void OnBotConnected(object sender, EventArgs e) @@ -157,7 +178,7 @@ private void OnBotConnected(object sender, EventArgs e) var badges = config.Connect.Badges.Value; if (!string.IsNullOrEmpty(badges)) - clientConnection?.ChangeBadges(badges); + ts3client?.ChangeBadges(badges); var onStart = config.Events.OnConnect.Value; if (!string.IsNullOrEmpty(onStart)) @@ -200,26 +221,28 @@ private void OnMessageReceived(object sender, TextMessage textMessage) Log.Info("User {0} requested: {1}", textMessage.InvokerName, textMessage.Message); - clientConnection.InvalidateClientBuffer(); + ts3client.InvalidateClientBuffer(); - ulong? channelId = null, databaseId = null, channelGroup = null; - ulong[] serverGroups = null; + ChannelId? channelId = null; + ClientDbId? databaseId = null; + ChannelGroupId? channelGroup = null; + ServerGroupId[] serverGroups = null; - if (tsFullClient.Book.Clients.TryGetValue(textMessage.InvokerId, out var bookClient)) + if (ts3FullClient.Book.Clients.TryGetValue(textMessage.InvokerId, out var bookClient)) { channelId = bookClient.Channel; databaseId = bookClient.DatabaseId; serverGroups = bookClient.ServerGroups.ToArray(); channelGroup = bookClient.ChannelGroup; } - else if (!clientConnection.GetClientInfoById(textMessage.InvokerId).GetOk(out var infoClient).GetError(out var infoClientError)) + else if (!ts3client.GetClientInfoById(textMessage.InvokerId).GetOk(out var infoClient).GetError(out var infoClientError)) { channelId = infoClient.ChannelId; databaseId = infoClient.DatabaseId; serverGroups = infoClient.ServerGroups; channelGroup = infoClient.ChannelGroup; } - else if (!clientConnection.GetCachedClientById(textMessage.InvokerId).GetOk(out var cachedClient).GetError(out var cachedClientError)) + else if (!ts3client.GetCachedClientById(textMessage.InvokerId).GetOk(out var cachedClient).GetError(out var cachedClientError)) { channelId = cachedClient.ChannelId; databaseId = cachedClient.DatabaseId; @@ -271,6 +294,50 @@ private void OnClientLeftView(object sender, ClientLeftView eventArgs) sessionManager.RemoveSession(eventArgs.ClientId); } + private void OnAloneChanged(object sender, AloneChanged e) + { + string script; + TimeSpan delay; + if (e.Alone) + { + script = config.Events.OnAlone.Value; + delay = config.Events.AloneDelay.Value; + } + else + { + script = config.Events.OnParty.Value; + delay = config.Events.PartyDelay.Value; + } + if (string.IsNullOrEmpty(script)) + return; + + void RunEvent() + { + var info = CreateExecInfo(); + CallScript(info, script, false, true); + }; + + SetAloneTickWorker(null); + if (delay <= TimeSpan.Zero) + { + RunEvent(); + } + else + { + var worker = TickPool.RegisterTickOnce(RunEvent, delay); + SetAloneTickWorker(worker); + } + } + + private void SetAloneTickWorker(TickWorker worker) + { + var oldWoker = Interlocked.Exchange(ref aloneTickWorker, worker); + if (oldWoker != null) + { + TickPool.UnregisterTicker(oldWoker); + } + } + private void LoggedUpdateBotStatus(object sender, EventArgs e) { if (IsDisposed) @@ -299,7 +366,7 @@ public E UpdateBotStatus(string overrideStr = null) setString = strings.info_botstatus_sleeping; } - return clientConnection.ChangeDescription(setString ?? ""); + return ts3client.ChangeDescription(setString ?? ""); } private void GenerateStatusImage(object sender, EventArgs e) @@ -307,36 +374,63 @@ private void GenerateStatusImage(object sender, EventArgs e) if (!config.GenerateStatusAvatar || IsDisposed) return; - if (e is PlayInfoEventArgs startEvent) + Stream GetRandomFile(string prefix) { - Task.Run(() => + try { - var thumresult = resourceFactory.GetThumbnail(startEvent.PlayResource); - if (!thumresult.Ok) - { - clientConnection.DeleteAvatar(); - return; - } + if (string.IsNullOrEmpty(config.LocalConfigDir)) + return null; + var avatarPath = new DirectoryInfo(Path.Combine(config.LocalConfigDir, BotPaths.Avatars)); + if (!avatarPath.Exists) + return null; + var avatars = avatarPath.EnumerateFiles(prefix).ToArray(); + if (avatars.Length == 0) + return null; + var pickedAvatar = Tools.PickRandom(avatars); + return pickedAvatar.Open(FileMode.Open, FileAccess.Read, FileShare.Read); + } + catch (Exception ex) + { + Log.Warn(ex, "Failed to load local avatar"); + return null; + } + } - using (var image = ImageUtil.ResizeImage(thumresult.Value)) + void Upload(Stream setStream) + { + if (setStream != null) + { + using (setStream) { - if (image is null) - return; - var result = clientConnection.UploadAvatar(image); + var result = ts3client.UploadAvatar(setStream); if (!result.Ok) Log.Warn("Could not save avatar: {0}", result.Error); } - }); + } } - else + + Task.Run(() => { - using (var sleepPic = Util.GetEmbeddedFile("TS3AudioBot.Media.SleepingKitty.png")) + Stream setStream = null; + if (e is PlayInfoEventArgs startEvent) { - var result = clientConnection.UploadAvatar(sleepPic); - if (!result.Ok) - Log.Warn("Could not save avatar: {0}", result.Error); + setStream = ImageUtil.ResizeImageSave(resourceResolver.GetThumbnail(startEvent.PlayResource).OkOr(null), out _).OkOr(null); + setStream = setStream ?? GetRandomFile("play*"); + Upload(setStream); } - } + else + { + setStream = GetRandomFile("sleep*"); + setStream = setStream ?? Util.GetEmbeddedFile("TS3AudioBot.Media.SleepingKitty.png"); + Upload(setStream); + } + + if (setStream is null) + { + ts3client.DeleteAvatar(); + return; + } + }); } private void BeforeResourceStarted(object sender, PlayInfoEventArgs e) @@ -365,6 +459,7 @@ private void BeforeResourceStarted(object sender, PlayInfoEventArgs e) private void CallScript(ExecutionInformation info, string command, bool answer, bool skipRights) { Log.Debug("Calling script (skipRights:{0}, answer:{1}): {2}", skipRights, answer, command); + stats.TrackCommandCall(answer); info.AddModule(new CallerInfo(false) { @@ -375,28 +470,15 @@ private void CallScript(ExecutionInformation info, string command, bool answer, TryCatchCommand(info, answer, () => { - // parse (and execute) the command - var res = commandManager.CommandSystem.Execute(info, command); + // parse and execute the command + var s = CommandManager.ExecuteCommand(info, command); if (!answer) return; // Write result to user - switch (res.ResultType) - { - case CommandResultType.String: - var sRes = (StringCommandResult)res; - if (!string.IsNullOrEmpty(sRes.Content)) - info.Write(sRes.Content).UnwrapToLog(Log); - break; - - case CommandResultType.Empty: - break; - - default: - Log.Warn("Got result which is not a string/empty. Result: {0}", res.ToString()); - break; - } + if (!string.IsNullOrEmpty(s)) + info.Write(s).UnwrapToLog(Log); }); } @@ -425,9 +507,12 @@ private void OnIdle() private void EnableIdleTickWorker() { - var idleTime = config.Events.IdleTime.Value; + var idleTime = config.Events.IdleDelay.Value; if (idleTime <= TimeSpan.Zero || string.IsNullOrEmpty(config.Events.OnIdle.Value)) + { + DisableIdleTickWorker(); return; + } var newWorker = TickPool.RegisterTick(OnIdle, idleTime, false); SetIdleTickWorker(newWorker); newWorker.Active = true; @@ -486,8 +571,8 @@ public BotLock GetBotLock() { Id = Id, Name = config.Name, - Server = tsFullClient.ConnectionData.Address, - Status = tsFullClient.Connected ? BotStatus.Connected : BotStatus.Connecting, + Server = ts3FullClient.ConnectionData.Address, + Status = ts3FullClient.Connected ? BotStatus.Connected : BotStatus.Connecting, }; public void Dispose() @@ -505,7 +590,7 @@ public void Dispose() Injector.GetModule()?.StopPlugins(this); Injector.GetModule()?.Stop(); - Injector.GetModule()?.Dispose(); + Injector.GetModule()?.Dispose(); Injector.GetModule()?.Dispose(); config.ClearEvents(); } diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index 5907cbc5..e95ac7f4 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -7,21 +7,22 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using TS3AudioBot.Config; +using TS3AudioBot.Dependency; +using TS3AudioBot.Helper; +using TSLib.Helper; + namespace TS3AudioBot { - using Config; - using Dependency; - using Helper; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - public class BotManager : IDisposable { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); - private List activeBots; + private List activeBots = new List(); private readonly object lockObj = new object(); private readonly ConfRoot confRoot; @@ -29,7 +30,6 @@ public class BotManager : IDisposable public BotManager(ConfRoot confRoot, CoreInjector coreInjector) { - Util.Init(out activeBots); this.confRoot = confRoot; this.coreInjector = coreInjector; } @@ -56,7 +56,7 @@ public void RunBots(bool interactive) string address = Interactive.LoopAction("Please enter the ip, domain or nickname (with port; default: 9987) where to connect to:", addr => { - if (TS3Client.TsDnsResolver.TryResolve(addr, out _)) + if (TSLib.TsDnsResolver.TryResolve(addr, out _)) return true; Console.WriteLine("The address seems invalid or could not be resolved, continue anyway? [y/N]"); return Interactive.UserAgree(defaultTo: false); @@ -129,7 +129,7 @@ public R RunBot(ConfBot config) var botInjector = new BotInjector(coreInjector); botInjector.AddModule(botInjector); - botInjector.AddModule(new TS3Client.Helper.Id(id.Value)); + botInjector.AddModule(new Id(id.Value)); botInjector.AddModule(config); if (!botInjector.TryCreate(out bot)) return "Failed to create new Bot"; @@ -220,6 +220,19 @@ public BotLock GetBotLock(string name) return bot.GetBotLock(); } + internal void IterateAll(Action body) + { + lock (lockObj) + { + if (activeBots is null) + return; + foreach (var bot in activeBots) + { + body(bot); + } + } + } + public void StopBot(Bot bot) { RemoveBot(bot); @@ -250,6 +263,16 @@ public BotInfo[] GetBotInfolist() } } + public uint GetRunningBotCount() + { + lock (lockObj) + { + if (activeBots is null) + return 0; + return (uint)activeBots.Count(x => x != null); + } + } + public void Dispose() { List disposeBots; diff --git a/TS3AudioBot/ClientCall.cs b/TS3AudioBot/ClientCall.cs index f3ffdf1c..384f9c87 100644 --- a/TS3AudioBot/ClientCall.cs +++ b/TS3AudioBot/ClientCall.cs @@ -7,6 +7,8 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using TSLib; + namespace TS3AudioBot { public class ClientCall : InvokerData @@ -14,17 +16,18 @@ public class ClientCall : InvokerData /// The original unmodified string which was received by the client. public string TextMessage { get; } - public ulong? DatabaseId { get; } - public ulong? ChannelId { get; } - public ushort? ClientId { get; } + public ClientDbId? DatabaseId { get; } + public ChannelId? ChannelId { get; } + public ClientId? ClientId { get; } public string NickName { get; } - public ulong[] ServerGroups { get; } - public ulong? ChannelGroup { get; } - public TS3Client.TextMessageTargetMode? Visibiliy { get; internal set; } + public ServerGroupId[] ServerGroups { get; } + public ChannelGroupId? ChannelGroup { get; } + public TextMessageTargetMode? Visibiliy { get; internal set; } - public ClientCall(string clientUid, string textMessage, ulong? databaseId = null, ulong? channelId = null, - ushort? clientId = null, string nickName = null, TS3Client.TextMessageTargetMode? visibiliy = null, - ulong[] serverGroups = null, ulong? channelGroup = null) : base(clientUid) + public ClientCall(Uid clientUid, string textMessage, ClientDbId? databaseId = null, + ChannelId? channelId = null, ClientId? clientId = null, string nickName = null, + TextMessageTargetMode? visibiliy = null, ServerGroupId[] serverGroups = null, + ChannelGroupId? channelGroup = null) : base(clientUid) { TextMessage = textMessage; DatabaseId = databaseId; diff --git a/TS3AudioBot/CommandSystem/Ast/AstCommand.cs b/TS3AudioBot/CommandSystem/Ast/AstCommand.cs index 56798565..d1ec2ecd 100644 --- a/TS3AudioBot/CommandSystem/Ast/AstCommand.cs +++ b/TS3AudioBot/CommandSystem/Ast/AstCommand.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; +using System.Text; + namespace TS3AudioBot.CommandSystem.Ast { - using System.Collections.Generic; - using System.Text; - internal class AstCommand : AstNode { public override AstType Type => AstType.Command; diff --git a/TS3AudioBot/CommandSystem/Ast/AstError.cs b/TS3AudioBot/CommandSystem/Ast/AstError.cs index cf14e8c7..082e1ccb 100644 --- a/TS3AudioBot/CommandSystem/Ast/AstError.cs +++ b/TS3AudioBot/CommandSystem/Ast/AstError.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Text; + namespace TS3AudioBot.CommandSystem.Ast { - using System.Text; - internal class AstError : AstNode { public override AstType Type => AstType.Error; diff --git a/TS3AudioBot/CommandSystem/Ast/AstNode.cs b/TS3AudioBot/CommandSystem/Ast/AstNode.cs index 533f6adb..6d1e5a60 100644 --- a/TS3AudioBot/CommandSystem/Ast/AstNode.cs +++ b/TS3AudioBot/CommandSystem/Ast/AstNode.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Text; + namespace TS3AudioBot.CommandSystem.Ast { - using System.Text; - public abstract class AstNode { public abstract AstType Type { get; } diff --git a/TS3AudioBot/CommandSystem/Ast/AstValue.cs b/TS3AudioBot/CommandSystem/Ast/AstValue.cs index 0d6af962..9b270be2 100644 --- a/TS3AudioBot/CommandSystem/Ast/AstValue.cs +++ b/TS3AudioBot/CommandSystem/Ast/AstValue.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Text; + namespace TS3AudioBot.CommandSystem.Ast { - using System.Text; - internal class AstValue : AstNode { private string value; diff --git a/TS3AudioBot/CommandSystem/Ast/StringType.cs b/TS3AudioBot/CommandSystem/Ast/StringType.cs index 8338886d..c84b217b 100644 --- a/TS3AudioBot/CommandSystem/Ast/StringType.cs +++ b/TS3AudioBot/CommandSystem/Ast/StringType.cs @@ -9,7 +9,7 @@ namespace TS3AudioBot.CommandSystem.Ast { - enum StringType + internal enum StringType { FreeString, QuotedString, diff --git a/TS3AudioBot/CommandSystem/BotCommand.cs b/TS3AudioBot/CommandSystem/BotCommand.cs index 2d6e93e8..7c36b3f6 100644 --- a/TS3AudioBot/CommandSystem/BotCommand.cs +++ b/TS3AudioBot/CommandSystem/BotCommand.cs @@ -7,28 +7,30 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using TS3AudioBot.CommandSystem.Commands; +using TS3AudioBot.Localization; + namespace TS3AudioBot.CommandSystem { - using CommandResults; - using Commands; - using Localization; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using System.Text; - [DebuggerDisplay("{DebuggerDisplay, nq}")] + [JsonObject(MemberSerialization.OptIn)] public class BotCommand : FunctionCommand { private readonly string helpLookupName; private string cachedFullQualifiedName; - private object cachedAsJsonObj; + [JsonProperty(PropertyName = "Name")] public string InvokeName { get; } private readonly string[] requiredRights; public string RequiredRight => requiredRights[0]; + [JsonProperty(PropertyName = "Description")] public string Description => LocalizationManager.GetString(helpLookupName); public UsageAttribute[] UsageList { get; } public string FullQualifiedName @@ -40,9 +42,9 @@ public string FullQualifiedName var strb = new StringBuilder(); strb.Append(InvokeName); strb.Append(" ("); - strb.Append(string.Join(", ", CommandParameter.Where(p => !p.kind.IsNormal()).Select(p => p.type.FullName).OrderBy(p => p))); + strb.Append(string.Join(", ", CommandParameter.Where(p => !p.Kind.IsNormal()).Select(p => p.Type.FullName).OrderBy(p => p))); strb.Append("|"); - strb.Append(string.Join(", ", CommandParameter.Where(p => p.kind.IsNormal()).Select(p => p.type.FullName))); + strb.Append(string.Join(", ", CommandParameter.Where(p => p.Kind.IsNormal()).Select(p => p.Type.FullName))); strb.Append(")"); cachedFullQualifiedName = strb.ToString(); } @@ -50,6 +52,13 @@ public string FullQualifiedName } } + [JsonProperty(PropertyName = "Return")] + public string Return { get; set; } + [JsonProperty(PropertyName = "Parameter")] + public (string name, string type, bool optional)[] Parameter { get; } + [JsonProperty(PropertyName = "Modules")] + public (string type, bool optional)[] Modules { get; } + public string DebuggerDisplay { get @@ -63,20 +72,41 @@ public string DebuggerDisplay } } - public object AsJsonObj => cachedAsJsonObj ?? (cachedAsJsonObj = new CommandSerializeObj(this)); - public BotCommand(CommandBuildInfo buildInfo) : base(buildInfo.Method, buildInfo.Parent) { InvokeName = buildInfo.CommandData.CommandNameSpace; helpLookupName = buildInfo.CommandData.OverrideHelpName ?? ("cmd_" + InvokeName.Replace(" ", "_") + "_help"); requiredRights = new[] { "cmd." + string.Join(".", InvokeName.Split(' ')) }; UsageList = buildInfo.UsageList?.ToArray() ?? Array.Empty(); + // Serialization + Return = UnwrapReturnType(CommandReturn).Name; + Parameter = ( + from x in CommandParameter + where x.Kind.IsNormal() + select (x.Name, UnwrapParamType(x.Type).Name, x.Optional)).ToArray(); + Modules = ( + from x in CommandParameter + where x.Kind == ParamKind.Dependency + select (x.Type.Name, x.Optional)).ToArray(); } public override string ToString() { var strb = new StringBuilder(); - strb.Append("\n!").Append(InvokeName).Append(": ").Append(Description ?? strings.error_no_help ?? ""); + strb.Append("\n!") + .Append(InvokeName); + + foreach (var (name, _, optional) in Parameter) + { + strb.Append(' '); + if (optional) + strb.Append("[<").Append(name).Append(">]"); + else + strb.Append('<').Append(name).Append('>'); + } + + strb.Append(": ") + .Append(Description ?? strings.error_no_help ?? ""); if (UsageList.Length > 0) { @@ -88,7 +118,7 @@ public override string ToString() return strb.ToString(); } - public override ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public override object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { // Check call complexity info.UseComplexityTokens(1); @@ -99,32 +129,6 @@ public override ICommandResult Execute(ExecutionInformation info, IReadOnlyList< return base.Execute(info, arguments, returnTypes); } - - private class CommandSerializeObj - { - private readonly BotCommand botCmd; - public string Name => botCmd.InvokeName; - public string Description => botCmd.Description; - public string[] Parameter { get; } - public string[] Modules { get; } - public string Return { get; } - - public CommandSerializeObj(BotCommand botCmd) - { - this.botCmd = botCmd; - Parameter = ( - from x in botCmd.CommandParameter - where x.kind.IsNormal() - select UnwrapParamType(x.type).Name + (x.optional ? "?" : "")).ToArray(); - Modules = ( - from x in botCmd.CommandParameter - where x.kind == ParamKind.Dependency - select x.type.Name + (x.optional ? "?" : "")).ToArray(); - Return = UnwrapReturnType(botCmd.CommandReturn).Name; - } - - public override string ToString() => botCmd.ToString(); - } } public class CommandBuildInfo diff --git a/TS3AudioBot/CommandSystem/CommandAttribute.cs b/TS3AudioBot/CommandSystem/CommandAttribute.cs index a2120e79..df718eb4 100644 --- a/TS3AudioBot/CommandSystem/CommandAttribute.cs +++ b/TS3AudioBot/CommandSystem/CommandAttribute.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.CommandSystem { - using System; - /// /// Marks a method as callable from the CommandSystem. /// The containing class must be registered in the CommandSystem to use this method. diff --git a/TS3AudioBot/CommandSystem/CommandException.cs b/TS3AudioBot/CommandSystem/CommandException.cs index 8361703c..ae7ae2b5 100644 --- a/TS3AudioBot/CommandSystem/CommandException.cs +++ b/TS3AudioBot/CommandSystem/CommandException.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Runtime.Serialization; + namespace TS3AudioBot.CommandSystem { - using System; - using System.Runtime.Serialization; - [Serializable] public class CommandException : Exception { diff --git a/TS3AudioBot/CommandSystem/CommandManager.cs b/TS3AudioBot/CommandSystem/CommandManager.cs index b6a598cb..285b18b4 100644 --- a/TS3AudioBot/CommandSystem/CommandManager.cs +++ b/TS3AudioBot/CommandSystem/CommandManager.cs @@ -7,18 +7,24 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using TS3AudioBot.CommandSystem.Ast; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.CommandSystem.Commands; +using TS3AudioBot.CommandSystem.Text; +using TS3AudioBot.Dependency; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.Rights; +using TSLib.Helper; +using static TS3AudioBot.CommandSystem.CommandSystemTypes; + namespace TS3AudioBot.CommandSystem { - using Commands; - using Helper; - using Rights; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Text.RegularExpressions; - using TS3AudioBot.Localization; - /// Mangement for the bot command system. public class CommandManager { @@ -26,30 +32,28 @@ public class CommandManager private static readonly Regex CommandNamespaceValidator = new Regex(@"^[a-z\d]+( [a-z\d]+)*$", Util.DefaultRegexConfig & ~RegexOptions.IgnoreCase); - private readonly Dictionary aliasPaths; - private readonly HashSet commandPaths; - private readonly HashSet baggedCommands; + private readonly Dictionary aliasPaths = new Dictionary(); + private readonly HashSet commandPaths = new HashSet(); + private readonly HashSet baggedCommands = new HashSet(); private readonly RightsManager rightsManager; + public RootGroup RootGroup { get; } = new RootGroup(); + public CommandManager(RightsManager rightsManager) { - CommandSystem = new XCommandSystem(); - Util.Init(out aliasPaths); - Util.Init(out commandPaths); - Util.Init(out baggedCommands); this.rightsManager = rightsManager; } - public XCommandSystem CommandSystem { get; } - public IEnumerable AllCommands => baggedCommands.SelectMany(x => x.BagCommands); public IEnumerable AllRights => AllCommands.Select(x => x.RequiredRight).Concat(baggedCommands.SelectMany(x => x.AdditionalRights)); + #region Management + public void RegisterCollection(ICommandBag bag) { if (baggedCommands.Contains(bag)) - throw new InvalidOperationException("This bag is already loaded."); + return; CheckDistinct(bag.BagCommands); baggedCommands.Add(bag); @@ -84,7 +88,7 @@ public E RegisterAlias(string path, string command) if (aliasPaths.ContainsKey(path)) return new LocalStr("Already exists"); // TODO - var dac = new AliasCommand(CommandSystem, command); + var dac = new AliasCommand(command); var res = LoadICommand(dac, path); if (!res) return new LocalStr(res.Error); // TODO @@ -181,40 +185,39 @@ private E LoadICommand(ICommand com, string path) private R BuildAndGet(IEnumerable comPath) { - CommandGroup group = CommandSystem.RootCommand; + CommandGroup group = RootGroup; // this for loop iterates through the separate names of // the command to be added. foreach (var comPathPart in comPath) { - ICommand currentCommand = group.GetCommand(comPathPart); - + switch (group.GetCommand(comPathPart)) + { // if a group to hold the next level command doesn't exist // it will be created here - if (currentCommand is null) - { + case null: var nextGroup = new CommandGroup(); group.AddCommand(comPathPart, nextGroup); group = nextGroup; - } + break; + // if the group already exists we can take it. - else if (currentCommand is CommandGroup cgCommand) - { + case CommandGroup cgCommand: group = cgCommand; - } + break; + // if the element is anything else, we have to replace it // with a group and put the old element back into it. - else if (currentCommand is FunctionCommand) - { + case FunctionCommand fnCommand: var subGroup = new CommandGroup(); group.RemoveCommand(comPathPart); group.AddCommand(comPathPart, subGroup); - var insertResult = InsertInto(group, currentCommand, comPathPart); + var insertResult = InsertInto(group, fnCommand, comPathPart); if (!insertResult.Ok) return insertResult.Error; group = subGroup; - } - else - { + break; + + default: return "An overloaded command cannot be replaced by a CommandGroup"; } } @@ -298,7 +301,7 @@ private void UnloadICommand(ICommand com, string path) var node = new CommandUnloadNode { ParentNode = null, - Self = CommandSystem.RootCommand, + Self = RootGroup, }; // build up the list to our desired node @@ -313,26 +316,28 @@ private void UnloadICommand(ICommand com, string path) Self = nextGroup, }; } + var subGroup = node.Self.GetCommand(comPath.Last()); + + switch (subGroup) + { // nothing to remove - if (subGroup is null) + case null: return; // if the subnode is a plain FunctionCommand then we found our command to delete - else if (subGroup is FunctionCommand || subGroup is AliasCommand) - { + case FunctionCommand _: + case AliasCommand _: node.Self.RemoveCommand(com); - } + break; // here we can delete our command from the overloader - else if (subGroup is OverloadedFunctionCommand subOverloadGroup) - { + case OverloadedFunctionCommand subOverloadGroup: if (com is FunctionCommand funcCom) subOverloadGroup.RemoveCommand(funcCom); else return; - } + break; // now to the special case when a command gets inserted with an empty string - else if (subGroup is CommandGroup insertGroup) - { + case CommandGroup insertGroup: // since we check precisely that only one command and only a simple FunctionCommand // can be added with an empty string, wen can delete it safely this way insertGroup.RemoveCommand(string.Empty); @@ -342,6 +347,7 @@ private void UnloadICommand(ICommand com, string path) ParentNode = node, Self = insertGroup, }; + break; } // and finally clean all empty nodes up @@ -358,5 +364,120 @@ private class CommandUnloadNode public CommandUnloadNode ParentNode { get; set; } public CommandGroup Self { get; set; } } + + #endregion + + #region Execution + + internal static ICommand AstToCommandResult(AstNode node) + { + switch (node.Type) + { + case AstType.Error: + throw new CommandException("Found an unconvertable ASTNode of type Error", CommandExceptionReason.InternalError); + case AstType.Command: + var cmd = (AstCommand)node; + var arguments = new ICommand[cmd.Parameter.Count]; + int tailCandidates = 0; + for (int i = cmd.Parameter.Count - 1; i >= 1; i--) + { + var para = cmd.Parameter[i]; + if (!(para is AstValue astVal) || astVal.StringType != StringType.FreeString) + break; + + arguments[i] = new TailStringAutoConvertCommand(new TailString(astVal.Value, astVal.TailString)); + tailCandidates++; + } + for (int i = 0; i < cmd.Parameter.Count - tailCandidates; i++) + arguments[i] = AstToCommandResult(cmd.Parameter[i]); + return new RootCommand(arguments); + case AstType.Value: + var astNode = (AstValue)node; + // Quoted strings are always strings, the rest gets automatically converted + if (astNode.StringType == StringType.FreeString) + return new AutoConvertResultCommand(astNode.Value); + else + return new ResultCommand(new PrimitiveResult(astNode.Value)); + default: + throw Tools.UnhandledDefault(node.Type); + } + } + + public static object Execute(ExecutionInformation info, string command, IReadOnlyList returnTypes) + { + var ast = CommandParser.ParseCommandRequest(command); + var cmd = AstToCommandResult(ast); + return cmd.Execute(info, Array.Empty(), returnTypes); + } + + public static object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + => info.GetModule().RootGroup.Execute(info, arguments, returnTypes); + + public static string ExecuteCommand(ExecutionInformation info, string command) + => CastResult(Execute(info, command, ReturnStringOrNothing)); + + public static string ExecuteCommand(ExecutionInformation info, IReadOnlyList arguments) + => CastResult(Execute(info, arguments, ReturnStringOrNothing)); + + private static string CastResult(object result) + { + if (result is IPrimitiveResult s) + return s.Get(); + if (result == null) + return null; + throw new CommandException("Expected a string or nothing as result", CommandExceptionReason.NoReturnMatch); + } + + public static object GetEmpty(IReadOnlyList resultTypes) + { + foreach (var item in resultTypes) + { + if (item == null) + return null; + else if (item == typeof(string)) + return string.Empty; + } + throw new CommandException("No empty return type available", CommandExceptionReason.NoReturnMatch); + } + + public static string GetTree(ICommand com) + { + var strb = new TextModBuilder(); + GetTree(com, strb, 0); + return strb.ToString(); + } + + private static void GetTree(ICommand com, TextModBuilder strb, int indent) + { + switch (com) + { + case CommandGroup group: + strb.AppendFormat("\n".Mod().Color(Color.Red)); + foreach (var subCom in group.Commands) + { + strb.Append(new string(' ', (indent + 1) * 2)).Append(subCom.Key); + GetTree(subCom.Value, strb, indent + 1); + } + break; + + case FunctionCommand _: + strb.AppendFormat("\n".Mod().Color(Color.Green)); + break; + + case OverloadedFunctionCommand ofunc: + strb.AppendFormat($"\n".Mod().Color(Color.Blue)); + break; + + case AliasCommand _: + strb.AppendFormat($"\n".Mod().Color(Color.Yellow)); + break; + + default: + strb.AppendFormat("\n"); + break; + } + } + + #endregion } } diff --git a/TS3AudioBot/CommandSystem/CommandParser.cs b/TS3AudioBot/CommandSystem/CommandParser.cs index f753cdc5..7ee63d67 100644 --- a/TS3AudioBot/CommandSystem/CommandParser.cs +++ b/TS3AudioBot/CommandSystem/CommandParser.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Text; +using TS3AudioBot.CommandSystem.Ast; + namespace TS3AudioBot.CommandSystem { - using Ast; - using System; - using System.Collections.Generic; - using System.Text; - internal static class CommandParser { public const char DefaultCommandChar = '!'; diff --git a/TS3AudioBot/CommandSystem/CommandResults/CommandCommandResult.cs b/TS3AudioBot/CommandSystem/CommandResults/CommandCommandResult.cs deleted file mode 100644 index bf94cc92..00000000 --- a/TS3AudioBot/CommandSystem/CommandResults/CommandCommandResult.cs +++ /dev/null @@ -1,27 +0,0 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3AudioBot.CommandSystem.CommandResults -{ - using Commands; - - public class CommandCommandResult : ICommandResult - { - public CommandResultType ResultType => CommandResultType.Command; - - public virtual ICommand Command { get; } - - public CommandCommandResult(ICommand commandArg) - { - Command = commandArg; - } - - public override string ToString() => "CommandCommandResult can't be converted into a string"; - } -} diff --git a/TS3AudioBot/CommandSystem/CommandResults/ICommandResult.cs b/TS3AudioBot/CommandSystem/CommandResults/IAudioResourceResult.cs similarity index 79% rename from TS3AudioBot/CommandSystem/CommandResults/ICommandResult.cs rename to TS3AudioBot/CommandSystem/CommandResults/IAudioResourceResult.cs index db91d319..2909fe3f 100644 --- a/TS3AudioBot/CommandSystem/CommandResults/ICommandResult.cs +++ b/TS3AudioBot/CommandSystem/CommandResults/IAudioResourceResult.cs @@ -7,10 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using TS3AudioBot.ResourceFactories; + namespace TS3AudioBot.CommandSystem.CommandResults { - public interface ICommandResult + public interface IAudioResourceResult { - CommandResultType ResultType { get; } + AudioResource AudioResource { get; } } } diff --git a/TS3AudioBot/CommandSystem/CommandResults/IPrimitiveResult.cs b/TS3AudioBot/CommandSystem/CommandResults/IPrimitiveResult.cs new file mode 100644 index 00000000..6fc2de2b --- /dev/null +++ b/TS3AudioBot/CommandSystem/CommandResults/IPrimitiveResult.cs @@ -0,0 +1,26 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +namespace TS3AudioBot.CommandSystem.CommandResults +{ + /// + /// A result which can safely used as a primitive like a string. + /// + /// The complete list of primitive types is . + /// + public interface IPrimitiveResult : IPrimitiveResult + { + new T Get(); + } + + public interface IPrimitiveResult + { + object Get(); + } +} diff --git a/TS3AudioBot/CommandSystem/CommandResults/EmptyCommandResult.cs b/TS3AudioBot/CommandSystem/CommandResults/PrimitiveResult.cs similarity index 60% rename from TS3AudioBot/CommandSystem/CommandResults/EmptyCommandResult.cs rename to TS3AudioBot/CommandSystem/CommandResults/PrimitiveResult.cs index e4560f51..ddbcc8ff 100644 --- a/TS3AudioBot/CommandSystem/CommandResults/EmptyCommandResult.cs +++ b/TS3AudioBot/CommandSystem/CommandResults/PrimitiveResult.cs @@ -9,13 +9,17 @@ namespace TS3AudioBot.CommandSystem.CommandResults { - public sealed class EmptyCommandResult : ICommandResult + public class PrimitiveResult : IPrimitiveResult { - public static EmptyCommandResult Instance { get; } = new EmptyCommandResult(); + public T Content { get; } - private EmptyCommandResult() { } + public PrimitiveResult(T contentArg) + { + Content = contentArg; + } - public CommandResultType ResultType => CommandResultType.Empty; - public override string ToString() => string.Empty; + public virtual T Get() => Content; + + object IPrimitiveResult.Get() => Content; } } diff --git a/TS3AudioBot/CommandSystem/CommandResults/ResultHelper.cs b/TS3AudioBot/CommandSystem/CommandResults/ResultHelper.cs new file mode 100644 index 00000000..0acc7741 --- /dev/null +++ b/TS3AudioBot/CommandSystem/CommandResults/ResultHelper.cs @@ -0,0 +1,61 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; +using static TS3AudioBot.CommandSystem.CommandSystemTypes; + +namespace TS3AudioBot.CommandSystem.CommandResults +{ + public static class ResultHelper + { + public static bool IsValidResult(object result, IReadOnlyList returnTypes) + { + if (result == null) + return returnTypes.Contains(null); + + return IsValidResultType(result.GetType(), returnTypes); + } + + public static bool IsValidResultType(Type resultType, IReadOnlyList returnTypes) + { + return returnTypes.Any(t => + { + if (t == null) + return false; + if (BasicTypes.Contains(t)) + { + var genType = typeof(IPrimitiveResult<>).MakeGenericType(t); + return genType.IsAssignableFrom(resultType); + } + else + return t.IsAssignableFrom(resultType); + }); + } + + /// + /// Automaticall wrapes primitive results in . + /// Otherwise returns the result. + /// + /// The valid result. + /// The type for the result. + /// The result value. + public static object ToResult(Type resultType, object result) + { + if (BasicTypes.Contains(resultType)) + { + var genType = typeof(PrimitiveResult<>).MakeGenericType(resultType); + return Activator.CreateInstance(genType, new object[] { result }); + } + else + return result; + } + } +} diff --git a/TS3AudioBot/CommandSystem/CommandResults/StringCommandResult.cs b/TS3AudioBot/CommandSystem/CommandResults/StringCommandResult.cs deleted file mode 100644 index f64b9827..00000000 --- a/TS3AudioBot/CommandSystem/CommandResults/StringCommandResult.cs +++ /dev/null @@ -1,26 +0,0 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3AudioBot.CommandSystem.CommandResults -{ - public class StringCommandResult : ICommandResult - { - public static readonly StringCommandResult Empty = new StringCommandResult(string.Empty); - - public CommandResultType ResultType => CommandResultType.String; - public virtual string Content { get; } - - public StringCommandResult(string contentArg) - { - Content = contentArg; - } - - public override string ToString() => Content; - } -} diff --git a/TS3AudioBot/CommandSystem/CommandResults/TailStringCommandResult.cs b/TS3AudioBot/CommandSystem/CommandResults/TailString.cs similarity index 68% rename from TS3AudioBot/CommandSystem/CommandResults/TailStringCommandResult.cs rename to TS3AudioBot/CommandSystem/CommandResults/TailString.cs index 4ba8ef14..0ea9a919 100644 --- a/TS3AudioBot/CommandSystem/CommandResults/TailStringCommandResult.cs +++ b/TS3AudioBot/CommandSystem/CommandResults/TailString.cs @@ -9,13 +9,13 @@ namespace TS3AudioBot.CommandSystem.CommandResults { - public class TailStringCommandResult : StringCommandResult + public class TailString : PrimitiveResult { - public string TailString { get; } + public string Tail { get; } - public TailStringCommandResult(string contentArg, string tailArg) : base(contentArg) + public TailString(string contentArg, string tailArg) : base(contentArg) { - TailString = tailArg; + Tail = tailArg; } } } diff --git a/TS3AudioBot/CommandSystem/CommandSystemExtensions.cs b/TS3AudioBot/CommandSystem/CommandSystemExtensions.cs index 99c527f4..a768350f 100644 --- a/TS3AudioBot/CommandSystem/CommandSystemExtensions.cs +++ b/TS3AudioBot/CommandSystem/CommandSystemExtensions.cs @@ -7,11 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TS3AudioBot.Algorithm; +using TS3AudioBot.Dependency; + namespace TS3AudioBot.CommandSystem { - using Algorithm; - using Dependency; - public static class CommandSystemExtensions { public static IFilter GetFilter(this IInjector injector) @@ -20,5 +21,8 @@ public static IFilter GetFilter(this IInjector injector) return filter; return Filter.DefaultFilter; } + + public static Lazy GetFilterLazy(this IInjector injector) + => new Lazy(() => injector.GetFilter(), false); } } diff --git a/TS3AudioBot/CommandSystem/CommandSystemTypes.cs b/TS3AudioBot/CommandSystem/CommandSystemTypes.cs new file mode 100644 index 00000000..c89c4a41 --- /dev/null +++ b/TS3AudioBot/CommandSystem/CommandSystemTypes.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.CommandSystem.Commands; +using TS3AudioBot.Web.Api; + +namespace TS3AudioBot.CommandSystem +{ + public static class CommandSystemTypes + { + public static readonly Type[] ReturnJson = { typeof(JsonObject) }; + public static readonly Type[] ReturnJsonOrDataOrNothing = { typeof(JsonObject), typeof(DataStream), null }; + public static readonly Type[] ReturnString = { typeof(string) }; + public static readonly Type[] ReturnStringOrNothing = { typeof(string), null }; + public static readonly Type[] ReturnCommandOrString = { typeof(ICommand), typeof(string) }; + public static readonly Type[] ReturnAnyPreferNothing = { null, typeof(string), typeof(JsonObject), typeof(ICommand) }; + + /// + /// The order of types, the first item has the highest priority, + /// items not in the list have higher priority as they are special types. + /// + public static readonly Type[] TypeOrder = { + typeof(bool), + typeof(sbyte), typeof(byte), + typeof(short), typeof(ushort), + typeof(int), typeof(uint), + typeof(long), typeof(ulong), + typeof(float), typeof(double), + typeof(TimeSpan), typeof(DateTime), + typeof(string) }; + public static readonly HashSet BasicTypes = new HashSet(TypeOrder); + + public static readonly HashSet AdvancedTypes = new HashSet(new Type[] { + typeof(IAudioResourceResult), + typeof(System.Collections.IEnumerable), + typeof(ResourceFactories.AudioResource), + typeof(History.AudioLogEntry), + typeof(Playlists.PlaylistItem), + }); + } +} diff --git a/TS3AudioBot/CommandSystem/Commands/AliasCommand.cs b/TS3AudioBot/CommandSystem/Commands/AliasCommand.cs index 0dbc36d1..17cb4732 100644 --- a/TS3AudioBot/CommandSystem/Commands/AliasCommand.cs +++ b/TS3AudioBot/CommandSystem/Commands/AliasCommand.cs @@ -7,27 +7,26 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using TS3AudioBot.Dependency; + namespace TS3AudioBot.CommandSystem.Commands { - using System; - using System.Collections.Generic; - using System.Linq; - using TS3AudioBot.CommandSystem.CommandResults; - using TS3AudioBot.Dependency; - public class AliasCommand : ICommand { private readonly ICommand aliasCommand; public string AliasString { get; } - public AliasCommand(XCommandSystem root, string command) + public AliasCommand(string command) { var ast = CommandParser.ParseCommandRequest(command); - aliasCommand = root.AstToCommandResult(ast); + aliasCommand = CommandManager.AstToCommandResult(ast); AliasString = command; } - public ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { info.UseComplexityTokens(1); diff --git a/TS3AudioBot/CommandSystem/Commands/AppliedCommand.cs b/TS3AudioBot/CommandSystem/Commands/AppliedCommand.cs index be2128f0..d4db70f7 100644 --- a/TS3AudioBot/CommandSystem/Commands/AppliedCommand.cs +++ b/TS3AudioBot/CommandSystem/Commands/AppliedCommand.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; + namespace TS3AudioBot.CommandSystem.Commands { - using CommandResults; - using System.Collections.Generic; - public class AppliedCommand : ICommand { private readonly ICommand internCommand; @@ -23,7 +23,7 @@ public AppliedCommand(ICommand command, IReadOnlyList arguments) internArguments = arguments; } - public virtual ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public virtual object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { var merged = new ICommand[internArguments.Count + arguments.Count]; internArguments.CopyTo(0, merged, 0); diff --git a/TS3AudioBot/CommandSystem/Commands/AutoConvertResultCommand.cs b/TS3AudioBot/CommandSystem/Commands/AutoConvertResultCommand.cs new file mode 100644 index 00000000..450eee22 --- /dev/null +++ b/TS3AudioBot/CommandSystem/Commands/AutoConvertResultCommand.cs @@ -0,0 +1,53 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.Localization; + +namespace TS3AudioBot.CommandSystem.Commands +{ + /// + /// A command that stores a result and returns it. + /// + public class AutoConvertResultCommand : ICommand + { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + + public IConvertible Content { get; } + + public AutoConvertResultCommand(IConvertible contentArg) + { + Content = contentArg; + } + + public virtual object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + { + var filterLazy = info.GetFilterLazy(); + foreach (var type in returnTypes) + { + try + { + var result = FunctionCommand.ConvertParam(Content, type, filterLazy); + Log.Debug("Converting command result {0} to {1} returns {2}", Content, type, result); + + return ResultHelper.ToResult(type, result); + } + catch (Exception ex) + { + Log.Debug(ex, "Converting command result {0} to {1} failed", Content, type); + } + } + throw new CommandException(strings.error_cmd_no_matching_overload, CommandExceptionReason.NoReturnMatch); + } + + public override string ToString() => ""; + } +} diff --git a/TS3AudioBot/CommandSystem/Commands/CommandGroup.cs b/TS3AudioBot/CommandSystem/Commands/CommandGroup.cs index ccf946eb..3dc1d06f 100644 --- a/TS3AudioBot/CommandSystem/Commands/CommandGroup.cs +++ b/TS3AudioBot/CommandSystem/Commands/CommandGroup.cs @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.Localization; + namespace TS3AudioBot.CommandSystem.Commands { - using CommandResults; - using Localization; - using System; - using System.Collections.Generic; - using System.Linq; - public class CommandGroup : ICommand { private readonly IDictionary commands = new Dictionary(); @@ -33,19 +33,19 @@ public bool RemoveCommand(ICommand command) public bool IsEmpty => commands.Count == 0; public IEnumerable> Commands => commands; - public virtual ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public virtual object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { string result; if (arguments.Count == 0) { - if (returnTypes.Contains(CommandResultType.Command)) - return new CommandCommandResult(this); + if (returnTypes.Contains(typeof(ICommand))) + return this; result = string.Empty; } else { - var comResult = arguments[0].Execute(info, Array.Empty(), XCommandSystem.ReturnString); - result = ((StringCommandResult)comResult).Content; + var comResult = arguments[0].Execute(info, Array.Empty(), CommandSystemTypes.ReturnString); + result = ((IPrimitiveResult)comResult).Get(); } var filter = info.GetFilter(); @@ -71,15 +71,7 @@ public virtual ICommandResult Execute(ExecutionInformation info, IReadOnlyList commands) - { - var commandsArray = commands.Where(x => !string.IsNullOrEmpty(x)).ToArray(); - var suggestions = string.Join(", ", commandsArray.Take(4)); - if (commandsArray.Length > 4) - { - suggestions += ", ..."; - } - return suggestions; - } + => string.Join(", ", commands.Where(x => !string.IsNullOrEmpty(x))); public override string ToString() => ""; } diff --git a/TS3AudioBot/CommandSystem/Commands/FunctionCommand.cs b/TS3AudioBot/CommandSystem/Commands/FunctionCommand.cs index d1aafd45..7a1578f5 100644 --- a/TS3AudioBot/CommandSystem/Commands/FunctionCommand.cs +++ b/TS3AudioBot/CommandSystem/Commands/FunctionCommand.cs @@ -7,21 +7,25 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.Dependency; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.Web.Api; +using TSLib.Helper; +using static TS3AudioBot.CommandSystem.CommandSystemTypes; + namespace TS3AudioBot.CommandSystem.Commands { - using CommandResults; - using Dependency; - using Helper; - using Localization; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Reflection; - using Web.Api; - public class FunctionCommand : ICommand { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + // Needed for non-static member methods private readonly object callee; /// The method that will be called internally by this command. @@ -48,8 +52,8 @@ public FunctionCommand(MethodInfo command, object obj = null, int? requiredParam callee = obj; - NormalParameters = CommandParameter.Count(p => p.kind.IsNormal()); - RequiredParameters = requiredParameters ?? CommandParameter.Count(p => !p.optional && p.kind.IsNormal()); + NormalParameters = CommandParameter.Count(p => p.Kind.IsNormal()); + RequiredParameters = requiredParameters ?? CommandParameter.Count(p => !p.Optional && p.Kind.IsNormal()); } // Provide some constructors that take lambda expressions directly @@ -81,18 +85,18 @@ protected virtual object ExecuteFunction(object[] parameters) /// The arguments that are applied to this function. /// The possible return types. /// How many arguments could be set. - private object[] FitArguments(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes, out int takenArguments) + private object[] FitArguments(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes, out int takenArguments) { var parameters = new object[CommandParameter.Length]; - var filterLazy = new Lazy(() => info.GetFilter(), false); + var filterLazy = info.GetFilterLazy(); // takenArguments: Index through arguments which have been moved into a parameter // p: Iterate through parameters takenArguments = 0; for (int p = 0; p < parameters.Length; p++) { - var arg = CommandParameter[p].type; - switch (CommandParameter[p].kind) + var arg = CommandParameter[p].Type; + switch (CommandParameter[p].Kind) { case ParamKind.SpecialArguments: parameters[p] = arguments; @@ -105,7 +109,7 @@ private object[] FitArguments(ExecutionInformation info, IReadOnlyList case ParamKind.Dependency: if (info.TryGet(arg, out var obj)) parameters[p] = obj; - else if (CommandParameter[p].optional) + else if (CommandParameter[p].Optional) parameters[p] = null; else throw new MissingContextCommandException($"Command '{internCommand.Name}' missing execution context '{arg.Name}'", arg); @@ -121,11 +125,15 @@ private object[] FitArguments(ExecutionInformation info, IReadOnlyList case ParamKind.NormalTailString: if (takenArguments >= arguments.Count) { parameters[p] = GetDefault(arg); break; } - var argResultP = arguments[takenArguments].Execute(info, Array.Empty(), XCommandSystem.ReturnString); - if (CommandParameter[p].kind == ParamKind.NormalTailString && argResultP is TailStringCommandResult tailString) - parameters[p] = tailString.TailString; + var types = GetTypes(arg); + if (CommandParameter[p].Kind == ParamKind.NormalTailString) + types.Insert(0, typeof(TailString)); + + var argResultP = arguments[takenArguments].Execute(info, Array.Empty(), types); + if (CommandParameter[p].Kind == ParamKind.NormalTailString && argResultP is TailString tailString) + parameters[p] = tailString.Tail; else - parameters[p] = ConvertParam(((StringCommandResult)argResultP).Content, arg, filterLazy.Value); + parameters[p] = ConvertParam(UnwrapPrimitive(argResultP), arg, filterLazy); takenArguments++; break; @@ -137,8 +145,8 @@ private object[] FitArguments(ExecutionInformation info, IReadOnlyList var args = Array.CreateInstance(typeArr, arguments.Count - takenArguments); for (int i = 0; i < args.Length; i++, takenArguments++) { - var argResultA = ((StringCommandResult)arguments[takenArguments].Execute(info, Array.Empty(), XCommandSystem.ReturnString)).Content; - var convResult = ConvertParam(argResultA, typeArr, filterLazy.Value); + var argResultA = arguments[takenArguments].Execute(info, Array.Empty(), GetTypes(typeArr)); + var convResult = ConvertParam(UnwrapPrimitive(argResultA), typeArr, filterLazy); args.SetValue(convResult, i); } @@ -146,19 +154,19 @@ private object[] FitArguments(ExecutionInformation info, IReadOnlyList break; default: - throw Util.UnhandledDefault(CommandParameter[p].kind); + throw Tools.UnhandledDefault(CommandParameter[p].Kind); } } // Check if we were able to set enough arguments int wantArgumentCount = Math.Min(parameters.Length, RequiredParameters); - if (takenArguments < wantArgumentCount && !returnTypes.Contains(CommandResultType.Command)) + if (takenArguments < wantArgumentCount && !returnTypes.Contains(typeof(ICommand))) throw ThrowAtLeastNArguments(wantArgumentCount); return parameters; } - public virtual ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public virtual object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { // Make arguments lazy, we only want to execute them once arguments = arguments.Select(c => new LazyCommand(c)).ToArray(); @@ -168,66 +176,85 @@ public virtual ICommandResult Execute(ExecutionInformation info, IReadOnlyList 0 - ? new CommandCommandResult(new AppliedCommand(this, arguments)) - : new CommandCommandResult(this); + ? (object)new AppliedCommand(this, arguments) + : this; } throw ThrowAtLeastNArguments(wantArgumentCount); } - if (CommandReturn == typeof(ICommandResult)) - return (ICommandResult)ExecuteFunction(parameters); + if (returnTypes[0] == null) + { + // Evaluate + ExecuteFunction(parameters); + return null; + } + + if (returnTypes[0] == typeof(ICommand)) + { + // Return a command if we can take more arguments + if (CommandParameter.Any(p => p.Type == typeof(string[])) || availableArguments < NormalParameters) + return new AppliedCommand(this, arguments); + } + + Log.Debug("Iterating over return types [{@returnTypes}]", returnTypes); + + var result = ExecuteFunction(parameters); + if (ResultHelper.IsValidResult(result, returnTypes)) + { + Log.Debug("{0} can be directly returned", result); + return result; + } + + if (result == null) + throw new CommandException("Couldn't find a proper command result for function " + internCommand.Name, CommandExceptionReason.NoReturnMatch); + var unwrapedResult = UnwrapReturn(result); + var resultType = result.GetType(); + var unwrapedResultType = unwrapedResult.GetType(); - bool executed = false; - object result = null; // Take first fitting command result foreach (var returnType in returnTypes) { - switch (returnType) + if (returnType == null) + return null; + + if (returnType.IsAssignableFrom(resultType)) + return ResultHelper.ToResult(returnType, result); + else if (returnType.IsAssignableFrom(unwrapedResultType)) + return ResultHelper.ToResult(returnType, unwrapedResult); + else if (returnType == typeof(string)) { - case CommandResultType.Command: - // Return a command if we can take more arguments - if (CommandParameter.Any(p => p.type == typeof(string[])) || availableArguments < NormalParameters) - return new CommandCommandResult(new AppliedCommand(this, arguments)); - break; - case CommandResultType.Empty: - if (!executed) - ExecuteFunction(parameters); - return EmptyCommandResult.Instance; - case CommandResultType.String: - if (!executed) - { - result = ExecuteFunction(parameters); - executed = true; - } - var resultStr = result?.ToString(); + Log.Debug("Convert {0} to a string", result); + var resultStr = result.ToString(); if (!string.IsNullOrEmpty(resultStr)) - return new StringCommandResult(resultStr); - break; - case CommandResultType.Json: - if (!executed) - { - result = ExecuteFunction(parameters); - executed = true; - } - - switch (result) + return new PrimitiveResult(resultStr); + } + else if (BasicTypes.Contains(returnType)) + { + if (BasicTypes.Contains(unwrapedResultType) && unwrapedResultType != typeof(string)) { - case null: break; - case JsonObject jsonResult: return new JsonCommandResult(jsonResult); - default: return new JsonCommandResult((JsonObject)Activator.CreateInstance(typeof(JsonValue<>).MakeGenericType(result.GetType()), result)); + // Automatically try to convert between primitive types + try + { + return ResultHelper.ToResult(resultType, + Convert.ChangeType(unwrapedResult, returnType, CultureInfo.InvariantCulture)); + } + catch + { + } } - break; - - default: - throw new ArgumentOutOfRangeException(); } + else if (returnType == typeof(JsonObject)) + { + if (result is JsonObject jsonResult) + return jsonResult; + else + return Activator.CreateInstance(typeof(JsonValue<>).MakeGenericType(result.GetType()), result); + } + // Ignore unknown types } - // Try to return an empty string - if (returnTypes.Contains(CommandResultType.String) && executed) - return new StringCommandResult(""); throw new CommandException("Couldn't find a proper command result for function " + internCommand.Name, CommandExceptionReason.NoReturnMatch); } @@ -236,26 +263,29 @@ private void PrecomputeTypes() for (int i = 0; i < CommandParameter.Length; i++) { ref var paramInfo = ref CommandParameter[i]; - var arg = paramInfo.type; + var arg = paramInfo.Type; if (arg == typeof(IReadOnlyList)) - paramInfo.kind = ParamKind.SpecialArguments; - else if (arg == typeof(IReadOnlyList)) - paramInfo.kind = ParamKind.SpecialReturns; + paramInfo.Kind = ParamKind.SpecialArguments; + else if (arg == typeof(IReadOnlyList)) + paramInfo.Kind = ParamKind.SpecialReturns; else if (arg == typeof(ICommand)) - paramInfo.kind = ParamKind.NormalCommand; + paramInfo.Kind = ParamKind.NormalCommand; else if (arg.IsArray) - paramInfo.kind = ParamKind.NormalArray; + paramInfo.Kind = ParamKind.NormalArray; else if (arg.IsEnum - || XCommandSystem.BasicTypes.Contains(arg) - || XCommandSystem.BasicTypes.Contains(UnwrapParamType(arg))) - paramInfo.kind = ParamKind.NormalParam; + || BasicTypes.Contains(arg) + || BasicTypes.Contains(UnwrapParamType(arg))) + paramInfo.Kind = ParamKind.NormalParam; + // TODO How to distinguish between special type and dependency? + else if (AdvancedTypes.Contains(arg)) + paramInfo.Kind = ParamKind.NormalParam; else - paramInfo.kind = ParamKind.Dependency; + paramInfo.Kind = ParamKind.Dependency; } - var tailStringIndex = Array.FindLastIndex(CommandParameter, c => c.kind == ParamKind.NormalParam); - if (tailStringIndex >= 0 && CommandParameter[tailStringIndex].type == typeof(string)) - CommandParameter[tailStringIndex].kind = ParamKind.NormalTailString; + var tailStringIndex = Array.FindLastIndex(CommandParameter, c => c.Kind.IsNormal()); + if (tailStringIndex >= 0 && CommandParameter[tailStringIndex].Type == typeof(string)) + CommandParameter[tailStringIndex].Kind = ParamKind.NormalTailString; } public static Type UnwrapParamType(Type type) @@ -280,6 +310,25 @@ public static Type UnwrapReturnType(Type type) return type; } + private static object UnwrapReturn(object value) + { + if (value == null) + return value; + + var type = value.GetType(); + if (type.IsConstructedGenericType) + { + var genDef = type.GetGenericTypeDefinition(); + if (genDef == typeof(Nullable<>)) + return type.GetProperty("Value").GetValue(value); + if (genDef == typeof(JsonValue<>)) + return type.GetProperty("Value").GetValue(value); + if (genDef == typeof(JsonArray<>)) + return type.GetProperty("Value").GetValue(value); + } + return value; + } + public static CommandException ThrowAtLeastNArguments(int count) { if (count <= 0) @@ -297,23 +346,53 @@ public static CommandException ThrowAtLeastNArguments(int count) return new CommandException(throwString, CommandExceptionReason.MissingParameter); } - private static object ConvertParam(string value, Type targetType, Algorithm.IFilter filter) + public static object ConvertParam(object value, Type targetType, Lazy filter) { - if (targetType == typeof(string)) + var valueType = value.GetType(); + if (targetType.IsAssignableFrom(valueType)) return value; if (targetType.IsEnum) { + var strValue = value.ToString(); var enumVals = Enum.GetValues(targetType).Cast(); - var result = filter.Filter(enumVals.Select(x => new KeyValuePair(x.ToString(), x)), value).Select(x => x.Value).FirstOrDefault(); + var result = filter.Value.Filter(enumVals.Select(x => new KeyValuePair(x.ToString(), x)), strValue).Select(x => x.Value).FirstOrDefault(); if (result is null) - throw new CommandException(string.Format(strings.error_cmd_could_not_convert_to, value, targetType.Name), CommandExceptionReason.MissingParameter); + throw new CommandException(string.Format(strings.error_cmd_could_not_convert_to, strValue, targetType.Name), CommandExceptionReason.MissingParameter); return result; } var unwrappedTargetType = UnwrapParamType(targetType); + if (valueType == typeof(string) && unwrappedTargetType == typeof(TimeSpan)) + { + return TextUtil.ParseTime((string)value); + } + // Autoconvert try { return Convert.ChangeType(value, unwrappedTargetType, CultureInfo.InvariantCulture); } catch (FormatException ex) { throw new CommandException(string.Format(strings.error_cmd_could_not_convert_to, value, unwrappedTargetType.Name), ex, CommandExceptionReason.MissingParameter); } catch (OverflowException ex) { throw new CommandException(strings.error_cmd_number_too_big, ex, CommandExceptionReason.MissingParameter); } + catch (InvalidCastException ex) { throw new CommandException(string.Format(strings.error_cmd_could_not_convert_to, value, unwrappedTargetType.Name), ex, CommandExceptionReason.MissingParameter); } + } + + private static List GetTypes(Type targetType) + { + var types = new List(); + types.Add(targetType); + var unwrappedTargetType = UnwrapParamType(targetType); + if (unwrappedTargetType != targetType) + types.Add(unwrappedTargetType); + + // Allow fallbacks to string + if (!types.Contains(typeof(string))) + types.Add(typeof(string)); + return types; + } + + private static object UnwrapPrimitive(object o) + { + if (o is IPrimitiveResult prim) + return prim.Get(); + else + return o; } private static object GetDefault(Type type) @@ -345,16 +424,17 @@ public enum ParamKind public struct ParamInfo { - public ParameterInfo param; - public Type type => param.ParameterType; - public ParamKind kind; - public bool optional; + public ParameterInfo Param { get; set; } + public Type Type => Param.ParameterType; + public string Name => Param.Name; + public ParamKind Kind; + public bool Optional; public ParamInfo(ParameterInfo param, ParamKind kind, bool optional) { - this.param = param; - this.kind = kind; - this.optional = optional; + Param = param; + Kind = kind; + Optional = optional; } } diff --git a/TS3AudioBot/CommandSystem/Commands/ICommand.cs b/TS3AudioBot/CommandSystem/Commands/ICommand.cs index 9c929b77..56344ad8 100644 --- a/TS3AudioBot/CommandSystem/Commands/ICommand.cs +++ b/TS3AudioBot/CommandSystem/Commands/ICommand.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; + namespace TS3AudioBot.CommandSystem.Commands { - using CommandResults; - using System.Collections.Generic; - public interface ICommand { /// Execute this command. @@ -23,7 +23,17 @@ public interface ICommand /// /// The possible return types that should be returned by this execution. /// They are ordered by priority so, if possible, the first return type should be picked, then the second and so on. + /// + /// These types can contain primitive types, the actual return value will then be wrapped into a . + /// null inside the list allows an empty result. /// - ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes); + /// + /// The result of this command. + /// + /// null is an empty result. + /// Primitive types are a special case, it should always implement , e.g. through the class. + /// The complete list of primitive types is . + /// + object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes); } } diff --git a/TS3AudioBot/CommandSystem/Commands/LazyCommand.cs b/TS3AudioBot/CommandSystem/Commands/LazyCommand.cs index 723c0eed..4f7599a8 100644 --- a/TS3AudioBot/CommandSystem/Commands/LazyCommand.cs +++ b/TS3AudioBot/CommandSystem/Commands/LazyCommand.cs @@ -7,34 +7,35 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using TS3AudioBot.CommandSystem.CommandResults; + namespace TS3AudioBot.CommandSystem.Commands { - using CommandResults; - using System.Collections.Generic; - using System.Linq; - public class LazyCommand : ICommand { private readonly ICommand innerCommand; + private bool executed = false; /// /// The cached result, if available. /// - private ICommandResult result; + private object result; public LazyCommand(ICommand innerCommandArg) { innerCommand = innerCommandArg; } - public virtual ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public virtual object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { - if (result is null) + if (!executed) { result = innerCommand.Execute(info, arguments, returnTypes); + executed = true; return result; } - // Check if we can return that type - if (!returnTypes.Contains(result.ResultType)) + if (!ResultHelper.IsValidResult(result, returnTypes)) throw new CommandException("The cached result can't be returned", CommandExceptionReason.NoReturnMatch); return result; } diff --git a/TS3AudioBot/CommandSystem/Commands/OverloadedFunctionCommand.cs b/TS3AudioBot/CommandSystem/Commands/OverloadedFunctionCommand.cs index 1bdeb2c1..c4cdc19a 100644 --- a/TS3AudioBot/CommandSystem/Commands/OverloadedFunctionCommand.cs +++ b/TS3AudioBot/CommandSystem/Commands/OverloadedFunctionCommand.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using TS3AudioBot.Localization; + namespace TS3AudioBot.CommandSystem.Commands { - using CommandResults; - using Localization; - using System; - using System.Collections.Generic; - using System.Linq; public class OverloadedFunctionCommand : ICommand { @@ -43,24 +43,21 @@ private void SortList() // Sort out special arguments // and remove the nullable wrapper var params1 = (from p in f1.CommandParameter - where p.kind.IsNormal() - select FunctionCommand.UnwrapParamType(p.type)).ToList(); + where p.Kind.IsNormal() + select FunctionCommand.UnwrapParamType(p.Type)).ToList(); var params2 = (from p in f2.CommandParameter - where p.kind.IsNormal() - select FunctionCommand.UnwrapParamType(p.type)).ToList(); + where p.Kind.IsNormal() + select FunctionCommand.UnwrapParamType(p.Type)).ToList(); for (int i = 0; i < params1.Count; i++) { // Prefer functions with higher parameter count if (i >= params2.Count) return -1; - int i1 = Array.IndexOf(XCommandSystem.TypeOrder, params1[i]); - if (i1 == -1) - i1 = XCommandSystem.TypeOrder.Length; - int i2 = Array.IndexOf(XCommandSystem.TypeOrder, params2[i]); - if (i2 == -1) - i2 = XCommandSystem.TypeOrder.Length; + // Not found returns -1, so more important than any found index + int i1 = Array.IndexOf(CommandSystemTypes.TypeOrder, params1[i]); + int i2 = Array.IndexOf(CommandSystemTypes.TypeOrder, params2[i]); // Prefer lower argument if (i1 < i2) return -1; @@ -74,7 +71,7 @@ where p.kind.IsNormal() }); } - public virtual ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public virtual object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { // Make arguments lazy, we only want to execute them once arguments = arguments.Select(c => new LazyCommand(c)).ToArray(); diff --git a/TS3AudioBot/CommandSystem/Commands/ResultCommand.cs b/TS3AudioBot/CommandSystem/Commands/ResultCommand.cs new file mode 100644 index 00000000..f008f604 --- /dev/null +++ b/TS3AudioBot/CommandSystem/Commands/ResultCommand.cs @@ -0,0 +1,43 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.Localization; + +namespace TS3AudioBot.CommandSystem.Commands +{ + /// + /// A command that stores a result and returns it. + /// + public class ResultCommand : ICommand + { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + + public object Content { get; } + + public ResultCommand(object contentArg) + { + Content = contentArg; + } + + public virtual object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + { + if (!ResultHelper.IsValidResult(Content, returnTypes)) + { + Log.Debug("Failed to return {0} ({1})", Content.GetType(), Content); + throw new CommandException(strings.error_cmd_no_matching_overload, CommandExceptionReason.NoReturnMatch); + } + return Content; + } + + public override string ToString() => ""; + } +} diff --git a/TS3AudioBot/CommandSystem/Commands/RootCommand.cs b/TS3AudioBot/CommandSystem/Commands/RootCommand.cs index a2884905..af86e372 100644 --- a/TS3AudioBot/CommandSystem/Commands/RootCommand.cs +++ b/TS3AudioBot/CommandSystem/Commands/RootCommand.cs @@ -7,34 +7,37 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using TS3AudioBot.Dependency; + namespace TS3AudioBot.CommandSystem.Commands { - using CommandResults; - using System; - using System.Collections.Generic; - /// /// A special group command that also accepts commands as first parameter and executes them on the left over parameters. + /// + /// This command is needed to enable easy use of higher order functions. + /// E.g. `!(!if 1 > 2 (!vol) (!print)) 10` /// - public class RootCommand : CommandGroup + public class RootCommand : ICommand { - public override ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + private readonly IReadOnlyList internArguments; + + public RootCommand(IReadOnlyList arguments) { - if (arguments.Count == 0) - return base.Execute(info, arguments, returnTypes); + internArguments = arguments; + } - var result = arguments[0].Execute(info, Array.Empty(), XCommandSystem.ReturnCommandOrString); - if (result.ResultType == CommandResultType.String) - { - // Use cached result so we don't execute the first argument twice - var passArgs = new ICommand[arguments.Count]; - passArgs[0] = new StringCommand(((StringCommandResult)result).Content); - arguments.CopyTo(1, passArgs, 1); - return base.Execute(info, passArgs, returnTypes); - } - return ((CommandCommandResult)result).Command.Execute(info, arguments.TrySegment(1), returnTypes); + public virtual object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + { + var merged = new ICommand[internArguments.Count + arguments.Count]; + internArguments.CopyTo(0, merged, 0); + arguments.CopyTo(0, merged, internArguments.Count); + if (!info.TryGet(out var cmdSys)) + throw new CommandException("Could not find local commandsystem tree", CommandExceptionReason.MissingContext); + return cmdSys.RootGroup.Execute(info, merged, returnTypes); } - public override string ToString() => ""; + public override string ToString() => $"RootCmd({string.Join(", ", internArguments)})"; } } diff --git a/TS3AudioBot/CommandSystem/Commands/RootGroup.cs b/TS3AudioBot/CommandSystem/Commands/RootGroup.cs new file mode 100644 index 00000000..3c8b76d7 --- /dev/null +++ b/TS3AudioBot/CommandSystem/Commands/RootGroup.cs @@ -0,0 +1,43 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using TS3AudioBot.CommandSystem.CommandResults; + +namespace TS3AudioBot.CommandSystem.Commands +{ + /// + /// A special group command that also accepts commands as first parameter and executes them on the left over parameters. + /// + /// This command is needed to enable easy use of higher order functions. + /// E.g. `!(!if 1 > 2 (!vol) (!print)) 10` + /// + public class RootGroup : CommandGroup + { + public override object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + { + if (arguments.Count == 0) + return base.Execute(info, arguments, returnTypes); + + var result = arguments[0].Execute(info, Array.Empty(), CommandSystemTypes.ReturnCommandOrString); + if (result is IPrimitiveResult) + { + // Use cached result so we don't execute the first argument twice + var passArgs = new ICommand[arguments.Count]; + passArgs[0] = new ResultCommand(result); + arguments.CopyTo(1, passArgs, 1); + return base.Execute(info, passArgs, returnTypes); + } + return ((ICommand)result).Execute(info, arguments.TrySegment(1), returnTypes); + } + + public override string ToString() => ""; + } +} diff --git a/TS3AudioBot/CommandSystem/Commands/StringCommand.cs b/TS3AudioBot/CommandSystem/Commands/StringCommand.cs deleted file mode 100644 index bc79642a..00000000 --- a/TS3AudioBot/CommandSystem/Commands/StringCommand.cs +++ /dev/null @@ -1,35 +0,0 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3AudioBot.CommandSystem.Commands -{ - using CommandResults; - using System.Collections.Generic; - - public class StringCommand : ICommand - { - private readonly string content; - private readonly string tailContent; - - public StringCommand(string content, string tailContent = null) - { - this.content = content; - this.tailContent = tailContent; - } - - public virtual ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) - { - if (tailContent != null) - return new TailStringCommandResult(content, tailContent); - return new StringCommandResult(content); - } - - public override string ToString() => $"S\"{content}\""; - } -} diff --git a/TS3AudioBot/CommandSystem/Commands/TailStringAutoConvertCommand.cs b/TS3AudioBot/CommandSystem/Commands/TailStringAutoConvertCommand.cs new file mode 100644 index 00000000..3f71a944 --- /dev/null +++ b/TS3AudioBot/CommandSystem/Commands/TailStringAutoConvertCommand.cs @@ -0,0 +1,56 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using System.Globalization; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.Localization; + +namespace TS3AudioBot.CommandSystem.Commands +{ + /// + /// A command that stores a result and returns it. + /// + public class TailStringAutoConvertCommand : ICommand + { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + + public TailString Content { get; } + + public TailStringAutoConvertCommand(TailString contentArg) + { + Content = contentArg; + } + + public virtual object Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + { + foreach (var type in returnTypes) + { + if (type == typeof(TailString)) + return Content; + + try + { + var result = Convert.ChangeType(Content.Content, type, CultureInfo.InvariantCulture); + Log.Debug("Converting command result {0} to {1} returns {2}", Content, type, result); + + return ResultHelper.ToResult(type, result); + } + catch + { + Log.Debug("Converting command result {0} to {1} failed", Content, type); + } + } + throw new CommandException(strings.error_cmd_no_matching_overload, CommandExceptionReason.NoReturnMatch); + } + + public override string ToString() => ""; + } +} diff --git a/TS3AudioBot/CommandSystem/ExecutionInformation.cs b/TS3AudioBot/CommandSystem/ExecutionInformation.cs index 94aa6524..31ab6b1d 100644 --- a/TS3AudioBot/CommandSystem/ExecutionInformation.cs +++ b/TS3AudioBot/CommandSystem/ExecutionInformation.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using TS3AudioBot.Dependency; + namespace TS3AudioBot.CommandSystem { - using Dependency; - public class ExecutionInformation : ChainedInjector { public ExecutionInformation() : this(NullInjector.Instance) { } diff --git a/TS3AudioBot/CommandSystem/ICommandBag.cs b/TS3AudioBot/CommandSystem/ICommandBag.cs index 5fb85479..73dcbab2 100644 --- a/TS3AudioBot/CommandSystem/ICommandBag.cs +++ b/TS3AudioBot/CommandSystem/ICommandBag.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; + namespace TS3AudioBot.CommandSystem { - using System.Collections.Generic; - public interface ICommandBag { IReadOnlyCollection BagCommands { get; } diff --git a/TS3AudioBot/CommandSystem/StaticList.cs b/TS3AudioBot/CommandSystem/StaticList.cs index 19932d1b..9a896e30 100644 --- a/TS3AudioBot/CommandSystem/StaticList.cs +++ b/TS3AudioBot/CommandSystem/StaticList.cs @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; + namespace TS3AudioBot.CommandSystem { - using System; - using System.Collections.Generic; - using System.Linq; - internal static class StaticList { public static IReadOnlyList TrySegment(this IReadOnlyList list, int start) diff --git a/TS3AudioBot/CommandSystem/Text/Color.cs b/TS3AudioBot/CommandSystem/Text/Color.cs index 5380619e..ee3a414d 100644 --- a/TS3AudioBot/CommandSystem/Text/Color.cs +++ b/TS3AudioBot/CommandSystem/Text/Color.cs @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Text; + namespace TS3AudioBot.CommandSystem.Text { - using System.Collections.Generic; - using System.Text; - using System; - public readonly struct Color { public byte R { get; } diff --git a/TS3AudioBot/CommandSystem/Text/LongTextTransform.cs b/TS3AudioBot/CommandSystem/Text/LongTextTransform.cs index 222a3a5e..822ac056 100644 --- a/TS3AudioBot/CommandSystem/Text/LongTextTransform.cs +++ b/TS3AudioBot/CommandSystem/Text/LongTextTransform.cs @@ -7,33 +7,33 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TSLib.Commands; +using TSLib.Helper; + namespace TS3AudioBot.CommandSystem.Text { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using TS3Client.Commands; - using TS3Client.Helper; - public static class LongTextTransform { private static readonly byte[] SeparatorWeight = new byte[] { (byte)'\n', (byte)',', (byte)' ' }; - public static IEnumerable Transform(string text, LongTextBehaviour behaviour, int limit = int.MaxValue, int maxMessageSize = Ts3Const.MaxSizeTextMessage) + public static IEnumerable Transform(string text, LongTextBehaviour behaviour, int limit = int.MaxValue, int maxMessageSize = TsConst.MaxSizeTextMessage) { if (maxMessageSize < 4) throw new ArgumentOutOfRangeException(nameof(maxMessageSize), "The minimum split length must be at least 4 bytes to fit all utf8 characters"); // Assuming worst case that each UTF-8 character which epands to 4 bytes. // If the message is still shorter we can safely return in 1 block. - if (text.Length * 4 <= Ts3Const.MaxSizeTextMessage) + if (text.Length * 4 <= TsConst.MaxSizeTextMessage) return new[] { text }; var bytes = Encoding.UTF8.GetBytes(text); // If the entire text UTF-8 encoded fits in one message we can return early. - if (bytes.Length * 2 < Ts3Const.MaxSizeTextMessage) + if (bytes.Length * 2 < TsConst.MaxSizeTextMessage) return new[] { text }; var list = new List(); @@ -48,7 +48,7 @@ public static IEnumerable Transform(string text, LongTextBehaviour behav for (; i < block.Length; i++) { - tokenCnt += Ts3String.IsDoubleChar(block[i]) ? 2 : 1; + tokenCnt += TsString.IsDoubleChar(block[i]) ? 2 : 1; if (tokenCnt > maxMessageSize) { diff --git a/TS3AudioBot/CommandSystem/Text/TextMod.cs b/TS3AudioBot/CommandSystem/Text/TextMod.cs index b1585d52..caca0b0a 100644 --- a/TS3AudioBot/CommandSystem/Text/TextMod.cs +++ b/TS3AudioBot/CommandSystem/Text/TextMod.cs @@ -7,9 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.CommandSystem.Text { - public readonly struct TextMod + public readonly struct TextMod : IEquatable { public static readonly TextMod None = new TextMod(0, null); @@ -35,5 +37,11 @@ public static string Format(bool color, AppliedTextMod format, params AppliedTex { return color ? Format(format, para) : string.Format(format.Text, para); } + + public bool Equals(TextMod other) => Flags == other.Flags && HasColor == other.HasColor; + public override bool Equals(object obj) => obj is TextMod tm && Equals(tm); + public static bool operator ==(TextMod a, TextMod b) => a.Flags == b.Flags && a.HasColor == b.HasColor; + public static bool operator !=(TextMod a, TextMod b) => a.Flags != b.Flags || a.HasColor != b.HasColor; + public override int GetHashCode() => ((int)Flags << 28) | HasColor.GetHashCode(); } } diff --git a/TS3AudioBot/CommandSystem/Text/TextModBuilder.cs b/TS3AudioBot/CommandSystem/Text/TextModBuilder.cs index 92e088cf..e4e7c569 100644 --- a/TS3AudioBot/CommandSystem/Text/TextModBuilder.cs +++ b/TS3AudioBot/CommandSystem/Text/TextModBuilder.cs @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Text; +using System.Text.RegularExpressions; + namespace TS3AudioBot.CommandSystem.Text { - using System; - using System.Text; - using System.Text.RegularExpressions; - public class TextModBuilder { private readonly bool color; diff --git a/TS3AudioBot/CommandSystem/Text/TextModFlag.cs b/TS3AudioBot/CommandSystem/Text/TextModFlag.cs index 5d27fee3..f2f30e7a 100644 --- a/TS3AudioBot/CommandSystem/Text/TextModFlag.cs +++ b/TS3AudioBot/CommandSystem/Text/TextModFlag.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.CommandSystem.Text { - using System; - [Flags] public enum TextModFlag { diff --git a/TS3AudioBot/CommandSystem/XCommandSystem.cs b/TS3AudioBot/CommandSystem/XCommandSystem.cs deleted file mode 100644 index 7f79491c..00000000 --- a/TS3AudioBot/CommandSystem/XCommandSystem.cs +++ /dev/null @@ -1,170 +0,0 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3AudioBot.CommandSystem -{ - using Ast; - using CommandResults; - using Commands; - using Helper; - using System; - using System.Collections.Generic; - using Text; - - public class XCommandSystem - { - public static readonly CommandResultType[] ReturnJson = { CommandResultType.Json }; - public static readonly CommandResultType[] ReturnJsonOrNothing = { CommandResultType.Json, CommandResultType.Empty }; - public static readonly CommandResultType[] ReturnString = { CommandResultType.String }; - public static readonly CommandResultType[] ReturnStringOrNothing = { CommandResultType.String, CommandResultType.Empty }; - public static readonly CommandResultType[] ReturnCommandOrString = { CommandResultType.Command, CommandResultType.String }; - public static readonly CommandResultType[] ReturnAnyPreferNothing = { CommandResultType.Empty, CommandResultType.String, CommandResultType.Json, CommandResultType.Command }; - - /// - /// The order of types, the first item has the highest priority, items not in the list have lower priority. - /// - public static readonly Type[] TypeOrder = { - typeof(bool), - typeof(sbyte), typeof(byte), - typeof(short), typeof(ushort), - typeof(int), typeof(uint), - typeof(long), typeof(ulong), - typeof(float), typeof(double), - typeof(TimeSpan), typeof(DateTime), - typeof(string) }; - public static readonly HashSet BasicTypes = new HashSet(TypeOrder); - - public RootCommand RootCommand { get; } - - public XCommandSystem() - { - RootCommand = new RootCommand(); - } - - internal ICommand AstToCommandResult(AstNode node) - { - switch (node.Type) - { - case AstType.Error: - throw new CommandException("Found an unconvertable ASTNode of type Error", CommandExceptionReason.InternalError); - case AstType.Command: - var cmd = (AstCommand)node; - var arguments = new ICommand[cmd.Parameter.Count]; - int tailCandidates = 0; - for (int i = cmd.Parameter.Count - 1; i >= 1; i--) - { - var para = cmd.Parameter[i]; - if (!(para is AstValue astVal) || astVal.StringType != StringType.FreeString) - break; - - arguments[i] = new StringCommand(astVal.Value, astVal.TailString); - tailCandidates++; - } - for (int i = 0; i < cmd.Parameter.Count - tailCandidates; i++) - arguments[i] = AstToCommandResult(cmd.Parameter[i]); - return new AppliedCommand(RootCommand, arguments); - case AstType.Value: - return new StringCommand(((AstValue)node).Value); - default: - throw Util.UnhandledDefault(node.Type); - } - } - - public ICommandResult Execute(ExecutionInformation info, string command) - { - return Execute(info, command, ReturnStringOrNothing); - } - - public ICommandResult Execute(ExecutionInformation info, string command, IReadOnlyList returnTypes) - { - var ast = CommandParser.ParseCommandRequest(command); - var cmd = AstToCommandResult(ast); - return cmd.Execute(info, Array.Empty(), returnTypes); - } - - public ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments) - { - return Execute(info, arguments, ReturnStringOrNothing); - } - - public ICommandResult Execute(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) - { - return RootCommand.Execute(info, arguments, returnTypes); - } - - public string ExecuteCommand(ExecutionInformation info, string command) - { - var result = Execute(info, command); - if (result.ResultType == CommandResultType.String) - return result.ToString(); - if (result.ResultType == CommandResultType.Empty) - return null; - throw new CommandException("Expected a string or nothing as result", CommandExceptionReason.NoReturnMatch); - } - - public static ICommandResult GetEmpty(IReadOnlyList resultTypes) - { - foreach (var item in resultTypes) - { - switch (item) - { - case CommandResultType.Empty: - return EmptyCommandResult.Instance; - case CommandResultType.Command: - break; - case CommandResultType.String: - return StringCommandResult.Empty; - case CommandResultType.Json: - break; - default: - throw Util.UnhandledDefault(item); - } - } - throw new CommandException("No empty return type available", CommandExceptionReason.NoReturnMatch); - } - - public static string GetTree(ICommand com) - { - var strb = new TextModBuilder(); - GetTree(com, strb, 0); - return strb.ToString(); - } - - private static void GetTree(ICommand com, TextModBuilder strb, int indent) - { - switch (com) - { - case CommandGroup group: - strb.AppendFormat("\n".Mod().Color(Color.Red)); - foreach (var subCom in group.Commands) - { - strb.Append(new string(' ', (indent + 1) * 2)).Append(subCom.Key); - GetTree(subCom.Value, strb, indent + 1); - } - break; - - case FunctionCommand _: - strb.AppendFormat("\n".Mod().Color(Color.Green)); - break; - - case OverloadedFunctionCommand ofunc: - strb.AppendFormat($"\n".Mod().Color(Color.Blue)); - break; - - case AliasCommand _: - strb.AppendFormat($"\n".Mod().Color(Color.Yellow)); - break; - - default: - strb.AppendFormat("\n"); - break; - } - } - } -} diff --git a/TS3AudioBot/Config/Config.cs b/TS3AudioBot/Config/Config.cs index 2b73127c..af228461 100644 --- a/TS3AudioBot/Config/Config.cs +++ b/TS3AudioBot/Config/Config.cs @@ -7,19 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; + namespace TS3AudioBot.Config { - using Helper; - using Localization; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - public partial class ConfRoot { - private const string BotFileName = "bot.toml"; - private string fileName; private readonly Dictionary botConfCache = new Dictionary(); @@ -90,7 +88,7 @@ internal R NameToPath(string name) var nameResult = Util.IsSafeFileName(name); if (!nameResult.Ok) return nameResult.Error; - return new FileInfo(Path.Combine(Configs.BotsPath.Value, name, BotFileName)); + return new FileInfo(Path.Combine(Configs.BotsPath.Value, name, FilesConst.BotConfig)); } public ConfBot CreateBot() diff --git a/TS3AudioBot/Config/ConfigArray.cs b/TS3AudioBot/Config/ConfigArray.cs index 4107f7cf..34d063cb 100644 --- a/TS3AudioBot/Config/ConfigArray.cs +++ b/TS3AudioBot/Config/ConfigArray.cs @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using TS3AudioBot.Helper; + namespace TS3AudioBot.Config { - using Nett; - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using Helper; - public class ConfigArray : ConfigValue> { public ConfigArray(string key, IReadOnlyList defaultVal, string doc = "") : base(key, defaultVal, doc) { } diff --git a/TS3AudioBot/Config/ConfigDynamicTable.cs b/TS3AudioBot/Config/ConfigDynamicTable.cs index 35838c92..07b1dc45 100644 --- a/TS3AudioBot/Config/ConfigDynamicTable.cs +++ b/TS3AudioBot/Config/ConfigDynamicTable.cs @@ -7,23 +7,21 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using System; +using System.Collections.Generic; +using System.Diagnostics; + namespace TS3AudioBot.Config { - using Helper; - using Nett; - using System; - using System.Collections.Generic; - using System.Diagnostics; - [DebuggerDisplay("dyntable:{Key}")] public class ConfigDynamicTable : ConfigEnumerable, IDynamicTable where T : ConfigPart { - private readonly Dictionary dynamicTables; + private readonly Dictionary dynamicTables = new Dictionary(); private readonly Func createFactory; public ConfigDynamicTable(Func createFactory) { - Util.Init(out dynamicTables); this.createFactory = createFactory; } diff --git a/TS3AudioBot/Config/ConfigEnumerable.cs b/TS3AudioBot/Config/ConfigEnumerable.cs index f3bc4826..dc76bf62 100644 --- a/TS3AudioBot/Config/ConfigEnumerable.cs +++ b/TS3AudioBot/Config/ConfigEnumerable.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + namespace TS3AudioBot.Config { - using Nett; - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - public abstract class ConfigEnumerable : ConfigPart { private static readonly object EmptyObject = new object(); diff --git a/TS3AudioBot/Config/ConfigHelper.cs b/TS3AudioBot/Config/ConfigHelper.cs index 18f7e641..a2b35309 100644 --- a/TS3AudioBot/Config/ConfigHelper.cs +++ b/TS3AudioBot/Config/ConfigHelper.cs @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System; +using System.Linq; +using System.Xml; +using TS3AudioBot.CommandSystem; + namespace TS3AudioBot.Config { - using CommandSystem; - using Newtonsoft.Json; - using System; - using System.Linq; - using System.Xml; - public static class ConfigHelper { public const string DefaultBotName = "default"; diff --git a/TS3AudioBot/Config/ConfigPart.cs b/TS3AudioBot/Config/ConfigPart.cs index ebc4c937..f3c25286 100644 --- a/TS3AudioBot/Config/ConfigPart.cs +++ b/TS3AudioBot/Config/ConfigPart.cs @@ -7,17 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using TS3AudioBot.Helper; +using static TS3AudioBot.Helper.TomlTools; + namespace TS3AudioBot.Config { - using Helper; - using Nett; - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using static Helper.TomlTools; - [DebuggerDisplay("unknown:{Key}")] public abstract class ConfigPart : IJsonSerializable { diff --git a/TS3AudioBot/Config/ConfigStructs.cs b/TS3AudioBot/Config/ConfigStructs.cs index 36db592f..a01683a9 100644 --- a/TS3AudioBot/Config/ConfigStructs.cs +++ b/TS3AudioBot/Config/ConfigStructs.cs @@ -7,14 +7,15 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using System; +using System.Collections.Generic; +using TS3AudioBot.CommandSystem.Text; +using TS3AudioBot.Helper; +using TS3AudioBot.ResourceFactories.Youtube; + namespace TS3AudioBot.Config { - using CommandSystem.Text; - using Nett; - using System; - using System.Collections.Generic; - using TS3AudioBot.Helper; - public partial class ConfRoot : ConfigTable { public ConfBot Bot { get; } = Create("bot", @@ -37,6 +38,11 @@ public class ConfConfigs : ConfigTable //public ConfigValue RootPath { get; } = new ConfigValue("root_path", "."); // TODO enable when done public ConfigValue BotsPath { get; } = new ConfigValue("bots_path", "bots", "Path to a folder where the configuration files for each bot template will be stored."); + public ConfigValue SendStats { get; } = new ConfigValue("send_stats", true, + "Enable to contribute to the global stats tracker to help us improve our service.\n" + + "We do NOT send/store any IPs, identifiable information or logs for this.\n" + + "If you want to check how a stats packet looks like you can run the bot with 'TS3AudioBot --stats-example'.\n" + + "To disable contributing without config you can run the bot with 'TS3AudioBot --stats-disabled'. This will ignore the config value."); } public class ConfDb : ConfigTable @@ -49,6 +55,18 @@ public class ConfFactories : ConfigTable { public ConfPath Media { get; } = Create("media", "The default path to look for local resources."); + public ConfResolverYoutube Youtube = Create("youtube"); + } + + public class ConfResolverYoutube : ConfigTable + { + public ConfigValue ResolverPriority { get; } = new ConfigValue( + "prefer_resolver", LoaderPriority.Internal, "Changes how to try to resolve youtube songs\n" + + " - youtubedl : uses youtube-dl only\n" + + " - internal : uses the internal resolver, then youtube-dl"); + public ConfigValue ApiKey { get; } = new ConfigValue("youtube_api_key", "", + "Set your own youtube api key to keep using the old youtube factory loader.\n" + + "This feature is unsupported and may break at any time"); } public class ConfTools : ConfigTable @@ -76,8 +94,6 @@ public class ConfPlugins : ConfigTable { public ConfigValue Path { get; } = new ConfigValue("path", "plugins", "The path to the plugins folder."); - public ConfigValue WriteStatusFiles { get; } = new ConfigValue("write_status_files", false, - "Write to .status files to store a plugin enable status persistently and restart them on launch."); // TODO deprecate public ConfPluginsLoad Load { get; } = Create("load"); } @@ -90,7 +106,7 @@ public class ConfPluginsLoad : ConfigTable public class ConfWeb : ConfigTable { public ConfigArray Hosts { get; } = new ConfigArray("hosts", new[] { "*" }, - "An array of all urls the web api should be possible to be accessed with."); + "An array of all urls the web api should be possible to be accessed with."); public ConfigValue Port { get; } = new ConfigValue("port", 58913, "The port for the web server."); @@ -238,7 +254,7 @@ public class ConfAudioVolume : ConfigTable public class ConfPlaylists : ConfigTable { - public ConfigValue MaxItemCount { get; } = new ConfigValue("max_item_count", 1000); // TODO + //public ConfigValue MaxItemCount { get; } = new ConfigValue("max_item_count", 1000); // TODO } public class ConfHistory : ConfigTable @@ -251,7 +267,7 @@ public class ConfHistory : ConfigTable public class ConfData : ConfigTable { - public ConfigValue MaxItemCount { get; } = new ConfigValue("disk_data", "1M"); // TODO + //public ConfigValue MaxItemCount { get; } = new ConfigValue("disk_data", "1M"); // TODO } public class ConfEvents : ConfigTable @@ -262,9 +278,19 @@ public class ConfEvents : ConfigTable "Called when the bot gets disconnected."); public ConfigValue OnIdle { get; } = new ConfigValue("onidle", "", "Called when the bot does not play anything for a certain amount of time."); - public ConfigValue IdleTime { get; } = new ConfigValue("idletime", TimeSpan.FromMinutes(5), + public ConfigValue IdleDelay { get; } = new ConfigValue("idletime", TimeSpan.Zero, "Specifies how long the bot has to be idle until the 'onidle' event gets fired.\n" + - "You can specify the time in the ISO-8601 format with quotation marks \"PT30S\" or like: 15s, 1h, 3m30s"); + "You can specify the time in the ISO-8601 format \"PT30S\" or like: 15s, 1h, 3m30s"); + public ConfigValue OnAlone { get; } = new ConfigValue("onalone", "", + "Called when the last client leaves the channel of the bot. Delay can be specified"); + public ConfigValue AloneDelay { get; } = new ConfigValue("alone_delay", TimeSpan.Zero, + "Specifies how long the bot has to be alone until the 'onalone' event gets fired.\n" + + "You can specify the time in the ISO-8601 format \"PT30S\" or like: 15s, 1h, 3m30s"); + public ConfigValue OnParty { get; } = new ConfigValue("onparty", "", + "Called when the bot was alone and a client joins his channel. Delay can be specified."); + public ConfigValue PartyDelay { get; } = new ConfigValue("party_delay", TimeSpan.Zero, + "Specifies how long the bot has to be alone until the 'onalone' event gets fired.\n" + + "You can specify the time in the ISO-8601 format \"PT30S\" or like: 15s, 1h, 3m30s"); } // Utility config structs @@ -284,13 +310,13 @@ public class ConfPassword : ConfigTable public ConfigValue Hashed { get; } = new ConfigValue("hashed", false); public ConfigValue AutoHash { get; } = new ConfigValue("autohash", false); - public TS3Client.Password Get() + public TSLib.Password Get() { if (string.IsNullOrEmpty(Password)) - return TS3Client.Password.Empty; + return TSLib.Password.Empty; var pass = Hashed - ? TS3Client.Password.FromHash(Password) - : TS3Client.Password.FromPlain(Password); + ? TSLib.Password.FromHash(Password) + : TSLib.Password.FromPlain(Password); if (AutoHash && !Hashed) { Password.Value = pass.HashedPassword; @@ -320,9 +346,9 @@ public static class ConfTimeExtensions var repeat = last == "repeat" || last == "repeat last"; // "repeat" might get removed for other loops, but for now keep as hidden alternative var max = repeat ? value.Count - 2 : value.Count - 1; if (index <= max) - return TomlTools.ParseTime(value[index]); + return TextUtil.ParseTime(value[index]); else - return TomlTools.ParseTime(value[max]); + return TextUtil.ParseTime(value[max]); } public static E ValidateTime(IReadOnlyList value) @@ -337,7 +363,7 @@ public static E ValidateTime(IReadOnlyList value) var max = repeat ? value.Count - 2 : value.Count - 1; for (int i = 0; i <= max; i++) { - var r = TomlTools.ValidateTime(value[i]); + var r = TextUtil.ValidateTime(value[i]); if (!r.Ok) return r; } diff --git a/TS3AudioBot/Config/ConfigTable.cs b/TS3AudioBot/Config/ConfigTable.cs index debbc2de..09c632d9 100644 --- a/TS3AudioBot/Config/ConfigTable.cs +++ b/TS3AudioBot/Config/ConfigTable.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + namespace TS3AudioBot.Config { - using Nett; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - [DebuggerDisplay("table:{Key}")] public abstract class ConfigTable : ConfigEnumerable { diff --git a/TS3AudioBot/Config/ConfigUpgrade2.cs b/TS3AudioBot/Config/ConfigUpgrade2.cs index b8a0fdd4..c4e722cf 100644 --- a/TS3AudioBot/Config/ConfigUpgrade2.cs +++ b/TS3AudioBot/Config/ConfigUpgrade2.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.IO; +using System.Text.RegularExpressions; +using TS3AudioBot.Helper; + namespace TS3AudioBot.Config { - using Helper; - using System; - using System.IO; - using System.Text.RegularExpressions; - internal static class ConfigUpgrade2 { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); diff --git a/TS3AudioBot/Config/ConfigValue.cs b/TS3AudioBot/Config/ConfigValue.cs index 269f788b..bdee0ac6 100644 --- a/TS3AudioBot/Config/ConfigValue.cs +++ b/TS3AudioBot/Config/ConfigValue.cs @@ -7,15 +7,15 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using TS3AudioBot.Helper; + namespace TS3AudioBot.Config { - using Nett; - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using Helper; - [DebuggerDisplay("{Key}:{Value}")] public class ConfigValue : ConfigPart { @@ -73,7 +73,7 @@ public override void FromToml(TomlObject tomlObject) } var validate = Validator?.Invoke(tomlValue) ?? R.Ok; - if(!validate.Ok) + if (!validate.Ok) { Log.Warn("Invalid value in '{0}', {1}", Key, validate.Error); return; diff --git a/TS3AudioBot/Config/Deprecated/ConfigFile.cs b/TS3AudioBot/Config/Deprecated/ConfigFile.cs index b27188fd..a7356954 100644 --- a/TS3AudioBot/Config/Deprecated/ConfigFile.cs +++ b/TS3AudioBot/Config/Deprecated/ConfigFile.cs @@ -7,18 +7,18 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using TS3AudioBot.Localization; +using TSLib.Helper; + namespace TS3AudioBot.Config.Deprecated { - using Helper; - using Localization; - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Reflection; - public abstract class ConfigFile { private const char SplitChar = '='; @@ -27,12 +27,7 @@ public abstract class ConfigFile private static readonly string[] CommentSeqArr = { CommentSeq, ";", "//" }; private const string NameSeperator = "::"; private bool changed; - private readonly Dictionary confObjects; - - protected ConfigFile() - { - Util.Init(out confObjects); - } + private readonly Dictionary confObjects = new Dictionary(); public static ConfigFile OpenOrCreate(string path) { @@ -180,7 +175,7 @@ public bool Open() } open = true; - var strLines = File.ReadAllLines(path, Util.Utf8Encoder); + var strLines = File.ReadAllLines(path, Tools.Utf8Encoder); fileLines.Clear(); for (int i = 0; i < strLines.Length; i++) { diff --git a/TS3AudioBot/Config/Deprecated/InfoAttribute.cs b/TS3AudioBot/Config/Deprecated/InfoAttribute.cs index 69033e2d..fb911739 100644 --- a/TS3AudioBot/Config/Deprecated/InfoAttribute.cs +++ b/TS3AudioBot/Config/Deprecated/InfoAttribute.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.Config.Deprecated { - using System; - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] internal sealed class InfoAttribute : Attribute { diff --git a/TS3AudioBot/Config/Deprecated/UpgradeScript.cs b/TS3AudioBot/Config/Deprecated/UpgradeScript.cs index 19fb8221..dc02ed4a 100644 --- a/TS3AudioBot/Config/Deprecated/UpgradeScript.cs +++ b/TS3AudioBot/Config/Deprecated/UpgradeScript.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.IO; + namespace TS3AudioBot.Config.Deprecated { - using System; - using System.IO; - internal static class UpgradeScript { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); @@ -73,7 +73,6 @@ private static void Upgrade(ConfigFile from, ConfRoot to) to.Tools.YoutubeDl.Path.Value = ytd.YoutubedlPath; to.Tools.Ffmpeg.Path.Value = qcd.FfmpegPath; to.Plugins.Path.Value = pmd.PluginPath; - to.Plugins.WriteStatusFiles.Value = pmd.WriteStatusFiles; to.Factories.Media.Path.Value = mfd.DefaultPath; to.Db.Path.Value = hmd.HistoryFile; diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index f4dbdc77..5d0b43a5 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -7,26 +7,26 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using NLog; +using System; +using System.Threading; +using System.Threading.Tasks; +using TS3AudioBot.CommandSystem; +using TS3AudioBot.Config; +using TS3AudioBot.Dependency; +using TS3AudioBot.Environment; +using TS3AudioBot.Helper; +using TS3AudioBot.Plugins; +using TS3AudioBot.ResourceFactories; +using TS3AudioBot.Rights; +using TS3AudioBot.Sessions; +using TS3AudioBot.Web; + namespace TS3AudioBot { - using Config; - using Dependency; - using Helper; - using Helper.Environment; - using NLog; - using Plugins; - using ResourceFactories; - using Rights; - using Sessions; - using System; - using System.Threading; - using TS3AudioBot.CommandSystem; - using Web; - public sealed class Core : IDisposable { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private const string DefaultConfigFileName = "ts3audiobot.toml"; private readonly string configFilePath; private bool forceNextExit; private readonly CoreInjector injector; @@ -52,9 +52,10 @@ internal static void Main(string[] args) // Initialize the actual core var core = new Core(setup.ConfigFile); AppDomain.CurrentDomain.UnhandledException += core.ExceptionHandler; + TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; Console.CancelKeyPress += core.ConsoleInterruptHandler; - var initResult = core.Run(setup.Interactive); + var initResult = core.Run(setup); if (!initResult) { Log.Error("Core initialization failed: {0}", initResult.Error); @@ -65,12 +66,12 @@ internal static void Main(string[] args) public Core(string configFilePath = null) { // setting defaults - this.configFilePath = configFilePath ?? DefaultConfigFileName; + this.configFilePath = configFilePath ?? FilesConst.CoreConfig; injector = new CoreInjector(); } - private E Run(bool interactive = false) + private E Run(ParameterData setup) { var configResult = ConfRoot.OpenOrCreate(configFilePath); if (!configResult.Ok) @@ -100,9 +101,8 @@ private E Run(bool interactive = false) builder.RequestModule(); builder.RequestModule(); builder.AddModule(config.Factories); - // TODO fix interaction: rfm needs to be in the same injector as the commandsystem, otherwise duplicate error - // Also TODO find solution to move commandsystem to bot, without breaking api - builder.RequestModule(); + builder.RequestModule(); + builder.RequestModule(); if (!builder.Build()) { @@ -114,9 +114,10 @@ private E Run(bool interactive = false) builder.GetModule().StartTimedSnapshots(); builder.GetModule().RegisterCollection(MainCommands.Bag); - builder.GetModule().CreateConfigIfNotExists(interactive); - builder.GetModule().RunBots(interactive); + builder.GetModule().CreateConfigIfNotExists(setup.Interactive); + builder.GetModule().RunBots(setup.Interactive); builder.GetModule().StartWebServer(); + builder.GetModule().StartTimer(setup.SendStats); return R.Ok; } @@ -125,7 +126,12 @@ public void ExceptionHandler(object sender, UnhandledExceptionEventArgs e) { Log.Fatal(e.ExceptionObject as Exception, "Critical program failure!"); Dispose(); - Environment.Exit(-1); + System.Environment.Exit(-1); + } + + public static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs e) + { + Log.Fatal(e.Exception, "Critical program error!"); } public void ConsoleInterruptHandler(object sender, ConsoleCancelEventArgs e) @@ -142,7 +148,7 @@ public void ConsoleInterruptHandler(object sender, ConsoleCancelEventArgs e) else { Log.Info("Got multiple interrupt signals, trying to force-exit."); - Environment.Exit(0); + System.Environment.Exit(0); } } } @@ -155,7 +161,7 @@ public void Dispose() injector.GetModule()?.Dispose(); injector.GetModule()?.Dispose(); injector.GetModule()?.Dispose(); - injector.GetModule()?.Dispose(); + injector.GetModule()?.Dispose(); TickPool.Close(); } } diff --git a/TS3AudioBot/DbStore.cs b/TS3AudioBot/DbStore.cs index ba122a71..a2755e75 100644 --- a/TS3AudioBot/DbStore.cs +++ b/TS3AudioBot/DbStore.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using LiteDB; +using System; +using System.IO; +using TS3AudioBot.Config; + namespace TS3AudioBot { - using Config; - using LiteDB; - using System; - using System.IO; - public class DbStore : IDisposable { private const string DbMetaInformationTable = "dbmeta"; @@ -42,7 +42,7 @@ public DbMetaData GetMetaData(string table) public void UpdateMetaData(DbMetaData metaData) { - metaTable.Update(metaData); + metaTable.Upsert(metaData); } public LiteCollection GetCollection(string name) @@ -50,6 +50,8 @@ public LiteCollection GetCollection(string name) return database.GetCollection(name); } + public void DropCollection(string name) => database.DropCollection(name); + public void CleanFile() { database.Shrink(); @@ -65,6 +67,6 @@ public class DbMetaData { public string Id { get; set; } public int Version { get; set; } - public object CustomData { get; set; } + public string CustomData { get; set; } } } diff --git a/TS3AudioBot/Dependency/BasicInjector.cs b/TS3AudioBot/Dependency/BasicInjector.cs index 6d2e393b..373df5a8 100644 --- a/TS3AudioBot/Dependency/BasicInjector.cs +++ b/TS3AudioBot/Dependency/BasicInjector.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; + namespace TS3AudioBot.Dependency { - using System; - using System.Collections.Generic; - public class BasicInjector : IInjector { private readonly Dictionary dynamicObjects; diff --git a/TS3AudioBot/Dependency/ChainedInjector.cs b/TS3AudioBot/Dependency/ChainedInjector.cs index ef621284..eaad97d8 100644 --- a/TS3AudioBot/Dependency/ChainedInjector.cs +++ b/TS3AudioBot/Dependency/ChainedInjector.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.Dependency { - using System; - - public class ChainedInjector : IInjector where T: class, IInjector + public class ChainedInjector : IInjector where T : class, IInjector { public IInjector ParentInjector { get; set; } public T OwnInjector { get; protected set; } diff --git a/TS3AudioBot/Dependency/DependencyBuilder.cs b/TS3AudioBot/Dependency/DependencyBuilder.cs index 531f1b38..cb5c94c2 100644 --- a/TS3AudioBot/Dependency/DependencyBuilder.cs +++ b/TS3AudioBot/Dependency/DependencyBuilder.cs @@ -7,13 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; + namespace TS3AudioBot.Dependency { - using Helper; - using System; - using System.Collections.Generic; - using System.Linq; - public sealed class CoreInjector : BasicInjector { } public sealed class BotInjector : ChainedInjector { @@ -27,11 +26,10 @@ public class DependencyBuilder : IInjector private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private readonly IInjector injector; - private readonly LinkedList modules; + private readonly LinkedList modules = new LinkedList(); public DependencyBuilder(IInjector injector) { - Util.Init(out modules); this.injector = injector; } diff --git a/TS3AudioBot/Dependency/IInjector.cs b/TS3AudioBot/Dependency/IInjector.cs index 43cd8b7e..4992ae80 100644 --- a/TS3AudioBot/Dependency/IInjector.cs +++ b/TS3AudioBot/Dependency/IInjector.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.Dependency { - using System; - public interface IInjector { object GetModule(Type type); diff --git a/TS3AudioBot/Dependency/InjectorExtensions.cs b/TS3AudioBot/Dependency/InjectorExtensions.cs index 4283c68c..ca684bcd 100644 --- a/TS3AudioBot/Dependency/InjectorExtensions.cs +++ b/TS3AudioBot/Dependency/InjectorExtensions.cs @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Linq; +using System.Reflection; + namespace TS3AudioBot.Dependency { - using System; - using System.Reflection; - using System.Linq; - public static class InjectorExtensions { public static T GetModule(this IInjector injector) diff --git a/TS3AudioBot/Dependency/Module.cs b/TS3AudioBot/Dependency/Module.cs index 8e26cbc1..844acd24 100644 --- a/TS3AudioBot/Dependency/Module.cs +++ b/TS3AudioBot/Dependency/Module.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Linq; + namespace TS3AudioBot.Dependency { - using System; - using System.Linq; - internal class Module { public Type TImplementation { get; } diff --git a/TS3AudioBot/Dependency/NullInjector.cs b/TS3AudioBot/Dependency/NullInjector.cs index 7b869592..1fbc848f 100644 --- a/TS3AudioBot/Dependency/NullInjector.cs +++ b/TS3AudioBot/Dependency/NullInjector.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.Dependency { - using System; - public class NullInjector : IInjector { public static readonly IInjector Instance = new NullInjector(); diff --git a/TS3AudioBot/Environment/Stats.cs b/TS3AudioBot/Environment/Stats.cs new file mode 100644 index 00000000..3402af6b --- /dev/null +++ b/TS3AudioBot/Environment/Stats.cs @@ -0,0 +1,350 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using LiteDB; +using Newtonsoft.Json; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TSLib.Helper; + +namespace TS3AudioBot.Environment +{ + public class Stats + { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + private const string StatsTable = "stats"; + private const int StatsVersion = 1; + private static readonly TimeSpan CheckInterval = TimeSpan.FromMinutes(1); + private static readonly TimeSpan SendInterval = TimeSpan.FromDays(1); + private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + Formatting = Formatting.None, + }; + private static readonly Newtonsoft.Json.JsonSerializer JsonSer = Newtonsoft.Json.JsonSerializer.Create(JsonSettings); + + private readonly ConfRoot conf; + private readonly DbStore database; + private readonly BotManager botManager; + private TickWorker ticker; + private bool uploadParamEnabled; + private bool UploadEnabled => uploadParamEnabled && conf.Configs.SendStats; + + private DbMetaData meta; + private StatsData overallStats; + private StatsMeta statsPoints; + private LiteCollection trackEntries; + private readonly StatsData CurrentStatsData = new StatsData() + { + SongStats = new ConcurrentDictionary() + }; + private DateTime runtimeLastTrack; + // bot id -> factory + private readonly ConcurrentDictionary runningSongsPerFactory = new ConcurrentDictionary(); + + public Stats(ConfRoot conf, DbStore database, BotManager botManager) + { + this.conf = conf; + this.database = database; + this.botManager = botManager; + uploadParamEnabled = true; + runtimeLastTrack = Tools.Now; + + ReadAndUpgradeStats(); + } + + private void ReadAndUpgradeStats() + { + meta = database.GetMetaData(StatsTable); + trackEntries = database.GetCollection(StatsTable); + trackEntries.EnsureIndex(x => x.Time); + + if (meta.Version != StatsVersion) + { + statsPoints = new StatsMeta + { + LastSend = Tools.Now, + }; + meta.Version = StatsVersion; + UpdateMeta(); + } + else + { + statsPoints = JsonConvert.DeserializeObject(meta.CustomData, JsonSettings); + // Upgrade steps here + } + + overallStats = trackEntries.FindById(0); + if (overallStats is null) + { + overallStats = new StatsData + { + Id = 0 + }; + } + } + + private void UpdateMeta() + { + meta.CustomData = JsonConvert.SerializeObject(statsPoints, JsonSettings); + database.UpdateMetaData(meta); + } + + public void StartTimer(bool upload) + { + uploadParamEnabled = upload; + if (ticker != null) + throw new InvalidOperationException(); + ticker = TickPool.RegisterTick(() => TrackPoint(), CheckInterval, true); + } + + private void SendStats(StatsPing sendPacket) + { + try + { + Log.Debug("Send: {@data}", sendPacket); + var request = WebWrapper.CreateRequest(new Uri("https://splamy.de/api/tab/stats")).Unwrap(); + request.Timeout = 2000; // ms + request.Method = "POST"; + request.ContentType = "application/json"; + using (var rStream = request.GetRequestStream()) + using (var sw = new StreamWriter(rStream, Tools.Utf8Encoder)) + { + JsonSer.Serialize(sw, sendPacket); + } + request.GetResponse().Dispose(); + } + catch (Exception ex) { Log.Debug(ex, "Could not upload stats"); } + } + + private StatsPing GetStatsTill(DateTime date) + { + var sendPacket = GetDefaultStatsPing(); + + uint count = 0; + uint avgBots = 0; + var entries = trackEntries.Find(x => x.Time > date); + foreach (var entry in entries) + { + count++; + sendPacket.Add(entry); + avgBots += entry.RunningBots; + } + sendPacket.RunningBots = avgBots / count; + return sendPacket; + } + + private void TrackPoint() + { + var nextId = statsPoints.GenNextIndex(); + + var now = Tools.Now; + CurrentStatsData.Time = now; + CurrentStatsData.Id = nextId; + var trackTime = now - runtimeLastTrack; + runtimeLastTrack = now; + CurrentStatsData.TotalUptime += trackTime; + CurrentStatsData.RunningBots = botManager.GetRunningBotCount(); + CurrentStatsData.BotsRuntime = TimeSpan.FromTicks(trackTime.Ticks * CurrentStatsData.RunningBots); + foreach (var factory in runningSongsPerFactory.Values) + CurrentStatsData.SongStats.GetOrNew(factory).Playtime += trackTime; + + Log.Debug("Track: {@data}", CurrentStatsData); + trackEntries.Upsert(CurrentStatsData); + overallStats.Add(CurrentStatsData); + trackEntries.Upsert(overallStats); + CurrentStatsData.Reset(); + + if (UploadEnabled && statsPoints.LastSend + SendInterval < now) + { + var sendData = GetStatsTill(statsPoints.LastSend); + SendStats(sendData); + statsPoints.LastSend = now; + } + + UpdateMeta(); + } + + // Track operations + + public void TrackSongLoad(string factory, bool successful, bool fromUser) + { + var statsFactory = CurrentStatsData.SongStats.GetOrNew(factory ?? ""); + statsFactory.PlayRequests++; + if (successful) statsFactory.PlaySucessful++; + if (fromUser) statsFactory.PlayFromUser++; + } + + public void TrackCommandCall(bool byUser) + { + CurrentStatsData.CommandCalls++; + if (byUser) CurrentStatsData.CommandFromUser++; + } + + public void TrackCommandApiCall() + { + CurrentStatsData.CommandCalls++; + CurrentStatsData.CommandFromApi++; + } + + public void TrackSongStart(Id bot, string factory) + { + factory = factory ?? ""; + runningSongsPerFactory[bot] = factory; + var statsFactory = CurrentStatsData.SongStats.GetOrNew(factory); + statsFactory.Playtime -= (Tools.Now - runtimeLastTrack); + } + + public void TrackSongStop(Id bot) + { + if (runningSongsPerFactory.TryRemove(bot, out var factory)) + { + var statsFactory = CurrentStatsData.SongStats.GetOrNew(factory); + statsFactory.Playtime += (Tools.Now - runtimeLastTrack); + } + } + + private static StatsPing GetDefaultStatsPing() + { + return new StatsPing + { + BotVersion = SystemData.AssemblyData.ToString(), + Platform = SystemData.PlatformData, + Runtime = SystemData.RuntimeData.FullName, + }; + } + + public static string CreateExample() + { + var sendData = GetDefaultStatsPing(); + sendData.TotalUptime = TimeSpan.FromHours(12.34); + sendData.BotsRuntime = TimeSpan.FromHours(4.20); + sendData.CommandCalls = 1234; + sendData.CommandFromApi = 100; + sendData.RunningBots = 3; + sendData.SongStats = new Dictionary() + { + {"youtube", new StatsFactory{ + PlayRequests = 100, + PlayFromUser = 42, + Playtime = TimeSpan.FromMinutes(12.34), + SearchRequests = 5, + }} + }; + + return JsonConvert.SerializeObject(sendData, Formatting.Indented); + } + } + + internal class StatsPing : StatsData + { + // Meta + public string BotVersion { get; set; } + public string Platform { get; set; } + public string Runtime { get; set; } + } + + internal class StatsMeta + { + public const int RingOff = 1; + public const int RingSize = 60 * 24 * 7; /* min * day * week */ + + public int CurrentIndex { get; set; } = 0; + public DateTime LastSend = DateTime.MinValue; + + public int GenNextIndex() + { + CurrentIndex = (CurrentIndex + 1) % RingSize; + return CurrentIndex + RingOff; + } + } + + internal class StatsData + { + [JsonIgnore] + public int Id { get; set; } + [JsonIgnore] + public DateTime Time { get; set; } + public uint RunningBots { get; set; } + public TimeSpan BotsRuntime { get; set; } = TimeSpan.Zero; + + public TimeSpan TotalUptime { get; set; } = TimeSpan.Zero; + public IDictionary SongStats { get; set; } = new Dictionary(); + + public uint CommandCalls { get; set; } + ///How many actually were started by a user (and not i.e. by event) + public uint CommandFromUser { get; set; } + public uint CommandFromApi { get; set; } + + public bool ShouldSerializeSongStats() => SongStats.Count > 0; + public bool ShouldSerializeCommandCalls() => CommandCalls != 0; + public bool ShouldSerializeCommandFromUser() => CommandFromUser != 0; + public bool ShouldSerializeCommandFromApi() => CommandFromApi != 0; + + public void Add(StatsData other) + { + TotalUptime += other.TotalUptime; + BotsRuntime += other.BotsRuntime; + foreach (var kvp in other.SongStats) + SongStats.GetOrNew(kvp.Key).Add(kvp.Value); + CommandCalls += other.CommandCalls; + CommandFromUser += other.CommandFromUser; + CommandFromApi += other.CommandFromApi; + } + + public void Reset() + { + TotalUptime = TimeSpan.Zero; + RunningBots = 0; + BotsRuntime = TimeSpan.Zero; + SongStats.Clear(); + CommandCalls = 0; + CommandFromUser = 0; + CommandFromApi = 0; + } + } + + internal class StatsFactory + { + public uint PlayRequests { get; set; } + public uint PlaySucessful { get; set; } + ///How many actually were started by a user (and not i.e. from a playlist) + public uint PlayFromUser { get; set; } + public uint SearchRequests { get; set; } + public TimeSpan Playtime { get; set; } + + public bool ShouldSerializePlayRequests() => PlayRequests != 0; + public bool ShouldSerializePlaySucessful() => PlaySucessful != 0; + public bool ShouldSerializePlayFromUser() => PlayFromUser != 0; + public bool ShouldSerializeSearchRequests() => SearchRequests != 0; + public bool ShouldSerializePlaytime() => Playtime != TimeSpan.Zero; + + public void Add(StatsFactory other) + { + PlayRequests += other.PlayRequests; + PlaySucessful += other.PlaySucessful; + PlayFromUser += other.PlayFromUser; + SearchRequests += other.SearchRequests; + Playtime += other.Playtime; + } + + public void Reset() + { + PlayRequests = 0; + PlaySucessful = 0; + PlayFromUser = 0; + SearchRequests = 0; + Playtime = TimeSpan.Zero; + } + } +} diff --git a/TS3AudioBot/Helper/Environment/SystemData.cs b/TS3AudioBot/Environment/SystemData.cs similarity index 77% rename from TS3AudioBot/Helper/Environment/SystemData.cs rename to TS3AudioBot/Environment/SystemData.cs index e2d7b225..f48830be 100644 --- a/TS3AudioBot/Helper/Environment/SystemData.cs +++ b/TS3AudioBot/Environment/SystemData.cs @@ -7,33 +7,27 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3AudioBot.Helper.Environment +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text.RegularExpressions; +using TS3AudioBot.Helper; +using TSLib.Helper; +//using PlatformVersion = System.ValueTuple; + +namespace TS3AudioBot.Environment { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Reflection; - using System.Text.RegularExpressions; - using PlatformVersion = System.ValueTuple; - public static class SystemData { private static readonly Regex PlatformRegex = new Regex(@"(\w+)=(.*)", RegexOptions.IgnoreCase | RegexOptions.ECMAScript | RegexOptions.Multiline); private static readonly Regex SemVerRegex = new Regex(@"(\d+)(?:\.(\d+)){1,3}", RegexOptions.IgnoreCase | RegexOptions.ECMAScript | RegexOptions.Multiline); - public static bool IsLinux { get; } - = Environment.OSVersion.Platform == PlatformID.Unix - || Environment.OSVersion.Platform == PlatformID.MacOSX - || ((int)Environment.OSVersion.Platform == 128); - public static BuildData AssemblyData { get; } = GenAssemblyData(); private static BuildData GenAssemblyData() { - // Path for GVT <=4.0.0-beta14 - var gitInfoType = Assembly.GetExecutingAssembly().GetType(nameof(TS3AudioBot) + ".GitVersionInformation"); - // Path for GVT >=4.0.0 - gitInfoType = gitInfoType ?? Assembly.GetExecutingAssembly().GetType("GitVersionInformation"); + var gitInfoType = Assembly.GetExecutingAssembly().GetType("GitVersionInformation"); if (gitInfoType is null) return new BuildData(); @@ -50,9 +44,9 @@ private static string GenPlatformDat() { string platform = null; string version = null; - string bitness = Environment.Is64BitProcess ? "64bit" : "32bit"; + string bitness = System.Environment.Is64BitProcess ? "64bit" : "32bit"; - if (IsLinux) + if (Tools.IsLinux) { var values = new Dictionary(); @@ -103,7 +97,7 @@ private static string GenPlatformDat() else { platform = "Windows"; - version = Environment.OSVersion.Version.ToString(); + version = System.Environment.OSVersion.Version.ToString(); } return $"{platform} {version} ({bitness})"; @@ -135,25 +129,25 @@ private static void RunBash(string param, Action action) catch { } } - public static (Runtime Runtime, string FullName, Version SemVer) RuntimeData { get; } = GenRuntimeData(); + public static PlatformVersion RuntimeData { get; } = GenRuntimeData(); private static PlatformVersion GenRuntimeData() { var ver = GetNetCoreVersion(); - if (ver.HasValue) - return ver.Value; + if (ver != null) + return ver; ver = GetMonoVersion(); - if (ver.HasValue) - return ver.Value; + if (ver != null) + return ver; ver = GetNetFrameworkVersion(); - if (ver.HasValue) - return ver.Value; + if (ver != null) + return ver; - return (Runtime.Unknown, "? (?)", null); + return new PlatformVersion(Runtime.Unknown, "? (?)", null); } - private static PlatformVersion? GetNetCoreVersion() + private static PlatformVersion GetNetCoreVersion() { var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); @@ -162,27 +156,27 @@ private static PlatformVersion GenRuntimeData() return null; var version = assemblyPath[netCoreAppIndex + 1]; var semVer = ParseToSemVer(version); - return (Runtime.Core, $".NET Core ({version})", semVer); + return new PlatformVersion(Runtime.Core, $".NET Core ({version})", semVer); } - private static PlatformVersion? GetMonoVersion() + private static PlatformVersion GetMonoVersion() { var type = Type.GetType("Mono.Runtime"); if (type is null) return null; var displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); if (displayName is null) - return (Runtime.Mono, "Mono (?)", null); + return new PlatformVersion(Runtime.Mono, "Mono (?)", null); var version = displayName.Invoke(null, null) as string; var semVer = ParseToSemVer(version); - return (Runtime.Mono, $"Mono ({version})", semVer); + return new PlatformVersion(Runtime.Mono, $"Mono ({version})", semVer); } - private static PlatformVersion? GetNetFrameworkVersion() + private static PlatformVersion GetNetFrameworkVersion() { - var version = Environment.Version.ToString(); + var version = System.Environment.Version.ToString(); var semVer = ParseToSemVer(version); - return (Runtime.Net, $".NET Framework {version}", semVer); + return new PlatformVersion(Runtime.Net, $".NET Framework {version}", semVer); } private static Version ParseToSemVer(string version) @@ -219,6 +213,22 @@ public class BuildData public override string ToString() => $"{Version}/{Branch}/{(CommitSha.Length > 8 ? CommitSha.Substring(0, 8) : CommitSha)}"; } + public class PlatformVersion + { + public Runtime Runtime; + public string FullName; + public Version SemVer; + + public PlatformVersion(Runtime runtime, string fullName, Version semVer) + { + Runtime = runtime; + FullName = fullName; + SemVer = semVer; + } + + public override string ToString() => FullName; + } + public static class SemVerExtension { public static string AsSemVer(this Version version) => $"{version.Major}.{version.Minor}.{version.Build}" + (version.Revision != 0 ? $".{version.Revision}" : null); diff --git a/TS3AudioBot/Helper/Environment/SystemMonitor.cs b/TS3AudioBot/Environment/SystemMonitor.cs similarity index 74% rename from TS3AudioBot/Helper/Environment/SystemMonitor.cs rename to TS3AudioBot/Environment/SystemMonitor.cs index d656a3a5..71a729d5 100644 --- a/TS3AudioBot/Helper/Environment/SystemMonitor.cs +++ b/TS3AudioBot/Environment/SystemMonitor.cs @@ -1,11 +1,22 @@ -namespace TS3AudioBot.Helper.Environment -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Threading; +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using TS3AudioBot.Helper; +using TSLib.Helper; +namespace TS3AudioBot.Environment +{ public class SystemMonitor { private static readonly Process CurrentProcess = Process.GetCurrentProcess(); @@ -18,7 +29,7 @@ public class SystemMonitor private DateTime lastSnapshotTime = DateTime.MinValue; private TimeSpan lastCpuTime = TimeSpan.Zero; - public DateTime StartTime { get; } = Util.GetNow(); + public DateTime StartTime { get; } = Tools.Now; public void StartTimedSnapshots() { @@ -35,12 +46,12 @@ public void CreateSnapshot() { } - var currentSnapshotTime = Util.GetNow(); + var currentSnapshotTime = Tools.Now; var currentCpuTime = CurrentProcess.TotalProcessorTime; var timeDiff = currentSnapshotTime - lastSnapshotTime; var cpuDiff = currentCpuTime - lastCpuTime; - var cpu = (cpuDiff.Ticks / (float)timeDiff.Ticks); + var cpu = cpuDiff.Ticks / (float)timeDiff.Ticks; lastSnapshotTime = currentSnapshotTime; lastCpuTime = currentCpuTime; diff --git a/TS3AudioBot/Helper/Const.cs b/TS3AudioBot/Helper/Const.cs index 130500a4..06c11159 100644 --- a/TS3AudioBot/Helper/Const.cs +++ b/TS3AudioBot/Helper/Const.cs @@ -11,7 +11,19 @@ namespace TS3AudioBot.Helper { internal static class SessionConst { - public const string Playlist = "tab_playlist"; public const string SearchResult = "tab_searchresult"; } + + public static class BotPaths + { + public const string Playlists = "playlists"; + public const string Avatars = "avatars"; + public const string Music = "music"; + } + + public static class FilesConst + { + public const string CoreConfig = "ts3audiobot.toml"; + public const string BotConfig = "bot.toml"; + } } diff --git a/TS3AudioBot/Helper/Diagnose/SelfDiagnoseMessage.cs b/TS3AudioBot/Helper/Diagnose/SelfDiagnoseMessage.cs index ee6cb439..c7d2c741 100644 --- a/TS3AudioBot/Helper/Diagnose/SelfDiagnoseMessage.cs +++ b/TS3AudioBot/Helper/Diagnose/SelfDiagnoseMessage.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; + namespace TS3AudioBot.Helper.Diagnose { - using Newtonsoft.Json; - public class SelfDiagnoseMessage { public string Description { get; set; } diff --git a/TS3AudioBot/Helper/IJsonConfig.cs b/TS3AudioBot/Helper/IJsonConfig.cs index 8891e06c..6dfb0875 100644 --- a/TS3AudioBot/Helper/IJsonConfig.cs +++ b/TS3AudioBot/Helper/IJsonConfig.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System; +using System.IO; +using System.Text; + namespace TS3AudioBot.Helper { - using Newtonsoft.Json; - using System; - using System.IO; - using System.Text; - public interface IJsonSerializable { bool ExpectsString { get; } diff --git a/TS3AudioBot/Helper/ImageUtil.cs b/TS3AudioBot/Helper/ImageUtil.cs index 5e9c0879..3593809a 100644 --- a/TS3AudioBot/Helper/ImageUtil.cs +++ b/TS3AudioBot/Helper/ImageUtil.cs @@ -7,51 +7,79 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Processing; +using System; +using System.IO; +using TS3AudioBot.Localization; + namespace TS3AudioBot.Helper { - using SixLabors.ImageSharp; - using SixLabors.ImageSharp.Formats.Gif; - using SixLabors.ImageSharp.Formats.Jpeg; - using SixLabors.ImageSharp.Processing; - using System; - using System.IO; - internal static class ImageUtil { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + public const int ResizeMaxWidthDefault = 320; - public static Stream ResizeImage(Stream imgStream, int resizeMaxWidth = ResizeMaxWidthDefault) + public static R ResizeImageSave(Stream imgStream, out string mime, int resizeMaxWidth = ResizeMaxWidthDefault) { + mime = null; + if (imgStream == null) + return new LocalStr("Stream not found"); try { - using (var img = Image.Load(imgStream)) - { - if (img.Width <= resizeMaxWidth) - return SaveAdaptive(img); - - float ratio = img.Width / (float)img.Height; - img.Mutate(x => x.Resize(resizeMaxWidth, (int)(resizeMaxWidth / ratio))); - - return SaveAdaptive(img); - } + using (var limitStream = new LimitStream(imgStream, Limits.MaxImageStreamSize)) + return ResizeImage(limitStream, out mime, resizeMaxWidth); } catch (NotSupportedException) { - return null; + Log.Debug("Dropping image because of unknown format"); + return new LocalStr("Dropping image because of unknown format"); // TODO + } + catch (EntityTooLargeException) + { + Log.Debug("Dropping image because too large"); + return new LocalStr("Dropping image because too large"); // TODO } } - private static MemoryStream SaveAdaptive(Image img) + private static Stream ResizeImage(Stream imgStream, out string mime, int resizeMaxWidth = ResizeMaxWidthDefault) { - var mem = new MemoryStream(); + mime = null; + using (var img = Image.Load(imgStream)) + { + if (img.Width > Limits.MaxImageDimension || img.Height > Limits.MaxImageDimension + || img.Width == 0 || img.Height == 0) + return null; + + if (img.Width <= resizeMaxWidth) + return SaveAdaptive(img, out mime); + + float ratio = img.Width / (float)img.Height; + img.Mutate(x => x.Resize(resizeMaxWidth, (int)(resizeMaxWidth / ratio))); + + return SaveAdaptive(img, out mime); + } + } + + private static Stream SaveAdaptive(Image img, out string mime) + { + IImageFormat format; if (img.Frames.Count > 1) { - img.Save(mem, GifFormat.Instance); + format = GifFormat.Instance; } else { - img.Save(mem, JpegFormat.Instance); + format = JpegFormat.Instance; } + mime = format.DefaultMimeType; + var mem = new MemoryStream(); + img.Save(mem, format); + mem.Seek(0, SeekOrigin.Begin); return mem; } } diff --git a/TS3AudioBot/Helper/Interactive.cs b/TS3AudioBot/Helper/Interactive.cs index 7e1bd4bf..69b1206f 100644 --- a/TS3AudioBot/Helper/Interactive.cs +++ b/TS3AudioBot/Helper/Interactive.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.Helper { - using System; - - public class Interactive + public static class Interactive { public static bool UserAgree(bool defaultTo = true) { diff --git a/TS3AudioBot/Helper/LimitStream.cs b/TS3AudioBot/Helper/LimitStream.cs index b26a4116..817c97ca 100644 --- a/TS3AudioBot/Helper/LimitStream.cs +++ b/TS3AudioBot/Helper/LimitStream.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.IO; + namespace TS3AudioBot.Helper { - using System; - using System.IO; - public class LimitStream : Stream { private readonly Stream baseStream; @@ -26,7 +26,7 @@ public LimitStream(Stream baseStream, long maxLength) public long IOBytes { get; private set; } public override bool CanRead => baseStream.CanRead; - public override bool CanSeek => baseStream.CanSeek; + public override bool CanSeek => false; public override bool CanWrite => baseStream.CanWrite; public override long Length => baseStream.Length; public override long Position { get => baseStream.Position; set => baseStream.Position = value; } diff --git a/TS3AudioBot/Helper/TextUtil.cs b/TS3AudioBot/Helper/TextUtil.cs index 0923c916..c3003a2e 100644 --- a/TS3AudioBot/Helper/TextUtil.cs +++ b/TS3AudioBot/Helper/TextUtil.cs @@ -7,13 +7,15 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Globalization; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; + namespace TS3AudioBot.Helper { - using System; - using System.Security.Cryptography; - using System.Text; - using System.Text.RegularExpressions; - public static class TextUtil { public static Answer GetAnswer(string answer) @@ -69,6 +71,76 @@ public static string GenToken(int length) return strb.ToString(); } } + + private static readonly Regex TimeReg = new Regex(@"^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?(?:(\d+)ms)?$", Util.DefaultRegexConfig); + + public static TimeSpan? ParseTime(string value) + { + if (value is null) return null; + return ParseTimeAsSimple(value) + ?? ParseTimeAsDigital(value) + ?? ParseTimeAsXml(value); + } + + private static TimeSpan? ParseTimeAsSimple(string value) + { + int AsNum(string svalue) + { + if (string.IsNullOrEmpty(svalue)) + return 0; + return int.TryParse(svalue, out var num) ? num : 0; + } + + var match = TimeReg.Match(value); + if (match.Success) + { + try + { + return new TimeSpan( + AsNum(match.Groups[1].Value), + AsNum(match.Groups[2].Value), + AsNum(match.Groups[3].Value), + AsNum(match.Groups[4].Value), + AsNum(match.Groups[5].Value)); + } + catch { } + } + return null; + } + + private static TimeSpan? ParseTimeAsDigital(string value) + { + if (value.Contains(":")) + { + string[] splittime = value.Split(':'); + + if (splittime.Length == 2 + && int.TryParse(splittime[0], out var minutes) + && double.TryParse(splittime[1], NumberStyles.Integer | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var seconds)) + { + return TimeSpan.FromSeconds(seconds) + TimeSpan.FromMinutes(minutes); + } + } + else + { + if (double.TryParse(value, NumberStyles.Integer | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var seconds)) + return TimeSpan.FromSeconds(seconds); + } + return null; + } + + private static TimeSpan? ParseTimeAsXml(string value) + { + try { return XmlConvert.ToTimeSpan(value); } + catch (FormatException) { return null; } + } + + public static E ValidateTime(string value) + { + if (ParseTime(value) != null) + return R.Ok; + return $"Value '{value}' is not a valid time."; + } } public enum Answer diff --git a/TS3AudioBot/Helper/TickPool.cs b/TS3AudioBot/Helper/TickPool.cs index dad29f98..2d3d676e 100644 --- a/TS3AudioBot/Helper/TickPool.cs +++ b/TS3AudioBot/Helper/TickPool.cs @@ -7,22 +7,27 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + namespace TS3AudioBot.Helper { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Threading; - public static class TickPool { - private static bool run = false; + private static bool run = true; private static readonly Thread tickThread = new Thread(Tick) { Name = "TickPool" }; private static readonly object tickLock = new object(); private static readonly TimeSpan MinTick = TimeSpan.FromMilliseconds(1000); private static readonly List workList = new List(); private static readonly AutoResetEvent tickLoopPulse = new AutoResetEvent(false); + static TickPool() + { + tickThread.Start(); + } + public static TickWorker RegisterTickOnce(Action method, TimeSpan? delay = null) { if (method is null) throw new ArgumentNullException(nameof(method)); @@ -45,13 +50,11 @@ private static void AddWorker(TickWorker worker) { lock (tickLock) { + if (!run) + return; + workList.Add(worker); worker.Timer.Start(); - if (!run) - { - run = true; - tickThread.Start(); - } } } diff --git a/TS3AudioBot/Helper/TomlTools.cs b/TS3AudioBot/Helper/TomlTools.cs index 7ae0aeeb..d35e0860 100644 --- a/TS3AudioBot/Helper/TomlTools.cs +++ b/TS3AudioBot/Helper/TomlTools.cs @@ -7,22 +7,18 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + namespace TS3AudioBot.Helper { - using Nett; - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using System.Xml; - public static class TomlTools { - private static readonly Regex TimeReg = new Regex(@"^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?(?:(\d+)ms)?$", Util.DefaultRegexConfig); - // *** Convenience method for getting values out of a toml object. *** public static T[] TryGetValueArray(this TomlObject tomlObj) @@ -125,7 +121,7 @@ public static bool TryGetValue(this TomlObject tomlObj, out T value) { try { - value = (T)(object)ParseTime(((TomlString)tomlObj).Value); + value = (T)(object)TextUtil.ParseTime(((TomlString)tomlObj).Value); return true; } catch (FormatException) { } @@ -136,47 +132,12 @@ public static bool TryGetValue(this TomlObject tomlObj, out T value) return false; } - public static TimeSpan? ParseTime(string value) - { - int AsNum(string svalue) - { - if (string.IsNullOrEmpty(svalue)) - return 0; - return int.TryParse(svalue, out var num) ? num : 0; - } - - var match = TimeReg.Match(value); - if (match.Success) - { - try - { - return new TimeSpan( - AsNum(match.Groups[1].Value), - AsNum(match.Groups[2].Value), - AsNum(match.Groups[3].Value), - AsNum(match.Groups[4].Value), - AsNum(match.Groups[5].Value)); - } - catch { } - } - - try { return XmlConvert.ToTimeSpan(value); } - catch (FormatException) { } - - return null; - } - - public static E ValidateTime(string value) - { - if (TimeReg.IsMatch(value)) - return R.Ok; - return $"Value '{value}' is not a valid time."; - } - public static string SerializeTime(TimeSpan time) { + if (time.TotalMilliseconds < 1) + return "0s"; var strb = new StringBuilder(); - if (time.TotalDays > 1) + if (time.TotalDays >= 1) { strb.Append(time.TotalDays.ToString("F0")).Append('d'); time -= TimeSpan.FromDays(time.Days); @@ -465,7 +426,7 @@ public static string DumpToJson(this TomlObject obj) var sw = new StringWriter(sb); using (var writer = new JsonTextWriter(sw)) { - writer.Formatting = Newtonsoft.Json.Formatting.Indented; + writer.Formatting = Formatting.Indented; DumpToJson(obj, writer); } return sb.ToString(); diff --git a/TS3AudioBot/Helper/Util.cs b/TS3AudioBot/Helper/Util.cs index 59d7cfaa..cedbdcf5 100644 --- a/TS3AudioBot/Helper/Util.cs +++ b/TS3AudioBot/Helper/Util.cs @@ -7,57 +7,27 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using TS3AudioBot.CommandSystem; +using TS3AudioBot.Localization; + namespace TS3AudioBot.Helper { - using CommandSystem; - using Localization; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Text; - using System.Text.RegularExpressions; - public static class Util { public const RegexOptions DefaultRegexConfig = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ECMAScript; private static readonly Regex SafeFileNameMatcher = new Regex(@"^[\w-_]+$", DefaultRegexConfig); - public static DateTime GetNow() => DateTime.Now; - - public static readonly DateTime UnixTimeStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - - public static uint ToUnix(this DateTime dateTime) => (uint)(dateTime - UnixTimeStart).TotalSeconds; - - public static void Init(out T obj) where T : new() => obj = new T(); - - public static Random Random { get; } = new Random(); - - public static Encoding Utf8Encoder { get; } = new UTF8Encoding(false, false); - - public static int MathMod(int x, int mod) => ((x % mod) + mod) % mod; - - private static long Pow(long b, int pow) - { - long ret = 1; - while (pow != 0) - { - if ((pow & 1) == 1) - ret *= b; - b *= b; - pow >>= 1; - } - return ret; - } - - public static float Clamp(float value, float min, float max) => Math.Min(Math.Max(value, min), max); - public static int Clamp(int value, int min, int max) => Math.Min(Math.Max(value, min), max); - private static readonly string[] byteSuffix = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; public static string FormatBytesHumanReadable(long bytes) @@ -103,6 +73,19 @@ public static int ToSeed(string seed) return unchecked((int)uval); } + private static long Pow(long b, int pow) + { + long ret = 1; + while (pow != 0) + { + if ((pow & 1) == 1) + ret *= b; + b *= b; + pow >>= 1; + } + return ret; + } + public static void UnwrapThrow(this E r) { if (!r.Ok) @@ -135,28 +118,29 @@ public static string UnrollException(this Exception ex) return strb.ToString(); } - public static Exception UnhandledDefault(T value) where T : struct { return new MissingEnumCaseException(typeof(T).Name, value.ToString()); } - public static Stream GetEmbeddedFile(string name) { var assembly = Assembly.GetExecutingAssembly(); return assembly.GetManifestResourceStream(name); } - public static R TryCast(this JToken token, string key) + public static bool TryCast(this JToken token, string key, out T value) { + value = default; if (token is null) - return R.Err; - var value = token.SelectToken(key); - if (value is null) - return R.Err; - try { - var t = value.ToObject(); + return false; + var jValue = token.SelectToken(key); + if (jValue is null) + return false; + try + { + var t = jValue.ToObject(); if ((object)t is null) - return R.Err; - return t; + return false; + value = t; + return true; } - catch (JsonReaderException) { return R.Err; } + catch (JsonReaderException) { return false; } } public static E IsSafeFileName(string name) @@ -179,12 +163,14 @@ public static bool HasExitedSafe(this Process process) catch { return true; } } - internal static void SetLogId(string id) => NLog.MappedDiagnosticsContext.Set("BotId", id); - } - - public class MissingEnumCaseException : Exception - { - public MissingEnumCaseException(string enumTypeName, string valueName) : base($"The switch does not handle the value \"{valueName}\" from \"{enumTypeName}\".") { } - public MissingEnumCaseException(string message, Exception inner) : base(message, inner) { } + public static V GetOrNew(this IDictionary dict, K key) where V : new() + { + if (!dict.TryGetValue(key, out var val)) + { + val = new V(); + dict[key] = val; + } + return val; + } } } diff --git a/TS3AudioBot/Helper/WebWrapper.cs b/TS3AudioBot/Helper/WebWrapper.cs index da1d72ba..20aefec0 100644 --- a/TS3AudioBot/Helper/WebWrapper.cs +++ b/TS3AudioBot/Helper/WebWrapper.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.IO; +using System.Net; +using TS3AudioBot.Localization; + namespace TS3AudioBot.Helper { - using Localization; - using System; - using System.IO; - using System.Net; - public static class WebWrapper { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); @@ -88,7 +88,7 @@ public static R GetResponse(Uri link, Func body, using (var response = request.GetResponse()) { var result = body.Invoke(response); - if (result == null) + if ((object)result is null) return new LocalStr(strings.error_net_unknown); return result; } @@ -99,9 +99,14 @@ public static R GetResponse(Uri link, Func body, } } + public static E GetResponseLoc(Uri link, Func> body) + => GetResponse(link, body).Flat(); + public static E GetResponseLoc(Uri link, Func> body, TimeSpan timeout) + => GetResponse(link, body, timeout).Flat(); + public static R GetResponseUnsafe(string link) { - if(!Uri.TryCreate(link, UriKind.RelativeOrAbsolute, out var uri)) + if (!Uri.TryCreate(link, UriKind.RelativeOrAbsolute, out var uri)) return new LocalStr(strings.error_media_invalid_uri); return GetResponseUnsafe(uri, DefaultTimeout); diff --git a/TS3AudioBot/History/AudioLogEntry.cs b/TS3AudioBot/History/AudioLogEntry.cs index 1242ea36..31a29d90 100644 --- a/TS3AudioBot/History/AudioLogEntry.cs +++ b/TS3AudioBot/History/AudioLogEntry.cs @@ -7,13 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Globalization; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.ResourceFactories; + namespace TS3AudioBot.History { - using ResourceFactories; - using System; - using System.Globalization; - - public class AudioLogEntry + public class AudioLogEntry : IAudioResourceResult { /// A unique id for each , given by the history system. public int Id { get; set; } diff --git a/TS3AudioBot/History/HistoryManager.cs b/TS3AudioBot/History/HistoryManager.cs index 5a6f8616..cac5973d 100644 --- a/TS3AudioBot/History/HistoryManager.cs +++ b/TS3AudioBot/History/HistoryManager.cs @@ -7,17 +7,18 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using LiteDB; +using System; +using System.Collections.Generic; +using System.Linq; +using TS3AudioBot.Config; +using TS3AudioBot.Localization; +using TS3AudioBot.ResourceFactories; +using TSLib; +using TSLib.Helper; + namespace TS3AudioBot.History { - using Config; - using Helper; - using LiteDB; - using Localization; - using ResourceFactories; - using System; - using System.Collections.Generic; - using System.Linq; - /// Stores all played songs. Can be used to search and restore played songs. public sealed class HistoryManager { @@ -27,7 +28,7 @@ public sealed class HistoryManager private const string ResourceTitleQueryColumn = "lowTitle"; private LiteCollection audioLogEntries; - private readonly LinkedList unusedIds; + private readonly LinkedList unusedIds = new LinkedList(); private readonly object dbLock = new object(); private readonly ConfHistory config; private readonly DbStore database; @@ -45,7 +46,6 @@ public HistoryManager(ConfHistory config, DbStore database) { Formatter = new SmartHistoryFormatter(); - Util.Init(out unusedIds); this.config = config; this.database = database; @@ -146,7 +146,7 @@ private void LogEntryPlay(AudioLogEntry ale) throw new ArgumentNullException(nameof(ale)); // update the playtime - ale.Timestamp = Util.GetNow(); + ale.Timestamp = Tools.Now; // update the playcount ale.PlayCount++; @@ -171,8 +171,8 @@ private R CreateLogEntry(HistorySaveData saveData) var ale = new AudioLogEntry(nextHid, saveData.Resource) { - UserUid = saveData.InvokerUid, - Timestamp = Util.GetNow(), + UserUid = saveData.InvokerUid.Value, + Timestamp = Tools.Now, PlayCount = 1, }; @@ -263,7 +263,7 @@ public void RenameEntry(AudioLogEntry ale, string newName) audioLogEntries.Update(ale); } - public void RemoveBrokenLinks(ResourceFactory resourceFactory) + public void RemoveBrokenLinks(ResolveContext resourceFactory) { const int iterations = 3; var currentIter = audioLogEntries.FindAll().ToList(); @@ -289,7 +289,7 @@ public void RemoveBrokenLinks(ResourceFactory resourceFactory) /// /// The list to iterate. /// A new list with all working items. - private List FilterList(ResourceFactory resourceFactory, IReadOnlyCollection list) + private List FilterList(ResolveContext resourceFactory, IReadOnlyCollection list) { int userNotifyCnt = 0; var nextIter = new List(list.Count); @@ -311,7 +311,7 @@ private List FilterList(ResourceFactory resourceFactory, IReadOnl public void UpdadeDbIdToUid(Ts3Client ts3Client) { var upgradedEntries = new List(); - var dbIdCache = new Dictionary(); + var dbIdCache = new Dictionary(); foreach (var audioLogEntry in audioLogEntries.FindAll()) { @@ -328,8 +328,8 @@ public void UpdadeDbIdToUid(Ts3Client ts3Client) if (!dbIdCache.TryGetValue(audioLogEntry.UserInvokeId.Value, out var data)) { - var result = ts3Client.GetDbClientByDbId(audioLogEntry.UserInvokeId.Value); - data.uid = (data.valid = result.Ok) ? result.Value.Uid : null; + var result = ts3Client.GetDbClientByDbId((ClientDbId)audioLogEntry.UserInvokeId.Value); + data.uid = (data.valid = result.Ok) ? result.Value.Uid : Uid.Null; if (!data.valid) { Log.Warn("Client DbId {0} could not be found.", audioLogEntry.UserInvokeId.Value); @@ -341,7 +341,7 @@ public void UpdadeDbIdToUid(Ts3Client ts3Client) continue; audioLogEntry.UserInvokeId = null; - audioLogEntry.UserUid = data.uid; + audioLogEntry.UserUid = data.uid.Value; upgradedEntries.Add(audioLogEntry); #pragma warning restore CS0612 } diff --git a/TS3AudioBot/History/HistorySaveData.cs b/TS3AudioBot/History/HistorySaveData.cs index 58bf1326..3712dc0b 100644 --- a/TS3AudioBot/History/HistorySaveData.cs +++ b/TS3AudioBot/History/HistorySaveData.cs @@ -7,17 +7,18 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TS3AudioBot.ResourceFactories; +using TSLib; + namespace TS3AudioBot.History { - using System; - using ResourceFactories; - public class HistorySaveData { public AudioResource Resource { get; } - public string InvokerUid { get; } + public Uid InvokerUid { get; } - public HistorySaveData(AudioResource resource, string invokerUid) + public HistorySaveData(AudioResource resource, Uid invokerUid) { Resource = resource ?? throw new ArgumentNullException(nameof(resource)); InvokerUid = invokerUid; diff --git a/TS3AudioBot/History/IHistoryFormatter.cs b/TS3AudioBot/History/IHistoryFormatter.cs index a37b89f3..58fb0e35 100644 --- a/TS3AudioBot/History/IHistoryFormatter.cs +++ b/TS3AudioBot/History/IHistoryFormatter.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; + namespace TS3AudioBot.History { - using System; - using System.Collections.Generic; - public interface IHistoryFormatter { string ProcessQuery(AudioLogEntry entry, Func format); @@ -26,4 +26,4 @@ public enum HistoryDisplayColumn UserName, AleTitle, } -} \ No newline at end of file +} diff --git a/TS3AudioBot/History/SearchQuery.cs b/TS3AudioBot/History/SearchQuery.cs index efcd5d8f..13652f21 100644 --- a/TS3AudioBot/History/SearchQuery.cs +++ b/TS3AudioBot/History/SearchQuery.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.History { - using System; - public class SeachQuery { public string TitlePart { get; set; } diff --git a/TS3AudioBot/History/SmartHistoryFormatter.cs b/TS3AudioBot/History/SmartHistoryFormatter.cs index 01846170..e9315d31 100644 --- a/TS3AudioBot/History/SmartHistoryFormatter.cs +++ b/TS3AudioBot/History/SmartHistoryFormatter.cs @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TSLib.Commands; + namespace TS3AudioBot.History { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using TS3Client.Commands; - public class SmartHistoryFormatter : IHistoryFormatter { // configurable constants @@ -22,12 +22,12 @@ public class SmartHistoryFormatter : IHistoryFormatter private const int MinTokenLine = 40; private readonly bool fairDistribute = true; // resulting constants from configuration - private static readonly int LineBreakLen = Ts3String.TokenLength(LineBreak); + private static readonly int LineBreakLen = TsString.TokenLength(LineBreak); private static readonly int UseableTokenLine = MinTokenLine - LineBreakLen; public string ProcessQuery(AudioLogEntry entry, Func format) { - return SubstringToken(format(entry), Ts3Const.MaxSizeTextMessage); + return SubstringToken(format(entry), TsConst.MaxSizeTextMessage); } public string ProcessQuery(IEnumerable entries, Func format) @@ -36,7 +36,7 @@ public string ProcessQuery(IEnumerable entries, Func { string finStr = format(e); - return new Line { Value = finStr, TokenLength = Ts3String.TokenLength(finStr) }; + return new Line { Value = finStr, TokenLength = TsString.TokenLength(finStr) }; }); //! entryLines[n] is the most recent entry @@ -46,7 +46,7 @@ public string ProcessQuery(IEnumerable entries, Func entries, Func entries, Func= 0; i--) { var eL = useList[i]; @@ -140,7 +140,7 @@ private static string SubstringToken(string value, int token) int tokens = 0; for (int i = 0; i < value.Length; i++) { - int addToken = Ts3String.IsDoubleChar(value[i]) ? 2 : 1; + int addToken = TsString.IsDoubleChar(value[i]) ? 2 : 1; if (tokens + addToken > token) return value.Substring(0, i); else tokens += addToken; } diff --git a/TS3AudioBot/InvokerData.cs b/TS3AudioBot/InvokerData.cs index befcf252..adeee0f3 100644 --- a/TS3AudioBot/InvokerData.cs +++ b/TS3AudioBot/InvokerData.cs @@ -7,21 +7,21 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using TSLib; + namespace TS3AudioBot { - using System; - public class InvokerData { - public string ClientUid { get; } + public Uid ClientUid { get; } public bool IsAnonymous => ClientUid == AnonymousUid; - protected const string AnonymousUid = "Anonymous"; + protected static readonly Uid AnonymousUid = (Uid)"Anonymous"; public static readonly InvokerData Anonymous = new InvokerData(AnonymousUid); - public InvokerData(string clientUid) + public InvokerData(Uid clientUid) { - ClientUid = clientUid ?? throw new ArgumentNullException(nameof(clientUid)); + ClientUid = clientUid; } } } diff --git a/TS3AudioBot/Limits.cs b/TS3AudioBot/Limits.cs new file mode 100644 index 00000000..b4b7fb33 --- /dev/null +++ b/TS3AudioBot/Limits.cs @@ -0,0 +1,19 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +namespace TS3AudioBot +{ + public static class Limits + { + /// Max stream size to download before aborting. + public static long MaxImageStreamSize { get; } = 10_000_000; + /// Max image size which is allowed to be resized from. + public static long MaxImageDimension { get; } = 10_000; + } +} diff --git a/TS3AudioBot/Localization/DynamicResourceManager.cs b/TS3AudioBot/Localization/DynamicResourceManager.cs index 4a09c181..4c9f25ed 100644 --- a/TS3AudioBot/Localization/DynamicResourceManager.cs +++ b/TS3AudioBot/Localization/DynamicResourceManager.cs @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Resources; +using System.Threading; + namespace TS3AudioBot.Localization { - using System.Collections.Generic; - using System.Globalization; - using System.Reflection; - using System.Resources; - using System.Threading; - internal class DynamicResourceManager : ResourceManager { private readonly Dictionary dynamicResourceSets = new Dictionary(); diff --git a/TS3AudioBot/Localization/LocalizationManager.cs b/TS3AudioBot/Localization/LocalizationManager.cs index fd3aba1d..e58cdb23 100644 --- a/TS3AudioBot/Localization/LocalizationManager.cs +++ b/TS3AudioBot/Localization/LocalizationManager.cs @@ -7,17 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Resources; +using System.Threading; +using TS3AudioBot.Helper; + namespace TS3AudioBot.Localization { - using Helper; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Reflection; - using System.Resources; - using System.Threading; - public static class LocalizationManager { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); @@ -45,8 +45,7 @@ public static E LoadLanguage(string lang, bool forceDownload) try { culture = CultureInfo.GetCultureInfo(lang); } catch (CultureNotFoundException) { return "Language not found"; } - if (!loadedLanguage.TryGetValue(culture.Name, out var languageDataInfo)) - loadedLanguage[culture.Name] = languageDataInfo = new LanguageData(); + var languageDataInfo = loadedLanguage.GetOrNew(culture.Name); if (!languageDataInfo.LoadedSuccessfully) { diff --git a/TS3AudioBot/Localization/strings.Designer.cs b/TS3AudioBot/Localization/strings.Designer.cs index 81904d37..4e793504 100644 --- a/TS3AudioBot/Localization/strings.Designer.cs +++ b/TS3AudioBot/Localization/strings.Designer.cs @@ -114,15 +114,6 @@ internal static string cmd_alias_show_help { } } - /// - /// Looks up a localized string similar to Generates an api nonce.. - /// - internal static string cmd_api_nonce_help { - get { - return ResourceManager.GetString("cmd_api_nonce_help", resourceCulture); - } - } - /// /// Looks up a localized string similar to Generates an api token.. /// @@ -2367,6 +2358,15 @@ internal static string error_ts_code_2568 { } } + /// + /// Looks up a localized string similar to Invalid channel password.. + /// + internal static string error_ts_code_781 { + get { + return ResourceManager.GetString("error_ts_code_781", resourceCulture); + } + } + /// /// Looks up a localized string similar to Teamspeak Error: {0}. /// @@ -2376,6 +2376,15 @@ internal static string error_ts_error { } } + /// + /// Looks up a localized string similar to The file to upload is too big.. + /// + internal static string error_ts_file_too_big { + get { + return ResourceManager.GetString("error_ts_file_too_big", resourceCulture); + } + } + /// /// Looks up a localized string similar to The new name is too long or invalid.. /// @@ -2539,7 +2548,7 @@ internal static string info_empty { } /// - /// Looks up a localized string similar to <number>. + /// Looks up a localized string similar to number. /// internal static string info_number { get { diff --git a/TS3AudioBot/Localization/strings.resx b/TS3AudioBot/Localization/strings.resx index 742eb0a9..31d41dbb 100644 --- a/TS3AudioBot/Localization/strings.resx +++ b/TS3AudioBot/Localization/strings.resx @@ -123,9 +123,6 @@ Generates an api token. - - Generates an api nonce. - Gets the status of the channel commander mode. @@ -973,9 +970,15 @@ with the id {1}? Your value is out of range. Select one between {0} and {1}. - <number> + number Selects a song from a previous search and plays it. + + The file to upload is too big. + + + Invalid channel password. + \ No newline at end of file diff --git a/TS3AudioBot/MainCommands.cs b/TS3AudioBot/MainCommands.cs index 16e29505..be9e148a 100644 --- a/TS3AudioBot/MainCommands.cs +++ b/TS3AudioBot/MainCommands.cs @@ -7,41 +7,43 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using TS3AudioBot.Algorithm; +using TS3AudioBot.Audio; +using TS3AudioBot.CommandSystem; +using TS3AudioBot.CommandSystem.Ast; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.CommandSystem.Commands; +using TS3AudioBot.CommandSystem.Text; +using TS3AudioBot.Config; +using TS3AudioBot.Dependency; +using TS3AudioBot.Environment; +using TS3AudioBot.Helper; +using TS3AudioBot.Helper.Diagnose; +using TS3AudioBot.History; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists; +using TS3AudioBot.Plugins; +using TS3AudioBot.ResourceFactories; +using TS3AudioBot.Rights; +using TS3AudioBot.Sessions; +using TS3AudioBot.Web.Api; +using TS3AudioBot.Web.Model; +using TSLib; +using TSLib.Audio; +using TSLib.Full.Book; +using TSLib.Helper; +using TSLib.Messages; +using static TS3AudioBot.CommandSystem.CommandSystemTypes; + namespace TS3AudioBot { - using Algorithm; - using Audio; - using CommandSystem; - using CommandSystem.Ast; - using CommandSystem.CommandResults; - using CommandSystem.Commands; - using CommandSystem.Text; - using Config; - using Dependency; - using Helper; - using Helper.Environment; - using History; - using Localization; - using Newtonsoft.Json.Linq; - using Playlists; - using Plugins; - using ResourceFactories; - using Rights; - using Sessions; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Threading; - using TS3AudioBot.Helper.Diagnose; - using TS3AudioBot.Web.Model; - using TS3Client; - using TS3Client.Audio; - using TS3Client.Full.Book; - using TS3Client.Messages; - using Web.Api; - public static class MainCommands { internal static ICommandBag Bag { get; } = new MainCommandsBag(); @@ -64,8 +66,12 @@ internal class MainCommandsBag : ICommandBag // ReSharper disable UnusedMember.Global [Command("add")] - public static void CommandAdd(PlayManager playManager, InvokerData invoker, string url) - => playManager.Enqueue(invoker, url).UnwrapThrow(); + public static void CommandAdd(PlayManager playManager, InvokerData invoker, string url, params string[] attributes) + => playManager.Enqueue(invoker, url, meta: PlayManager.ParseAttributes(attributes)).UnwrapThrow(); + + [Command("add")] + public static void CommandAdd(PlayManager playManager, InvokerData invoker, IAudioResourceResult rsc, params string[] attributes) + => playManager.Enqueue(invoker, rsc.AudioResource, meta: PlayManager.ParseAttributes(attributes)).UnwrapThrow(); [Command("alias add")] public static void CommandAliasAdd(CommandManager commandManager, ConfBot confBot, string commandName, string command) @@ -113,20 +119,7 @@ public static string CommandApiToken(TokenManager tokenManager, ClientCall invok { throw new CommandException(strings.error_invalid_token_duration, oex, CommandExceptionReason.CommandError); } - return tokenManager.GenerateToken(invoker.ClientUid, validSpan); - } - - [Command("api nonce")] - public static string CommandApiNonce(TokenManager tokenManager, ClientCall invoker) - { - if (invoker.Visibiliy.HasValue && invoker.Visibiliy != TextMessageTargetMode.Private) - throw new CommandException(strings.error_use_private, CommandExceptionReason.CommandError); - if (invoker.IsAnonymous) - throw new MissingContextCommandException(strings.error_no_uid_found, typeof(ClientCall)); - - var token = tokenManager.GetToken(invoker.ClientUid).UnwrapThrow(); - var nonce = token.CreateNonce(); - return nonce.Value; + return tokenManager.GenerateToken(invoker.ClientUid.Value, validSpan); } [Command("bot avatar set")] @@ -137,14 +130,14 @@ public static void CommandBotAvatarSet(Ts3Client ts3Client, string url) try { uri = new Uri(url); } catch (Exception ex) { throw new CommandException(strings.error_media_invalid_uri, ex, CommandExceptionReason.CommandError); } - WebWrapper.GetResponse(uri, x => + WebWrapper.GetResponseLoc(uri, x => { using (var stream = x.GetResponseStream()) - using (var image = ImageUtil.ResizeImage(stream)) { - if (image is null) - throw new CommandException(strings.error_media_internal_invalid, CommandExceptionReason.CommandError); - ts3Client.UploadAvatar(image).UnwrapThrow(); + var imageResult = ImageUtil.ResizeImageSave(stream, out _); + if (!imageResult.Ok) + return imageResult.Error; + return ts3Client.UploadAvatar(imageResult.Value); } }).UnwrapThrow(); } @@ -159,7 +152,7 @@ public static void CommandBotAvatarSet(Ts3Client ts3Client, string url) public static void CommandBotDescriptionSet(Ts3Client ts3Client, string description) => ts3Client.ChangeDescription(description).UnwrapThrow(); [Command("bot diagnose", "_undocumented")] - public static JsonArray CommandBotDiagnose(IPlayerConnection player, Connection book) + public static JsonArray CommandBotDiagnose(Player player, IVoiceTarget target, Connection book) { var problems = new List(); // ** Diagnose common playback problems and more ** @@ -173,11 +166,23 @@ public static JsonArray CommandBotDiagnose(IPlayerConnectio // Check volume 0 if (player.Volume == 0) - problems.Add(new SelfDiagnoseMessage { Description = "The volume level is a 0.", LevelValue = SelfDiagnoseLevel.Warning }); + problems.Add(new SelfDiagnoseMessage { Description = "The volume level is at 0.", LevelValue = SelfDiagnoseLevel.Warning }); + + // Check if send mode hasn't been selected yet + if (target.SendMode == TargetSendMode.None) + problems.Add(new SelfDiagnoseMessage { Description = "Send mode is currently 'None', use '!whisper off' for example to send via voice.", LevelValue = SelfDiagnoseLevel.Warning }); // ... more - return new JsonArray(problems, x => string.Join("\n", x.Select(problem => problem.Description))); + return new JsonArray(problems, x => + { + if (x.Count == 0) + return "No problems detected"; + var strb = new StringBuilder("The following issues have been found:"); + foreach (var prob in x) + strb.Append("\n- ").Append(prob.Description); + return strb.ToString(); + }); } [Command("bot disconnect")] @@ -198,9 +203,9 @@ public static JsonValue CommandBotCommander(Ts3Client ts3Client) public static void CommandBotCome(Ts3Client ts3Client, ClientCall invoker, string password = null) { var channel = invoker?.ChannelId; - if (!channel.HasValue) + if (channel == null) throw new CommandException(strings.error_no_target_channel, CommandExceptionReason.CommandError); - CommandBotMove(ts3Client, channel.Value, password); + ts3Client.MoveTo(channel.Value, password).UnwrapThrow(); } [Command("bot connect template")] @@ -257,7 +262,7 @@ public static JsonArray CommandBotList(BotManager bots, ConfRoot config } [Command("bot move")] - public static void CommandBotMove(Ts3Client ts3Client, ulong channel, string password = null) => ts3Client.MoveTo(channel, password).UnwrapThrow(); + public static void CommandBotMove(Ts3Client ts3Client, ulong channel, string password = null) => ts3Client.MoveTo((ChannelId)channel, password).UnwrapThrow(); [Command("bot name")] public static void CommandBotName(Ts3Client ts3Client, string name) => ts3Client.ChangeName(name).UnwrapThrow(); @@ -273,20 +278,20 @@ public static void CommandBotSetup(Ts3Client ts3Client, string adminToken = null } [Command("bot template", "cmd_bot_use_help")] - public static ICommandResult CommandBotTemplate(ExecutionInformation info, IReadOnlyList returnTypes, BotManager bots, string botName, ICommand cmd) + public static object CommandBotTemplate(ExecutionInformation info, IReadOnlyList returnTypes, BotManager bots, string botName, ICommand cmd) { using (var botLock = bots.GetBotLock(botName)) return CommandBotUseInternal(info, returnTypes, botLock, cmd); } [Command("bot use")] - public static ICommandResult CommandBotUse(ExecutionInformation info, IReadOnlyList returnTypes, BotManager bots, int botId, ICommand cmd) + public static object CommandBotUse(ExecutionInformation info, IReadOnlyList returnTypes, BotManager bots, int botId, ICommand cmd) { using (var botLock = bots.GetBotLock(botId)) return CommandBotUseInternal(info, returnTypes, botLock, cmd); } - private static ICommandResult CommandBotUseInternal(ExecutionInformation info, IReadOnlyList returnTypes, BotLock botLock, ICommand cmd) + private static object CommandBotUseInternal(ExecutionInformation info, IReadOnlyList returnTypes, BotLock botLock, ICommand cmd) { if (botLock is null) throw new CommandException(strings.error_bot_does_not_exist, CommandExceptionReason.CommandError); @@ -322,64 +327,103 @@ public static JsonValue CommandParse(string parameter) [Command("command tree", "_undocumented")] public static string CommandTree(CommandManager commandManager) { - return XCommandSystem.GetTree(commandManager.CommandSystem.RootCommand); + return CommandManager.GetTree(commandManager.RootGroup); } + [Command("data song cover get", "_undocumented")] + public static DataStream CommandData(ResolveContext resourceFactory, PlayManager playManager) => + new DataStream(response => + { + var cur = playManager.CurrentPlayData; + if (cur == null) + return false; + if (resourceFactory.GetThumbnail(cur.PlayResource).GetOk(out var stream) + && ImageUtil.ResizeImageSave(stream, out var mime).GetOk(out var image)) + { + using (image) + { + response.ContentType = mime; + image.CopyTo(response.Body); + return true; + } + } + return false; + }); + [Command("eval")] [Usage(" ", "Executes the given command on arguments")] [Usage("", "Concat the strings and execute them with the command system")] - public static ICommandResult CommandEval(ExecutionInformation info, CommandManager commandManager, IReadOnlyList arguments, IReadOnlyList returnTypes) + public static object CommandEval(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { // Evaluate the first argument on the rest of the arguments if (arguments.Count == 0) throw new CommandException(strings.error_cmd_at_least_one_argument, CommandExceptionReason.MissingParameter); var leftArguments = arguments.TrySegment(1); - var arg0 = arguments[0].Execute(info, Array.Empty(), XCommandSystem.ReturnCommandOrString); - if (arg0.ResultType == CommandResultType.Command) - return ((CommandCommandResult)arg0).Command.Execute(info, leftArguments, returnTypes); + var arg0 = arguments[0].Execute(info, Array.Empty(), ReturnCommandOrString); + if (arg0 is ICommand cmd) + return cmd.Execute(info, leftArguments, returnTypes); // We got a string back so parse and evaluate it - var args = ((StringCommandResult)arg0).Content; + var args = ((IPrimitiveResult)arg0).Get(); - var cmd = commandManager.CommandSystem.AstToCommandResult(CommandParser.ParseCommandRequest(args)); + cmd = CommandManager.AstToCommandResult(CommandParser.ParseCommandRequest(args)); return cmd.Execute(info, leftArguments, returnTypes); } + [Command("from", "_undocumented")] + public static void CommandFrom(PlayManager playManager, InvokerData invoker, string factoryName, string url) + { + playManager.Play(invoker, url, factoryName).UnwrapThrow(); + } + + [Command("get", "_undocumented")] + [Usage(" ", "Get an element out of a list")] + public static object CommandGet(uint index, System.Collections.IEnumerable list) + { + foreach (var i in list) + { + if (index == 0) + return i; + index--; + } + return null; + } + [Command("getmy id")] public static ushort CommandGetId(ClientCall invoker) - => invoker.ClientId ?? throw new CommandException(strings.error_not_found, CommandExceptionReason.CommandError); + => invoker.ClientId?.Value ?? throw new CommandException(strings.error_not_found, CommandExceptionReason.CommandError); [Command("getmy uid")] public static string CommandGetUid(ClientCall invoker) - => invoker.ClientUid ?? throw new CommandException(strings.error_not_found, CommandExceptionReason.CommandError); + => invoker.ClientUid.Value; [Command("getmy name")] public static string CommandGetName(ClientCall invoker) => invoker.NickName ?? throw new CommandException(strings.error_not_found, CommandExceptionReason.CommandError); [Command("getmy dbid")] public static ulong CommandGetDbId(ClientCall invoker) - => invoker.DatabaseId ?? throw new CommandException(strings.error_not_found, CommandExceptionReason.CommandError); + => invoker.DatabaseId?.Value ?? throw new CommandException(strings.error_not_found, CommandExceptionReason.CommandError); [Command("getmy channel")] public static ulong CommandGetChannel(ClientCall invoker) - => invoker.ChannelId ?? throw new CommandException(strings.error_not_found, CommandExceptionReason.CommandError); + => invoker.ChannelId?.Value ?? throw new CommandException(strings.error_not_found, CommandExceptionReason.CommandError); [Command("getmy all")] public static JsonValue CommandGetUser(ClientCall invoker) => new JsonValue(invoker, $"Client: Id:{invoker.ClientId} DbId:{invoker.DatabaseId} ChanId:{invoker.ChannelId} Uid:{invoker.ClientUid}"); // LOC: TODO [Command("getuser uid byid")] - public static string CommandGetUidById(Ts3Client ts3Client, ushort id) => ts3Client.GetFallbackedClientById(id).UnwrapThrow().Uid; + public static string CommandGetUidById(Ts3Client ts3Client, ushort id) => ts3Client.GetFallbackedClientById((ClientId)id).UnwrapThrow().Uid.Value; [Command("getuser name byid")] - public static string CommandGetNameById(Ts3Client ts3Client, ushort id) => ts3Client.GetFallbackedClientById(id).UnwrapThrow().Name; + public static string CommandGetNameById(Ts3Client ts3Client, ushort id) => ts3Client.GetFallbackedClientById((ClientId)id).UnwrapThrow().Name; [Command("getuser dbid byid")] - public static ulong CommandGetDbIdById(Ts3Client ts3Client, ushort id) => ts3Client.GetFallbackedClientById(id).UnwrapThrow().DatabaseId; + public static ulong CommandGetDbIdById(Ts3Client ts3Client, ushort id) => ts3Client.GetFallbackedClientById((ClientId)id).UnwrapThrow().DatabaseId.Value; [Command("getuser channel byid")] - public static ulong CommandGetChannelById(Ts3Client ts3Client, ushort id) => ts3Client.GetFallbackedClientById(id).UnwrapThrow().ChannelId; + public static ulong CommandGetChannelById(Ts3Client ts3Client, ushort id) => ts3Client.GetFallbackedClientById((ClientId)id).UnwrapThrow().ChannelId.Value; [Command("getuser all byid")] public static JsonValue CommandGetUserById(Ts3Client ts3Client, ushort id) { - var client = ts3Client.GetFallbackedClientById(id).UnwrapThrow(); + var client = ts3Client.GetFallbackedClientById((ClientId)id).UnwrapThrow(); return new JsonValue(client, $"Client: Id:{client.ClientId} DbId:{client.DatabaseId} ChanId:{client.ChannelId} Uid:{client.Uid}"); } [Command("getuser id byname")] - public static ushort CommandGetIdByName(Ts3Client ts3Client, string username) => ts3Client.GetClientByName(username).UnwrapThrow().ClientId; + public static ushort CommandGetIdByName(Ts3Client ts3Client, string username) => ts3Client.GetClientByName(username).UnwrapThrow().ClientId.Value; [Command("getuser all byname")] public static JsonValue CommandGetUserByName(Ts3Client ts3Client, string username) { @@ -387,9 +431,9 @@ public static JsonValue CommandGetUserByName(Ts3Client ts3Client, st return new JsonValue(client, $"Client: Id:{client.ClientId} DbId:{client.DatabaseId} ChanId:{client.ChannelId} Uid:{client.Uid}"); } [Command("getuser name bydbid")] - public static string CommandGetNameByDbId(Ts3Client ts3Client, ulong dbId) => ts3Client.GetDbClientByDbId(dbId).UnwrapThrow().Name; + public static string CommandGetNameByDbId(Ts3Client ts3Client, ulong dbId) => ts3Client.GetDbClientByDbId((ClientDbId)dbId).UnwrapThrow().Name; [Command("getuser uid bydbid")] - public static string CommandGetUidByDbId(Ts3Client ts3Client, ulong dbId) => ts3Client.GetDbClientByDbId(dbId).UnwrapThrow().Uid; + public static string CommandGetUidByDbId(Ts3Client ts3Client, ulong dbId) => ts3Client.GetDbClientByDbId((ClientDbId)dbId).UnwrapThrow().Uid.Value; private static readonly TextMod HelpCommand = new TextMod(TextModFlag.Bold); private static readonly TextMod HelpCommandParam = new TextMod(TextModFlag.Italic); @@ -411,9 +455,9 @@ public static string CommandHelp(CallerInfo callerInfo) } [Command("help all", "_undocumented")] - public static JsonObject CommandHelpAll(CommandManager commandManager) + public static JsonArray CommandHelpAll(CommandManager commandManager) { - var botComList = commandManager.AllCommands.Select(c => c.InvokeName).OrderBy(x => x).GroupBy(n => n.Split(' ')[0]).Select(x => x.Key).ToArray(); + var botComList = commandManager.RootGroup.Commands.Select(c => c.Key).ToArray(); return new JsonArray(botComList, bcl => { var strb = new StringBuilder(); @@ -432,9 +476,10 @@ public static JsonObject CommandHelpCommand(CommandManager commandManager, IFilt return new JsonEmpty(strings.error_cmd_at_least_one_argument); } - CommandGroup group = commandManager.CommandSystem.RootCommand; + CommandGroup group = commandManager.RootGroup; ICommand target = group; filter = filter ?? Filter.DefaultFilter; + var realPath = new List(); for (int i = 0; i < command.Length; i++) { var possibilities = filter.Filter(group.Commands, command[i]).ToList(); @@ -443,19 +488,21 @@ public static JsonObject CommandHelpCommand(CommandManager commandManager, IFilt if (possibilities.Count > 1) throw new CommandException(string.Format(strings.cmd_help_error_ambiguous_command, string.Join(", ", possibilities.Select(kvp => kvp.Key))), CommandExceptionReason.CommandError); + realPath.Add(possibilities[0].Key); target = possibilities[0].Value; + if (i < command.Length - 1) { group = target as CommandGroup; if (group is null) - throw new CommandException(string.Format(strings.cmd_help_error_no_further_subfunctions, string.Join(" ", command, 0, i)), CommandExceptionReason.CommandError); + throw new CommandException(string.Format(strings.cmd_help_error_no_further_subfunctions, string.Join(" ", realPath, 0, i)), CommandExceptionReason.CommandError); } } switch (target) { case BotCommand targetB: - return new JsonValue(targetB.AsJsonObj); + return new JsonValue(targetB); case CommandGroup targetCg: var subList = targetCg.Commands.Select(g => g.Key).ToArray(); return new JsonArray(subList, string.Format(strings.cmd_help_info_contains_subfunctions, string.Join(", ", subList))); @@ -464,6 +511,8 @@ public static JsonObject CommandHelpCommand(CommandManager commandManager, IFilt foreach (var botCom in targetOfc.Functions.OfType()) strb.Append(botCom); return new JsonValue(strb.ToString()); + case AliasCommand targetAlias: + return new JsonValue(string.Format("'{0}' is an alias for:\n{1}", string.Join(" ", realPath), targetAlias.AliasString)); default: throw new CommandException(strings.cmd_help_error_unknown_error, CommandExceptionReason.CommandError); } @@ -505,7 +554,7 @@ string ResponseHistoryClean(string message) } [Command("history clean removedefective")] - public static JsonEmpty CommandHistoryCleanRemove(HistoryManager historyManager, ResourceFactory resourceFactory, CallerInfo caller, UserSession session = null) + public static JsonEmpty CommandHistoryCleanRemove(HistoryManager historyManager, ResolveContext resourceFactory, CallerInfo caller, UserSession session = null) { if (caller.ApiCall) { @@ -660,14 +709,8 @@ public static JsonArray CommandHistoryTitle(HistoryManager histor [Command("if")] [Usage(" ", "Compares the two arguments and returns or executes the then-argument")] [Usage(" ", "Same as before and return the else-arguments if the condition is false")] - public static ICommandResult CommandIf(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public static object CommandIf(ExecutionInformation info, IReadOnlyList returnTypes, string arg0, string cmp, string arg1, ICommand then, ICommand other = null) { - if (arguments.Count < 4) - throw new CommandException(strings.error_cmd_at_least_four_argument, CommandExceptionReason.MissingParameter); - var arg0 = ((StringCommandResult)arguments[0].Execute(info, Array.Empty(), XCommandSystem.ReturnString)).Content; - var cmp = ((StringCommandResult)arguments[1].Execute(info, Array.Empty(), XCommandSystem.ReturnString)).Content; - var arg1 = ((StringCommandResult)arguments[2].Execute(info, Array.Empty(), XCommandSystem.ReturnString)).Content; - Func comparer; switch (cmp) { @@ -694,14 +737,14 @@ public static ICommandResult CommandIf(ExecutionInformation info, IReadOnlyList< // If branch if (cmpResult) - return arguments[3].Execute(info, Array.Empty(), returnTypes); + return then.Execute(info, Array.Empty(), returnTypes); // Else branch - if (arguments.Count > 4) - return arguments[4].Execute(info, Array.Empty(), returnTypes); + if (other != null) + return other.Execute(info, Array.Empty(), returnTypes); // Try to return nothing - if (returnTypes.Contains(CommandResultType.Empty)) - return EmptyCommandResult.Instance; + if (returnTypes.Contains(null)) + return null; throw new CommandException(strings.error_nothing_to_return, CommandExceptionReason.NoReturnMatch); } @@ -732,17 +775,17 @@ private static int GetIndexExpression(PlaylistManager playlistManager, string ex } [Command("info")] - public static JsonValue CommandInfo(PlayManager playManager, ResourceFactory resourceFactory, PlaylistManager playlistManager, string offset = null, int? count = null) - => CommandInfo(playManager, resourceFactory, playlistManager, GetIndexExpression(playlistManager, offset ?? "@-1"), count); + public static JsonValue CommandInfo(ResolveContext resourceFactory, PlaylistManager playlistManager, string offset = null, int? count = null) + => CommandInfo(resourceFactory, playlistManager, GetIndexExpression(playlistManager, offset ?? "@-1"), count); [Command("info")] - public static JsonValue CommandInfo(PlayManager playManager, ResourceFactory resourceFactory, PlaylistManager playlistManager, int offset, int? count = null) + public static JsonValue CommandInfo(ResolveContext resourceFactory, PlaylistManager playlistManager, int offset, int? count = null) { const int maxSongs = 20; var playIndex = playlistManager.Index; var plist = playlistManager.CurrentList; - int offsetV = Util.Clamp(offset, 0, plist.Items.Count); - int countV = Util.Clamp(count ?? 3, 0, Math.Min(maxSongs, plist.Items.Count - offsetV)); + int offsetV = Tools.Clamp(offset, 0, plist.Items.Count); + int countV = Tools.Clamp(count ?? 3, 0, Math.Min(maxSongs, plist.Items.Count - offsetV)); var items = plist.Items.Skip(offsetV).Take(countV).Select(x => resourceFactory.ToApiFormat(x)).ToArray(); var plInfo = new QueueInfo @@ -793,11 +836,11 @@ public static JsonArray CommandJsonMerge(ExecutionInformation info, ApiC var jsonArr = arguments .Select(arg => { - ICommandResult res; - try { res = arg.Execute(info, Array.Empty(), XCommandSystem.ReturnJson); } + object res; + try { res = arg.Execute(info, Array.Empty(), ReturnJson); } catch (CommandException) { return null; } - if (res.ResultType == CommandResultType.Json) - return ((JsonCommandResult)res).JsonObject.GetSerializeObject(); + if (res is JsonObject o) + return o.GetSerializeObject(); else throw new CommandException(strings.error_nothing_to_return, CommandExceptionReason.NoReturnMatch); }) @@ -835,13 +878,13 @@ private static void CommandKickme(Ts3Client ts3Client, ClientCall invoker, bool } [Command("list add")] - public static JsonValue CommandListAddInternal(ResourceFactory resourceFactory, InvokerData invoker, PlaylistManager playlistManager, string listId, string link /* TODO param */) + public static JsonValue CommandListAddInternal(ResolveContext resourceFactory, PlaylistManager playlistManager, string listId, string link /* TODO param */) { PlaylistItemGetData getData = null; playlistManager.ModifyPlaylist(listId, plist => { var playResource = resourceFactory.Load(link).UnwrapThrow(); - var item = new PlaylistItem(playResource.BaseData, new MetaData { ResourceOwnerUid = invoker.ClientUid }); + var item = new PlaylistItem(playResource.BaseData); plist.Add(item).UnwrapThrow(); getData = resourceFactory.ToApiFormat(item); //getData.Index = plist.Items.Count - 1; @@ -873,24 +916,35 @@ string ResponseListDelete(string message) public static void CommandListDelete(PlaylistManager playlistManager, ApiCall _, string listId) => playlistManager.DeletePlaylist(listId).UnwrapThrow(); + [Command("list from", "_undocumented")] + public static JsonValue PropagiateLoad(PlaylistManager playlistManager, ResolveContext resolver, string resolverName, string listId, string url) + { + var getList = resolver.LoadPlaylistFrom(url, resolverName).UnwrapThrow(); + return ImportMerge(playlistManager, resolver, getList, listId); + } + [Command("list import", "cmd_list_get_help")] // TODO readjust help texts - public static JsonValue CommandListImport(PlaylistManager playlistManager, ResourceFactory resourceFactory, string listId, string link) + public static JsonValue CommandListImport(PlaylistManager playlistManager, ResolveContext resolver, string listId, string link) { - var getList = resourceFactory.LoadPlaylistFrom(link).UnwrapThrow(); + var getList = resolver.LoadPlaylistFrom(link).UnwrapThrow(); + return ImportMerge(playlistManager, resolver, getList, listId); ; + } + private static JsonValue ImportMerge(PlaylistManager playlistManager, ResolveContext resolver, Playlist addList, string listId) + { if (!playlistManager.ExistsPlaylist(listId)) playlistManager.CreatePlaylist(listId).UnwrapThrow(); playlistManager.ModifyPlaylist(listId, playlist => { - playlist.AddRange(getList.Items).UnwrapThrow(); + playlist.AddRange(addList.Items).UnwrapThrow(); }).UnwrapThrow(); - return CommandListShow(playlistManager, resourceFactory, listId, null, null); + return CommandListShow(playlistManager, resolver, listId, null, null); } [Command("list insert", "_undocumented")] // TODO Doc - public static JsonValue CommandListAddInternal(ResourceFactory resourceFactory, InvokerData invoker, PlaylistManager playlistManager, string listId, int index, string link /* TODO param */) + public static JsonValue CommandListAddInternal(PlaylistManager playlistManager, ResolveContext resourceFactory, string listId, int index, string link /* TODO param */) { PlaylistItemGetData getData = null; playlistManager.ModifyPlaylist(listId, plist => @@ -899,7 +953,7 @@ public static JsonValue CommandListAddInternal(ResourceFact throw new CommandException(strings.error_playlist_item_index_out_of_range, CommandExceptionReason.CommandError); var playResource = resourceFactory.Load(link).UnwrapThrow(); - var item = new PlaylistItem(playResource.BaseData, new MetaData { ResourceOwnerUid = invoker.ClientUid }); + var item = new PlaylistItem(playResource.BaseData); plist.Insert(index, item).UnwrapThrow(); getData = resourceFactory.ToApiFormat(item); //getData.Index = plist.Items.Count - 1; @@ -907,6 +961,16 @@ public static JsonValue CommandListAddInternal(ResourceFact return JsonValue.Create(getData, strings.info_ok); } + [Command("list item get", "_undocumented")] + public static PlaylistItem CommandListItemMove(PlaylistManager playlistManager, string name, int index) + { + var plist = playlistManager.LoadPlaylist(name).UnwrapThrow(); + if (index < 0 || index >= plist.Items.Count) + throw new CommandException(strings.error_playlist_item_index_out_of_range, CommandExceptionReason.CommandError); + + return plist[index]; + } + [Command("list item move")] // TODO return modified elements public static void CommandListItemMove(PlaylistManager playlistManager, string listId, int from, int to) { @@ -939,7 +1003,7 @@ public static JsonEmpty CommandListItemDelete(PlaylistManager playlistManager, s deletedItem = plist[index]; plist.RemoveAt(index); }).UnwrapThrow(); - return new JsonEmpty(string.Format(strings.info_removed, deletedItem.DisplayString)); + return new JsonEmpty(string.Format(strings.info_removed, deletedItem)); } [Command("list item name")] // TODO return modified elements @@ -950,7 +1014,7 @@ public static void CommandListItemName(PlaylistManager playlistManager, string l if (index < 0 || index >= plist.Items.Count) throw new CommandException(strings.error_playlist_item_index_out_of_range, CommandExceptionReason.CommandError); - plist[index].Resource.ResourceTitle = title; + plist[index].AudioResource.ResourceTitle = title; }).UnwrapThrow(); } @@ -1002,12 +1066,12 @@ public static void CommandListQueue(PlaylistManager playlistManager, PlayManager [Command("list show")] [Usage(" ", "Lets you specify the starting index from which songs should be listed.")] - public static JsonValue CommandListShow(PlaylistManager playlistManager, ResourceFactory resourceFactory, string listId, int? offset = null, int? count = null) + public static JsonValue CommandListShow(PlaylistManager playlistManager, ResolveContext resourceFactory, string listId, int? offset = null, int? count = null) { const int maxSongs = 20; var plist = playlistManager.LoadPlaylist(listId).UnwrapThrow(); - int offsetV = Util.Clamp(offset ?? 0, 0, plist.Items.Count); - int countV = Util.Clamp(count ?? maxSongs, 0, Math.Min(maxSongs, plist.Items.Count - offsetV)); + int offsetV = Tools.Clamp(offset ?? 0, 0, plist.Items.Count); + int countV = Tools.Clamp(count ?? maxSongs, 0, Math.Min(maxSongs, plist.Items.Count - offsetV)); var items = plist.Items.Skip(offsetV).Take(countV).Select(x => resourceFactory.ToApiFormat(x)).ToArray(); var plInfo = new PlaylistInfo { @@ -1034,13 +1098,13 @@ public static void CommandNext(PlayManager playManager, InvokerData invoker) => playManager.Next(invoker).UnwrapThrow(); [Command("param", "_undocumented")] // TODO add documentation, when name decided - public static ICommandResult CommandParam(ExecutionInformation info, IReadOnlyList resultTypes, int index) + public static object CommandParam(ExecutionInformation info, IReadOnlyList resultTypes, int index) { if (!info.TryGet(out var ctx) || ctx.Arguments == null) throw new CommandException("No parameter available", CommandExceptionReason.CommandError); if (index < 0 || index >= ctx.Arguments.Count) - return XCommandSystem.GetEmpty(resultTypes); + return CommandManager.GetEmpty(resultTypes); var backup = ctx.Arguments; ctx.Arguments = null; @@ -1063,18 +1127,27 @@ public static string CommandPm(ClientCall invoker) public static void CommandPmServer(Ts3Client ts3Client, string message) => ts3Client.SendServerMessage(message).UnwrapThrow(); [Command("pm user")] - public static void CommandPmUser(Ts3Client ts3Client, ushort clientId, string message) => ts3Client.SendMessage(message, clientId).UnwrapThrow(); + public static void CommandPmUser(Ts3Client ts3Client, ushort clientId, string message) => ts3Client.SendMessage(message, (ClientId)clientId).UnwrapThrow(); [Command("pause")] - public static void CommandPause(IPlayerConnection playerConnection) => playerConnection.Paused = !playerConnection.Paused; + public static void CommandPause(Player playerConnection) => playerConnection.Paused = !playerConnection.Paused; + + [Command("play")] + public static void CommandPlay(PlayManager playManager, Player playerConnection, InvokerData invoker) + { + if (!playManager.IsPlaying) + playManager.Play(invoker).UnwrapThrow(); + else + playerConnection.Paused = false; + } [Command("play")] - public static void CommandPlay(IPlayerConnection playerConnection) - => playerConnection.Paused = false; + public static void CommandPlay(PlayManager playManager, InvokerData invoker, string url, params string[] attributes) + => playManager.Play(invoker, url, meta: PlayManager.ParseAttributes(attributes)).UnwrapThrow(); [Command("play")] - public static void CommandPlay(PlayManager playManager, InvokerData invoker, string url) - => playManager.Play(invoker, url).UnwrapThrow(); + public static void CommandPlay(PlayManager playManager, InvokerData invoker, IAudioResourceResult rsc, params string[] attributes) + => playManager.Play(invoker, rsc.AudioResource, meta: PlayManager.ParseAttributes(attributes)).UnwrapThrow(); [Command("plugin list")] public static JsonArray CommandPluginList(PluginManager pluginManager, Bot bot = null) @@ -1154,7 +1227,7 @@ public static JsonValue CommandRepeat(PlaylistManager playlistManager) => new JsonValue(playlistManager.Loop, x => x == LoopMode.Off ? strings.cmd_repeat_info_off : x == LoopMode.One ? strings.cmd_repeat_info_one : - x == LoopMode.All ? strings.cmd_repeat_info_all : throw Util.UnhandledDefault(playlistManager.Loop)); + x == LoopMode.All ? strings.cmd_repeat_info_all : throw Tools.UnhandledDefault(playlistManager.Loop)); [Command("repeat off")] public static void CommandRepeatOff(PlaylistManager playlistManager) => playlistManager.Loop = LoopMode.Off; [Command("repeat one")] @@ -1184,75 +1257,81 @@ public static int CommandRng(int? first = null, int? second = null) { if (first.HasValue && second.HasValue) { - return Util.Random.Next(Math.Min(first.Value, second.Value), Math.Max(first.Value, second.Value)); + return Tools.Random.Next(Math.Min(first.Value, second.Value), Math.Max(first.Value, second.Value)); } else if (first.HasValue) { if (first.Value <= 0) throw new CommandException(strings.cmd_rng_value_must_be_positive, CommandExceptionReason.CommandError); - return Util.Random.Next(first.Value); + return Tools.Random.Next(first.Value); } else { - return Util.Random.Next(0, 100); + return Tools.Random.Next(0, 100); } } [Command("seek")] [Usage("", "Time in seconds")] [Usage("", "Time in Minutes:Seconds")] - public static void CommandSeek(IPlayerConnection playerConnection, string position) + [Usage("<0h0m0s>", "Time in hours, minutes and seconds")] + public static void CommandSeek(Player playerConnection, TimeSpan position) { - TimeSpan span; - bool parsed = false; - if (position.Contains(":")) - { - string[] splittime = position.Split(':'); - - if (splittime.Length == 2 - && int.TryParse(splittime[0], out var minutes) - && float.TryParse(splittime[1], NumberStyles.Integer | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var seconds)) - { - parsed = true; - span = TimeSpan.FromSeconds(seconds) + TimeSpan.FromMinutes(minutes); - } - else - { - span = TimeSpan.MinValue; - } - } - else - { - parsed = float.TryParse(position, NumberStyles.Integer | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var seconds); - span = TimeSpan.FromSeconds(seconds); - } - - if (!parsed) - throw new CommandException(strings.cmd_seek_invalid_format, CommandExceptionReason.CommandError); - else if (span < TimeSpan.Zero || span > playerConnection.Length) + //if (!parsed) + // throw new CommandException(strings.cmd_seek_invalid_format, CommandExceptionReason.CommandError); + if (position < TimeSpan.Zero || position > playerConnection.Length) throw new CommandException(strings.cmd_seek_out_of_range, CommandExceptionReason.CommandError); - else - playerConnection.Position = span; + + playerConnection.Position = position; } private static AudioResource GetSearchResult(this UserSession session, int index) { - var result = session.Get>(SessionConst.SearchResult); - if (!result.Ok) + if (!session.Get>(SessionConst.SearchResult, out var sessionList)) throw new CommandException(strings.error_select_empty, CommandExceptionReason.CommandError); - if (index < 0 || index >= result.Value.Count) - throw new CommandException(string.Format(strings.error_value_not_in_range, 0, result.Value.Count), CommandExceptionReason.CommandError); + if (index < 0 || index >= sessionList.Count) + throw new CommandException(string.Format(strings.error_value_not_in_range, 0, sessionList.Count), CommandExceptionReason.CommandError); - return result.Value[index]; + return sessionList[index]; } [Command("search add", "_undocumented")] // TODO Doc - public static void CommandSelect(PlayManager playManager, InvokerData invoker, UserSession session, int index) + public static void CommandSearchAdd(PlayManager playManager, InvokerData invoker, UserSession session, int index) => playManager.Enqueue(invoker, session.GetSearchResult(index)).UnwrapThrow(); + [Command("search from", "_undocumented")] // TODO Doc + public static JsonArray PropagiateSearch(UserSession session, CallerInfo callerInfo, ResolveContext resolver, string resolverName, string query) + { + var result = resolver.Search(resolverName, query); + var list = result.UnwrapThrow(); + session.Set(SessionConst.SearchResult, list); + + return new JsonArray(list, searchResults => + { + if (searchResults.Count == 0) + return strings.cmd_search_no_result; + + var tmb = new TextModBuilder(callerInfo.IsColor); + tmb.AppendFormat( + strings.cmd_search_header.Mod().Bold(), + $"!search play <{strings.info_number}>".Mod().Italic(), + $"!search add <{strings.info_number}>".Mod().Italic()).Append("\n"); + for (int i = 0; i < searchResults.Count; i++) + { + tmb.AppendFormat("{0}: {1}\n", i.ToString().Mod().Bold(), searchResults[i].ResourceTitle); + } + + return tmb.ToString(); + }); + } + + [Command("search get", "_undocumented")] // TODO Doc + public static void CommandSearchGet(UserSession session, int index) + => session.GetSearchResult(index); + [Command("search play", "_undocumented")] // TODO Doc - public static void CommandSelect(PlayManager playManager, ClientCall clientCall, UserSession session, int index) + public static void CommandSeachPlay(PlayManager playManager, ClientCall clientCall, UserSession session, int index) => playManager.Play(clientCall, session.GetSearchResult(index)).UnwrapThrow(); [Command("server tree", "_undocumented")] @@ -1403,7 +1482,7 @@ public static string CommandSettingsHelp(ConfRoot config, string path) } [Command("song")] - public static JsonValue CommandSong(PlayManager playManager, IPlayerConnection playerConnection, Bot bot, ClientCall invoker = null) + public static JsonValue CommandSong(PlayManager playManager, Player playerConnection, Bot bot, ClientCall invoker = null) { if (playManager.CurrentPlayData is null) throw new CommandException(strings.info_currently_not_playing, CommandExceptionReason.CommandError); @@ -1446,18 +1525,18 @@ public static void CommandSubscribe(IVoiceTarget targetManager, ClientCall invok } [Command("subscribe tempchannel")] - public static void CommandSubscribeTempChannel(IVoiceTarget targetManager, ClientCall invoker = null, ulong? channel = null) + public static void CommandSubscribeTempChannel(IVoiceTarget targetManager, ClientCall invoker = null, ChannelId? channel = null) { - var subChan = channel ?? invoker?.ChannelId ?? 0; - if (subChan != 0) + var subChan = channel ?? invoker?.ChannelId ?? ChannelId.Null; + if (subChan != ChannelId.Null) targetManager.WhisperChannelSubscribe(true, subChan); } [Command("subscribe channel")] - public static void CommandSubscribeChannel(IVoiceTarget targetManager, ClientCall invoker = null, ulong? channel = null) + public static void CommandSubscribeChannel(IVoiceTarget targetManager, ClientCall invoker = null, ChannelId? channel = null) { - var subChan = channel ?? invoker?.ChannelId ?? 0; - if (subChan != 0) + var subChan = channel ?? invoker?.ChannelId ?? ChannelId.Null; + if (subChan != ChannelId.Null) targetManager.WhisperChannelSubscribe(false, subChan); } @@ -1506,7 +1585,7 @@ string ResponseQuit(string message) [Usage(" ", "Take only parts of the text")] [Usage(" ", "Take parts, starting with the part at ")] [Usage(" ", "Specify another delimiter for the parts than spaces")] - public static ICommandResult CommandTake(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public static object CommandTake(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { if (arguments.Count < 2) throw new CommandException(strings.error_cmd_at_least_two_argument, CommandExceptionReason.MissingParameter); @@ -1515,24 +1594,24 @@ public static ICommandResult CommandTake(ExecutionInformation info, IReadOnlyLis string delimiter = null; // Get count - var res = ((StringCommandResult)arguments[0].Execute(info, Array.Empty(), XCommandSystem.ReturnString)).Content; + var res = ((IPrimitiveResult)arguments[0].Execute(info, Array.Empty(), ReturnString)).Get(); if (!int.TryParse(res, out int count) || count < 0) throw new CommandException("Count must be an integer >= 0", CommandExceptionReason.CommandError); // LOC: TODO if (arguments.Count > 2) { // Get start - res = ((StringCommandResult)arguments[1].Execute(info, Array.Empty(), XCommandSystem.ReturnString)).Content; + res = ((IPrimitiveResult)arguments[1].Execute(info, Array.Empty(), ReturnString)).Get(); if (!int.TryParse(res, out start) || start < 0) throw new CommandException("Start must be an integer >= 0", CommandExceptionReason.CommandError); // LOC: TODO } // Get delimiter if exists if (arguments.Count > 3) - delimiter = ((StringCommandResult)arguments[2].Execute(info, Array.Empty(), XCommandSystem.ReturnString)).Content; + delimiter = ((IPrimitiveResult)arguments[2].Execute(info, Array.Empty(), ReturnString)).Get(); - string text = ((StringCommandResult)arguments[Math.Min(arguments.Count - 1, 3)] - .Execute(info, Array.Empty(), XCommandSystem.ReturnString)).Content; + string text = ((IPrimitiveResult)arguments[Math.Min(arguments.Count - 1, 3)] + .Execute(info, Array.Empty(), ReturnString)).Get(); var splitted = delimiter is null ? text.Split() @@ -1543,10 +1622,8 @@ public static ICommandResult CommandTake(ExecutionInformation info, IReadOnlyLis foreach (var returnType in returnTypes) { - if (returnType == CommandResultType.String) - return new StringCommandResult(string.Join(delimiter ?? " ", splittedarr)); - if (returnType == CommandResultType.Json) - return new JsonCommandResult(new JsonArray(splittedarr, string.Join(delimiter ?? " ", splittedarr))); + if (returnType == typeof(string)) + return new PrimitiveResult(string.Join(delimiter ?? " ", splittedarr)); } throw new CommandException(strings.error_nothing_to_return, CommandExceptionReason.NoReturnMatch); @@ -1562,7 +1639,7 @@ public static void CommandUnsubscribe(IVoiceTarget targetManager, ClientCall inv [Command("unsubscribe channel")] public static void CommandUnsubscribeChannel(IVoiceTarget targetManager, ClientCall invoker = null, ulong? channel = null) { - var subChan = channel ?? invoker?.ChannelId; + var subChan = (ChannelId?)channel ?? invoker?.ChannelId; if (subChan.HasValue) targetManager.WhisperChannelUnsubscribe(false, subChan.Value); } @@ -1574,13 +1651,13 @@ public static void CommandUnsubscribeChannel(IVoiceTarget targetManager, ClientC public static JsonValue CommandVersion() => new JsonValue(SystemData.AssemblyData, d => d.ToLongString()); [Command("volume")] - public static JsonValue CommandVolume(IPlayerConnection playerConnection) + public static JsonValue CommandVolume(Player playerConnection) => new JsonValue(playerConnection.Volume, string.Format(strings.cmd_volume_current, playerConnection.Volume.ToString("0.#"))); [Command("volume")] [Usage("", "A new volume level between 0 and 100.")] [Usage("+/-", "Adds or subtracts a value from the current volume.")] - public static JsonValue CommandVolume(ExecutionInformation info, IPlayerConnection playerConnection, CallerInfo caller, ConfBot config, string volume, UserSession session = null) + public static JsonValue CommandVolume(ExecutionInformation info, Player playerConnection, CallerInfo caller, ConfBot config, string volume, UserSession session = null) { volume = volume.Trim(); bool relPos = volume.StartsWith("+", StringComparison.Ordinal); @@ -1696,7 +1773,7 @@ public static JsonObject CommandWhisperList(IVoiceTarget targetManager) public static void CommandXecute(ExecutionInformation info, IReadOnlyList arguments) { foreach (var arg in arguments) - arg.Execute(info, Array.Empty(), XCommandSystem.ReturnAnyPreferNothing); + arg.Execute(info, Array.Empty(), ReturnAnyPreferNothing); } // ReSharper enable UnusedMember.Global @@ -1755,7 +1832,7 @@ public static E Write(this ExecutionInformation info, string message) result = ts3Client.SendServerMessage(msgPart); break; default: - throw Util.UnhandledDefault(invoker.Visibiliy.Value); + throw Tools.UnhandledDefault(invoker.Visibiliy.Value); } if (!result.Ok) diff --git a/TS3AudioBot/NLog.config b/TS3AudioBot/NLog.config index 85d3e0bd..766d9584 100644 --- a/TS3AudioBot/NLog.config +++ b/TS3AudioBot/NLog.config @@ -8,7 +8,7 @@ - - + + diff --git a/TS3AudioBot/Playlists/Parser/JspfContent.cs b/TS3AudioBot/Playlists/Parser/JspfContent.cs index b044ae0e..4579de42 100644 --- a/TS3AudioBot/Playlists/Parser/JspfContent.cs +++ b/TS3AudioBot/Playlists/Parser/JspfContent.cs @@ -7,16 +7,16 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using PlaylistsNET.Content; +using PlaylistsNET.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + namespace TS3AudioBot.Playlists.Parser { - using Newtonsoft.Json; - using PlaylistsNET.Content; - using PlaylistsNET.Models; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - public class JspfContent : IPlaylistParser, IPlaylistWriter { public XspfPlaylist GetFromStream(Stream stream) diff --git a/TS3AudioBot/Playlists/Playlist.cs b/TS3AudioBot/Playlists/Playlist.cs index 213edf64..732777f4 100644 --- a/TS3AudioBot/Playlists/Playlist.cs +++ b/TS3AudioBot/Playlists/Playlist.cs @@ -7,14 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using TS3AudioBot.Localization; + namespace TS3AudioBot.Playlists { - using Audio; - using System; - using System.Collections.Generic; - using System.Linq; - using TS3AudioBot.Localization; - public class Playlist : IReadOnlyPlaylist { private const int MaxSongs = 1000; @@ -23,7 +22,7 @@ public class Playlist : IReadOnlyPlaylist private readonly List items; public IReadOnlyList Items => items; - public PlaylistItem this[int i] => this.Get(i); + public PlaylistItem this[int i] => items[i]; public Playlist() : this(new List()) @@ -88,17 +87,8 @@ public E Insert(int index, PlaylistItem song) public interface IReadOnlyPlaylist { + PlaylistItem this[int i] { get; } string Title { get; } IReadOnlyList Items { get; } } - - public static class PlaylistExtensions - { - public static PlaylistItem Get(this IReadOnlyPlaylist self, int index) - { - var item = self.Items[index]; - item.Meta.From = PlaySource.FromPlaylist; - return item; - } - } } diff --git a/TS3AudioBot/Playlists/PlaylistApiExtensions.cs b/TS3AudioBot/Playlists/PlaylistApiExtensions.cs index e090f012..923b3ea0 100644 --- a/TS3AudioBot/Playlists/PlaylistApiExtensions.cs +++ b/TS3AudioBot/Playlists/PlaylistApiExtensions.cs @@ -7,16 +7,16 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using TS3AudioBot.ResourceFactories; +using TS3AudioBot.Web.Model; + namespace TS3AudioBot.Playlists { - using TS3AudioBot.ResourceFactories; - using TS3AudioBot.Web.Model; - public static class PlaylistApiExtensions { - public static PlaylistItemGetData ToApiFormat(this ResourceFactory resourceFactory, PlaylistItem item) + public static PlaylistItemGetData ToApiFormat(this ResolveContext resourceFactory, PlaylistItem item) { - var resource = item.Resource; + var resource = item.AudioResource; return new PlaylistItemGetData { Link = resourceFactory.RestoreLink(resource).OkOr(null), diff --git a/TS3AudioBot/Playlists/PlaylistIO.cs b/TS3AudioBot/Playlists/PlaylistIO.cs index 90260ac5..c559fd05 100644 --- a/TS3AudioBot/Playlists/PlaylistIO.cs +++ b/TS3AudioBot/Playlists/PlaylistIO.cs @@ -7,44 +7,41 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using TS3AudioBot.Algorithm; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.ResourceFactories; +using TS3AudioBot.Web.Model; +using TSLib.Helper; + namespace TS3AudioBot.Playlists { - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using TS3AudioBot.Algorithm; - using TS3AudioBot.Config; - using TS3AudioBot.Helper; - using TS3AudioBot.Localization; - using TS3AudioBot.ResourceFactories; - using TS3AudioBot.Web.Model; - public class PlaylistIO : IDisposable { private readonly ConfBot confBot; private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); - private readonly Dictionary playlistInfo; - private readonly LruCache playlistCache; - private readonly HashSet dirtyList; + private readonly Dictionary playlistInfo = new Dictionary(); + private readonly LruCache playlistCache = new LruCache(16); + private readonly HashSet dirtyList = new HashSet(); private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(); - private const string PlaylistsFolder = "playlists"; private bool reloadFolderCache = true; private const int FileVersion = 3; public PlaylistIO(ConfBot confBot) { this.confBot = confBot; - playlistCache = new LruCache(16); - Util.Init(out playlistInfo); - Util.Init(out dirtyList); } private FileInfo NameToFile(string listId) { - return new FileInfo(Path.Combine(confBot.LocalConfigDir, PlaylistsFolder, listId)); + return new FileInfo(Path.Combine(confBot.LocalConfigDir, BotPaths.Playlists, listId)); } public R Read(string listId) => ReadInternal(listId, false, false); @@ -103,7 +100,7 @@ private R ReadFromFile(string listId, bool headOnly = false) if (!fi.Exists) return new LocalStr(strings.error_playlist_not_found); - using (var sr = new StreamReader(fi.Open(FileMode.Open, FileAccess.Read, FileShare.Read), Util.Utf8Encoder)) + using (var sr = new StreamReader(fi.Open(FileMode.Open, FileAccess.Read, FileShare.Read), Tools.Utf8Encoder)) { var metaRes = ReadHeadStream(sr); if (!metaRes.Ok) @@ -232,18 +229,15 @@ private E WriteToFile(string listId, IReadOnlyPlaylist plist) if (!dir.Exists) dir.Create(); - using (var sw = new StreamWriter(fi.Open(FileMode.Create, FileAccess.Write, FileShare.Read), Util.Utf8Encoder)) + using (var sw = new StreamWriter(fi.Open(FileMode.Create, FileAccess.Write, FileShare.Read), Tools.Utf8Encoder)) { var serializer = new JsonSerializer { Formatting = Formatting.None, }; - if (!playlistInfo.TryGetValue(listId, out var meta)) - { - meta = new PlaylistMeta { }; - playlistInfo.Add(listId, meta); - } + + var meta = playlistInfo.GetOrNew(listId); meta.Title = plist.Title; meta.Count = plist.Items.Count; meta.Version = FileVersion; @@ -258,7 +252,7 @@ private E WriteToFile(string listId, IReadOnlyPlaylist plist) foreach (var pli in plist.Items) { sw.Write("rsj:"); - serializer.Serialize(sw, pli.Resource); + serializer.Serialize(sw, pli.AudioResource); sw.WriteLine(); } } @@ -312,7 +306,7 @@ public R ListPlaylists(string pattern) rwLock.EnterWriteLock(); hasWriteLock = true; - var di = new DirectoryInfo(Path.Combine(confBot.LocalConfigDir, PlaylistsFolder)); + var di = new DirectoryInfo(Path.Combine(confBot.LocalConfigDir, BotPaths.Playlists)); if (!di.Exists) return Array.Empty(); diff --git a/TS3AudioBot/Playlists/PlaylistItem.cs b/TS3AudioBot/Playlists/PlaylistItem.cs index 61c84283..55f80fad 100644 --- a/TS3AudioBot/Playlists/PlaylistItem.cs +++ b/TS3AudioBot/Playlists/PlaylistItem.cs @@ -7,25 +7,28 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TS3AudioBot.Audio; +using TS3AudioBot.CommandSystem.CommandResults; +using TS3AudioBot.ResourceFactories; + namespace TS3AudioBot.Playlists { - using System; - using Audio; - using ResourceFactories; - - public class PlaylistItem + public class PlaylistItem : IAudioResourceResult { - public MetaData Meta { get; } - public AudioResource Resource { get; private set; } - - public string DisplayString => Resource.ResourceTitle ?? $"{Resource.AudioType}: {Resource.ResourceId}"; + public MetaData Meta { get; set; } + public AudioResource AudioResource { get; } - private PlaylistItem(MetaData meta) { Meta = meta ?? new MetaData(); } - public PlaylistItem(AudioResource resource, MetaData meta = null) : this(meta) { Resource = resource; } + private PlaylistItem(MetaData meta = null) + { + Meta = meta; + } - public void Resolve(AudioResource resource) + public PlaylistItem(AudioResource resource, MetaData meta = null) : this(meta) { - Resource = resource ?? throw new ArgumentNullException(nameof(resource)); + AudioResource = resource ?? throw new ArgumentNullException(nameof(resource)); } + + public override string ToString() => AudioResource.ResourceTitle ?? $"{AudioResource.AudioType}: {AudioResource.ResourceId}"; } } diff --git a/TS3AudioBot/Playlists/PlaylistManager.cs b/TS3AudioBot/Playlists/PlaylistManager.cs index 5295c902..ddffc1f4 100644 --- a/TS3AudioBot/Playlists/PlaylistManager.cs +++ b/TS3AudioBot/Playlists/PlaylistManager.cs @@ -7,16 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists.Shuffle; +using TS3AudioBot.Web.Model; +using TSLib.Helper; + namespace TS3AudioBot.Playlists { - using Config; - using Helper; - using Localization; - using Shuffle; - using System; - using System.Collections.Generic; - using TS3AudioBot.Web.Model; - public sealed class PlaylistManager { private readonly ConfPlaylists config; @@ -120,7 +121,7 @@ public void Clear() private void SetRandomSeed() { - shuffle.Seed = Util.Random.Next(); + shuffle.Seed = Tools.Random.Next(); } public R LoadPlaylist(string listId) diff --git a/TS3AudioBot/Playlists/Shuffle/IShuffleAlgorithm.cs b/TS3AudioBot/Playlists/Shuffle/IShuffleAlgorithm.cs index c461de3a..e46574e9 100644 --- a/TS3AudioBot/Playlists/Shuffle/IShuffleAlgorithm.cs +++ b/TS3AudioBot/Playlists/Shuffle/IShuffleAlgorithm.cs @@ -23,7 +23,7 @@ public interface IShuffleAlgorithm // Output conventions: // // if Index = x, x >= Length - // => Index = Util.MathMod(Index, Length) + // => Index = Tools.MathMod(Index, Length) // if Index = x, x < 0 // => Index : undefined // if Index = x, Length < 0 diff --git a/TS3AudioBot/Playlists/Shuffle/LinearFeedbackShiftRegister.cs b/TS3AudioBot/Playlists/Shuffle/LinearFeedbackShiftRegister.cs index da9b9a9b..9004ab07 100644 --- a/TS3AudioBot/Playlists/Shuffle/LinearFeedbackShiftRegister.cs +++ b/TS3AudioBot/Playlists/Shuffle/LinearFeedbackShiftRegister.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TSLib.Helper; + namespace TS3AudioBot.Playlists.Shuffle { - using Helper; - using System; - public class LinearFeedbackShiftRegister : IShuffleAlgorithm { private int register = 1; // aka index @@ -30,14 +30,14 @@ public int Index { if (Length <= 0) return -1; - return Util.MathMod(register + Seed, Length); + return Tools.MathMod(register + Seed, Length); } set { if (Length <= 0) return; Recalc(); - register = Util.MathMod(value - Seed, Length); + register = Tools.MathMod(value - Seed, Length); startRegister = register; } } @@ -70,28 +70,40 @@ public bool Next() return register == startRegister; } + private int NextOf(int val) + { + var lsb = val & 1; + val >>= 1; + val ^= -lsb & mask; + return val; + } + public bool Prev() { if (Length <= 0) return false; Recalc(); - for (int i = 0; i < maskLength; i++) + do { - if (NextOf(i) == register) - { - register = i; - return register == startRegister; - } - } + register = PrevOf(register); + } while ((uint)register > Length); + return register == startRegister; + } + private int PrevOf(int val) + { + var v0 = PrevOfTest(val, 0); + var v1 = PrevOfTest(val, 1); + if (v0 < maskLength && NextOf(v0) == val) + return v0; + if (v1 < maskLength && NextOf(v1) == val) + return v1; throw new InvalidOperationException(); } - private int NextOf(int val) + private int PrevOfTest(int val, int lsb) { - var lsb = val & 1; - val >>= 1; - val ^= -lsb & mask; - return val; + var pval = (-lsb & mask) ^ val; + return (pval << 1) | lsb; } private static int GenerateGaloisMask(int bits, int seedOffset) @@ -105,7 +117,7 @@ private static int GenerateGaloisMask(int bits, int seedOffset) for (int i = 0; i < diff; i++) { - int checkMask = Util.MathMod(i + seedOffset, diff) + start; + int checkMask = Tools.MathMod(i + seedOffset, diff) + start; if (NumberOfSetBits(checkMask) % 2 != 0) continue; if (TestLfsr(checkMask, end)) @@ -131,6 +143,10 @@ private static bool TestLfsr(int mask, int max) private static int NumberOfSetBits(int i) { +#if NETCOREAPP3_0 + if (System.Runtime.Intrinsics.X86.Popcnt.IsSupported) + return unchecked((int)System.Runtime.Intrinsics.X86.Popcnt.PopCount((uint)i)); +#endif i -= ((i >> 1) & 0x55555555); i = (i & 0x33333333) + ((i >> 2) & 0x33333333); return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; diff --git a/TS3AudioBot/Playlists/Shuffle/ListedShuffle.cs b/TS3AudioBot/Playlists/Shuffle/ListedShuffle.cs index 993c1217..7e23cf50 100644 --- a/TS3AudioBot/Playlists/Shuffle/ListedShuffle.cs +++ b/TS3AudioBot/Playlists/Shuffle/ListedShuffle.cs @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Linq; +using TSLib.Helper; + namespace TS3AudioBot.Playlists.Shuffle { - using Helper; - using System; - using System.Linq; - public class ListedShuffle : IShuffleAlgorithm { private int[] permutation; @@ -44,7 +44,7 @@ public int Index { if (Length <= 0) return; GenList(); - index = Array.IndexOf(permutation, Util.MathMod(value, permutation.Length)); + index = Array.IndexOf(permutation, Tools.MathMod(value, permutation.Length)); } } @@ -73,7 +73,7 @@ public bool Prev() if (Length <= 0) return false; GenList(); - index = (index - 1) % permutation.Length; + index = ((index - 1) % permutation.Length + permutation.Length) % permutation.Length; return index == permutation.Length - 1; } } diff --git a/TS3AudioBot/Playlists/Shuffle/NormalOrder.cs b/TS3AudioBot/Playlists/Shuffle/NormalOrder.cs index 5995beb8..e66d4b15 100644 --- a/TS3AudioBot/Playlists/Shuffle/NormalOrder.cs +++ b/TS3AudioBot/Playlists/Shuffle/NormalOrder.cs @@ -7,6 +7,8 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using TSLib.Helper; + namespace TS3AudioBot.Playlists.Shuffle { public class NormalOrder : IShuffleAlgorithm @@ -17,13 +19,13 @@ public class NormalOrder : IShuffleAlgorithm public bool Next() { - Index = Helper.Util.MathMod(Index + 1, Length); + Index = Tools.MathMod(Index + 1, Length); return Index == 0; } public bool Prev() { - Index = Helper.Util.MathMod(Index - 1, Length); + Index = Tools.MathMod(Index - 1, Length); return Index == Length - 1; } } diff --git a/TS3AudioBot/Plugins/ITabPlugin.cs b/TS3AudioBot/Plugins/ITabPlugin.cs index 5b17ef5b..e3729c96 100644 --- a/TS3AudioBot/Plugins/ITabPlugin.cs +++ b/TS3AudioBot/Plugins/ITabPlugin.cs @@ -7,17 +7,29 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.Plugins { - using System; - - public interface ICorePlugin : IDisposable + public interface ITabPlugin : IDisposable { void Initialize(); } - public interface IBotPlugin : ICorePlugin { } + public interface ICorePlugin : ITabPlugin { } + + public interface IBotPlugin : ITabPlugin { } + + public interface IPluginMeta + { + string Name { get; } + string Description { get; } + string Author { get; } + Uri ProjectUrl { get; } + Version Version { get; } + } [AttributeUsage(AttributeTargets.Class, Inherited = false)] + [Obsolete("Static Plugins are deprecated, use an ICorePlugin instead")] public sealed class StaticPluginAttribute : Attribute { } } diff --git a/TS3AudioBot/Plugins/Plugin.cs b/TS3AudioBot/Plugins/Plugin.cs index a9da660b..c002e096 100644 --- a/TS3AudioBot/Plugins/Plugin.cs +++ b/TS3AudioBot/Plugins/Plugin.cs @@ -7,47 +7,48 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using TS3AudioBot.CommandSystem; +using TS3AudioBot.Dependency; +using TS3AudioBot.ResourceFactories; +using Microsoft.CodeAnalysis.Emit; +using TSLib.Helper; + namespace TS3AudioBot.Plugins { - using CommandSystem; - using Dependency; - using Helper; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.Text; - using ResourceFactories; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Security.Cryptography; - using System.Text; - - internal class Plugin : ICommandBag + internal class Plugin { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); public CoreInjector CoreInjector { get; set; } - public ResourceFactory ResourceFactory { get; set; } - public CommandManager CommandManager { get; set; } + public ResourceResolver ResourceResolver { get; set; } + public BotManager BotManager { get; set; } private byte[] md5CacheSum; - private ICorePlugin pluginObject; - private Dictionary pluginObjectList; - private IFactory factoryObject; - private Type coreType; - private readonly bool writeStatus; + private PluginObjects corePlugin; + private readonly Dictionary botPluginList = new Dictionary(); + private IResolver factoryObject; + private Type pluginType; private PluginStatus status; internal PluginType Type { get; private set; } public int Id { get; } public FileInfo File { get; } + // TODO remove after plugin rework + internal PluginObjects CorePlugin => corePlugin; - public Plugin(FileInfo file, int id, bool writeStatus) + public Plugin(FileInfo file, int id) { - pluginObject = null; - this.writeStatus = writeStatus; + corePlugin = null; File = file; Id = id; status = PluginStatus.Off; @@ -61,13 +62,13 @@ public string Name if (CheckStatus(null) == PluginStatus.Error) return $"{File.Name} (Error)"; - var name = coreType?.Name ?? File.Name; + var name = pluginType?.Name ?? File.Name; switch (Type) { case PluginType.Factory: - if (factoryObject?.FactoryFor != null) - return $"{factoryObject.FactoryFor}-factory"; + if (factoryObject?.ResolverFor != null) + return $"{factoryObject.ResolverFor}-factory"; return $"{name} (Factory)"; case PluginType.BotPlugin: return $"{name} (BotPlugin)"; @@ -78,30 +79,11 @@ public string Name case PluginType.None: return $"{File.Name} (Unknown)"; default: - throw Util.UnhandledDefault(Type); + throw Tools.UnhandledDefault(Type); } } } - public bool PersistentEnabled - { - get - { - if (!System.IO.File.Exists(File.FullName + ".status")) - return false; - return System.IO.File.ReadAllText(File.FullName + ".status") != "0"; - } - set - { - if (!System.IO.File.Exists(File.FullName + ".status")) - return; - System.IO.File.WriteAllText(File.FullName + ".status", value ? "1" : "0"); - } - } - - public IReadOnlyCollection BagCommands { get; private set; } - public IReadOnlyCollection AdditionalRights => Array.Empty(); - public PluginStatus CheckStatus(Bot bot) { if (Type != PluginType.BotPlugin) @@ -113,10 +95,10 @@ public PluginStatus CheckStatus(Bot bot) if (bot is null) return PluginStatus.NotAvailable; if (status == PluginStatus.Ready) - return pluginObjectList.ContainsKey(bot) ? PluginStatus.Active : PluginStatus.Ready; + return botPluginList.ContainsKey(bot) ? PluginStatus.Active : PluginStatus.Ready; if (status == PluginStatus.Active) throw new InvalidOperationException("BotPlugin must not be active"); - throw Util.UnhandledDefault(status); + throw Tools.UnhandledDefault(status); } public PluginResponse Load() @@ -213,21 +195,26 @@ private PluginResponse PrepareSource() { var sourceTree = CSharpSyntaxTree.ParseText(SourceText.From(pluginFileStream)); - var compilation = CSharpCompilation.Create($"plugin_{File.Name}_{Util.Random.Next()}") + var compilation = CSharpCompilation.Create($"plugin_{File.Name}_{Tools.Random.Next()}") .WithOptions(new CSharpCompilationOptions( outputKind: OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release)) .AddReferences(param) .AddSyntaxTrees(sourceTree); - using (var ms = new MemoryStream()) + using (var ms_assembly = new MemoryStream()) + using (var ms_pdb = new MemoryStream()) { - var result = compilation.Emit(ms); + var result = compilation.Emit(ms_assembly, ms_pdb, + options: new EmitOptions() + .WithDebugInformationFormat(DebugInformationFormat.PortablePdb) + ); if (result.Success) { - ms.Seek(0, SeekOrigin.Begin); - var assembly = Assembly.Load(ms.ToArray()); + ms_assembly.Seek(0, SeekOrigin.Begin); + ms_pdb.Seek(0, SeekOrigin.Begin); + var assembly = Assembly.Load(ms_assembly.ToArray(), ms_pdb.ToArray()); return InitializeAssembly(assembly); } else @@ -258,9 +245,11 @@ private PluginResponse InitializeAssembly(Assembly assembly) try { var allTypes = assembly.GetExportedTypes(); - var pluginTypes = allTypes.Where(t => typeof(ICorePlugin).IsAssignableFrom(t)).ToArray(); - var factoryTypes = allTypes.Where(t => typeof(IFactory).IsAssignableFrom(t)).ToArray(); + var pluginTypes = allTypes.Where(t => typeof(ITabPlugin).IsAssignableFrom(t)).ToArray(); + var factoryTypes = allTypes.Where(t => typeof(IResolver).IsAssignableFrom(t)).ToArray(); +#pragma warning disable CS0618 // Type or member is obsolete var commandsTypes = allTypes.Where(t => t.GetCustomAttribute() != null).ToArray(); +#pragma warning restore CS0618 // Type or member is obsolete if (pluginTypes.Length + factoryTypes.Length + commandsTypes.Length > 1) { @@ -275,25 +264,22 @@ private PluginResponse InitializeAssembly(Assembly assembly) if (pluginTypes.Length == 1) { - coreType = pluginTypes[0]; - if (typeof(IBotPlugin).IsAssignableFrom(coreType)) - { + pluginType = pluginTypes[0]; + if (typeof(IBotPlugin).IsAssignableFrom(pluginType)) Type = PluginType.BotPlugin; - Util.Init(out pluginObjectList); - } - else - { + else if (typeof(ICorePlugin).IsAssignableFrom(pluginType)) Type = PluginType.CorePlugin; - } + else + throw new InvalidOperationException("Do not inherit from 'ITabPlugin', instead use 'IBotPlugin' or 'ICorePlugin'"); } else if (factoryTypes.Length == 1) { - coreType = factoryTypes[0]; + pluginType = factoryTypes[0]; Type = PluginType.Factory; } else if (commandsTypes.Length == 1) { - coreType = commandsTypes[0]; + pluginType = commandsTypes[0]; Type = PluginType.Commands; } else @@ -324,9 +310,6 @@ private PluginResponse InitializeAssembly(Assembly assembly) /// The bot instance where this plugin should be started. Can be null when not required. public PluginResponse Start(Bot bot) { - if (writeStatus) - PersistentEnabled = true; - switch (CheckStatus(bot)) { case PluginStatus.Disabled: @@ -374,40 +357,45 @@ private bool StartInternal(Bot bot) status = PluginStatus.Error; return false; } - if (pluginObjectList.ContainsKey(bot)) + if (botPluginList.ContainsKey(bot)) throw new InvalidOperationException("Plugin is already instantiated on this bot"); - var pluginInstance = (IBotPlugin)Activator.CreateInstance(coreType); - if (pluginObjectList.Count == 0) - RegisterCommands(pluginInstance, coreType); - pluginObjectList.Add(bot, pluginInstance); - bot.Injector.FillProperties(pluginInstance); - pluginInstance.Initialize(); + + var botPluginObjs = CreatePluginObjects(bot.Injector, false); + botPluginList.Add(bot, botPluginObjs); + botPluginObjs.Plugin.Initialize(); break; case PluginType.CorePlugin: - pluginObject = (ICorePlugin)Activator.CreateInstance(coreType); - RegisterCommands(pluginObject, coreType); - CoreInjector.FillProperties(pluginObject); - pluginObject.Initialize(); + corePlugin = CreatePluginObjects(CoreInjector, false); + BotManager.IterateAll(b => + { + try + { + if (b.Injector.TryGet(out var commandManager)) + commandManager.RegisterCollection(corePlugin.Bag); + } + catch (Exception ex) { Log.Error(ex, "Faile to register commands from plugin '{0}' for bot '{1}'", Name, b.Id); } + }); + corePlugin.Plugin.Initialize(); break; case PluginType.Factory: - factoryObject = (IFactory)Activator.CreateInstance(coreType); - ResourceFactory.AddFactory(factoryObject); + factoryObject = (IResolver)Activator.CreateInstance(pluginType); + ResourceResolver.AddResolver(factoryObject); break; case PluginType.Commands: - RegisterCommands(null, coreType); + corePlugin = CreatePluginObjects(CoreInjector, true); break; default: - throw Util.UnhandledDefault(Type); + throw Tools.UnhandledDefault(Type); } } catch (Exception ex) { if (ex is MissingMethodException) - Log.Error(ex, "Plugins and Factories needs a parameterless constructor."); + Log.Error(ex, "Factories needs a parameterless constructor."); else Log.Error(ex, "Plugin '{0}' failed to load: {1}.", Name, ex.Message); Stop(bot); @@ -422,19 +410,30 @@ private bool StartInternal(Bot bot) return true; } - private void RegisterCommands(object obj, Type t) - { - BagCommands = CommandManager.GetBotCommands(obj, t).ToArray(); - CommandManager.RegisterCollection(this); - } - - private void UnregisterCommands() + // Note, the 'isStatic' flag is only temporary while StaticPlugins are being + // deprecated, after that this distinction is not necessary anymore and + // can be removed. + public PluginObjects CreatePluginObjects(IInjector injector, bool isStatic) { - if (BagCommands != null) + object pluginInstance = null; + if (!isStatic) { - CommandManager.UnregisterCollection(this); - BagCommands = null; + if (!injector.TryCreate(pluginType, out pluginInstance)) + return null; // TODO + injector.FillProperties(pluginInstance); } + if (!injector.TryGet(out var commandManager)) + return null; //TODO + + var pluginObjs = new PluginObjects + { + Plugin = (ITabPlugin)pluginInstance, + Bag = new PluginCommandBag(pluginInstance, pluginType), + CommandManager = commandManager, + }; + + pluginObjs.CommandManager.RegisterCollection(pluginObjs.Bag); + return pluginObjs; } /// @@ -444,9 +443,6 @@ private void UnregisterCommands() /// The bot instance where this plugin should be stopped. Can be null when not required. public PluginResponse Stop(Bot bot) { - if (writeStatus) - PersistentEnabled = false; - switch (Type) { case PluginType.None: @@ -455,38 +451,44 @@ public PluginResponse Stop(Bot bot) case PluginType.BotPlugin: if (bot is null) { - foreach (var plugin in pluginObjectList.Values) - plugin.Dispose(); - pluginObjectList.Clear(); + foreach (var pluginObjs in botPluginList.Values) + DestroyPluginObjects(pluginObjs); + botPluginList.Clear(); } else { - if (pluginObjectList.TryGetValue(bot, out var plugin)) + if (botPluginList.TryGetValue(bot, out var pluginObjs)) { - SaveDisposePlugin(plugin); - pluginObjectList.Remove(bot); + botPluginList.Remove(bot); + DestroyPluginObjects(pluginObjs); } } - if (pluginObjectList.Count == 0) - UnregisterCommands(); break; case PluginType.CorePlugin: - SaveDisposePlugin(pluginObject); - UnregisterCommands(); - pluginObject = null; + if (corePlugin != null) + { + BotManager.IterateAll(b => + { + if (b.Injector.TryGet(out var commandManager)) + commandManager.UnregisterCollection(corePlugin.Bag); + }); + DestroyPluginObjects(corePlugin); + corePlugin = null; + } break; case PluginType.Factory: - ResourceFactory.RemoveFactory(factoryObject); + ResourceResolver.RemoveResolver(factoryObject); break; case PluginType.Commands: - UnregisterCommands(); + if (corePlugin != null) + DestroyPluginObjects(corePlugin); break; default: - throw Util.UnhandledDefault(Type); + throw Tools.UnhandledDefault(Type); } status = PluginStatus.Ready; @@ -494,11 +496,13 @@ public PluginResponse Stop(Bot bot) return PluginResponse.Ok; } - private void SaveDisposePlugin(ICorePlugin plugin) + private void DestroyPluginObjects(PluginObjects pluginObjs) { + pluginObjs.CommandManager.UnregisterCollection(pluginObjs.Bag); + try { - plugin?.Dispose(); + pluginObjs.Plugin?.Dispose(); } catch (Exception ex) { @@ -510,7 +514,7 @@ public void Unload() { Stop(null); - coreType = null; + pluginType = null; if (CheckStatus(null) == PluginStatus.Ready) status = PluginStatus.Off; diff --git a/TS3AudioBot/Plugins/PluginCommandBag.cs b/TS3AudioBot/Plugins/PluginCommandBag.cs new file mode 100644 index 00000000..0f673eb7 --- /dev/null +++ b/TS3AudioBot/Plugins/PluginCommandBag.cs @@ -0,0 +1,27 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; +using TS3AudioBot.CommandSystem; + +namespace TS3AudioBot.Plugins +{ + internal class PluginCommandBag : ICommandBag + { + public IReadOnlyCollection BagCommands { get; } + public IReadOnlyCollection AdditionalRights => Array.Empty(); + + public PluginCommandBag(object obj, Type t) + { + BagCommands = CommandManager.GetBotCommands(obj, t).ToArray(); + } + } +} diff --git a/TS3AudioBot/CommandSystem/CommandResults/JsonCommandResult.cs b/TS3AudioBot/Plugins/PluginExtensions.cs similarity index 51% rename from TS3AudioBot/CommandSystem/CommandResults/JsonCommandResult.cs rename to TS3AudioBot/Plugins/PluginExtensions.cs index 5167fef3..ddab45f7 100644 --- a/TS3AudioBot/CommandSystem/CommandResults/JsonCommandResult.cs +++ b/TS3AudioBot/Plugins/PluginExtensions.cs @@ -7,21 +7,21 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3AudioBot.CommandSystem.CommandResults -{ - using Web.Api; +using NLog; +using System.Diagnostics; +using System.Runtime.CompilerServices; - public class JsonCommandResult : ICommandResult +namespace TS3AudioBot.Plugins +{ + public static class PluginExtensions { - public CommandResultType ResultType => CommandResultType.Json; - - public JsonObject JsonObject { get; } - - public JsonCommandResult(JsonObject jsonObj) + [MethodImpl(MethodImplOptions.NoInlining)] + public static Logger GetLogger() { - JsonObject = jsonObj; - } + var mth = new StackTrace().GetFrame(1).GetMethod(); + var cls = mth.ReflectedType.Name; - public override string ToString() => "JsonCommandResult can't be converted into a string"; + return LogManager.GetLogger($"TS3AudioBot.Plugins.{cls}"); + } } } diff --git a/TS3AudioBot/Plugins/PluginManager.cs b/TS3AudioBot/Plugins/PluginManager.cs index 57c3edde..9088079f 100644 --- a/TS3AudioBot/Plugins/PluginManager.cs +++ b/TS3AudioBot/Plugins/PluginManager.cs @@ -7,18 +7,18 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using TS3AudioBot.Config; +using TS3AudioBot.Dependency; +using TSLib.Helper; + namespace TS3AudioBot.Plugins { - using Config; - using Dependency; - using Helper; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - // Start Plugin: // ! Start plugins before rights system to ensure all rights are loaded // - Get all commands @@ -35,14 +35,15 @@ public class PluginManager : IDisposable { private readonly ConfPlugins config; private readonly CoreInjector coreInjector; - private readonly Dictionary plugins; - private readonly HashSet usedIds; + private readonly Dictionary plugins = new Dictionary(); + private readonly HashSet usedIds = new HashSet(); private readonly object pluginsLock = new object(); + // TODO remove after plugin rework + internal ICollection Plugins => plugins.Values; + public PluginManager(ConfPlugins config, CoreInjector coreInjector) { - Util.Init(out plugins); - Util.Init(out usedIds); this.config = config; this.coreInjector = coreInjector; } @@ -78,7 +79,7 @@ private void CheckLocalPlugins(Bot bot) plugin.Load(); break; default: - throw Util.UnhandledDefault(status); + throw Tools.UnhandledDefault(status); } } else @@ -86,7 +87,7 @@ private void CheckLocalPlugins(Bot bot) if (IsIgnored(file)) continue; - plugin = new Plugin(file, GetFreeId(), config.WriteStatusFiles); + plugin = new Plugin(file, GetFreeId()); if (plugin.Load() == PluginResponse.Disabled) { @@ -209,7 +210,7 @@ public static string FormatOverview(ICollection pluginList) case PluginStatus.Disabled: strb.Append("UNL"); break; case PluginStatus.Error: strb.Append("ERR"); break; case PluginStatus.NotAvailable: strb.Append("N/A"); break; - default: throw Util.UnhandledDefault(plugin.Status); + default: throw Tools.UnhandledDefault(plugin.Status); } strb.Append('|').AppendLine(plugin.Name ?? ""); } diff --git a/TS3AudioBot/ResourceFactories/IThumbnailFactory.cs b/TS3AudioBot/Plugins/PluginObjects.cs similarity index 63% rename from TS3AudioBot/ResourceFactories/IThumbnailFactory.cs rename to TS3AudioBot/Plugins/PluginObjects.cs index 91a335b7..af5066a0 100644 --- a/TS3AudioBot/ResourceFactories/IThumbnailFactory.cs +++ b/TS3AudioBot/Plugins/PluginObjects.cs @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3AudioBot.ResourceFactories -{ - using Localization; - using System; - using System.IO; +using TS3AudioBot.CommandSystem; - public interface IThumbnailFactory : IFactory +namespace TS3AudioBot.Plugins +{ + internal class PluginObjects { - R GetThumbnail(PlayResource playResource); + public PluginCommandBag Bag { get; set; } + public ITabPlugin Plugin { get; set; } + public CommandManager CommandManager { get; set; } } } diff --git a/TS3AudioBot/ResourceFactories/AudioResource.cs b/TS3AudioBot/ResourceFactories/AudioResource.cs index 0e5c72b1..6367ceb7 100644 --- a/TS3AudioBot/ResourceFactories/AudioResource.cs +++ b/TS3AudioBot/ResourceFactories/AudioResource.cs @@ -7,26 +7,30 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System.Collections.Generic; +using TS3AudioBot.Audio; +using TS3AudioBot.CommandSystem.CommandResults; + namespace TS3AudioBot.ResourceFactories { - using Newtonsoft.Json; - using System.Collections.Generic; - public class PlayResource { public AudioResource BaseData { get; } public string PlayUri { get; } + public MetaData Meta { get; set; } - public PlayResource(string uri, AudioResource baseData) + public PlayResource(string uri, AudioResource baseData, MetaData meta = null) { BaseData = baseData; PlayUri = uri; + Meta = meta; } public override string ToString() => BaseData.ToString(); } - public class AudioResource + public class AudioResource : IAudioResourceResult { /// The resource type. [JsonProperty(PropertyName = "type")] @@ -43,6 +47,8 @@ public class AudioResource /// An identifier wich is unique among all and resource type string of a factory. [JsonIgnore] public string UniqueId => ResourceId + AudioType; + [JsonIgnore] + AudioResource IAudioResourceResult.AudioResource => this; public AudioResource() { } @@ -78,12 +84,7 @@ public override bool Equals(object obj) && ResourceId == other.ResourceId; } - public override int GetHashCode() - { - int hash = 0x7FFFF + AudioType.GetHashCode(); - hash = (hash * 0x1FFFF) + ResourceId.GetHashCode(); - return hash; - } + public override int GetHashCode() => (AudioType, ResourceId).GetHashCode(); public override string ToString() { diff --git a/TS3AudioBot/ResourceFactories/AudioTags/AudioTagReader.cs b/TS3AudioBot/ResourceFactories/AudioTags/AudioTagReader.cs index 43352407..7e847058 100644 --- a/TS3AudioBot/ResourceFactories/AudioTags/AudioTagReader.cs +++ b/TS3AudioBot/ResourceFactories/AudioTags/AudioTagReader.cs @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + namespace TS3AudioBot.ResourceFactories.AudioTags { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - internal static class AudioTagReader { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); diff --git a/TS3AudioBot/ResourceFactories/AudioTags/BinaryReaderBigEndianExtensions.cs b/TS3AudioBot/ResourceFactories/AudioTags/BinaryReaderBigEndianExtensions.cs index 67faf45a..46ab6106 100644 --- a/TS3AudioBot/ResourceFactories/AudioTags/BinaryReaderBigEndianExtensions.cs +++ b/TS3AudioBot/ResourceFactories/AudioTags/BinaryReaderBigEndianExtensions.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.IO; +using System.Runtime.InteropServices; + namespace TS3AudioBot.ResourceFactories.AudioTags { - using System.IO; - using System.Runtime.InteropServices; - internal static class BinaryReaderBigEndianExtensions { public static short ReadInt16Be(this BinaryReader br) diff --git a/TS3AudioBot/ResourceFactories/AudioTags/M3uReader.cs b/TS3AudioBot/ResourceFactories/AudioTags/M3uReader.cs index 76c52941..c5eccb9a 100644 --- a/TS3AudioBot/ResourceFactories/AudioTags/M3uReader.cs +++ b/TS3AudioBot/ResourceFactories/AudioTags/M3uReader.cs @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using TSLib.Helper; + namespace TS3AudioBot.ResourceFactories.AudioTags { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using TS3Client.Helper; - public static class M3uReader { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); diff --git a/TS3AudioBot/ResourceFactories/BandcampFactory.cs b/TS3AudioBot/ResourceFactories/BandcampResolver.cs similarity index 83% rename from TS3AudioBot/ResourceFactories/BandcampFactory.cs rename to TS3AudioBot/ResourceFactories/BandcampResolver.cs index caf6951e..d21a781f 100644 --- a/TS3AudioBot/ResourceFactories/BandcampFactory.cs +++ b/TS3AudioBot/ResourceFactories/BandcampResolver.cs @@ -7,17 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.IO; +using System.Text.RegularExpressions; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; + namespace TS3AudioBot.ResourceFactories { - using Helper; - using Localization; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using System; - using System.IO; - using System.Text.RegularExpressions; - - public class BandcampFactory : IResourceFactory, IThumbnailFactory + public class BandcampResolver : IResourceResolver, IThumbnailResolver { private static readonly Regex BandcampUrlRegex = new Regex(@"([\w_-]+).bandcamp.com/track/([\w_-]+)", Util.DefaultRegexConfig); private static readonly Regex TrackLinkRegex = new Regex(@"""mp3-128""\s*:\s*""([^""]*)""", Util.DefaultRegexConfig); @@ -28,11 +28,11 @@ public class BandcampFactory : IResourceFactory, IThumbnailFactory private const string AddArtist = "artist"; private const string AddTrack = "track"; - public string FactoryFor => "bandcamp"; + public string ResolverFor => "bandcamp"; - public MatchCertainty MatchResource(string uri) => BandcampUrlRegex.IsMatch(uri).ToMatchCertainty(); + public MatchCertainty MatchResource(ResolveContext _, string uri) => BandcampUrlRegex.IsMatch(uri).ToMatchCertainty(); - public R GetResource(string url) + public R GetResource(ResolveContext _, string url) { var match = BandcampUrlRegex.Match(url); if (!match.Success) @@ -57,20 +57,20 @@ public R GetResource(string url) return new LocalStr(strings.error_media_no_stream_extracted); var firstTrack = jarr[0]; - var id = firstTrack.TryCast("track_id").OkOr(null); - var title = firstTrack.TryCast("title").OkOr(null); - var trackObj = firstTrack["file"]?.TryCast("mp3-128").OkOr(""); - if (id is null || title is null || trackObj is null) + if (!firstTrack.TryCast("track_id", out var id) + || !firstTrack.TryCast("title", out var title) + || firstTrack["file"] == null + || !firstTrack["file"].TryCast("mp3-128", out var trackObj)) return new LocalStr(strings.error_media_no_stream_extracted); return new BandcampPlayResource(trackObj, - new AudioResource(id, title, FactoryFor) + new AudioResource(id, title, ResolverFor) .Add(AddArtist, artistName) .Add(AddTrack, trackName), GetTrackArtId(webSite)); } - public R GetResourceById(AudioResource resource) + public R GetResourceById(ResolveContext _, AudioResource resource) { var result = DownloadEmbeddedSite(resource.ResourceId); if (!result.Ok) return result.Error; @@ -91,7 +91,7 @@ public R GetResourceById(AudioResource resource) return new BandcampPlayResource(match.Groups[1].Value, resource, GetTrackArtId(webSite)); } - public string RestoreLink(AudioResource resource) + public string RestoreLink(ResolveContext _, AudioResource resource) { var artistName = resource.Get(AddArtist); var trackName = resource.Get(AddTrack); @@ -111,7 +111,7 @@ private static R DownloadEmbeddedSite(string id) return webSite; } - public R GetThumbnail(PlayResource playResource) + public R GetThumbnail(ResolveContext _, PlayResource playResource) { string artId; if (playResource is BandcampPlayResource bandcampPlayResource) diff --git a/TS3AudioBot/ResourceFactories/IPlaylistResolver.cs b/TS3AudioBot/ResourceFactories/IPlaylistResolver.cs new file mode 100644 index 00000000..8500ab99 --- /dev/null +++ b/TS3AudioBot/ResourceFactories/IPlaylistResolver.cs @@ -0,0 +1,22 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists; + +namespace TS3AudioBot.ResourceFactories +{ + public interface IPlaylistResolver : IResolver + { + MatchCertainty MatchPlaylist(ResolveContext ctx, string uri); + + R GetPlaylist(ResolveContext ctx, string url); + } +} diff --git a/TS3AudioBot/ResourceFactories/IFactory.cs b/TS3AudioBot/ResourceFactories/IResolver.cs similarity index 83% rename from TS3AudioBot/ResourceFactories/IFactory.cs rename to TS3AudioBot/ResourceFactories/IResolver.cs index 3ca33947..ffb066c4 100644 --- a/TS3AudioBot/ResourceFactories/IFactory.cs +++ b/TS3AudioBot/ResourceFactories/IResolver.cs @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.ResourceFactories { - using System; - - public interface IFactory : IDisposable + public interface IResolver : IDisposable { - string FactoryFor { get; } + string ResolverFor { get; } } } diff --git a/TS3AudioBot/ResourceFactories/IResourceFactory.cs b/TS3AudioBot/ResourceFactories/IResourceResolver.cs similarity index 81% rename from TS3AudioBot/ResourceFactories/IResourceFactory.cs rename to TS3AudioBot/ResourceFactories/IResourceResolver.cs index c3863be8..99c00529 100644 --- a/TS3AudioBot/ResourceFactories/IResourceFactory.cs +++ b/TS3AudioBot/ResourceFactories/IResourceResolver.cs @@ -7,28 +7,28 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TS3AudioBot.Localization; + namespace TS3AudioBot.ResourceFactories { - using Localization; - using System; - - public interface IResourceFactory : IFactory + public interface IResourceResolver : IResolver { /// Check method to ask if a factory can load the given link. /// Any link or something similar a user can obtain to pass it here. /// True if the factory thinks it can parse it, false otherwise. - MatchCertainty MatchResource(string uri); + MatchCertainty MatchResource(ResolveContext ctx, string uri); /// The factory will try to parse the uri and create a playable resource from it. /// Any link or something similar a user can obtain to pass it here. /// The playable resource if successful, or an error message otherwise - R GetResource(string uri); + R GetResource(ResolveContext ctx, string uri); /// The factory will try to parse the unique identifier of its scope of responsibility and create a playable resource from it. /// A resource containing the unique id for a song this factory is responsible for. /// The playable resource if successful, or an error message otherwise - R GetResourceById(AudioResource resource); + R GetResourceById(ResolveContext ctx, AudioResource resource); /// Gets a link to the original site/location. This may differ from the link the resource was orininally created. /// The unique id for a song this factory is responsible for. /// The (close to) original link if successful, null otherwise. - string RestoreLink(AudioResource resource); + string RestoreLink(ResolveContext ctx, AudioResource resource); } } diff --git a/TS3AudioBot/ResourceFactories/ISearchFactory.cs b/TS3AudioBot/ResourceFactories/ISearchResolver.cs similarity index 80% rename from TS3AudioBot/ResourceFactories/ISearchFactory.cs rename to TS3AudioBot/ResourceFactories/ISearchResolver.cs index 97e7caf6..91431a75 100644 --- a/TS3AudioBot/ResourceFactories/ISearchFactory.cs +++ b/TS3AudioBot/ResourceFactories/ISearchResolver.cs @@ -13,8 +13,8 @@ namespace TS3AudioBot.ResourceFactories using System.Collections.Generic; using TS3AudioBot.Localization; - public interface ISearchFactory : IFactory + public interface ISearchResolver : IResolver { - R, LocalStr> Search(string keyword); + R, LocalStr> Search(ResolveContext ctx, string keyword); } } diff --git a/TS3AudioBot/ResourceFactories/IPlaylistFactory.cs b/TS3AudioBot/ResourceFactories/IThumbnailResolver.cs similarity index 69% rename from TS3AudioBot/ResourceFactories/IPlaylistFactory.cs rename to TS3AudioBot/ResourceFactories/IThumbnailResolver.cs index 7197d739..4912ddb0 100644 --- a/TS3AudioBot/ResourceFactories/IPlaylistFactory.cs +++ b/TS3AudioBot/ResourceFactories/IThumbnailResolver.cs @@ -7,16 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.IO; +using TS3AudioBot.Localization; + namespace TS3AudioBot.ResourceFactories { - using Localization; - using Playlists; - using System; - - public interface IPlaylistFactory : IFactory + public interface IThumbnailResolver : IResolver { - MatchCertainty MatchPlaylist(string uri); - - R GetPlaylist(string url); + R GetThumbnail(ResolveContext ctx, PlayResource playResource); } } diff --git a/TS3AudioBot/ResourceFactories/MediaFactory.cs b/TS3AudioBot/ResourceFactories/MediaResolver.cs similarity index 76% rename from TS3AudioBot/ResourceFactories/MediaFactory.cs rename to TS3AudioBot/ResourceFactories/MediaResolver.cs index 494e62e6..71689a94 100644 --- a/TS3AudioBot/ResourceFactories/MediaFactory.cs +++ b/TS3AudioBot/ResourceFactories/MediaResolver.cs @@ -7,50 +7,44 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using PlaylistsNET.Content; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists; +using TS3AudioBot.ResourceFactories.AudioTags; + namespace TS3AudioBot.ResourceFactories { - using AudioTags; - using Config; - using Helper; - using Localization; - using Playlists; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net; - using PlaylistsNET.Content; - - public sealed class MediaFactory : IResourceFactory, IPlaylistFactory, IThumbnailFactory + public sealed class MediaResolver : IResourceResolver, IPlaylistResolver, IThumbnailResolver { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); - private readonly ConfPath config; - - public MediaFactory(ConfPath config) - { - this.config = config; - } - public string FactoryFor => "media"; + public string ResolverFor => "media"; - public MatchCertainty MatchResource(string uri) => + public MatchCertainty MatchResource(ResolveContext _, string uri) => File.Exists(uri) ? MatchCertainty.Always : MatchCertainty.OnlyIfLast; - public MatchCertainty MatchPlaylist(string uri) => + public MatchCertainty MatchPlaylist(ResolveContext _, string uri) => Directory.Exists(uri) ? MatchCertainty.Always : File.Exists(uri) ? MatchCertainty.Always : MatchCertainty.OnlyIfLast; - public R GetResource(string uri) + public R GetResource(ResolveContext ctx, string uri) { - return GetResourceById(new AudioResource(uri, null, FactoryFor)); + return GetResourceById(ctx, new AudioResource(uri, null, ResolverFor)); } - public R GetResourceById(AudioResource resource) + public R GetResourceById(ResolveContext ctx, AudioResource resource) { - var result = ValidateFromString(resource.ResourceId); + var result = ValidateFromString(ctx.Config, resource.ResourceId); if (!result) return result.Error; @@ -72,11 +66,11 @@ public R GetResourceById(AudioResource resource) return new MediaPlayResource(resData.FullUri, resource, resData.Image, false); } - public string RestoreLink(AudioResource resource) => resource.ResourceId; + public string RestoreLink(ResolveContext _, AudioResource resource) => resource.ResourceId; - private R ValidateFromString(string uriStr) + private R ValidateFromString(ConfBot config, string uriStr) { - if (TryGetUri(uriStr).GetOk(out var uri)) + if (TryGetUri(config, uriStr).GetOk(out var uri)) return ValidateUri(uri); return new LocalStr(strings.error_media_invalid_uri); } @@ -157,7 +151,7 @@ private R ValidateFile(Uri foundPath) } } - private R TryGetUri(string uri) + private R TryGetUri(ConfBot conf, string uri) { if (Uri.TryCreate(uri, UriKind.Absolute, out Uri uriResult)) { @@ -165,24 +159,25 @@ private R TryGetUri(string uri) } else { - var file = FindFile(uri); + Log.Trace("Finding media path: '{0}'", uri); + + var file = + TryInPath(Path.Combine(conf.LocalConfigDir, BotPaths.Music), uri) + ?? TryInPath(conf.GetParent().Factories.Media.Path.Value, uri); + if (file == null) return new LocalStr(strings.error_media_file_not_found); return file; } } - private Uri FindFile(string path) + private static Uri TryInPath(string pathPrefix, string file) { - Log.Trace("Finding media path: '{0}'", path); - try { - var fullPath = Path.GetFullPath(path); - if (File.Exists(fullPath)) - return new Uri(fullPath, UriKind.Absolute); - fullPath = Path.GetFullPath(Path.Combine(config.Path.Value, path)); - if (File.Exists(fullPath)) + var musicPathPrefix = Path.GetFullPath(pathPrefix); + var fullPath = Path.Combine(musicPathPrefix, file); + if (fullPath.StartsWith(musicPathPrefix) && File.Exists(fullPath)) return new Uri(fullPath, UriKind.Absolute); } catch (Exception ex) @@ -193,21 +188,19 @@ private Uri FindFile(string path) return null; } - public void Dispose() { } - - public R GetPlaylist(string url) + public R GetPlaylist(ResolveContext ctx, string url) { - if (Directory.Exists(url)) + if (Directory.Exists(url)) // TODO rework for security { try { var di = new DirectoryInfo(url); var plist = new Playlist().SetTitle(di.Name); var resources = from file in di.EnumerateFiles() - select ValidateFromString(file.FullName) into result + select ValidateFromString(ctx.Config, file.FullName) into result where result.Ok select result.Value into val - select new AudioResource(val.FullUri, string.IsNullOrWhiteSpace(val.Title) ? val.FullUri : val.Title, FactoryFor) into res + select new AudioResource(val.FullUri, string.IsNullOrWhiteSpace(val.Title) ? val.FullUri : val.Title, ResolverFor) into res select new PlaylistItem(res); plist.AddRange(resources); @@ -220,37 +213,38 @@ select result.Value into val } } - var plistResult = R.Err(new LocalStr(strings.error_media_invalid_uri)); - try { - if (File.Exists(url)) - { - using (var stream = File.OpenRead(url)) - plistResult = GetPlaylistContent(stream, url); - } - else if (TryGetUri(url).GetOk(out var uri)) + if (TryGetUri(ctx.Config, url).GetOk(out var uri)) { - var status = WebWrapper.GetResponse(uri, response => + if (uri.IsFile()) { - var contentType = response.Headers.Get("Content-Type"); - int index = url.LastIndexOf('.'); - string anyId = index >= 0 ? url.Substring(index) : url; + if (File.Exists(url)) + { + using (var stream = File.OpenRead(uri.AbsolutePath)) + return GetPlaylistContent(stream, url); + } + } + else if (uri.IsWeb()) + { + return WebWrapper.GetResponse(uri, response => + { + var contentType = response.Headers.Get("Content-Type"); + int index = url.LastIndexOf('.'); + string anyId = index >= 0 ? url.Substring(index) : url; - using (var stream = response.GetResponseStream()) - plistResult = GetPlaylistContent(stream, url, contentType); - }); - if (!status.Ok) - return status.Error; + using (var stream = response.GetResponseStream()) + return GetPlaylistContent(stream, url, contentType); + }).Flat(); + } } + return new LocalStr(strings.error_media_invalid_uri); } catch (Exception ex) { Log.Warn(ex, "Error opening/reading playlist file"); - return new LocalStr(strings.error_media_invalid_uri); + return new LocalStr(strings.error_io_unknown_error); } - - return plistResult; } private R GetPlaylistContent(Stream stream, string url, string mime = null) @@ -270,7 +264,7 @@ private R GetPlaylistContent(Stream stream, string url, stri items = new List( from e in list.PlaylistEntries - select new PlaylistItem(new AudioResource(e.Path, e.Title, FactoryFor))); + select new PlaylistItem(new AudioResource(e.Path, e.Title, ResolverFor))); break; } case ".m3u8": @@ -286,7 +280,7 @@ from e in list.PlaylistEntries items = new List( from e in list.PlaylistEntries - select new PlaylistItem(new AudioResource(e.Path, e.Title, FactoryFor))); + select new PlaylistItem(new AudioResource(e.Path, e.Title, ResolverFor))); break; } case ".pls": @@ -299,7 +293,7 @@ from e in list.PlaylistEntries items = new List( from e in list.PlaylistEntries - select new PlaylistItem(new AudioResource(e.Path, e.Title, FactoryFor))); + select new PlaylistItem(new AudioResource(e.Path, e.Title, ResolverFor))); break; } case ".wpl": @@ -309,7 +303,7 @@ from e in list.PlaylistEntries items = new List( from e in list.PlaylistEntries - select new PlaylistItem(new AudioResource(e.Path, e.TrackTitle, FactoryFor))); + select new PlaylistItem(new AudioResource(e.Path, e.TrackTitle, ResolverFor))); name = list.Title; break; } @@ -320,7 +314,7 @@ from e in list.PlaylistEntries items = new List( from e in list.PlaylistEntries - select new PlaylistItem(new AudioResource(e.Path, e.TrackTitle, FactoryFor))); + select new PlaylistItem(new AudioResource(e.Path, e.TrackTitle, ResolverFor))); name = list.Title; break; } @@ -351,7 +345,7 @@ private static R GetStreamFromUriUnsafe(Uri uri) return new LocalStr(strings.error_media_invalid_uri); } - public R GetThumbnail(PlayResource playResource) + public R GetThumbnail(ResolveContext _, PlayResource playResource) { byte[] rawImgData; @@ -377,6 +371,8 @@ public R GetThumbnail(PlayResource playResource) return new MemoryStream(rawImgData); } + + public void Dispose() { } } internal class ResData diff --git a/TS3AudioBot/ResourceFactories/ResolveContext.cs b/TS3AudioBot/ResourceFactories/ResolveContext.cs new file mode 100644 index 00000000..9d88586b --- /dev/null +++ b/TS3AudioBot/ResourceFactories/ResolveContext.cs @@ -0,0 +1,38 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using System.IO; +using TS3AudioBot.Config; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists; + +namespace TS3AudioBot.ResourceFactories +{ + public class ResolveContext + { + public ResourceResolver Resolver { get; } + public ConfBot Config { get; } + + public ResolveContext(ResourceResolver resolver, ConfBot config) + { + Resolver = resolver; + Config = config; + } + + public R Load(AudioResource resource) => Resolver.Load(this, resource); + public R Load(string message, string audioType = null) => Resolver.Load(this, message, audioType); + public R LoadPlaylistFrom(string message) => Resolver.LoadPlaylistFrom(this, message); + public R LoadPlaylistFrom(string message, string audioType = null) => Resolver.LoadPlaylistFrom(this, message, audioType); + public R RestoreLink(AudioResource res) => Resolver.RestoreLink(this, res); + public R GetThumbnail(PlayResource playResource) => Resolver.GetThumbnail(this, playResource); + public R, LocalStr> Search(string resolverName, string query) => Resolver.Search(this, resolverName, query); + } +} diff --git a/TS3AudioBot/ResourceFactories/ResourceFactory.cs b/TS3AudioBot/ResourceFactories/ResourceFactory.cs deleted file mode 100644 index 670db5e1..00000000 --- a/TS3AudioBot/ResourceFactories/ResourceFactory.cs +++ /dev/null @@ -1,389 +0,0 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3AudioBot.ResourceFactories -{ - using Audio; - using CommandSystem; - using Config; - using Helper; - using Localization; - using Playlists; - using Sessions; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Text; - using TS3AudioBot.CommandSystem.Text; - using TS3AudioBot.Web.Api; - - public sealed class ResourceFactory : IDisposable - { - private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); - private const string CmdResPrepath = "from "; - private const string CmdListPrepath = "list from "; - private const string CmdSearchPrepath = "search from "; - - private readonly Dictionary allFacories; - private readonly List listFactories; - private readonly List resFactories; - private readonly List searchFactories; - private readonly CommandManager commandManager; - - public ResourceFactory(ConfFactories config, CommandManager commandManager) - { - Util.Init(out allFacories); - Util.Init(out resFactories); - Util.Init(out listFactories); - Util.Init(out searchFactories); - this.commandManager = commandManager; - - AddFactory(new MediaFactory(config.Media)); - AddFactory(new YoutubeFactory()); - AddFactory(new SoundcloudFactory()); - AddFactory(new TwitchFactory()); - AddFactory(new BandcampFactory()); - } - - private T GetFactoryByType(string audioType) where T : class, IFactory => - // ToLower for legacy reasons - allFacories.TryGetValue(audioType.ToLowerInvariant(), out var factoryInfo) && factoryInfo.Factory is T factory - ? factory - : null; - - private IEnumerable<(IResourceFactory, MatchCertainty)> GetResFactoryByLink(string uri) => - from fac in resFactories - let facCertain = fac.MatchResource(uri) - where facCertain != MatchCertainty.Never - orderby facCertain descending - select (fac, facCertain); - - private IEnumerable<(IPlaylistFactory, MatchCertainty)> GetListFactoryByLink(string uri) => - from fac in listFactories - let facCertain = fac.MatchPlaylist(uri) - where facCertain != MatchCertainty.Never - orderby facCertain descending - select (fac, facCertain); - - private static IEnumerable FilterUsable(IEnumerable<(T, MatchCertainty)> enu) - { - var highestCertainty = MatchCertainty.Never; - foreach (var (fac, cert) in enu) - { - if ((highestCertainty == MatchCertainty.Always && cert < MatchCertainty.Always) - || (highestCertainty > MatchCertainty.Never && cert <= MatchCertainty.OnlyIfLast)) - yield break; - - yield return fac; - - if (cert > highestCertainty) - highestCertainty = cert; - } - } - - /// Generates a new which can be played. - /// An with at least - /// and set. - /// The playable resource if successful, or an error message otherwise. - public R Load(AudioResource resource) - { - if (resource is null) - throw new ArgumentNullException(nameof(resource)); - - var factory = GetFactoryByType(resource.AudioType); - if (factory is null) - return CouldNotLoad(string.Format(strings.error_resfac_no_registered_factory, resource.AudioType)); - - var sw = Stopwatch.StartNew(); - R result; - try - { - result = factory.GetResourceById(resource); - } - catch (Exception ex) - { - Log.Error(ex, "Resource factory '{0}' threw while trying to resolve '{@resource}'", factory.FactoryFor, resource); - return CouldNotLoad(strings.error_playmgr_internal_error); - } - if (!result.Ok) - return CouldNotLoad(result.Error.Str); - Log.Debug("Took {0}ms to resolve resource.", sw.ElapsedMilliseconds); - return result.Value; - } - - /// Generates a new which can be played. - /// The message used will be cleared of bb-tags. Also lets you pick an - /// identifier to optionally select a factory. - /// - /// The link/uri to resolve for the resource. - /// The associated resource type string to a factory. - /// Leave null to let it detect automatically. - /// The playable resource if successful, or an error message otherwise. - public R Load(string message, string audioType = null) - { - if (string.IsNullOrWhiteSpace(message)) - throw new ArgumentNullException(nameof(message)); - - var netlinkurl = TextUtil.ExtractUrlFromBb(message); - - if (audioType != null) - { - var factory = GetFactoryByType(audioType); - if (factory is null) - return CouldNotLoad(string.Format(strings.error_resfac_no_registered_factory, audioType)); - - var result = factory.GetResource(netlinkurl); - if (!result.Ok) - return CouldNotLoad(result.Error.Str); - return result; - } - - var sw = Stopwatch.StartNew(); - var factories = FilterUsable(GetResFactoryByLink(netlinkurl)); - List<(string, LocalStr)> errors = null; - foreach (var factory in factories) - { - var result = factory.GetResource(netlinkurl); - Log.Trace("ResFactory {0} tried, result: {1}", factory.FactoryFor, result.Ok ? "Ok" : result.Error.Str); - if (result) - return result; - (errors = errors ?? new List<(string, LocalStr)>()).Add((factory.FactoryFor, result.Error)); - } - Log.Debug("Took {0}ms to resolve resource.", sw.ElapsedMilliseconds); - - return ToErrorString(errors); - } - - public R LoadPlaylistFrom(string message) => LoadPlaylistFrom(message, null); - - private R LoadPlaylistFrom(string message, IPlaylistFactory listFactory) - { - if (string.IsNullOrWhiteSpace(message)) - throw new ArgumentNullException(nameof(message)); - - string netlinkurl = TextUtil.ExtractUrlFromBb(message); - - if (listFactory != null) - return listFactory.GetPlaylist(netlinkurl); - - var factories = FilterUsable(GetListFactoryByLink(netlinkurl)); - List<(string, LocalStr)> errors = null; - foreach (var factory in factories) - { - var result = factory.GetPlaylist(netlinkurl); - Log.Trace("ListFactory {0} tried, result: {1}", factory.FactoryFor, result.Ok ? "Ok" : result.Error.Str); - if (result) - return result; - (errors = errors ?? new List<(string, LocalStr)>()).Add((factory.FactoryFor, result.Error)); - } - - return ToErrorString(errors); - } - - public R RestoreLink(AudioResource res) - { - var factory = GetFactoryByType(res.AudioType); - if (factory is null) - return CouldNotLoad(); - return factory.RestoreLink(res); - } - - public R GetThumbnail(PlayResource playResource) - { - var factory = GetFactoryByType(playResource.BaseData.AudioType); - if (factory is null) - return new LocalStr(string.Format(strings.error_resfac_no_registered_factory, playResource.BaseData.AudioType)); - - var sw = Stopwatch.StartNew(); - var result = factory.GetThumbnail(playResource); - Log.Debug("Took {0}ms to load thumbnail.", sw.ElapsedMilliseconds); - return result; - } - - public void AddFactory(IFactory factory) - { - if (factory.FactoryFor.ToLowerInvariant() != factory.FactoryFor) - throw new ArgumentException($"The factory audio type \"{nameof(IFactory.FactoryFor)}\" must be in lower case.", nameof(factory)); - if (allFacories.ContainsKey(factory.FactoryFor)) - throw new ArgumentException("A factory for this type already has been registered.", nameof(factory)); - - var commands = new List(); - if (factory is IResourceFactory resFactory) - { - commands.Add(new PlayCommand(factory.FactoryFor, CmdResPrepath + resFactory.FactoryFor)); - resFactories.Add(resFactory); - } - if (factory is IPlaylistFactory listFactory) - { - commands.Add(new PlayListCommand(listFactory, CmdListPrepath + listFactory.FactoryFor)); - listFactories.Add(listFactory); - } - if (factory is ISearchFactory searchFactory) - { - commands.Add(new SearchCommand(searchFactory, CmdSearchPrepath + searchFactory.FactoryFor)); - searchFactories.Add(searchFactory); - } - - var factoryInfo = new FactoryData(factory, commands.ToArray()); - allFacories.Add(factory.FactoryFor, factoryInfo); - commandManager.RegisterCollection(factoryInfo); - } - - public void RemoveFactory(IFactory factory) - { - if (!allFacories.TryGetValue(factory.FactoryFor, out var factoryInfo)) - return; - - allFacories.Remove(factory.FactoryFor); - - if (factory is IResourceFactory resFactory) - resFactories.Remove(resFactory); - if (factory is IPlaylistFactory listFactory) - listFactories.Remove(listFactory); - if (factory is ISearchFactory searchFactory) - searchFactories.Remove(searchFactory); - - commandManager.UnregisterCollection(factoryInfo); - } - - private static LocalStr CouldNotLoad(string reason = null) - { - if (reason is null) - return new LocalStr(strings.error_resfac_could_not_load); - var strb = new StringBuilder(strings.error_resfac_could_not_load); - strb.Append(" (").Append(reason).Append(")"); - return new LocalStr(strb.ToString()); - } - - private static LocalStr ToErrorString(List<(string fact, LocalStr err)> errors) - { - if (errors is null || errors.Count == 0) - throw new ArgumentException("No errors provided", nameof(errors)); - if (errors.Count == 1) - return CouldNotLoad($"{errors[0].fact}: {errors[0].err}"); - return CouldNotLoad(strings.error_resfac_multiple_factories_failed); - } - - public void Dispose() - { - foreach (var factoryInfo in allFacories.Values) - factoryInfo.Factory.Dispose(); - allFacories.Clear(); - } - - private sealed class FactoryData : ICommandBag - { - private readonly FactoryCommand[] registeredCommands; - - public IFactory Factory { get; } - public IReadOnlyCollection BagCommands { get; } - public IReadOnlyCollection AdditionalRights => Array.Empty(); - - public FactoryData(IFactory factory, FactoryCommand[] commands) - { - Factory = factory; - registeredCommands = commands; - BagCommands = registeredCommands.Select(x => x.Command).ToArray(); - } - } - - private abstract class FactoryCommand - { - public BotCommand Command { get; protected set; } - } - - private sealed class PlayCommand : FactoryCommand - { - private static readonly MethodInfo Method = typeof(PlayCommand).GetMethod(nameof(PropagiatePlay)); - private readonly string audioType; - - public PlayCommand(string audioType, string cmdPath) - { - this.audioType = audioType; - var builder = new CommandBuildInfo( - this, - Method, - new CommandAttribute(cmdPath)); - Command = new BotCommand(builder); - } - - public void PropagiatePlay(PlayManager playManager, InvokerData invoker, string url) - { - playManager.Play(invoker, url, audioType).UnwrapThrow(); - } - } - - private sealed class PlayListCommand : FactoryCommand - { - private static readonly MethodInfo Method = typeof(PlayListCommand).GetMethod(nameof(PropagiateLoad)); - private readonly IPlaylistFactory factory; - - public PlayListCommand(IPlaylistFactory factory, string cmdPath) - { - this.factory = factory; - var builder = new CommandBuildInfo( - this, - Method, - new CommandAttribute(cmdPath)); - Command = new BotCommand(builder); - } - - public void PropagiateLoad(ResourceFactory resourceFactory, UserSession session, string url) - { - var playlist = resourceFactory.LoadPlaylistFrom(url, factory).UnwrapThrow(); - - session.Set(SessionConst.Playlist, playlist); - } - } - - private sealed class SearchCommand : FactoryCommand - { - private static readonly MethodInfo Method = typeof(SearchCommand).GetMethod(nameof(PropagiateSearch)); - private readonly ISearchFactory factory; - - public SearchCommand(ISearchFactory factory, string cmdPath) - { - this.factory = factory; - var builder = new CommandBuildInfo( - this, - Method, - new CommandAttribute(cmdPath)); - Command = new BotCommand(builder); - } - - public JsonArray PropagiateSearch(UserSession session, CallerInfo callerInfo, string keyword) - { - var result = factory.Search(keyword); - var list = result.UnwrapThrow(); - session.Set(SessionConst.SearchResult, list); - - return new JsonArray(list, searchResults => - { - if (searchResults.Count == 0) - return strings.cmd_search_no_result; - - var tmb = new TextModBuilder(callerInfo.IsColor); - tmb.AppendFormat( - strings.cmd_search_header.Mod().Bold(), - ("!search play " + strings.info_number).Mod().Italic(), - ("!search add " + strings.info_number).Mod().Italic()).Append("\n"); - for (int i = 0; i < searchResults.Count; i++) - { - tmb.AppendFormat("{0}: {1}\n", i.ToString().Mod().Bold(), searchResults[i].ResourceTitle); - } - - return tmb.ToString(); - }); - } - } - } -} diff --git a/TS3AudioBot/ResourceFactories/ResourceResolver.cs b/TS3AudioBot/ResourceFactories/ResourceResolver.cs new file mode 100644 index 00000000..50dc4b3d --- /dev/null +++ b/TS3AudioBot/ResourceFactories/ResourceResolver.cs @@ -0,0 +1,275 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists; +using TS3AudioBot.ResourceFactories.Youtube; + +namespace TS3AudioBot.ResourceFactories +{ + public sealed class ResourceResolver : IDisposable + { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + + private readonly Dictionary allResolvers = new Dictionary(); + private readonly List listResolvers = new List(); + private readonly List resResolvers = new List(); + private readonly List searchResolvers = new List(); + + public ResourceResolver(ConfFactories conf) + { + AddResolver(new MediaResolver()); + AddResolver(new YoutubeResolver(conf)); + AddResolver(new SoundcloudResolver()); + AddResolver(new TwitchResolver()); + AddResolver(new BandcampResolver()); + } + + private T GetResolverByType(string audioType) where T : class, IResolver => + // ToLower for legacy reasons + allResolvers.TryGetValue(audioType.ToLowerInvariant(), out var resolver) && resolver is T resolverT + ? resolverT + : null; + + private IEnumerable<(IResourceResolver, MatchCertainty)> GetResResolverByLink(ResolveContext ctx, string uri) => + from rsv in resResolvers + let rsvCertain = rsv.MatchResource(ctx, uri) + where rsvCertain != MatchCertainty.Never + orderby rsvCertain descending + select (rsv, rsvCertain); + + private IEnumerable<(IPlaylistResolver, MatchCertainty)> GetListResolverByLink(ResolveContext ctx, string uri) => + from rsv in listResolvers + let rsvCertain = rsv.MatchPlaylist(ctx, uri) + where rsvCertain != MatchCertainty.Never + orderby rsvCertain descending + select (rsv, rsvCertain); + + private static IEnumerable FilterUsable(IEnumerable<(T, MatchCertainty)> enu) + { + var highestCertainty = MatchCertainty.Never; + foreach (var (rsv, cert) in enu) + { + if ((highestCertainty == MatchCertainty.Always && cert < MatchCertainty.Always) + || (highestCertainty > MatchCertainty.Never && cert <= MatchCertainty.OnlyIfLast)) + yield break; + + yield return rsv; + + if (cert > highestCertainty) + highestCertainty = cert; + } + } + + /// Generates a new which can be played. + /// An with at least + /// and set. + /// The playable resource if successful, or an error message otherwise. + public R Load(ResolveContext ctx, AudioResource resource) + { + if (resource is null) + throw new ArgumentNullException(nameof(resource)); + + var resolver = GetResolverByType(resource.AudioType); + if (resolver is null) + return CouldNotLoad(string.Format(strings.error_resfac_no_registered_factory, resource.AudioType)); + + var sw = Stopwatch.StartNew(); + R result; + try + { + result = resolver.GetResourceById(ctx, resource); + } + catch (Exception ex) + { + Log.Error(ex, "Resource resolver '{0}' threw while trying to resolve '{@resource}'", resolver.ResolverFor, resource); + return CouldNotLoad(strings.error_playmgr_internal_error); + } + if (!result.Ok) + return CouldNotLoad(result.Error.Str); + Log.Debug("Took {0}ms to resolve resource.", sw.ElapsedMilliseconds); + return result.Value; + } + + /// Generates a new which can be played. + /// The message used will be cleared of bb-tags. Also lets you pick an + /// identifier to optionally select a resolver. + /// + /// The link/uri to resolve for the resource. + /// The associated resource type string to a resolver. + /// Leave null to let it detect automatically. + /// The playable resource if successful, or an error message otherwise. + public R Load(ResolveContext ctx, string message, string audioType = null) + { + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentNullException(nameof(message)); + + var netlinkurl = TextUtil.ExtractUrlFromBb(message); + + if (audioType != null) + { + var resolver = GetResolverByType(audioType); + if (resolver is null) + return CouldNotLoad(string.Format(strings.error_resfac_no_registered_factory, audioType)); + + var result = resolver.GetResource(ctx, netlinkurl); + if (!result.Ok) + return CouldNotLoad(result.Error.Str); + return result; + } + + var sw = Stopwatch.StartNew(); + var resolvers = FilterUsable(GetResResolverByLink(ctx, netlinkurl)); + List<(string, LocalStr)> errors = null; + foreach (var resolver in resolvers) + { + var result = resolver.GetResource(ctx, netlinkurl); + Log.Trace("Resolver {0} tried, result: {1}", resolver.ResolverFor, result.Ok ? "Ok" : result.Error.Str); + if (result) + return result; + (errors = errors ?? new List<(string, LocalStr)>()).Add((resolver.ResolverFor, result.Error)); + } + Log.Debug("Took {0}ms to resolve resource.", sw.ElapsedMilliseconds); + + return ToErrorString(errors); + } + + public R LoadPlaylistFrom(ResolveContext ctx, string message, string audioType = null) + { + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentNullException(nameof(message)); + + string netlinkurl = TextUtil.ExtractUrlFromBb(message); + + if (audioType != null) + { + var resolver = GetResolverByType(audioType); + if (resolver is null) + return CouldNotLoad(string.Format(strings.error_resfac_no_registered_factory, audioType)); + + var result = resolver.GetPlaylist(ctx, netlinkurl); + if (!result.Ok) + return CouldNotLoad(result.Error.Str); + return result; + } + + var resolvers = FilterUsable(GetListResolverByLink(ctx, netlinkurl)); + List<(string, LocalStr)> errors = null; + foreach (var resolver in resolvers) + { + var result = resolver.GetPlaylist(ctx, netlinkurl); + Log.Trace("ListResolver {0} tried, result: {1}", resolver.ResolverFor, result.Ok ? "Ok" : result.Error.Str); + if (result) + return result; + (errors = errors ?? new List<(string, LocalStr)>()).Add((resolver.ResolverFor, result.Error)); + } + + return ToErrorString(errors); + } + + public R RestoreLink(ResolveContext ctx, AudioResource res) + { + var resolver = GetResolverByType(res.AudioType); + if (resolver is null) + return CouldNotLoad(); + return resolver.RestoreLink(ctx, res); + } + + public R GetThumbnail(ResolveContext ctx, PlayResource playResource) + { + var resolver = GetResolverByType(playResource.BaseData.AudioType); + if (resolver is null) + return new LocalStr(string.Format(strings.error_resfac_no_registered_factory, playResource.BaseData.AudioType)); + + var sw = Stopwatch.StartNew(); + var result = resolver.GetThumbnail(ctx, playResource); + Log.Debug("Took {0}ms to load thumbnail.", sw.ElapsedMilliseconds); + return result; + } + + public R, LocalStr> Search(ResolveContext ctx, string resolverName, string query) + { + var resolver = GetResolverByType(resolverName); + if (resolver is null) + return CouldNotLoad(string.Format(strings.error_resfac_no_registered_factory, resolverName)); + return resolver.Search(ctx, query); + } + + public void AddResolver(IResolver resolver) + { + if (resolver.ResolverFor.ToLowerInvariant() != resolver.ResolverFor) + throw new ArgumentException($"The resolver audio type \"{nameof(IResolver.ResolverFor)}\" must be in lower case.", nameof(resolver)); + if (allResolvers.ContainsKey(resolver.ResolverFor)) + throw new ArgumentException("A resolver for this type already has been registered.", nameof(resolver)); + + if (resolver is IResourceResolver resResolver) + { + resResolvers.Add(resResolver); + } + if (resolver is IPlaylistResolver listResolver) + { + listResolvers.Add(listResolver); + } + if (resolver is ISearchResolver searchResolver) + { + searchResolvers.Add(searchResolver); + } + + allResolvers.Add(resolver.ResolverFor, resolver); + } + + public void RemoveResolver(IResolver Resolver) + { + if (!allResolvers.TryGetValue(Resolver.ResolverFor, out var resolverInfo)) + return; + + allResolvers.Remove(Resolver.ResolverFor); + + if (Resolver is IResourceResolver resResolver) + resResolvers.Remove(resResolver); + if (Resolver is IPlaylistResolver listResolver) + listResolvers.Remove(listResolver); + if (Resolver is ISearchResolver searchResolver) + searchResolvers.Remove(searchResolver); + } + + private static LocalStr CouldNotLoad(string reason = null) + { + if (reason is null) + return new LocalStr(strings.error_resfac_could_not_load); + var strb = new StringBuilder(strings.error_resfac_could_not_load); + strb.Append(" (").Append(reason).Append(")"); + return new LocalStr(strb.ToString()); + } + + private static LocalStr ToErrorString(List<(string rsv, LocalStr err)> errors) + { + if (errors is null || errors.Count == 0) + throw new ArgumentException("No errors provided", nameof(errors)); + if (errors.Count == 1) + return CouldNotLoad($"{errors[0].rsv}: {errors[0].err}"); + return CouldNotLoad(strings.error_resfac_multiple_factories_failed); + } + + public void Dispose() + { + foreach (var resolver in allResolvers.Values) + resolver.Dispose(); + allResolvers.Clear(); + } + } +} diff --git a/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs b/TS3AudioBot/ResourceFactories/SoundcloudResolver.cs similarity index 76% rename from TS3AudioBot/ResourceFactories/SoundcloudFactory.cs rename to TS3AudioBot/ResourceFactories/SoundcloudResolver.cs index aa970677..d7b7a2c2 100644 --- a/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs +++ b/TS3AudioBot/ResourceFactories/SoundcloudResolver.cs @@ -7,20 +7,20 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists; + namespace TS3AudioBot.ResourceFactories { - using Helper; - using Localization; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using Playlists; - using System; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text.RegularExpressions; - - public sealed class SoundcloudFactory : IResourceFactory, IPlaylistFactory, IThumbnailFactory + public sealed class SoundcloudResolver : IResourceResolver, IPlaylistResolver, IThumbnailResolver { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex SoundcloudLink = new Regex(@"^https?\:\/\/(www\.)?soundcloud\.", Util.DefaultRegexConfig); @@ -29,13 +29,13 @@ public sealed class SoundcloudFactory : IResourceFactory, IPlaylistFactory, IThu private const string AddArtist = "artist"; private const string AddTrack = "track"; - public string FactoryFor => "soundcloud"; + public string ResolverFor => "soundcloud"; - public MatchCertainty MatchResource(string uri) => SoundcloudLink.IsMatch(uri).ToMatchCertainty(); + public MatchCertainty MatchResource(ResolveContext _, string uri) => SoundcloudLink.IsMatch(uri).ToMatchCertainty(); - public MatchCertainty MatchPlaylist(string uri) => MatchResource(uri); + public MatchCertainty MatchPlaylist(ResolveContext _, string uri) => MatchResource(null, uri); - public R GetResource(string uri) + public R GetResource(ResolveContext _, string uri) { var uriObj = new Uri($"https://api.soundcloud.com/resolve.json?url={Uri.EscapeUriString(uri)}&client_id={SoundcloudClientId}"); if (!WebWrapper.DownloadString(out string jsonResponse, uriObj)) @@ -51,26 +51,26 @@ public R GetResource(string uri) return GetResourceById(resource, false); } - public R GetResourceById(AudioResource resource) => GetResourceById(resource, true); + public R GetResourceById(ResolveContext _, AudioResource resource) => GetResourceById(resource, true); private R GetResourceById(AudioResource resource, bool allowNullName) { if (SoundcloudLink.IsMatch(resource.ResourceId)) - return GetResource(resource.ResourceId); + return GetResource(null, resource.ResourceId); if (resource.ResourceTitle is null) { if (!allowNullName) return new LocalStr(strings.error_media_internal_missing + " (title)"); - string link = RestoreLink(resource); + string link = RestoreLink(null, resource); if (link is null) return new LocalStr(strings.error_media_internal_missing + " (link)"); - return GetResource(link); + return GetResource(null, link); } string finalRequest = $"https://api.soundcloud.com/tracks/{resource.ResourceId}/stream?client_id={SoundcloudClientId}"; return new PlayResource(finalRequest, resource); } - public string RestoreLink(AudioResource resource) + public string RestoreLink(ResolveContext _, AudioResource resource) { var artistName = resource.Get(AddArtist); var trackName = resource.Get(AddTrack); @@ -99,7 +99,7 @@ private AudioResource CheckAndGet(JsonTrackInfo track) return new AudioResource( track.id.ToString(CultureInfo.InvariantCulture), track.title, - FactoryFor) + ResolverFor) .Add(AddArtist, track.user.permalink) .Add(AddTrack, track.permalink); } @@ -108,20 +108,24 @@ private R YoutubeDlWrapped(string link) { Log.Debug("Falling back to youtube-dl!"); - var result = YoutubeDlHelper.FindAndRunYoutubeDl(link); + var result = YoutubeDlHelper.GetSingleVideo(link); if (!result.Ok) return result.Error; - var (title, urls) = result.Value; - if (urls.Count == 0 || string.IsNullOrEmpty(title) || string.IsNullOrEmpty(urls[0])) + var response = result.Value; + var title = response.title ?? $"Soundcloud-{link}"; + var format = YoutubeDlHelper.FilterBest(response.formats); + var url = format?.url; + + if (string.IsNullOrEmpty(url)) return new LocalStr(strings.error_ytdl_empty_response); Log.Debug("youtube-dl succeeded!"); - return new PlayResource(urls[0], new AudioResource(link, title, FactoryFor)); + return new PlayResource(url, new AudioResource(link, title, ResolverFor)); } - public R GetPlaylist(string url) + public R GetPlaylist(ResolveContext _, string url) { var uri = new Uri($"https://api.soundcloud.com/resolve.json?url={Uri.EscapeUriString(url)}&client_id={SoundcloudClientId}"); if (!WebWrapper.DownloadString(out string jsonResponse, uri)) @@ -149,7 +153,7 @@ public R GetPlaylist(string url) return plist; } - public R GetThumbnail(PlayResource playResource) + public R GetThumbnail(ResolveContext _, PlayResource playResource) { var uri = new Uri($"https://api.soundcloud.com/tracks/{playResource.BaseData.ResourceId}?client_id={SoundcloudClientId}"); if (!WebWrapper.DownloadString(out string jsonResponse, uri)) @@ -159,8 +163,7 @@ public R GetThumbnail(PlayResource playResource) if (parsedDict is null) return new LocalStr(strings.error_media_internal_missing + " (parsedDict)"); - var imgUrl = parsedDict.TryCast("artwork_url").OkOr(null); - if (imgUrl is null) + if (!parsedDict.TryCast("artwork_url", out var imgUrl)) return new LocalStr(strings.error_media_internal_missing + " (artwork_url)"); // t500x500: 500px×500px diff --git a/TS3AudioBot/ResourceFactories/TwitchFactory.cs b/TS3AudioBot/ResourceFactories/TwitchResolver.cs similarity index 71% rename from TS3AudioBot/ResourceFactories/TwitchFactory.cs rename to TS3AudioBot/ResourceFactories/TwitchResolver.cs index 9ea88e9f..b39d420f 100644 --- a/TS3AudioBot/ResourceFactories/TwitchFactory.cs +++ b/TS3AudioBot/ResourceFactories/TwitchResolver.cs @@ -7,51 +7,61 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; + namespace TS3AudioBot.ResourceFactories { - using Helper; - using Localization; - using Newtonsoft.Json.Linq; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Text.RegularExpressions; - - public sealed class TwitchFactory : IResourceFactory + public sealed class TwitchResolver : IResourceResolver { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex TwitchMatch = new Regex(@"^(https?://)?(www\.)?twitch\.tv/(\w+)", Util.DefaultRegexConfig); private static readonly Regex M3U8ExtMatch = new Regex(@"#([\w-]+)(:(([\w-]+)=(""[^""]*""|[^,]+),?)*)?", Util.DefaultRegexConfig); private const string TwitchClientId = "t9nlhlxnfux3gk2d6z1p093rj2c71i3"; + // See: https://github.com/streamlink/streamlink/issues/2680 + private const string TwitchClientIdPrivate = "kimne78kx3ncx6brgo4mv6wki5h1ko"; - public string FactoryFor => "twitch"; + public string ResolverFor => "twitch"; - public MatchCertainty MatchResource(string uri) => TwitchMatch.IsMatch(uri).ToMatchCertainty(); + public MatchCertainty MatchResource(ResolveContext _, string uri) => TwitchMatch.IsMatch(uri).ToMatchCertainty(); - public R GetResource(string uri) + public R GetResource(ResolveContext _, string uri) { var match = TwitchMatch.Match(uri); if (!match.Success) return new LocalStr(strings.error_media_invalid_uri); - return GetResourceById(new AudioResource(match.Groups[3].Value, null, FactoryFor)); + return GetResourceById(null, new AudioResource(match.Groups[3].Value, null, ResolverFor)); } - public R GetResourceById(AudioResource resource) + public R GetResourceById(ResolveContext _, AudioResource resource) { var channel = resource.ResourceId; // request api token - if (!WebWrapper.DownloadString(out string jsonResponse, new Uri($"https://api.twitch.tv/api/channels/{channel}/access_token"), ("Client-ID", TwitchClientId))) + if (!WebWrapper.DownloadString(out string jsonResponse, new Uri($"https://api.twitch.tv/api/channels/{channel}/access_token"), ("Client-ID", TwitchClientIdPrivate))) return new LocalStr(strings.error_net_no_connection); - var jObj = JObject.Parse(jsonResponse); + JsonAccessToken access; + try + { + access = JsonConvert.DeserializeObject(jsonResponse); + } + catch (Exception ex) + { + Log.Debug(ex, "Failed to parse jsonResponse. (Data: {0})", jsonResponse); + return new LocalStr(strings.error_media_internal_invalid + " (jsonResponse)"); + } // request m3u8 file - var tokenResult = jObj.TryCast("token"); - var sigResult = jObj.TryCast("sig"); - if (!tokenResult.Ok || !sigResult.Ok) + if (access.token is null || access.sig is null) return new LocalStr(strings.error_media_internal_invalid + " (tokenResult|sigResult)"); - var token = Uri.EscapeUriString(tokenResult.Value); - var sig = sigResult.Value; + var token = Uri.EscapeUriString(access.token); + var sig = access.sig; // guaranteed to be random, chosen by fair dice roll. const int random = 4; if (!WebWrapper.DownloadString(out string m3u8, new Uri($"http://usher.twitch.tv/api/channel/hls/{channel}.m3u8?player=twitchweb&&token={token}&sig={sig}&allow_audio_only=true&allow_source=true&type=any&p={random}"))) @@ -129,9 +139,18 @@ public R GetResourceById(AudioResource resource) private static int SelectStream(List list) => list.FindIndex(s => s.QualityType == StreamQuality.audio_only); - public string RestoreLink(AudioResource resource) => "https://www.twitch.tv/" + resource.ResourceId; + public string RestoreLink(ResolveContext _, AudioResource resource) => "https://www.twitch.tv/" + resource.ResourceId; public void Dispose() { } + +#pragma warning disable IDE1006 // Naming Styles + private class JsonAccessToken + { + public string token { get; set; } + public string sig { get; set; } + public DateTime expires_at { get; set; } + } +#pragma warning restore IDE1006 // Naming Styles } public sealed class StreamData diff --git a/TS3AudioBot/ResourceFactories/Youtube/Json.cs b/TS3AudioBot/ResourceFactories/Youtube/Json.cs new file mode 100644 index 00000000..6206f618 --- /dev/null +++ b/TS3AudioBot/ResourceFactories/Youtube/Json.cs @@ -0,0 +1,74 @@ +namespace TS3AudioBot.ResourceFactories.Youtube +{ +#pragma warning disable CS0649, CS0169, IDE1006 + // ReSharper disable ClassNeverInstantiated.Local, InconsistentNaming + public class JsonVideoListResponse // # youtube#videoListResponse + { + public string nextPageToken { get; set; } + public JsonVideo[] items { get; set; } + } + public class JsonVideo // youtube#video + { + public JsonContentDetails contentDetails { get; set; } + public JsonSnippet snippet { get; set; } + } + public class JsonSearchListResponse // youtube#searchListResponse + { + public JsonSearchResult[] items { get; set; } + } + public class JsonSearchResult // youtube#searchResult + { + public JsonContentDetails id { get; set; } + public JsonSnippet snippet { get; set; } + } + public class JsonContentDetails + { + public string videoId { get; set; } + } + public class JsonSnippet + { + public string title { get; set; } + public JsonThumbnailList thumbnails { get; set; } + } + public class JsonThumbnailList + { + public JsonThumbnail @default { get; set; } + public JsonThumbnail medium { get; set; } + public JsonThumbnail high { get; set; } + public JsonThumbnail standard { get; set; } + public JsonThumbnail maxres { get; set; } + } + public class JsonThumbnail + { + public string url { get; set; } + public int heigth { get; set; } + public int width { get; set; } + } + // Custom json + public class JsonPlayerResponse + { + public JsonStreamingData streamingData { get; set; } + public JsonVideoDetails videoDetails { get; set; } + } + public class JsonStreamingData + { + public string dashManifestUrl { get; set; } + public string hlsManifestUrl { get; set; } + } + public class JsonVideoDetails + { + public string title { get; set; } + public bool? isLive { get; set; } + public bool useCipher { get; set; } + public bool isLiveContent { get; set; } + } + public class JsonPlayFormat + { + public string mimeType { get; set; } + public int bitrate { get; set; } + public string cipher { get; set; } + public string url { get; set; } + } + // ReSharper enable ClassNeverInstantiated.Local, InconsistentNaming +#pragma warning restore CS0649, CS0169, IDE1006 +} diff --git a/TS3AudioBot/Audio/PlaySource.cs b/TS3AudioBot/ResourceFactories/Youtube/LoaderPriority.cs similarity index 79% rename from TS3AudioBot/Audio/PlaySource.cs rename to TS3AudioBot/ResourceFactories/Youtube/LoaderPriority.cs index ba7de72a..b5e4a0a0 100644 --- a/TS3AudioBot/Audio/PlaySource.cs +++ b/TS3AudioBot/ResourceFactories/Youtube/LoaderPriority.cs @@ -7,12 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3AudioBot.Audio +namespace TS3AudioBot.ResourceFactories.Youtube { - public enum PlaySource + public enum LoaderPriority { - PlayRequest, - FromQueue, - FromPlaylist, + Internal, + YoutubeDl } } diff --git a/TS3AudioBot/CommandSystem/CommandResults/CommandResultType.cs b/TS3AudioBot/ResourceFactories/Youtube/VideoCodec.cs similarity index 76% rename from TS3AudioBot/CommandSystem/CommandResults/CommandResultType.cs rename to TS3AudioBot/ResourceFactories/Youtube/VideoCodec.cs index cce375cb..003e510a 100644 --- a/TS3AudioBot/CommandSystem/CommandResults/CommandResultType.cs +++ b/TS3AudioBot/ResourceFactories/Youtube/VideoCodec.cs @@ -7,13 +7,15 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3AudioBot.CommandSystem.CommandResults +namespace TS3AudioBot.ResourceFactories.Youtube { - public enum CommandResultType + public enum VideoCodec { - Empty, - Command, - String, - Json, + Unknown, + Mp4, + M4A, + Webm, + Flv, + ThreeGp, } } diff --git a/TS3AudioBot/ResourceFactories/Youtube/VideoData.cs b/TS3AudioBot/ResourceFactories/Youtube/VideoData.cs new file mode 100644 index 00000000..723fb8bd --- /dev/null +++ b/TS3AudioBot/ResourceFactories/Youtube/VideoData.cs @@ -0,0 +1,22 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +namespace TS3AudioBot.ResourceFactories.Youtube +{ + public sealed class VideoData + { + public string Link { get; set; } + public string Qualitydesciption { get; set; } + public VideoCodec Codec { get; set; } + public bool AudioOnly { get; set; } + public bool VideoOnly { get; set; } + + public override string ToString() => $"{Qualitydesciption} @ {Codec} - {Link}"; + } +} diff --git a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs b/TS3AudioBot/ResourceFactories/Youtube/YoutubeResolver.cs similarity index 66% rename from TS3AudioBot/ResourceFactories/YoutubeFactory.cs rename to TS3AudioBot/ResourceFactories/Youtube/YoutubeResolver.cs index c816bc9f..ebfc02eb 100644 --- a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs +++ b/TS3AudioBot/ResourceFactories/Youtube/YoutubeResolver.cs @@ -7,19 +7,21 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3AudioBot.ResourceFactories +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.Playlists; +using TSLib.Helper; + +namespace TS3AudioBot.ResourceFactories.Youtube { - using Helper; - using Localization; - using Newtonsoft.Json; - using Playlists; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text.RegularExpressions; - - public sealed class YoutubeFactory : IResourceFactory, IPlaylistFactory, IThumbnailFactory, ISearchFactory + public sealed class YoutubeResolver : IResourceResolver, IPlaylistResolver, IThumbnailResolver, ISearchResolver { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex IdMatch = new Regex(@"((&|\?)v=|youtu\.be\/)([\w\-_]{11})", Util.DefaultRegexConfig); @@ -27,32 +29,48 @@ public sealed class YoutubeFactory : IResourceFactory, IPlaylistFactory, IThumbn private static readonly Regex ListMatch = new Regex(@"(&|\?)list=([\w\-_]+)", Util.DefaultRegexConfig); private static readonly Regex StreamCodecMatch = new Regex(@"CODECS=""([^""]*)""", Util.DefaultRegexConfig); private static readonly Regex StreamBitrateMatch = new Regex(@"BANDWIDTH=(\d+)", Util.DefaultRegexConfig); - private const string YoutubeProjectId = "AIzaSyBOqG5LUbGSkBfRUoYfUUea37-5xlEyxNs"; + private string YoutubeProjectId => conf.ApiKey.Value; + private readonly ConfResolverYoutube conf; - public string FactoryFor => "youtube"; + public YoutubeResolver(ConfFactories conf) + { + this.conf = conf.Youtube; + } + + public string ResolverFor => "youtube"; - MatchCertainty IResourceFactory.MatchResource(string uri) => + public MatchCertainty MatchResource(ResolveContext _, string uri) => LinkMatch.IsMatch(uri) || IdMatch.IsMatch(uri) ? MatchCertainty.Always : MatchCertainty.Never; - MatchCertainty IPlaylistFactory.MatchPlaylist(string uri) => ListMatch.IsMatch(uri) ? MatchCertainty.Always : MatchCertainty.Never; + public MatchCertainty MatchPlaylist(ResolveContext _, string uri) => ListMatch.IsMatch(uri) ? MatchCertainty.Always : MatchCertainty.Never; - public R GetResource(string uri) + public R GetResource(ResolveContext _, string uri) { Match matchYtId = IdMatch.Match(uri); if (!matchYtId.Success) return new LocalStr(strings.error_media_failed_to_parse_id); - return GetResourceById(new AudioResource(matchYtId.Groups[3].Value, null, FactoryFor)); + return GetResourceById(null, new AudioResource(matchYtId.Groups[3].Value, null, ResolverFor)); } - public R GetResourceById(AudioResource resource) + public R GetResourceById(ResolveContext _, AudioResource resource) { - var result = ResolveResourceInternal(resource); - if (result.Ok) - return result; + var priority = conf.ResolverPriority.Value; + switch (priority) + { + case LoaderPriority.Internal: + var result = ResolveResourceInternal(resource); + if (result.Ok) + return result; + goto case LoaderPriority.YoutubeDl; + + case LoaderPriority.YoutubeDl: + return YoutubeDlWrapped(resource); - return YoutubeDlWrapped(resource); + default: + throw Tools.UnhandledDefault(priority); + } } private R ResolveResourceInternal(AudioResource resource) @@ -213,7 +231,7 @@ private static void ParseAdaptiveFmt(List videoDataUnsplit, List "https://youtu.be/" + resource.ResourceId; + public string RestoreLink(ResolveContext _, AudioResource resource) => "https://youtu.be/" + resource.ResourceId; private static int SelectStream(List list) { @@ -272,14 +290,22 @@ private static VideoCodec GetCodec(string type) } } - public R GetPlaylist(string url) + public R GetPlaylist(ResolveContext _, string url) { Match matchYtId = ListMatch.Match(url); if (!matchYtId.Success) return new LocalStr(strings.error_media_failed_to_parse_id); string id = matchYtId.Groups[2].Value; - var plist = new Playlist().SetTitle(id); // TODO TITLE !!!!!!!!! + if (string.IsNullOrEmpty(YoutubeProjectId)) + return GetPlaylistYoutubeDl(id); + else + return GetPlaylistYoutubeApi(id); + } + + private R GetPlaylistYoutubeApi(string id) + { + var plist = new Playlist().SetTitle(id); string nextToken = null; do @@ -290,7 +316,7 @@ public R GetPlaylist(string url) + "&fields=" + Uri.EscapeDataString("items(contentDetails/videoId,snippet/title),nextPageToken") + "&maxResults=50" + "&playlistId=" + id - + (nextToken != null ? ("&pageToken=" + nextToken) : string.Empty) + + (nextToken != null ? "&pageToken=" + nextToken : string.Empty) + "&key=" + YoutubeProjectId); if (!WebWrapper.DownloadString(out string response, queryString)) @@ -303,7 +329,7 @@ public R GetPlaylist(string url) new AudioResource( item.contentDetails.videoId, item.snippet.title, - FactoryFor + ResolverFor ) ) ) @@ -315,37 +341,42 @@ public R GetPlaylist(string url) return plist; } + private R GetPlaylistYoutubeDl(string id) + { + var result = YoutubeDlHelper.GetPlaylist(id); + if (!result.Ok) + return result.Error; + + var plistData = result.Value; + var plist = new Playlist().SetTitle(plistData.title); + plist.AddRange(plistData.entries.Select(entry => + new PlaylistItem( + new AudioResource( + entry.id, + entry.title, + ResolverFor + ) + ))); + + return plist; + } + private static R YoutubeDlWrapped(AudioResource resource) { Log.Debug("Falling back to youtube-dl!"); - var result = YoutubeDlHelper.FindAndRunYoutubeDl(resource.ResourceId); + var result = YoutubeDlHelper.GetSingleVideo(resource.ResourceId); if (!result.Ok) return result.Error; var response = result.Value; - var title = response.title; - var urlOptions = response.links; + resource.ResourceTitle = response.track ?? response.title ?? $"Youtube-{resource.ResourceId}"; + var format = YoutubeDlHelper.FilterBest(response.formats); + string url = format?.url; - string url = null; - if (urlOptions.Count == 1) - { - url = urlOptions[0]; - } - else if (urlOptions.Count >= 1) - { - Uri[] uriList = urlOptions.Select(s => new Uri(s)).ToArray(); - Uri bestMatch = uriList - .FirstOrDefault(u => ParseQueryString(u.Query).TryGetValue("mime", out var mimes) - && mimes.Any(x => x.StartsWith("audio", StringComparison.OrdinalIgnoreCase))); - url = (bestMatch ?? uriList[0]).OriginalString; - } - - if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(url)) + if (string.IsNullOrEmpty(url)) return new LocalStr(strings.error_ytdl_empty_response); - resource.ResourceTitle = title; - Log.Debug("youtube-dl succeeded!"); return new PlayResource(url, resource); } @@ -360,33 +391,34 @@ public static Dictionary> ParseQueryString(string requestQu int index = row.IndexOf('='); if (index < 0) continue; var param = Uri.UnescapeDataString(row.Substring(0, index).Replace('+', ' ')); - if (!rc.TryGetValue(param, out var list)) - { - list = new List(); - rc[param] = list; - } + + var list = rc.GetOrNew(param); list.Add(Uri.UnescapeDataString(row.Substring(index + 1).Replace('+', ' '))); } return rc; } - public R GetThumbnail(PlayResource playResource) + public R GetThumbnail(ResolveContext _, PlayResource playResource) { - if (!WebWrapper.DownloadString(out string response, - new Uri($"https://www.googleapis.com/youtube/v3/videos?part=snippet&id={playResource.BaseData.ResourceId}&key={YoutubeProjectId}"))) - return new LocalStr(strings.error_net_no_connection); - var parsed = JsonConvert.DeserializeObject(response); - - // default: 120px/ 90px - // medium : 320px/180px - // high : 480px/360px - var imgurl = new Uri(parsed.items[0].snippet.thumbnails.medium.url); + // default : 120px/ 90px /default.jpg + // medium : 320px/180px /mqdefault.jpg + // high : 480px/360px /hqdefault.jpg + // standard : 640px/480px /sddefault.jpg + // maxres : 1280px/720px /maxresdefault.jpg + var imgurl = new Uri($"https://i.ytimg.com/vi/{playResource.BaseData.ResourceId}/mqdefault.jpg"); return WebWrapper.GetResponseUnsafe(imgurl); } - public R, LocalStr> Search(string keyword) + public R, LocalStr> Search(ResolveContext _, string keyword) + { + if (string.IsNullOrEmpty(YoutubeProjectId)) + return SearchYoutubeDl(keyword); + else + return SearchYoutubeApi(keyword); + } + + public R, LocalStr> SearchYoutubeApi(string keyword) { - // TODO checkout https://developers.google.com/youtube/v3/docs/search/list ->relatedToVideoId for auto radio play const int maxResults = 10; if (!WebWrapper.DownloadString(out string response, new Uri("https://www.googleapis.com/youtube/v3/search" @@ -403,102 +435,24 @@ public R, LocalStr> Search(string keyword) return parsed.items.Select(x => new AudioResource( x.id.videoId, x.snippet.title, - FactoryFor)).ToArray(); + ResolverFor)).ToArray(); } - public void Dispose() { } - -#pragma warning disable CS0649, CS0169, IDE1006 - // ReSharper disable ClassNeverInstantiated.Local, InconsistentNaming - private class JsonVideoListResponse // # youtube#videoListResponse + public R, LocalStr> SearchYoutubeDl(string keyword) { - public string nextPageToken { get; set; } - public JsonVideo[] items { get; set; } - } - private class JsonVideo // youtube#video - { - public JsonContentDetails contentDetails { get; set; } - public JsonSnippet snippet { get; set; } - } - private class JsonSearchListResponse // youtube#searchListResponse - { - public JsonSearchResult[] items { get; set; } - } - private class JsonSearchResult // youtube#searchResult - { - public JsonContentDetails id { get; set; } - public JsonSnippet snippet { get; set; } - } - private class JsonContentDetails - { - public string videoId { get; set; } - } - private class JsonSnippet - { - public string title { get; set; } - public JsonThumbnailList thumbnails { get; set; } - } - private class JsonThumbnailList - { - public JsonThumbnail @default { get; set; } - public JsonThumbnail medium { get; set; } - public JsonThumbnail high { get; set; } - public JsonThumbnail standard { get; set; } - public JsonThumbnail maxres { get; set; } - } - private class JsonThumbnail - { - public string url { get; set; } - public int heigth { get; set; } - public int width { get; set; } - } - // Custom json - private class JsonPlayerResponse - { - public JsonStreamingData streamingData { get; set; } - public JsonVideoDetails videoDetails { get; set; } - } - private class JsonStreamingData - { - public string dashManifestUrl { get; set; } - public string hlsManifestUrl { get; set; } - } - private class JsonVideoDetails - { - public string title { get; set; } - public bool? isLive { get; set; } - public bool useCipher { get; set; } - public bool isLiveContent { get; set; } - } - private class JsonPlayFormat - { - public string mimeType { get; set; } - public int bitrate { get; set; } - public string cipher { get; set; } - public string url { get; set; } + var result = YoutubeDlHelper.GetSearch(keyword); + if (!result.Ok) + return result.Error; + var search = result.Value; + + return search.entries.Select(entry => + new AudioResource( + entry.id, + entry.title, + ResolverFor + )).ToArray(); } - // ReSharper enable ClassNeverInstantiated.Local, InconsistentNaming -#pragma warning restore CS0649, CS0169, IDE1006 - } - - public sealed class VideoData - { - public string Link { get; set; } - public string Qualitydesciption { get; set; } - public VideoCodec Codec { get; set; } - public bool AudioOnly { get; set; } - public bool VideoOnly { get; set; } - public override string ToString() => $"{Qualitydesciption} @ {Codec} - {Link}"; - } - - public enum VideoCodec - { - Unknown, - Mp4, - M4A, - Webm, - Flv, - ThreeGp, + public void Dispose() { } } } diff --git a/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs b/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs index ff331704..00a45265 100644 --- a/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs +++ b/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs @@ -7,42 +7,69 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; + namespace TS3AudioBot.ResourceFactories { - using Config; - using Localization; - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Diagnostics; - using System.IO; - - internal static class YoutubeDlHelper + public static class YoutubeDlHelper { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); public static ConfPath DataObj { private get; set; } private static string YoutubeDlPath => DataObj?.Path.Value; - public static R<(string title, IList links), LocalStr> FindAndRunYoutubeDl(string id) + const string ParamGetSingleVideo = " --no-warnings --dump-json --id --"; + const string ParamGetPlaylist = "--no-warnings --yes-playlist --flat-playlist --dump-single-json --id --"; + const string ParamGetSearch = "--no-warnings --flat-playlist --dump-single-json -- ytsearch10:"; + + public static R GetSingleVideo(string id) + { + var ytdlPath = FindYoutubeDl(); + if (ytdlPath is null) + return new LocalStr(strings.error_ytdl_not_found); + + var param = $"{ytdlPath.Value.param}{ParamGetSingleVideo} {id}"; + return RunYoutubeDl(ytdlPath.Value.ytdlpath, param); + } + + public static R GetPlaylist(string id) { - var ytdlPath = FindYoutubeDl(id); + var ytdlPath = FindYoutubeDl(); if (ytdlPath is null) return new LocalStr(strings.error_ytdl_not_found); - return RunYoutubeDl(ytdlPath.Value.ytdlpath, ytdlPath.Value.param); + var param = $"{ytdlPath.Value.param}{ParamGetPlaylist} {id}"; + return RunYoutubeDl(ytdlPath.Value.ytdlpath, param); } - public static (string ytdlpath, string param)? FindYoutubeDl(string id) + public static R GetSearch(string text) { - string param = $"--no-warnings --get-title --get-url --format bestaudio/best --id -- {id}"; + var ytdlPath = FindYoutubeDl(); + if (ytdlPath is null) + return new LocalStr(strings.error_ytdl_not_found); + + var param = $"{ytdlPath.Value.param}{ParamGetSearch}\"{text}\""; + return RunYoutubeDl(ytdlPath.Value.ytdlpath, param); + } + public static (string ytdlpath, string param)? FindYoutubeDl() + { var youtubeDlPath = YoutubeDlPath; if (string.IsNullOrEmpty(youtubeDlPath)) { // Default path youtube-dl is suggesting to install const string defaultYtDlPath = "/usr/local/bin/youtube-dl"; if (File.Exists(defaultYtDlPath)) - return (defaultYtDlPath, param); + return (defaultYtDlPath, ""); youtubeDlPath = Directory.GetCurrentDirectory(); } @@ -57,25 +84,29 @@ public static (string ytdlpath, string param)? FindYoutubeDl(string id) // Example: /home/teamspeak/youtube-dl where 'youtube-dl' is the binary if (File.Exists(fullCustomPath) || File.Exists(fullCustomPath + ".exe")) - return (fullCustomPath, param); + return (fullCustomPath, ""); // Example: /home/teamspeak where the binary 'youtube-dl' lies in ./teamspeak/ string fullCustomPathWithoutFile = Path.Combine(fullCustomPath, "youtube-dl"); if (File.Exists(fullCustomPathWithoutFile) || File.Exists(fullCustomPathWithoutFile + ".exe")) - return (fullCustomPathWithoutFile, param); + return (fullCustomPathWithoutFile, ""); // Example: /home/teamspeak/youtube-dl where 'youtube-dl' is the github project folder string fullCustomPathGhProject = Path.Combine(fullCustomPath, "youtube_dl", "__main__.py"); if (File.Exists(fullCustomPathGhProject)) - return ("python", $"\"{fullCustomPathGhProject}\" {param}"); + return ("python", $"\"{fullCustomPathGhProject}\""); return null; } - public static R<(string title, IList links), LocalStr> RunYoutubeDl(string path, string args) + public static R RunYoutubeDl(string path, string args) { try { + bool stdOutDone = false; + var stdOut = new StringBuilder(); + var stdErr = new StringBuilder(); + using (var tmproc = new Process()) { tmproc.StartInfo.FileName = path; @@ -86,19 +117,42 @@ public static (string ytdlpath, string param)? FindYoutubeDl(string id) tmproc.StartInfo.RedirectStandardError = true; tmproc.EnableRaisingEvents = true; tmproc.Start(); - tmproc.WaitForExit(10000); + tmproc.OutputDataReceived += (s, e) => + { + if (e.Data is null) + stdOutDone = true; + else + stdOut.Append(e.Data); + }; + tmproc.ErrorDataReceived += (s, e) => stdErr.Append(e.Data); + tmproc.BeginOutputReadLine(); + tmproc.BeginErrorReadLine(); + tmproc.WaitForExit(20000); + + if (!tmproc.HasExitedSafe()) + { + try { tmproc.Kill(); } + catch (Exception ex) { Log.Debug(ex, "Failed to kill"); } + } - using (var reader = tmproc.StandardError) + var timeout = Stopwatch.StartNew(); + while (!stdOutDone) { - string result = reader.ReadToEnd(); - if (!string.IsNullOrEmpty(result)) + if (timeout.Elapsed >= TimeSpan.FromSeconds(5)) { - Log.Error("youtube-dl failed to load the resource:\n{0}", result); - return new LocalStr(strings.error_ytdl_song_failed_to_load); + stdErr.Append(strings.error_ytdl_empty_response).Append(" (timeout)"); + break; } + Thread.Sleep(50); } - return ParseResponse(tmproc.StandardOutput); + if (stdErr.Length > 0) + { + Log.Debug("youtube-dl failed to load the resource:\n{0}", stdErr); + return new LocalStr(strings.error_ytdl_song_failed_to_load); + } + + return ParseResponse(stdOut.ToString()); } } catch (Win32Exception ex) @@ -108,16 +162,88 @@ public static (string ytdlpath, string param)? FindYoutubeDl(string id) } } - public static (string title, IList links) ParseResponse(StreamReader stream) + public static R ParseResponse(string json) { - string title = stream.ReadLine(); + try + { + if (string.IsNullOrEmpty(json)) + return new LocalStr(strings.error_ytdl_empty_response); - var urlOptions = new List(); - string line; - while ((line = stream.ReadLine()) != null) - urlOptions.Add(line); + return JsonConvert.DeserializeObject(json); + } + catch (Exception ex) + { + Log.Debug(ex, "Failed to read youtube-dl json data"); + return new LocalStr(strings.error_media_internal_invalid); + } + } - return (title, urlOptions); + public static JsonYtdlFormat FilterBest(IEnumerable formats) + { + JsonYtdlFormat best = null; + foreach (var format in formats) + { + if (format.acodec == "none") + continue; + if (best == null + || format.abr > best.abr + || (format.vcodec == "none" && format.abr >= best.abr)) + { + best = format; + } + } + return best; } } + +#pragma warning disable CS0649, CS0169, IDE1006 + public abstract class JsonYtdlBase + { + public string extractor { get; set; } + public string extractor_key { get; set; } + } + + public class JsonYtdlDump : JsonYtdlBase + { + public string title { get; set; } + public string track { get; set; } + // TODO int -> timespan converter + public float duration { get; set; } + public string id { get; set; } + public JsonYtdlFormat[] formats { get; set; } + public JsonYtdlFormat[] requested_formats { get; set; } + + public string AutoTitle => title; + } + + public class JsonYtdlFormat + { + public string vcodec { get; set; } + public string acodec { get; set; } + /// audioBitRate + public float? abr { get; set; } + /// audioSampleRate + public float? asr { get; set; } + /// totalBitRate + public float? tbr { get; set; } + //public object http_headers { get; set; } + public string format { get; set; } + public string format_id { get; set; } + public string url { get; set; } + public string ext { get; set; } + } + + public class JsonYtdlPlaylistDump : JsonYtdlBase + { + public string id { get; set; } + public string title { get; set; } + public JsonYtdlPlaylistEntry[] entries { get; set; } + } + + public class JsonYtdlPlaylistEntry + { + public string title { get; set; } + public string id { get; set; } + } +#pragma warning restore CS0649, CS0169, IDE1006 } diff --git a/TS3AudioBot/Rights/ExecuteContext.cs b/TS3AudioBot/Rights/ExecuteContext.cs index 78343127..e550cac6 100644 --- a/TS3AudioBot/Rights/ExecuteContext.cs +++ b/TS3AudioBot/Rights/ExecuteContext.cs @@ -7,20 +7,20 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Net; +using TSLib; +using TSLib.Messages; + namespace TS3AudioBot.Rights { - using System; - using System.Collections.Generic; - using System.Net; - using TS3Client; - using TS3Client.Messages; - internal class ExecuteContext { public string Host { get; set; } - public ulong[] ServerGroups { get; set; } = Array.Empty(); - public ulong? ChannelGroupId { get; set; } - public string ClientUid { get; set; } + public ServerGroupId[] ServerGroups { get; set; } = Array.Empty(); + public ChannelGroupId? ChannelGroupId { get; set; } + public Uid ClientUid { get; set; } public bool IsApi { get; set; } public IPAddress ApiCallerIp { get; set; } public string ApiToken { get; set; } diff --git a/TS3AudioBot/Rights/Matchers/MatchApiCallerIp.cs b/TS3AudioBot/Rights/Matchers/MatchApiCallerIp.cs index a3fdc641..a2163a24 100644 --- a/TS3AudioBot/Rights/Matchers/MatchApiCallerIp.cs +++ b/TS3AudioBot/Rights/Matchers/MatchApiCallerIp.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; +using System.Net; + namespace TS3AudioBot.Rights.Matchers { - using System.Collections.Generic; - using System.Net; - internal class MatchApiCallerIp : Matcher { private readonly HashSet requestIps; diff --git a/TS3AudioBot/Rights/Matchers/MatchBot.cs b/TS3AudioBot/Rights/Matchers/MatchBot.cs index 14da4643..15881735 100644 --- a/TS3AudioBot/Rights/Matchers/MatchBot.cs +++ b/TS3AudioBot/Rights/Matchers/MatchBot.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; + namespace TS3AudioBot.Rights.Matchers { - using System.Collections.Generic; - internal class MatchBot : Matcher { private readonly HashSet bots; diff --git a/TS3AudioBot/Rights/Matchers/MatchChannelGroupId.cs b/TS3AudioBot/Rights/Matchers/MatchChannelGroupId.cs index af14a83c..cea6a2cb 100644 --- a/TS3AudioBot/Rights/Matchers/MatchChannelGroupId.cs +++ b/TS3AudioBot/Rights/Matchers/MatchChannelGroupId.cs @@ -7,15 +7,16 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; +using TSLib; + namespace TS3AudioBot.Rights.Matchers { - using System.Collections.Generic; - internal class MatchChannelGroupId : Matcher { - private readonly HashSet channelGroupIds; + private readonly HashSet channelGroupIds; - public MatchChannelGroupId(IEnumerable channelGroupIds) => this.channelGroupIds = new HashSet(channelGroupIds); + public MatchChannelGroupId(IEnumerable channelGroupIds) => this.channelGroupIds = new HashSet(channelGroupIds); public override bool Matches(ExecuteContext ctx) => ctx.ChannelGroupId.HasValue && channelGroupIds.Contains(ctx.ChannelGroupId.Value); } diff --git a/TS3AudioBot/Rights/Matchers/MatchClientGroupId.cs b/TS3AudioBot/Rights/Matchers/MatchClientGroupId.cs index 57b264ea..b25696aa 100644 --- a/TS3AudioBot/Rights/Matchers/MatchClientGroupId.cs +++ b/TS3AudioBot/Rights/Matchers/MatchClientGroupId.cs @@ -7,15 +7,16 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; +using TSLib; + namespace TS3AudioBot.Rights.Matchers { - using System.Collections.Generic; - internal class MatchServerGroupId : Matcher { - private readonly HashSet serverGroupIds; + private readonly HashSet serverGroupIds; - public MatchServerGroupId(IEnumerable serverGroupIds) => this.serverGroupIds = new HashSet(serverGroupIds); + public MatchServerGroupId(IEnumerable serverGroupIds) => this.serverGroupIds = new HashSet(serverGroupIds); public override bool Matches(ExecuteContext ctx) => ctx.ServerGroups?.Length > 0 && serverGroupIds.Overlaps(ctx.ServerGroups); } diff --git a/TS3AudioBot/Rights/Matchers/MatchClientUid.cs b/TS3AudioBot/Rights/Matchers/MatchClientUid.cs index dfa09bb7..5a7fbbdb 100644 --- a/TS3AudioBot/Rights/Matchers/MatchClientUid.cs +++ b/TS3AudioBot/Rights/Matchers/MatchClientUid.cs @@ -7,15 +7,16 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; +using TSLib; + namespace TS3AudioBot.Rights.Matchers { - using System.Collections.Generic; - internal class MatchClientUid : Matcher { - private readonly HashSet clientUids; + private readonly HashSet clientUids; - public MatchClientUid(IEnumerable clientUids) => this.clientUids = new HashSet(clientUids); + public MatchClientUid(IEnumerable clientUids) => this.clientUids = new HashSet(clientUids); public override bool Matches(ExecuteContext ctx) => ctx.ClientUid != null && clientUids.Contains(ctx.ClientUid); } diff --git a/TS3AudioBot/Rights/Matchers/MatchHost.cs b/TS3AudioBot/Rights/Matchers/MatchHost.cs index 7785a1f2..6dbf9b72 100644 --- a/TS3AudioBot/Rights/Matchers/MatchHost.cs +++ b/TS3AudioBot/Rights/Matchers/MatchHost.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; + namespace TS3AudioBot.Rights.Matchers { - using System.Collections.Generic; - internal class MatchHost : Matcher { private readonly HashSet hosts; diff --git a/TS3AudioBot/Rights/Matchers/MatchPermission.cs b/TS3AudioBot/Rights/Matchers/MatchPermission.cs index 09a9f174..68efcec0 100644 --- a/TS3AudioBot/Rights/Matchers/MatchPermission.cs +++ b/TS3AudioBot/Rights/Matchers/MatchPermission.cs @@ -7,22 +7,23 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using TS3AudioBot.Helper; +using TSLib; +using TSLib.Helper; + namespace TS3AudioBot.Rights.Matchers { - using System; - using System.Collections.Generic; - using System.Text.RegularExpressions; - using TS3AudioBot.Helper; - using TS3Client; - internal class MatchPermission : Matcher { private static readonly Regex expressionMatch = new Regex(@"(\w+)\s*(<|>|=|>=|<=|!=)\s*(-?\d+|true|false)", Util.DefaultRegexConfig); - private readonly Dictionary permissions; + private readonly Dictionary permissions; public MatchPermission(string[] permissions, ParseContext ctx) { - this.permissions = new Dictionary(permissions.Length); + this.permissions = new Dictionary(permissions.Length); foreach (var expression in permissions) { var match = expressionMatch.Match(expression); @@ -36,7 +37,7 @@ public MatchPermission(string[] permissions, ParseContext ctx) var compare = match.Groups[2].Value; var value = match.Groups[3].Value; - if (!Enum.TryParse(permission, out var permissionId)) + if (!Enum.TryParse(permission, out var permissionId)) { ctx.Errors.Add($"The teamspeak permission \"{permission}\" was not found"); continue; @@ -72,7 +73,7 @@ public MatchPermission(string[] permissions, ParseContext ctx) } } - public IReadOnlyCollection ComparingPermissions() => permissions.Keys; + public IReadOnlyCollection ComparingPermissions() => permissions.Keys; public override bool Matches(ExecuteContext ctx) { @@ -95,7 +96,7 @@ public override bool Matches(ExecuteContext ctx) case PermCompare.GreaterOrEqual: if (value >= compare.value) return true; break; case PermCompare.Less: if (value < compare.value) return true; break; case PermCompare.LessOrEqual: if (value <= compare.value) return true; break; - default: throw Util.UnhandledDefault(compare.op); + default: throw Tools.UnhandledDefault(compare.op); } } } diff --git a/TS3AudioBot/Rights/Matchers/MatchToken.cs b/TS3AudioBot/Rights/Matchers/MatchToken.cs index 8df41eb3..51fd7f03 100644 --- a/TS3AudioBot/Rights/Matchers/MatchToken.cs +++ b/TS3AudioBot/Rights/Matchers/MatchToken.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Collections.Generic; + namespace TS3AudioBot.Rights.Matchers { - using System.Collections.Generic; - internal class MatchToken : Matcher { private readonly HashSet tokens; diff --git a/TS3AudioBot/Rights/Matchers/MatchVisibility.cs b/TS3AudioBot/Rights/Matchers/MatchVisibility.cs index a7397064..ca9d6640 100644 --- a/TS3AudioBot/Rights/Matchers/MatchVisibility.cs +++ b/TS3AudioBot/Rights/Matchers/MatchVisibility.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Linq; +using TSLib; + namespace TS3AudioBot.Rights.Matchers { - using System.Linq; - using TS3Client; - internal class MatchVisibility : Matcher { private readonly TextMessageTargetMode[] visibility; diff --git a/TS3AudioBot/Rights/ParseContext.cs b/TS3AudioBot/Rights/ParseContext.cs index 34c7071b..0ae2e3c4 100644 --- a/TS3AudioBot/Rights/ParseContext.cs +++ b/TS3AudioBot/Rights/ParseContext.cs @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TSLib; + namespace TS3AudioBot.Rights { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using TS3Client; - internal class ParseContext { public List Declarations { get; } @@ -27,7 +27,7 @@ internal class ParseContext public RightsRule RootRule { get; } public bool NeedsAvailableGroups { get; set; } = false; public bool NeedsAvailableChanGroups { get; set; } = false; - public Ts3Permission[] NeedsPermOverview { get; set; } = Array.Empty(); + public TsPermission[] NeedsPermOverview { get; set; } = Array.Empty(); public ParseContext(ISet registeredRights) { diff --git a/TS3AudioBot/Rights/RightsDecl.cs b/TS3AudioBot/Rights/RightsDecl.cs index a7bc3867..35eff46a 100644 --- a/TS3AudioBot/Rights/RightsDecl.cs +++ b/TS3AudioBot/Rights/RightsDecl.cs @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using System; +using System.Collections.Generic; +using System.Linq; +using TS3AudioBot.Helper; + namespace TS3AudioBot.Rights { - using Helper; - using Nett; - using System; - using System.Collections.Generic; - using System.Linq; - internal abstract class RightsDecl { public int Id { get; private set; } diff --git a/TS3AudioBot/Rights/RightsManager.cs b/TS3AudioBot/Rights/RightsManager.cs index 6c648a5a..49452639 100644 --- a/TS3AudioBot/Rights/RightsManager.cs +++ b/TS3AudioBot/Rights/RightsManager.cs @@ -7,22 +7,23 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using TS3AudioBot.CommandSystem; +using TS3AudioBot.Config; +using TS3AudioBot.Dependency; +using TS3AudioBot.Helper; +using TS3AudioBot.Rights.Matchers; +using TS3AudioBot.Web.Api; +using TSLib; +using TSLib.Helper; +using TSLib.Messages; + namespace TS3AudioBot.Rights { - using CommandSystem; - using Dependency; - using Config; - using Helper; - using Matchers; - using Nett; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using TS3Client; - using TS3Client.Messages; - using TS3AudioBot.Web.Api; - /// Permission system of the bot. public class RightsManager { @@ -32,7 +33,7 @@ public class RightsManager private bool needsRecalculation; private readonly ConfRights config; private RightsRule rootRule; - private readonly HashSet registeredRights; + private HashSet registeredRights = new HashSet(); private readonly object rootRuleLock = new object(); // Required Matcher Data: @@ -41,21 +42,23 @@ public class RightsManager // This will save us from making unnecessary query calls. private bool needsAvailableGroups = true; private bool needsAvailableChanGroups = true; - private Ts3Permission[] needsPermOverview = Array.Empty(); + private TsPermission[] needsPermOverview = Array.Empty(); public RightsManager(ConfRights config) { - Util.Init(out registeredRights); this.config = config; needsRecalculation = true; } public void SetRightsList(IEnumerable rights) { - // TODO validate right names - registeredRights.Clear(); - registeredRights.UnionWith(rights); - needsRecalculation = true; + var newRights = new HashSet(rights); + if (!registeredRights.SetEquals(newRights)) + { + // TODO validate right names + registeredRights = newRights; + needsRecalculation = true; + } } public bool HasAllRights(ExecutionInformation info, params string[] requestedRights) @@ -94,12 +97,12 @@ private ExecuteContext GetRightsContext(ExecutionInformation info) // For this step we will prefer query calls which can give us more than one information // at once and lazily fall back to other calls as long as needed. - if (info.TryGet(out var ts) && info.TryGet(out var tsClient)) + if (info.TryGet(out var ts) && info.TryGet(out var tsClient)) { - ulong[] serverGroups = clientCall.ServerGroups; - ulong? channelId = clientCall.ChannelId; - ulong? databaseId = clientCall.DatabaseId; - ulong? channelGroup = clientCall.ChannelGroup; + ServerGroupId[] serverGroups = clientCall.ServerGroups; + ChannelId? channelId = clientCall.ChannelId; + ClientDbId? databaseId = clientCall.DatabaseId; + ChannelGroupId? channelGroup = clientCall.ChannelGroup; if (clientCall.ClientId != null && ((needsAvailableGroups && serverGroups is null) @@ -138,7 +141,7 @@ private ExecuteContext GetRightsContext(ExecutionInformation info) } execCtx.ChannelGroupId = channelGroup; - execCtx.ServerGroups = serverGroups ?? Array.Empty(); + execCtx.ServerGroups = serverGroups ?? Array.Empty(); if (needsPermOverview.Length > 0 && databaseId != null && channelId != null) { @@ -146,7 +149,7 @@ private ExecuteContext GetRightsContext(ExecutionInformation info) var result = tsClient.PermOverview(databaseId.Value, channelId.Value, 0); if (result.Ok) { - execCtx.Permissions = new PermOverview[Enum.GetValues(typeof(Ts3Permission)).Length]; + execCtx.Permissions = new PermOverview[Enum.GetValues(typeof(TsPermission)).Length]; foreach (var perm in result.Value) { if (perm.PermissionId < 0 || (int)perm.PermissionId >= execCtx.Permissions.Length) @@ -271,13 +274,13 @@ public void CreateConfig(CreateFileSettings settings) string toml = null; using (var fs = Util.GetEmbeddedFile("TS3AudioBot.Rights.DefaultRights.toml")) - using (var reader = new StreamReader(fs, Util.Utf8Encoder)) + using (var reader = new StreamReader(fs, Tools.Utf8Encoder)) { toml = reader.ReadToEnd(); } using (var fs = File.Open(config.Path, FileMode.Create, FileAccess.Write, FileShare.None)) - using (var writer = new StreamWriter(fs, Util.Utf8Encoder)) + using (var writer = new StreamWriter(fs, Tools.Utf8Encoder)) { string replaceAdminUids = settings.AdminUids != null ? string.Join(" ,", settings.AdminUids.Select(x => $"\"{x}\"")) @@ -307,7 +310,7 @@ public void CreateConfigIfNotExists(bool interactive = false) { var adminUid = Interactive.LoopAction("Please enter an admin uid", uid => { - if (!TS3Client.Full.IdentityData.IsUidValid(uid)) + if (!Uid.IsValid(uid)) { Console.WriteLine("The uid seems to be invalid, continue anyway? [y/N]"); return Interactive.UserAgree(defaultTo: false); @@ -606,7 +609,7 @@ private static void FlattenRules(RightsRule root) /// The parsing context for the current file processing. private static void CheckRequiredCalls(ParseContext ctx) { - var needsPermOverview = new HashSet(); + var needsPermOverview = new HashSet(); foreach (var group in ctx.Rules) { @@ -628,7 +631,7 @@ private static void CheckRequiredCalls(ParseContext ctx) } } } - ctx.NeedsPermOverview = needsPermOverview.Count > 0 ? needsPermOverview.ToArray() : Array.Empty(); + ctx.NeedsPermOverview = needsPermOverview.Count > 0 ? needsPermOverview.ToArray() : Array.Empty(); } } } diff --git a/TS3AudioBot/Rights/RightsRule.cs b/TS3AudioBot/Rights/RightsRule.cs index 212aacb0..aa5ed417 100644 --- a/TS3AudioBot/Rights/RightsRule.cs +++ b/TS3AudioBot/Rights/RightsRule.cs @@ -7,16 +7,16 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Nett; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using TS3AudioBot.Helper; +using TS3AudioBot.Rights.Matchers; +using TSLib; + namespace TS3AudioBot.Rights { - using Helper; - using Matchers; - using Nett; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using TS3Client; - // Adding a new Matcher: // 1) Add public MatchHashSet // 2) Add To Has Matches condition when empty @@ -71,17 +71,17 @@ public override bool ParseKey(string key, TomlObject tomlObj, ParseContext ctx) case "groupid": var servergroupid = tomlObj.TryGetValueArray(); if (servergroupid is null) ctx.Errors.Add(" Field has invalid data."); - else Matcher.Add(new MatchServerGroupId(servergroupid)); + else Matcher.Add(new MatchServerGroupId(servergroupid.Select(ServerGroupId.To))); return true; case "channelgroupid": var channelgroupid = tomlObj.TryGetValueArray(); if (channelgroupid is null) ctx.Errors.Add(" Field has invalid data."); - else Matcher.Add(new MatchChannelGroupId(channelgroupid)); + else Matcher.Add(new MatchChannelGroupId(channelgroupid.Select(ChannelGroupId.To))); return true; case "useruid": var useruid = tomlObj.TryGetValueArray(); if (useruid is null) ctx.Errors.Add(" Field has invalid data."); - else Matcher.Add(new MatchClientUid(useruid)); + else Matcher.Add(new MatchClientUid(useruid.Select(Uid.To))); return true; case "perm": var perm = tomlObj.TryGetValueArray(); diff --git a/TS3AudioBot/Sessions/AnonymousSession.cs b/TS3AudioBot/Sessions/AnonymousSession.cs index c9df4159..55eb41aa 100644 --- a/TS3AudioBot/Sessions/AnonymousSession.cs +++ b/TS3AudioBot/Sessions/AnonymousSession.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + namespace TS3AudioBot.Sessions { - using System; - internal class AnonymousSession : UserSession { private static readonly IDisposable DummyLock = new AnonymousSessionLock(); diff --git a/TS3AudioBot/Sessions/ApiNonce.cs b/TS3AudioBot/Sessions/ApiNonce.cs deleted file mode 100644 index ead065f6..00000000 --- a/TS3AudioBot/Sessions/ApiNonce.cs +++ /dev/null @@ -1,28 +0,0 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3AudioBot.Sessions -{ - using System; - - internal class ApiNonce - { - public string Value { get; } - public DateTime Timeout { get; } - - public ApiNonce(string nonce, DateTime useTime) - { - Value = nonce; - Timeout = useTime; - } - - public override bool Equals(object obj) => Value.Equals((obj as ApiNonce)?.Value, StringComparison.Ordinal); - public override int GetHashCode() => Value.GetHashCode(); - } -} diff --git a/TS3AudioBot/Sessions/ApiToken.cs b/TS3AudioBot/Sessions/ApiToken.cs index d8ea4feb..f54a103e 100644 --- a/TS3AudioBot/Sessions/ApiToken.cs +++ b/TS3AudioBot/Sessions/ApiToken.cs @@ -7,76 +7,24 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using TSLib.Helper; + namespace TS3AudioBot.Sessions { - using Helper; - using System; - using System.Collections.Generic; - internal class ApiToken { public const int TokenLen = 32; - public const int NonceLen = 32; - public const int MaxNonceCount = 100; public static readonly TimeSpan DefaultTokenTimeout = TimeSpan.MaxValue; - public static readonly TimeSpan DefaultNonceTimeout = TimeSpan.FromHours(1); public string Value { get; set; } public DateTime Timeout { get; set; } - public bool ApiTokenActive => Value != null && Timeout > Util.GetNow(); - private readonly Dictionary nonceList; + public bool ApiTokenActive => Value != null && Timeout > Tools.Now; public ApiToken() { Value = null; Timeout = DateTime.MinValue; - Util.Init(out nonceList); - } - - public ApiNonce UseNonce(string nonce) - { - lock (nonceList) - { - if (!nonceList.Remove(nonce)) - return null; - - return CreateNonceInternal(); - } - } - - private ApiNonce CreateNonceInternal() - { - DateTime now = Util.GetNow(); - - // Clean up old - var oldestNonce = new ApiNonce(string.Empty, DateTime.MaxValue); - - var vals = nonceList.Values; - foreach (var val in vals) - { - if (val.Timeout < now) - nonceList.Remove(val.Value); - else if (oldestNonce.Timeout < val.Timeout) - oldestNonce = val; - } - if (nonceList.Count >= MaxNonceCount && oldestNonce.Value != string.Empty) - nonceList.Remove(oldestNonce.Value); - - // Create new - string nextNonce; - do { nextNonce = TextUtil.GenToken(NonceLen); } while (nonceList.ContainsKey(nextNonce)); - var newNonce = new ApiNonce(nextNonce, now + DefaultNonceTimeout); - nonceList.Add(newNonce.Value, newNonce); - - return newNonce; - } - - public ApiNonce CreateNonce() - { - lock (nonceList) - { - return CreateNonceInternal(); - } } } } diff --git a/TS3AudioBot/Sessions/SessionManager.cs b/TS3AudioBot/Sessions/SessionManager.cs index 58fca128..8809c114 100644 --- a/TS3AudioBot/Sessions/SessionManager.cs +++ b/TS3AudioBot/Sessions/SessionManager.cs @@ -7,26 +7,21 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using TSLib; + namespace TS3AudioBot.Sessions { - using Helper; - using System; - using System.Collections.Generic; - /// Management for clients talking with the bot. public class SessionManager { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); // Map: Id => UserSession - private readonly Dictionary openSessions; - - public SessionManager() - { - Util.Init(out openSessions); - } + private readonly Dictionary openSessions = new Dictionary(); - public UserSession GetOrCreateSession(ushort clientId) + public UserSession GetOrCreateSession(ClientId clientId) { lock (openSessions) { @@ -40,7 +35,7 @@ public UserSession GetOrCreateSession(ushort clientId) } } - public R GetSession(ushort id) + public R GetSession(ClientId id) { lock (openSessions) { @@ -51,7 +46,7 @@ public R GetSession(ushort id) } } - public void RemoveSession(ushort id) + public void RemoveSession(ClientId id) { lock (openSessions) { diff --git a/TS3AudioBot/Sessions/TokenManager.cs b/TS3AudioBot/Sessions/TokenManager.cs index 45a9313c..6884c45b 100644 --- a/TS3AudioBot/Sessions/TokenManager.cs +++ b/TS3AudioBot/Sessions/TokenManager.cs @@ -7,21 +7,21 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using LiteDB; +using System; +using System.Collections.Generic; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TSLib.Helper; + namespace TS3AudioBot.Sessions { - using Helper; - using LiteDB; - using Localization; - using System; - using System.Collections.Generic; - public class TokenManager { private const string TokenFormat = "{0}:{1}"; private const string ApiTokenTable = "apiToken"; private readonly LiteCollection dbTokenList; - // Map: Uid => ApiToken private readonly Dictionary liveTokenList = new Dictionary(); public TokenManager(DbStore database) @@ -33,33 +33,28 @@ public TokenManager(DbStore database) database.GetMetaData(ApiTokenTable); } - public string GenerateToken(string uid, TimeSpan? timeout = null) + public string GenerateToken(string authId, TimeSpan? timeout = null) { - if (string.IsNullOrEmpty(uid)) - throw new ArgumentNullException(nameof(uid)); - - if (!liveTokenList.TryGetValue(uid, out var token)) - { - token = new ApiToken(); - liveTokenList.Add(uid, token); - } + if (string.IsNullOrEmpty(authId)) + throw new ArgumentNullException(nameof(authId)); + var token = liveTokenList.GetOrNew(authId); token.Value = TextUtil.GenToken(ApiToken.TokenLen); if (timeout.HasValue) token.Timeout = timeout.Value == TimeSpan.MaxValue ? DateTime.MaxValue - : AddTimeSpanSafe(Util.GetNow(), timeout.Value); + : AddTimeSpanSafe(Tools.Now, timeout.Value); else - token.Timeout = AddTimeSpanSafe(Util.GetNow(), ApiToken.DefaultTokenTimeout); + token.Timeout = AddTimeSpanSafe(Tools.Now, ApiToken.DefaultTokenTimeout); dbTokenList.Upsert(new DbApiToken { - UserUid = uid, + UserUid = authId, Token = token.Value, ValidUntil = token.Timeout }); - return string.Format(TokenFormat, uid, token.Value); + return string.Format(TokenFormat, authId, token.Value); } private static DateTime AddTimeSpanSafe(DateTime dateTime, TimeSpan addSpan) @@ -78,24 +73,24 @@ private static DateTime AddTimeSpanSafe(DateTime dateTime, TimeSpan addSpan) } } - internal R GetToken(string uid) + internal R GetToken(string authId) { - if (liveTokenList.TryGetValue(uid, out var token) + if (liveTokenList.TryGetValue(authId, out var token) && token.ApiTokenActive) return token; - var dbToken = dbTokenList.FindById(uid); + var dbToken = dbTokenList.FindById(authId); if (dbToken is null) return new LocalStr(strings.error_no_active_token); - if (dbToken.ValidUntil < Util.GetNow()) + if (dbToken.ValidUntil < Tools.Now) { - dbTokenList.Delete(uid); + dbTokenList.Delete(authId); return new LocalStr(strings.error_no_active_token); } token = new ApiToken { Value = dbToken.Token }; - liveTokenList[uid] = token; + liveTokenList[authId] = token; return token; } diff --git a/TS3AudioBot/Sessions/UserSession.cs b/TS3AudioBot/Sessions/UserSession.cs index d013c90a..32002714 100644 --- a/TS3AudioBot/Sessions/UserSession.cs +++ b/TS3AudioBot/Sessions/UserSession.cs @@ -7,15 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Threading; +using TS3AudioBot.CommandSystem; +using Response = System.Func; + namespace TS3AudioBot.Sessions { - using CommandSystem; - using Helper; - using System; - using System.Collections.Generic; - using System.Threading; - using Response = System.Func; - public class UserSession { private Dictionary assocMap; @@ -42,20 +41,22 @@ public void ClearResponse() ResponseProcessor = null; } - public R Get(string key) + public bool Get(string key, out TData value) { VerifyLock(); + value = default; if (assocMap is null) - return R.Err; + return false; - if (!assocMap.TryGetValue(key, out object value)) - return R.Err; + if (!assocMap.TryGetValue(key, out object valueObj)) + return false; - if (!(value is TData)) - return R.Err; + if (!(valueObj is TData valueT)) + return false; - return (TData)value; + value = valueT; + return true; } public void Set(string key, TData data) @@ -63,7 +64,7 @@ public void Set(string key, TData data) VerifyLock(); if (assocMap is null) - Util.Init(out assocMap); + assocMap = new Dictionary(); assocMap[key] = data; } diff --git a/TS3AudioBot/Setup.cs b/TS3AudioBot/Setup.cs index f8f096df..7b73a355 100644 --- a/TS3AudioBot/Setup.cs +++ b/TS3AudioBot/Setup.cs @@ -1,11 +1,20 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using NLog; +using System; +using System.Runtime; +using TS3AudioBot.Helper; +using TS3AudioBot.Environment; + namespace TS3AudioBot { - using Helper; - using Helper.Environment; - using NLog; - using System; - using System.Runtime; - internal static class Setup { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); @@ -60,7 +69,7 @@ public static bool VerifyMono() public static bool VerifyLibopus() { - bool loaded = TS3Client.Audio.Opus.NativeMethods.PreloadLibrary(); + bool loaded = TSLib.Audio.Opus.NativeMethods.PreloadLibrary(); if (!loaded) Log.Error("Couldn't find libopus. Make sure it is installed or placed in the correct folder."); return loaded; @@ -68,11 +77,7 @@ public static bool VerifyLibopus() public static ParameterData ReadParameter(string[] args) { - var data = new ParameterData { - Interactive = true, - Llgc = true, - Exit = ExitType.No, - }; + var data = new ParameterData(); ParameterData Cancel() { data.Exit = ExitType.Immediately; return data; } @@ -96,12 +101,14 @@ public static ParameterData ReadParameter(string[] args) case "?": case "-h": case "--help": + Console.WriteLine(" --help -h Prints this help..."); Console.WriteLine(" --config -c Specifies the path to the config file."); Console.WriteLine(" --version -V Gets the bot version."); Console.WriteLine(" --skip-checks Skips checking the system for all required tools."); Console.WriteLine(" --hide-banner Does not print the version information header."); Console.WriteLine(" --non-interactive Disables console prompts from setup tools."); - Console.WriteLine(" --help -h Prints this help..."); + Console.WriteLine(" --stats-example Shows you what the bot sends to the global stats tracker."); + Console.WriteLine(" --stats-disabled Disables sending to the global stats tracker."); return Cancel(); case "-c": @@ -130,6 +137,18 @@ public static ParameterData ReadParameter(string[] args) data.Llgc = false; break; + case "--stats-example": + Console.WriteLine("The bot will contribute to the stats counter about once per day."); + Console.WriteLine("We do NOT store any IP or identifiable information."); + Console.WriteLine("Please keep this feature enabled to help us improve and grow."); + Console.WriteLine("An example stats packet looks like this:"); + Console.WriteLine(Stats.CreateExample()); + return Cancel(); + + case "--stats-disabled": + data.SendStats = false; + break; + case "-V": case "--version": Console.WriteLine(SystemData.AssemblyData.ToLongString()); @@ -155,7 +174,7 @@ public static void LogHeader() Log.Info("[ Version: {0}", SystemData.AssemblyData); Log.Info("[ Platform: {0}", SystemData.PlatformData); Log.Info("[ Runtime: {0} ServerGC:{1} GC:{2}", SystemData.RuntimeData.FullName, GCSettings.IsServerGC, GCSettings.LatencyMode); - Log.Info("[ Opus: {0}", TS3Client.Audio.Opus.NativeMethods.Info); + Log.Info("[ Opus: {0}", TSLib.Audio.Opus.NativeMethods.Info); // ffmpeg // youtube-dl Log.Info("[==============================================]"); @@ -164,12 +183,13 @@ public static void LogHeader() internal class ParameterData { - public ExitType Exit { get; set; } - public string ConfigFile { get; set; } - public bool SkipVerifications { get; set; } - public bool HideBanner { get; set; } - public bool Interactive { get; set; } - public bool Llgc { get; set; } + public ExitType Exit { get; set; } = ExitType.No; + public string ConfigFile { get; set; } = null; + public bool SkipVerifications { get; set; } = false; + public bool HideBanner { get; set; } = false; + public bool Interactive { get; set; } = true; + public bool Llgc { get; set; } = true; + public bool SendStats { get; set; } = true; } internal enum ExitType diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index 1ba765a7..9660d0dc 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -2,7 +2,7 @@ Exe - net472;netcoreapp2.2;netcoreapp3.0;netstandard2.0 + net472;netcoreapp2.2;netcoreapp3.1;netstandard2.0 7.3 TS3AudioBot TS3AudioBot @@ -30,11 +30,12 @@ false false false + false true - + all build @@ -42,12 +43,11 @@ - + analyzers - - - + + @@ -71,7 +71,7 @@ - + diff --git a/TS3AudioBot/Ts3Client.cs b/TS3AudioBot/Ts3Client.cs index 2afc307e..96a49dda 100644 --- a/TS3AudioBot/Ts3Client.cs +++ b/TS3AudioBot/Ts3Client.cs @@ -7,35 +7,32 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TS3AudioBot.Algorithm; +using TS3AudioBot.Config; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TSLib; +using TSLib.Commands; +using TSLib.Full; +using TSLib.Helper; +using TSLib.Messages; + namespace TS3AudioBot { - using Audio; - using Config; - using Helper; - using Helper.Environment; - using Localization; - using RExtensions; - using System; - using System.Collections.Generic; - using System.Linq; - using TS3AudioBot.Algorithm; - using TS3AudioBot.ResourceFactories; - using TS3Client; - using TS3Client.Audio; - using TS3Client.Commands; - using TS3Client.Full; - using TS3Client.Helper; - using TS3Client.Messages; - - public sealed class Ts3Client : IPlayerConnection, IDisposable + public sealed class Ts3Client : IDisposable { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private readonly Id id; - private const Codec SendCodec = Codec.OpusMusic; - public event EventHandler OnBotConnected; + public event EventHandler OnBotConnected; public event EventHandler OnBotDisconnect; public event EventHandler OnMessageReceived; + public event EventHandler OnAloneChanged; + public event EventHandler OnWhisperNoTarget; private static readonly string[] QuitMessages = { "I'm outta here", "You're boring", "Have a nice day", "Bye", "Good night", @@ -54,57 +51,47 @@ public sealed class Ts3Client : IPlayerConnection, IDisposable private ReconnectType? lastReconnect; private readonly ConfBot config; - private readonly Ts3FullClient tsFullClient; + private readonly TsFullClient ts3FullClient; private IdentityData identity; - private List clientbuffer; + private List clientbuffer = new List(); private bool clientbufferOutdated = true; - // dbid -> DbData - private readonly TimedCache clientDbNames; - // uid -> dbid - private readonly LruCache dbIdCache; - - private readonly StallCheckPipe stallCheckPipe; - private readonly VolumePipe volumePipe; - private readonly FfmpegProducer ffmpegProducer; - private readonly PreciseTimedPipe timePipe; - private readonly PassiveMergePipe mergePipe; - private readonly EncoderPipe encoderPipe; - internal CustomTargetPipe TargetPipe { get; } + private readonly TimedCache clientDbNames = new TimedCache(); + private readonly LruCache dbIdCache = new LruCache(1024); + private bool alone = true; + private ChannelId? reconnectChannel = null; + private ClientId[] ownChannelClients = Array.Empty(); - public bool Connected => tsFullClient.Connected; - public ConnectionData ConnectionData => tsFullClient.ConnectionData; + public bool Connected => ts3FullClient.Connected; + public ConnectionData ConnectionData => ts3FullClient.ConnectionData; - public Ts3Client(ConfBot config, Ts3FullClient tsFullClient, Id id) + public Ts3Client(ConfBot config, TsFullClient ts3FullClient, Id id) { this.id = id; - Util.Init(out clientDbNames); - Util.Init(out clientbuffer); - dbIdCache = new LruCache(1024); - this.tsFullClient = tsFullClient; - tsFullClient.OnEachTextMessage += ExtendedTextMessage; - tsFullClient.OnErrorEvent += TsFullClient_OnErrorEvent; - tsFullClient.OnConnected += TsFullClient_OnConnected; - tsFullClient.OnDisconnected += TsFullClient_OnDisconnected; - - int ScaleBitrate(int value) => Util.Clamp(value, 1, 255) * 1000; + this.ts3FullClient = ts3FullClient; + ts3FullClient.OnEachTextMessage += ExtendedTextMessage; + ts3FullClient.OnErrorEvent += TsFullClient_OnErrorEvent; + ts3FullClient.OnConnected += TsFullClient_OnConnected; + ts3FullClient.OnDisconnected += TsFullClient_OnDisconnected; + ts3FullClient.OnEachClientMoved += (s, e) => + { + UpdateReconnectChannel(e.ClientId, e.TargetChannelId); + if (AloneRecheckRequired(e.ClientId, e.TargetChannelId)) IsAloneRecheck(); + }; + ts3FullClient.OnEachClientEnterView += (s, e) => + { + UpdateReconnectChannel(e.ClientId, e.TargetChannelId); + if (AloneRecheckRequired(e.ClientId, e.TargetChannelId)) IsAloneRecheck(); + else if (AloneRecheckRequired(e.ClientId, e.SourceChannelId)) IsAloneRecheck(); + }; + ts3FullClient.OnEachClientLeftView += (s, e) => + { + UpdateReconnectChannel(e.ClientId, e.TargetChannelId); + if (AloneRecheckRequired(e.ClientId, e.TargetChannelId)) IsAloneRecheck(); + else if (AloneRecheckRequired(e.ClientId, e.SourceChannelId)) IsAloneRecheck(); + }; this.config = config; - this.config.Audio.Bitrate.Changed += (s, e) => encoderPipe.Bitrate = ScaleBitrate(e.NewValue); - - ffmpegProducer = new FfmpegProducer(config.GetParent().Tools.Ffmpeg, id); - stallCheckPipe = new StallCheckPipe(); - volumePipe = new VolumePipe(); - Volume = config.Audio.Volume.Default; - encoderPipe = new EncoderPipe(SendCodec) { Bitrate = ScaleBitrate(config.Audio.Bitrate) }; - timePipe = new PreciseTimedPipe { ReadBufferSize = encoderPipe.PacketSize }; - timePipe.Initialize(encoderPipe, id); - TargetPipe = new CustomTargetPipe(tsFullClient); - mergePipe = new PassiveMergePipe(); - - mergePipe.Add(ffmpegProducer); - mergePipe.Into(timePipe).Chain().Chain(stallCheckPipe).Chain(volumePipe).Chain(encoderPipe).Chain(TargetPipe); - identity = null; } @@ -114,13 +101,13 @@ public E Connect() var identityConf = config.Connect.Identity; if (string.IsNullOrEmpty(identityConf.PrivateKey)) { - identity = Ts3Crypt.GenerateNewIdentity(); + identity = TsCrypt.GenerateNewIdentity(); identityConf.PrivateKey.Value = identity.PrivateKeyString; identityConf.Offset.Value = identity.ValidKeyOffset; } else { - var identityResult = Ts3Crypt.LoadIdentityDynamic(identityConf.PrivateKey.Value, identityConf.Offset.Value); + var identityResult = TsCrypt.LoadIdentityDynamic(identityConf.PrivateKey.Value, identityConf.Offset.Value); if (!identityResult.Ok) { Log.Error("The identity from the config file is corrupted. Remove it to generate a new one next start; or try to repair it."); @@ -140,7 +127,8 @@ public E Connect() reconnectCounter = 0; lastReconnect = null; - tsFullClient.QuitMessage = QuitMessages[Util.Random.Next(0, QuitMessages.Length)]; + reconnectChannel = null; + ts3FullClient.QuitMessage = Tools.PickRandom(QuitMessages); ClearAllCaches(); return ConnectClient(); } @@ -163,7 +151,7 @@ private E ConnectClient() versionSign = VersionSign.VER_WIN_3_X_X; } } - else if (SystemData.IsLinux) + else if (Tools.IsLinux) { versionSign = VersionSign.VER_LIN_3_X_X; } @@ -181,16 +169,16 @@ private E ConnectClient() Address = config.Connect.Address, Identity = identity, VersionSign = versionSign, - DefaultChannel = config.Connect.Channel, + DefaultChannel = reconnectChannel?.ToPath() ?? config.Connect.Channel, DefaultChannelPassword = config.Connect.ChannelPassword.Get(), LogId = id, }; config.SaveWhenExists(); - tsFullClient.Connect(connectionConfig); + ts3FullClient.Connect(connectionConfig); return R.Ok; } - catch (Ts3Exception qcex) + catch (TsException qcex) { Log.Error(qcex, "There is either a problem with your connection configuration, or the bot has not all permissions it needs."); return "Connect error"; @@ -199,10 +187,10 @@ private E ConnectClient() private void UpdateIndentityToSecurityLevel(int targetLevel) { - if (Ts3Crypt.GetSecurityLevel(identity) < targetLevel) + if (TsCrypt.GetSecurityLevel(identity) < targetLevel) { Log.Info("Calculating up to required security level: {0}", targetLevel); - Ts3Crypt.ImproveSecurity(identity, targetLevel); + TsCrypt.ImproveSecurity(identity, targetLevel); config.Connect.Identity.Offset.Value = identity.ValidKeyOffset; } } @@ -215,71 +203,63 @@ private void StopReconnectTickWorker() TickPool.UnregisterTicker(reconnectTickLocal); } - [Obsolete(AttributeStrings.UnderDevelopment)] - public void MixInStreamOnce(StreamAudioProducer producer) - { - mergePipe.Add(producer); - producer.HitEnd += (s, e) => mergePipe.Remove(producer); - timePipe.Paused = false; - } - - #region Ts3Client functions wrapper + #region TSLib functions wrapper - public E SendMessage(string message, ushort clientId) + public E SendMessage(string message, ClientId clientId) { - if (Ts3String.TokenLength(message) > Ts3Const.MaxSizeTextMessage) + if (TsString.TokenLength(message) > TsConst.MaxSizeTextMessage) return new LocalStr(strings.error_ts_msg_too_long); - return tsFullClient.SendPrivateMessage(message, clientId).FormatLocal(); + return ts3FullClient.SendPrivateMessage(message, clientId).FormatLocal(); } public E SendChannelMessage(string message) { - if (Ts3String.TokenLength(message) > Ts3Const.MaxSizeTextMessage) + if (TsString.TokenLength(message) > TsConst.MaxSizeTextMessage) return new LocalStr(strings.error_ts_msg_too_long); - return tsFullClient.SendChannelMessage(message).FormatLocal(); + return ts3FullClient.SendChannelMessage(message).FormatLocal(); } public E SendServerMessage(string message) { - if (Ts3String.TokenLength(message) > Ts3Const.MaxSizeTextMessage) + if (TsString.TokenLength(message) > TsConst.MaxSizeTextMessage) return new LocalStr(strings.error_ts_msg_too_long); - return tsFullClient.SendServerMessage(message, 1).FormatLocal(); + return ts3FullClient.SendServerMessage(message, 1).FormatLocal(); } - public E KickClientFromServer(ushort clientId) => tsFullClient.KickClientFromServer(new[] { clientId }).FormatLocal(); - public E KickClientFromChannel(ushort clientId) => tsFullClient.KickClientFromChannel(new[] { clientId }).FormatLocal(); + public E KickClientFromServer(params ClientId[] clientId) => ts3FullClient.KickClientFromServer(clientId).FormatLocal(); + public E KickClientFromChannel(params ClientId[] clientId) => ts3FullClient.KickClientFromChannel(clientId).FormatLocal(); public E ChangeDescription(string description) - => tsFullClient.ChangeDescription(description, tsFullClient.ClientId).FormatLocal(); + => ts3FullClient.ChangeDescription(description, ts3FullClient.ClientId).FormatLocal(); public E ChangeBadges(string badgesString) { if (!badgesString.StartsWith("overwolf=") && !badgesString.StartsWith("badges=")) badgesString = "overwolf=0:badges=" + badgesString; - return tsFullClient.ChangeBadges(badgesString).FormatLocal(); + return ts3FullClient.ChangeBadges(badgesString).FormatLocal(); } public E ChangeName(string name) { - var result = tsFullClient.ChangeName(name); + var result = ts3FullClient.ChangeName(name); if (result.Ok) return R.Ok; - if (result.Error.Id == Ts3ErrorCode.parameter_invalid_size) + if (result.Error.Id == TsErrorCode.parameter_invalid_size) return new LocalStr(strings.error_ts_invalid_name); else return result.Error.FormatLocal(); } - public R GetCachedClientById(ushort id) => ClientBufferRequest(client => client.ClientId == id); + public R GetCachedClientById(ClientId id) => ClientBufferRequest(client => client.ClientId == id); - public R GetFallbackedClientById(ushort id) + public R GetFallbackedClientById(ClientId id) { var result = ClientBufferRequest(client => client.ClientId == id); if (result.Ok) return result; Log.Warn("Slow double request due to missing or wrong permission configuration!"); - var result2 = tsFullClient.Send("clientinfo", new CommandParameter("clid", id)).WrapSingle(); + var result2 = ts3FullClient.Send("clientinfo", new CommandParameter("clid", id)).WrapSingle(); if (!result2.Ok) return new LocalStr(strings.error_ts_no_client_found); ClientList cd = result2.Value; @@ -293,7 +273,7 @@ public R GetClientByName(string name) var refreshResult = RefreshClientBuffer(false); if (!refreshResult) return refreshResult.Error; - var clients = Algorithm.Filter.DefaultFilter.Filter( + var clients = Filter.DefaultFilter.Filter( clientbuffer.Select(cb => new KeyValuePair(cb.Name, cb)), name).ToArray(); if (clients.Length <= 0) return new LocalStr(strings.error_ts_no_client_found); @@ -315,7 +295,7 @@ public E RefreshClientBuffer(bool force) { if (clientbufferOutdated || force) { - var result = tsFullClient.ClientList(ClientListOptions.uid); + var result = ts3FullClient.ClientList(ClientListOptions.uid); if (!result) { Log.Debug("Clientlist failed ({0})", result.Error.ErrorFormat()); @@ -327,20 +307,20 @@ public E RefreshClientBuffer(bool force) return R.Ok; } - public R GetClientServerGroups(ulong dbId) + public R GetClientServerGroups(ClientDbId dbId) { - var result = tsFullClient.ServerGroupsByClientDbId(dbId); + var result = ts3FullClient.ServerGroupsByClientDbId(dbId); if (!result.Ok) return new LocalStr(strings.error_ts_no_client_found); return result.Value.Select(csg => csg.ServerGroupId).ToArray(); } - public R GetDbClientByDbId(ulong clientDbId) + public R GetDbClientByDbId(ClientDbId clientDbId) { if (clientDbNames.TryGetValue(clientDbId, out var clientData)) return clientData; - var result = tsFullClient.ClientDbInfo(clientDbId); + var result = ts3FullClient.ClientDbInfo(clientDbId); if (!result.Ok) return new LocalStr(strings.error_ts_no_client_found); clientData = result.Value; @@ -348,14 +328,14 @@ public R GetDbClientByDbId(ulong clientDbId) return clientData; } - public R GetClientInfoById(ushort id) => tsFullClient.ClientInfo(id).FormatLocal(() => strings.error_ts_no_client_found); + public R GetClientInfoById(ClientId id) => ts3FullClient.ClientInfo(id).FormatLocal(_ => (strings.error_ts_no_client_found, true)); - public R GetClientDbIdByUid(string uid) + public R GetClientDbIdByUid(Uid uid) { if (dbIdCache.TryGetValue(uid, out var dbid)) return dbid; - var result = tsFullClient.GetClientDbIdFromUid(uid); + var result = ts3FullClient.GetClientDbIdFromUid(uid); if (!result.Ok) return new LocalStr(strings.error_ts_no_client_found); @@ -365,7 +345,7 @@ public R GetClientDbIdByUid(string uid) internal bool SetupRights(string key) { - var dbResult = tsFullClient.GetClientDbIdFromUid(identity.ClientUid); + var dbResult = ts3FullClient.GetClientDbIdFromUid(identity.ClientUid); if (!dbResult.Ok) { Log.Error("Getting own dbid failed ({0})", dbResult.Error.ErrorFormat()); @@ -375,12 +355,12 @@ internal bool SetupRights(string key) // Check all own server groups var getGroupResult = GetClientServerGroups(myDbId); - var groups = getGroupResult.Ok ? getGroupResult.Value : Array.Empty(); + var groups = getGroupResult.Ok ? getGroupResult.Value : Array.Empty(); // Add self to master group (via token) if (!string.IsNullOrEmpty(key)) { - var privKeyUseResult = tsFullClient.PrivilegeKeyUse(key); + var privKeyUseResult = ts3FullClient.PrivilegeKeyUse(key); if (!privKeyUseResult.Ok) { Log.Error("Using privilege key failed ({0})", privKeyUseResult.Error.ErrorFormat()); @@ -389,24 +369,24 @@ internal bool SetupRights(string key) } // Remember new group (or check if in new group at all) - var groupDiff = Array.Empty(); + var groupDiff = Array.Empty(); if (getGroupResult.Ok) { getGroupResult = GetClientServerGroups(myDbId); - var groupsNew = getGroupResult.Ok ? getGroupResult.Value : Array.Empty(); + var groupsNew = getGroupResult.Ok ? getGroupResult.Value : Array.Empty(); groupDiff = groupsNew.Except(groups).ToArray(); } if (config.BotGroupId == 0) { // Create new Bot group - var botGroup = tsFullClient.ServerGroupAdd("ServerBot"); + var botGroup = ts3FullClient.ServerGroupAdd("ServerBot"); if (botGroup.Ok) { - config.BotGroupId.Value = botGroup.Value.ServerGroupId; + config.BotGroupId.Value = botGroup.Value.ServerGroupId.Value; // Add self to new group - var grpresult = tsFullClient.ServerGroupAddClient(botGroup.Value.ServerGroupId, myDbId); + var grpresult = ts3FullClient.ServerGroupAddClient(botGroup.Value.ServerGroupId, myDbId); if (!grpresult.Ok) Log.Error("Adding group failed ({0})", grpresult.Error.ErrorFormat()); } @@ -416,42 +396,44 @@ internal bool SetupRights(string key) const int ava = 500000; // max size in bytes for the avatar // Add various rights to the bot group - var permresult = tsFullClient.ServerGroupAddPerm(config.BotGroupId.Value, + var permresult = ts3FullClient.ServerGroupAddPerm((ServerGroupId)config.BotGroupId.Value, new[] { - Ts3Permission.i_client_whisper_power, // + Required for whisper channel playing - Ts3Permission.i_client_private_textmessage_power, // + Communication - Ts3Permission.b_client_server_textmessage_send, // + Communication - Ts3Permission.b_client_channel_textmessage_send, // + Communication - - Ts3Permission.b_client_modify_dbproperties, // ? Dont know but seems also required for the next one - Ts3Permission.b_client_modify_description, // + Used to change the description of our bot - Ts3Permission.b_client_info_view, // (+) only used as fallback usually - Ts3Permission.b_virtualserver_client_list, // ? Dont know but seems also required for the next one - - Ts3Permission.i_channel_subscribe_power, // + Required to find user to communicate - Ts3Permission.b_virtualserver_client_dbinfo, // + Required to get basic user information for history, api, etc... - Ts3Permission.i_client_talk_power, // + Required for normal channel playing - Ts3Permission.b_client_modify_own_description, // ? not sure if this makes b_client_modify_description superfluous - - Ts3Permission.b_group_is_permanent, // + Group should stay even if bot disconnects - Ts3Permission.i_client_kick_from_channel_power, // + Optional for kicking - Ts3Permission.i_client_kick_from_server_power, // + Optional for kicking - Ts3Permission.i_client_max_clones_uid, // + In case that bot times out and tries to join again - - Ts3Permission.b_client_ignore_antiflood, // + The bot should be resistent to forced spam attacks - Ts3Permission.b_channel_join_ignore_password, // + The noble bot will not abuse this power - Ts3Permission.b_channel_join_permanent, // + Allow joining to all channel even on strict servers - Ts3Permission.b_channel_join_semi_permanent, // + Allow joining to all channel even on strict servers - - Ts3Permission.b_channel_join_temporary, // + Allow joining to all channel even on strict servers - Ts3Permission.b_channel_join_ignore_maxclients, // + Allow joining full channels - Ts3Permission.i_channel_join_power, // + Allow joining to all channel even on strict servers - Ts3Permission.b_client_permissionoverview_view, // + Scanning through given perms for rights system - - Ts3Permission.i_client_max_avatar_filesize, // + Uploading thumbnails as avatar - Ts3Permission.b_client_use_channel_commander, // + Enable channel commander - Ts3Permission.b_client_ignore_bans, // + The bot should be resistent to bans - Ts3Permission.b_client_ignore_sticky, // + Should skip weird movement restrictions + TsPermission.i_client_whisper_power, // + Required for whisper channel playing + TsPermission.i_client_private_textmessage_power, // + Communication + TsPermission.b_client_server_textmessage_send, // + Communication + TsPermission.b_client_channel_textmessage_send, // + Communication + + TsPermission.b_client_modify_dbproperties, // ? Dont know but seems also required for the next one + TsPermission.b_client_modify_description, // + Used to change the description of our bot + TsPermission.b_client_info_view, // (+) only used as fallback usually + TsPermission.b_virtualserver_client_list, // ? Dont know but seems also required for the next one + + TsPermission.i_channel_subscribe_power, // + Required to find user to communicate + TsPermission.b_virtualserver_client_dbinfo, // + Required to get basic user information for history, api, etc... + TsPermission.i_client_talk_power, // + Required for normal channel playing + TsPermission.b_client_modify_own_description, // ? not sure if this makes b_client_modify_description superfluous + + TsPermission.b_group_is_permanent, // + Group should stay even if bot disconnects + TsPermission.i_client_kick_from_channel_power, // + Optional for kicking + TsPermission.i_client_kick_from_server_power, // + Optional for kicking + TsPermission.i_client_max_clones_uid, // + In case that bot times out and tries to join again + + TsPermission.b_client_ignore_antiflood, // + The bot should be resistent to forced spam attacks + TsPermission.b_channel_join_ignore_password, // + The noble bot will not abuse this power + TsPermission.b_channel_join_permanent, // + Allow joining to all channel even on strict servers + TsPermission.b_channel_join_semi_permanent, // + Allow joining to all channel even on strict servers + + TsPermission.b_channel_join_temporary, // + Allow joining to all channel even on strict servers + TsPermission.b_channel_join_ignore_maxclients, // + Allow joining full channels + TsPermission.i_channel_join_power, // + Allow joining to all channel even on strict servers + TsPermission.b_client_permissionoverview_view, // + Scanning through given perms for rights system + + TsPermission.i_client_max_avatar_filesize, // + Uploading thumbnails as avatar + TsPermission.b_client_use_channel_commander, // + Enable channel commander + TsPermission.b_client_ignore_bans, // + The bot should be resistent to bans + TsPermission.b_client_ignore_sticky, // + Should skip weird movement restrictions + + TsPermission.i_client_max_channel_subscriptions, // + Required to find user to communicate }, new[] { max, max, 1, 1, @@ -461,6 +443,7 @@ internal bool SetupRights(string key) 1, 1, 1, 1, 1, 1, max, 1, ava, 1, 1, 1, + -1, }, new[] { false, false, false, false, @@ -470,6 +453,7 @@ internal bool SetupRights(string key) false, false, false, false, false, false, false, false, false, false, false, false, + false, }, new[] { false, false, false, false, @@ -479,6 +463,7 @@ internal bool SetupRights(string key) false, false, false, false, false, false, false, false, false, false, false, false, + false, }); if (!permresult) @@ -489,7 +474,7 @@ internal bool SetupRights(string key) { foreach (var grp in groupDiff) { - var grpresult = tsFullClient.ServerGroupDelClient(grp, myDbId); + var grpresult = ts3FullClient.ServerGroupDelClient(grp, myDbId); if (!grpresult.Ok) Log.Error("Removing group failed ({0})", grpresult.Error.ErrorFormat()); } @@ -498,25 +483,27 @@ internal bool SetupRights(string key) return true; } - public E UploadAvatar(System.IO.Stream stream) => tsFullClient.UploadAvatar(stream).FormatLocal(); + public E UploadAvatar(System.IO.Stream stream) => ts3FullClient.UploadAvatar(stream).FormatLocal(e => + (e == TsErrorCode.permission_invalid_size ? strings.error_ts_file_too_big : null, false) + ); // TODO C# 8 switch expressions - public E DeleteAvatar() => tsFullClient.DeleteAvatar().FormatLocal(); + public E DeleteAvatar() => ts3FullClient.DeleteAvatar().FormatLocal(); - public E MoveTo(ulong channelId, string password = null) - => tsFullClient.ClientMove(tsFullClient.ClientId, channelId, password).FormatLocal(() => strings.error_ts_cannot_move); + public E MoveTo(ChannelId channelId, string password = null) + => ts3FullClient.ClientMove(ts3FullClient.ClientId, channelId, password).FormatLocal(_ => (strings.error_ts_cannot_move, true)); public E SetChannelCommander(bool isCommander) - => tsFullClient.ChangeIsChannelCommander(isCommander).FormatLocal(() => strings.error_ts_cannot_set_commander); + => ts3FullClient.ChangeIsChannelCommander(isCommander).FormatLocal(_ => (strings.error_ts_cannot_set_commander, true)); public R IsChannelCommander() { - var getInfoResult = GetClientInfoById(tsFullClient.ClientId); + var getInfoResult = GetClientInfoById(ts3FullClient.ClientId); if (!getInfoResult.Ok) return getInfoResult.Error; return getInfoResult.Value.IsChannelCommander; } - public R GetSelf() => tsFullClient.ClientInfo(tsFullClient.ClientId).FormatLocal(); + public R GetSelf() => ts3FullClient.ClientInfo(ts3FullClient.ClientId).FormatLocal(); public void InvalidateClientBuffer() => clientbufferOutdated = true; @@ -525,6 +512,8 @@ private void ClearAllCaches() InvalidateClientBuffer(); dbIdCache.Clear(); clientDbNames.Clear(); + alone = true; + ownChannelClients = Array.Empty(); } #endregion @@ -535,8 +524,8 @@ private void TsFullClient_OnErrorEvent(object sender, CommandError error) { switch (error.Id) { - case Ts3ErrorCode.whisper_no_targets: - stallCheckPipe.SetStall(); + case TsErrorCode.whisper_no_targets: + OnWhisperNoTarget?.Invoke(this, EventArgs.Empty); break; default: @@ -552,7 +541,7 @@ private void TsFullClient_OnDisconnected(object sender, DisconnectEventArgs e) var error = e.Error; switch (error.Id) { - case Ts3ErrorCode.client_could_not_validate_identity: + case TsErrorCode.client_could_not_validate_identity: if (config.Connect.Identity.Level.Value == -1) { int targetSecLevel = int.Parse(error.ExtraMessage); @@ -567,13 +556,13 @@ private void TsFullClient_OnDisconnected(object sender, DisconnectEventArgs e) } break; - case Ts3ErrorCode.client_too_many_clones_connected: + case TsErrorCode.client_too_many_clones_connected: Log.Warn("Seems like another client with the same identity is already connected."); if (TryReconnect(ReconnectType.Error)) return; break; - case Ts3ErrorCode.connect_failed_banned: + case TsErrorCode.connect_failed_banned: Log.Warn("This bot is banned."); if (TryReconnect(ReconnectType.Ban)) return; @@ -591,7 +580,7 @@ private void TsFullClient_OnDisconnected(object sender, DisconnectEventArgs e) Log.Debug("Bot disconnected. Reason: {0}", e.ExitReason); if (TryReconnect( // TODO c# 8.0 switch expression - e.ExitReason == Reason.Timeout ? ReconnectType.Timeout : + e.ExitReason == Reason.Timeout || e.ExitReason == Reason.SocketError ? ReconnectType.Timeout : e.ExitReason == Reason.KickedFromServer ? ReconnectType.Kick : e.ExitReason == Reason.ServerShutdown || e.ExitReason == Reason.ServerStopped ? ReconnectType.ServerShutdown : e.ExitReason == Reason.Banned ? ReconnectType.Ban : @@ -607,9 +596,17 @@ private bool TryReconnect(ReconnectType type) if (closed) return false; - if (lastReconnect != type) - reconnectCounter = 0; - lastReconnect = type; + // Check if we want to keep the last disconnect type + if (type == ReconnectType.Timeout && lastReconnect == ReconnectType.ServerShutdown) + { + type = lastReconnect.Value; + } + else + { + if (lastReconnect != type) + reconnectCounter = 0; + lastReconnect = type; + } TimeSpan? delay; switch (type) @@ -621,7 +618,7 @@ private bool TryReconnect(ReconnectType type) case ReconnectType.Error: delay = config.Reconnect.OnError.GetValueAsTime(reconnectCounter); break; case ReconnectType.None: return false; - default: throw Util.UnhandledDefault(type); + default: throw Tools.UnhandledDefault(type); } reconnectCounter++; @@ -647,82 +644,45 @@ private void TsFullClient_OnConnected(object sender, EventArgs e) private void ExtendedTextMessage(object sender, TextMessage textMessage) { // Prevent loopback of own textmessages - if (textMessage.InvokerId == tsFullClient.ClientId) + if (textMessage.InvokerId == ts3FullClient.ClientId) return; OnMessageReceived?.Invoke(sender, textMessage); } - #endregion - - #region IPlayerConnection - - public event EventHandler OnSongEnd - { - add => ffmpegProducer.OnSongEnd += value; - remove => ffmpegProducer.OnSongEnd -= value; - } - - public event EventHandler OnSongUpdated - { - add => ffmpegProducer.OnSongUpdated += value; - remove => ffmpegProducer.OnSongUpdated -= value; - } - - public E AudioStart(PlayResource res) - { - E result; - if (res is MediaPlayResource mres && mres.IsIcyStream) - result = ffmpegProducer.AudioStartIcy(res.PlayUri); - else - result = ffmpegProducer.AudioStart(res.PlayUri); - if (result) - timePipe.Paused = false; - return result; - } - - public E AudioStop() + private void UpdateReconnectChannel(ClientId clientId, ChannelId channelId) { - // TODO clean up all mixins - timePipe.Paused = true; - ffmpegProducer.AudioStop(); - return R.Ok; - } - - public TimeSpan Length => ffmpegProducer.Length; - - public TimeSpan Position - { - get => ffmpegProducer.Position; - set => ffmpegProducer.Position = value; + if (clientId == ts3FullClient.ClientId && channelId != ChannelId.Null) + reconnectChannel = channelId; } - public float Volume - { - get => AudioValues.FactorToHumanVolume(volumePipe.Volume); - set => volumePipe.Volume = AudioValues.HumanVolumeToFactor(value); - } + private bool AloneRecheckRequired(ClientId clientId, ChannelId channelId) + => ownChannelClients.Contains(clientId) || channelId == ts3FullClient.Book.Self()?.Channel; - public bool Paused + private void IsAloneRecheck() { - get => timePipe.Paused; - set => timePipe.Paused = value; + var self = ts3FullClient.Book.Self(); + if (self == null) + return; + var ownChannel = self.Channel; + ownChannelClients = ts3FullClient.Book.Clients.Values.Where(c => c.Channel == ownChannel && c != self).Select(c => c.Id).ToArray(); + var newAlone = ownChannelClients.Length == 0; + if (newAlone != alone) + { + alone = newAlone; + OnAloneChanged?.Invoke(this, new AloneChanged(newAlone)); + } } - public bool Playing => !timePipe.Paused; - #endregion public void Dispose() { closed = true; StopReconnectTickWorker(); - timePipe?.Dispose(); - ffmpegProducer?.Dispose(); - encoderPipe?.Dispose(); - tsFullClient.Dispose(); + ts3FullClient.Dispose(); } - enum ReconnectType + private enum ReconnectType { None, Timeout, @@ -733,33 +693,68 @@ enum ReconnectType } } - namespace RExtensions + public class AloneChanged : EventArgs + { + public bool Alone { get; } + + public AloneChanged(bool alone) + { + Alone = alone; + } + } + + internal static class CommandErrorExtentions { - internal static class RExtentions + public static R FormatLocal(this R cmdErr, Func prefix = null) + { + if (cmdErr.Ok) + return cmdErr.Value; + return cmdErr.Error.FormatLocal(prefix); + } + + public static E FormatLocal(this E cmdErr, Func prefix = null) { - public static R FormatLocal(this R cmdErr, Func prefix = null) + if (cmdErr.Ok) + return R.Ok; + return cmdErr.Error.FormatLocal(prefix); + } + + public static LocalStr FormatLocal(this CommandError err, Func prefix = null) + { + var strb = new StringBuilder(); + bool msg = true; + + if (prefix != null) { - if (cmdErr.Ok) - return cmdErr.Value; - return cmdErr.Error.FormatLocal(prefix); + string prefixStr; + (prefixStr, msg) = prefix(err.Id); + if (prefixStr != null) + { + strb.Append(prefixStr); + } } - public static E FormatLocal(this E cmdErr, Func prefix = null) + if (strb.Length == 0) { - if (cmdErr.Ok) - return R.Ok; - return cmdErr.Error.FormatLocal(prefix); + strb.Append(strings.error_ts_unknown_error); } - public static LocalStr FormatLocal(this CommandError err, Func prefix = null) + if (msg) { - var str = LocalizationManager.GetString("error_ts_code_" + (uint)err.Id) - ?? $"{strings.error_ts_unknown_error} ({err.Message})"; - - if (prefix != null) - str = $"{prefix()} ({str})"; - return new LocalStr(str); + if (strb.Length > 0) + strb.Append(" ("); + var localStr = LocalizationManager.GetString("error_ts_code_" + (uint)err.Id); + if (localStr != null) + strb.Append(localStr); + else + strb.Append(err.Message); + strb.Append(')'); } + + if (err.MissingPermissionId != TsPermission.undefined) + strb.Append(" (").Append(err.MissingPermissionId).Append(')'); + + return new LocalStr(strb.ToString()); } } } diff --git a/TS3AudioBot/Web/Api/ApiCall.cs b/TS3AudioBot/Web/Api/ApiCall.cs index e366b8b8..9fa071fb 100644 --- a/TS3AudioBot/Web/Api/ApiCall.cs +++ b/TS3AudioBot/Web/Api/ApiCall.cs @@ -7,11 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Net; +using TSLib; + namespace TS3AudioBot.Web.Api { - using System; - using System.Net; - public class ApiCall : InvokerData { public string Token { get; set; } @@ -21,7 +22,7 @@ public class ApiCall : InvokerData public static ApiCall CreateAnonymous() => new ApiCall(AnonymousUid); - public ApiCall(string clientUid, IPAddress ipAddress = null, Uri requestUrl = null, string token = null, string body = null) : base(clientUid) + public ApiCall(Uid clientUid, IPAddress ipAddress = null, Uri requestUrl = null, string token = null, string body = null) : base(clientUid) { Token = token; IpAddress = ipAddress; diff --git a/TS3AudioBot/Web/Api/DataStream.cs b/TS3AudioBot/Web/Api/DataStream.cs new file mode 100644 index 00000000..80cd3444 --- /dev/null +++ b/TS3AudioBot/Web/Api/DataStream.cs @@ -0,0 +1,28 @@ +// TS3AudioBot - An advanced Musicbot for Teamspeak 3 +// Copyright (C) 2017 TS3AudioBot contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using Microsoft.AspNetCore.Http; +using System; + +namespace TS3AudioBot.Web.Api +{ + public class DataStream + { + private readonly Func writeFunc; + + public DataStream(Func writeFunc) + { + this.writeFunc = writeFunc; + } + + public bool WriteOut(HttpResponse response) => writeFunc(response); + + public override string ToString() => null; + } +} diff --git a/TS3AudioBot/Web/Api/JsonArray.cs b/TS3AudioBot/Web/Api/JsonArray.cs index 136bd088..28f6d5a1 100644 --- a/TS3AudioBot/Web/Api/JsonArray.cs +++ b/TS3AudioBot/Web/Api/JsonArray.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; +using System.Collections.Generic; + namespace TS3AudioBot.Web.Api { - using System; - using System.Collections.Generic; - public class JsonArray : JsonValue> { public JsonArray(IList value, string msg) : base(value, msg) { } diff --git a/TS3AudioBot/Web/Api/JsonError.cs b/TS3AudioBot/Web/Api/JsonError.cs index 442b1dae..cfdfaa82 100644 --- a/TS3AudioBot/Web/Api/JsonError.cs +++ b/TS3AudioBot/Web/Api/JsonError.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using TS3AudioBot.CommandSystem; + namespace TS3AudioBot.Web.Api { - using CommandSystem; - using Newtonsoft.Json; - public class JsonError : JsonObject { private static readonly JsonSerializerSettings ErrorSerializeSettings = new JsonSerializerSettings diff --git a/TS3AudioBot/Web/Api/JsonObject.cs b/TS3AudioBot/Web/Api/JsonObject.cs index 61d25430..fd02b03b 100644 --- a/TS3AudioBot/Web/Api/JsonObject.cs +++ b/TS3AudioBot/Web/Api/JsonObject.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using TS3AudioBot.Helper; + namespace TS3AudioBot.Web.Api { - using Newtonsoft.Json; - using TS3AudioBot.Helper; - public abstract class JsonObject { private static readonly JsonSerializerSettings DefaultSettigs = new JsonSerializerSettings(); diff --git a/TS3AudioBot/Web/Api/JsonValue.cs b/TS3AudioBot/Web/Api/JsonValue.cs index d34af2c9..f03a6f44 100644 --- a/TS3AudioBot/Web/Api/JsonValue.cs +++ b/TS3AudioBot/Web/Api/JsonValue.cs @@ -7,17 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System; +using TS3AudioBot.CommandSystem; + namespace TS3AudioBot.Web.Api { - using CommandSystem; - using Newtonsoft.Json; - using System; - public class JsonValue : JsonValue { protected Func AsString { get; } - new public T Value => (T)base.Value; + public new T Value => (T)base.Value; public JsonValue(T value) : base(value) { } public JsonValue(T value, string msg) : base(value, msg) { } @@ -58,7 +58,7 @@ public abstract class JsonValue : JsonObject public override string Serialize() { var seriObj = GetSerializeObject(); - if (seriObj != null && XCommandSystem.BasicTypes.Contains(seriObj.GetType())) + if (seriObj != null && CommandSystemTypes.BasicTypes.Contains(seriObj.GetType())) return JsonConvert.SerializeObject(this); return base.Serialize(); } diff --git a/TS3AudioBot/Web/Api/OpenApiGenerator.cs b/TS3AudioBot/Web/Api/OpenApiGenerator.cs index a00e3088..b2d8edee 100644 --- a/TS3AudioBot/Web/Api/OpenApiGenerator.cs +++ b/TS3AudioBot/Web/Api/OpenApiGenerator.cs @@ -7,18 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Text; +using TS3AudioBot.CommandSystem; +using TS3AudioBot.CommandSystem.Commands; +using TSLib.Helper; + namespace TS3AudioBot.Web.Api { - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using System; - using System.Collections.Generic; - using System.Text; - using TS3AudioBot.CommandSystem; - using TS3AudioBot.CommandSystem.CommandResults; - using TS3AudioBot.CommandSystem.Commands; - using TS3AudioBot.Helper; - public static class OpenApiGenerator { private static readonly JsonSerializer seri = JsonSerializer.CreateDefault(); @@ -98,7 +97,7 @@ private static JToken GenerateCommand(CommandManager commandManager, BotCommand pathBuilder.Append(command.InvokeName.Replace(' ', '/')); foreach (var param in command.CommandParameter) { - switch (param.kind) + switch (param.Kind) { case ParamKind.Unknown: break; @@ -111,25 +110,25 @@ private static JToken GenerateCommand(CommandManager commandManager, BotCommand case ParamKind.NormalParam: case ParamKind.NormalArray: case ParamKind.NormalTailString: - if (param.kind == ParamKind.NormalArray) - pathBuilder.Append("/{").Append(param.param.Name).Append("}..."); + if (param.Kind == ParamKind.NormalArray) + pathBuilder.Append("/{").Append(param.Name).Append("}..."); else - pathBuilder.Append("/{").Append(param.param.Name).Append("}"); + pathBuilder.Append("/{").Append(param.Name).Append("}"); var addparam = new JObject( - new JProperty("name", param.param.Name), + new JProperty("name", param.Name), new JProperty("in", "path"), new JProperty("description", "useful help"), new JProperty("required", true) // param.optional ); - var paramschema = NormalToSchema(param.type); + var paramschema = NormalToSchema(param.Type); if (paramschema != null) addparam.Add("schema", JObject.FromObject(paramschema, seri)); parameters.Add(addparam); break; default: - throw Util.UnhandledDefault(param.kind); + throw Tools.UnhandledDefault(param.Kind); } } @@ -144,7 +143,7 @@ private static JToken GenerateCommand(CommandManager commandManager, BotCommand var tags = new JArray(); int spaceIndex = command.InvokeName.IndexOf(' '); string baseName = spaceIndex >= 0 ? command.InvokeName.Substring(0, spaceIndex) : command.InvokeName; - var commandroot = commandManager.CommandSystem.RootCommand.GetCommand(baseName); + var commandroot = commandManager.RootGroup.GetCommand(baseName); switch (commandroot) { case null: @@ -235,7 +234,7 @@ private static OApiSchema NormalToSchema(Type type) else if (type == typeof(DateTime)) return OApiSchema.FromBasic("string", "date-time"); else if (type == typeof(string)) return OApiSchema.FromBasic("string", null); else if (type == typeof(JsonEmpty) || type == typeof(void)) return null; - else if (type == typeof(JsonObject) || type == typeof(object) || type == typeof(ICommandResult)) return OApiSchema.FromBasic("object"); + else if (type == typeof(JsonObject) || type == typeof(object)) return OApiSchema.FromBasic("object"); else if (type == typeof(ICommand)) return OApiSchema.FromBasic("λ"); else { @@ -243,7 +242,7 @@ private static OApiSchema NormalToSchema(Type type) } } - class OApiSchema + private class OApiSchema { public string type { get; set; } public string format { get; set; } diff --git a/TS3AudioBot/Web/Api/TimeSpanConverter.cs b/TS3AudioBot/Web/Api/TimeSpanConverter.cs index 15cf9acc..31239712 100644 --- a/TS3AudioBot/Web/Api/TimeSpanConverter.cs +++ b/TS3AudioBot/Web/Api/TimeSpanConverter.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System; + namespace TS3AudioBot.Web.Api { - using Newtonsoft.Json; - using System; - internal class TimeSpanConverter : JsonConverter { public override void WriteJson(JsonWriter writer, TimeSpan value, JsonSerializer serializer) diff --git a/TS3AudioBot/Web/Api/WebApi.cs b/TS3AudioBot/Web/Api/WebApi.cs index 1389fe34..ee041e9e 100644 --- a/TS3AudioBot/Web/Api/WebApi.cs +++ b/TS3AudioBot/Web/Api/WebApi.cs @@ -7,32 +7,36 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using System; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using TS3AudioBot.Algorithm; +using TS3AudioBot.Audio; +using TS3AudioBot.CommandSystem; +using TS3AudioBot.CommandSystem.Ast; +using TS3AudioBot.CommandSystem.Commands; +using TS3AudioBot.Config; +using TS3AudioBot.Dependency; +using TS3AudioBot.Environment; +using TS3AudioBot.Helper; +using TS3AudioBot.Localization; +using TS3AudioBot.Sessions; +using TSLib; +using TSLib.Helper; + namespace TS3AudioBot.Web.Api { - using Audio; - using CommandSystem; - using CommandSystem.Ast; - using CommandSystem.CommandResults; - using CommandSystem.Commands; - using Config; - using Dependency; - using Helper; - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Features; - using Sessions; - using System; - using System.Globalization; - using System.IO; - using System.Net; - using System.Text; - using System.Threading; - using TS3AudioBot.Algorithm; - using TS3AudioBot.Localization; - public sealed class WebApi { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + private static readonly Uri Dummy = new Uri("http://dummy/"); + private const string ErrorNoUserOrToken = "Unknown user or no active token found"; private const string ErrorAuthFailure = "Authentication failed"; private const string ErrorAnonymousDisabled = "This bot does not allow anonymous api requests"; @@ -41,15 +45,15 @@ public sealed class WebApi public bool AllowAnonymousRequest { get; set; } = true; private readonly ConfWebApi config; private readonly CoreInjector coreInjector; - private readonly CommandManager commandManager; private readonly TokenManager tokenManager; + private readonly Stats stats; - public WebApi(ConfWebApi config, CoreInjector coreInjector, CommandManager commandManager, TokenManager tokenManager) + public WebApi(ConfWebApi config, CoreInjector coreInjector, TokenManager tokenManager, Stats stats) { this.config = config; this.coreInjector = coreInjector; - this.commandManager = commandManager; this.tokenManager = tokenManager; + this.stats = stats; } public void ProcessApiV1Call(HttpContext context) @@ -68,7 +72,7 @@ public void ProcessApiV1Call(HttpContext context) ReturnError(new CommandException(authResult.Error, CommandExceptionReason.Unauthorized), response); return; } - if (!AllowAnonymousRequest && string.IsNullOrEmpty(authResult.Value.ClientUid)) + if (!AllowAnonymousRequest && authResult.Value.ClientUid == Uid.Null) { Log.Debug("Unauthorized request!"); ReturnError(new CommandException(ErrorAnonymousDisabled, CommandExceptionReason.Unauthorized), response); @@ -90,7 +94,7 @@ public void ProcessApiV1Call(HttpContext context) remoteAddress = realIp; } apiCallData.IpAddress = remoteAddress; - apiCallData.RequestUrl = new Uri(WebUtil.Dummy, context.Features.Get().RawTarget); + apiCallData.RequestUrl = new Uri(Dummy, context.Features.Get().RawTarget); Log.Info("{0} Requested: {1}", remoteAddress, apiCallData.RequestUrl.PathAndQuery); @@ -106,21 +110,30 @@ public void ProcessApiV1Call(HttpContext context) try { + stats.TrackCommandApiCall(); Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; - var res = command.Execute(execInfo, Array.Empty(), XCommandSystem.ReturnJsonOrNothing); + var res = command.Execute(execInfo, Array.Empty(), CommandSystemTypes.ReturnJsonOrDataOrNothing); - if (res.ResultType == CommandResultType.Empty) + if (res == null) { response.StatusCode = (int)HttpStatusCode.NoContent; } - else if (res.ResultType == CommandResultType.Json) + else if (res is JsonObject json) { - var returnJson = (JsonCommandResult)res; - var returnString = returnJson.JsonObject.Serialize(); + var returnString = json.Serialize(); response.StatusCode = returnString.Length == 0 ? (int)HttpStatusCode.NoContent : (int)HttpStatusCode.OK; using (var responseStream = new StreamWriter(response.Body)) responseStream.Write(returnString); } + else if (res is DataStream data) + { + response.StatusCode = (int)HttpStatusCode.OK; + using (response.Body) + { + if (!data.WriteOut(response)) + response.StatusCode = (int)HttpStatusCode.NotFound; + } + } } catch (CommandException ex) { @@ -139,7 +152,7 @@ private ICommand BuildCommand(Uri requestUrl) var ast = CommandParser.ParseCommandRequest(apirequest, '/', '/'); UnescapeAstTree(ast); Log.Trace(ast.ToString); - return commandManager.CommandSystem.AstToCommandResult(ast); + return CommandManager.AstToCommandResult(ast); } private ExecutionInformation BuildContext(ApiCall apiCallData) @@ -164,7 +177,7 @@ private E ProcessBodyData(HttpRequest request, ApiCall apiCallData) try { - using (var sr = new StreamReader(request.Body, Util.Utf8Encoder)) + using (var sr = new StreamReader(request.Body, Tools.Utf8Encoder)) apiCallData.Body = sr.ReadToEnd(); return R.Ok; } @@ -249,7 +262,7 @@ private static JsonError ReturnCommandError(CommandException ex, HttpResponse re { jsonError.HelpMessage += "Creating UserSessions via api is currently not implemented yet."; } - else if (mcex.MissingType == typeof(Bot) || mcex.MissingType == typeof(IPlayerConnection) + else if (mcex.MissingType == typeof(Bot) || mcex.MissingType == typeof(Player) || mcex.MissingType == typeof(PlayManager) || mcex.MissingType == typeof(Ts3Client) || mcex.MissingType == typeof(IVoiceTarget) || mcex.MissingType == typeof(IVoiceTarget) || mcex.MissingType == typeof(ConfBot)) @@ -271,7 +284,7 @@ private static JsonError ReturnCommandError(CommandException ex, HttpResponse re break; default: - throw Util.UnhandledDefault(ex.Reason); + throw Tools.UnhandledDefault(ex.Reason); } return jsonError; @@ -292,7 +305,7 @@ private static void UnescapeAstTree(AstNode node) break; case AstType.Error: break; default: - throw Util.UnhandledDefault(node.Type); + throw Tools.UnhandledDefault(node.Type); } } @@ -330,7 +343,7 @@ private R Authenticate(HttpRequest request) if (dbToken.Value != token) return ErrorAuthFailure; - return new ApiCall(userUid, token: dbToken.Value); + return new ApiCall((Uid)userUid, token: dbToken.Value); } } } diff --git a/TS3AudioBot/Web/Model/PlaylistInfo.cs b/TS3AudioBot/Web/Model/PlaylistInfo.cs index 96678d9f..767b229a 100644 --- a/TS3AudioBot/Web/Model/PlaylistInfo.cs +++ b/TS3AudioBot/Web/Model/PlaylistInfo.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; + namespace TS3AudioBot.Web.Model { - using Newtonsoft.Json; - public class PlaylistInfo { // TODO better names diff --git a/TS3AudioBot/Web/Model/QueueInfo.cs b/TS3AudioBot/Web/Model/QueueInfo.cs index ac755b8d..b4b4b71b 100644 --- a/TS3AudioBot/Web/Model/QueueInfo.cs +++ b/TS3AudioBot/Web/Model/QueueInfo.cs @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; + namespace TS3AudioBot.Web.Model { - using Newtonsoft.Json; - public class QueueInfo : PlaylistInfo { [JsonProperty(PropertyName = "PlaybackIndex")] diff --git a/TS3AudioBot/Web/Model/SongInfo.cs b/TS3AudioBot/Web/Model/SongInfo.cs index 7d54493b..40a4dbdd 100644 --- a/TS3AudioBot/Web/Model/SongInfo.cs +++ b/TS3AudioBot/Web/Model/SongInfo.cs @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Newtonsoft.Json; +using System; + namespace TS3AudioBot.Web.Model { - using Newtonsoft.Json; - using System; - public class SongInfo : PlaylistItemGetData { [JsonProperty(PropertyName = "Position")] diff --git a/TS3AudioBot/Web/WebServer.cs b/TS3AudioBot/Web/WebServer.cs index 327f75d3..bf2e26a8 100644 --- a/TS3AudioBot/Web/WebServer.cs +++ b/TS3AudioBot/Web/WebServer.cs @@ -7,21 +7,21 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using TS3AudioBot.Config; +using TS3AudioBot.Dependency; + namespace TS3AudioBot.Web { - using Config; - using Dependency; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; - using System; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - public sealed class WebServer : IDisposable { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); @@ -131,11 +131,6 @@ private void StartWebServerInternal() map.Run(ctx => Task.Run(() => Log.Swallow(() => api.ProcessApiV1Call(ctx)))); }); - app.Map(new PathString("/data"), map => - { - map.Run(ctx => Task.Run(() => Log.Swallow(() => { /* TODO */ }))); - }); - if (config.Interface.Enabled) { app.UseFileServer(); diff --git a/TS3AudioBot/Web/WebUtil.cs b/TS3AudioBot/Web/WebUtil.cs deleted file mode 100644 index a2191a7c..00000000 --- a/TS3AudioBot/Web/WebUtil.cs +++ /dev/null @@ -1,34 +0,0 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3AudioBot.Web -{ - using System; - - internal static class WebUtil - { - public static readonly Uri Dummy = new Uri("http://dummy/"); - - public const string Default404 = -@" - - - - - TS3AudioBot - 404 - - -

Not Found

- - -"; - - public static readonly byte[] Default404Data = System.Text.Encoding.UTF8.GetBytes(Default404); - } -} diff --git a/TS3Client/Commands/CommandMultiParameter.cs b/TS3Client/Commands/CommandMultiParameter.cs deleted file mode 100644 index 981351df..00000000 --- a/TS3Client/Commands/CommandMultiParameter.cs +++ /dev/null @@ -1,40 +0,0 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3Client.Commands -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - - /// Represents an array of data. Will be expanded to a pipe seperated list when sent. - /// Multiple will be merged automatically but will need the same array length. - public sealed class CommandMultiParameter : ICommandPart - { - public string Key { get; } - public string[] Values { get; } - public CommandPartType Type => CommandPartType.MultiParameter; - - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - //[DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } // Ambiguous - [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } - } -} diff --git a/TS3Client/Commands/CommandParameter.cs b/TS3Client/Commands/CommandParameter.cs deleted file mode 100644 index 6a0d9eb6..00000000 --- a/TS3Client/Commands/CommandParameter.cs +++ /dev/null @@ -1,54 +0,0 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3Client.Commands -{ - using Helper; - using System; - using System.Diagnostics; - using System.Globalization; - - /// Simple parameter which will be expanded to "Key=Value" and automatically escaped. - public class CommandParameter : ICommandPart - { - public string Key { get; } - public string Value { get; } - public CommandPartType Type => CommandPartType.SingleParameter; - - [DebuggerStepThrough] public static string Serialize(bool value) { return (value ? "1" : "0"); } - [DebuggerStepThrough] public static string Serialize(sbyte value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(byte value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(short value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(ushort value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(int value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(uint value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(long value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(ulong value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(float value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(double value) { return value.ToString(CultureInfo.InvariantCulture); } - [DebuggerStepThrough] public static string Serialize(string value) { return Ts3String.Escape(value); } - //[DebuggerStepThrough] public static string Serialize(TimeSpan value) { return value.TotalSeconds.ToString("F0", CultureInfo.InvariantCulture); } // ambiguous - [DebuggerStepThrough] public static string Serialize(DateTime value) { return (value - Util.UnixTimeStart).TotalSeconds.ToString("F0", CultureInfo.InvariantCulture); } - - [DebuggerStepThrough] public CommandParameter(string key, bool value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, sbyte value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, byte value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, short value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, ushort value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, int value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, uint value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, long value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, ulong value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, float value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, double value) { Key = key; Value = Serialize(value); } - [DebuggerStepThrough] public CommandParameter(string key, string value) { Key = key; Value = Serialize(value); } - //[DebuggerStepThrough] public CommandParameter(string key, TimeSpan value) { Key = key; Value = Serialize(value); } // ambiguous - [DebuggerStepThrough] public CommandParameter(string key, DateTime value) { Key = key; Value = Serialize(value); } - } -} diff --git a/TS3Client/Commands/Ts3CommandSugar.cs b/TS3Client/Commands/Ts3CommandSugar.cs deleted file mode 100644 index 26419907..00000000 --- a/TS3Client/Commands/Ts3CommandSugar.cs +++ /dev/null @@ -1,106 +0,0 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . -// - - - - - - - - - -namespace TS3Client.Commands -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - - public partial class Ts3Command - { - - - [DebuggerStepThrough] public Ts3Command Add(string key, bool? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, bool value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, sbyte? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, sbyte value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, byte? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, byte value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, short? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, short value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, ushort? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, ushort value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, int? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, int value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, uint? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, uint value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, long? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, long value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, ulong? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, ulong value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, float? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, float value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, double? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, double value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, string value) { if(value != null) Add(new CommandParameter(key, value)); return this; } - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - - [DebuggerStepThrough] public Ts3Command Add(string key, DateTime? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, DateTime value) => Add(new CommandParameter(key, value)); - - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); - - } -} \ No newline at end of file diff --git a/TS3Client/Commands/Ts3CommandSugar.tt b/TS3Client/Commands/Ts3CommandSugar.tt deleted file mode 100644 index 3f9b0112..00000000 --- a/TS3Client/Commands/Ts3CommandSugar.tt +++ /dev/null @@ -1,41 +0,0 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . -// -<#@ template debug="true" hostspecific="true" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ output extension=".cs" #> - -<# -// "TimeSpan" (Currently removed, as it is too ambiguous) -var types = new [] {"bool", "sbyte", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double", "string", "DateTime"}; -var classTypes = new [] { "string" }; -#> - -namespace TS3Client.Commands -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - - public partial class Ts3Command - { -<# foreach (var type in types) { #> -<# if (classTypes.Contains(type)) { #> - [DebuggerStepThrough] public Ts3Command Add(string key, <#= type #> value) { if(value != null) Add(new CommandParameter(key, value)); return this; } -<# } else { #> - [DebuggerStepThrough] public Ts3Command Add(string key, <#= type #>? value) { if(value.HasValue) return Add(key, value.Value); return this; } - [DebuggerStepThrough] public Ts3Command Add(string key, <#= type #> value) => Add(new CommandParameter(key, value)); -<# } #> - [DebuggerStepThrough] public Ts3Command Add(string key, IEnumerable<<#= type #>> value) => Add(new CommandMultiParameter(key, value)); -<# } #> - } -} \ No newline at end of file diff --git a/TS3Client/Declarations b/TS3Client/Declarations deleted file mode 160000 index 3f07224a..00000000 --- a/TS3Client/Declarations +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3f07224a6d03ad13918e030b8493847f59b8e147 diff --git a/TS3Client/Full/Ts3Server.old b/TS3Client/Full/Ts3Server.old deleted file mode 100644 index 0afad5ac..00000000 --- a/TS3Client/Full/Ts3Server.old +++ /dev/null @@ -1,151 +0,0 @@ -namespace TS3Client.Full -{ - using Commands; - using Helper; - using Messages; - using System; - using System.Diagnostics; - using System.IO; - using System.Net; - using CmdR = System.E; - - public class Ts3Server : IDisposable - { - private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); - private readonly Ts3Crypt ts3Crypt; - private readonly PacketHandler packetHandler; - private readonly AsyncMessageProcessor msgProc; - private readonly IEventDispatcher dispatcher; - bool initCheckDone = false; - public bool Init { get; set; } = false; - - private ConnectionContext context; - private readonly object statusLock = new object(); - - public Ts3Server() - { - ts3Crypt = new Ts3Crypt(); - ts3Crypt.Identity = Ts3Crypt.GenerateNewIdentity(0); - packetHandler = new PacketHandler(ts3Crypt, Id.Null); - msgProc = new AsyncMessageProcessor(MessageHelper.GetToServerNotificationType); - dispatcher = new ExtraThreadEventDispatcher(); - } - - private void InvokeEvent(LazyNotification lazyNotification) - { - if (!initCheckDone) - return; - - var notification = lazyNotification.Notifications; - switch (lazyNotification.NotifyType) - { - case NotificationType.ClientInit: { var result = lazyNotification.WrapSingle(); if (result.Ok) ProcessClientInit(result.Value); } break; - case NotificationType.ClientInitIv: { var result = lazyNotification.WrapSingle(); if (result.Ok) ProcessClientInitIv(result.Value); } break; - case NotificationType.Unknown: - throw Util.UnhandledDefault(lazyNotification.NotifyType); - } - } - - public void Listen(IPEndPoint addr) - { - var ctx = new ConnectionContext(); - context = ctx; - packetHandler.PacketEvent = (ref Packet packet) => { PacketEvent(ctx, ref packet); }; - packetHandler.Listen(addr); - dispatcher.Init(InvokeEvent, Id.Null); - } - - private void PacketEvent(ConnectionContext ctx, ref Packet packet) - { - if (ctx.WasExit) - return; - switch (packet.PacketType) - { - case PacketType.Command: - case PacketType.CommandLow: - var result = msgProc.PushMessage(packet.Data); - if (result.HasValue) - dispatcher.Invoke(result.Value); - break; - - case PacketType.Init1: - if (packet.Data.Length >= 301 && packet.Data[4] == 4) - { - initCheckDone = true; - var resultI = msgProc.PushMessage(packet.Data.AsMemory(301, packet.Data.Length - 301)); - if (resultI.HasValue) - dispatcher.Invoke(resultI.Value); - } - break; - } - } - - [DebuggerStepThrough] - protected CmdR SendNoResponsed(Ts3Command command) - => SendCommand(command.ExpectsResponse(false)); - - public R SendCommand(Ts3Command com) where T : IResponse, new() - { - using (var wb = new WaitBlock(msgProc.Deserializer, false)) - { - var result = SendCommandBase(wb, com); - if (!result.Ok) - return result.Error; - if (com.ExpectResponse) - return wb.WaitForMessage(); - else - // This might not be the nicest way to return in this case - // but we don't know what the response is, so this acceptable. - return Util.NoResultCommandError; - } - } - - private E SendCommandBase(WaitBlock wb, Ts3Command com) - { - if (context.WasExit || com.ExpectResponse) - return Util.TimeOutCommandError; - - var message = com.ToString(); - byte[] data = Util.Encoder.GetBytes(message); - packetHandler.AddOutgoingPacket(data, PacketType.Command); - return R.Ok; - } - - private void ProcessClientInitIv(ClientInitIv clientInitIv) - { - Log.Info("clientinitiv in"); - lock (statusLock) - { - if (ts3Crypt.CryptoInitComplete) - return; - - var beta = new byte[10]; - Util.Random.NextBytes(beta); - var betaStr = Convert.ToBase64String(beta); - - InitIvExpand(clientInitIv.Alpha, betaStr, ts3Crypt.Identity.PublicKeyString); - - ts3Crypt.CryptoInit(clientInitIv.Alpha, betaStr, clientInitIv.Omega); - } - } - - private void ProcessClientInit(ClientInit clientInit) - { - Init = true; - Console.WriteLine("init!"); - File.AppendAllText("sign.out", $"{clientInit.ClientVersion},{clientInit.ClientPlatform},{clientInit.ClientVersionSign}\n"); - } - - public CmdR InitIvExpand(string alpha, string beta, string omega) - => SendNoResponsed(new Ts3Command("initivexpand") { - { "alpha", alpha }, - { "beta", beta }, - { "omega", omega } - }); - - public void Dispose() - { - packetHandler.Stop(); - } - } -} diff --git a/TS3Client/Generated/Permissions.cs b/TS3Client/Generated/Permissions.cs deleted file mode 100644 index eb07bea7..00000000 --- a/TS3Client/Generated/Permissions.cs +++ /dev/null @@ -1,789 +0,0 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . -// - - - - - - - - - -namespace TS3Client -{ - using Helper; - - // Source: https://www.tsviewer.com/index.php?page=faq&id=12&newlanguage=en - public enum Ts3Permission - { - // ReSharper disable InconsistentNaming, UnusedMember.Global - undefined, - /// May occour on error returns with no associated permission - unknown, - /// Retrieve information about ServerQuery commands - b_serverinstance_help_view, - /// Retrieve global server version (including platform and build number) - b_serverinstance_version_view, - /// Retrieve global server information - b_serverinstance_info_view, - /// List virtual servers stored in the database - b_serverinstance_virtualserver_list, - /// List active IP bindings on multi-homed machines - b_serverinstance_binding_list, - /// List permissions available available on the server instance - b_serverinstance_permission_list, - /// Search permission assignments by name or ID - b_serverinstance_permission_find, - /// Create virtual servers - b_virtualserver_create, - /// Delete virtual servers - b_virtualserver_delete, - /// Start any virtual server in the server instance - b_virtualserver_start_any, - /// Stop any virtual server in the server instance - b_virtualserver_stop_any, - /// Change a virtual servers machine ID - b_virtualserver_change_machine_id, - /// Edit virtual server default template values - b_virtualserver_change_template, - /// Login to ServerQuery - b_serverquery_login, - /// Send text messages to all virtual servers at once - b_serverinstance_textmessage_send, - /// Retrieve global server log - b_serverinstance_log_view, - /// Write to global server log - b_serverinstance_log_add, - /// Shutdown the server process - b_serverinstance_stop, - /// Edit global settings - b_serverinstance_modify_settings, - /// Edit global ServerQuery groups - b_serverinstance_modify_querygroup, - /// Edit global template groups - b_serverinstance_modify_templates, - /// Select a virtual server - b_virtualserver_select, - /// Retrieve virtual server information - b_virtualserver_info_view, - /// Retrieve virtual server connection information - b_virtualserver_connectioninfo_view, - /// List channels on a virtual server - b_virtualserver_channel_list, - /// Search for channels on a virtual server - b_virtualserver_channel_search, - /// List clients online on a virtual server - b_virtualserver_client_list, - /// Search for clients online on a virtual server - b_virtualserver_client_search, - /// List client identities known by the virtual server - b_virtualserver_client_dblist, - /// Search for client identities known by the virtual server - b_virtualserver_client_dbsearch, - /// Retrieve client information - b_virtualserver_client_dbinfo, - /// Find permissions - b_virtualserver_permission_find, - /// Find custom fields - b_virtualserver_custom_search, - /// Start own virtual server - b_virtualserver_start, - /// Stop own virtual server - b_virtualserver_stop, - /// List privilege keys available - b_virtualserver_token_list, - /// Create new privilege keys - b_virtualserver_token_add, - /// Use a privilege keys to gain access to groups - b_virtualserver_token_use, - /// Delete a privilege key - b_virtualserver_token_delete, - /// Retrieve virtual server log - b_virtualserver_log_view, - /// Write to virtual server log - b_virtualserver_log_add, - /// Join virtual server ignoring its password - b_virtualserver_join_ignore_password, - /// Register for server notifications - b_virtualserver_notify_register, - /// Unregister from server notifications - b_virtualserver_notify_unregister, - /// Create server snapshots - b_virtualserver_snapshot_create, - /// Deploy server snapshots - b_virtualserver_snapshot_deploy, - /// Reset the server permission settings to default values - b_virtualserver_permission_reset, - /// Modify server name - b_virtualserver_modify_name, - /// Modify welcome message - b_virtualserver_modify_welcomemessage, - /// Modify servers max clients - b_virtualserver_modify_maxclients, - /// Modify reserved slots - b_virtualserver_modify_reserved_slots, - /// Modify server password - b_virtualserver_modify_password, - /// Modify default Server Group - b_virtualserver_modify_default_servergroup, - /// Modify default Channel Group - b_virtualserver_modify_default_channelgroup, - /// Modify default Channel Admin Group - b_virtualserver_modify_default_channeladmingroup, - /// Modify channel force silence value - b_virtualserver_modify_channel_forced_silence, - /// Modify individual complain settings - b_virtualserver_modify_complain, - /// Modify individual antiflood settings - b_virtualserver_modify_antiflood, - /// Modify file transfer settings - b_virtualserver_modify_ft_settings, - /// Modify file transfer quotas - b_virtualserver_modify_ft_quotas, - /// Modify individual hostmessage settings - b_virtualserver_modify_hostmessage, - /// Modify individual hostbanner settings - b_virtualserver_modify_hostbanner, - /// Modify individual hostbutton settings - b_virtualserver_modify_hostbutton, - /// Modify server port - b_virtualserver_modify_port, - /// Modify server autostart - b_virtualserver_modify_autostart, - /// Modify required identity security level - b_virtualserver_modify_needed_identity_security_level, - /// Modify priority speaker dimm modificator - b_virtualserver_modify_priority_speaker_dimm_modificator, - /// Modify log settings - b_virtualserver_modify_log_settings, - /// Modify min client version - b_virtualserver_modify_min_client_version, - /// Modify server icon - b_virtualserver_modify_icon_id, - /// Modify web server list reporting settings - b_virtualserver_modify_weblist, - /// Modify codec encryption mode - b_virtualserver_modify_codec_encryption_mode, - /// Modify temporary serverpasswords - b_virtualserver_modify_temporary_passwords, - /// Modify own temporary serverpasswords - b_virtualserver_modify_temporary_passwords_own, - /// Modify default temporary channel delete delay - b_virtualserver_modify_channel_temp_delete_delay_default, - /// Modify server nicknames - b_virtualserver_modify_nickname, - /// Modify integrations - b_virtualserver_modify_integrations, - /// Min channel creation depth in hierarchy - i_channel_min_depth, - /// Max channel creation depth in hierarchy - i_channel_max_depth, - /// Stop inheritance of channel group permissions - b_channel_group_inheritance_end, - /// Modify channel permission power - i_channel_permission_modify_power, - /// Needed modify channel permission power - i_channel_needed_permission_modify_power, - /// Retrieve channel information - b_channel_info_view, - /// Create sub-channels - b_channel_create_child, - /// Create permanent channels - b_channel_create_permanent, - /// Create semi-permanent channels - b_channel_create_semi_permanent, - /// Create temporary channels - b_channel_create_temporary, - /// Create private channel - b_channel_create_private, - /// Create channels with a topic - b_channel_create_with_topic, - /// Create channels with a description - b_channel_create_with_description, - /// Create password protected channels - b_channel_create_with_password, - /// Create channels using Speex Narrowband (8 kHz) codecs - b_channel_create_modify_with_codec_speex8, - /// Create channels using Speex Wideband (16 kHz) codecs - b_channel_create_modify_with_codec_speex16, - /// Create channels using Speex Ultra-Wideband (32 kHz) codecs - b_channel_create_modify_with_codec_speex32, - /// Create channels using the CELT Mono (48 kHz) codec - b_channel_create_modify_with_codec_celtmono48, - /// Create channels using OPUS (voice) codec - b_channel_create_modify_with_codec_opusvoice, - /// Create channels using OPUS (music) codec - b_channel_create_modify_with_codec_opusmusic, - /// Create channels with custom codec quality - i_channel_create_modify_with_codec_maxquality, - /// Create channels with minimal custom codec latency factor - i_channel_create_modify_with_codec_latency_factor_min, - /// Create channels with custom max clients - b_channel_create_with_maxclients, - /// Create channels with custom max family clients - b_channel_create_with_maxfamilyclients, - /// Create channels with custom sort order - b_channel_create_with_sortorder, - /// Create default channels - b_channel_create_with_default, - /// Create channels with needed talk power - b_channel_create_with_needed_talk_power, - /// Create new channels only with password - b_channel_create_modify_with_force_password, - /// Max delete delay for temporary channels - i_channel_create_modify_with_temp_delete_delay, - /// Move channels - b_channel_modify_parent, - /// Make channel default - b_channel_modify_make_default, - /// Make channel permanent - b_channel_modify_make_permanent, - /// Make channel semi-permanent - b_channel_modify_make_semi_permanent, - /// Make channel temporary - b_channel_modify_make_temporary, - /// Modify channel name - b_channel_modify_name, - /// Modify channel topic - b_channel_modify_topic, - /// Modify channel description - b_channel_modify_description, - /// Modify channel password - b_channel_modify_password, - /// Modify channel codec - b_channel_modify_codec, - /// Modify channel codec quality - b_channel_modify_codec_quality, - /// Modify channel codec latency factor - b_channel_modify_codec_latency_factor, - /// Modify channels max clients - b_channel_modify_maxclients, - /// Modify channels max family clients - b_channel_modify_maxfamilyclients, - /// Modify channel sort order - b_channel_modify_sortorder, - /// Change needed channel talk power - b_channel_modify_needed_talk_power, - /// Channel modify power - i_channel_modify_power, - /// Needed channel modify power - i_channel_needed_modify_power, - /// Make channel codec encrypted - b_channel_modify_make_codec_encrypted, - /// Modify temporary channel delete delay - b_channel_modify_temp_delete_delay, - /// Delete permanent channels - b_channel_delete_permanent, - /// Delete semi-permanent channels - b_channel_delete_semi_permanent, - /// Delete temporary channels - b_channel_delete_temporary, - /// Force channel delete - b_channel_delete_flag_force, - /// Delete channel power - i_channel_delete_power, - /// Needed delete channel power - i_channel_needed_delete_power, - /// Join permanent channels - b_channel_join_permanent, - /// Join semi-permanent channels - b_channel_join_semi_permanent, - /// Join temporary channels - b_channel_join_temporary, - /// Join channel ignoring its password - b_channel_join_ignore_password, - /// Ignore channels max clients limit - b_channel_join_ignore_maxclients, - /// Channel join power - i_channel_join_power, - /// Needed channel join power - i_channel_needed_join_power, - /// Channel subscribe power - i_channel_subscribe_power, - /// Needed channel subscribe power - i_channel_needed_subscribe_power, - /// Channel description view power - i_channel_description_view_power, - /// Needed channel needed description view power - i_channel_needed_description_view_power, - /// Group icon identifier - i_icon_id, - /// Max icon filesize in bytes - i_max_icon_filesize, - /// Enables icon management - b_icon_manage, - /// Group is permanent - b_group_is_permanent, - /// Group auto-update type - i_group_auto_update_type, - /// Group auto-update max value - i_group_auto_update_max_value, - /// Group sort id - i_group_sort_id, - /// Show group name in tree depending on selected mode - i_group_show_name_in_tree, - /// List server groups - b_virtualserver_servergroup_list, - /// List server group permissions - b_virtualserver_servergroup_permission_list, - /// List clients from a server group - b_virtualserver_servergroup_client_list, - /// List channel groups - b_virtualserver_channelgroup_list, - /// List channel group permissions - b_virtualserver_channelgroup_permission_list, - /// List clients from a channel group - b_virtualserver_channelgroup_client_list, - /// List client permissions - b_virtualserver_client_permission_list, - /// List channel permissions - b_virtualserver_channel_permission_list, - /// List channel client permissions - b_virtualserver_channelclient_permission_list, - /// Create server groups - b_virtualserver_servergroup_create, - /// Create channel groups - b_virtualserver_channelgroup_create, - /// Group modify power - i_group_modify_power, - /// Needed group modify power - i_group_needed_modify_power, - /// Group member add power - i_group_member_add_power, - /// Needed group member add power - i_group_needed_member_add_power, - /// Group member delete power - i_group_member_remove_power, - /// Needed group member delete power - i_group_needed_member_remove_power, - /// Permission modify power - i_permission_modify_power, - /// Ignore needed permission modify power - b_permission_modify_power_ignore, - /// Delete server groups - b_virtualserver_servergroup_delete, - /// Delete channel groups - b_virtualserver_channelgroup_delete, - /// Client permission modify power - i_client_permission_modify_power, - /// Needed client permission modify power - i_client_needed_permission_modify_power, - /// Max additional connections per client identity - i_client_max_clones_uid, - /// Max idle time in seconds - i_client_max_idletime, - /// Max avatar filesize in bytes - i_client_max_avatar_filesize, - /// Max channel subscriptions - i_client_max_channel_subscriptions, - /// Client is priority speaker - b_client_is_priority_speaker, - /// Ignore channel group permissions - b_client_skip_channelgroup_permissions, - /// Force Push-To-Talk capture mode - b_client_force_push_to_talk, - /// Ignore bans - b_client_ignore_bans, - /// Ignore antiflood measurements - b_client_ignore_antiflood, - /// Issue query commands from client - b_client_issue_client_query_command, - /// Use an reserved slot - b_client_use_reserved_slot, - /// Use channel commander - b_client_use_channel_commander, - /// Allow to request talk power - b_client_request_talker, - /// Allow deletion of avatars from other clients - b_client_avatar_delete_other, - /// Client will be sticked to current channel - b_client_is_sticky, - /// Client ignores sticky flag - b_client_ignore_sticky, - /// Retrieve client information - b_client_info_view, - /// Retrieve client permissions overview - b_client_permissionoverview_view, - /// Retrieve clients own permissions overview - b_client_permissionoverview_own, - /// View client IP address and port - b_client_remoteaddress_view, - /// ServerQuery view power - i_client_serverquery_view_power, - /// Needed ServerQuery view power - i_client_needed_serverquery_view_power, - /// View custom fields - b_client_custom_info_view, - /// Client kick power from server - i_client_kick_from_server_power, - /// Needed client kick power from server - i_client_needed_kick_from_server_power, - /// Client kick power from channel - i_client_kick_from_channel_power, - /// Needed client kick power from channel - i_client_needed_kick_from_channel_power, - /// Client ban power - i_client_ban_power, - /// Needed client ban power - i_client_needed_ban_power, - /// Client move power - i_client_move_power, - /// Needed client move power - i_client_needed_move_power, - /// Complain power - i_client_complain_power, - /// Needed complain power - i_client_needed_complain_power, - /// Show complain list - b_client_complain_list, - /// Delete own complains - b_client_complain_delete_own, - /// Delete complains - b_client_complain_delete, - /// Show banlist - b_client_ban_list, - /// Add a ban - b_client_ban_create, - /// Delete own bans - b_client_ban_delete_own, - /// Delete bans - b_client_ban_delete, - /// Max bantime - i_client_ban_max_bantime, - /// Client private message power - i_client_private_textmessage_power, - /// Needed client private message power - i_client_needed_private_textmessage_power, - /// Send text messages to virtual server - b_client_server_textmessage_send, - /// Send text messages to channel - b_client_channel_textmessage_send, - /// Send offline messages to clients - b_client_offline_textmessage_send, - /// Client talk power - i_client_talk_power, - /// Needed client talk power - i_client_needed_talk_power, - /// Client poke power - i_client_poke_power, - /// Needed client poke power - i_client_needed_poke_power, - /// Set the talker flag for clients and allow them to speak - b_client_set_flag_talker, - /// Client whisper power - i_client_whisper_power, - /// Client needed whisper power - i_client_needed_whisper_power, - /// Edit a clients description - b_client_modify_description, - /// Allow client to edit own description - b_client_modify_own_description, - /// Edit a clients properties in the database - b_client_modify_dbproperties, - /// Delete a clients properties in the database - b_client_delete_dbproperties, - /// Create or modify own ServerQuery account - b_client_create_modify_serverquery_login, - /// Browse files without channel password - b_ft_ignore_password, - /// Retrieve list of running filetransfers - b_ft_transfer_list, - /// File upload power - i_ft_file_upload_power, - /// Needed file upload power - i_ft_needed_file_upload_power, - /// File download power - i_ft_file_download_power, - /// Needed file download power - i_ft_needed_file_download_power, - /// File delete power - i_ft_file_delete_power, - /// Needed file delete power - i_ft_needed_file_delete_power, - /// File rename power - i_ft_file_rename_power, - /// Needed file rename power - i_ft_needed_file_rename_power, - /// File browse power - i_ft_file_browse_power, - /// Needed file browse power - i_ft_needed_file_browse_power, - /// Create directory power - i_ft_directory_create_power, - /// Needed create directory power - i_ft_needed_directory_create_power, - /// Download quota per client in MByte - i_ft_quota_mb_download_per_client, - /// Upload quota per client in MByte - i_ft_quota_mb_upload_per_client, - // ReSharper restore InconsistentNaming, UnusedMember.Global - } - - public static partial class Ts3PermissionHelper - { - public static string GetDescription(Ts3Permission permid) - { - switch (permid) - { - case Ts3Permission.undefined: return "Undefined permission"; - case Ts3Permission.unknown : return "May occour on error returns with no associated permission"; - case Ts3Permission.b_serverinstance_help_view : return "Retrieve information about ServerQuery commands"; - case Ts3Permission.b_serverinstance_version_view : return "Retrieve global server version (including platform and build number)"; - case Ts3Permission.b_serverinstance_info_view : return "Retrieve global server information"; - case Ts3Permission.b_serverinstance_virtualserver_list : return "List virtual servers stored in the database"; - case Ts3Permission.b_serverinstance_binding_list : return "List active IP bindings on multi-homed machines"; - case Ts3Permission.b_serverinstance_permission_list : return "List permissions available available on the server instance"; - case Ts3Permission.b_serverinstance_permission_find : return "Search permission assignments by name or ID"; - case Ts3Permission.b_virtualserver_create : return "Create virtual servers"; - case Ts3Permission.b_virtualserver_delete : return "Delete virtual servers"; - case Ts3Permission.b_virtualserver_start_any : return "Start any virtual server in the server instance"; - case Ts3Permission.b_virtualserver_stop_any : return "Stop any virtual server in the server instance"; - case Ts3Permission.b_virtualserver_change_machine_id : return "Change a virtual servers machine ID"; - case Ts3Permission.b_virtualserver_change_template : return "Edit virtual server default template values"; - case Ts3Permission.b_serverquery_login : return "Login to ServerQuery"; - case Ts3Permission.b_serverinstance_textmessage_send : return "Send text messages to all virtual servers at once"; - case Ts3Permission.b_serverinstance_log_view : return "Retrieve global server log"; - case Ts3Permission.b_serverinstance_log_add : return "Write to global server log"; - case Ts3Permission.b_serverinstance_stop : return "Shutdown the server process"; - case Ts3Permission.b_serverinstance_modify_settings : return "Edit global settings"; - case Ts3Permission.b_serverinstance_modify_querygroup : return "Edit global ServerQuery groups"; - case Ts3Permission.b_serverinstance_modify_templates : return "Edit global template groups"; - case Ts3Permission.b_virtualserver_select : return "Select a virtual server"; - case Ts3Permission.b_virtualserver_info_view : return "Retrieve virtual server information"; - case Ts3Permission.b_virtualserver_connectioninfo_view : return "Retrieve virtual server connection information"; - case Ts3Permission.b_virtualserver_channel_list : return "List channels on a virtual server"; - case Ts3Permission.b_virtualserver_channel_search : return "Search for channels on a virtual server"; - case Ts3Permission.b_virtualserver_client_list : return "List clients online on a virtual server"; - case Ts3Permission.b_virtualserver_client_search : return "Search for clients online on a virtual server"; - case Ts3Permission.b_virtualserver_client_dblist : return "List client identities known by the virtual server"; - case Ts3Permission.b_virtualserver_client_dbsearch : return "Search for client identities known by the virtual server"; - case Ts3Permission.b_virtualserver_client_dbinfo : return "Retrieve client information"; - case Ts3Permission.b_virtualserver_permission_find : return "Find permissions"; - case Ts3Permission.b_virtualserver_custom_search : return "Find custom fields"; - case Ts3Permission.b_virtualserver_start : return "Start own virtual server"; - case Ts3Permission.b_virtualserver_stop : return "Stop own virtual server"; - case Ts3Permission.b_virtualserver_token_list : return "List privilege keys available"; - case Ts3Permission.b_virtualserver_token_add : return "Create new privilege keys"; - case Ts3Permission.b_virtualserver_token_use : return "Use a privilege keys to gain access to groups"; - case Ts3Permission.b_virtualserver_token_delete : return "Delete a privilege key"; - case Ts3Permission.b_virtualserver_log_view : return "Retrieve virtual server log"; - case Ts3Permission.b_virtualserver_log_add : return "Write to virtual server log"; - case Ts3Permission.b_virtualserver_join_ignore_password : return "Join virtual server ignoring its password"; - case Ts3Permission.b_virtualserver_notify_register : return "Register for server notifications"; - case Ts3Permission.b_virtualserver_notify_unregister : return "Unregister from server notifications"; - case Ts3Permission.b_virtualserver_snapshot_create : return "Create server snapshots"; - case Ts3Permission.b_virtualserver_snapshot_deploy : return "Deploy server snapshots"; - case Ts3Permission.b_virtualserver_permission_reset : return "Reset the server permission settings to default values"; - case Ts3Permission.b_virtualserver_modify_name : return "Modify server name"; - case Ts3Permission.b_virtualserver_modify_welcomemessage : return "Modify welcome message"; - case Ts3Permission.b_virtualserver_modify_maxclients : return "Modify servers max clients"; - case Ts3Permission.b_virtualserver_modify_reserved_slots : return "Modify reserved slots"; - case Ts3Permission.b_virtualserver_modify_password : return "Modify server password"; - case Ts3Permission.b_virtualserver_modify_default_servergroup : return "Modify default Server Group"; - case Ts3Permission.b_virtualserver_modify_default_channelgroup : return "Modify default Channel Group"; - case Ts3Permission.b_virtualserver_modify_default_channeladmingroup : return "Modify default Channel Admin Group"; - case Ts3Permission.b_virtualserver_modify_channel_forced_silence : return "Modify channel force silence value"; - case Ts3Permission.b_virtualserver_modify_complain : return "Modify individual complain settings"; - case Ts3Permission.b_virtualserver_modify_antiflood : return "Modify individual antiflood settings"; - case Ts3Permission.b_virtualserver_modify_ft_settings : return "Modify file transfer settings"; - case Ts3Permission.b_virtualserver_modify_ft_quotas : return "Modify file transfer quotas"; - case Ts3Permission.b_virtualserver_modify_hostmessage : return "Modify individual hostmessage settings"; - case Ts3Permission.b_virtualserver_modify_hostbanner : return "Modify individual hostbanner settings"; - case Ts3Permission.b_virtualserver_modify_hostbutton : return "Modify individual hostbutton settings"; - case Ts3Permission.b_virtualserver_modify_port : return "Modify server port"; - case Ts3Permission.b_virtualserver_modify_autostart : return "Modify server autostart"; - case Ts3Permission.b_virtualserver_modify_needed_identity_security_level : return "Modify required identity security level"; - case Ts3Permission.b_virtualserver_modify_priority_speaker_dimm_modificator : return "Modify priority speaker dimm modificator"; - case Ts3Permission.b_virtualserver_modify_log_settings : return "Modify log settings"; - case Ts3Permission.b_virtualserver_modify_min_client_version : return "Modify min client version"; - case Ts3Permission.b_virtualserver_modify_icon_id : return "Modify server icon"; - case Ts3Permission.b_virtualserver_modify_weblist : return "Modify web server list reporting settings"; - case Ts3Permission.b_virtualserver_modify_codec_encryption_mode : return "Modify codec encryption mode"; - case Ts3Permission.b_virtualserver_modify_temporary_passwords : return "Modify temporary serverpasswords"; - case Ts3Permission.b_virtualserver_modify_temporary_passwords_own : return "Modify own temporary serverpasswords"; - case Ts3Permission.b_virtualserver_modify_channel_temp_delete_delay_default : return "Modify default temporary channel delete delay"; - case Ts3Permission.b_virtualserver_modify_nickname : return "Modify server nicknames"; - case Ts3Permission.b_virtualserver_modify_integrations : return "Modify integrations"; - case Ts3Permission.i_channel_min_depth : return "Min channel creation depth in hierarchy"; - case Ts3Permission.i_channel_max_depth : return "Max channel creation depth in hierarchy"; - case Ts3Permission.b_channel_group_inheritance_end : return "Stop inheritance of channel group permissions"; - case Ts3Permission.i_channel_permission_modify_power : return "Modify channel permission power"; - case Ts3Permission.i_channel_needed_permission_modify_power : return "Needed modify channel permission power"; - case Ts3Permission.b_channel_info_view : return "Retrieve channel information"; - case Ts3Permission.b_channel_create_child : return "Create sub-channels"; - case Ts3Permission.b_channel_create_permanent : return "Create permanent channels"; - case Ts3Permission.b_channel_create_semi_permanent : return "Create semi-permanent channels"; - case Ts3Permission.b_channel_create_temporary : return "Create temporary channels"; - case Ts3Permission.b_channel_create_private : return "Create private channel"; - case Ts3Permission.b_channel_create_with_topic : return "Create channels with a topic"; - case Ts3Permission.b_channel_create_with_description : return "Create channels with a description"; - case Ts3Permission.b_channel_create_with_password : return "Create password protected channels"; - case Ts3Permission.b_channel_create_modify_with_codec_speex8 : return "Create channels using Speex Narrowband (8 kHz) codecs"; - case Ts3Permission.b_channel_create_modify_with_codec_speex16 : return "Create channels using Speex Wideband (16 kHz) codecs"; - case Ts3Permission.b_channel_create_modify_with_codec_speex32 : return "Create channels using Speex Ultra-Wideband (32 kHz) codecs"; - case Ts3Permission.b_channel_create_modify_with_codec_celtmono48 : return "Create channels using the CELT Mono (48 kHz) codec"; - case Ts3Permission.b_channel_create_modify_with_codec_opusvoice : return "Create channels using OPUS (voice) codec"; - case Ts3Permission.b_channel_create_modify_with_codec_opusmusic : return "Create channels using OPUS (music) codec"; - case Ts3Permission.i_channel_create_modify_with_codec_maxquality : return "Create channels with custom codec quality"; - case Ts3Permission.i_channel_create_modify_with_codec_latency_factor_min : return "Create channels with minimal custom codec latency factor"; - case Ts3Permission.b_channel_create_with_maxclients : return "Create channels with custom max clients"; - case Ts3Permission.b_channel_create_with_maxfamilyclients : return "Create channels with custom max family clients"; - case Ts3Permission.b_channel_create_with_sortorder : return "Create channels with custom sort order"; - case Ts3Permission.b_channel_create_with_default : return "Create default channels"; - case Ts3Permission.b_channel_create_with_needed_talk_power : return "Create channels with needed talk power"; - case Ts3Permission.b_channel_create_modify_with_force_password : return "Create new channels only with password"; - case Ts3Permission.i_channel_create_modify_with_temp_delete_delay : return "Max delete delay for temporary channels"; - case Ts3Permission.b_channel_modify_parent : return "Move channels"; - case Ts3Permission.b_channel_modify_make_default : return "Make channel default"; - case Ts3Permission.b_channel_modify_make_permanent : return "Make channel permanent"; - case Ts3Permission.b_channel_modify_make_semi_permanent : return "Make channel semi-permanent"; - case Ts3Permission.b_channel_modify_make_temporary : return "Make channel temporary"; - case Ts3Permission.b_channel_modify_name : return "Modify channel name"; - case Ts3Permission.b_channel_modify_topic : return "Modify channel topic"; - case Ts3Permission.b_channel_modify_description : return "Modify channel description"; - case Ts3Permission.b_channel_modify_password : return "Modify channel password"; - case Ts3Permission.b_channel_modify_codec : return "Modify channel codec"; - case Ts3Permission.b_channel_modify_codec_quality : return "Modify channel codec quality"; - case Ts3Permission.b_channel_modify_codec_latency_factor : return "Modify channel codec latency factor"; - case Ts3Permission.b_channel_modify_maxclients : return "Modify channels max clients"; - case Ts3Permission.b_channel_modify_maxfamilyclients : return "Modify channels max family clients"; - case Ts3Permission.b_channel_modify_sortorder : return "Modify channel sort order"; - case Ts3Permission.b_channel_modify_needed_talk_power : return "Change needed channel talk power"; - case Ts3Permission.i_channel_modify_power : return "Channel modify power"; - case Ts3Permission.i_channel_needed_modify_power : return "Needed channel modify power"; - case Ts3Permission.b_channel_modify_make_codec_encrypted : return "Make channel codec encrypted"; - case Ts3Permission.b_channel_modify_temp_delete_delay : return "Modify temporary channel delete delay"; - case Ts3Permission.b_channel_delete_permanent : return "Delete permanent channels"; - case Ts3Permission.b_channel_delete_semi_permanent : return "Delete semi-permanent channels"; - case Ts3Permission.b_channel_delete_temporary : return "Delete temporary channels"; - case Ts3Permission.b_channel_delete_flag_force : return "Force channel delete"; - case Ts3Permission.i_channel_delete_power : return "Delete channel power"; - case Ts3Permission.i_channel_needed_delete_power : return "Needed delete channel power"; - case Ts3Permission.b_channel_join_permanent : return "Join permanent channels"; - case Ts3Permission.b_channel_join_semi_permanent : return "Join semi-permanent channels"; - case Ts3Permission.b_channel_join_temporary : return "Join temporary channels"; - case Ts3Permission.b_channel_join_ignore_password : return "Join channel ignoring its password"; - case Ts3Permission.b_channel_join_ignore_maxclients : return "Ignore channels max clients limit"; - case Ts3Permission.i_channel_join_power : return "Channel join power"; - case Ts3Permission.i_channel_needed_join_power : return "Needed channel join power"; - case Ts3Permission.i_channel_subscribe_power : return "Channel subscribe power"; - case Ts3Permission.i_channel_needed_subscribe_power : return "Needed channel subscribe power"; - case Ts3Permission.i_channel_description_view_power : return "Channel description view power"; - case Ts3Permission.i_channel_needed_description_view_power : return "Needed channel needed description view power"; - case Ts3Permission.i_icon_id : return "Group icon identifier"; - case Ts3Permission.i_max_icon_filesize : return "Max icon filesize in bytes"; - case Ts3Permission.b_icon_manage : return "Enables icon management"; - case Ts3Permission.b_group_is_permanent : return "Group is permanent"; - case Ts3Permission.i_group_auto_update_type : return "Group auto-update type"; - case Ts3Permission.i_group_auto_update_max_value : return "Group auto-update max value"; - case Ts3Permission.i_group_sort_id : return "Group sort id"; - case Ts3Permission.i_group_show_name_in_tree : return "Show group name in tree depending on selected mode"; - case Ts3Permission.b_virtualserver_servergroup_list : return "List server groups"; - case Ts3Permission.b_virtualserver_servergroup_permission_list : return "List server group permissions"; - case Ts3Permission.b_virtualserver_servergroup_client_list : return "List clients from a server group"; - case Ts3Permission.b_virtualserver_channelgroup_list : return "List channel groups"; - case Ts3Permission.b_virtualserver_channelgroup_permission_list : return "List channel group permissions"; - case Ts3Permission.b_virtualserver_channelgroup_client_list : return "List clients from a channel group"; - case Ts3Permission.b_virtualserver_client_permission_list : return "List client permissions"; - case Ts3Permission.b_virtualserver_channel_permission_list : return "List channel permissions"; - case Ts3Permission.b_virtualserver_channelclient_permission_list : return "List channel client permissions"; - case Ts3Permission.b_virtualserver_servergroup_create : return "Create server groups"; - case Ts3Permission.b_virtualserver_channelgroup_create : return "Create channel groups"; - case Ts3Permission.i_group_modify_power : return "Group modify power"; - case Ts3Permission.i_group_needed_modify_power : return "Needed group modify power"; - case Ts3Permission.i_group_member_add_power : return "Group member add power"; - case Ts3Permission.i_group_needed_member_add_power : return "Needed group member add power"; - case Ts3Permission.i_group_member_remove_power : return "Group member delete power"; - case Ts3Permission.i_group_needed_member_remove_power : return "Needed group member delete power"; - case Ts3Permission.i_permission_modify_power : return "Permission modify power"; - case Ts3Permission.b_permission_modify_power_ignore : return "Ignore needed permission modify power"; - case Ts3Permission.b_virtualserver_servergroup_delete : return "Delete server groups"; - case Ts3Permission.b_virtualserver_channelgroup_delete : return "Delete channel groups"; - case Ts3Permission.i_client_permission_modify_power : return "Client permission modify power"; - case Ts3Permission.i_client_needed_permission_modify_power : return "Needed client permission modify power"; - case Ts3Permission.i_client_max_clones_uid : return "Max additional connections per client identity"; - case Ts3Permission.i_client_max_idletime : return "Max idle time in seconds"; - case Ts3Permission.i_client_max_avatar_filesize : return "Max avatar filesize in bytes"; - case Ts3Permission.i_client_max_channel_subscriptions : return "Max channel subscriptions"; - case Ts3Permission.b_client_is_priority_speaker : return "Client is priority speaker"; - case Ts3Permission.b_client_skip_channelgroup_permissions : return "Ignore channel group permissions"; - case Ts3Permission.b_client_force_push_to_talk : return "Force Push-To-Talk capture mode"; - case Ts3Permission.b_client_ignore_bans : return "Ignore bans"; - case Ts3Permission.b_client_ignore_antiflood : return "Ignore antiflood measurements"; - case Ts3Permission.b_client_issue_client_query_command : return "Issue query commands from client"; - case Ts3Permission.b_client_use_reserved_slot : return "Use an reserved slot"; - case Ts3Permission.b_client_use_channel_commander : return "Use channel commander"; - case Ts3Permission.b_client_request_talker : return "Allow to request talk power"; - case Ts3Permission.b_client_avatar_delete_other : return "Allow deletion of avatars from other clients"; - case Ts3Permission.b_client_is_sticky : return "Client will be sticked to current channel"; - case Ts3Permission.b_client_ignore_sticky : return "Client ignores sticky flag"; - case Ts3Permission.b_client_info_view : return "Retrieve client information"; - case Ts3Permission.b_client_permissionoverview_view : return "Retrieve client permissions overview"; - case Ts3Permission.b_client_permissionoverview_own : return "Retrieve clients own permissions overview"; - case Ts3Permission.b_client_remoteaddress_view : return "View client IP address and port"; - case Ts3Permission.i_client_serverquery_view_power : return "ServerQuery view power"; - case Ts3Permission.i_client_needed_serverquery_view_power : return "Needed ServerQuery view power"; - case Ts3Permission.b_client_custom_info_view : return "View custom fields"; - case Ts3Permission.i_client_kick_from_server_power : return "Client kick power from server"; - case Ts3Permission.i_client_needed_kick_from_server_power : return "Needed client kick power from server"; - case Ts3Permission.i_client_kick_from_channel_power : return "Client kick power from channel"; - case Ts3Permission.i_client_needed_kick_from_channel_power : return "Needed client kick power from channel"; - case Ts3Permission.i_client_ban_power : return "Client ban power"; - case Ts3Permission.i_client_needed_ban_power : return "Needed client ban power"; - case Ts3Permission.i_client_move_power : return "Client move power"; - case Ts3Permission.i_client_needed_move_power : return "Needed client move power"; - case Ts3Permission.i_client_complain_power : return "Complain power"; - case Ts3Permission.i_client_needed_complain_power : return "Needed complain power"; - case Ts3Permission.b_client_complain_list : return "Show complain list"; - case Ts3Permission.b_client_complain_delete_own : return "Delete own complains"; - case Ts3Permission.b_client_complain_delete : return "Delete complains"; - case Ts3Permission.b_client_ban_list : return "Show banlist"; - case Ts3Permission.b_client_ban_create : return "Add a ban"; - case Ts3Permission.b_client_ban_delete_own : return "Delete own bans"; - case Ts3Permission.b_client_ban_delete : return "Delete bans"; - case Ts3Permission.i_client_ban_max_bantime : return "Max bantime"; - case Ts3Permission.i_client_private_textmessage_power : return "Client private message power"; - case Ts3Permission.i_client_needed_private_textmessage_power : return "Needed client private message power"; - case Ts3Permission.b_client_server_textmessage_send : return "Send text messages to virtual server"; - case Ts3Permission.b_client_channel_textmessage_send : return "Send text messages to channel"; - case Ts3Permission.b_client_offline_textmessage_send : return "Send offline messages to clients"; - case Ts3Permission.i_client_talk_power : return "Client talk power"; - case Ts3Permission.i_client_needed_talk_power : return "Needed client talk power"; - case Ts3Permission.i_client_poke_power : return "Client poke power"; - case Ts3Permission.i_client_needed_poke_power : return "Needed client poke power"; - case Ts3Permission.b_client_set_flag_talker : return "Set the talker flag for clients and allow them to speak"; - case Ts3Permission.i_client_whisper_power : return "Client whisper power"; - case Ts3Permission.i_client_needed_whisper_power : return "Client needed whisper power"; - case Ts3Permission.b_client_modify_description : return "Edit a clients description"; - case Ts3Permission.b_client_modify_own_description : return "Allow client to edit own description"; - case Ts3Permission.b_client_modify_dbproperties : return "Edit a clients properties in the database"; - case Ts3Permission.b_client_delete_dbproperties : return "Delete a clients properties in the database"; - case Ts3Permission.b_client_create_modify_serverquery_login : return "Create or modify own ServerQuery account"; - case Ts3Permission.b_ft_ignore_password : return "Browse files without channel password"; - case Ts3Permission.b_ft_transfer_list : return "Retrieve list of running filetransfers"; - case Ts3Permission.i_ft_file_upload_power : return "File upload power"; - case Ts3Permission.i_ft_needed_file_upload_power : return "Needed file upload power"; - case Ts3Permission.i_ft_file_download_power : return "File download power"; - case Ts3Permission.i_ft_needed_file_download_power : return "Needed file download power"; - case Ts3Permission.i_ft_file_delete_power : return "File delete power"; - case Ts3Permission.i_ft_needed_file_delete_power : return "Needed file delete power"; - case Ts3Permission.i_ft_file_rename_power : return "File rename power"; - case Ts3Permission.i_ft_needed_file_rename_power : return "Needed file rename power"; - case Ts3Permission.i_ft_file_browse_power : return "File browse power"; - case Ts3Permission.i_ft_needed_file_browse_power : return "Needed file browse power"; - case Ts3Permission.i_ft_directory_create_power : return "Create directory power"; - case Ts3Permission.i_ft_needed_directory_create_power : return "Needed create directory power"; - case Ts3Permission.i_ft_quota_mb_download_per_client : return "Download quota per client in MByte"; - case Ts3Permission.i_ft_quota_mb_upload_per_client : return "Upload quota per client in MByte"; - default: throw Util.UnhandledDefault(permid); - } - } - } -} \ No newline at end of file diff --git a/TS3Client/Helper/Extensions.cs b/TS3Client/Helper/Extensions.cs deleted file mode 100644 index 4519ea9a..00000000 --- a/TS3Client/Helper/Extensions.cs +++ /dev/null @@ -1,85 +0,0 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3Client.Helper -{ - using Messages; - using System; - using System.Collections.Generic; - using System.Linq; - - /// Provides useful extension methods and error formatting. - public static class Extensions - { - public static string ErrorFormat(this CommandError error) - { - if (error.MissingPermissionId != Ts3Permission.unknown && error.MissingPermissionId != Ts3Permission.undefined) - return $"{error.Id}: the command failed to execute: {error.Message} (missing permission:{error.MissingPermissionId})"; - else - return $"{error.Id}: the command failed to execute: {error.Message}"; - } - - public static R WrapSingle(in this R result) where T : class - { - if (result.Ok) - return WrapSingle(result.Value); - return R.Err(result.Error); - } - - internal static R WrapSingle(this IEnumerable enu) where T : class - { - var first = enu.FirstOrDefault(); - if (first != null) - return R.OkR(first); - return R.Err(Util.NoResultCommandError); - } - - public static R UnwrapNotification(in this R result) where T : class - { - if (!result.Ok) - return result.Error; - return R.OkR((T[])result.Value.Notifications); - } - - public static string NewUtf8String(this ReadOnlySpan span) - { -#if NETCOREAPP2_2 || NETCOREAPP3_0 - return System.Text.Encoding.UTF8.GetString(span); -#else - return System.Text.Encoding.UTF8.GetString(span.ToArray()); -#endif - } - - public static string NewUtf8String(this Span span) => NewUtf8String((ReadOnlySpan)span); - - internal static ReadOnlySpan Trim(this ReadOnlySpan span, byte elem) => span.TrimStart(elem).TrimEnd(elem); - - internal static ReadOnlySpan TrimStart(this ReadOnlySpan span, byte elem) - { - int start = 0; - for (; start < span.Length; start++) - { - if (span[start] != elem) - break; - } - return span.Slice(start); - } - - internal static ReadOnlySpan TrimEnd(this ReadOnlySpan span, byte elem) - { - int end = span.Length - 1; - for (; end >= 0; end--) - { - if (span[end] != elem) - break; - } - return span.Slice(0, end + 1); - } - } -} diff --git a/TS3Client/Helper/Util.cs b/TS3Client/Helper/Util.cs deleted file mode 100644 index aa8e6290..00000000 --- a/TS3Client/Helper/Util.cs +++ /dev/null @@ -1,86 +0,0 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3Client.Helper -{ - using Messages; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - - internal static class Util - { - public static bool IsLinux - { - get - { - int p = (int)Environment.OSVersion.Platform; - return (p == 4) || (p == 6) || (p == 128); - } - } - - public static IEnumerable GetFlags(this Enum input) => Enum.GetValues(input.GetType()).Cast().Where(input.HasFlag); - - public static Encoding Encoder { get; } = new UTF8Encoding(false, false); - - public static readonly DateTime UnixTimeStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - - public static uint UnixNow => (uint)(DateTime.UtcNow - UnixTimeStart).TotalSeconds; - - public static Random Random { get; } = new Random(); - - public static DateTime Now => DateTime.UtcNow; - - public static TimeSpan Min(TimeSpan a, TimeSpan b) => a < b ? a : b; - public static TimeSpan Max(TimeSpan a, TimeSpan b) => a > b ? a : b; - - public static Exception UnhandledDefault(T value) where T : struct { return new MissingEnumCaseException(typeof(T).Name, value.ToString()); } - - public static CommandError TimeOutCommandError { get; } = CustomError("Connection closed"); - - public static CommandError NoResultCommandError { get; } = CustomError("Result is empty"); - - public static CommandError ParserCommandError { get; } = CustomError("Result could not be parsed"); - - public static CommandError CustomError(string message) => new CommandError { Id = Ts3ErrorCode.custom_error, Message = message }; - - internal static void SetLogId(Id id) => NLog.MappedDiagnosticsContext.Set("BotId", id.ToString()); - } - - internal sealed class MissingEnumCaseException : Exception - { - public MissingEnumCaseException(string enumTypeName, string valueName) : base($"The switch does not handle the value \"{valueName}\" from \"{enumTypeName}\".") { } - public MissingEnumCaseException(string message, Exception inner) : base(message, inner) { } - } - - internal static class DebugUtil - { - public static string DebugToHex(byte[] bytes) => bytes is null ? "" : DebugToHex(bytes.AsSpan()); - - public static string DebugToHex(ReadOnlySpan bytes) - { - char[] c = new char[bytes.Length * 3]; - for (int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx) - { - byte b = (byte)(bytes[bx] >> 4); - c[cx] = (char)(b > 9 ? b - 10 + 'A' : b + '0'); - - b = (byte)(bytes[bx] & 0x0F); - c[++cx] = (char)(b > 9 ? b - 10 + 'A' : b + '0'); - c[++cx] = ' '; - } - return new string(c); - } - - public static byte[] DebugFromHex(string hex) - => hex.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToByte(x, 16)).ToArray(); - } -} diff --git a/TS3Client/Ts3Version.cs b/TS3Client/Ts3Version.cs deleted file mode 100644 index 08eb5349..00000000 --- a/TS3Client/Ts3Version.cs +++ /dev/null @@ -1,39 +0,0 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the Open Software License v. 3.0 -// -// You should have received a copy of the Open Software License along with this -// program. If not, see . - -namespace TS3Client -{ - using System.Text.RegularExpressions; - - public readonly struct Ts3Version - { - private static readonly Regex versionMatch = new Regex(@"([\d\.]+) \[Build: (\d)+\]", RegexOptions.ECMAScript | RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Ts3Version Empty = new Ts3Version("", 0); - - public string VersionName { get; } - public int BuildNumber { get; } - - private Ts3Version(string versionName, int buildNumber) - { - VersionName = versionName; - BuildNumber = buildNumber; - } - - public static Ts3Version? Parse(string versionString) - { - var match = versionMatch.Match(versionString); - if (!match.Success) - return null; - if (!int.TryParse(match.Groups[2].Value, out var buildNumber)) - return null; - return new Ts3Version(match.Groups[1].Value, buildNumber); - } - } -} diff --git a/TS3Client/Audio/AudioInterfaces.cs b/TSLib/Audio/AudioInterfaces.cs similarity index 79% rename from TS3Client/Audio/AudioInterfaces.cs rename to TSLib/Audio/AudioInterfaces.cs index 74ce7200..d838b458 100644 --- a/TS3Client/Audio/AudioInterfaces.cs +++ b/TSLib/Audio/AudioInterfaces.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,18 +7,18 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; +using System; +namespace TSLib.Audio +{ public interface IAudioStream { } - /// Passive producer will serve audio data that must be requested manually. - public interface IAudioPassiveProducer : IAudioStream + /// Passive producer will serve audio data that must be read. + public interface IAudioPassiveProducer : IAudioStream, IDisposable { int Read(byte[] buffer, int offset, int length, out Meta meta); } - /// Active producer will push audio to the out stream as soon as available. + /// Active producer will push audio to the out stream. public interface IAudioActiveProducer : IAudioStream { IAudioPassiveConsumer OutStream { get; set; } @@ -29,7 +29,7 @@ public interface IAudioPassiveConsumer : IAudioStream bool Active { get; } void Write(Span data, Meta meta); } - /// Active consumer will pull audio data as soon as available. + /// Active consumer will pull audio data when required. public interface IAudioActiveConsumer : IAudioStream { IAudioPassiveProducer InStream { get; set; } diff --git a/TS3Client/Audio/AudioMeta.cs b/TSLib/Audio/AudioMeta.cs similarity index 69% rename from TS3Client/Audio/AudioMeta.cs rename to TSLib/Audio/AudioMeta.cs index 46b9dcb0..e110e561 100644 --- a/TS3Client/Audio/AudioMeta.cs +++ b/TSLib/Audio/AudioMeta.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,12 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System.Collections.Generic; - using ClientIdT = System.UInt16; - using ChannelIdT = System.UInt64; +using System.Collections.Generic; +namespace TSLib.Audio +{ public class Meta { public Codec? Codec { get; set; } @@ -23,7 +21,7 @@ public class Meta public struct MetaIn { - public ClientIdT Sender { get; set; } + public ClientId Sender { get; set; } public bool Whisper { get; set; } } @@ -33,8 +31,8 @@ public class MetaOut public ulong TargetId { get; set; } public GroupWhisperTarget GroupWhisperTarget { get; set; } public GroupWhisperType GroupWhisperType { get; set; } - public IReadOnlyList ChannelIds { get; set; } - public IReadOnlyList ClientIds { get; set; } + public IReadOnlyList ChannelIds { get; set; } + public IReadOnlyList ClientIds { get; set; } } public enum TargetSendMode diff --git a/TS3Client/Audio/AudioPacketReader.cs b/TSLib/Audio/AudioPacketReader.cs similarity index 75% rename from TS3Client/Audio/AudioPacketReader.cs rename to TSLib/Audio/AudioPacketReader.cs index 0e9e62ea..be376b27 100644 --- a/TS3Client/Audio/AudioPacketReader.cs +++ b/TSLib/Audio/AudioPacketReader.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; - using System.Buffers.Binary; +using System; +using System.Buffers.Binary; +namespace TSLib.Audio +{ public class AudioPacketReader : IAudioPipe { public bool Active => OutStream?.Active ?? false; @@ -28,7 +28,7 @@ public void Write(Span data, Meta meta) // Skip [0,2) Voice Packet Id for now // TODO add packet id order checking // TODO add defragment start - meta.In.Sender = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(2, 2)); + meta.In.Sender = (ClientId)BinaryPrimitives.ReadUInt16BigEndian(data.Slice(2, 2)); meta.Codec = (Codec)data[4]; OutStream?.Write(data.Slice(5), meta); } diff --git a/TS3Client/Audio/AudioPipeExtensions.cs b/TSLib/Audio/AudioPipeExtensions.cs similarity index 91% rename from TS3Client/Audio/AudioPipeExtensions.cs rename to TSLib/Audio/AudioPipeExtensions.cs index 8372076c..458ac7ec 100644 --- a/TS3Client/Audio/AudioPipeExtensions.cs +++ b/TSLib/Audio/AudioPipeExtensions.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; +using System; +namespace TSLib.Audio +{ public static class AudioPipeExtensions { public static T Chain(this IAudioActiveProducer producer, T addConsumer) where T : IAudioPassiveConsumer diff --git a/TS3Client/Audio/AudioTools.cs b/TSLib/Audio/AudioTools.cs similarity index 80% rename from TS3Client/Audio/AudioTools.cs rename to TSLib/Audio/AudioTools.cs index 7ea39dec..8ac10cdd 100644 --- a/TS3Client/Audio/AudioTools.cs +++ b/TSLib/Audio/AudioTools.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System.Runtime.InteropServices; +using System.Runtime.InteropServices; +namespace TSLib.Audio +{ public static class AudioTools { public static bool TryMonoToStereo(byte[] pcm, ref int length) diff --git a/TS3Client/Audio/CheckActivePipe.cs b/TSLib/Audio/CheckActivePipe.cs similarity index 80% rename from TS3Client/Audio/CheckActivePipe.cs rename to TSLib/Audio/CheckActivePipe.cs index 601c9dbd..d0544b87 100644 --- a/TS3Client/Audio/CheckActivePipe.cs +++ b/TSLib/Audio/CheckActivePipe.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; +using System; +namespace TSLib.Audio +{ public class CheckActivePipe : IAudioPipe { public bool Active => OutStream?.Active ?? false; diff --git a/TS3Client/Audio/ClientMixdown.cs b/TSLib/Audio/ClientMixdown.cs similarity index 74% rename from TS3Client/Audio/ClientMixdown.cs rename to TSLib/Audio/ClientMixdown.cs index 96d3296f..4ea6384c 100644 --- a/TS3Client/Audio/ClientMixdown.cs +++ b/TSLib/Audio/ClientMixdown.cs @@ -1,16 +1,24 @@ -namespace TS3Client.Audio -{ - using Helper; - using System; - using System.Collections.Generic; +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +namespace TSLib.Audio +{ public class ClientMixdown : PassiveMergePipe, IAudioPassiveConsumer { public bool Active => true; private const int BufferSize = 4096 * 8; - private readonly Dictionary mixdownBuffer = new Dictionary(); + private readonly Dictionary mixdownBuffer = new Dictionary(); public void Write(Span data, Meta meta) { @@ -84,6 +92,8 @@ public int Read(byte[] buffer, int offset, int length, out Meta meta) return take; } } + + public void Dispose() { } } } } diff --git a/TS3Client/Audio/DecoderPipe.cs b/TSLib/Audio/DecoderPipe.cs similarity index 86% rename from TS3Client/Audio/DecoderPipe.cs rename to TSLib/Audio/DecoderPipe.cs index edbdba58..ac99c95c 100644 --- a/TS3Client/Audio/DecoderPipe.cs +++ b/TSLib/Audio/DecoderPipe.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using Opus; - using System; - using System.Collections.Generic; +using System; +using System.Collections.Generic; +using TSLib.Audio.Opus; +namespace TSLib.Audio +{ public class DecoderPipe : IAudioPipe, IDisposable, ISampleInfo { public bool Active => OutStream?.Active ?? false; @@ -27,7 +27,7 @@ public class DecoderPipe : IAudioPipe, IDisposable, ISampleInfo // - Clean up decoders after some time (Control: Tick?) // - Make dispose threadsafe OR redefine thread safety requirements for pipes. - private readonly Dictionary decoders = new Dictionary(); + private readonly Dictionary decoders = new Dictionary(); private readonly byte[] decodedBuffer; public DecoderPipe() @@ -67,7 +67,7 @@ public void Write(Span data, Meta meta) } } - private OpusDecoder GetDecoder(ushort sender, Codec codec) + private OpusDecoder GetDecoder(ClientId sender, Codec codec) { if (decoders.TryGetValue(sender, out var decoder)) { diff --git a/TS3Client/Audio/EncoderPipe.cs b/TSLib/Audio/EncoderPipe.cs similarity index 94% rename from TS3Client/Audio/EncoderPipe.cs rename to TSLib/Audio/EncoderPipe.cs index 21170b87..820de0f3 100644 --- a/TS3Client/Audio/EncoderPipe.cs +++ b/TSLib/Audio/EncoderPipe.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using Opus; - using System; +using System; +using TSLib.Audio.Opus; +namespace TSLib.Audio +{ public class EncoderPipe : IAudioPipe, IDisposable, ISampleInfo { public bool Active => OutStream?.Active ?? false; @@ -33,7 +33,7 @@ public class EncoderPipe : IAudioPipe, IDisposable, ISampleInfo private byte[] notEncodedBuffer = Array.Empty(); private int notEncodedLength; // https://tools.ietf.org/html/rfc6716#section-3.2.1 - private const int max_encoded_size = 255*4+255; + private const int max_encoded_size = 255 * 4 + 255; private readonly byte[] encodedBuffer = new byte[max_encoded_size]; public EncoderPipe(Codec codec) diff --git a/TS3Client/Audio/Opus/LICENSE b/TSLib/Audio/Opus/LICENSE similarity index 100% rename from TS3Client/Audio/Opus/LICENSE rename to TSLib/Audio/Opus/LICENSE diff --git a/TS3Client/Audio/Opus/NativeMethods.cs b/TSLib/Audio/Opus/NativeMethods.cs similarity index 98% rename from TS3Client/Audio/Opus/NativeMethods.cs rename to TSLib/Audio/Opus/NativeMethods.cs index de3d8664..d8d5fb79 100644 --- a/TS3Client/Audio/Opus/NativeMethods.cs +++ b/TSLib/Audio/Opus/NativeMethods.cs @@ -19,12 +19,12 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -namespace TS3Client.Audio.Opus -{ - using Helper; - using System; - using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; +using TSLib.Helper; +namespace TSLib.Audio.Opus +{ /// /// Wraps the Opus API. /// diff --git a/TS3Client/Audio/Opus/OPUS_LICENSE b/TSLib/Audio/Opus/OPUS_LICENSE similarity index 100% rename from TS3Client/Audio/Opus/OPUS_LICENSE rename to TSLib/Audio/Opus/OPUS_LICENSE diff --git a/TS3Client/Audio/Opus/OpusDecoder.cs b/TSLib/Audio/Opus/OpusDecoder.cs similarity index 99% rename from TS3Client/Audio/Opus/OpusDecoder.cs rename to TSLib/Audio/Opus/OpusDecoder.cs index 564acab6..9af15cbe 100644 --- a/TS3Client/Audio/Opus/OpusDecoder.cs +++ b/TSLib/Audio/Opus/OpusDecoder.cs @@ -19,10 +19,10 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -namespace TS3Client.Audio.Opus -{ - using System; +using System; +namespace TSLib.Audio.Opus +{ /// /// Opus audio decoder. /// diff --git a/TS3Client/Audio/Opus/OpusEncoder.cs b/TSLib/Audio/Opus/OpusEncoder.cs similarity index 99% rename from TS3Client/Audio/Opus/OpusEncoder.cs rename to TSLib/Audio/Opus/OpusEncoder.cs index 26b30084..ed6c4d12 100644 --- a/TS3Client/Audio/Opus/OpusEncoder.cs +++ b/TSLib/Audio/Opus/OpusEncoder.cs @@ -19,10 +19,10 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -namespace TS3Client.Audio.Opus -{ - using System; +using System; +namespace TSLib.Audio.Opus +{ /// /// Opus codec wrapper. /// diff --git a/TS3Client/Audio/Opus/README b/TSLib/Audio/Opus/README similarity index 100% rename from TS3Client/Audio/Opus/README rename to TSLib/Audio/Opus/README diff --git a/TS3Client/Audio/PassiveMergePipe.cs b/TSLib/Audio/PassiveMergePipe.cs similarity index 53% rename from TS3Client/Audio/PassiveMergePipe.cs rename to TSLib/Audio/PassiveMergePipe.cs index 2e3a6d34..7d983df3 100644 --- a/TS3Client/Audio/PassiveMergePipe.cs +++ b/TSLib/Audio/PassiveMergePipe.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,42 +7,52 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; - public class PassiveMergePipe : IAudioPassiveProducer +namespace TSLib.Audio +{ + public class PassiveMergePipe : IAudioPassiveProducer, IEnumerable, ICollection { - private readonly List safeProducerList = new List(); + private IAudioPassiveProducer[] safeProducerList = Array.Empty(); private readonly List producerList = new List(); private readonly object listLock = new object(); private bool changed; private readonly int[] accBuffer = new int[4096]; + public int Count => safeProducerList.Length; + public bool IsReadOnly => false; + public void Add(IAudioPassiveProducer addProducer) { if (!producerList.Contains(addProducer) && addProducer != this) { lock (listLock) { - producerList.Add(addProducer); - changed = true; + if (!producerList.Contains(addProducer)) + { + producerList.Add(addProducer); + changed = true; + } } } } - public void Remove(IAudioPassiveProducer removeProducer) + public bool Remove(IAudioPassiveProducer removeProducer) { if (producerList.Contains(removeProducer) && removeProducer != this) { lock (listLock) { - producerList.Remove(removeProducer); - changed = true; + var removed = producerList.Remove(removeProducer); + changed |= removed; + return removed; } } + return false; } public void Clear() @@ -62,8 +72,7 @@ public int Read(byte[] buffer, int offset, int length, out Meta meta) { if (changed) { - safeProducerList.Clear(); - safeProducerList.AddRange(producerList); + safeProducerList = producerList.ToArray(); changed = false; } } @@ -71,10 +80,10 @@ public int Read(byte[] buffer, int offset, int length, out Meta meta) meta = null; - if (safeProducerList.Count == 0) + if (safeProducerList.Length == 0) return 0; - if (safeProducerList.Count == 1) + if (safeProducerList.Length == 1) return safeProducerList[0].Read(buffer, offset, length, out meta); int maxReadLength = Math.Min(accBuffer.Length, length); @@ -99,5 +108,21 @@ public int Read(byte[] buffer, int offset, int length, out Meta meta) return read; } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)safeProducerList).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => safeProducerList.GetEnumerator(); + + public bool Contains(IAudioPassiveProducer item) => Enumerable.Contains(safeProducerList, item); + + public void CopyTo(IAudioPassiveProducer[] array, int arrayIndex) => Array.Copy(safeProducerList, 0, array, arrayIndex, array.Length); + + public void Dispose() + { + var list = safeProducerList; + Clear(); + foreach (var producer in list) + producer.Dispose(); + } } } diff --git a/TS3Client/Audio/PassiveSplitterPipe.cs b/TSLib/Audio/PassiveSplitterPipe.cs similarity index 87% rename from TS3Client/Audio/PassiveSplitterPipe.cs rename to TSLib/Audio/PassiveSplitterPipe.cs index 0ecac17a..7e5209a4 100644 --- a/TS3Client/Audio/PassiveSplitterPipe.cs +++ b/TSLib/Audio/PassiveSplitterPipe.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; - using System.Collections.Generic; - using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +namespace TSLib.Audio +{ public class PassiveSplitterPipe : IAudioPipe { public bool Active => consumerList.Count > 0 && consumerList.Any(x => x.Active); @@ -69,7 +69,7 @@ public void Write(Span data, Meta meta) if (buffer.Length < data.Length) buffer = new byte[data.Length]; - var bufSpan = new Span(buffer, 0, data.Length); + var bufSpan = buffer.AsSpan(0, data.Length); for (int i = 0; i < safeConsumerList.Count - 1; i++) { data.CopyTo(bufSpan); diff --git a/TS3Client/Audio/PreciseAudioTimer.cs b/TSLib/Audio/PreciseAudioTimer.cs similarity index 88% rename from TS3Client/Audio/PreciseAudioTimer.cs rename to TSLib/Audio/PreciseAudioTimer.cs index f5dc248a..500583c2 100644 --- a/TS3Client/Audio/PreciseAudioTimer.cs +++ b/TSLib/Audio/PreciseAudioTimer.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; - using System.Diagnostics; +using System; +using System.Diagnostics; +namespace TSLib.Audio +{ /// Provides a precise way to measure a playbackbuffer by tracking /// sent bytes and elapsed time. public class PreciseAudioTimer : ISampleInfo @@ -61,6 +61,18 @@ public void Start() public void Stop() => stopwatch.Stop(); + public void Reset() + { + SongPositionOffset = TimeSpan.Zero; + AbsoluteBufferLength = 0; + } + public void PushBytes(int count) => AbsoluteBufferLength += count; + + public void ResetRemoteBuffer() + { + SongPositionOffset = SongPosition; + Start(); + } } } diff --git a/TS3Client/Audio/PreciseTimedPipe.cs b/TSLib/Audio/PreciseTimedPipe.cs similarity index 85% rename from TS3Client/Audio/PreciseTimedPipe.cs rename to TSLib/Audio/PreciseTimedPipe.cs index 36de047d..f1695e46 100644 --- a/TS3Client/Audio/PreciseTimedPipe.cs +++ b/TSLib/Audio/PreciseTimedPipe.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; - using System.Threading; - using TS3Client.Helper; +using System; +using System.Threading; +using TSLib.Helper; +namespace TSLib.Audio +{ public class PreciseTimedPipe : IAudioActiveConsumer, IAudioActiveProducer, IDisposable { public PreciseAudioTimer AudioTimer { get; private set; } @@ -89,14 +89,14 @@ private void ReadTick() { int read = inStream.Read(readBuffer, 0, readBuffer.Length, out var meta); if (read == 0) - { - AudioTimer.Start(); return; - } + + if (AudioTimer.RemainingBufferDuration < TimeSpan.Zero) + AudioTimer.ResetRemoteBuffer(); AudioTimer.PushBytes(read); - OutStream?.Write(new Span(readBuffer, 0, read), meta); + OutStream?.Write(readBuffer.AsSpan(0, read), meta); } } @@ -111,7 +111,7 @@ public void Initialize(ISampleInfo info, Id id) return; running = true; - tickThread = new Thread(() => { Util.SetLogId(id); ReadLoop(); }) { Name = $"AudioPipe[{id}]" }; + tickThread = new Thread(() => { Tools.SetLogId(id); ReadLoop(); }) { Name = $"AudioPipe[{id}]" }; tickThread.Start(); } } diff --git a/TS3Client/Audio/StaticMetaPipe.cs b/TSLib/Audio/StaticMetaPipe.cs similarity index 85% rename from TS3Client/Audio/StaticMetaPipe.cs rename to TSLib/Audio/StaticMetaPipe.cs index 2a8c8afc..f080c898 100644 --- a/TS3Client/Audio/StaticMetaPipe.cs +++ b/TSLib/Audio/StaticMetaPipe.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,13 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; - using System.Collections.Generic; - using ClientIdT = System.UInt16; - using ChannelIdT = System.UInt64; +using System; +using System.Collections.Generic; +namespace TSLib.Audio +{ public class StaticMetaPipe : IAudioPipe { public bool Active => OutStream?.Active ?? false; @@ -40,7 +38,7 @@ public void SetVoice() SendMode = TargetSendMode.Voice; } - public void SetWhisper(IReadOnlyList channelIds, IReadOnlyList clientIds) + public void SetWhisper(IReadOnlyList channelIds, IReadOnlyList clientIds) { ClearData(); SendMode = TargetSendMode.Whisper; diff --git a/TS3Client/Audio/StreamAudioProducer.cs b/TSLib/Audio/StreamAudioProducer.cs similarity index 62% rename from TS3Client/Audio/StreamAudioProducer.cs rename to TSLib/Audio/StreamAudioProducer.cs index 371861c7..e0ac7915 100644 --- a/TS3Client/Audio/StreamAudioProducer.cs +++ b/TSLib/Audio/StreamAudioProducer.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,24 +7,22 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; - using System.IO; +using System.IO; +namespace TSLib.Audio +{ public class StreamAudioProducer : IAudioPassiveProducer { private readonly Stream stream; - public event EventHandler HitEnd; + + public StreamAudioProducer(Stream stream) { this.stream = stream; } public int Read(byte[] buffer, int offset, int length, out Meta meta) { meta = default; - int read = stream.Read(buffer, offset, length); - if (read < length) - HitEnd?.Invoke(this, EventArgs.Empty); - return read; + return stream.Read(buffer, offset, length); } - public StreamAudioProducer(Stream stream) { this.stream = stream; } + + public void Dispose() => stream.Dispose(); } } diff --git a/TS3Client/Audio/VolumePipe.cs b/TSLib/Audio/VolumePipe.cs similarity index 90% rename from TS3Client/Audio/VolumePipe.cs rename to TSLib/Audio/VolumePipe.cs index 44baaf02..dfbefe8e 100644 --- a/TS3Client/Audio/VolumePipe.cs +++ b/TSLib/Audio/VolumePipe.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Audio -{ - using System; - using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; +namespace TSLib.Audio +{ public class VolumePipe : IAudioPipe { public bool Active => OutStream?.Active ?? false; diff --git a/TSLib/Commands/CommandMultiParameter.cs b/TSLib/Commands/CommandMultiParameter.cs new file mode 100644 index 00000000..3557ce8d --- /dev/null +++ b/TSLib/Commands/CommandMultiParameter.cs @@ -0,0 +1,20 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +namespace TSLib.Commands +{ + /// Represents an array of data. Will be expanded to a pipe seperated list when sent. + /// Multiple will be merged automatically but will need the same array length. + public sealed partial class CommandMultiParameter : ICommandPart + { + public string Key { get; } + public string[] Values { get; } + public CommandPartType Type => CommandPartType.MultiParameter; + } +} diff --git a/TS3Client/Commands/CommandOption.cs b/TSLib/Commands/CommandOption.cs similarity index 83% rename from TS3Client/Commands/CommandOption.cs rename to TSLib/Commands/CommandOption.cs index 7b2aad92..05c351e4 100644 --- a/TS3Client/Commands/CommandOption.cs +++ b/TSLib/Commands/CommandOption.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Commands -{ - using Helper; - using System; - using System.Linq; - using System.Text; +using System; +using System.Linq; +using System.Text; +using TSLib.Helper; +namespace TSLib.Commands +{ /// Command options which will be added with "-name" at the and. public class CommandOption : ICommandPart { diff --git a/TSLib/Commands/CommandParameter.cs b/TSLib/Commands/CommandParameter.cs new file mode 100644 index 00000000..1a9ad731 --- /dev/null +++ b/TSLib/Commands/CommandParameter.cs @@ -0,0 +1,38 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Diagnostics; +using System.Globalization; +using TSLib.Helper; + +namespace TSLib.Commands +{ + /// Simple parameter which will be expanded to "Key=Value" and automatically escaped. + public sealed partial class CommandParameter : ICommandPart + { + public string Key { get; } + public string Value { get; } + public CommandPartType Type => CommandPartType.SingleParameter; + + [DebuggerStepThrough] public static string Serialize(bool value) => value ? "1" : "0"; + [DebuggerStepThrough] public static string Serialize(sbyte value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(byte value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(short value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(ushort value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(int value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(uint value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(long value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(ulong value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(float value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(double value) => value.ToString(CultureInfo.InvariantCulture); + [DebuggerStepThrough] public static string Serialize(string value) => TsString.Escape(value); + [DebuggerStepThrough] public static string Serialize(DateTime value) => Tools.ToUnix(value).ToString(CultureInfo.InvariantCulture); + } +} diff --git a/TS3Client/Commands/ICommandPart.cs b/TSLib/Commands/ICommandPart.cs similarity index 77% rename from TS3Client/Commands/ICommandPart.cs rename to TSLib/Commands/ICommandPart.cs index 073e9f5b..22a7eaef 100644 --- a/TS3Client/Commands/ICommandPart.cs +++ b/TSLib/Commands/ICommandPart.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,7 +7,7 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Commands +namespace TSLib.Commands { public interface ICommandPart { diff --git a/TS3Client/Commands/Ts3Command.cs b/TSLib/Commands/TsCommand.cs similarity index 82% rename from TS3Client/Commands/Ts3Command.cs rename to TSLib/Commands/TsCommand.cs index 2b25c9bd..c9abe899 100644 --- a/TS3Client/Commands/Ts3Command.cs +++ b/TSLib/Commands/TsCommand.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,19 +7,19 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Commands -{ - using Helper; - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using TSLib.Helper; +namespace TSLib.Commands +{ /// Builds TeamSpeak (query) commands from parameters. - public partial class Ts3Command : IEnumerable, IEnumerable + public partial class TsCommand : IEnumerable, IEnumerable { private static readonly Regex CommandMatch = new Regex("[a-z0-9_]+", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ECMAScript); @@ -32,7 +32,7 @@ public partial class Ts3Command : IEnumerable, IEnumerable /// Creates a new command. /// The command name. [DebuggerStepThrough] - public Ts3Command(string command) + public TsCommand(string command) { ExpectResponse = true; Command = command; @@ -43,16 +43,19 @@ public Ts3Command(string command) /// The parameters to be added to this command. /// See , or for more information. [DebuggerStepThrough] - public Ts3Command(string command, ICollection parameter) : this(command) + public TsCommand(string command, ICollection parameter) : this(command) { this.parameter = parameter; } [DebuggerStepThrough] - public virtual Ts3Command Add(ICommandPart addParameter) + public virtual TsCommand Add(ICommandPart addParameter) { cached = false; - parameter = parameter ?? new List(); + if(parameter == null) + parameter = new List(); + else if (parameter.IsReadOnly) + parameter = new List(parameter); parameter.Add(addParameter); return this; } @@ -66,7 +69,7 @@ public virtual Ts3Command Add(ICommandPart addParameter) /// Whether or not to wait for the error-return-code /// Returns this command. Useful for fluent command building. [DebuggerStepThrough] - public Ts3Command ExpectsResponse(bool expects) + public TsCommand ExpectsResponse(bool expects) { ExpectResponse = expects; return this; @@ -97,7 +100,7 @@ public static string BuildToString(string command, IEnumerable par if (!CommandMatch.IsMatch(command)) throw new ArgumentException("Invalid command characters", nameof(command)); - var strb = new StringBuilder(Ts3String.Escape(command)); + var strb = new StringBuilder(TsString.Escape(command)); List multiParamList = null; List optionList = null; @@ -120,7 +123,7 @@ public static string BuildToString(string command, IEnumerable par optionList.Add((CommandOption)param); break; default: - throw Util.UnhandledDefault(param.Type); + throw Tools.UnhandledDefault(param.Type); } } @@ -159,15 +162,15 @@ public static string BuildToString(string command, IEnumerable par IEnumerator IEnumerable.GetEnumerator() => GetParameter().GetEnumerator(); } - public class Ts3RawCommand : Ts3Command + public class TsRawCommand : TsCommand { - public Ts3RawCommand(string raw) : base(null) + public TsRawCommand(string raw) : base(null) { this.raw = raw; this.cached = true; } - public override Ts3Command Add(ICommandPart addParameter) + public override TsCommand Add(ICommandPart addParameter) { throw new InvalidOperationException("Raw commands cannot be extented"); } diff --git a/TSLib/Commands/TsCommand.gen.cs b/TSLib/Commands/TsCommand.gen.cs new file mode 100644 index 00000000..6eb12b65 --- /dev/null +++ b/TSLib/Commands/TsCommand.gen.cs @@ -0,0 +1,210 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . +// + + + + + + + + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace TSLib.Commands +{ + partial class TsCommand + { + + [DebuggerStepThrough] public TsCommand Add(string key, bool? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, bool value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, sbyte? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, sbyte value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, byte? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, byte value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, short? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, short value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, ushort? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, ushort value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, int? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, int value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, uint? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, uint value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, long? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, long value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, ulong? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, ulong value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, float? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, float value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, double? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, double value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, string value) { if(value != null) Add(new CommandParameter(key, value)); return this; } + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, DateTime? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, DateTime value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, Uid? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, Uid value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, ClientDbId? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, ClientDbId value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, ClientId? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, ClientId value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, ChannelId? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, ChannelId value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, ServerGroupId? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, ServerGroupId value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, ChannelGroupId? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, ChannelGroupId value) => Add(new CommandParameter(key, value)); + + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable value) => Add(new CommandMultiParameter(key, value)); + + } + + partial class CommandParameter + { + + [DebuggerStepThrough] public CommandParameter(string key, bool value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, sbyte value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, byte value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, short value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, ushort value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, int value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, uint value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, long value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, ulong value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, float value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, double value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, string value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, DateTime value) { Key = key; Value = Serialize(value); } + + [DebuggerStepThrough] public CommandParameter(string key, Uid value) { Key = key; Value = Serialize(value.Value); } + + [DebuggerStepThrough] public CommandParameter(string key, ClientDbId value) { Key = key; Value = Serialize(value.Value); } + + [DebuggerStepThrough] public CommandParameter(string key, ClientId value) { Key = key; Value = Serialize(value.Value); } + + [DebuggerStepThrough] public CommandParameter(string key, ChannelId value) { Key = key; Value = Serialize(value.Value); } + + [DebuggerStepThrough] public CommandParameter(string key, ServerGroupId value) { Key = key; Value = Serialize(value.Value); } + + [DebuggerStepThrough] public CommandParameter(string key, ChannelGroupId value) { Key = key; Value = Serialize(value.Value); } + + } + + partial class CommandMultiParameter + { + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(v => CommandParameter.Serialize(v.Value)).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(v => CommandParameter.Serialize(v.Value)).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(v => CommandParameter.Serialize(v.Value)).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(v => CommandParameter.Serialize(v.Value)).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(v => CommandParameter.Serialize(v.Value)).ToArray(); } + + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable value) { Key = key; Values = value.Select(v => CommandParameter.Serialize(v.Value)).ToArray(); } + + } +} \ No newline at end of file diff --git a/TSLib/Commands/TsCommand.gen.tt b/TSLib/Commands/TsCommand.gen.tt new file mode 100644 index 00000000..f73cdb71 --- /dev/null +++ b/TSLib/Commands/TsCommand.gen.tt @@ -0,0 +1,67 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . +// +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> + +<# +// "TimeSpan" (Currently removed, as it is too ambiguous) +var types = new [] {"bool", "sbyte", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double", "string", "DateTime" }; +var aliasTypes = new [] { "Uid", "ClientDbId", "ClientId", "ChannelId", "ServerGroupId", "ChannelGroupId"}; +var classTypes = new [] { "string" }; +#> + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace TSLib.Commands +{ + partial class TsCommand + { +<# +foreach (var type in types.Concat(aliasTypes)) { + if (classTypes.Contains(type)) { #> + [DebuggerStepThrough] public TsCommand Add(string key, <#= type #> value) { if(value != null) Add(new CommandParameter(key, value)); return this; } +<# } else { #> + [DebuggerStepThrough] public TsCommand Add(string key, <#= type #>? value) { if(value.HasValue) return Add(key, value.Value); return this; } + [DebuggerStepThrough] public TsCommand Add(string key, <#= type #> value) => Add(new CommandParameter(key, value)); +<# } #> + [DebuggerStepThrough] public TsCommand Add(string key, IEnumerable<<#= type #>> value) => Add(new CommandMultiParameter(key, value)); +<# } #> + } + + partial class CommandParameter + { +<# foreach (var type in types.Concat(aliasTypes)) { + var value = aliasTypes.Contains(type) ? "value.Value" : "value"; + #> + [DebuggerStepThrough] public CommandParameter(string key, <#= type #> value) { Key = key; Value = Serialize(<#= value #>); } +<# } #> + } + + partial class CommandMultiParameter + { +<# +foreach (var type in types.Concat(aliasTypes)) { + if (aliasTypes.Contains(type)) { #> + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable<<#= type #>> value) { Key = key; Values = value.Select(v => CommandParameter.Serialize(v.Value)).ToArray(); } +<# } else { #> + [DebuggerStepThrough] public CommandMultiParameter(string key, IEnumerable<<#= type #>> value) { Key = key; Values = value.Select(CommandParameter.Serialize).ToArray(); } +<# + } +} +#> + } +} \ No newline at end of file diff --git a/TS3Client/Commands/Ts3Const.cs b/TSLib/Commands/TsConst.cs similarity index 91% rename from TS3Client/Commands/Ts3Const.cs rename to TSLib/Commands/TsConst.cs index 6de86eaa..f1c69f19 100644 --- a/TS3Client/Commands/Ts3Const.cs +++ b/TSLib/Commands/TsConst.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,9 +7,9 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Commands +namespace TSLib.Commands { - public static class Ts3Const + public static class TsConst { // Common Definitions diff --git a/TS3Client/Commands/Ts3String.cs b/TSLib/Commands/TsString.cs similarity index 93% rename from TS3Client/Commands/Ts3String.cs rename to TSLib/Commands/TsString.cs index 29cb345a..042f64ef 100644 --- a/TS3Client/Commands/Ts3String.cs +++ b/TSLib/Commands/TsString.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,18 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Commands -{ - using System; - using System.Linq; - using System.Text; +using System; +using System.Linq; +using System.Text; #if NETCOREAPP3_0 - using System.Runtime.Intrinsics; - using System.Runtime.Intrinsics.X86; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; #endif - - public static class Ts3String +namespace TSLib.Commands +{ + public static class TsString { public static string Escape(string stringToEscape) => Escape(stringToEscape.AsSpan()); diff --git a/TS3Client/ConnectionData.cs b/TSLib/ConnectionData.cs similarity index 92% rename from TS3Client/ConnectionData.cs rename to TSLib/ConnectionData.cs index e78de6b9..54cd5246 100644 --- a/TS3Client/ConnectionData.cs +++ b/TSLib/ConnectionData.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using Full; - using Helper; +using TSLib.Full; +using TSLib.Helper; +namespace TSLib +{ /// Used to pass basic connecting information. (Usually for the query) public class ConnectionData { @@ -35,7 +35,7 @@ public class ConnectionDataFull : ConnectionData /// class. Please keep in mind that the version data has to have valid sign /// to be accepted by an official TeamSpeak 3 Server. ///
- public VersionSign VersionSign { get; set; } = VersionSign.VER_WIN_3_1_8; + public VersionSign VersionSign { get; set; } = VersionSign.VER_WIN_3_X_X; /// The display username. public string Username { get; set; } /// The server password. Leave null if none. @@ -58,7 +58,7 @@ public readonly struct Password private Password(string hashed) { HashedPassword = hashed; } public static Password FromHash(string hash) => new Password(hash); - public static Password FromPlain(string pass) => new Password(Ts3Crypt.HashPassword(pass)); + public static Password FromPlain(string pass) => new Password(TsCrypt.HashPassword(pass)); public static implicit operator Password(string pass) => FromPlain(pass); } diff --git a/TSLib/Declarations b/TSLib/Declarations new file mode 160000 index 00000000..b83a2fbf --- /dev/null +++ b/TSLib/Declarations @@ -0,0 +1 @@ +Subproject commit b83a2fbfbd52c710a1e83163bc969a5ecfed4b21 diff --git a/TS3Client/DisconnectEventArgs.cs b/TSLib/DisconnectEventArgs.cs similarity index 78% rename from TS3Client/DisconnectEventArgs.cs rename to TSLib/DisconnectEventArgs.cs index aa665135..38c5b025 100644 --- a/TS3Client/DisconnectEventArgs.cs +++ b/TSLib/DisconnectEventArgs.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using Messages; - using System; +using System; +using TSLib.Messages; +namespace TSLib +{ public class DisconnectEventArgs : EventArgs { public Reason ExitReason { get; } diff --git a/TS3Client/EventDispatcher.cs b/TSLib/EventDispatcher.cs similarity index 90% rename from TS3Client/EventDispatcher.cs rename to TSLib/EventDispatcher.cs index f93669b8..acf9f29a 100644 --- a/TS3Client/EventDispatcher.cs +++ b/TSLib/EventDispatcher.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,17 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using Helper; - using System; - using System.Collections.Concurrent; - using System.Threading; +using System; +using System.Collections.Concurrent; +using System.Threading; +using TSLib.Helper; +namespace TSLib +{ internal static class EventDispatcherHelper { - public const string DispatcherTitle = "TS3 Dispatcher"; - public const string EventLoopTitle = "TS3 MessageLoop"; + public const string DispatcherTitle = "TS Dispatcher"; + public const string EventLoopTitle = "TS MessageLoop"; internal static string CreateLogThreadName(string threadName, Id id) => threadName + (id == Id.Null ? "" : $"[{id}]"); @@ -56,7 +56,7 @@ public void Init(Action dispatcher, Id id) dispatchThread = new Thread(() => { - Util.SetLogId(id); + Tools.SetLogId(id); DispatchLoop(); }) { Name = EventDispatcherHelper.CreateDispatcherTitle(id) }; diff --git a/TS3Client/FileTransferManager.cs b/TSLib/FileTransferManager.cs similarity index 88% rename from TS3Client/FileTransferManager.cs rename to TSLib/FileTransferManager.cs index 0e5fe7a4..cf4ecd7d 100644 --- a/TS3Client/FileTransferManager.cs +++ b/TSLib/FileTransferManager.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,36 +7,35 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using Helper; - using Messages; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net.Sockets; - using System.Security.Cryptography; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using ChannelIdT = System.UInt64; - using IOFileInfo = System.IO.FileInfo; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TSLib.Helper; +using TSLib.Messages; +using IOFileInfo = System.IO.FileInfo; +namespace TSLib +{ /// Queues and manages up- and downloads. public sealed class FileTransferManager { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); - private readonly Ts3BaseFunctions parent; + private readonly TsBaseFunctions parent; private readonly Queue transferQueue = new Queue(); private Thread workerThread; private bool threadEnd; private ushort transferIdCnt; - public FileTransferManager(Ts3BaseFunctions ts3Connection) + public FileTransferManager(TsBaseFunctions tsConnection) { - parent = ts3Connection; - //ts3connection.OnFileTransferStatus += FileStatusNotification; + parent = tsConnection; + //tsconnection.OnFileTransferStatus += FileStatusNotification; } /// Initiate a file upload to the server. @@ -47,7 +46,7 @@ public FileTransferManager(Ts3BaseFunctions ts3Connection) /// False will throw an exception if the file already exists. /// The password for the channel. /// A token to track the file transfer. - public R UploadFile(IOFileInfo file, ChannelIdT channel, string path, bool overwrite = false, string channelPassword = "") + public R UploadFile(IOFileInfo file, ChannelId channel, string path, bool overwrite = false, string channelPassword = "") => UploadFile(file.Open(FileMode.Open, FileAccess.Read), channel, path, overwrite, channelPassword); /// Initiate a file upload to the server. @@ -60,7 +59,7 @@ public R UploadFile(IOFileInfo file, ChannelIdT /// True will the stream after the upload is finished. /// Will generate a md5 sum of the uploaded file. /// A token to track the file transfer. - public R UploadFile(Stream stream, ChannelIdT channel, string path, bool overwrite = false, string channelPassword = "", bool closeStream = true, bool createMd5 = false) + public R UploadFile(Stream stream, ChannelId channel, string path, bool overwrite = false, string channelPassword = "", bool closeStream = true, bool createMd5 = false) { ushort cftid = GetFreeTransferId(); var request = parent.FileTransferInitUpload(channel, path, channelPassword, cftid, stream.Length, overwrite, false); @@ -80,7 +79,7 @@ public R UploadFile(Stream stream, ChannelIdT c /// The download path within the channel. Eg: "file.txt", "path/file.png" /// The password for the channel. /// A token to track the file transfer. - public R DownloadFile(IOFileInfo file, ChannelIdT channel, string path, string channelPassword = "") + public R DownloadFile(IOFileInfo file, ChannelId channel, string path, string channelPassword = "") => DownloadFile(file.Open(FileMode.Create, FileAccess.Write), channel, path, channelPassword, true); /// Initiate a file download from the server. @@ -90,7 +89,7 @@ public R DownloadFile(IOFileInfo file, ChannelI /// The password for the channel. /// True will the stream after the download is finished. /// A token to track the file transfer. - public R DownloadFile(Stream stream, ChannelIdT channel, string path, string channelPassword = "", bool closeStream = true) + public R DownloadFile(Stream stream, ChannelId channel, string path, string channelPassword = "", bool closeStream = true) { ushort cftid = GetFreeTransferId(); var request = parent.FileTransferInitDownload(channel, path, channelPassword, cftid, 0); @@ -113,7 +112,7 @@ private void StartWorker(FileTransferToken token) if (threadEnd || workerThread is null || !workerThread.IsAlive) { threadEnd = false; - workerThread = new Thread(() => { Util.SetLogId(parent.ConnectionData.LogId); TransferLoop(); }) { Name = $"FileTransfer[{parent.ConnectionData.LogId}]" }; + workerThread = new Thread(() => { Tools.SetLogId(parent.ConnectionData.LogId); TransferLoop(); }) { Name = $"FileTransfer[{parent.ConnectionData.LogId}]" }; workerThread.Start(); } } @@ -131,7 +130,7 @@ public E Resume(FileTransferToken token) lock (token) { if (token.Status != TransferStatus.Cancelled) - return Util.CustomError("Only cancelled transfers can be resumed"); + return CommandError.Custom("Only cancelled transfers can be resumed"); if (token.Direction == TransferDirection.Upload) { @@ -188,7 +187,7 @@ public R GetStats(FileTransferToken token) lock (token) { if (token.Status != TransferStatus.Transfering) - return Util.CustomError("No transfer found"); + return CommandError.Custom("No transfer found"); } var result = parent.FileTransferList(); if (result.Ok) @@ -292,7 +291,7 @@ public sealed class FileTransferToken { public Stream LocalStream { get; } public TransferDirection Direction { get; } - public ChannelIdT ChannelId { get; } + public ChannelId ChannelId { get; } public string Path { get; } public long Size { get; } public ushort ClientTransferId { get; } @@ -307,20 +306,20 @@ public sealed class FileTransferToken public TransferStatus Status { get; internal set; } - public FileTransferToken(Stream localStream, FileUpload upload, ChannelIdT channelId, + public FileTransferToken(Stream localStream, FileUpload upload, ChannelId channelId, string path, string channelPassword, long size, bool createMd5) : this(localStream, upload.ClientFileTransferId, upload.ServerFileTransferId, TransferDirection.Upload, channelId, path, channelPassword, upload.Port, (long)upload.SeekPosition, upload.FileTransferKey, size, createMd5) { } - public FileTransferToken(Stream localStream, FileDownload download, ChannelIdT channelId, + public FileTransferToken(Stream localStream, FileDownload download, ChannelId channelId, string path, string channelPassword, long seekPos) : this(localStream, download.ClientFileTransferId, download.ServerFileTransferId, TransferDirection.Download, channelId, path, channelPassword, download.Port, seekPos, download.FileTransferKey, (long)download.Size, false) { } public FileTransferToken(Stream localStream, ushort cftid, ushort sftid, - TransferDirection dir, ChannelIdT channelId, string path, string channelPassword, ushort port, long seekPos, + TransferDirection dir, ChannelId channelId, string path, string channelPassword, ushort port, long seekPos, string transferKey, long size, bool createMd5) { CloseStreamWhenDone = false; diff --git a/TS3Client/Full/Book/Book.cs b/TSLib/Full/Book/Book.cs similarity index 78% rename from TS3Client/Full/Book/Book.cs rename to TSLib/Full/Book/Book.cs index 50bd49f0..a756609b 100644 --- a/TS3Client/Full/Book/Book.cs +++ b/TSLib/Full/Book/Book.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,41 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full.Book -{ - using Helper; - using Messages; - using System; - using System.Linq; - -#pragma warning disable CS8019 - using i8 = System.SByte; - using u8 = System.Byte; - using i16 = System.Int16; - using u16 = System.UInt16; - using i32 = System.Int32; - using u32 = System.UInt32; - using i64 = System.Int64; - using u64 = System.UInt64; - using f32 = System.Single; - using d64 = System.Double; - using str = System.String; - - using Duration = System.TimeSpan; - using DurationSeconds = System.TimeSpan; - using DurationMilliseconds = System.TimeSpan; - using SocketAddr = System.String; - - using Uid = System.String; - using ClientDbId = System.UInt64; - using ClientId = System.UInt16; - using ChannelId = System.UInt64; - using ServerGroupId = System.UInt64; - using ChannelGroupId = System.UInt64; - using IconHash = System.Int32; - using ConnectionId = System.UInt32; -#pragma warning restore CS8019 +using System; +using System.Linq; +using TSLib.Helper; +using TSLib.Messages; +using SocketAddr = System.String; +namespace TSLib.Full.Book +{ public partial class Connection { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); @@ -55,10 +28,6 @@ public Channel CurrentChannel() return GetChannel(self.Channel); } - // TODO - // Many operations can be checked if they were successful (like remove or get). - // In cases which this fails we should print an error. - private void SetServer(Server server) { Server = server; @@ -119,19 +88,35 @@ private Server GetServer() return Server; } + // Manual post event functions + + partial void PostClientEnterView(ClientEnterView msg) => SetOwnChannelSubscribed(msg.ClientId); + partial void PostClientMoved(ClientMoved msg) => SetOwnChannelSubscribed(msg.ClientId); + private void SetOwnChannelSubscribed(ClientId clientId) + { + if (clientId == OwnClient) + { + var curChan = CurrentChannel(); + if (curChan != null) + { + curChan.Subscribed = true; + } + } + } + // Manual move functions private static (MaxClients?, MaxClients?) MaxClientsCcFun(ChannelCreated msg) => MaxClientsFun(msg.MaxClients, msg.IsMaxClientsUnlimited, msg.MaxFamilyClients, msg.IsMaxFamilyClientsUnlimited, msg.InheritsMaxFamilyClients); private static (MaxClients?, MaxClients?) MaxClientsCeFun(ChannelEdited msg) => MaxClientsFun(msg.MaxClients, msg.IsMaxClientsUnlimited, msg.MaxFamilyClients, msg.IsMaxFamilyClientsUnlimited, msg.InheritsMaxFamilyClients); private static (MaxClients?, MaxClients?) MaxClientsClFun(ChannelList msg) => MaxClientsFun(msg.MaxClients, msg.IsMaxClientsUnlimited, msg.MaxFamilyClients, msg.IsMaxFamilyClientsUnlimited, msg.InheritsMaxFamilyClients); - private static (MaxClients?, MaxClients?) MaxClientsFun(i32? MaxClients, bool? IsMaxClientsUnlimited, i32? MaxFamilyClients, bool? IsMaxFamilyClientsUnlimited, bool? InheritsMaxFamilyClients) + private static (MaxClients?, MaxClients?) MaxClientsFun(int? MaxClients, bool? IsMaxClientsUnlimited, int? MaxFamilyClients, bool? IsMaxFamilyClientsUnlimited, bool? InheritsMaxFamilyClients) { var chn = new MaxClients(); if (IsMaxClientsUnlimited == true) chn.LimitKind = MaxClientsKind.Unlimited; else { chn.LimitKind = MaxClientsKind.Limited; - chn.Count = (u16)Math.Max(Math.Min(ushort.MaxValue, MaxClients ?? ushort.MaxValue), 0); + chn.Count = (ushort)Math.Max(Math.Min(ushort.MaxValue, MaxClients ?? ushort.MaxValue), 0); } var fam = new MaxClients(); @@ -140,7 +125,7 @@ private static (MaxClients?, MaxClients?) MaxClientsFun(i32? MaxClients, bool? I else { fam.LimitKind = MaxClientsKind.Limited; - fam.Count = (u16)Math.Max(Math.Min(ushort.MaxValue, MaxFamilyClients ?? ushort.MaxValue), 0); + fam.Count = (ushort)Math.Max(Math.Min(ushort.MaxValue, MaxFamilyClients ?? ushort.MaxValue), 0); } return (chn, fam); } @@ -155,23 +140,33 @@ private static ChannelType ChannelTypeFun(bool? semi, bool? perma) else return ChannelType.Temporary; } - private str AwayCevFun(ClientEnterView msg) => default; - private str AwayCuFun(ClientUpdated msg) => default; + private static string AwayCevFun(ClientEnterView msg) => AwayFun(msg.IsAway, msg.AwayMessage); + private static string AwayCuFun(ClientUpdated msg) => AwayFun(msg.IsAway, msg.AwayMessage); + private static string AwayFun(bool? away, string msg) + { + if (away == true) + return msg ?? ""; + if (away == false) + return ""; + return null; + } private static TalkPowerRequest? TalkPowerCevFun(ClientEnterView msg) { - if (msg.TalkPowerRequestTime != Util.UnixTimeStart) + if (msg.TalkPowerRequestTime != Tools.UnixTimeStart) return new TalkPowerRequest() { Time = msg.TalkPowerRequestTime, Message = msg.TalkPowerRequestMessage ?? "" }; return null; } private static TalkPowerRequest? TalkPowerCuFun(ClientUpdated msg) => TalkPowerFun(msg.TalkPowerRequestTime, msg.TalkPowerRequestMessage); - private static TalkPowerRequest? TalkPowerFun(DateTime? time, str message) + private static TalkPowerRequest? TalkPowerFun(DateTime? time, string message) { - if (time != null && time != Util.UnixTimeStart) // TODO + if (time != null && time != Tools.UnixTimeStart) // TODO return new TalkPowerRequest() { Time = time.Value, Message = message ?? "" }; return null; } + private static ClientType ClientTypeCevFun(ClientEnterView msg) => msg.ClientType; + private ChannelId ChannelOrderCcFun(ChannelCreated msg) { ChannelOrderInsert(msg.ChannelId, msg.Order, msg.ParentId); @@ -237,7 +232,7 @@ private void SetClientDataFun(InitServer initServer) OwnClient = initServer.ClientId; } - private bool ChannelSubscribeFun(ChannelSubscribed msg) => true; + private bool ChannelSubscribeFun(ChannelSubscribed _) => true; private bool ChannelUnsubscribeFun(ChannelUnsubscribed msg) { diff --git a/TS3Client/Full/Book/SpecialTypes.cs b/TSLib/Full/Book/SpecialTypes.cs similarity index 82% rename from TS3Client/Full/Book/SpecialTypes.cs rename to TSLib/Full/Book/SpecialTypes.cs index d4281e5e..033649b5 100644 --- a/TS3Client/Full/Book/SpecialTypes.cs +++ b/TSLib/Full/Book/SpecialTypes.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full.Book -{ - using System; +using System; +namespace TSLib.Full.Book +{ public struct MaxClients { public ushort Count { get; internal set; } diff --git a/TS3Client/Full/GenerationWindow.cs b/TSLib/Full/GenerationWindow.cs similarity index 94% rename from TS3Client/Full/GenerationWindow.cs rename to TSLib/Full/GenerationWindow.cs index c432b4dd..2350535e 100644 --- a/TS3Client/Full/GenerationWindow.cs +++ b/TSLib/Full/GenerationWindow.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full -{ - using System; +using System; +namespace TSLib.Full +{ public sealed class GenerationWindow { public int MappedBaseOffset { get; set; } diff --git a/TS3Client/Full/IdentityData.cs b/TSLib/Full/IdentityData.cs similarity index 69% rename from TS3Client/Full/IdentityData.cs rename to TSLib/Full/IdentityData.cs index d819cbc4..2180bf7d 100644 --- a/TS3Client/Full/IdentityData.cs +++ b/TSLib/Full/IdentityData.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,15 +7,15 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full -{ - using Org.BouncyCastle.Math; - using Org.BouncyCastle.Math.EC; - using System; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using System; +namespace TSLib.Full +{ /// Represents the identity of a user. - /// To generate new identities use . - /// To improve the security level of this identity use . + /// To generate new identities use . + /// To improve the security level of this identity use . public class IdentityData { private string publicKeyString; @@ -23,12 +23,12 @@ public class IdentityData private string publicAndPrivateKeyString; /// The public key encoded in base64. - public string PublicKeyString => publicKeyString ?? (publicKeyString = Ts3Crypt.ExportPublicKey(PublicKey)); + public string PublicKeyString => publicKeyString ?? (publicKeyString = TsCrypt.ExportPublicKey(PublicKey)); /// The private key encoded in base64. - public string PrivateKeyString => privateKeyString ?? (privateKeyString = Ts3Crypt.ExportPrivateKey(PrivateKey)); + public string PrivateKeyString => privateKeyString ?? (privateKeyString = TsCrypt.ExportPrivateKey(PrivateKey)); /// The public and private key encoded in base64. public string PublicAndPrivateKeyString => - publicAndPrivateKeyString ?? (publicAndPrivateKeyString = Ts3Crypt.ExportPublicAndPrivateKey(PublicKey, PrivateKey)); + publicAndPrivateKeyString ?? (publicAndPrivateKeyString = TsCrypt.ExportPublicAndPrivateKey(PublicKey, PrivateKey)); /// The public key represented as its cryptographic data structure. public ECPoint PublicKey { get; } /// The private key represented as its cryptographic data structure. @@ -39,21 +39,29 @@ public class IdentityData /// can be stored here to resume from when continuing to search. public ulong LastCheckedKeyOffset { get; set; } - private string clientUid; + private Uid? clientUid; /// The client uid, which can be used in teamspeak for various features. - public string ClientUid => clientUid ?? (clientUid = Ts3Crypt.GetUidFromPublicKey(PublicKeyString)); + public Uid ClientUid + { + get + { + if (clientUid == null) + clientUid = (Uid)TsCrypt.GetUidFromPublicKey(PublicKeyString); + return clientUid.Value; + } + } public IdentityData(BigInteger privateKey, ECPoint publicKey = null) { PrivateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey)); - PublicKey = publicKey ?? Ts3Crypt.RestorePublicFromPrivateKey(privateKey); + PublicKey = publicKey ?? TsCrypt.RestorePublicFromPrivateKey(privateKey); } public static bool IsUidValid(string uid) { if (uid == "anonymous" || uid == "serveradmin") return true; - var result = Ts3Crypt.Base64Decode(uid); + var result = TsCrypt.Base64Decode(uid); return result.Ok && result.Value.Length == 20; } } diff --git a/TS3Client/Full/License.cs b/TSLib/Full/License.cs similarity index 90% rename from TS3Client/Full/License.cs rename to TSLib/Full/License.cs index e383e6a3..e6fa5072 100644 --- a/TS3Client/Full/License.cs +++ b/TSLib/Full/License.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full -{ - using Chaos.NaCl.Ed25519Ref10; - using Helper; - using System; - using System.Buffers.Binary; - using System.Collections.Generic; +using Chaos.NaCl.Ed25519Ref10; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using TSLib.Helper; +namespace TSLib.Full +{ public class Licenses { public static readonly byte[] LicenseRootKey = @@ -113,15 +113,15 @@ public abstract class LicenseBlock return $"Invalid license block type {data[33]}"; } - block.NotValidBefore = Util.UnixTimeStart.AddSeconds(BinaryPrimitives.ReadUInt32BigEndian(data.Slice(34)) + 0x50e22700uL); - block.NotValidAfter = Util.UnixTimeStart.AddSeconds(BinaryPrimitives.ReadUInt32BigEndian(data.Slice(38)) + 0x50e22700uL); + block.NotValidBefore = Tools.UnixTimeStart.AddSeconds(BinaryPrimitives.ReadUInt32BigEndian(data.Slice(34)) + 0x50e22700uL); + block.NotValidAfter = Tools.UnixTimeStart.AddSeconds(BinaryPrimitives.ReadUInt32BigEndian(data.Slice(38)) + 0x50e22700uL); if (block.NotValidAfter < block.NotValidBefore) return "License times are invalid"; block.Key = data.Slice(1, 32).ToArray(); var allLen = MinBlockLen + read; - var hash = Ts3Crypt.Hash512It(data.Slice(1, allLen - 1).ToArray()); + var hash = TsCrypt.Hash512It(data.Slice(1, allLen - 1).ToArray()); block.Hash = hash.AsSpan(0, 32).ToArray(); return (block, allLen); diff --git a/TS3Client/Full/NetworkStats.cs b/TSLib/Full/NetworkStats.cs similarity index 94% rename from TS3Client/Full/NetworkStats.cs rename to TSLib/Full/NetworkStats.cs index 98cbb9eb..23be0b04 100644 --- a/TS3Client/Full/NetworkStats.cs +++ b/TSLib/Full/NetworkStats.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full -{ - using Commands; - using Helper; - using System; - using System.Collections.Generic; - using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using TSLib.Commands; +using TSLib.Helper; +namespace TSLib.Full +{ // TODO include udp/ip header size to get correct values /// Provides connection stats by logging packets. public sealed class NetworkStats @@ -38,7 +38,7 @@ internal void LogOutPacket(ref Packet packet) lock (queueLock) { DropOver(outBytesTime, TimeMinute); - outBytesTime.Enqueue(new PacketData((ushort)packet.Raw.Length, Util.Now, kind)); + outBytesTime.Enqueue(new PacketData((ushort)packet.Raw.Length, Tools.Now, kind)); } } @@ -50,7 +50,7 @@ internal void LogInPacket(ref Packet packet) lock (queueLock) { DropOver(inBytesTime, TimeMinute); - inBytesTime.Enqueue(new PacketData((ushort)packet.Raw.Length, Util.Now, kind)); + inBytesTime.Enqueue(new PacketData((ushort)packet.Raw.Length, Tools.Now, kind)); } } @@ -92,7 +92,7 @@ private static PacketKind TypeToKind(PacketType type) private static long[] GetWithin(Queue queue, TimeSpan time) { - var now = Util.Now; + var now = Tools.Now; var bandwidth = new long[3]; foreach (var pack in queue.Reverse()) if (now - pack.SendPoint <= time) @@ -106,12 +106,12 @@ private static long[] GetWithin(Queue queue, TimeSpan time) private static void DropOver(Queue queue, TimeSpan time) { - var now = Util.Now; + var now = Tools.Now; while (queue.Count > 0 && now - queue.Peek().SendPoint > time) queue.Dequeue(); } - public Ts3Command GenerateStatusAnswer() + public TsCommand GenerateStatusAnswer() { long[] lastSecondIn; long[] lastSecondOut; @@ -136,7 +136,7 @@ public Ts3Command GenerateStatusAnswer() } } - return new Ts3Command("setconnectioninfo") { + return new TsCommand("setconnectioninfo") { { "connection_ping", Math.Round(lastPing, 0) }, { "connection_ping_deviation", deviationPing }, { "connection_packets_sent_speech", outPackets[(int)PacketKind.Speech] }, diff --git a/TS3Client/Full/Packet.cs b/TSLib/Full/Packet.cs similarity index 90% rename from TS3Client/Full/Packet.cs rename to TSLib/Full/Packet.cs index dbccf01f..5d270849 100644 --- a/TS3Client/Full/Packet.cs +++ b/TSLib/Full/Packet.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full -{ - using Helper; - using System; - using System.Buffers.Binary; - using System.Runtime.CompilerServices; +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using TSLib.Helper; +namespace TSLib.Full +{ internal struct Packet { public static bool FromServer { get; } = typeof(TDir) == typeof(S2C); @@ -79,7 +79,7 @@ public bool UnencryptedFlag public Packet(ReadOnlySpan data, PacketType packetType, ushort packetId, uint generationId) : this() { - Raw = new byte[data.Length + HeaderLength + Ts3Crypt.MacLen]; + Raw = new byte[data.Length + HeaderLength + TsCrypt.MacLen]; Header = new byte[HeaderLength]; Data = data.ToArray(); PacketType = packetType; @@ -90,7 +90,7 @@ public Packet(ReadOnlySpan data, PacketType packetType, ushort packetId, u [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Packet? FromRaw(ReadOnlySpan raw) { - if (raw.Length < HeaderLength + Ts3Crypt.MacLen) + if (raw.Length < HeaderLength + TsCrypt.MacLen) return null; var packet = new Packet { @@ -143,15 +143,15 @@ public void FromHeader() var rawSpan = Raw.AsSpan(); if (typeof(TDir) == typeof(S2C)) { - PacketId = BinaryPrimitives.ReadUInt16BigEndian(rawSpan.Slice(Ts3Crypt.MacLen)); - PacketTypeFlagged = Raw[Ts3Crypt.MacLen + 2]; + PacketId = BinaryPrimitives.ReadUInt16BigEndian(rawSpan.Slice(TsCrypt.MacLen)); + PacketTypeFlagged = Raw[TsCrypt.MacLen + 2]; } else if (typeof(TDir) == typeof(C2S)) { var ext = new C2S(); - PacketId = BinaryPrimitives.ReadUInt16BigEndian(rawSpan.Slice(Ts3Crypt.MacLen)); - ext.ClientId = BinaryPrimitives.ReadUInt16BigEndian(rawSpan.Slice(Ts3Crypt.MacLen + 2)); - PacketTypeFlagged = Raw[Ts3Crypt.MacLen + 4]; + PacketId = BinaryPrimitives.ReadUInt16BigEndian(rawSpan.Slice(TsCrypt.MacLen)); + ext.ClientId = BinaryPrimitives.ReadUInt16BigEndian(rawSpan.Slice(TsCrypt.MacLen + 2)); + PacketTypeFlagged = Raw[TsCrypt.MacLen + 4]; HeaderExt = (TDir)(object)ext; } else @@ -170,7 +170,7 @@ internal class ResendPacket public ResendPacket(Packet packet) { Packet = packet; - var now = Util.Now; + var now = Tools.Now; FirstSendTime = now; LastSendTime = now; } diff --git a/TS3Client/Full/PacketHandler.cs b/TSLib/Full/PacketHandler.cs similarity index 92% rename from TS3Client/Full/PacketHandler.cs rename to TSLib/Full/PacketHandler.cs index 21cdfd82..cc8e8aa6 100644 --- a/TS3Client/Full/PacketHandler.cs +++ b/TSLib/Full/PacketHandler.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,22 +7,22 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full +using NLog; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using TSLib.Helper; +using static TSLib.Full.PacketHandlerConst; + +namespace TSLib.Full { - using Helper; - using NLog; - using System; - using System.Buffers.Binary; - using System.Collections.Generic; - using System.Diagnostics; - using System.Net; - using System.Net.Sockets; - using System.Threading; - using static PacketHandlerConst; - internal sealed class PacketHandler { - private static readonly int OutHeaderSize = Ts3Crypt.MacLen + Packet.HeaderLength; + private static readonly int OutHeaderSize = TsCrypt.MacLen + Packet.HeaderLength; private static readonly int MaxOutContentSize = MaxOutPacketSize - OutHeaderSize; // Timout calculations @@ -51,7 +51,7 @@ internal sealed class PacketHandler private readonly RingQueue> receiveQueueCommandLow; // ==== private readonly object sendLoopLock = new object(); - private readonly Ts3Crypt ts3Crypt; + private readonly TsCrypt tsCrypt; private Socket socket; private Timer resendTimer; private DateTime pingCheck; @@ -60,14 +60,14 @@ internal sealed class PacketHandler public NetworkStats NetworkStats { get; } - public ushort ClientId { get; set; } + public ClientId ClientId { get; set; } private IPEndPoint remoteAddress; private int closed; // bool public PacketEvent PacketEvent; - public Action StopEvent; + public Action StopEvent; - public PacketHandler(Ts3Crypt ts3Crypt, Id id) + public PacketHandler(TsCrypt ts3Crypt, Id id) { receiveQueueCommand = new RingQueue>(ReceivePacketWindowSize, ushort.MaxValue + 1); receiveQueueCommandLow = new RingQueue>(ReceivePacketWindowSize, ushort.MaxValue + 1); @@ -76,9 +76,9 @@ public PacketHandler(Ts3Crypt ts3Crypt, Id id) NetworkStats = new NetworkStats(); - packetCounter = new ushort[Ts3Crypt.PacketTypeKinds]; - generationCounter = new uint[Ts3Crypt.PacketTypeKinds]; - this.ts3Crypt = ts3Crypt; + packetCounter = new ushort[TsCrypt.PacketTypeKinds]; + generationCounter = new uint[TsCrypt.PacketTypeKinds]; + this.tsCrypt = ts3Crypt; this.id = id; } @@ -92,7 +92,7 @@ public void Connect(IPEndPoint address) // it because the packed-ids the server expects are fixed. IncPacketCounter(PacketType.Command); // Send the actual new init packet. - AddOutgoingPacket(ts3Crypt.ProcessInit1(null).Value, PacketType.Init1); + AddOutgoingPacket(tsCrypt.ProcessInit1(null).Value, PacketType.Init1); } public void Listen(IPEndPoint address) @@ -113,7 +113,7 @@ private void Initialize(IPEndPoint address, bool connect) { lock (sendLoopLock) { - ClientId = 0; + ClientId = default; closed = 0; smoothedRtt = MaxRetryInterval; smoothedRttVar = TimeSpan.Zero; @@ -156,16 +156,17 @@ private void Initialize(IPEndPoint address, bool connect) // TODO init socketevargs stuff } } - catch (SocketException ex) { throw new Ts3Exception("Could not connect", ex); } + catch (SocketException ex) { throw new TsException("Could not connect", ex); } pingCheckRunning = 0; - pingCheck = Util.Now; + pingCheck = Tools.Now; if (resendTimer == null) resendTimer = new Timer((_) => { using (MappedDiagnosticsContext.SetScoped("BotId", id)) ResendLoop(); }, null, ClockResolution, ClockResolution); } } - public void Stop(Reason closeReason = Reason.Timeout) + public void Stop() => Stop(null); + private void Stop(Reason? closeReason) { var wasClosed = Interlocked.Exchange(ref closed, 1); if (wasClosed != 0) @@ -253,7 +254,7 @@ private E SendOutgoingData(ReadOnlySpan data, PacketType packetTyp if (typeof(TOut) == typeof(C2S)) // TODO: XXX { var meta = (C2S)(object)packet.HeaderExt; - meta.ClientId = ClientId; + meta.ClientId = ClientId.Value; packet.HeaderExt = (TOut)(object)meta; } packet.PacketFlags |= flags; @@ -302,10 +303,10 @@ private E SendOutgoingData(ReadOnlySpan data, PacketType packetTyp LogRaw.Trace("[O] {0}", packet); break; - default: throw Util.UnhandledDefault(packet.PacketType); + default: throw Tools.UnhandledDefault(packet.PacketType); } - ts3Crypt.Encrypt(ref packet); + tsCrypt.Encrypt(ref packet); return SendRaw(ref packet); } @@ -345,7 +346,7 @@ private static void FetchPacketEvent(object selfObj, SocketAsyncEventArgs args) Log.Debug("Socket error: {@args}", args); if (args.SocketError == SocketError.ConnectionReset) { - self.Stop(); + self.Stop(Reason.SocketError); } } @@ -377,7 +378,7 @@ private void FetchPackets(Span buffer) LogRaw.Trace("[I] Raw {0}", DebugUtil.DebugToHex(packet.Raw)); FindIncommingGenerationId(ref packet); - if (!ts3Crypt.Decrypt(ref packet)) + if (!tsCrypt.Decrypt(ref packet)) { LogRaw.Warn("Dropping not decryptable packet: {0}", DebugUtil.DebugToHex(packet.Raw)); return; @@ -423,7 +424,7 @@ private void FetchPackets(Span buffer) if (!LogRaw.IsDebugEnabled) LogRaw.Trace("[I] {0}", packet); passPacketToEvent = ReceiveInitAck(ref packet); break; - default: throw Util.UnhandledDefault(packet.PacketType); + default: throw Tools.UnhandledDefault(packet.PacketType); } if (passPacketToEvent) @@ -564,7 +565,7 @@ private bool ReceiveAck(ref Packet packet) { if (packetAckManager.TryGetValue(packetId, out var ackPacket)) { - UpdateRto(Util.Now - ackPacket.LastSendTime); + UpdateRto(Tools.Now - ackPacket.LastSendTime); packetAckManager.Remove(packetId); } } @@ -615,7 +616,7 @@ private bool ReceiveInitAck(ref Packet packet) if (initPacketCheck is null) return true; // optional: add random number check from init data - var forwardData = ts3Crypt.ProcessInit1(packet.Data); + var forwardData = tsCrypt.ProcessInit1(packet.Data); if (!forwardData.Ok) { LogRaw.Debug("Error init: {0}", forwardData.Error); @@ -642,7 +643,7 @@ private void UpdateRto(TimeSpan sampleRtt) else smoothedRtt = TimeSpan.FromTicks((long)((1 - AlphaSmooth) * smoothedRtt.Ticks + AlphaSmooth * sampleRtt.Ticks)); smoothedRttVar = TimeSpan.FromTicks((long)((1 - BetaSmooth) * smoothedRttVar.Ticks + BetaSmooth * Math.Abs(sampleRtt.Ticks - smoothedRtt.Ticks))); - currentRto = smoothedRtt + Util.Max(ClockResolution, TimeSpan.FromTicks(4 * smoothedRttVar.Ticks)); + currentRto = smoothedRtt + Tools.Max(ClockResolution, TimeSpan.FromTicks(4 * smoothedRttVar.Ticks)); LogRtt.Debug("RTT SRTT:{0} RTTVAR:{1} RTO:{2}", smoothedRtt, smoothedRttVar, currentRto); } @@ -672,14 +673,14 @@ private void ResendLoop() } if (close) { - Stop(); + Stop(Reason.Timeout); return; } - var now = Util.Now; + var now = Tools.Now; var nextTest = now - pingCheck - PingInterval; // we need to check if CryptoInitComplete because while false packet ids won't be incremented - if (nextTest > TimeSpan.Zero && ts3Crypt.CryptoInitComplete) + if (nextTest > TimeSpan.Zero && tsCrypt.CryptoInitComplete) { // Check that the last ping is more than PingInterval but not more than // 2*PingInterval away. This might happen for e.g. when the process was @@ -695,7 +696,7 @@ private void ResendLoop() if (elapsed > PacketTimeout) { LogTimeout.Debug("TIMEOUT: Got no ping packet response for {0}", elapsed); - Stop(); + Stop(Reason.Timeout); return; } } @@ -715,7 +716,7 @@ private bool ResendPackets(IEnumerable> packetList) private bool ResendPacket(ResendPacket packet) { - var now = Util.Now; + var now = Tools.Now; // Check if the packet timed out completely if (packet.FirstSendTime < now - PacketTimeout) { @@ -730,7 +731,7 @@ private bool ResendPacket(ResendPacket packet) currentRto += currentRto; if (currentRto > MaxRetryInterval) currentRto = MaxRetryInterval; - packet.LastSendTime = Util.Now; + packet.LastSendTime = Tools.Now; SendRaw(ref packet.Packet); } @@ -760,14 +761,14 @@ private E SendRaw(ref Packet packet) internal static class PacketHandlerConst { - public static readonly Logger Log = LogManager.GetLogger("TS3Client.PacketHandler"); - public static readonly Logger LogRtt = LogManager.GetLogger("TS3Client.PacketHandler.Rtt"); - public static readonly Logger LogRaw = LogManager.GetLogger("TS3Client.PacketHandler.Raw"); - public static readonly Logger LogRawVoice = LogManager.GetLogger("TS3Client.PacketHandler.Raw.Voice"); - public static readonly Logger LogTimeout = LogManager.GetLogger("TS3Client.PacketHandler.Timeout"); + public static readonly Logger Log = LogManager.GetLogger("TSLib.PacketHandler"); + public static readonly Logger LogRtt = LogManager.GetLogger("TSLib.PacketHandler.Rtt"); + public static readonly Logger LogRaw = LogManager.GetLogger("TSLib.PacketHandler.Raw"); + public static readonly Logger LogRawVoice = LogManager.GetLogger("TSLib.PacketHandler.Raw.Voice"); + public static readonly Logger LogTimeout = LogManager.GetLogger("TSLib.PacketHandler.Timeout"); /// Elapsed time since first send timestamp until the connection is considered lost. - public static readonly TimeSpan PacketTimeout = TimeSpan.FromSeconds(20); + public static readonly TimeSpan PacketTimeout = TimeSpan.FromSeconds(30); /// Smoothing factor for the SmoothedRtt. public const float AlphaSmooth = 0.125f; /// Smoothing factor for the SmoothedRttDev. diff --git a/TS3Client/Full/PacketType.cs b/TSLib/Full/PacketType.cs similarity index 81% rename from TS3Client/Full/PacketType.cs rename to TSLib/Full/PacketType.cs index e9dc5176..e221b5e8 100644 --- a/TS3Client/Full/PacketType.cs +++ b/TSLib/Full/PacketType.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full -{ - using System; +using System; +namespace TSLib.Full +{ public enum PacketType : byte { Voice = 0x0, diff --git a/TS3Client/Full/QuickerLz.cs b/TSLib/Full/QuickerLz.cs similarity index 90% rename from TS3Client/Full/QuickerLz.cs rename to TSLib/Full/QuickerLz.cs index a89471d1..a678a85a 100644 --- a/TS3Client/Full/QuickerLz.cs +++ b/TSLib/Full/QuickerLz.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,13 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full -{ - using System; - using System.Buffers.Binary; - using System.IO; - using System.Runtime.CompilerServices; +using System; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.CompilerServices; +namespace TSLib.Full +{ /// An alternative QuickLZ compression implementation for C#. public static class QuickerLz { @@ -105,7 +105,7 @@ public static Span Compress(ReadOnlySpan data, int level) } else { - Write24(dest, destPos, hash << 4 | (matchlen << 16)); + Write24(destSpan, destPos, hash << 4 | (matchlen << 16)); destPos += 3; } sourcePos += matchlen; @@ -262,16 +262,19 @@ private static void WriteHeader(Span dest, int srcLen, int level, int head } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Write24(byte[] outArr, int outOff, int value) + private static void Write24(Span outArr, int outOff, int value) { - outArr[outOff + 0] = unchecked((byte)(value >> 00)); - outArr[outOff + 1] = unchecked((byte)(value >> 08)); - outArr[outOff + 2] = unchecked((byte)(value >> 16)); + var sli3 = outArr.Slice(outOff, 3); + BinaryPrimitives.WriteUInt16LittleEndian(sli3, unchecked((ushort)value)); + sli3[2] = unchecked((byte)(value >> 16)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Read24(ReadOnlySpan intArr, int inOff) - => unchecked(intArr[inOff] | (intArr[inOff + 1] << 8) | (intArr[inOff + 2] << 16)); + { + var sli3 = intArr.Slice(inOff, 3); + return unchecked(BinaryPrimitives.ReadUInt16LittleEndian(sli3) | (sli3[2] << 16)); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Hash(int value) => ((value >> 12) ^ value) & 0xfff; @@ -279,11 +282,10 @@ private static int Read24(ReadOnlySpan intArr, int inOff) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool Is6Same(ReadOnlySpan arr) { - return arr[0] == arr[1] - && arr[1] == arr[2] - && arr[2] == arr[3] - && arr[3] == arr[4] - && arr[4] == arr[5]; + var sli6 = arr.Slice(0, 6); + var i0 = BinaryPrimitives.ReadUInt32LittleEndian(sli6); + var u1 = BinaryPrimitives.ReadUInt16LittleEndian(sli6.Slice(4)); + return i0 == i0 >> 8 && unchecked((ushort)i0) == u1; } /// Copy [start; start + length) bytes from `data` to the end of `data` diff --git a/TS3Client/Full/RingQueue.cs b/TSLib/Full/RingQueue.cs similarity index 97% rename from TS3Client/Full/RingQueue.cs rename to TSLib/Full/RingQueue.cs index 4f1f8be7..d57d710a 100644 --- a/TS3Client/Full/RingQueue.cs +++ b/TSLib/Full/RingQueue.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full -{ - using System; +using System; +namespace TSLib.Full +{ /// Provides a ring queue with packet offset and direct item access functionality. /// Item type public sealed class RingQueue diff --git a/TS3Client/Full/Ts3Crypt.cs b/TSLib/Full/TsCrypt.cs similarity index 93% rename from TS3Client/Full/Ts3Crypt.cs rename to TSLib/Full/TsCrypt.cs index d9071190..7ce64a26 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TSLib/Full/TsCrypt.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,30 +7,30 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full +using Chaos.NaCl.Ed25519Ref10; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using TSLib.Commands; +using TSLib.Helper; + +namespace TSLib.Full { - using Chaos.NaCl.Ed25519Ref10; - using Commands; - using Helper; - using Org.BouncyCastle.Asn1; - using Org.BouncyCastle.Asn1.X9; - using Org.BouncyCastle.Crypto; - using Org.BouncyCastle.Crypto.Engines; - using Org.BouncyCastle.Crypto.Generators; - using Org.BouncyCastle.Crypto.Modes; - using Org.BouncyCastle.Crypto.Parameters; - using Org.BouncyCastle.Math; - using Org.BouncyCastle.Math.EC; - using Org.BouncyCastle.Security; - using System; - using System.Buffers.Binary; - using System.Diagnostics; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - /// Provides all cryptographic functions needed for the low- and high level TeamSpeak protocol usage. - public sealed class Ts3Crypt + public sealed class TsCrypt { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private const string DummyKeyAndNonceString = "c:\\windows\\system\\firewall32.cpl"; @@ -53,7 +53,7 @@ public sealed class Ts3Crypt private readonly byte[] fakeSignature = new byte[MacLen]; private readonly (byte[] key, byte[] nonce, uint generation)?[] cachedKeyNonces = new (byte[], byte[], uint)?[PacketTypeKinds * 2]; - public Ts3Crypt() + public TsCrypt() { Reset(); } @@ -75,7 +75,7 @@ internal void Reset() /// /// The Teamspeak 3 key as it is stored by the normal client. /// A libtomcrypt public+private key export. (+KeyOffset). - /// A TS3Client's private-only key export. (+KeyOffset). + /// A TSLib private-only key export. (+KeyOffset). /// /// Keys with "(+KeyOffset)" should add the key offset for the security level in the separate parameter. /// @@ -85,15 +85,15 @@ internal void Reset() /// The identity information. public static R LoadIdentityDynamic(string key, ulong keyOffset = 0, ulong lastCheckedKeyOffset = 0) { - var ts3identity = DeobfuscateAndImportTs3Identity(key); - if (ts3identity.Ok) - return ts3identity.Value; + var tsIdentity = DeobfuscateAndImportTsIdentity(key); + if (tsIdentity.Ok) + return tsIdentity.Value; return LoadIdentity(key, keyOffset, lastCheckedKeyOffset); } /// This methods loads a secret identity. /// The key stored in base64, encoded like the libtomcrypt export method of a private key. - /// Or the TS3Client's shorted private-only key. + /// Or the TSLib's shorted private-only key. /// A number which determines the security level of an identity. /// The last brute forced number. Default 0: will take the current keyOffset. /// The identity information. @@ -207,9 +207,9 @@ internal static ECPoint RestorePublicFromPrivateKey(BigInteger privateKey) return curve.G.Multiply(privateKey).Normalize(); } - private static readonly byte[] Ts3IdentityObfuscationKey = Encoding.ASCII.GetBytes("b9dfaa7bee6ac57ac7b65f1094a1c155e747327bc2fe5d51c512023fe54a280201004e90ad1daaae1075d53b7d571c30e063b5a62a4a017bb394833aa0983e6e"); + private static readonly byte[] TsIdentityObfuscationKey = Encoding.ASCII.GetBytes("b9dfaa7bee6ac57ac7b65f1094a1c155e747327bc2fe5d51c512023fe54a280201004e90ad1daaae1075d53b7d571c30e063b5a62a4a017bb394833aa0983e6e"); - public static R DeobfuscateAndImportTs3Identity(string identity) + public static R DeobfuscateAndImportTsIdentity(string identity) { var match = IdentityRegex.Match(identity); if (!match.Success) @@ -230,7 +230,7 @@ public static R DeobfuscateAndImportTs3Identity(string ide var hash = Hash1It(identityArr, 20, nullIdx < 0 ? identityArr.Length - 20 : nullIdx); XorBinary(identityArr, hash, 20, identityArr); - XorBinary(identityArr, Ts3IdentityObfuscationKey, Math.Min(100, identityArr.Length), identityArr); + XorBinary(identityArr, TsIdentityObfuscationKey, Math.Min(100, identityArr.Length), identityArr); if (System.Buffers.Text.Base64.DecodeFromUtf8InPlace(identityArr, out var length) != System.Buffers.OperationStatus.Done) return "Invalid deobfuscated base64 string"; @@ -298,7 +298,7 @@ private E SetSharedSecret(ReadOnlySpan alpha, ReadOnlySpan b // prepares the ivstruct consisting of 2 random byte chains of 10/10 or 10/54 bytes which both sides agreed on ivStruct = new byte[10 + beta.Length]; - // applying hashes to get the required values for ts3 + // applying hashes to get the required values for ts XorBinary(sharedKey, alpha, alpha.Length, ivStruct); XorBinary(sharedKey.Slice(10), beta, beta.Length, ivStruct.AsSpan(10)); @@ -402,8 +402,8 @@ internal R ProcessInit1(byte[] data) sendData = new byte[versionLen + initTypeLen + 4 + 4 + 8]; BinaryPrimitives.WriteUInt32BigEndian(sendData.AsSpan(0), InitVersion); // initVersion sendData[versionLen] = 0x00; // initType - BinaryPrimitives.WriteUInt32BigEndian(sendData.AsSpan(versionLen + initTypeLen), Util.UnixNow); // 4byte timestamp - BinaryPrimitives.WriteInt32BigEndian(sendData.AsSpan(versionLen + initTypeLen + 4), Util.Random.Next()); // 4byte random + BinaryPrimitives.WriteUInt32BigEndian(sendData.AsSpan(versionLen + initTypeLen), Tools.UnixNow); // 4byte timestamp + BinaryPrimitives.WriteInt32BigEndian(sendData.AsSpan(versionLen + initTypeLen + 4), Tools.Random.Next()); // 4byte random return sendData; case 0: @@ -425,8 +425,8 @@ internal R ProcessInit1(byte[] data) return sendData; case 5: var errorNum = BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(initTypeLen)); - if (Enum.IsDefined(typeof(Ts3ErrorCode), errorNum)) - return $"Got Init1(1) error: {(Ts3ErrorCode)errorNum}"; + if (Enum.IsDefined(typeof(TsErrorCode), errorNum)) + return $"Got Init1(1) error: {(TsErrorCode)errorNum}"; return $"Got Init1(1) undefined error code: {errorNum}"; default: return packetInvalidLength; @@ -446,15 +446,15 @@ internal R ProcessInit1(byte[] data) if (data.Length != initTypeLen + 64 + 64 + 4 + 100) return packetInvalidLength; alphaTmp = new byte[10]; - Util.Random.NextBytes(alphaTmp); + Tools.Random.NextBytes(alphaTmp); var alpha = Convert.ToBase64String(alphaTmp); - string initAdd = Ts3Command.BuildToString("clientinitiv", + string initAdd = TsCommand.BuildToString("clientinitiv", new ICommandPart[] { new CommandParameter("alpha", alpha), new CommandParameter("omega", Identity.PublicKeyString), new CommandParameter("ot", 1), new CommandParameter("ip", string.Empty) }); - var textBytes = Util.Encoder.GetBytes(initAdd); + var textBytes = Tools.Utf8Encoder.GetBytes(initAdd); // Prepare solution int level = BinaryPrimitives.ReadInt32BigEndian(data.AsSpan(initTypeLen + 128)); @@ -549,11 +549,11 @@ internal void Encrypt(ref Packet packet) len = eaxCipher.ProcessBytes(packet.Data, 0, packet.Size, result, 0); len += eaxCipher.DoFinal(result, len); } - catch (Exception ex) { throw new Ts3Exception("Internal encryption error.", ex); } + catch (Exception ex) { throw new TsException("Internal encryption error.", ex); } } // result consists of [Data..., Mac...] - // to build the final TS3/libtomcrypt we need to copy it into another order + // to build the final TS/libtomcrypt we need to copy it into another order // len is Data.Length + Mac.Length // //packet.Raw = new byte[Packet.HeaderLength + len]; @@ -649,7 +649,7 @@ private static bool FakeDecrypt(ref Packet packet, byte[] mac) return true; } - /// TS3 uses a new key and nonce for each packet sent and received. This method generates and caches these. + /// TS uses a new key and nonce for each packet sent and received. This method generates and caches these. /// True if the packet is from server to client, false for client to server. /// The id of the packet, host order. /// Each time the packetId reaches 65535 the next packet will go on with 0 and the generationId will be increased by 1. @@ -739,7 +739,7 @@ public static string HashPassword(string password) { if (string.IsNullOrEmpty(password)) return string.Empty; - var bytes = Util.Encoder.GetBytes(password); + var bytes = Tools.Utf8Encoder.GetBytes(password); var hashed = Hash1It(bytes); return Convert.ToBase64String(hashed); } @@ -762,7 +762,7 @@ public static bool VerifySign(ECPoint publicKey, byte[] data, byte[] proof) return signer.VerifySignature(proof); } - private static readonly byte[] Ts3VersionSignPublicKey = Convert.FromBase64String("UrN1jX0dBE1vulTNLCoYwrVpfITyo+NBuq/twbf9hLw="); + private static readonly byte[] TsVersionSignPublicKey = Convert.FromBase64String("UrN1jX0dBE1vulTNLCoYwrVpfITyo+NBuq/twbf9hLw="); public static bool EdCheck(VersionSign sign) { @@ -770,7 +770,7 @@ public static bool EdCheck(VersionSign sign) var signArr = Base64Decode(sign.Sign); if (!signArr.Ok) return false; - return Chaos.NaCl.Ed25519.Verify(signArr.Value, ver, Ts3VersionSignPublicKey); + return Chaos.NaCl.Ed25519.Verify(signArr.Value, ver, TsVersionSignPublicKey); } public static void VersionSelfCheck() diff --git a/TS3Client/Full/Ts3FullClient.cs b/TSLib/Full/TsFullClient.cs similarity index 75% rename from TS3Client/Full/Ts3FullClient.cs rename to TSLib/Full/TsFullClient.cs index 319eccf4..87bfa32d 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TSLib/Full/TsFullClient.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,26 +7,25 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Full +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using TSLib.Audio; +using TSLib.Commands; +using TSLib.Full.Book; +using TSLib.Helper; +using TSLib.Messages; +using CmdR = System.E; + +namespace TSLib.Full { - using Audio; - using Commands; - using Helper; - using Messages; - using System; - using System.Buffers.Binary; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using ChannelIdT = System.UInt64; - using ClientIdT = System.UInt16; - using CmdR = System.E; - /// Creates a full TeamSpeak3 client with voice capabilities. - public sealed partial class Ts3FullClient : Ts3BaseFunctions, IAudioActiveProducer, IAudioPassiveConsumer + public sealed partial class TsFullClient : TsBaseFunctions, IAudioActiveProducer, IAudioPassiveConsumer { - private Ts3Crypt ts3Crypt; + private TsCrypt tsCrypt; private PacketHandler packetHandler; private readonly AsyncMessageProcessor msgProc; @@ -38,19 +37,19 @@ public sealed partial class Ts3FullClient : Ts3BaseFunctions, IAudioActiveProduc private IEventDispatcher dispatcher; public override ClientType ClientType => ClientType.Full; /// The client id given to this connection by the server. - public ushort ClientId => packetHandler.ClientId; + public ClientId ClientId => packetHandler.ClientId; /// The disonnect message when leaving. public string QuitMessage { get; set; } = "Disconnected"; /// The used to connect. public VersionSign VersionSign { get; private set; } /// The used to connect. - public IdentityData Identity => ts3Crypt.Identity; - private Ts3ClientStatus status; - public override bool Connected { get { lock (statusLock) return status == Ts3ClientStatus.Connected; } } - public override bool Connecting { get { lock (statusLock) return status == Ts3ClientStatus.Connecting; } } + public IdentityData Identity => tsCrypt.Identity; + private TsClientStatus status; + public override bool Connected { get { lock (statusLock) return status == TsClientStatus.Connected; } } + public override bool Connecting { get { lock (statusLock) return status == TsClientStatus.Connecting; } } protected override Deserializer Deserializer => msgProc.Deserializer; private ConnectionDataFull connectionDataFull; - public Book.Connection Book { get; set; } = new Book.Connection(); + public Connection Book { get; set; } = new Connection(); public override event EventHandler OnConnected; public override event EventHandler OnDisconnected; @@ -59,9 +58,9 @@ public sealed partial class Ts3FullClient : Ts3BaseFunctions, IAudioActiveProduc /// Creates a new client. A client can manage one connection to a server. /// The message processing method for incomming notifications. /// See for further information about each type. - public Ts3FullClient() + public TsFullClient() { - status = Ts3ClientStatus.Disconnected; + status = TsClientStatus.Disconnected; msgProc = new AsyncMessageProcessor(MessageHelper.GetToClientNotificationType); context = new ConnectionContext { WasExit = true }; } @@ -70,7 +69,7 @@ public Ts3FullClient() /// Set the connection information properties as needed. /// For further details about each setting see the respective property documentation in /// When some required values are not set or invalid. - /// When the connection could not be established. + /// When the connection could not be established. public override void Connect(ConnectionData conData) { if (!(conData is ConnectionDataFull conDataFull)) throw new ArgumentException($"Use the {nameof(ConnectionDataFull)} derivative to connect with the full client.", nameof(conData)); @@ -82,23 +81,23 @@ public override void Connect(ConnectionData conData) Disconnect(); if (!TsDnsResolver.TryResolve(conData.Address, out remoteAddress)) - throw new Ts3Exception("Could not read or resolve address."); + throw new TsException("Could not read or resolve address."); lock (statusLock) { returnCode = 0; - status = Ts3ClientStatus.Connecting; + status = TsClientStatus.Connecting; VersionSign = conDataFull.VersionSign; - ts3Crypt = new Ts3Crypt(); - ts3Crypt.Identity = conDataFull.Identity; + tsCrypt = new TsCrypt(); + tsCrypt.Identity = conDataFull.Identity; var ctx = new ConnectionContext { WasExit = false }; context = ctx; - packetHandler = new PacketHandler(ts3Crypt, conData.LogId); + packetHandler = new PacketHandler(tsCrypt, conData.LogId); packetHandler.PacketEvent = (ref Packet packet) => { PacketEvent(ctx, ref packet); }; - packetHandler.StopEvent = (closeReason) => { ctx.ExitReason = closeReason; DisconnectInternal(ctx, setStatus: Ts3ClientStatus.Disconnected); }; + packetHandler.StopEvent = (closeReason) => { ctx.ExitReason = ctx.ExitReason ?? closeReason; DisconnectInternal(ctx, setStatus: TsClientStatus.Disconnected); }; packetHandler.Connect(remoteAddress); dispatcher = new ExtraThreadEventDispatcher(); dispatcher.Init(InvokeEvent, conData.LogId); @@ -122,7 +121,7 @@ public override void Disconnect() } } - private void DisconnectInternal(ConnectionContext ctx, CommandError error = null, Ts3ClientStatus? setStatus = null) + private void DisconnectInternal(ConnectionContext ctx, CommandError error = null, TsClientStatus? setStatus = null) { bool triggerEventSafe = false; @@ -138,8 +137,8 @@ private void DisconnectInternal(ConnectionContext ctx, CommandError error = null switch (status) { - case Ts3ClientStatus.Connecting: - case Ts3ClientStatus.Disconnected: + case TsClientStatus.Connecting: + case TsClientStatus.Disconnected: ctx.WasExit = true; packetHandler.Stop(); msgProc.DropQueue(); @@ -147,14 +146,14 @@ private void DisconnectInternal(ConnectionContext ctx, CommandError error = null dispatcher = null; triggerEventSafe = true; break; - case Ts3ClientStatus.Disconnecting: + case TsClientStatus.Disconnecting: break; - case Ts3ClientStatus.Connected: + case TsClientStatus.Connected: ClientDisconnect(Reason.LeftServer, QuitMessage); - status = Ts3ClientStatus.Disconnecting; + status = TsClientStatus.Disconnecting; break; default: - throw Util.UnhandledDefault(status); + throw Tools.UnhandledDefault(status); } } @@ -173,7 +172,7 @@ private void PacketEvent(ConnectionContext ctx, ref Packet packet) { case PacketType.Command: case PacketType.CommandLow: - Log.ConditionalDebug("[I] {0}", Util.Encoder.GetString(packet.Data)); + Log.ConditionalDebug("[I] {0}", Tools.Utf8Encoder.GetString(packet.Data)); var result = msgProc.PushMessage(packet.Data); if (result.HasValue) dispatcher.Invoke(result.Value); @@ -195,11 +194,11 @@ private void PacketEvent(ConnectionContext ctx, ref Packet packet) if (packet.Data.Length == 5 && packet.Data[0] == 1) { var errorNum = BinaryPrimitives.ReadUInt32LittleEndian(packet.Data.AsSpan(1)); - if (Enum.IsDefined(typeof(Ts3ErrorCode), errorNum)) - Log.Info("Got init error: {0}", (Ts3ErrorCode)errorNum); + if (Enum.IsDefined(typeof(TsErrorCode), errorNum)) + Log.Info("Got init error: {0}", (TsErrorCode)errorNum); else Log.Warn("Got undefined init error: {0}", errorNum); - DisconnectInternal(ctx, setStatus: Ts3ClientStatus.Disconnected); + DisconnectInternal(ctx, setStatus: TsClientStatus.Disconnected); } break; } @@ -212,10 +211,10 @@ partial void ProcessEachInitIvExpand(InitIvExpand initIvExpand) { packetHandler.ReceivedFinalInitAck(); - var result = ts3Crypt.CryptoInit(initIvExpand.Alpha, initIvExpand.Beta, initIvExpand.Omega); + var result = tsCrypt.CryptoInit(initIvExpand.Alpha, initIvExpand.Beta, initIvExpand.Omega); if (!result) { - DisconnectInternal(context, Util.CustomError($"Failed to calculate shared secret: {result.Error}")); + DisconnectInternal(context, CommandError.Custom($"Failed to calculate shared secret: {result.Error}")); return; } @@ -226,21 +225,21 @@ partial void ProcessEachInitIvExpand2(InitIvExpand2 initIvExpand2) { packetHandler.ReceivedFinalInitAck(); - var (publicKey, privateKey) = Ts3Crypt.GenerateTemporaryKey(); + var (publicKey, privateKey) = TsCrypt.GenerateTemporaryKey(); var ekBase64 = Convert.ToBase64String(publicKey); var toSign = new byte[86]; Array.Copy(publicKey, 0, toSign, 0, 32); var beta = Convert.FromBase64String(initIvExpand2.Beta); Array.Copy(beta, 0, toSign, 32, 54); - var sign = Ts3Crypt.Sign(connectionDataFull.Identity.PrivateKey, toSign); + var sign = TsCrypt.Sign(connectionDataFull.Identity.PrivateKey, toSign); var proof = Convert.ToBase64String(sign); ClientEk(ekBase64, proof); - var result = ts3Crypt.CryptoInit2(initIvExpand2.License, initIvExpand2.Omega, initIvExpand2.Proof, initIvExpand2.Beta, privateKey); + var result = tsCrypt.CryptoInit2(initIvExpand2.License, initIvExpand2.Omega, initIvExpand2.Proof, initIvExpand2.Beta, privateKey); if (!result) { - DisconnectInternal(context, Util.CustomError($"Failed to calculate shared secret: {result.Error}")); + DisconnectInternal(context, CommandError.Custom($"Failed to calculate shared secret: {result.Error}")); return; } @@ -252,7 +251,7 @@ partial void ProcessEachInitServer(InitServer initServer) packetHandler.ClientId = initServer.ClientId; lock (statusLock) - status = Ts3ClientStatus.Connected; + status = TsClientStatus.Connected; OnConnected?.Invoke(this, EventArgs.Empty); } @@ -268,7 +267,7 @@ partial void ProcessEachCommandError(CommandError error) bool disconnect = false; lock (statusLock) { - if (status == Ts3ClientStatus.Connecting) + if (status == TsClientStatus.Connecting) { disconnect = true; skipError = true; @@ -276,7 +275,7 @@ partial void ProcessEachCommandError(CommandError error) } if (disconnect) - DisconnectInternal(context, error, Ts3ClientStatus.Disconnected); + DisconnectInternal(context, error, TsClientStatus.Disconnected); if (!skipError) OnErrorEvent?.Invoke(this, error); } @@ -285,8 +284,8 @@ partial void ProcessEachClientLeftView(ClientLeftView clientLeftView) { if (clientLeftView.ClientId == packetHandler.ClientId) { - context.ExitReason = Reason.LeftServer; - DisconnectInternal(context, setStatus: Ts3ClientStatus.Disconnected); + context.ExitReason = clientLeftView.Reason; + DisconnectInternal(context, setStatus: TsClientStatus.Disconnected); } } @@ -303,15 +302,15 @@ partial void ProcessEachClientConnectionInfoUpdateRequest(ClientConnectionInfoUp partial void ProcessPermList(PermList[] permList) { - var buildPermissions = new List(permList.Length + 1) { Ts3Permission.undefined }; + var buildPermissions = new List(permList.Length + 1) { TsPermission.undefined }; foreach (var perm in permList) { if (!string.IsNullOrEmpty(perm.PermissionName)) { - if (Enum.TryParse(perm.PermissionName, out var ts3perm)) - buildPermissions.Add(ts3perm); + if (Enum.TryParse(perm.PermissionName, out var tsPerm)) + buildPermissions.Add(tsPerm); else - buildPermissions.Add(Ts3Permission.undefined); + buildPermissions.Add(TsPermission.undefined); } } Deserializer.PermissionTransform = new TablePermissionTransform(buildPermissions.ToArray()); @@ -326,7 +325,7 @@ private CmdR DefaultClientInit() => ClientInit( connectionDataFull.DefaultChannelPassword.HashedPassword, connectionDataFull.ServerPassword.HashedPassword, string.Empty, string.Empty, string.Empty, - connectionDataFull.Identity.ClientUid, VersionSign); + connectionDataFull.Identity.ClientUid.Value, VersionSign); /// /// Sends a command to the server. Commands look exactly like query commands and mostly also behave identically. @@ -334,11 +333,11 @@ private CmdR DefaultClientInit() => ClientInit( /// /// The type to deserialize the response to. Use for unknow response data. /// The command to send. - /// NOTE: By default does the command expect an answer from the server. Set to false - /// if the client hangs after a special command ( will return a generic error instead). + /// NOTE: By default does the command expect an answer from the server. Set to false + /// if the client hangs after a special command ( will return a generic error instead). /// Returns R(OK) with an enumeration of the deserialized and split up in objects data. /// Or R(ERR) with the returned error if no response is expected. - public override R Send(Ts3Command com) + public override R Send(TsCommand com) { using (var wb = new WaitBlock(msgProc.Deserializer, false)) { @@ -357,13 +356,13 @@ public override R Send(Ts3Command com) /// NOTE: Do not use this method unless you are sure the ts3 command fits the criteria. /// /// The command to send. - public CmdR SendNoResponsed(Ts3Command command) - => Send(command.ExpectsResponse(false)); + public CmdR SendNoResponsed(TsCommand command) + => SendVoid(command.ExpectsResponse(false)); - public override R SendHybrid(Ts3Command com, NotificationType type) + public override R SendHybrid(TsCommand com, NotificationType type) => SendNotifyCommand(com, type).UnwrapNotification(); - public R SendNotifyCommand(Ts3Command com, params NotificationType[] dependsOn) + public R SendNotifyCommand(TsCommand com, params NotificationType[] dependsOn) { if (!com.ExpectResponse) throw new ArgumentException("A special command must take a response"); @@ -377,12 +376,12 @@ public R SendNotifyCommand(Ts3Command com, param } } - private E SendCommandBase(WaitBlock wb, Ts3Command com) + private E SendCommandBase(WaitBlock wb, TsCommand com) { lock (statusLock) { if (context.WasExit || (!Connected && com.ExpectResponse)) - return Util.TimeOutCommandError; + return CommandError.TimeOut; if (com.ExpectResponse) { @@ -394,13 +393,13 @@ private E SendCommandBase(WaitBlock wb, Ts3Command com) var message = com.ToString(); Log.Debug("[O] {0}", message); - byte[] data = Util.Encoder.GetBytes(message); + byte[] data = Tools.Utf8Encoder.GetBytes(message); packetHandler.AddOutgoingPacket(data, PacketType.Command); } return R.Ok; } - public async Task> SendCommandAsync(Ts3Command com) where T : IResponse, new() + public async Task> SendCommandAsync(TsCommand com) where T : IResponse, new() { using (var wb = new WaitBlock(msgProc.Deserializer, true)) { @@ -412,7 +411,7 @@ private E SendCommandBase(WaitBlock wb, Ts3Command com) else // This might not be the nicest way to return in this case // but we don't know what the response is, so this acceptable. - return Util.NoResultCommandError; + return CommandError.NoResult; } } @@ -460,23 +459,23 @@ public void Write(Span data, Meta meta) #region FULLCLIENT SPECIFIC COMMANDS public CmdR ChangeIsChannelCommander(bool isChannelCommander) - => Send(new Ts3Command("clientupdate") { + => SendVoid(new TsCommand("clientupdate") { { "client_is_channel_commander", isChannelCommander }, }); public CmdR RequestTalkPower(string message = null) - => Send(new Ts3Command("clientupdate") { + => SendVoid(new TsCommand("clientupdate") { { "client_talk_request", true }, { "client_talk_request_msg", message }, }); public CmdR CancelTalkPowerRequest() - => Send(new Ts3Command("clientupdate") { + => SendVoid(new TsCommand("clientupdate") { { "client_talk_request", false }, }); public CmdR ClientEk(string ek, string proof) - => SendNoResponsed(new Ts3Command("clientek") { + => SendNoResponsed(new TsCommand("clientek") { { "ek", ek }, { "proof", proof }, }); @@ -484,7 +483,7 @@ public CmdR ClientEk(string ek, string proof) public CmdR ClientInit(string nickname, bool inputHardware, bool outputHardware, string defaultChannel, string defaultChannelPassword, string serverPassword, string metaData, string nicknamePhonetic, string defaultToken, string hwid, VersionSign versionSign) - => SendNoResponsed(new Ts3Command("clientinit") { + => SendNoResponsed(new TsCommand("clientinit") { { "client_nickname", nickname }, { "client_version", versionSign.Name }, { "client_platform", versionSign.PlatformName }, @@ -502,19 +501,19 @@ public CmdR ClientInit(string nickname, bool inputHardware, bool outputHardware, }); public CmdR ClientDisconnect(Reason reason, string reasonMsg) - => SendNoResponsed(new Ts3Command("clientdisconnect") { + => SendNoResponsed(new TsCommand("clientdisconnect") { { "reasonid", (int)reason }, { "reasonmsg", reasonMsg } }); public CmdR ChannelSubscribeAll() - => Send(new Ts3Command("channelsubscribeall")); + => SendVoid(new TsCommand("channelsubscribeall")); public CmdR ChannelUnsubscribeAll() - => Send(new Ts3Command("channelunsubscribeall")); + => SendVoid(new TsCommand("channelunsubscribeall")); - public CmdR PokeClient(string message, ClientIdT clientId) - => SendNoResponsed(new Ts3Command("clientpoke") { + public CmdR PokeClient(string message, ushort clientId) + => SendNoResponsed(new TsCommand("clientpoke") { { "clid", clientId }, { "msg", message }, }); @@ -532,7 +531,7 @@ public void SendAudio(in ReadOnlySpan data, Codec codec) packetHandler.AddOutgoingPacket(tmpBuffer, PacketType.Voice); } - public void SendAudioWhisper(in ReadOnlySpan data, Codec codec, IReadOnlyList channelIds, IReadOnlyList clientIds) + public void SendAudioWhisper(in ReadOnlySpan data, Codec codec, IReadOnlyList channelIds, IReadOnlyList clientIds) { // [X,X,Y,N,M,(U,U,U,U,U,U,U,U)*,(T,T)*,DATA] // > X is a ushort in H2N order of an own audio packet counter @@ -548,9 +547,9 @@ public void SendAudioWhisper(in ReadOnlySpan data, Codec codec, IReadOnlyL tmpBuffer[3] = (byte)channelIds.Count; tmpBuffer[4] = (byte)clientIds.Count; for (int i = 0; i < channelIds.Count; i++) - BinaryPrimitives.WriteUInt64BigEndian(tmpBuffer.Slice(5 + (i * 8)), channelIds[i]); + BinaryPrimitives.WriteUInt64BigEndian(tmpBuffer.Slice(5 + (i * 8)), channelIds[i].Value); for (int i = 0; i < clientIds.Count; i++) - BinaryPrimitives.WriteUInt16BigEndian(tmpBuffer.Slice(5 + channelIds.Count * 8 + (i * 2)), clientIds[i]); + BinaryPrimitives.WriteUInt16BigEndian(tmpBuffer.Slice(5 + channelIds.Count * 8 + (i * 2)), clientIds[i].Value); data.CopyTo(tmpBuffer.Slice(offset)); packetHandler.AddOutgoingPacket(tmpBuffer, PacketType.VoiceWhisper); @@ -575,9 +574,9 @@ public void SendAudioGroupWhisper(in ReadOnlySpan data, Codec codec, Group packetHandler.AddOutgoingPacket(tmpBuffer, PacketType.VoiceWhisper, PacketFlags.Newprotocol); } - public R GetClientConnectionInfo(ClientIdT clientId) + public R GetClientConnectionInfo(ClientId clientId) { - var result = SendNotifyCommand(new Ts3Command("getconnectioninfo") { + var result = SendNotifyCommand(new TsCommand("getconnectioninfo") { { "clid", clientId } }, NotificationType.ClientConnectionInfo); if (!result.Ok) @@ -588,27 +587,42 @@ public R GetClientConnectionInfo(ClientIdT c .WrapSingle(); } - public R GetClientVariables(ClientIdT clientId) - => SendNotifyCommand(new Ts3Command("clientgetvariables") { + public R GetClientVariables(ushort clientId) + => SendNotifyCommand(new TsCommand("clientgetvariables") { { "clid", clientId } }, NotificationType.ClientUpdated).UnwrapNotification().WrapSingle(); public R GetServerVariables() - => SendNotifyCommand(new Ts3Command("servergetvariables"), + => SendNotifyCommand(new TsCommand("servergetvariables"), NotificationType.ServerUpdated).UnwrapNotification().WrapSingle(); public CmdR SendPluginCommand(string name, string data, PluginTargetMode targetmode) - => Send(new Ts3Command("plugincmd") { + => SendVoid(new TsCommand("plugincmd") { { "name", name }, { "data", data }, { "targetmode", (int)targetmode }, - }).OnlyError(); + }); // Splitted base commands + public override R ChannelCreate(string name, + string namePhonetic = null, string topic = null, string description = null, string password = null, + Codec? codec = null, int? codecQuality = null, int? codecLatencyFactor = null, bool? codecEncrypted = null, + int? maxClients = null, int? maxFamilyClients = null, bool? maxClientsUnlimited = null, + bool? maxFamilyClientsUnlimited = null, bool? maxFamilyClientsInherited = null, ChannelId? order = null, + ChannelId? parent = null, ChannelType? type = null, TimeSpan? deleteDelay = null, int? neededTalkPower = null) + => SendNotifyCommand(ChannelOp("channelcreate", null, name, namePhonetic, topic, description, + password, codec, codecQuality, codecLatencyFactor, codecEncrypted, + maxClients, maxFamilyClients, maxClientsUnlimited, maxFamilyClientsUnlimited, + maxFamilyClientsInherited, order, parent, type, deleteDelay, neededTalkPower), + NotificationType.ChannelCreated) + .UnwrapNotification() + .WrapSingle() + .WrapInterface(); + public override R ServerGroupAdd(string name, GroupType? type = null) { - var result = SendNotifyCommand(new Ts3Command("servergroupadd") { + var result = SendNotifyCommand(new TsCommand("servergroupadd") { { "name", name }, { "type", (int?)type } }, NotificationType.ServerGroupList); @@ -622,10 +636,10 @@ public override R ServerGroupAdd(string na .WrapSingle(); } - public override R FileTransferInitUpload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, + public override R FileTransferInitUpload(ChannelId channelId, string path, string channelPassword, ushort clientTransferId, long fileSize, bool overwrite, bool resume) { - var result = SendNotifyCommand(new Ts3Command("ftinitupload") { + var result = SendNotifyCommand(new TsCommand("ftinitupload") { { "cid", channelId }, { "name", path }, { "cpw", channelPassword }, @@ -647,10 +661,10 @@ public override R FileTransferInitUpload(ChannelIdT ch } } - public override R FileTransferInitDownload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, + public override R FileTransferInitDownload(ChannelId channelId, string path, string channelPassword, ushort clientTransferId, long seek) { - var result = SendNotifyCommand(new Ts3Command("ftinitdownload") { + var result = SendNotifyCommand(new TsCommand("ftinitdownload") { { "cid", channelId }, { "name", path }, { "cpw", channelPassword }, @@ -671,7 +685,7 @@ public override R FileTransferInitDownload(ChannelId #endregion - private enum Ts3ClientStatus + private enum TsClientStatus { Disconnected, Disconnecting, diff --git a/TS3Client/Generated/Ts3FullEvents.cs b/TSLib/Full/TsFullClient.gen.cs similarity index 99% rename from TS3Client/Generated/Ts3FullEvents.cs rename to TSLib/Full/TsFullClient.gen.cs index 5a1cc30c..9aa34c0d 100644 --- a/TS3Client/Generated/Ts3FullEvents.cs +++ b/TSLib/Full/TsFullClient.gen.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -23,13 +23,13 @@ -namespace TS3Client.Full -{ - using Helper; - using Messages; - using System; +using System; +using TSLib.Helper; +using TSLib.Messages; - public sealed partial class Ts3FullClient +namespace TSLib.Full +{ + public sealed partial class TsFullClient { public event NotifyEventHandler OnBanList; @@ -191,8 +191,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachBanList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachBanList(that); + ev?.Invoke(this, that); } break; } @@ -204,8 +204,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelChanged; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachChannelChanged(that); + ev?.Invoke(this, that); } break; } @@ -217,8 +217,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelClientPermList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachChannelClientPermList(that); + ev?.Invoke(this, that); } break; } @@ -230,9 +230,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelCreated; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachChannelCreated(that); book?.UpdateChannelCreated(that); + ProcessEachChannelCreated(that); + ev?.Invoke(this, that); } break; } @@ -244,9 +244,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelDeleted; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachChannelDeleted(that); book?.UpdateChannelDeleted(that); + ProcessEachChannelDeleted(that); + ev?.Invoke(this, that); } break; } @@ -258,8 +258,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelDescriptionChanged; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachChannelDescriptionChanged(that); + ev?.Invoke(this, that); } break; } @@ -271,9 +271,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelEdited; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachChannelEdited(that); book?.UpdateChannelEdited(that); + ProcessEachChannelEdited(that); + ev?.Invoke(this, that); } break; } @@ -285,8 +285,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelGroupClientList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachChannelGroupClientList(that); + ev?.Invoke(this, that); } break; } @@ -298,8 +298,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelGroupList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachChannelGroupList(that); + ev?.Invoke(this, that); } break; } @@ -311,8 +311,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelGroupPermList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachChannelGroupPermList(that); + ev?.Invoke(this, that); } break; } @@ -324,9 +324,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachChannelList(that); book?.UpdateChannelList(that); + ProcessEachChannelList(that); + ev?.Invoke(this, that); } break; } @@ -338,8 +338,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelListFinished; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachChannelListFinished(that); + ev?.Invoke(this, that); } break; } @@ -351,9 +351,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelMoved; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachChannelMoved(that); book?.UpdateChannelMoved(that); + ProcessEachChannelMoved(that); + ev?.Invoke(this, that); } break; } @@ -365,8 +365,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelPasswordChanged; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachChannelPasswordChanged(that); + ev?.Invoke(this, that); } break; } @@ -378,9 +378,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelPermissionHints; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachChannelPermissionHints(that); book?.UpdateChannelPermissionHints(that); + ProcessEachChannelPermissionHints(that); + ev?.Invoke(this, that); } break; } @@ -392,8 +392,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelPermList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachChannelPermList(that); + ev?.Invoke(this, that); } break; } @@ -405,9 +405,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelSubscribed; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachChannelSubscribed(that); book?.UpdateChannelSubscribed(that); + ProcessEachChannelSubscribed(that); + ev?.Invoke(this, that); } break; } @@ -419,9 +419,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachChannelUnsubscribed; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachChannelUnsubscribed(that); book?.UpdateChannelUnsubscribed(that); + ProcessEachChannelUnsubscribed(that); + ev?.Invoke(this, that); } break; } @@ -433,9 +433,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientChannelGroupChanged; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachClientChannelGroupChanged(that); book?.UpdateClientChannelGroupChanged(that); + ProcessEachClientChannelGroupChanged(that); + ev?.Invoke(this, that); } break; } @@ -447,8 +447,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientChatClosed; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientChatClosed(that); + ev?.Invoke(this, that); } break; } @@ -460,8 +460,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientChatComposing; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientChatComposing(that); + ev?.Invoke(this, that); } break; } @@ -473,9 +473,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientConnectionInfo; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachClientConnectionInfo(that); book?.UpdateClientConnectionInfo(that); + ProcessEachClientConnectionInfo(that); + ev?.Invoke(this, that); } break; } @@ -487,8 +487,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientConnectionInfoUpdateRequest; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientConnectionInfoUpdateRequest(that); + ev?.Invoke(this, that); } break; } @@ -500,8 +500,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientDbFind; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientDbFind(that); + ev?.Invoke(this, that); } break; } @@ -513,8 +513,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientDbIdFromUid; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientDbIdFromUid(that); + ev?.Invoke(this, that); } break; } @@ -526,8 +526,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientDbList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientDbList(that); + ev?.Invoke(this, that); } break; } @@ -539,9 +539,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientEnterView; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachClientEnterView(that); book?.UpdateClientEnterView(that); + ProcessEachClientEnterView(that); + ev?.Invoke(this, that); } break; } @@ -553,8 +553,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientIds; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientIds(that); + ev?.Invoke(this, that); } break; } @@ -566,9 +566,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientLeftView; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachClientLeftView(that); book?.UpdateClientLeftView(that); + ProcessEachClientLeftView(that); + ev?.Invoke(this, that); } break; } @@ -580,9 +580,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientMoved; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachClientMoved(that); book?.UpdateClientMoved(that); + ProcessEachClientMoved(that); + ev?.Invoke(this, that); } break; } @@ -594,8 +594,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientNameFromDbId; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientNameFromDbId(that); + ev?.Invoke(this, that); } break; } @@ -607,8 +607,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientNameFromUid; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientNameFromUid(that); + ev?.Invoke(this, that); } break; } @@ -620,8 +620,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientNeededPermissions; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientNeededPermissions(that); + ev?.Invoke(this, that); } break; } @@ -633,9 +633,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientPermissionHints; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachClientPermissionHints(that); book?.UpdateClientPermissionHints(that); + ProcessEachClientPermissionHints(that); + ev?.Invoke(this, that); } break; } @@ -647,8 +647,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientPermList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientPermList(that); + ev?.Invoke(this, that); } break; } @@ -660,8 +660,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientPoke; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientPoke(that); + ev?.Invoke(this, that); } break; } @@ -673,9 +673,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientServerGroupAdded; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachClientServerGroupAdded(that); book?.UpdateClientServerGroupAdded(that); + ProcessEachClientServerGroupAdded(that); + ev?.Invoke(this, that); } break; } @@ -687,9 +687,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientServerGroupRemoved; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachClientServerGroupRemoved(that); book?.UpdateClientServerGroupRemoved(that); + ProcessEachClientServerGroupRemoved(that); + ev?.Invoke(this, that); } break; } @@ -701,8 +701,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientSetServerQueryLogin; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientSetServerQueryLogin(that); + ev?.Invoke(this, that); } break; } @@ -714,8 +714,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientUidFromClid; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachClientUidFromClid(that); + ev?.Invoke(this, that); } break; } @@ -727,9 +727,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachClientUpdated; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachClientUpdated(that); book?.UpdateClientUpdated(that); + ProcessEachClientUpdated(that); + ev?.Invoke(this, that); } break; } @@ -741,8 +741,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachCommandError; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachCommandError(that); + ev?.Invoke(this, that); } break; } @@ -754,8 +754,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachComplainList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachComplainList(that); + ev?.Invoke(this, that); } break; } @@ -767,8 +767,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachFileDownload; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachFileDownload(that); + ev?.Invoke(this, that); } break; } @@ -780,8 +780,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachFileInfo; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachFileInfo(that); + ev?.Invoke(this, that); } break; } @@ -793,8 +793,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachFileList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachFileList(that); + ev?.Invoke(this, that); } break; } @@ -806,8 +806,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachFileListFinished; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachFileListFinished(that); + ev?.Invoke(this, that); } break; } @@ -819,8 +819,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachFileTransfer; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachFileTransfer(that); + ev?.Invoke(this, that); } break; } @@ -832,8 +832,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachFileTransferStatus; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachFileTransferStatus(that); + ev?.Invoke(this, that); } break; } @@ -845,8 +845,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachFileUpload; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachFileUpload(that); + ev?.Invoke(this, that); } break; } @@ -858,8 +858,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachInitIvExpand; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachInitIvExpand(that); + ev?.Invoke(this, that); } break; } @@ -871,8 +871,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachInitIvExpand2; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachInitIvExpand2(that); + ev?.Invoke(this, that); } break; } @@ -884,9 +884,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachInitServer; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachInitServer(that); book?.UpdateInitServer(that); + ProcessEachInitServer(that); + ev?.Invoke(this, that); } break; } @@ -898,8 +898,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachOfflineMessage; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachOfflineMessage(that); + ev?.Invoke(this, that); } break; } @@ -911,8 +911,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachOfflineMessageList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachOfflineMessageList(that); + ev?.Invoke(this, that); } break; } @@ -924,8 +924,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachPermFind; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachPermFind(that); + ev?.Invoke(this, that); } break; } @@ -937,8 +937,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachPermList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachPermList(that); + ev?.Invoke(this, that); } break; } @@ -950,8 +950,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachPermOverview; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachPermOverview(that); + ev?.Invoke(this, that); } break; } @@ -963,8 +963,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachPluginCommand; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachPluginCommand(that); + ev?.Invoke(this, that); } break; } @@ -976,8 +976,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachServerConnectionInfo; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachServerConnectionInfo(that); + ev?.Invoke(this, that); } break; } @@ -989,9 +989,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachServerEdited; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachServerEdited(that); book?.UpdateServerEdited(that); + ProcessEachServerEdited(that); + ev?.Invoke(this, that); } break; } @@ -1003,8 +1003,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachServerGroupClientList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachServerGroupClientList(that); + ev?.Invoke(this, that); } break; } @@ -1016,9 +1016,9 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachServerGroupList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEachServerGroupList(that); book?.UpdateServerGroupList(that); + ProcessEachServerGroupList(that); + ev?.Invoke(this, that); } break; } @@ -1030,8 +1030,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachServerGroupPermList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachServerGroupPermList(that); + ev?.Invoke(this, that); } break; } @@ -1043,8 +1043,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachServerGroupsByClientId; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachServerGroupsByClientId(that); + ev?.Invoke(this, that); } break; } @@ -1056,8 +1056,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachServerLog; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachServerLog(that); + ev?.Invoke(this, that); } break; } @@ -1069,8 +1069,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachServerTempPasswordList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachServerTempPasswordList(that); + ev?.Invoke(this, that); } break; } @@ -1082,8 +1082,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachServerUpdated; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachServerUpdated(that); + ev?.Invoke(this, that); } break; } @@ -1095,8 +1095,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachTextMessage; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachTextMessage(that); + ev?.Invoke(this, that); } break; } @@ -1108,8 +1108,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachTokenAdd; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachTokenAdd(that); + ev?.Invoke(this, that); } break; } @@ -1121,8 +1121,8 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachTokenList; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachTokenList(that); + ev?.Invoke(this, that); } break; } @@ -1134,15 +1134,15 @@ private void InvokeEvent(LazyNotification lazyNotification) var ev = OnEachTokenUsed; var book = Book; foreach(var that in ntfc) { - ev?.Invoke(this, that); ProcessEachTokenUsed(that); + ev?.Invoke(this, that); } break; } case NotificationType.Unknown: default: - throw Util.UnhandledDefault(lazyNotification.NotifyType); + throw Tools.UnhandledDefault(lazyNotification.NotifyType); } } diff --git a/TS3Client/Generated/Ts3FullEvents.tt b/TSLib/Full/TsFullClient.gen.tt similarity index 80% rename from TS3Client/Generated/Ts3FullEvents.tt rename to TSLib/Full/TsFullClient.gen.tt index 3c2207d0..f901bb85 100644 --- a/TS3Client/Generated/Ts3FullEvents.tt +++ b/TSLib/Full/TsFullClient.gen.tt @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -9,19 +9,19 @@ // <#@ template debug="true" hostSpecific="true" language="C#" #> -<#@ include file="M2BParser.ttinclude" once="true" #> -<#@ include file="MessageParser.ttinclude" once="true" #> -<#@ include file="BookParser.ttinclude" once="true" #> +<#@ include file="../Generated/M2BParser.ttinclude" once="true" #> +<#@ include file="../Generated/MessageParser.ttinclude" once="true" #> +<#@ include file="../Generated/BookParser.ttinclude" once="true" #> <#@ output extension=".cs" #> <#@ import namespace="System.Collections.Generic" #> -namespace TS3Client.Full -{ - using Helper; - using Messages; - using System; +using System; +using TSLib.Helper; +using TSLib.Messages; - public sealed partial class Ts3FullClient +namespace TSLib.Full +{ + public sealed partial class TsFullClient { <# var genbook = BookDeclarations.Parse(Host.ResolvePath("../Declarations/Book.toml")); @@ -66,13 +66,13 @@ namespace TS3Client.Full On<#= ntfy.name #>?.Invoke(this, ntfc); var ev = OnEach<#= ntfy.name #>; var book = Book; - foreach(var that in ntfc) { - ev?.Invoke(this, that); - ProcessEach<#= ntfy.name #>(that);<# + foreach(var that in ntfc) {<# var bookitem = genm2b.rule.FirstOrDefault(x => x.from == ntfy.name); if(bookitem != null) { #> book?.Update<#= ntfy.name #>(that);<# } #> + ProcessEach<#= ntfy.name #>(that); + ev?.Invoke(this, that); } break; } @@ -81,7 +81,7 @@ namespace TS3Client.Full #> case NotificationType.Unknown: default: - throw Util.UnhandledDefault(lazyNotification.NotifyType); + throw Tools.UnhandledDefault(lazyNotification.NotifyType); } } diff --git a/TS3Client/Generated/Book.cs b/TSLib/Generated/Book.cs similarity index 92% rename from TS3Client/Generated/Book.cs rename to TSLib/Generated/Book.cs index 7722f17f..4a037dd7 100644 --- a/TS3Client/Generated/Book.cs +++ b/TSLib/Generated/Book.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -19,41 +19,37 @@ -namespace TS3Client.Full.Book -{ - using System; - using System.Collections.Generic; - - #pragma warning disable CS8019 // Ignore unused imports - using i8 = System.SByte; - using u8 = System.Byte; - using i16 = System.Int16; - using u16 = System.UInt16; - using i32 = System.Int32; - using u32 = System.UInt32; - using i64 = System.Int64; - using u64 = System.UInt64; - using f32 = System.Single; - using f64 = System.Double; - using str = System.String; - - using DateTime = System.DateTime; - using Duration = System.TimeSpan; - using DurationSeconds = System.TimeSpan; - using DurationMilliseconds = System.TimeSpan; - using SocketAddr = System.String; - using IpAddr = System.String; - - using Uid = System.String; - using ClientDbId = System.UInt64; - using ClientId = System.UInt16; - using ChannelId = System.UInt64; - using ServerGroupId = System.UInt64; - using ChannelGroupId = System.UInt64; - using IconHash = System.Int32; - using ConnectionId = System.UInt32; +using System.Collections.Generic; + +#pragma warning disable CS8019 // Ignore unused imports +using i8 = System.SByte; +using u8 = System.Byte; +using i16 = System.Int16; +using u16 = System.UInt16; +using i32 = System.Int32; +using u32 = System.UInt32; +using i64 = System.Int64; +using u64 = System.UInt64; +using f32 = System.Single; +using f64 = System.Double; +using str = System.String; + +using DateTime = System.DateTime; +using Duration = System.TimeSpan; +using DurationSeconds = System.TimeSpan; +using DurationMilliseconds = System.TimeSpan; +using SocketAddr = System.String; +using IpAddr = System.String; +using Ts3ErrorCode = TSLib.TsErrorCode; +using Ts3Permission = TSLib.TsPermission; + +using IconHash = System.Int32; +using ConnectionId = System.UInt32; #pragma warning restore CS8019 +namespace TSLib.Full.Book +{ + public sealed partial class ServerGroup { public ServerGroup() diff --git a/TS3Client/Generated/Book.tt b/TSLib/Generated/Book.tt similarity index 89% rename from TS3Client/Generated/Book.tt rename to TSLib/Generated/Book.tt index 82539420..7dfe2d91 100644 --- a/TS3Client/Generated/Book.tt +++ b/TSLib/Generated/Book.tt @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -16,12 +16,12 @@ <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> -namespace TS3Client.Full.Book -{ - using System; - using System.Collections.Generic; +using System.Collections.Generic; + +<#= ConversionSet #> - <#= ConversionSet #> +namespace TSLib.Full.Book +{ <# var gen = BookDeclarations.Parse(Host.ResolvePath("../Declarations/Book.toml")); diff --git a/TS3Client/Generated/BookParser.ttinclude b/TSLib/Generated/BookParser.ttinclude similarity index 100% rename from TS3Client/Generated/BookParser.ttinclude rename to TSLib/Generated/BookParser.ttinclude diff --git a/TS3Client/Generated/ErrorParser.ttinclude b/TSLib/Generated/ErrorParser.ttinclude similarity index 100% rename from TS3Client/Generated/ErrorParser.ttinclude rename to TSLib/Generated/ErrorParser.ttinclude diff --git a/TS3Client/Generated/M2B.cs b/TSLib/Generated/M2B.cs similarity index 89% rename from TS3Client/Generated/M2B.cs rename to TSLib/Generated/M2B.cs index 25a75f42..76264ebe 100644 --- a/TS3Client/Generated/M2B.cs +++ b/TSLib/Generated/M2B.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -26,41 +26,36 @@ +using TSLib.Messages; -namespace TS3Client.Full.Book -{ - using Messages; - - #pragma warning disable CS8019 // Ignore unused imports - using i8 = System.SByte; - using u8 = System.Byte; - using i16 = System.Int16; - using u16 = System.UInt16; - using i32 = System.Int32; - using u32 = System.UInt32; - using i64 = System.Int64; - using u64 = System.UInt64; - using f32 = System.Single; - using f64 = System.Double; - using str = System.String; - - using DateTime = System.DateTime; - using Duration = System.TimeSpan; - using DurationSeconds = System.TimeSpan; - using DurationMilliseconds = System.TimeSpan; - using SocketAddr = System.String; - using IpAddr = System.String; - - using Uid = System.String; - using ClientDbId = System.UInt64; - using ClientId = System.UInt16; - using ChannelId = System.UInt64; - using ServerGroupId = System.UInt64; - using ChannelGroupId = System.UInt64; - using IconHash = System.Int32; - using ConnectionId = System.UInt32; +#pragma warning disable CS8019 // Ignore unused imports +using i8 = System.SByte; +using u8 = System.Byte; +using i16 = System.Int16; +using u16 = System.UInt16; +using i32 = System.Int32; +using u32 = System.UInt32; +using i64 = System.Int64; +using u64 = System.UInt64; +using f32 = System.Single; +using f64 = System.Double; +using str = System.String; + +using DateTime = System.DateTime; +using Duration = System.TimeSpan; +using DurationSeconds = System.TimeSpan; +using DurationMilliseconds = System.TimeSpan; +using SocketAddr = System.String; +using IpAddr = System.String; +using Ts3ErrorCode = TSLib.TsErrorCode; +using Ts3Permission = TSLib.TsPermission; + +using IconHash = System.Int32; +using ConnectionId = System.UInt32; #pragma warning restore CS8019 +namespace TSLib.Full.Book +{ public partial class Connection { #pragma warning disable IDE0017, CS0472 // Ignore "Object initialization can be simplified", "Something with == and null..." @@ -97,8 +92,10 @@ public void UpdateInitServer(InitServer msg) { var tmpv = msg.TempChannelDefaultDeleteDelay; if (tmpv != null) obj.TempChannelDefaultDeleteDelay = (Duration)tmpv; } SetServer(obj); + PostInitServer(msg); } + partial void PostInitServer(InitServer msg); public void UpdateChannelCreated(ChannelCreated msg) { @@ -130,15 +127,19 @@ public void UpdateChannelCreated(ChannelCreated msg) { var tmpv = msg.PhoneticName; if (tmpv != null) obj.PhoneticName = (str)tmpv; } SetChannel(obj, msg.ChannelId); + PostChannelCreated(msg); } + partial void PostChannelCreated(ChannelCreated msg); public void UpdateChannelDeleted(ChannelDeleted msg) { RemoveChannel(msg.ChannelId); + PostChannelDeleted(msg); } + partial void PostChannelDeleted(ChannelDeleted msg); public void UpdateChannelEdited(ChannelEdited msg) { @@ -167,8 +168,10 @@ public void UpdateChannelEdited(ChannelEdited msg) { var tmpv = msg.DeleteDelay; if (tmpv != null) obj.DeleteDelay = (Duration)tmpv; } { var tmpv = msg.PhoneticName; if (tmpv != null) obj.PhoneticName = (str)tmpv; } + PostChannelEdited(msg); } + partial void PostChannelEdited(ChannelEdited msg); public void UpdateChannelList(ChannelList msg) { @@ -200,8 +203,10 @@ public void UpdateChannelList(ChannelList msg) { var tmpv = msg.IsPrivate; if (tmpv != null) obj.IsPrivate = (bool)tmpv; } SetChannel(obj, msg.ChannelId); + PostChannelList(msg); } + partial void PostChannelList(ChannelList msg); public void UpdateChannelMoved(ChannelMoved msg) { @@ -213,8 +218,10 @@ public void UpdateChannelMoved(ChannelMoved msg) { var tmpv = msg.ParentId; if (tmpv != null) obj.Parent = (ChannelId)tmpv; } { var tmpv = ChannelOrderCmFun(msg); if (tmpv != null) obj.Order = (ChannelId)tmpv; } + PostChannelMoved(msg); } + partial void PostChannelMoved(ChannelMoved msg); public void UpdateChannelSubscribed(ChannelSubscribed msg) { @@ -225,8 +232,10 @@ public void UpdateChannelSubscribed(ChannelSubscribed msg) } { var tmpv = ChannelSubscribeFun(msg); if (tmpv != null) obj.Subscribed = (bool)tmpv; } + PostChannelSubscribed(msg); } + partial void PostChannelSubscribed(ChannelSubscribed msg); public void UpdateChannelUnsubscribed(ChannelUnsubscribed msg) { @@ -237,8 +246,10 @@ public void UpdateChannelUnsubscribed(ChannelUnsubscribed msg) } { var tmpv = ChannelUnsubscribeFun(msg); if (tmpv != null) obj.Subscribed = (bool)tmpv; } + PostChannelUnsubscribed(msg); } + partial void PostChannelUnsubscribed(ChannelUnsubscribed msg); public void UpdateClientChannelGroupChanged(ClientChannelGroupChanged msg) { @@ -249,8 +260,10 @@ public void UpdateClientChannelGroupChanged(ClientChannelGroupChanged msg) } { var tmpv = msg.ChannelGroup; if (tmpv != null) obj.ChannelGroup = (ChannelGroupId)tmpv; } + PostClientChannelGroupChanged(msg); } + partial void PostClientChannelGroupChanged(ClientChannelGroupChanged msg); public void UpdateClientEnterView(ClientEnterView msg) { @@ -289,15 +302,19 @@ public void UpdateClientEnterView(ClientEnterView msg) { var tmpv = msg.Badges; if (tmpv != null) obj.Badges = (str)tmpv; } SetClient(obj, msg.ClientId); + PostClientEnterView(msg); } + partial void PostClientEnterView(ClientEnterView msg); public void UpdateClientLeftView(ClientLeftView msg) { RemoveClient(msg.ClientId); + PostClientLeftView(msg); } + partial void PostClientLeftView(ClientLeftView msg); public void UpdateClientMoved(ClientMoved msg) { @@ -308,8 +325,10 @@ public void UpdateClientMoved(ClientMoved msg) } { var tmpv = msg.TargetChannelId; if (tmpv != null) obj.Channel = (ChannelId)tmpv; } + PostClientMoved(msg); } + partial void PostClientMoved(ClientMoved msg); public void UpdateClientConnectionInfo(ClientConnectionInfo msg) { @@ -355,8 +374,10 @@ public void UpdateClientConnectionInfo(ClientConnectionInfo msg) { var tmpv = msg.IdleTime; if (tmpv != null) obj.IdleTime = (Duration)tmpv; } SetConnectionClientData(obj, msg.ClientId); + PostClientConnectionInfo(msg); } + partial void PostClientConnectionInfo(ClientConnectionInfo msg); public void UpdateClientServerGroupAdded(ClientServerGroupAdded msg) { @@ -367,8 +388,10 @@ public void UpdateClientServerGroupAdded(ClientServerGroupAdded msg) } obj.ServerGroups.Add(msg.ServerGroupId); + PostClientServerGroupAdded(msg); } + partial void PostClientServerGroupAdded(ClientServerGroupAdded msg); public void UpdateClientServerGroupRemoved(ClientServerGroupRemoved msg) { @@ -379,8 +402,10 @@ public void UpdateClientServerGroupRemoved(ClientServerGroupRemoved msg) } obj.ServerGroups.Remove(msg.ServerGroupId); + PostClientServerGroupRemoved(msg); } + partial void PostClientServerGroupRemoved(ClientServerGroupRemoved msg); public void UpdateClientUpdated(ClientUpdated msg) { @@ -395,6 +420,7 @@ public void UpdateClientUpdated(ClientUpdated msg) { var tmpv = msg.UnreadMessages; if (tmpv != null) obj.UnreadMessages = (u32)tmpv; } { var tmpv = msg.InputMuted; if (tmpv != null) obj.InputMuted = (bool)tmpv; } { var tmpv = msg.InputHardwareEnabled; if (tmpv != null) obj.InputHardwareEnabled = (bool)tmpv; } + { var tmpv = msg.OutputMuted; if (tmpv != null) obj.OutputMuted = (bool)tmpv; } { var tmpv = msg.OutputHardwareEnabled; if (tmpv != null) obj.OutputHardwareEnabled = (bool)tmpv; } { var tmpv = msg.Description; if (tmpv != null) obj.Description = (str)tmpv; } { var tmpv = msg.IsPrioritySpeaker; if (tmpv != null) obj.IsPrioritySpeaker = (bool)tmpv; } @@ -408,8 +434,10 @@ public void UpdateClientUpdated(ClientUpdated msg) { var tmpv = msg.TalkPower; if (tmpv != null) obj.TalkPower = (i32)tmpv; } { var tmpv = msg.IconId; if (tmpv != null) obj.IconId = (IconHash)tmpv; } + PostClientUpdated(msg); } + partial void PostClientUpdated(ClientUpdated msg); public void UpdateServerGroupList(ServerGroupList msg) { @@ -425,8 +453,10 @@ public void UpdateServerGroupList(ServerGroupList msg) { var tmpv = msg.NeededMemberRemovePower; if (tmpv != null) obj.NeededMemberRemovePower = (i32)tmpv; } SetServerGroup(obj, msg.ServerGroupId); + PostServerGroupList(msg); } + partial void PostServerGroupList(ServerGroupList msg); public void UpdateServerEdited(ServerEdited msg) { @@ -451,8 +481,10 @@ public void UpdateServerEdited(ServerEdited msg) { var tmpv = msg.HostbannerMode; if (tmpv != null) obj.HostbannerMode = (HostBannerMode)tmpv; } { var tmpv = msg.TempChannelDefaultDeleteDelay; if (tmpv != null) obj.TempChannelDefaultDeleteDelay = (Duration)tmpv; } + PostServerEdited(msg); } + partial void PostServerEdited(ServerEdited msg); public void UpdateChannelPermissionHints(ChannelPermissionHints msg) { @@ -463,8 +495,10 @@ public void UpdateChannelPermissionHints(ChannelPermissionHints msg) } { var tmpv = msg.Flags; if (tmpv != null) obj.PermissionHints = (ChannelPermissionHint)tmpv; } + PostChannelPermissionHints(msg); } + partial void PostChannelPermissionHints(ChannelPermissionHints msg); public void UpdateClientPermissionHints(ClientPermissionHints msg) { @@ -475,8 +509,10 @@ public void UpdateClientPermissionHints(ClientPermissionHints msg) } { var tmpv = msg.Flags; if (tmpv != null) obj.PermissionHints = (ClientPermissionHint)tmpv; } + PostClientPermissionHints(msg); } + partial void PostClientPermissionHints(ClientPermissionHints msg); #pragma warning restore IDE0017, CS0472 } diff --git a/TS3Client/Generated/M2B.tt b/TSLib/Generated/M2B.tt similarity index 94% rename from TS3Client/Generated/M2B.tt rename to TSLib/Generated/M2B.tt index a3389b77..a5440fc7 100644 --- a/TS3Client/Generated/M2B.tt +++ b/TSLib/Generated/M2B.tt @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -22,13 +22,12 @@ var genbook = BookDeclarations.Parse(Host.ResolvePath("../Declarations/Book.toml var genmsg = Messages.Parse(Host.ResolvePath("../Declarations/Messages.toml")); var genm2b = M2BDeclarations.Parse(Host.ResolvePath("../Declarations/MessagesToBook.toml"), genmsg, genbook); #> +using TSLib.Messages; -namespace TS3Client.Full.Book -{ - using Messages; - - <#= ConversionSet #> +<#= ConversionSet #> +namespace TSLib.Full.Book +{ public partial class Connection { #pragma warning disable IDE0017, CS0472 // Ignore "Object initialization can be simplified", "Something with == and null..." @@ -120,8 +119,10 @@ namespace TS3Client.Full.Book Remove<#=rule.to#>(<#=idStr#>);<# break; } #> + Post<#= msg.name #>(msg); } + partial void Post<#= msg.name #>(<#= msg.name #> msg); <# } #> #pragma warning restore IDE0017, CS0472 } diff --git a/TS3Client/Generated/M2BParser.ttinclude b/TSLib/Generated/M2BParser.ttinclude similarity index 100% rename from TS3Client/Generated/M2BParser.ttinclude rename to TSLib/Generated/M2BParser.ttinclude diff --git a/TS3Client/Generated/MessageParser.ttinclude b/TSLib/Generated/MessageParser.ttinclude similarity index 100% rename from TS3Client/Generated/MessageParser.ttinclude rename to TSLib/Generated/MessageParser.ttinclude diff --git a/TS3Client/Generated/Messages.cs b/TSLib/Generated/Messages.cs similarity index 80% rename from TS3Client/Generated/Messages.cs rename to TSLib/Generated/Messages.cs index 241ade8e..429a69c1 100644 --- a/TS3Client/Generated/Messages.cs +++ b/TSLib/Generated/Messages.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -21,46 +21,42 @@ +using System; +using System.Collections.Generic; +using System.Buffers.Text; +using TSLib.Commands; +using TSLib.Helper; -namespace TS3Client.Messages -{ - using Commands; - using Helper; - using System; - using System.Collections.Generic; - using System.Buffers.Text; - - #pragma warning disable CS8019 // Ignore unused imports - using i8 = System.SByte; - using u8 = System.Byte; - using i16 = System.Int16; - using u16 = System.UInt16; - using i32 = System.Int32; - using u32 = System.UInt32; - using i64 = System.Int64; - using u64 = System.UInt64; - using f32 = System.Single; - using f64 = System.Double; - using str = System.String; - - using DateTime = System.DateTime; - using Duration = System.TimeSpan; - using DurationSeconds = System.TimeSpan; - using DurationMilliseconds = System.TimeSpan; - using SocketAddr = System.String; - using IpAddr = System.String; - - using Uid = System.String; - using ClientDbId = System.UInt64; - using ClientId = System.UInt16; - using ChannelId = System.UInt64; - using ServerGroupId = System.UInt64; - using ChannelGroupId = System.UInt64; - using IconHash = System.Int32; - using ConnectionId = System.UInt32; +#pragma warning disable CS8019 // Ignore unused imports +using i8 = System.SByte; +using u8 = System.Byte; +using i16 = System.Int16; +using u16 = System.UInt16; +using i32 = System.Int32; +using u32 = System.UInt32; +using i64 = System.Int64; +using u64 = System.UInt64; +using f32 = System.Single; +using f64 = System.Double; +using str = System.String; + +using DateTime = System.DateTime; +using Duration = System.TimeSpan; +using DurationSeconds = System.TimeSpan; +using DurationMilliseconds = System.TimeSpan; +using SocketAddr = System.String; +using IpAddr = System.String; +using Ts3ErrorCode = TSLib.TsErrorCode; +using Ts3Permission = TSLib.TsPermission; + +using IconHash = System.Int32; +using ConnectionId = System.UInt32; #pragma warning restore CS8019 - public sealed class BanAdd : INotification +namespace TSLib.Messages +{ + + public sealed partial class BanAdd : INotification { public NotificationType NotifyType { get; } = NotificationType.BanAdd; @@ -76,11 +72,11 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "ip": Ip = Ts3String.Unescape(value); break; - case "name": Name = Ts3String.Unescape(value); break; - case "uid": Uid = Ts3String.Unescape(value); break; + case "ip": Ip = (IpAddr)TsString.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "uid": Uid = (Uid)TsString.Unescape(value); break; case "time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Time = TimeSpan.FromSeconds(oval); } break; - case "banreason": BanReason = Ts3String.Unescape(value); break; + case "banreason": BanReason = (str)TsString.Unescape(value); break; } @@ -105,7 +101,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class BanClient : INotification + public sealed partial class BanClient : INotification { public NotificationType NotifyType { get; } = NotificationType.BanClient; @@ -119,9 +115,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; case "time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Time = TimeSpan.FromSeconds(oval); } break; - case "banreason": BanReason = Ts3String.Unescape(value); break; + case "banreason": BanReason = (str)TsString.Unescape(value); break; } @@ -144,7 +140,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class BanDel : INotification + public sealed partial class BanDel : INotification { public NotificationType NotifyType { get; } = NotificationType.BanDel; @@ -156,7 +152,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "banid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) BanId = oval; } break; + case "banid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) BanId = (u32)oval; } break; } @@ -177,7 +173,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class BanDelAll : INotification + public sealed partial class BanDelAll : INotification { public NotificationType NotifyType { get; } = NotificationType.BanDelAll; @@ -192,7 +188,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class BanList : INotification, IResponse + public sealed partial class BanList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.BanList; public string ReturnCode { get; set; } @@ -216,20 +212,20 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "banid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) BanId = oval; } break; - case "ip": Ip = Ts3String.Unescape(value); break; - case "name": Name = Ts3String.Unescape(value); break; - case "uid": Uid = Ts3String.Unescape(value); break; - case "mytsid": MyTsId = Ts3String.Unescape(value); break; - case "lastnickname": LastNickname = Ts3String.Unescape(value); break; - case "created": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Created = Util.UnixTimeStart.AddSeconds(oval); } break; + case "banid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) BanId = (u32)oval; } break; + case "ip": Ip = (IpAddr)TsString.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "uid": Uid = (Uid)TsString.Unescape(value); break; + case "mytsid": MyTsId = (str)TsString.Unescape(value); break; + case "lastnickname": LastNickname = (str)TsString.Unescape(value); break; + case "created": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Created = Tools.FromUnix(oval); } break; case "duration": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Duration = TimeSpan.FromSeconds(oval); } break; - case "invokercldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) InvokerDatabaseId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "reason": Reason = Ts3String.Unescape(value); break; - case "enforcements": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Enforcements = oval; } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "invokercldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) InvokerDatabaseId = (ClientDbId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "reason": Reason = (str)TsString.Unescape(value); break; + case "enforcements": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Enforcements = (u32)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -261,7 +257,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class BanListRequest : INotification + public sealed partial class BanListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.BanListRequest; @@ -276,7 +272,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class BindingList : INotification + public sealed partial class BindingList : INotification { public NotificationType NotifyType { get; } = NotificationType.BindingList; @@ -288,7 +284,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "subsystem": Subsystem = Ts3String.Unescape(value); break; + case "subsystem": Subsystem = (str)TsString.Unescape(value); break; } @@ -309,7 +305,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelAddPerm : INotification + public sealed partial class ChannelAddPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelAddPerm; @@ -324,10 +320,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; } @@ -351,7 +347,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelChanged : INotification + public sealed partial class ChannelChanged : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelChanged; @@ -363,7 +359,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; } @@ -384,7 +380,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelClientAddPerm : INotification + public sealed partial class ChannelClientAddPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelClientAddPerm; @@ -400,11 +396,11 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; } @@ -429,7 +425,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelClientDelPerm : INotification + public sealed partial class ChannelClientDelPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelClientDelPerm; @@ -444,10 +440,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; } @@ -471,7 +467,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelClientPermList : INotification, IResponse + public sealed partial class ChannelClientPermList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ChannelClientPermList; public string ReturnCode { get; set; } @@ -489,14 +485,14 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; case "permnegated": PermissionNegated = value.Length > 0 && value[0] != '0'; break; case "permskip": PermissionSkip = value.Length > 0 && value[0] != '0'; break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -522,7 +518,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelClientPermListRequest : INotification + public sealed partial class ChannelClientPermListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelClientPermListRequest; @@ -535,8 +531,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -558,7 +554,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelCreate : INotification + public sealed partial class ChannelCreate : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelCreate; @@ -589,23 +585,23 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cpid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ParentId = oval; } break; - case "channel_name": Name = Ts3String.Unescape(value); break; - case "channel_topic": Topic = Ts3String.Unescape(value); break; - case "channel_description": Description = Ts3String.Unescape(value); break; - case "channel_password": Password = Ts3String.Unescape(value); break; + case "cpid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ParentId = (ChannelId)oval; } break; + case "channel_name": Name = (str)TsString.Unescape(value); break; + case "channel_topic": Topic = (str)TsString.Unescape(value); break; + case "channel_description": Description = (str)TsString.Unescape(value); break; + case "channel_password": Password = (str)TsString.Unescape(value); break; case "channel_codec": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Codec = (Codec)oval; } break; - case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = oval; } break; - case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = oval; } break; - case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = oval; } break; - case "channel_order": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) Order = oval; } break; + case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = (u8)oval; } break; + case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = (i32)oval; } break; + case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = (i32)oval; } break; + case "channel_order": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Order = (ChannelId)oval; } break; case "channel_flag_password": HasPassword = value.Length > 0 && value[0] != '0'; break; case "channel_codec_is_unencrypted": IsUnencrypted = value.Length > 0 && value[0] != '0'; break; case "channel_delete_delay": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DeleteDelay = TimeSpan.FromSeconds(oval); } break; case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_inherited": InheritsMaxFamilyClients = value.Length > 0 && value[0] != '0'; break; - case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "channel_name_phonetic": PhoneticName = (str)TsString.Unescape(value); break; case "channel_flag_permanent": IsPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_flag_semi_permanent": IsSemiPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_flag_default": IsDefault = value.Length > 0 && value[0] != '0'; break; @@ -648,7 +644,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelCreated : INotification, IResponse + public sealed partial class ChannelCreated : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ChannelCreated; public string ReturnCode { get; set; } @@ -684,32 +680,32 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "channel_order": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) Order = oval; } break; - case "channel_name": Name = Ts3String.Unescape(value); break; - case "channel_topic": Topic = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "channel_order": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Order = (ChannelId)oval; } break; + case "channel_name": Name = (str)TsString.Unescape(value); break; + case "channel_topic": Topic = (str)TsString.Unescape(value); break; case "channel_flag_default": IsDefault = value.Length > 0 && value[0] != '0'; break; case "channel_flag_password": HasPassword = value.Length > 0 && value[0] != '0'; break; case "channel_flag_permanent": IsPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_flag_semi_permanent": IsSemiPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_codec": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Codec = (Codec)oval; } break; - case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = oval; } break; - case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = oval; } break; + case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = (u8)oval; } break; + case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = (i32)oval; } break; case "channel_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; - case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = oval; } break; - case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = oval; } break; - case "channel_codec_latency_factor": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecLatencyFactor = oval; } break; + case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = (i32)oval; } break; + case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = (i32)oval; } break; + case "channel_codec_latency_factor": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecLatencyFactor = (i32)oval; } break; case "channel_codec_is_unencrypted": IsUnencrypted = value.Length > 0 && value[0] != '0'; break; case "channel_delete_delay": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DeleteDelay = TimeSpan.FromSeconds(oval); } break; case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_inherited": InheritsMaxFamilyClients = value.Length > 0 && value[0] != '0'; break; - case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "cpid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ParentId = oval; } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "channel_name_phonetic": PhoneticName = (str)TsString.Unescape(value); break; + case "cpid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ParentId = (ChannelId)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -753,7 +749,40 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelDelete : INotification + public sealed partial class ChannelCreateResponse : IResponse + { + + public string ReturnCode { get; set; } + + public ChannelId ChannelId { get; set; } + + public void SetField(string name, ReadOnlySpan value, Deserializer ser) + { + switch(name) + { + + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; + } + + } + + public void Expand(IMessage[] to, IEnumerable flds) + { + var toc = (ChannelCreateResponse[])to; + foreach (var fld in flds) + { + switch(fld) + { + + case "cid": foreach(var toi in toc) { toi.ChannelId = ChannelId; } break; + } + } + + } + } + + public sealed partial class ChannelDelete : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelDelete; @@ -766,7 +795,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; case "force": Force = value.Length > 0 && value[0] != '0'; break; } @@ -789,7 +818,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelDeleted : INotification, IResponse + public sealed partial class ChannelDeleted : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ChannelDeleted; public string ReturnCode { get; set; } @@ -804,11 +833,11 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -831,7 +860,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelDelPerm : INotification + public sealed partial class ChannelDelPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelDelPerm; @@ -846,7 +875,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelDescriptionChanged : INotification + public sealed partial class ChannelDescriptionChanged : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelDescriptionChanged; @@ -858,7 +887,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; } @@ -879,7 +908,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelDescriptionRequest : INotification + public sealed partial class ChannelDescriptionRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelDescriptionRequest; @@ -891,7 +920,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; } @@ -912,7 +941,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelEdit : INotification + public sealed partial class ChannelEdit : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelEdit; @@ -946,29 +975,29 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "channel_order": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) Order = oval; } break; - case "channel_name": Name = Ts3String.Unescape(value); break; - case "channel_topic": Topic = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "channel_order": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Order = (ChannelId)oval; } break; + case "channel_name": Name = (str)TsString.Unescape(value); break; + case "channel_topic": Topic = (str)TsString.Unescape(value); break; case "channel_flag_default": IsDefault = value.Length > 0 && value[0] != '0'; break; case "channel_flag_password": HasPassword = value.Length > 0 && value[0] != '0'; break; case "channel_flag_permanent": IsPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_flag_semi_permanent": IsSemiPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_codec": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Codec = (Codec)oval; } break; - case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = oval; } break; - case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = oval; } break; + case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = (u8)oval; } break; + case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = (i32)oval; } break; case "channel_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; - case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = oval; } break; - case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = oval; } break; - case "channel_codec_latency_factor": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecLatencyFactor = oval; } break; + case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = (i32)oval; } break; + case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = (i32)oval; } break; + case "channel_codec_latency_factor": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecLatencyFactor = (i32)oval; } break; case "channel_codec_is_unencrypted": IsUnencrypted = value.Length > 0 && value[0] != '0'; break; case "channel_delete_delay": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DeleteDelay = TimeSpan.FromSeconds(oval); } break; case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_inherited": InheritsMaxFamilyClients = value.Length > 0 && value[0] != '0'; break; - case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "cpid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ParentId = oval; } break; - case "channel_description": Description = Ts3String.Unescape(value); break; + case "channel_name_phonetic": PhoneticName = (str)TsString.Unescape(value); break; + case "cpid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ParentId = (ChannelId)oval; } break; + case "channel_description": Description = (str)TsString.Unescape(value); break; } @@ -1011,7 +1040,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelEdited : INotification, IResponse + public sealed partial class ChannelEdited : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ChannelEdited; public string ReturnCode { get; set; } @@ -1049,34 +1078,34 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; case "reasonid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Reason = (Reason)oval; } break; - case "channel_order": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) Order = oval; } break; - case "channel_name": Name = Ts3String.Unescape(value); break; - case "channel_topic": Topic = Ts3String.Unescape(value); break; + case "channel_order": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Order = (ChannelId)oval; } break; + case "channel_name": Name = (str)TsString.Unescape(value); break; + case "channel_topic": Topic = (str)TsString.Unescape(value); break; case "channel_flag_default": IsDefault = value.Length > 0 && value[0] != '0'; break; case "channel_flag_password": HasPassword = value.Length > 0 && value[0] != '0'; break; case "channel_flag_permanent": IsPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_flag_semi_permanent": IsSemiPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_codec": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Codec = (Codec)oval; } break; - case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = oval; } break; - case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = oval; } break; + case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = (u8)oval; } break; + case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = (i32)oval; } break; case "channel_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; - case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = oval; } break; - case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = oval; } break; - case "channel_codec_latency_factor": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecLatencyFactor = oval; } break; + case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = (i32)oval; } break; + case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = (i32)oval; } break; + case "channel_codec_latency_factor": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecLatencyFactor = (i32)oval; } break; case "channel_codec_is_unencrypted": IsUnencrypted = value.Length > 0 && value[0] != '0'; break; case "channel_delete_delay": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DeleteDelay = TimeSpan.FromSeconds(oval); } break; case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_inherited": InheritsMaxFamilyClients = value.Length > 0 && value[0] != '0'; break; - case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "cpid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ParentId = oval; } break; - case "channel_description": Description = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "channel_name_phonetic": PhoneticName = (str)TsString.Unescape(value); break; + case "cpid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ParentId = (ChannelId)oval; } break; + case "channel_description": Description = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -1122,7 +1151,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelFind : IResponse + public sealed partial class ChannelFind : IResponse { public string ReturnCode { get; set; } @@ -1135,9 +1164,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "channel_name": Name = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "channel_name": Name = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -1158,7 +1187,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelFindRequest : INotification + public sealed partial class ChannelFindRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelFindRequest; @@ -1170,7 +1199,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "pattern": Pattern = Ts3String.Unescape(value); break; + case "pattern": Pattern = (str)TsString.Unescape(value); break; } @@ -1191,7 +1220,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupAdd : INotification + public sealed partial class ChannelGroupAdd : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupAdd; @@ -1204,7 +1233,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "name": Name = Ts3String.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; case "type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) GroupType = (GroupType)oval; } break; } @@ -1227,7 +1256,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupAddPerm : INotification + public sealed partial class ChannelGroupAddPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupAddPerm; @@ -1242,10 +1271,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; } @@ -1269,7 +1298,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupClientList : INotification, IResponse + public sealed partial class ChannelGroupClientList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupClientList; public string ReturnCode { get; set; } @@ -1283,10 +1312,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -1308,7 +1337,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupClientListRequest : INotification + public sealed partial class ChannelGroupClientListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupClientListRequest; @@ -1322,9 +1351,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; } @@ -1347,7 +1376,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupCopy : INotification + public sealed partial class ChannelGroupCopy : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupCopy; @@ -1362,9 +1391,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "scgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) SourceChannelGroupId = oval; } break; - case "tcgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) TargetChannelGroupId = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; + case "scgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) SourceChannelGroupId = (ChannelGroupId)oval; } break; + case "tcgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetChannelGroupId = (ChannelGroupId)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; case "type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) GroupType = (GroupType)oval; } break; } @@ -1389,7 +1418,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupDel : INotification + public sealed partial class ChannelGroupDel : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupDel; @@ -1402,7 +1431,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; case "force": Force = value.Length > 0 && value[0] != '0'; break; } @@ -1425,7 +1454,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupDelPerm : INotification + public sealed partial class ChannelGroupDelPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupDelPerm; @@ -1439,9 +1468,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; } @@ -1464,7 +1493,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupList : INotification, IResponse + public sealed partial class ChannelGroupList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupList; public string ReturnCode { get; set; } @@ -1485,17 +1514,17 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; case "type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) GroupType = (GroupType)oval; } break; case "iconid": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; case "savedb": IsPermanent = value.Length > 0 && value[0] != '0'; break; - case "sortid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) SortId = oval; } break; + case "sortid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) SortId = (i32)oval; } break; case "namemode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NamingMode = (GroupNamingMode)oval; } break; - case "n_modifyp": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededModifyPower = oval; } break; - case "n_member_addp": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededMemberAddPower = oval; } break; - case "n_member_remove_p": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededMemberRemovePower = oval; } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "n_modifyp": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededModifyPower = (i32)oval; } break; + case "n_member_addp": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededMemberAddPower = (i32)oval; } break; + case "n_member_remove_p": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededMemberRemovePower = (i32)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -1524,7 +1553,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupListRequest : INotification + public sealed partial class ChannelGroupListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupListRequest; @@ -1539,7 +1568,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupPermList : INotification, IResponse + public sealed partial class ChannelGroupPermList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupPermList; public string ReturnCode { get; set; } @@ -1556,13 +1585,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; case "permnegated": PermissionNegated = value.Length > 0 && value[0] != '0'; break; case "permskip": PermissionSkip = value.Length > 0 && value[0] != '0'; break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -1587,7 +1616,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupPermListRequest : INotification + public sealed partial class ChannelGroupPermListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupPermListRequest; @@ -1599,7 +1628,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; } @@ -1620,7 +1649,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelGroupRename : INotification + public sealed partial class ChannelGroupRename : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelGroupRename; @@ -1633,8 +1662,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; } @@ -1656,7 +1685,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelInfoRequest : INotification + public sealed partial class ChannelInfoRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelInfoRequest; @@ -1668,7 +1697,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; } @@ -1689,7 +1718,124 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelList : INotification + public sealed partial class ChannelInfoResponse : IResponse + { + + public string ReturnCode { get; set; } + + public ChannelId ParentChannelId { get; set; } + public str Name { get; set; } + public str Topic { get; set; } + public str Description { get; set; } + public str Password { get; set; } + public Codec Codec { get; set; } + public u8 CodecQuality { get; set; } + public i32 MaxClients { get; set; } + public i32 MaxFamilyClients { get; set; } + public ChannelId Order { get; set; } + public bool IsPermanent { get; set; } + public bool IsSemiPermanent { get; set; } + public bool IsDefault { get; set; } + public bool HasPassword { get; set; } + public i32 CodecLatencyFactor { get; set; } + public bool IsUnencrypted { get; set; } + public str PasswordSalt { get; set; } + public DurationSeconds DeleteDelay { get; set; } + public bool IsMaxClientsUnlimited { get; set; } + public bool IsMaxFamilyClientsUnlimited { get; set; } + public bool InheritsMaxFamilyClients { get; set; } + public str FilePath { get; set; } + public i32 NeededTalkPower { get; set; } + public bool ForcedSilence { get; set; } + public str PhoneticName { get; set; } + public IconHash IconId { get; set; } + public str BannerGfxUrl { get; set; } + public str BannerMode { get; set; } + public DurationSeconds DurationEmpty { get; set; } + + public void SetField(string name, ReadOnlySpan value, Deserializer ser) + { + switch(name) + { + + case "pid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ParentChannelId = (ChannelId)oval; } break; + case "channel_name": Name = (str)TsString.Unescape(value); break; + case "channel_topic": Topic = (str)TsString.Unescape(value); break; + case "channel_description": Description = (str)TsString.Unescape(value); break; + case "channel_password": Password = (str)TsString.Unescape(value); break; + case "channel_codec": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Codec = (Codec)oval; } break; + case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = (u8)oval; } break; + case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = (i32)oval; } break; + case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = (i32)oval; } break; + case "channel_order": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Order = (ChannelId)oval; } break; + case "channel_flag_permanent": IsPermanent = value.Length > 0 && value[0] != '0'; break; + case "channel_flag_semi_permanent": IsSemiPermanent = value.Length > 0 && value[0] != '0'; break; + case "channel_flag_default": IsDefault = value.Length > 0 && value[0] != '0'; break; + case "channel_flag_password": HasPassword = value.Length > 0 && value[0] != '0'; break; + case "channel_codec_latency_factor": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecLatencyFactor = (i32)oval; } break; + case "channel_codec_is_unencrypted": IsUnencrypted = value.Length > 0 && value[0] != '0'; break; + case "channel_security_salt": PasswordSalt = (str)TsString.Unescape(value); break; + case "channel_delete_delay": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DeleteDelay = TimeSpan.FromSeconds(oval); } break; + case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value.Length > 0 && value[0] != '0'; break; + case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value.Length > 0 && value[0] != '0'; break; + case "channel_flag_maxfamilyclients_inherited": InheritsMaxFamilyClients = value.Length > 0 && value[0] != '0'; break; + case "channel_filepath": FilePath = (str)TsString.Unescape(value); break; + case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = (i32)oval; } break; + case "channel_forced_silence": ForcedSilence = value.Length > 0 && value[0] != '0'; break; + case "channel_name_phonetic": PhoneticName = (str)TsString.Unescape(value); break; + case "channel_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; + case "channel_banner_gfx_url": BannerGfxUrl = (str)TsString.Unescape(value); break; + case "channel_banner_mode": BannerMode = (str)TsString.Unescape(value); break; + case "seconds_empty": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DurationEmpty = TimeSpan.FromSeconds(oval); } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; + } + + } + + public void Expand(IMessage[] to, IEnumerable flds) + { + var toc = (ChannelInfoResponse[])to; + foreach (var fld in flds) + { + switch(fld) + { + + case "pid": foreach(var toi in toc) { toi.ParentChannelId = ParentChannelId; } break; + case "channel_name": foreach(var toi in toc) { toi.Name = Name; } break; + case "channel_topic": foreach(var toi in toc) { toi.Topic = Topic; } break; + case "channel_description": foreach(var toi in toc) { toi.Description = Description; } break; + case "channel_password": foreach(var toi in toc) { toi.Password = Password; } break; + case "channel_codec": foreach(var toi in toc) { toi.Codec = Codec; } break; + case "channel_codec_quality": foreach(var toi in toc) { toi.CodecQuality = CodecQuality; } break; + case "channel_maxclients": foreach(var toi in toc) { toi.MaxClients = MaxClients; } break; + case "channel_maxfamilyclients": foreach(var toi in toc) { toi.MaxFamilyClients = MaxFamilyClients; } break; + case "channel_order": foreach(var toi in toc) { toi.Order = Order; } break; + case "channel_flag_permanent": foreach(var toi in toc) { toi.IsPermanent = IsPermanent; } break; + case "channel_flag_semi_permanent": foreach(var toi in toc) { toi.IsSemiPermanent = IsSemiPermanent; } break; + case "channel_flag_default": foreach(var toi in toc) { toi.IsDefault = IsDefault; } break; + case "channel_flag_password": foreach(var toi in toc) { toi.HasPassword = HasPassword; } break; + case "channel_codec_latency_factor": foreach(var toi in toc) { toi.CodecLatencyFactor = CodecLatencyFactor; } break; + case "channel_codec_is_unencrypted": foreach(var toi in toc) { toi.IsUnencrypted = IsUnencrypted; } break; + case "channel_security_salt": foreach(var toi in toc) { toi.PasswordSalt = PasswordSalt; } break; + case "channel_delete_delay": foreach(var toi in toc) { toi.DeleteDelay = DeleteDelay; } break; + case "channel_flag_maxclients_unlimited": foreach(var toi in toc) { toi.IsMaxClientsUnlimited = IsMaxClientsUnlimited; } break; + case "channel_flag_maxfamilyclients_unlimited": foreach(var toi in toc) { toi.IsMaxFamilyClientsUnlimited = IsMaxFamilyClientsUnlimited; } break; + case "channel_flag_maxfamilyclients_inherited": foreach(var toi in toc) { toi.InheritsMaxFamilyClients = InheritsMaxFamilyClients; } break; + case "channel_filepath": foreach(var toi in toc) { toi.FilePath = FilePath; } break; + case "channel_needed_talk_power": foreach(var toi in toc) { toi.NeededTalkPower = NeededTalkPower; } break; + case "channel_forced_silence": foreach(var toi in toc) { toi.ForcedSilence = ForcedSilence; } break; + case "channel_name_phonetic": foreach(var toi in toc) { toi.PhoneticName = PhoneticName; } break; + case "channel_icon_id": foreach(var toi in toc) { toi.IconId = IconId; } break; + case "channel_banner_gfx_url": foreach(var toi in toc) { toi.BannerGfxUrl = BannerGfxUrl; } break; + case "channel_banner_mode": foreach(var toi in toc) { toi.BannerMode = BannerMode; } break; + case "seconds_empty": foreach(var toi in toc) { toi.DurationEmpty = DurationEmpty; } break; + } + } + + } + } + + public sealed partial class ChannelList : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelList; @@ -1724,28 +1870,28 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ParentId = oval; } break; - case "channel_name": Name = Ts3String.Unescape(value); break; - case "channel_topic": Topic = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ParentId = (ChannelId)oval; } break; + case "channel_name": Name = (str)TsString.Unescape(value); break; + case "channel_topic": Topic = (str)TsString.Unescape(value); break; case "channel_codec": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Codec = (Codec)oval; } break; - case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = oval; } break; - case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = oval; } break; - case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = oval; } break; - case "channel_order": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) Order = oval; } break; + case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = (u8)oval; } break; + case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = (i32)oval; } break; + case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = (i32)oval; } break; + case "channel_order": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Order = (ChannelId)oval; } break; case "channel_flag_permanent": IsPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_flag_semi_permanent": IsSemiPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_flag_default": IsDefault = value.Length > 0 && value[0] != '0'; break; case "channel_flag_password": HasPassword = value.Length > 0 && value[0] != '0'; break; - case "channel_codec_latency_factor": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecLatencyFactor = oval; } break; + case "channel_codec_latency_factor": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecLatencyFactor = (i32)oval; } break; case "channel_codec_is_unencrypted": IsUnencrypted = value.Length > 0 && value[0] != '0'; break; case "channel_delete_delay": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DeleteDelay = TimeSpan.FromSeconds(oval); } break; case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value.Length > 0 && value[0] != '0'; break; case "channel_flag_maxfamilyclients_inherited": InheritsMaxFamilyClients = value.Length > 0 && value[0] != '0'; break; - case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = oval; } break; + case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = (i32)oval; } break; case "channel_forced_silence": ForcedSilence = value.Length > 0 && value[0] != '0'; break; - case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "channel_name_phonetic": PhoneticName = (str)TsString.Unescape(value); break; case "channel_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; case "channel_flag_private": IsPrivate = value.Length > 0 && value[0] != '0'; break; @@ -1791,7 +1937,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelListFinished : INotification + public sealed partial class ChannelListFinished : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelListFinished; @@ -1806,7 +1952,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelListRequest : INotification + public sealed partial class ChannelListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelListRequest; @@ -1821,7 +1967,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelListResponse : IResponse + public sealed partial class ChannelListResponse : IResponse { public string ReturnCode { get; set; } @@ -1851,26 +1997,26 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "pid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ParentChannelId = oval; } break; - case "channel_order": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) Order = oval; } break; - case "channel_name": Name = Ts3String.Unescape(value); break; - case "total_clients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalClients = oval; } break; - case "channel_needed_subscribe_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededSubscribePower = oval; } break; - case "channel_topic": Topic = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "pid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ParentChannelId = (ChannelId)oval; } break; + case "channel_order": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Order = (ChannelId)oval; } break; + case "channel_name": Name = (str)TsString.Unescape(value); break; + case "total_clients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalClients = (i32)oval; } break; + case "channel_needed_subscribe_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededSubscribePower = (i32)oval; } break; + case "channel_topic": Topic = (str)TsString.Unescape(value); break; case "channel_flag_default": IsDefault = value.Length > 0 && value[0] != '0'; break; case "channel_flag_password": HasPassword = value.Length > 0 && value[0] != '0'; break; case "channel_flag_permanent": IsPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_flag_semi_permanent": IsSemiPermanent = value.Length > 0 && value[0] != '0'; break; case "channel_codec": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Codec = (Codec)oval; } break; - case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = oval; } break; - case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = oval; } break; - case "total_clients_family": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalFamilyClients = oval; } break; - case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = oval; } break; - case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = oval; } break; + case "channel_codec_quality": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) CodecQuality = (u8)oval; } break; + case "channel_needed_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededTalkPower = (i32)oval; } break; + case "total_clients_family": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalFamilyClients = (i32)oval; } break; + case "channel_maxclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxClients = (i32)oval; } break; + case "channel_maxfamilyclients": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) MaxFamilyClients = (i32)oval; } break; case "channel_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; case "seconds_empty": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DurationEmpty = TimeSpan.FromSeconds(oval); } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -1908,7 +2054,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelMove : INotification + public sealed partial class ChannelMove : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelMove; @@ -1922,9 +2068,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ParentId = oval; } break; - case "order": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) Order = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ParentId = (ChannelId)oval; } break; + case "order": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Order = (ChannelId)oval; } break; } @@ -1947,7 +2093,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelMoved : INotification + public sealed partial class ChannelMoved : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelMoved; @@ -1965,13 +2111,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "order": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) Order = oval; } break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "order": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Order = (ChannelId)oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; case "reasonid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Reason = (Reason)oval; } break; - case "cpid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ParentId = oval; } break; + case "cpid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ParentId = (ChannelId)oval; } break; } @@ -1998,7 +2144,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelPasswordChanged : INotification + public sealed partial class ChannelPasswordChanged : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelPasswordChanged; @@ -2010,7 +2156,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; } @@ -2031,7 +2177,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelPermissionHints : INotification + public sealed partial class ChannelPermissionHints : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelPermissionHints; @@ -2044,7 +2190,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; case "flags": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Flags = (ChannelPermissionHint)oval; } break; } @@ -2067,7 +2213,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelPermList : INotification, IResponse + public sealed partial class ChannelPermList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ChannelPermList; public string ReturnCode { get; set; } @@ -2083,12 +2229,12 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; case "permnegated": PermissionNegated = value.Length > 0 && value[0] != '0'; break; case "permskip": PermissionSkip = value.Length > 0 && value[0] != '0'; break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -2112,7 +2258,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelPermListRequest : INotification + public sealed partial class ChannelPermListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelPermListRequest; @@ -2124,7 +2270,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; } @@ -2145,7 +2291,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelSubscribe : INotification + public sealed partial class ChannelSubscribe : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelSubscribe; @@ -2157,7 +2303,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; } @@ -2178,7 +2324,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelSubscribeAll : INotification + public sealed partial class ChannelSubscribeAll : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelSubscribeAll; @@ -2193,7 +2339,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelSubscribed : INotification + public sealed partial class ChannelSubscribed : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelSubscribed; @@ -2206,7 +2352,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; case "es": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) EmptySince = TimeSpan.FromSeconds(oval); } break; } @@ -2229,7 +2375,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelUnsubscribe : INotification + public sealed partial class ChannelUnsubscribe : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelUnsubscribe; @@ -2241,7 +2387,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; } @@ -2262,7 +2408,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelUnsubscribeAll : INotification + public sealed partial class ChannelUnsubscribeAll : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelUnsubscribeAll; @@ -2277,7 +2423,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ChannelUnsubscribed : INotification + public sealed partial class ChannelUnsubscribed : INotification { public NotificationType NotifyType { get; } = NotificationType.ChannelUnsubscribed; @@ -2289,7 +2435,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; } @@ -2310,7 +2456,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientAddPerm : INotification + public sealed partial class ClientAddPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientAddPerm; @@ -2326,10 +2472,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; case "permskip": PermissionSkip = value.Length > 0 && value[0] != '0'; break; } @@ -2355,7 +2501,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientChannelGroupChanged : INotification + public sealed partial class ClientChannelGroupChanged : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientChannelGroupChanged; @@ -2373,13 +2519,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; - case "cgi": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroupIndex = oval; } break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; + case "cgi": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroupIndex = (ChannelGroupId)oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; } @@ -2406,7 +2552,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientChatClose : INotification + public sealed partial class ClientChatClose : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientChatClose; @@ -2419,8 +2565,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "cluid": ClientUid = Ts3String.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; } @@ -2442,7 +2588,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientChatClosed : INotification + public sealed partial class ClientChatClosed : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientChatClosed; @@ -2455,8 +2601,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "cluid": ClientUid = Ts3String.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; } @@ -2478,7 +2624,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientChatComposing : INotification + public sealed partial class ClientChatComposing : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientChatComposing; @@ -2491,8 +2637,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "cluid": ClientUid = Ts3String.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; } @@ -2514,7 +2660,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientConnectionInfo : INotification + public sealed partial class ClientConnectionInfo : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientConnectionInfo; @@ -2566,46 +2712,46 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; case "connection_ping": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Ping = TimeSpan.FromMilliseconds(oval); } break; case "connection_ping_deviation": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) PingDeviation = TimeSpan.FromMilliseconds(oval); } break; case "connection_connected_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ConnectedTime = TimeSpan.FromMilliseconds(oval); } break; - case "connection_client_ip": Ip = Ts3String.Unescape(value); break; - case "connection_client_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) Port = oval; } break; - case "connection_packets_sent_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentSpeech = oval; } break; - case "connection_packets_sent_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentKeepalive = oval; } break; - case "connection_packets_sent_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentControl = oval; } break; - case "connection_bytes_sent_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentSpeech = oval; } break; - case "connection_bytes_sent_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentKeepalive = oval; } break; - case "connection_bytes_sent_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentControl = oval; } break; - case "connection_packets_received_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedSpeech = oval; } break; - case "connection_packets_received_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedKeepalive = oval; } break; - case "connection_packets_received_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedControl = oval; } break; - case "connection_bytes_received_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedSpeech = oval; } break; - case "connection_bytes_received_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedKeepalive = oval; } break; - case "connection_bytes_received_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedControl = oval; } break; - case "connection_server2client_packetloss_speech": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ServerToClientPacketlossSpeech = oval; } break; - case "connection_server2client_packetloss_keepalive": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ServerToClientPacketlossKeepalive = oval; } break; - case "connection_server2client_packetloss_control": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ServerToClientPacketlossControl = oval; } break; - case "connection_server2client_packetloss_total": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ServerToClientPacketlossTotal = oval; } break; - case "connection_client2server_packetloss_speech": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ClientToServerPacketlossSpeech = oval; } break; - case "connection_client2server_packetloss_keepalive": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ClientToServerPacketlossKeepalive = oval; } break; - case "connection_client2server_packetloss_control": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ClientToServerPacketlossControl = oval; } break; - case "connection_client2server_packetloss_total": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ClientToServerPacketlossTotal = oval; } break; - case "connection_bandwidth_sent_last_second_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondSpeech = oval; } break; - case "connection_bandwidth_sent_last_second_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondKeepalive = oval; } break; - case "connection_bandwidth_sent_last_second_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondControl = oval; } break; - case "connection_bandwidth_sent_last_minute_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteSpeech = oval; } break; - case "connection_bandwidth_sent_last_minute_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteKeepalive = oval; } break; - case "connection_bandwidth_sent_last_minute_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteControl = oval; } break; - case "connection_bandwidth_received_last_second_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondSpeech = oval; } break; - case "connection_bandwidth_received_last_second_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondKeepalive = oval; } break; - case "connection_bandwidth_received_last_second_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondControl = oval; } break; - case "connection_bandwidth_received_last_minute_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteSpeech = oval; } break; - case "connection_bandwidth_received_last_minute_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteKeepalive = oval; } break; - case "connection_bandwidth_received_last_minute_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteControl = oval; } break; - case "connection_filetransfer_bandwidth_sent": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthSent = oval; } break; - case "connection_filetransfer_bandwidth_received": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthReceived = oval; } break; + case "connection_client_ip": Ip = (str)TsString.Unescape(value); break; + case "connection_client_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) Port = (u16)oval; } break; + case "connection_packets_sent_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentSpeech = (u64)oval; } break; + case "connection_packets_sent_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentKeepalive = (u64)oval; } break; + case "connection_packets_sent_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentControl = (u64)oval; } break; + case "connection_bytes_sent_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentSpeech = (u64)oval; } break; + case "connection_bytes_sent_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentKeepalive = (u64)oval; } break; + case "connection_bytes_sent_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentControl = (u64)oval; } break; + case "connection_packets_received_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedSpeech = (u64)oval; } break; + case "connection_packets_received_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedKeepalive = (u64)oval; } break; + case "connection_packets_received_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedControl = (u64)oval; } break; + case "connection_bytes_received_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedSpeech = (u64)oval; } break; + case "connection_bytes_received_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedKeepalive = (u64)oval; } break; + case "connection_bytes_received_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedControl = (u64)oval; } break; + case "connection_server2client_packetloss_speech": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ServerToClientPacketlossSpeech = (f32)oval; } break; + case "connection_server2client_packetloss_keepalive": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ServerToClientPacketlossKeepalive = (f32)oval; } break; + case "connection_server2client_packetloss_control": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ServerToClientPacketlossControl = (f32)oval; } break; + case "connection_server2client_packetloss_total": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ServerToClientPacketlossTotal = (f32)oval; } break; + case "connection_client2server_packetloss_speech": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ClientToServerPacketlossSpeech = (f32)oval; } break; + case "connection_client2server_packetloss_keepalive": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ClientToServerPacketlossKeepalive = (f32)oval; } break; + case "connection_client2server_packetloss_control": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ClientToServerPacketlossControl = (f32)oval; } break; + case "connection_client2server_packetloss_total": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) ClientToServerPacketlossTotal = (f32)oval; } break; + case "connection_bandwidth_sent_last_second_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondSpeech = (u64)oval; } break; + case "connection_bandwidth_sent_last_second_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondKeepalive = (u64)oval; } break; + case "connection_bandwidth_sent_last_second_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondControl = (u64)oval; } break; + case "connection_bandwidth_sent_last_minute_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteSpeech = (u64)oval; } break; + case "connection_bandwidth_sent_last_minute_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteKeepalive = (u64)oval; } break; + case "connection_bandwidth_sent_last_minute_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteControl = (u64)oval; } break; + case "connection_bandwidth_received_last_second_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondSpeech = (u64)oval; } break; + case "connection_bandwidth_received_last_second_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondKeepalive = (u64)oval; } break; + case "connection_bandwidth_received_last_second_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondControl = (u64)oval; } break; + case "connection_bandwidth_received_last_minute_speech": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteSpeech = (u64)oval; } break; + case "connection_bandwidth_received_last_minute_keepalive": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteKeepalive = (u64)oval; } break; + case "connection_bandwidth_received_last_minute_control": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteControl = (u64)oval; } break; + case "connection_filetransfer_bandwidth_sent": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthSent = (u64)oval; } break; + case "connection_filetransfer_bandwidth_received": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthReceived = (u64)oval; } break; case "connection_idle_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) IdleTime = TimeSpan.FromMilliseconds(oval); } break; } @@ -2667,7 +2813,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientConnectionInfoRequest : INotification + public sealed partial class ClientConnectionInfoRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientConnectionInfoRequest; @@ -2679,7 +2825,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; } @@ -2700,7 +2846,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientConnectionInfoUpdateRequest : INotification + public sealed partial class ClientConnectionInfoUpdateRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientConnectionInfoUpdateRequest; @@ -2715,7 +2861,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbDelete : INotification + public sealed partial class ClientDbDelete : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientDbDelete; @@ -2727,7 +2873,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -2748,7 +2894,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbEdit : INotification + public sealed partial class ClientDbEdit : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientDbEdit; @@ -2760,7 +2906,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -2781,7 +2927,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbFind : INotification, IResponse + public sealed partial class ClientDbFind : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ClientDbFind; public string ReturnCode { get; set; } @@ -2797,12 +2943,12 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "client_nickname": Name = Ts3String.Unescape(value); break; - case "client_lastconnected": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) LastConnected = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = oval; } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "client_unique_identifier": Uid = (Uid)TsString.Unescape(value); break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; + case "client_lastconnected": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) LastConnected = Tools.FromUnix(oval); } break; + case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = (i32)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -2826,7 +2972,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbFindRequest : INotification + public sealed partial class ClientDbFindRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientDbFindRequest; @@ -2838,7 +2984,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "pattern": Pattern = Ts3String.Unescape(value); break; + case "pattern": Pattern = (str)TsString.Unescape(value); break; } @@ -2859,7 +3005,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbIdFromUid : INotification, IResponse + public sealed partial class ClientDbIdFromUid : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ClientDbIdFromUid; public string ReturnCode { get; set; } @@ -2872,9 +3018,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cluid": ClientUid = Ts3String.Unescape(value); break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -2895,7 +3041,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbIdFromUidRequest : INotification + public sealed partial class ClientDbIdFromUidRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientDbIdFromUidRequest; @@ -2907,7 +3053,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cluid": ClientUid = Ts3String.Unescape(value); break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; } @@ -2928,7 +3074,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbInfo : IResponse + public sealed partial class ClientDbInfo : IResponse { public string ReturnCode { get; set; } @@ -2957,25 +3103,25 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "client_lastip": LastIp = Ts3String.Unescape(value); break; - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "client_database_id": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) DatabaseId = oval; } break; - case "client_nickname": Name = Ts3String.Unescape(value); break; + case "client_lastip": LastIp = (str)TsString.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "client_unique_identifier": Uid = (Uid)TsString.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "client_database_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DatabaseId = (ClientDbId)oval; } break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; case "client_type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) ClientType = (ClientType)oval; } break; - case "client_flag_avatar": AvatarHash = Ts3String.Unescape(value); break; - case "client_description": Description = Ts3String.Unescape(value); break; + case "client_flag_avatar": AvatarHash = (str)TsString.Unescape(value); break; + case "client_description": Description = (str)TsString.Unescape(value); break; case "client_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; - case "client_created": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) CreationDate = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_lastconnected": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) LastConnected = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = oval; } break; - case "client_month_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyUploadQuota = oval; } break; - case "client_month_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyDownloadQuota = oval; } break; - case "client_total_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalUploadQuota = oval; } break; - case "client_total_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalDownloadQuota = oval; } break; - case "client_base64HashClientUID": Base64HashClientUid = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "client_created": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) CreationDate = Tools.FromUnix(oval); } break; + case "client_lastconnected": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) LastConnected = Tools.FromUnix(oval); } break; + case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = (i32)oval; } break; + case "client_month_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyUploadQuota = (i64)oval; } break; + case "client_month_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyDownloadQuota = (i64)oval; } break; + case "client_total_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalUploadQuota = (i64)oval; } break; + case "client_total_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalDownloadQuota = (i64)oval; } break; + case "client_base64HashClientUID": Base64HashClientUid = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -3012,7 +3158,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbInfoRequest : INotification + public sealed partial class ClientDbInfoRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientDbInfoRequest; @@ -3024,7 +3170,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -3045,7 +3191,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbList : INotification, IResponse + public sealed partial class ClientDbList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ClientDbList; public string ReturnCode { get; set; } @@ -3064,15 +3210,15 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "client_nickname": Name = Ts3String.Unescape(value); break; - case "client_created": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) CreationDate = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_lastconnected": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) LastConnected = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = oval; } break; - case "client_description": Description = Ts3String.Unescape(value); break; - case "client_lastip": LastIp = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "client_unique_identifier": Uid = (Uid)TsString.Unescape(value); break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; + case "client_created": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) CreationDate = Tools.FromUnix(oval); } break; + case "client_lastconnected": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) LastConnected = Tools.FromUnix(oval); } break; + case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = (i32)oval; } break; + case "client_description": Description = (str)TsString.Unescape(value); break; + case "client_lastip": LastIp = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -3099,7 +3245,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDbListRequest : INotification + public sealed partial class ClientDbListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientDbListRequest; @@ -3112,8 +3258,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "start": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Offset = oval; } break; - case "duration": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Limit = oval; } break; + case "start": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Offset = (u32)oval; } break; + case "duration": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Limit = (u32)oval; } break; } @@ -3135,7 +3281,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientDelPerm : INotification + public sealed partial class ClientDelPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientDelPerm; @@ -3149,9 +3295,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; } @@ -3174,7 +3320,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientEdit : INotification + public sealed partial class ClientEdit : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientEdit; @@ -3188,8 +3334,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "client_description": Description = Ts3String.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "client_description": Description = (str)TsString.Unescape(value); break; case "client_is_talker": TalkPowerGranted = value.Length > 0 && value[0] != '0'; break; } @@ -3213,7 +3359,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientEnterView : INotification + public sealed partial class ClientEnterView : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientEnterView; @@ -3262,42 +3408,42 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "reasonid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Reason = (Reason)oval; } break; - case "ctid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) TargetChannelId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "client_database_id": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) DatabaseId = oval; } break; - case "client_nickname": Name = Ts3String.Unescape(value); break; + case "ctid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetChannelId = (ChannelId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "client_database_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DatabaseId = (ClientDbId)oval; } break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; case "client_type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) ClientType = (ClientType)oval; } break; - case "cfid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) SourceChannelId = oval; } break; - case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "client_flag_avatar": AvatarHash = Ts3String.Unescape(value); break; - case "client_description": Description = Ts3String.Unescape(value); break; + case "cfid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) SourceChannelId = (ChannelId)oval; } break; + case "client_unique_identifier": Uid = (Uid)TsString.Unescape(value); break; + case "client_flag_avatar": AvatarHash = (str)TsString.Unescape(value); break; + case "client_description": Description = (str)TsString.Unescape(value); break; case "client_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; case "client_input_muted": InputMuted = value.Length > 0 && value[0] != '0'; break; case "client_output_muted": OutputMuted = value.Length > 0 && value[0] != '0'; break; case "client_outputonly_muted": OutputOnlyMuted = value.Length > 0 && value[0] != '0'; break; case "client_input_hardware": InputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; case "client_output_hardware": OutputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; - case "client_meta_data": Metadata = Ts3String.Unescape(value); break; + case "client_meta_data": Metadata = (str)TsString.Unescape(value); break; case "client_is_recording": IsRecording = value.Length > 0 && value[0] != '0'; break; - case "client_channel_group_id": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; - case "client_channel_group_inherited_channel_id": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) InheritedChannelGroupFromChannel = oval; } break; - case "client_servergroups": { if(value.Length == 0) ServerGroups = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupId[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { { if(Utf8Parser.TryParse(ss.Trim(value), out ServerGroupId oval, out _)) ServerGroups[i] = oval; } if (i < cnt) value = ss.Next(value); } } } break; + case "client_channel_group_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; + case "client_channel_group_inherited_channel_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) InheritedChannelGroupFromChannel = (ChannelId)oval; } break; + case "client_servergroups": { if(value.Length == 0) ServerGroups = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupId[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { { if(Utf8Parser.TryParse(ss.Trim(value), out u64 oval, out _)) ServerGroups[i] = (ServerGroupId)oval; } if (i < cnt) value = ss.Next(value); } } } break; case "client_away": IsAway = value.Length > 0 && value[0] != '0'; break; - case "client_away_message": AwayMessage = Ts3String.Unescape(value); break; - case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = oval; } break; - case "client_talk_request": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) TalkPowerRequestTime = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_talk_request_msg": TalkPowerRequestMessage = Ts3String.Unescape(value); break; + case "client_away_message": AwayMessage = (str)TsString.Unescape(value); break; + case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = (i32)oval; } break; + case "client_talk_request": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) TalkPowerRequestTime = Tools.FromUnix(oval); } break; + case "client_talk_request_msg": TalkPowerRequestMessage = (str)TsString.Unescape(value); break; case "client_is_talker": TalkPowerGranted = value.Length > 0 && value[0] != '0'; break; case "client_is_priority_speaker": IsPrioritySpeaker = value.Length > 0 && value[0] != '0'; break; - case "client_unread_messages": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) UnreadMessages = oval; } break; - case "client_nickname_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "client_needed_serverquery_view_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededServerqueryViewPower = oval; } break; + case "client_unread_messages": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) UnreadMessages = (u32)oval; } break; + case "client_nickname_phonetic": PhoneticName = (str)TsString.Unescape(value); break; + case "client_needed_serverquery_view_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededServerqueryViewPower = (i32)oval; } break; case "client_is_channel_commander": IsChannelCommander = value.Length > 0 && value[0] != '0'; break; - case "client_country": CountryCode = Ts3String.Unescape(value); break; - case "client_badges": Badges = Ts3String.Unescape(value); break; + case "client_country": CountryCode = (str)TsString.Unescape(value); break; + case "client_badges": Badges = (str)TsString.Unescape(value); break; } @@ -3354,7 +3500,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientFindRequest : INotification + public sealed partial class ClientFindRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientFindRequest; @@ -3366,7 +3512,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "pattern": Pattern = Ts3String.Unescape(value); break; + case "pattern": Pattern = (str)TsString.Unescape(value); break; } @@ -3387,7 +3533,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientIds : INotification, IResponse + public sealed partial class ClientIds : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ClientIds; public string ReturnCode { get; set; } @@ -3401,10 +3547,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cluid": ClientUid = Ts3String.Unescape(value); break; - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -3426,7 +3572,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientIdsRequest : INotification + public sealed partial class ClientIdsRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientIdsRequest; @@ -3438,7 +3584,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cluid": ClientUid = Ts3String.Unescape(value); break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; } @@ -3459,7 +3605,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientInfo : IResponse + public sealed partial class ClientInfo : IResponse { public string ReturnCode { get; set; } @@ -3532,67 +3678,67 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "client_idle_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ClientIdleTime = TimeSpan.FromMilliseconds(oval); } break; - case "client_version": ClientVersion = Ts3String.Unescape(value); break; - case "client_version_sign": ClientVersionSign = Ts3String.Unescape(value); break; - case "client_platform": ClientPlatform = Ts3String.Unescape(value); break; - case "client_default_channel": DefaultChannel = Ts3String.Unescape(value); break; - case "client_security_hash": SecurityHash = Ts3String.Unescape(value); break; - case "client_login_name": LoginName = Ts3String.Unescape(value); break; - case "client_default_token": DefaultToken = Ts3String.Unescape(value); break; - case "connection_filetransfer_bandwidth_sent": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthSent = oval; } break; - case "connection_filetransfer_bandwidth_received": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthReceived = oval; } break; - case "connection_packets_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentTotal = oval; } break; - case "connection_packets_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedTotal = oval; } break; - case "connection_bytes_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentTotal = oval; } break; - case "connection_bytes_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedTotal = oval; } break; - case "connection_bandwidth_sent_last_second_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondTotal = oval; } break; - case "connection_bandwidth_received_last_second_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondTotal = oval; } break; - case "connection_bandwidth_sent_last_minute_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteTotal = oval; } break; - case "connection_bandwidth_received_last_minute_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteTotal = oval; } break; + case "client_version": ClientVersion = (str)TsString.Unescape(value); break; + case "client_version_sign": ClientVersionSign = (str)TsString.Unescape(value); break; + case "client_platform": ClientPlatform = (str)TsString.Unescape(value); break; + case "client_default_channel": DefaultChannel = (str)TsString.Unescape(value); break; + case "client_security_hash": SecurityHash = (str)TsString.Unescape(value); break; + case "client_login_name": LoginName = (str)TsString.Unescape(value); break; + case "client_default_token": DefaultToken = (str)TsString.Unescape(value); break; + case "connection_filetransfer_bandwidth_sent": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthSent = (u64)oval; } break; + case "connection_filetransfer_bandwidth_received": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthReceived = (u64)oval; } break; + case "connection_packets_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentTotal = (u64)oval; } break; + case "connection_packets_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedTotal = (u64)oval; } break; + case "connection_bytes_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentTotal = (u64)oval; } break; + case "connection_bytes_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedTotal = (u64)oval; } break; + case "connection_bandwidth_sent_last_second_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondTotal = (u64)oval; } break; + case "connection_bandwidth_received_last_second_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondTotal = (u64)oval; } break; + case "connection_bandwidth_sent_last_minute_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteTotal = (u64)oval; } break; + case "connection_bandwidth_received_last_minute_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteTotal = (u64)oval; } break; case "connection_connected_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ConnectedTime = TimeSpan.FromMilliseconds(oval); } break; - case "connection_client_ip": Ip = Ts3String.Unescape(value); break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "client_database_id": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) DatabaseId = oval; } break; - case "client_nickname": Name = Ts3String.Unescape(value); break; + case "connection_client_ip": Ip = (str)TsString.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "client_unique_identifier": Uid = (Uid)TsString.Unescape(value); break; + case "client_database_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DatabaseId = (ClientDbId)oval; } break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; case "client_type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) ClientType = (ClientType)oval; } break; case "client_input_muted": InputMuted = value.Length > 0 && value[0] != '0'; break; case "client_output_muted": OutputMuted = value.Length > 0 && value[0] != '0'; break; case "client_outputonly_muted": OutputOnlyMuted = value.Length > 0 && value[0] != '0'; break; case "client_input_hardware": InputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; case "client_output_hardware": OutputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; - case "client_meta_data": Metadata = Ts3String.Unescape(value); break; + case "client_meta_data": Metadata = (str)TsString.Unescape(value); break; case "client_is_recording": IsRecording = value.Length > 0 && value[0] != '0'; break; - case "client_channel_group_id": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; - case "client_channel_group_inherited_channel_id": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) InheritedChannelGroupFromChannel = oval; } break; - case "client_servergroups": { if(value.Length == 0) ServerGroups = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupId[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { { if(Utf8Parser.TryParse(ss.Trim(value), out ServerGroupId oval, out _)) ServerGroups[i] = oval; } if (i < cnt) value = ss.Next(value); } } } break; + case "client_channel_group_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; + case "client_channel_group_inherited_channel_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) InheritedChannelGroupFromChannel = (ChannelId)oval; } break; + case "client_servergroups": { if(value.Length == 0) ServerGroups = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupId[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { { if(Utf8Parser.TryParse(ss.Trim(value), out u64 oval, out _)) ServerGroups[i] = (ServerGroupId)oval; } if (i < cnt) value = ss.Next(value); } } } break; case "client_away": IsAway = value.Length > 0 && value[0] != '0'; break; - case "client_away_message": AwayMessage = Ts3String.Unescape(value); break; - case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = oval; } break; - case "client_talk_request": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) TalkPowerRequestTime = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_talk_request_msg": TalkPowerRequestMessage = Ts3String.Unescape(value); break; + case "client_away_message": AwayMessage = (str)TsString.Unescape(value); break; + case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = (i32)oval; } break; + case "client_talk_request": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) TalkPowerRequestTime = Tools.FromUnix(oval); } break; + case "client_talk_request_msg": TalkPowerRequestMessage = (str)TsString.Unescape(value); break; case "client_is_talker": TalkPowerGranted = value.Length > 0 && value[0] != '0'; break; case "client_is_priority_speaker": IsPrioritySpeaker = value.Length > 0 && value[0] != '0'; break; - case "client_unread_messages": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) UnreadMessages = oval; } break; - case "client_nickname_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "client_needed_serverquery_view_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededServerqueryViewPower = oval; } break; + case "client_unread_messages": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) UnreadMessages = (u32)oval; } break; + case "client_nickname_phonetic": PhoneticName = (str)TsString.Unescape(value); break; + case "client_needed_serverquery_view_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededServerqueryViewPower = (i32)oval; } break; case "client_is_channel_commander": IsChannelCommander = value.Length > 0 && value[0] != '0'; break; - case "client_country": CountryCode = Ts3String.Unescape(value); break; - case "client_badges": Badges = Ts3String.Unescape(value); break; - case "client_created": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) CreationDate = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_lastconnected": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) LastConnected = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = oval; } break; - case "client_month_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyUploadQuota = oval; } break; - case "client_month_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyDownloadQuota = oval; } break; - case "client_total_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalUploadQuota = oval; } break; - case "client_total_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalDownloadQuota = oval; } break; - case "client_base64HashClientUID": Base64HashClientUid = Ts3String.Unescape(value); break; - case "client_flag_avatar": AvatarHash = Ts3String.Unescape(value); break; - case "client_description": Description = Ts3String.Unescape(value); break; + case "client_country": CountryCode = (str)TsString.Unescape(value); break; + case "client_badges": Badges = (str)TsString.Unescape(value); break; + case "client_created": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) CreationDate = Tools.FromUnix(oval); } break; + case "client_lastconnected": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) LastConnected = Tools.FromUnix(oval); } break; + case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = (i32)oval; } break; + case "client_month_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyUploadQuota = (i64)oval; } break; + case "client_month_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyDownloadQuota = (i64)oval; } break; + case "client_total_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalUploadQuota = (i64)oval; } break; + case "client_total_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalDownloadQuota = (i64)oval; } break; + case "client_base64HashClientUID": Base64HashClientUid = (str)TsString.Unescape(value); break; + case "client_flag_avatar": AvatarHash = (str)TsString.Unescape(value); break; + case "client_description": Description = (str)TsString.Unescape(value); break; case "client_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; - case "client_myteamspeak_id": MyTeamSpeakId = Ts3String.Unescape(value); break; - case "client_integrations": Integrations = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "client_myteamspeak_id": MyTeamSpeakId = (str)TsString.Unescape(value); break; + case "client_integrations": Integrations = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -3672,7 +3818,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientInfoRequest : INotification + public sealed partial class ClientInfoRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientInfoRequest; @@ -3684,7 +3830,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; } @@ -3705,7 +3851,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientInit : INotification + public sealed partial class ClientInit : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientInit; @@ -3731,21 +3877,21 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "client_nickname": Name = Ts3String.Unescape(value); break; - case "client_version": ClientVersion = Ts3String.Unescape(value); break; - case "client_platform": ClientPlatform = Ts3String.Unescape(value); break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; + case "client_version": ClientVersion = (str)TsString.Unescape(value); break; + case "client_platform": ClientPlatform = (str)TsString.Unescape(value); break; case "client_input_hardware": InputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; case "client_output_hardware": OutputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; - case "client_default_channel": DefaultChannel = Ts3String.Unescape(value); break; - case "client_default_channel_password": DefaultChannelPassword = Ts3String.Unescape(value); break; - case "client_server_password": Password = Ts3String.Unescape(value); break; - case "client_meta_data": Metadata = Ts3String.Unescape(value); break; - case "client_version_sign": ClientVersionSign = Ts3String.Unescape(value); break; - case "client_key_offset": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientKeyOffset = oval; } break; - case "client_nickname_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "client_default_token": DefaultToken = Ts3String.Unescape(value); break; - case "hwid": HardwareId = Ts3String.Unescape(value); break; - case "client_badges": Badges = Ts3String.Unescape(value); break; + case "client_default_channel": DefaultChannel = (str)TsString.Unescape(value); break; + case "client_default_channel_password": DefaultChannelPassword = (str)TsString.Unescape(value); break; + case "client_server_password": Password = (str)TsString.Unescape(value); break; + case "client_meta_data": Metadata = (str)TsString.Unescape(value); break; + case "client_version_sign": ClientVersionSign = (str)TsString.Unescape(value); break; + case "client_key_offset": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientKeyOffset = (u64)oval; } break; + case "client_nickname_phonetic": PhoneticName = (str)TsString.Unescape(value); break; + case "client_default_token": DefaultToken = (str)TsString.Unescape(value); break; + case "hwid": HardwareId = (str)TsString.Unescape(value); break; + case "client_badges": Badges = (str)TsString.Unescape(value); break; } @@ -3780,7 +3926,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientInitIv : INotification + public sealed partial class ClientInitIv : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientInitIv; @@ -3794,9 +3940,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "alpha": Alpha = Ts3String.Unescape(value); break; - case "omega": Omega = Ts3String.Unescape(value); break; - case "ip": Ip = Ts3String.Unescape(value); break; + case "alpha": Alpha = (str)TsString.Unescape(value); break; + case "omega": Omega = (str)TsString.Unescape(value); break; + case "ip": Ip = (IpAddr)TsString.Unescape(value); break; } @@ -3819,7 +3965,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientKick : INotification + public sealed partial class ClientKick : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientKick; @@ -3833,9 +3979,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; case "reasonid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Reason = (Reason)oval; } break; - case "reasonmsg": ReasonMessage = Ts3String.Unescape(value); break; + case "reasonmsg": ReasonMessage = (str)TsString.Unescape(value); break; } @@ -3858,7 +4004,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientLeftView : INotification + public sealed partial class ClientLeftView : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientLeftView; @@ -3878,15 +4024,15 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "reasonmsg": ReasonMessage = Ts3String.Unescape(value); break; + case "reasonmsg": ReasonMessage = (str)TsString.Unescape(value); break; case "bantime": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) BanTime = TimeSpan.FromSeconds(oval); } break; case "reasonid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Reason = (Reason)oval; } break; - case "ctid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) TargetChannelId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "cfid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) SourceChannelId = oval; } break; + case "ctid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetChannelId = (ChannelId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "cfid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) SourceChannelId = (ChannelId)oval; } break; } @@ -3915,7 +4061,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientList : IResponse + public sealed partial class ClientList : IResponse { public string ReturnCode { get; set; } @@ -3955,36 +4101,36 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "client_database_id": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) DatabaseId = oval; } break; - case "client_nickname": Name = Ts3String.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "client_database_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DatabaseId = (ClientDbId)oval; } break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; case "client_type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) ClientType = (ClientType)oval; } break; - case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; + case "client_unique_identifier": Uid = (Uid)TsString.Unescape(value); break; case "client_away": IsAway = value.Length > 0 && value[0] != '0'; break; - case "client_away_message": AwayMessage = Ts3String.Unescape(value); break; + case "client_away_message": AwayMessage = (str)TsString.Unescape(value); break; case "client_flag_talking": IsTalking = value.Length > 0 && value[0] != '0'; break; case "client_input_muted": InputMuted = value.Length > 0 && value[0] != '0'; break; case "client_output_muted": OutputMuted = value.Length > 0 && value[0] != '0'; break; case "client_input_hardware": InputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; case "client_output_hardware": OutputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; - case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = oval; } break; + case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = (i32)oval; } break; case "client_is_talker": TalkPowerGranted = value.Length > 0 && value[0] != '0'; break; case "client_is_priority_speaker": IsPrioritySpeaker = value.Length > 0 && value[0] != '0'; break; case "client_is_recording": IsRecording = value.Length > 0 && value[0] != '0'; break; case "client_is_channel_commander": IsChannelCommander = value.Length > 0 && value[0] != '0'; break; case "client_idle_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ClientIdleTime = TimeSpan.FromMilliseconds(oval); } break; - case "client_created": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) CreationDate = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_lastconnected": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) LastConnected = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_servergroups": { if(value.Length == 0) ServerGroups = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupId[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { { if(Utf8Parser.TryParse(ss.Trim(value), out ServerGroupId oval, out _)) ServerGroups[i] = oval; } if (i < cnt) value = ss.Next(value); } } } break; - case "client_channel_group_id": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; - case "client_channel_group_inherited_channel_id": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) InheritedChannelGroupFromChannel = oval; } break; - case "client_version": ClientVersion = Ts3String.Unescape(value); break; - case "client_platform": ClientPlatform = Ts3String.Unescape(value); break; - case "client_country": CountryCode = Ts3String.Unescape(value); break; - case "connection_client_ip": Ip = Ts3String.Unescape(value); break; - case "client_badges": Badges = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "client_created": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) CreationDate = Tools.FromUnix(oval); } break; + case "client_lastconnected": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) LastConnected = Tools.FromUnix(oval); } break; + case "client_servergroups": { if(value.Length == 0) ServerGroups = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupId[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { { if(Utf8Parser.TryParse(ss.Trim(value), out u64 oval, out _)) ServerGroups[i] = (ServerGroupId)oval; } if (i < cnt) value = ss.Next(value); } } } break; + case "client_channel_group_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; + case "client_channel_group_inherited_channel_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) InheritedChannelGroupFromChannel = (ChannelId)oval; } break; + case "client_version": ClientVersion = (str)TsString.Unescape(value); break; + case "client_platform": ClientPlatform = (str)TsString.Unescape(value); break; + case "client_country": CountryCode = (str)TsString.Unescape(value); break; + case "connection_client_ip": Ip = (str)TsString.Unescape(value); break; + case "client_badges": Badges = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -4032,7 +4178,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientListRequest : INotification + public sealed partial class ClientListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientListRequest; @@ -4047,7 +4193,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientMove : INotification + public sealed partial class ClientMove : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientMove; @@ -4061,9 +4207,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpw": ChannelPassword = Ts3String.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpw": ChannelPassword = (str)TsString.Unescape(value); break; } @@ -4086,7 +4232,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientMoved : INotification + public sealed partial class ClientMoved : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientMoved; @@ -4104,13 +4250,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; case "reasonid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Reason = (Reason)oval; } break; - case "ctid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) TargetChannelId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "reasonmsg": ReasonMessage = Ts3String.Unescape(value); break; + case "ctid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetChannelId = (ChannelId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "reasonmsg": ReasonMessage = (str)TsString.Unescape(value); break; } @@ -4137,7 +4283,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientNameFromDbId : INotification, IResponse + public sealed partial class ClientNameFromDbId : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ClientNameFromDbId; public string ReturnCode { get; set; } @@ -4151,10 +4297,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cluid": ClientUid = Ts3String.Unescape(value); break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -4176,7 +4322,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientNameFromDbIdRequest : INotification + public sealed partial class ClientNameFromDbIdRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientNameFromDbIdRequest; @@ -4188,7 +4334,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -4209,7 +4355,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientNameFromUid : INotification, IResponse + public sealed partial class ClientNameFromUid : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ClientNameFromUid; public string ReturnCode { get; set; } @@ -4223,10 +4369,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cluid": ClientUid = Ts3String.Unescape(value); break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -4248,7 +4394,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientNameFromUidRequest : INotification + public sealed partial class ClientNameFromUidRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientNameFromUidRequest; @@ -4260,7 +4406,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cluid": ClientUid = Ts3String.Unescape(value); break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; } @@ -4281,7 +4427,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientNeededPermissions : INotification + public sealed partial class ClientNeededPermissions : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientNeededPermissions; @@ -4295,7 +4441,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; } @@ -4317,7 +4463,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientPermissionHints : INotification + public sealed partial class ClientPermissionHints : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientPermissionHints; @@ -4330,7 +4476,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; case "flags": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Flags = (ClientPermissionHint)oval; } break; } @@ -4353,7 +4499,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientPermList : INotification, IResponse + public sealed partial class ClientPermList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ClientPermList; public string ReturnCode { get; set; } @@ -4370,13 +4516,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; case "permnegated": PermissionNegated = value.Length > 0 && value[0] != '0'; break; case "permskip": PermissionSkip = value.Length > 0 && value[0] != '0'; break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -4401,7 +4547,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientPermListRequest : INotification + public sealed partial class ClientPermListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientPermListRequest; @@ -4413,7 +4559,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -4434,7 +4580,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientPoke : INotification + public sealed partial class ClientPoke : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientPoke; @@ -4449,10 +4595,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "msg": Message = Ts3String.Unescape(value); break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "msg": Message = (str)TsString.Unescape(value); break; } @@ -4476,7 +4622,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientPokeRequest : INotification + public sealed partial class ClientPokeRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientPokeRequest; @@ -4489,8 +4635,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "msg": Message = Ts3String.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "msg": Message = (str)TsString.Unescape(value); break; } @@ -4512,7 +4658,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientServerGroupAdded : INotification + public sealed partial class ClientServerGroupAdded : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientServerGroupAdded; @@ -4530,13 +4676,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "name": Name = Ts3String.Unescape(value); break; - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "cluid": ClientUid = Ts3String.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; } @@ -4563,7 +4709,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientServerGroupRemoved : INotification + public sealed partial class ClientServerGroupRemoved : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientServerGroupRemoved; @@ -4581,13 +4727,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "name": Name = Ts3String.Unescape(value); break; - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "cluid": ClientUid = Ts3String.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; } @@ -4614,7 +4760,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientSetServerQueryLogin : INotification, IResponse + public sealed partial class ClientSetServerQueryLogin : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ClientSetServerQueryLogin; public string ReturnCode { get; set; } @@ -4626,8 +4772,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "client_login_password": LoginPassword = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "client_login_password": LoginPassword = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -4647,7 +4793,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientSetServerQueryLoginRequest : INotification + public sealed partial class ClientSetServerQueryLoginRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientSetServerQueryLoginRequest; @@ -4661,9 +4807,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "client_login_name": LoginName = Ts3String.Unescape(value); break; - case "client_login_password": LoginPassword = Ts3String.Unescape(value); break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "client_login_name": LoginName = (str)TsString.Unescape(value); break; + case "client_login_password": LoginPassword = (str)TsString.Unescape(value); break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -4686,7 +4832,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientUidFromClid : INotification, IResponse + public sealed partial class ClientUidFromClid : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ClientUidFromClid; public string ReturnCode { get; set; } @@ -4700,10 +4846,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cluid": ClientUid = Ts3String.Unescape(value); break; - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "nickname": Nickname = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "nickname": Nickname = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -4725,7 +4871,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientUidFromClidRequest : INotification + public sealed partial class ClientUidFromClidRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientUidFromClidRequest; @@ -4737,7 +4883,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; } @@ -4758,7 +4904,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientUpdate : INotification + public sealed partial class ClientUpdate : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientUpdate; @@ -4783,20 +4929,20 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "client_nickname": Name = Ts3String.Unescape(value); break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; case "client_input_muted": InputMuted = value.Length > 0 && value[0] != '0'; break; case "client_output_muted": OutputMuted = value.Length > 0 && value[0] != '0'; break; case "client_away": IsAway = value.Length > 0 && value[0] != '0'; break; - case "client_away_message": AwayMessage = Ts3String.Unescape(value); break; + case "client_away_message": AwayMessage = (str)TsString.Unescape(value); break; case "client_input_hardware": InputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; case "client_output_hardware": OutputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; case "client_is_channel_commander": IsChannelCommander = value.Length > 0 && value[0] != '0'; break; - case "client_flag_avatar": AvatarHash = Ts3String.Unescape(value); break; - case "client_nickname_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "client_talk_request": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) TalkPowerRequestTime = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_talk_request_msg": TalkPowerRequestMessage = Ts3String.Unescape(value); break; + case "client_flag_avatar": AvatarHash = (str)TsString.Unescape(value); break; + case "client_nickname_phonetic": PhoneticName = (str)TsString.Unescape(value); break; + case "client_talk_request": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) TalkPowerRequestTime = Tools.FromUnix(oval); } break; + case "client_talk_request_msg": TalkPowerRequestMessage = (str)TsString.Unescape(value); break; case "client_is_recording": IsRecording = value.Length > 0 && value[0] != '0'; break; - case "client_badges": Badges = Ts3String.Unescape(value); break; + case "client_badges": Badges = (str)TsString.Unescape(value); break; } @@ -4830,7 +4976,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ClientUpdated : INotification + public sealed partial class ClientUpdated : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientUpdated; @@ -4850,6 +4996,7 @@ public sealed class ClientUpdated : INotification public i64? TotalDownloadQuota { get; set; } public bool? InputMuted { get; set; } public bool? InputHardwareEnabled { get; set; } + public bool? OutputMuted { get; set; } public bool? OutputHardwareEnabled { get; set; } public str Description { get; set; } public bool? IsPrioritySpeaker { get; set; } @@ -4865,42 +5012,47 @@ public sealed class ClientUpdated : INotification public str Badges { get; set; } public i32? TalkPower { get; set; } public IconHash? IconId { get; set; } + public bool? IsAway { get; set; } + public str AwayMessage { get; set; } public void SetField(string name, ReadOnlySpan value, Deserializer ser) { switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "client_nickname": Name = Ts3String.Unescape(value); break; - case "client_unread_messages": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) UnreadMessages = oval; } break; - case "client_version": ClientVersion = Ts3String.Unescape(value); break; - case "client_platform": ClientPlatform = Ts3String.Unescape(value); break; - case "client_login_name": LoginName = Ts3String.Unescape(value); break; - case "client_created": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) CreationDate = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_lastconnected": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) LastConnected = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = oval; } break; - case "client_month_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyUploadQuota = oval; } break; - case "client_month_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyDownloadQuota = oval; } break; - case "client_total_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalUploadQuota = oval; } break; - case "client_total_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalDownloadQuota = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; + case "client_unread_messages": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) UnreadMessages = (u32)oval; } break; + case "client_version": ClientVersion = (str)TsString.Unescape(value); break; + case "client_platform": ClientPlatform = (str)TsString.Unescape(value); break; + case "client_login_name": LoginName = (str)TsString.Unescape(value); break; + case "client_created": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) CreationDate = Tools.FromUnix(oval); } break; + case "client_lastconnected": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) LastConnected = Tools.FromUnix(oval); } break; + case "client_totalconnections": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TotalConnections = (i32)oval; } break; + case "client_month_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyUploadQuota = (i64)oval; } break; + case "client_month_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) MonthlyDownloadQuota = (i64)oval; } break; + case "client_total_bytes_uploaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalUploadQuota = (i64)oval; } break; + case "client_total_bytes_downloaded": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) TotalDownloadQuota = (i64)oval; } break; case "client_input_muted": InputMuted = value.Length > 0 && value[0] != '0'; break; case "client_input_hardware": InputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; + case "client_output_muted": OutputMuted = value.Length > 0 && value[0] != '0'; break; case "client_output_hardware": OutputHardwareEnabled = value.Length > 0 && value[0] != '0'; break; - case "client_description": Description = Ts3String.Unescape(value); break; + case "client_description": Description = (str)TsString.Unescape(value); break; case "client_is_priority_speaker": IsPrioritySpeaker = value.Length > 0 && value[0] != '0'; break; case "client_is_channel_commander": IsChannelCommander = value.Length > 0 && value[0] != '0'; break; - case "client_flag_avatar": AvatarHash = Ts3String.Unescape(value); break; - case "client_talk_request": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) TalkPowerRequestTime = Util.UnixTimeStart.AddSeconds(oval); } break; - case "client_talk_request_msg": TalkPowerRequestMessage = Ts3String.Unescape(value); break; + case "client_flag_avatar": AvatarHash = (str)TsString.Unescape(value); break; + case "client_talk_request": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) TalkPowerRequestTime = Tools.FromUnix(oval); } break; + case "client_talk_request_msg": TalkPowerRequestMessage = (str)TsString.Unescape(value); break; case "client_is_talker": TalkPowerGranted = value.Length > 0 && value[0] != '0'; break; - case "client_nickname_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "client_nickname_phonetic": PhoneticName = (str)TsString.Unescape(value); break; case "client_is_recording": IsRecording = value.Length > 0 && value[0] != '0'; break; - case "client_servergroups": { if(value.Length == 0) ServerGroups = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupId[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { { if(Utf8Parser.TryParse(ss.Trim(value), out ServerGroupId oval, out _)) ServerGroups[i] = oval; } if (i < cnt) value = ss.Next(value); } } } break; - case "client_myteamspeak_id": MyTeamSpeakId = Ts3String.Unescape(value); break; - case "client_badges": Badges = Ts3String.Unescape(value); break; - case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = oval; } break; + case "client_servergroups": { if(value.Length == 0) ServerGroups = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupId[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { { if(Utf8Parser.TryParse(ss.Trim(value), out u64 oval, out _)) ServerGroups[i] = (ServerGroupId)oval; } if (i < cnt) value = ss.Next(value); } } } break; + case "client_myteamspeak_id": MyTeamSpeakId = (str)TsString.Unescape(value); break; + case "client_badges": Badges = (str)TsString.Unescape(value); break; + case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = (i32)oval; } break; case "client_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; + case "client_away": IsAway = value.Length > 0 && value[0] != '0'; break; + case "client_away_message": AwayMessage = (str)TsString.Unescape(value); break; } @@ -4929,6 +5081,7 @@ public void Expand(IMessage[] to, IEnumerable flds) case "client_total_bytes_downloaded": foreach(var toi in toc) { toi.TotalDownloadQuota = TotalDownloadQuota; } break; case "client_input_muted": foreach(var toi in toc) { toi.InputMuted = InputMuted; } break; case "client_input_hardware": foreach(var toi in toc) { toi.InputHardwareEnabled = InputHardwareEnabled; } break; + case "client_output_muted": foreach(var toi in toc) { toi.OutputMuted = OutputMuted; } break; case "client_output_hardware": foreach(var toi in toc) { toi.OutputHardwareEnabled = OutputHardwareEnabled; } break; case "client_description": foreach(var toi in toc) { toi.Description = Description; } break; case "client_is_priority_speaker": foreach(var toi in toc) { toi.IsPrioritySpeaker = IsPrioritySpeaker; } break; @@ -4944,13 +5097,15 @@ public void Expand(IMessage[] to, IEnumerable flds) case "client_badges": foreach(var toi in toc) { toi.Badges = Badges; } break; case "client_talk_power": foreach(var toi in toc) { toi.TalkPower = TalkPower; } break; case "client_icon_id": foreach(var toi in toc) { toi.IconId = IconId; } break; + case "client_away": foreach(var toi in toc) { toi.IsAway = IsAway; } break; + case "client_away_message": foreach(var toi in toc) { toi.AwayMessage = AwayMessage; } break; } } } } - public sealed class ClientVariablesRequest : INotification + public sealed partial class ClientVariablesRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ClientVariablesRequest; @@ -4962,7 +5117,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; } @@ -4983,7 +5138,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class CommandError : INotification + public sealed partial class CommandError : INotification { public NotificationType NotifyType { get; } = NotificationType.CommandError; @@ -5000,10 +5155,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "id": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Id = (Ts3ErrorCode)oval; } break; - case "msg": Message = Ts3String.Unescape(value); break; + case "msg": Message = (str)TsString.Unescape(value); break; case "failed_permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) MissingPermissionId = ser.PermissionTransform.GetName(oval); } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; - case "extra_msg": ExtraMessage = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; + case "extra_msg": ExtraMessage = (str)TsString.Unescape(value); break; } @@ -5028,7 +5183,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ComplainAdd : INotification + public sealed partial class ComplainAdd : INotification { public NotificationType NotifyType { get; } = NotificationType.ComplainAdd; @@ -5041,8 +5196,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "tcldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) TargetClientDbId = oval; } break; - case "message": Message = Ts3String.Unescape(value); break; + case "tcldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetClientDbId = (ClientDbId)oval; } break; + case "message": Message = (str)TsString.Unescape(value); break; } @@ -5064,7 +5219,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ComplainDel : INotification + public sealed partial class ComplainDel : INotification { public NotificationType NotifyType { get; } = NotificationType.ComplainDel; @@ -5077,8 +5232,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "tcldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) TargetClientDbId = oval; } break; - case "fcldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) FromClientDbId = oval; } break; + case "tcldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetClientDbId = (ClientDbId)oval; } break; + case "fcldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FromClientDbId = (ClientDbId)oval; } break; } @@ -5100,7 +5255,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ComplainDelAll : INotification + public sealed partial class ComplainDelAll : INotification { public NotificationType NotifyType { get; } = NotificationType.ComplainDelAll; @@ -5112,7 +5267,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "tcldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) TargetClientDbId = oval; } break; + case "tcldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetClientDbId = (ClientDbId)oval; } break; } @@ -5133,7 +5288,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ComplainList : INotification, IResponse + public sealed partial class ComplainList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ComplainList; public string ReturnCode { get; set; } @@ -5150,13 +5305,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "tcldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) TargetClientDbId = oval; } break; - case "tname": TargetName = Ts3String.Unescape(value); break; - case "fcldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) FromClientDbId = oval; } break; - case "fname": FromName = Ts3String.Unescape(value); break; - case "message": Message = Ts3String.Unescape(value); break; - case "timestamp": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Timestamp = Util.UnixTimeStart.AddSeconds(oval); } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "tcldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetClientDbId = (ClientDbId)oval; } break; + case "tname": TargetName = (str)TsString.Unescape(value); break; + case "fcldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FromClientDbId = (ClientDbId)oval; } break; + case "fname": FromName = (str)TsString.Unescape(value); break; + case "message": Message = (str)TsString.Unescape(value); break; + case "timestamp": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Timestamp = Tools.FromUnix(oval); } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -5181,7 +5336,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ComplainListRequest : INotification + public sealed partial class ComplainListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ComplainListRequest; @@ -5193,7 +5348,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "tcldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) TargetClientDbId = oval; } break; + case "tcldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetClientDbId = (ClientDbId)oval; } break; } @@ -5214,7 +5369,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class CustomDelete : INotification + public sealed partial class CustomDelete : INotification { public NotificationType NotifyType { get; } = NotificationType.CustomDelete; @@ -5227,8 +5382,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "ident": ExternalIdentity = Ts3String.Unescape(value); break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "ident": ExternalIdentity = (str)TsString.Unescape(value); break; } @@ -5250,7 +5405,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class CustomInfoRequest : INotification + public sealed partial class CustomInfoRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.CustomInfoRequest; @@ -5262,7 +5417,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -5283,7 +5438,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class CustomSearch : INotification + public sealed partial class CustomSearch : INotification { public NotificationType NotifyType { get; } = NotificationType.CustomSearch; @@ -5296,8 +5451,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "ident": ExternalIdentity = Ts3String.Unescape(value); break; - case "pattern": Pattern = Ts3String.Unescape(value); break; + case "ident": ExternalIdentity = (str)TsString.Unescape(value); break; + case "pattern": Pattern = (str)TsString.Unescape(value); break; } @@ -5319,7 +5474,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class CustomSet : INotification + public sealed partial class CustomSet : INotification { public NotificationType NotifyType { get; } = NotificationType.CustomSet; @@ -5333,9 +5488,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "ident": ExternalIdentity = Ts3String.Unescape(value); break; - case "value": Value = Ts3String.Unescape(value); break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "ident": ExternalIdentity = (str)TsString.Unescape(value); break; + case "value": Value = (str)TsString.Unescape(value); break; } @@ -5358,7 +5513,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class Disconnect : INotification + public sealed partial class Disconnect : INotification { public NotificationType NotifyType { get; } = NotificationType.Disconnect; @@ -5372,7 +5527,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "reasonid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Reason = (Reason)oval; } break; - case "reasonmsg": ReasonMessage = Ts3String.Unescape(value); break; + case "reasonmsg": ReasonMessage = (str)TsString.Unescape(value); break; } @@ -5394,7 +5549,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FileDownload : INotification, IResponse + public sealed partial class FileDownload : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.FileDownload; public string ReturnCode { get; set; } @@ -5412,14 +5567,14 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = oval; } break; - case "serverftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ServerFileTransferId = oval; } break; - case "ftkey": FileTransferKey = Ts3String.Unescape(value); break; - case "port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) Port = oval; } break; - case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = oval; } break; - case "proto": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Protocol = oval; } break; - case "ip": Ip = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = (u16)oval; } break; + case "serverftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ServerFileTransferId = (u16)oval; } break; + case "ftkey": FileTransferKey = (str)TsString.Unescape(value); break; + case "port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) Port = (u16)oval; } break; + case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = (u64)oval; } break; + case "proto": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Protocol = (u8)oval; } break; + case "ip": Ip = (IpAddr)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -5445,7 +5600,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FileInfo : INotification, IResponse + public sealed partial class FileInfo : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.FileInfo; public string ReturnCode { get; set; } @@ -5461,12 +5616,12 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "path": Path = Ts3String.Unescape(value); break; - case "name": Name = Ts3String.Unescape(value); break; - case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = oval; } break; - case "datetime": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DateTime = Util.UnixTimeStart.AddSeconds(oval); } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "path": Path = (str)TsString.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = (u64)oval; } break; + case "datetime": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) DateTime = Tools.FromUnix(oval); } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -5490,7 +5645,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FileList : INotification, IResponse + public sealed partial class FileList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.FileList; public string ReturnCode { get; set; } @@ -5507,13 +5662,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "path": Path = Ts3String.Unescape(value); break; - case "name": Name = Ts3String.Unescape(value); break; - case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = oval; } break; - case "datetime": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) DateTime = Util.UnixTimeStart.AddSeconds(oval); } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "path": Path = (str)TsString.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = (u64)oval; } break; + case "datetime": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) DateTime = Tools.FromUnix(oval); } break; case "type": IsFile = value.Length > 0 && value[0] != '0'; break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -5538,7 +5693,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FileListFinished : INotification + public sealed partial class FileListFinished : INotification { public NotificationType NotifyType { get; } = NotificationType.FileListFinished; @@ -5551,8 +5706,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "path": Path = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "path": Path = (str)TsString.Unescape(value); break; } @@ -5574,7 +5729,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FileTransfer : INotification, IResponse + public sealed partial class FileTransfer : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.FileTransfer; public string ReturnCode { get; set; } @@ -5597,19 +5752,19 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "path": Path = Ts3String.Unescape(value); break; - case "name": Name = Ts3String.Unescape(value); break; - case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = oval; } break; - case "sizedone": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) SizeDone = oval; } break; - case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = oval; } break; - case "serverftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ServerFileTransferId = oval; } break; - case "sender": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Sender = oval; } break; - case "status": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Status = oval; } break; - case "current_speed": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) CurrentSpeed = oval; } break; - case "average_speed": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) AverageSpeed = oval; } break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "path": Path = (str)TsString.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = (u64)oval; } break; + case "sizedone": { if(Utf8Parser.TryParse(value, out i64 oval, out _)) SizeDone = (i64)oval; } break; + case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = (u16)oval; } break; + case "serverftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ServerFileTransferId = (u16)oval; } break; + case "sender": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Sender = (u64)oval; } break; + case "status": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Status = (i32)oval; } break; + case "current_speed": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) CurrentSpeed = (f32)oval; } break; + case "average_speed": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) AverageSpeed = (f32)oval; } break; case "runtime": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Runtime = TimeSpan.FromSeconds(oval); } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -5640,7 +5795,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FileTransferStatus : INotification + public sealed partial class FileTransferStatus : INotification { public NotificationType NotifyType { get; } = NotificationType.FileTransferStatus; @@ -5655,10 +5810,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = oval; } break; + case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = (u16)oval; } break; case "status": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Status = (Ts3ErrorCode)oval; } break; - case "msg": Message = Ts3String.Unescape(value); break; - case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = oval; } break; + case "msg": Message = (str)TsString.Unescape(value); break; + case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = (u64)oval; } break; } @@ -5682,7 +5837,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FileUpload : INotification, IResponse + public sealed partial class FileUpload : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.FileUpload; public string ReturnCode { get; set; } @@ -5700,14 +5855,14 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = oval; } break; - case "serverftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ServerFileTransferId = oval; } break; - case "ftkey": FileTransferKey = Ts3String.Unescape(value); break; - case "port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) Port = oval; } break; - case "seekpos": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) SeekPosition = oval; } break; - case "proto": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Protocol = oval; } break; - case "ip": Ip = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = (u16)oval; } break; + case "serverftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ServerFileTransferId = (u16)oval; } break; + case "ftkey": FileTransferKey = (str)TsString.Unescape(value); break; + case "port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) Port = (u16)oval; } break; + case "seekpos": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) SeekPosition = (u64)oval; } break; + case "proto": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Protocol = (u8)oval; } break; + case "ip": Ip = (IpAddr)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -5733,7 +5888,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FtCreateDir : INotification + public sealed partial class FtCreateDir : INotification { public NotificationType NotifyType { get; } = NotificationType.FtCreateDir; @@ -5747,9 +5902,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpw": ChannelPassword = Ts3String.Unescape(value); break; - case "dirname": DirectoryName = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpw": ChannelPassword = (str)TsString.Unescape(value); break; + case "dirname": DirectoryName = (str)TsString.Unescape(value); break; } @@ -5772,7 +5927,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FtDeleteFile : INotification + public sealed partial class FtDeleteFile : INotification { public NotificationType NotifyType { get; } = NotificationType.FtDeleteFile; @@ -5786,9 +5941,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpw": ChannelPassword = Ts3String.Unescape(value); break; - case "name": Name = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpw": ChannelPassword = (str)TsString.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; } @@ -5811,7 +5966,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FtFileInfoRequest : INotification + public sealed partial class FtFileInfoRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.FtFileInfoRequest; @@ -5825,9 +5980,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpw": ChannelPassword = Ts3String.Unescape(value); break; - case "name": Name = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpw": ChannelPassword = (str)TsString.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; } @@ -5850,7 +6005,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FtFileListRequest : INotification + public sealed partial class FtFileListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.FtFileListRequest; @@ -5864,9 +6019,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpw": ChannelPassword = Ts3String.Unescape(value); break; - case "path": Path = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpw": ChannelPassword = (str)TsString.Unescape(value); break; + case "path": Path = (str)TsString.Unescape(value); break; } @@ -5889,7 +6044,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FtInitDownload : INotification + public sealed partial class FtInitDownload : INotification { public NotificationType NotifyType { get; } = NotificationType.FtInitDownload; @@ -5906,12 +6061,12 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpw": ChannelPassword = Ts3String.Unescape(value); break; - case "seekpos": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) SeekPosition = oval; } break; - case "proto": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Protocol = oval; } break; + case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = (u16)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpw": ChannelPassword = (str)TsString.Unescape(value); break; + case "seekpos": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) SeekPosition = (u64)oval; } break; + case "proto": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Protocol = (u8)oval; } break; } @@ -5937,7 +6092,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FtInitUpload : INotification + public sealed partial class FtInitUpload : INotification { public NotificationType NotifyType { get; } = NotificationType.FtInitUpload; @@ -5956,14 +6111,14 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpw": ChannelPassword = Ts3String.Unescape(value); break; - case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = oval; } break; + case "clientftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientFileTransferId = (u16)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpw": ChannelPassword = (str)TsString.Unescape(value); break; + case "size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Size = (u64)oval; } break; case "overwrite": Overwrite = value.Length > 0 && value[0] != '0'; break; case "resume": Resume = value.Length > 0 && value[0] != '0'; break; - case "proto": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Protocol = oval; } break; + case "proto": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) Protocol = (u8)oval; } break; } @@ -5991,7 +6146,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FtList : INotification + public sealed partial class FtList : INotification { public NotificationType NotifyType { get; } = NotificationType.FtList; @@ -6006,7 +6161,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FtRenameFile : INotification + public sealed partial class FtRenameFile : INotification { public NotificationType NotifyType { get; } = NotificationType.FtRenameFile; @@ -6023,12 +6178,12 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cpw": ChannelPassword = Ts3String.Unescape(value); break; - case "tcid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) TargetChannelId = oval; } break; - case "tcpw": TargetChannelPassword = Ts3String.Unescape(value); break; - case "oldname": OldName = Ts3String.Unescape(value); break; - case "newname": NewName = Ts3String.Unescape(value); break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cpw": ChannelPassword = (str)TsString.Unescape(value); break; + case "tcid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetChannelId = (ChannelId)oval; } break; + case "tcpw": TargetChannelPassword = (str)TsString.Unescape(value); break; + case "oldname": OldName = (str)TsString.Unescape(value); break; + case "newname": NewName = (str)TsString.Unescape(value); break; } @@ -6054,7 +6209,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class FtStop : INotification + public sealed partial class FtStop : INotification { public NotificationType NotifyType { get; } = NotificationType.FtStop; @@ -6067,7 +6222,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "serverftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ServerFileTransferId = oval; } break; + case "serverftfid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ServerFileTransferId = (u16)oval; } break; case "delete": Delete = value.Length > 0 && value[0] != '0'; break; } @@ -6090,7 +6245,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class GlobalMessage : INotification + public sealed partial class GlobalMessage : INotification { public NotificationType NotifyType { get; } = NotificationType.GlobalMessage; @@ -6102,7 +6257,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "msg": Message = Ts3String.Unescape(value); break; + case "msg": Message = (str)TsString.Unescape(value); break; } @@ -6123,7 +6278,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class HostInfoRequest : INotification + public sealed partial class HostInfoRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.HostInfoRequest; @@ -6138,7 +6293,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class InitIvExpand : INotification + public sealed partial class InitIvExpand : INotification { public NotificationType NotifyType { get; } = NotificationType.InitIvExpand; @@ -6152,9 +6307,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "alpha": Alpha = Ts3String.Unescape(value); break; - case "beta": Beta = Ts3String.Unescape(value); break; - case "omega": Omega = Ts3String.Unescape(value); break; + case "alpha": Alpha = (str)TsString.Unescape(value); break; + case "beta": Beta = (str)TsString.Unescape(value); break; + case "omega": Omega = (str)TsString.Unescape(value); break; } @@ -6177,7 +6332,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class InitIvExpand2 : INotification + public sealed partial class InitIvExpand2 : INotification { public NotificationType NotifyType { get; } = NotificationType.InitIvExpand2; @@ -6194,12 +6349,12 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "l": License = Ts3String.Unescape(value); break; - case "beta": Beta = Ts3String.Unescape(value); break; - case "omega": Omega = Ts3String.Unescape(value); break; + case "l": License = (str)TsString.Unescape(value); break; + case "beta": Beta = (str)TsString.Unescape(value); break; + case "omega": Omega = (str)TsString.Unescape(value); break; case "ot": Ot = value.Length > 0 && value[0] != '0'; break; - case "proof": Proof = Ts3String.Unescape(value); break; - case "tvd": Tvd = Ts3String.Unescape(value); break; + case "proof": Proof = (str)TsString.Unescape(value); break; + case "tvd": Tvd = (str)TsString.Unescape(value); break; } @@ -6225,7 +6380,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class InitServer : INotification + public sealed partial class InitServer : INotification { public NotificationType NotifyType { get; } = NotificationType.InitServer; @@ -6267,34 +6422,34 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "virtualserver_welcomemessage": WelcomeMessage = Ts3String.Unescape(value); break; - case "virtualserver_platform": ServerPlatform = Ts3String.Unescape(value); break; - case "virtualserver_version": ServerVersion = Ts3String.Unescape(value); break; - case "virtualserver_maxclients": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) MaxClients = oval; } break; - case "virtualserver_created": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ServerCreated = Util.UnixTimeStart.AddSeconds(oval); } break; - case "virtualserver_hostmessage": Hostmessage = Ts3String.Unescape(value); break; + case "virtualserver_welcomemessage": WelcomeMessage = (str)TsString.Unescape(value); break; + case "virtualserver_platform": ServerPlatform = (str)TsString.Unescape(value); break; + case "virtualserver_version": ServerVersion = (str)TsString.Unescape(value); break; + case "virtualserver_maxclients": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) MaxClients = (u16)oval; } break; + case "virtualserver_created": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerCreated = Tools.FromUnix(oval); } break; + case "virtualserver_hostmessage": Hostmessage = (str)TsString.Unescape(value); break; case "virtualserver_hostmessage_mode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) HostmessageMode = (HostMessageMode)oval; } break; - case "virtualserver_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) VirtualServerId = oval; } break; - case "virtualserver_ip": { if(value.Length == 0) ServerIp = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerIp = new IpAddr[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { ServerIp[i] = Ts3String.Unescape(ss.Trim(value)); if (i < cnt) value = ss.Next(value); } } } break; + case "virtualserver_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) VirtualServerId = (u64)oval; } break; + case "virtualserver_ip": { if(value.Length == 0) ServerIp = Array.Empty(); else { var ss = new SpanSplitter(); ss.First(value, (byte)','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerIp = new IpAddr[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { ServerIp[i] = (IpAddr)TsString.Unescape(ss.Trim(value)); if (i < cnt) value = ss.Next(value); } } } break; case "virtualserver_ask_for_privilegekey": AskForPrivilegekey = value.Length > 0 && value[0] != '0'; break; - case "acn": ClientName = Ts3String.Unescape(value); break; - case "aclid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "pv": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ProtocolVersion = oval; } break; + case "acn": ClientName = (str)TsString.Unescape(value); break; + case "aclid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "pv": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ProtocolVersion = (u16)oval; } break; case "lt": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) LicenseType = (LicenseType)oval; } break; - case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = oval; } break; - case "client_needed_serverquery_view_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededServerqueryViewPower = oval; } break; - case "virtualserver_name": Name = Ts3String.Unescape(value); break; + case "client_talk_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TalkPower = (i32)oval; } break; + case "client_needed_serverquery_view_power": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededServerqueryViewPower = (i32)oval; } break; + case "virtualserver_name": Name = (str)TsString.Unescape(value); break; case "virtualserver_codec_encryption_mode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecEncryptionMode = (CodecEncryptionMode)oval; } break; - case "virtualserver_default_server_group": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) DefaultServerGroup = oval; } break; - case "virtualserver_default_channel_group": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) DefaultChannelGroup = oval; } break; - case "virtualserver_hostbanner_url": HostbannerUrl = Ts3String.Unescape(value); break; - case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = Ts3String.Unescape(value); break; + case "virtualserver_default_server_group": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DefaultServerGroup = (ServerGroupId)oval; } break; + case "virtualserver_default_channel_group": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DefaultChannelGroup = (ChannelGroupId)oval; } break; + case "virtualserver_hostbanner_url": HostbannerUrl = (str)TsString.Unescape(value); break; + case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = (str)TsString.Unescape(value); break; case "virtualserver_hostbanner_gfx_interval": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) HostbannerGfxInterval = TimeSpan.FromSeconds(oval); } break; - case "virtualserver_priority_speaker_dimm_modificator": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PrioritySpeakerDimmModificator = oval; } break; - case "virtualserver_hostbutton_tooltip": HostbuttonTooltip = Ts3String.Unescape(value); break; - case "virtualserver_hostbutton_url": HostbuttonUrl = Ts3String.Unescape(value); break; - case "virtualserver_hostbutton_gfx_url": HostbuttonGfxUrl = Ts3String.Unescape(value); break; - case "virtualserver_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "virtualserver_priority_speaker_dimm_modificator": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PrioritySpeakerDimmModificator = (f32)oval; } break; + case "virtualserver_hostbutton_tooltip": HostbuttonTooltip = (str)TsString.Unescape(value); break; + case "virtualserver_hostbutton_url": HostbuttonUrl = (str)TsString.Unescape(value); break; + case "virtualserver_hostbutton_gfx_url": HostbuttonGfxUrl = (str)TsString.Unescape(value); break; + case "virtualserver_name_phonetic": PhoneticName = (str)TsString.Unescape(value); break; case "virtualserver_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; case "virtualserver_hostbanner_mode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) HostbannerMode = (HostBannerMode)oval; } break; case "virtualserver_channel_temp_delete_delay_default": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) TempChannelDefaultDeleteDelay = TimeSpan.FromSeconds(oval); } break; @@ -6348,7 +6503,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class InstanceEdit : INotification + public sealed partial class InstanceEdit : INotification { public NotificationType NotifyType { get; } = NotificationType.InstanceEdit; @@ -6363,7 +6518,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class InstanceInfo : INotification + public sealed partial class InstanceInfo : INotification { public NotificationType NotifyType { get; } = NotificationType.InstanceInfo; @@ -6378,7 +6533,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class LogAdd : INotification + public sealed partial class LogAdd : INotification { public NotificationType NotifyType { get; } = NotificationType.LogAdd; @@ -6392,7 +6547,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "loglevel": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) LogLevel = (LogLevel)oval; } break; - case "logmsg": LogMessage = Ts3String.Unescape(value); break; + case "logmsg": LogMessage = (str)TsString.Unescape(value); break; } @@ -6414,7 +6569,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class Login : INotification + public sealed partial class Login : INotification { public NotificationType NotifyType { get; } = NotificationType.Login; @@ -6427,8 +6582,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "client_login_name": LoginName = Ts3String.Unescape(value); break; - case "client_login_password": LoginPassword = Ts3String.Unescape(value); break; + case "client_login_name": LoginName = (str)TsString.Unescape(value); break; + case "client_login_password": LoginPassword = (str)TsString.Unescape(value); break; } @@ -6450,7 +6605,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class Logout : INotification + public sealed partial class Logout : INotification { public NotificationType NotifyType { get; } = NotificationType.Logout; @@ -6465,7 +6620,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class LogView : INotification + public sealed partial class LogView : INotification { public NotificationType NotifyType { get; } = NotificationType.LogView; @@ -6480,10 +6635,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "lines": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Lines = oval; } break; + case "lines": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Lines = (u32)oval; } break; case "reverse": Reverse = value.Length > 0 && value[0] != '0'; break; case "instance": InstanceLog = value.Length > 0 && value[0] != '0'; break; - case "begin_pos": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Offset = oval; } break; + case "begin_pos": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Offset = (u64)oval; } break; } @@ -6507,7 +6662,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class OfflineMessage : INotification, IResponse + public sealed partial class OfflineMessage : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.OfflineMessage; public string ReturnCode { get; set; } @@ -6523,12 +6678,12 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = oval; } break; - case "cluid": ClientUid = Ts3String.Unescape(value); break; - case "subject": Subject = Ts3String.Unescape(value); break; - case "message": Message = Ts3String.Unescape(value); break; - case "timestamp": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Timestamp = Util.UnixTimeStart.AddSeconds(oval); } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = (u32)oval; } break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; + case "subject": Subject = (str)TsString.Unescape(value); break; + case "message": Message = (str)TsString.Unescape(value); break; + case "timestamp": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Timestamp = Tools.FromUnix(oval); } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -6552,7 +6707,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class OfflineMessageAdd : INotification + public sealed partial class OfflineMessageAdd : INotification { public NotificationType NotifyType { get; } = NotificationType.OfflineMessageAdd; @@ -6566,9 +6721,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cluid": ClientUid = Ts3String.Unescape(value); break; - case "subject": Subject = Ts3String.Unescape(value); break; - case "message": Message = Ts3String.Unescape(value); break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; + case "subject": Subject = (str)TsString.Unescape(value); break; + case "message": Message = (str)TsString.Unescape(value); break; } @@ -6591,7 +6746,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class OfflineMessageDel : INotification + public sealed partial class OfflineMessageDel : INotification { public NotificationType NotifyType { get; } = NotificationType.OfflineMessageDel; @@ -6603,7 +6758,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = oval; } break; + case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = (u32)oval; } break; } @@ -6624,7 +6779,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class OfflineMessageGet : INotification + public sealed partial class OfflineMessageGet : INotification { public NotificationType NotifyType { get; } = NotificationType.OfflineMessageGet; @@ -6636,7 +6791,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = oval; } break; + case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = (u32)oval; } break; } @@ -6657,7 +6812,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class OfflineMessageList : INotification, IResponse + public sealed partial class OfflineMessageList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.OfflineMessageList; public string ReturnCode { get; set; } @@ -6673,12 +6828,12 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = oval; } break; - case "cluid": ClientUid = Ts3String.Unescape(value); break; - case "subject": Subject = Ts3String.Unescape(value); break; - case "timestamp": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Timestamp = Util.UnixTimeStart.AddSeconds(oval); } break; + case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = (u32)oval; } break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; + case "subject": Subject = (str)TsString.Unescape(value); break; + case "timestamp": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Timestamp = Tools.FromUnix(oval); } break; case "flag_read": IsRead = value.Length > 0 && value[0] != '0'; break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -6702,7 +6857,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class OfflineMessageListRequest : INotification + public sealed partial class OfflineMessageListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.OfflineMessageListRequest; @@ -6717,7 +6872,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class OfflineMessageUpdateFlag : INotification + public sealed partial class OfflineMessageUpdateFlag : INotification { public NotificationType NotifyType { get; } = NotificationType.OfflineMessageUpdateFlag; @@ -6730,7 +6885,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = oval; } break; + case "msgid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MessageId = (u32)oval; } break; case "flag": IsRead = value.Length > 0 && value[0] != '0'; break; } @@ -6753,7 +6908,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PermFind : INotification, IResponse + public sealed partial class PermFind : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.PermFind; public string ReturnCode { get; set; } @@ -6769,10 +6924,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "t": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionType = (PermissionType)oval; } break; - case "id1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Id1 = oval; } break; - case "id2": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Id2 = oval; } break; + case "id1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Id1 = (u64)oval; } break; + case "id2": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Id2 = (u64)oval; } break; case "p": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -6795,7 +6950,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PermFindRequest : INotification + public sealed partial class PermFindRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.PermFindRequest; @@ -6809,7 +6964,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; } @@ -6831,7 +6986,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PermIdByNameRequest : INotification + public sealed partial class PermIdByNameRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.PermIdByNameRequest; @@ -6843,7 +6998,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; } @@ -6864,7 +7019,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PermList : INotification, IResponse + public sealed partial class PermList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.PermList; public string ReturnCode { get; set; } @@ -6881,9 +7036,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) case "group_id_end": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) GroupIdEnd = ser.PermissionTransform.GetName(oval); } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permname": PermissionName = Ts3String.Unescape(value); break; - case "permdesc": PermissionDescription = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "permname": PermissionName = (str)TsString.Unescape(value); break; + case "permdesc": PermissionDescription = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -6906,7 +7061,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PermListRequest : INotification + public sealed partial class PermListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.PermListRequest; @@ -6921,7 +7076,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PermOverview : INotification, IResponse + public sealed partial class PermOverview : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.PermOverview; public string ReturnCode { get; set; } @@ -6941,16 +7096,16 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; case "t": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionType = (PermissionType)oval; } break; - case "id1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Id1 = oval; } break; - case "id2": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Id2 = oval; } break; + case "id1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Id1 = (u64)oval; } break; + case "id2": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Id2 = (u64)oval; } break; case "p": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "v": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "v": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; case "n": PermissionNegated = value.Length > 0 && value[0] != '0'; break; case "s": PermissionSkip = value.Length > 0 && value[0] != '0'; break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -6978,7 +7133,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PermOverviewRequest : INotification + public sealed partial class PermOverviewRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.PermOverviewRequest; @@ -6993,10 +7148,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; } @@ -7020,7 +7175,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PermRequest : INotification + public sealed partial class PermRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.PermRequest; @@ -7053,7 +7208,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PermReset : INotification + public sealed partial class PermReset : INotification { public NotificationType NotifyType { get; } = NotificationType.PermReset; @@ -7068,7 +7223,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PluginCommand : INotification + public sealed partial class PluginCommand : INotification { public NotificationType NotifyType { get; } = NotificationType.PluginCommand; @@ -7081,8 +7236,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "name": Name = Ts3String.Unescape(value); break; - case "data": Data = Ts3String.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "data": Data = (str)TsString.Unescape(value); break; } @@ -7104,7 +7259,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PluginCommandRequest : INotification + public sealed partial class PluginCommandRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.PluginCommandRequest; @@ -7118,8 +7273,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "name": Name = Ts3String.Unescape(value); break; - case "data": Data = Ts3String.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "data": Data = (str)TsString.Unescape(value); break; case "targetmode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Target = (PluginTargetMode)oval; } break; } @@ -7143,7 +7298,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PrivilegeKeyAddRequest : INotification + public sealed partial class PrivilegeKeyAddRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.PrivilegeKeyAddRequest; @@ -7160,10 +7315,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "tokentype": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TokenType = (TokenType)oval; } break; - case "tokenid1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TokenId1 = oval; } break; - case "tokenid2": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) TokenId2 = oval; } break; - case "tokendescription": TokenDescription = Ts3String.Unescape(value); break; - case "tokencustomset": TokenCustomSet = Ts3String.Unescape(value); break; + case "tokenid1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TokenId1 = (u64)oval; } break; + case "tokenid2": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TokenId2 = (ChannelId)oval; } break; + case "tokendescription": TokenDescription = (str)TsString.Unescape(value); break; + case "tokencustomset": TokenCustomSet = (str)TsString.Unescape(value); break; } @@ -7188,7 +7343,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PrivilegeKeyDelete : INotification + public sealed partial class PrivilegeKeyDelete : INotification { public NotificationType NotifyType { get; } = NotificationType.PrivilegeKeyDelete; @@ -7200,7 +7355,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "token": Token = Ts3String.Unescape(value); break; + case "token": Token = (str)TsString.Unescape(value); break; } @@ -7221,7 +7376,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PrivilegeKeyListRequest : INotification + public sealed partial class PrivilegeKeyListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.PrivilegeKeyListRequest; @@ -7236,7 +7391,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class PrivilegeKeyUse : INotification + public sealed partial class PrivilegeKeyUse : INotification { public NotificationType NotifyType { get; } = NotificationType.PrivilegeKeyUse; @@ -7248,7 +7403,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "token": Token = Ts3String.Unescape(value); break; + case "token": Token = (str)TsString.Unescape(value); break; } @@ -7269,7 +7424,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class Quit : INotification + public sealed partial class Quit : INotification { public NotificationType NotifyType { get; } = NotificationType.Quit; @@ -7284,7 +7439,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class SendTextMessage : INotification + public sealed partial class SendTextMessage : INotification { public NotificationType NotifyType { get; } = NotificationType.SendTextMessage; @@ -7299,8 +7454,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "targetmode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Target = (TextMessageTargetMode)oval; } break; - case "target": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) TargetClientId = oval; } break; - case "msg": Message = Ts3String.Unescape(value); break; + case "target": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) TargetClientId = (ClientId)oval; } break; + case "msg": Message = (str)TsString.Unescape(value); break; } @@ -7323,7 +7478,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerConnectionInfo : INotification, IResponse + public sealed partial class ServerConnectionInfo : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ServerConnectionInfo; public string ReturnCode { get; set; } @@ -7349,22 +7504,22 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "connection_filetransfer_bandwidth_sent": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthSent = oval; } break; - case "connection_filetransfer_bandwidth_received": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthReceived = oval; } break; - case "connection_filetransfer_bytes_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBytesSentTotal = oval; } break; - case "connection_filetransfer_bytes_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBytesReceivedTotal = oval; } break; - case "connection_packets_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentTotal = oval; } break; - case "connection_bytes_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentTotal = oval; } break; - case "connection_packets_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedTotal = oval; } break; - case "connection_bytes_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedTotal = oval; } break; - case "connection_bandwidth_sent_last_second_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondTotal = oval; } break; - case "connection_bandwidth_sent_last_minute_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteTotal = oval; } break; - case "connection_bandwidth_received_last_second_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondTotal = oval; } break; - case "connection_bandwidth_received_last_minute_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteTotal = oval; } break; + case "connection_filetransfer_bandwidth_sent": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthSent = (u64)oval; } break; + case "connection_filetransfer_bandwidth_received": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBandwidthReceived = (u64)oval; } break; + case "connection_filetransfer_bytes_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBytesSentTotal = (u64)oval; } break; + case "connection_filetransfer_bytes_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FiletransferBytesReceivedTotal = (u64)oval; } break; + case "connection_packets_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsSentTotal = (u64)oval; } break; + case "connection_bytes_sent_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesSentTotal = (u64)oval; } break; + case "connection_packets_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) PacketsReceivedTotal = (u64)oval; } break; + case "connection_bytes_received_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesReceivedTotal = (u64)oval; } break; + case "connection_bandwidth_sent_last_second_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastSecondTotal = (u64)oval; } break; + case "connection_bandwidth_sent_last_minute_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthSentLastMinuteTotal = (u64)oval; } break; + case "connection_bandwidth_received_last_second_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastSecondTotal = (u64)oval; } break; + case "connection_bandwidth_received_last_minute_total": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BandwidthReceivedLastMinuteTotal = (u64)oval; } break; case "connection_connected_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ConnectedTime = TimeSpan.FromMilliseconds(oval); } break; - case "connection_packetloss_total": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotal = oval; } break; + case "connection_packetloss_total": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotal = (f32)oval; } break; case "connection_ping": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Ping = TimeSpan.FromMilliseconds(oval); } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -7398,7 +7553,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerConnectionInfoRequest : INotification + public sealed partial class ServerConnectionInfoRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerConnectionInfoRequest; @@ -7413,7 +7568,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerCreate : INotification + public sealed partial class ServerCreate : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerCreate; @@ -7425,7 +7580,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "virtualserver_name": Name = Ts3String.Unescape(value); break; + case "virtualserver_name": Name = (str)TsString.Unescape(value); break; } @@ -7446,70 +7601,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerData : IResponse - { - - public string ReturnCode { get; set; } - - public u16 ClientsOnline { get; set; } - public u16 QueriesOnline { get; set; } - public u16 MaxClients { get; set; } - public DurationSeconds Uptime { get; set; } - public bool Autostart { get; set; } - public str MachineId { get; set; } - public str Name { get; set; } - public u64 VirtualServerId { get; set; } - public Uid VirtualServerUid { get; set; } - public u16 VirtualServerPort { get; set; } - public str VirtualServerStatus { get; set; } - - public void SetField(string name, ReadOnlySpan value, Deserializer ser) - { - switch(name) - { - - case "virtualserver_clientsonline": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientsOnline = oval; } break; - case "virtualserver_queryclientsonline": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) QueriesOnline = oval; } break; - case "virtualserver_maxclients": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) MaxClients = oval; } break; - case "virtualserver_uptime": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Uptime = TimeSpan.FromSeconds(oval); } break; - case "virtualserver_autostart": Autostart = value.Length > 0 && value[0] != '0'; break; - case "virtualserver_machine_id": MachineId = Ts3String.Unescape(value); break; - case "virtualserver_name": Name = Ts3String.Unescape(value); break; - case "virtualserver_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) VirtualServerId = oval; } break; - case "virtualserver_unique_identifier": VirtualServerUid = Ts3String.Unescape(value); break; - case "virtualserver_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) VirtualServerPort = oval; } break; - case "virtualserver_status": VirtualServerStatus = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; - } - - } - - public void Expand(IMessage[] to, IEnumerable flds) - { - var toc = (ServerData[])to; - foreach (var fld in flds) - { - switch(fld) - { - - case "virtualserver_clientsonline": foreach(var toi in toc) { toi.ClientsOnline = ClientsOnline; } break; - case "virtualserver_queryclientsonline": foreach(var toi in toc) { toi.QueriesOnline = QueriesOnline; } break; - case "virtualserver_maxclients": foreach(var toi in toc) { toi.MaxClients = MaxClients; } break; - case "virtualserver_uptime": foreach(var toi in toc) { toi.Uptime = Uptime; } break; - case "virtualserver_autostart": foreach(var toi in toc) { toi.Autostart = Autostart; } break; - case "virtualserver_machine_id": foreach(var toi in toc) { toi.MachineId = MachineId; } break; - case "virtualserver_name": foreach(var toi in toc) { toi.Name = Name; } break; - case "virtualserver_id": foreach(var toi in toc) { toi.VirtualServerId = VirtualServerId; } break; - case "virtualserver_unique_identifier": foreach(var toi in toc) { toi.VirtualServerUid = VirtualServerUid; } break; - case "virtualserver_port": foreach(var toi in toc) { toi.VirtualServerPort = VirtualServerPort; } break; - case "virtualserver_status": foreach(var toi in toc) { toi.VirtualServerStatus = VirtualServerStatus; } break; - } - } - - } - } - - public sealed class ServerDelete : INotification + public sealed partial class ServerDelete : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerDelete; @@ -7521,7 +7613,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = oval; } break; + case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = (u32)oval; } break; } @@ -7542,7 +7634,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerEdit : INotification + public sealed partial class ServerEdit : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerEdit; @@ -7596,41 +7688,41 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = oval; } break; - case "virtualserver_name": Name = Ts3String.Unescape(value); break; - case "virtualserver_welcomemessage": WelcomeMessage = Ts3String.Unescape(value); break; - case "virtualserver_maxclients": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) MaxClients = oval; } break; - case "virtualserver_password": ServerPassword = Ts3String.Unescape(value); break; - case "virtualserver_hostmessage": Hostmessage = Ts3String.Unescape(value); break; + case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = (u32)oval; } break; + case "virtualserver_name": Name = (str)TsString.Unescape(value); break; + case "virtualserver_welcomemessage": WelcomeMessage = (str)TsString.Unescape(value); break; + case "virtualserver_maxclients": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) MaxClients = (u16)oval; } break; + case "virtualserver_password": ServerPassword = (str)TsString.Unescape(value); break; + case "virtualserver_hostmessage": Hostmessage = (str)TsString.Unescape(value); break; case "virtualserver_hostmessage_mode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) HostmessageMode = (HostMessageMode)oval; } break; - case "virtualserver_hostbanner_url": HostbannerUrl = Ts3String.Unescape(value); break; - case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = Ts3String.Unescape(value); break; + case "virtualserver_hostbanner_url": HostbannerUrl = (str)TsString.Unescape(value); break; + case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = (str)TsString.Unescape(value); break; case "virtualserver_hostbanner_gfx_interval": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) HostbannerGfxInterval = TimeSpan.FromSeconds(oval); } break; - case "virtualserver_hostbutton_tooltip": HostbuttonTooltip = Ts3String.Unescape(value); break; - case "virtualserver_hostbutton_url": HostbuttonUrl = Ts3String.Unescape(value); break; - case "virtualserver_hostbutton_gfx_url": HostbuttonGfxUrl = Ts3String.Unescape(value); break; + case "virtualserver_hostbutton_tooltip": HostbuttonTooltip = (str)TsString.Unescape(value); break; + case "virtualserver_hostbutton_url": HostbuttonUrl = (str)TsString.Unescape(value); break; + case "virtualserver_hostbutton_gfx_url": HostbuttonGfxUrl = (str)TsString.Unescape(value); break; case "virtualserver_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; - case "virtualserver_reserved_slots": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ReservedSlots = oval; } break; + case "virtualserver_reserved_slots": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ReservedSlots = (u16)oval; } break; case "virtualserver_hostbanner_mode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) HostbannerMode = (HostBannerMode)oval; } break; - case "virtualserver_nickname": Nickname = Ts3String.Unescape(value); break; - case "virtualserver_max_download_total_bandwidth": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) MaxDownloadTotalBandwidth = oval; } break; - case "virtualserver_max_upload_total_bandwidth": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) MaxUploadTotalBandwidth = oval; } break; - case "virtualserver_download_quota": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DownloadQuota = oval; } break; - case "virtualserver_upload_quota": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) UploadQuota = oval; } break; - case "virtualserver_antiflood_points_tick_reduce": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsTickReduce = oval; } break; - case "virtualserver_antiflood_points_needed_command_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToCommandBlock = oval; } break; - case "virtualserver_antiflood_points_needed_ip_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToIpBlock = oval; } break; + case "virtualserver_nickname": Nickname = (str)TsString.Unescape(value); break; + case "virtualserver_max_download_total_bandwidth": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) MaxDownloadTotalBandwidth = (u64)oval; } break; + case "virtualserver_max_upload_total_bandwidth": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) MaxUploadTotalBandwidth = (u64)oval; } break; + case "virtualserver_download_quota": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DownloadQuota = (u64)oval; } break; + case "virtualserver_upload_quota": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) UploadQuota = (u64)oval; } break; + case "virtualserver_antiflood_points_tick_reduce": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsTickReduce = (u32)oval; } break; + case "virtualserver_antiflood_points_needed_command_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToCommandBlock = (u32)oval; } break; + case "virtualserver_antiflood_points_needed_ip_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToIpBlock = (u32)oval; } break; case "virtualserver_codec_encryption_mode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecEncryptionMode = (CodecEncryptionMode)oval; } break; - case "virtualserver_needed_identity_security_level": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) IdentitySecurityLevel = oval; } break; - case "virtualserver_default_server_group": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) DefaultServerGroup = oval; } break; - case "virtualserver_default_channel_group": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) DefaultChannelGroup = oval; } break; - case "virtualserver_default_channel_admin_group": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) DefaultChannelAdminGroup = oval; } break; - case "virtualserver_complain_autoban_count": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ComplainAutobanCount = oval; } break; + case "virtualserver_needed_identity_security_level": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) IdentitySecurityLevel = (u8)oval; } break; + case "virtualserver_default_server_group": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DefaultServerGroup = (ServerGroupId)oval; } break; + case "virtualserver_default_channel_group": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DefaultChannelGroup = (ChannelGroupId)oval; } break; + case "virtualserver_default_channel_admin_group": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DefaultChannelAdminGroup = (ChannelGroupId)oval; } break; + case "virtualserver_complain_autoban_count": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ComplainAutobanCount = (u32)oval; } break; case "virtualserver_complain_autoban_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ComplainAutobanTime = TimeSpan.FromSeconds(oval); } break; case "virtualserver_complain_remove_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ComplainRemoveTime = TimeSpan.FromSeconds(oval); } break; - case "virtualserver_min_clients_in_channel_before_forced_silence": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinClientsInChannelBeforeForcedSilence = oval; } break; - case "virtualserver_priority_speaker_dimm_modificator": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PrioritySpeakerDimmModificator = oval; } break; - case "virtualserver_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "virtualserver_min_clients_in_channel_before_forced_silence": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinClientsInChannelBeforeForcedSilence = (u32)oval; } break; + case "virtualserver_priority_speaker_dimm_modificator": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PrioritySpeakerDimmModificator = (f32)oval; } break; + case "virtualserver_name_phonetic": PhoneticName = (str)TsString.Unescape(value); break; case "virtualserver_channel_temp_delete_delay_default": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) TempChannelDefaultDeleteDelay = TimeSpan.FromSeconds(oval); } break; case "virtualserver_weblist_enabled": WeblistEnabled = value.Length > 0 && value[0] != '0'; break; case "virtualserver_log_client": LogClient = value.Length > 0 && value[0] != '0'; break; @@ -7701,7 +7793,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerEdited : INotification + public sealed partial class ServerEdited : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerEdited; @@ -7732,23 +7824,23 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; case "reasonid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Reason = (Reason)oval; } break; - case "virtualserver_name": Name = Ts3String.Unescape(value); break; - case "virtualserver_nickname": Nickname = Ts3String.Unescape(value); break; + case "virtualserver_name": Name = (str)TsString.Unescape(value); break; + case "virtualserver_nickname": Nickname = (str)TsString.Unescape(value); break; case "virtualserver_codec_encryption_mode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) CodecEncryptionMode = (CodecEncryptionMode)oval; } break; - case "virtualserver_default_server_group": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) DefaultServerGroup = oval; } break; - case "virtualserver_default_channel_group": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) DefaultChannelGroup = oval; } break; - case "virtualserver_hostbanner_url": HostbannerUrl = Ts3String.Unescape(value); break; - case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = Ts3String.Unescape(value); break; + case "virtualserver_default_server_group": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DefaultServerGroup = (ServerGroupId)oval; } break; + case "virtualserver_default_channel_group": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DefaultChannelGroup = (ChannelGroupId)oval; } break; + case "virtualserver_hostbanner_url": HostbannerUrl = (str)TsString.Unescape(value); break; + case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = (str)TsString.Unescape(value); break; case "virtualserver_hostbanner_gfx_interval": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) HostbannerGfxInterval = TimeSpan.FromSeconds(oval); } break; - case "virtualserver_priority_speaker_dimm_modificator": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PrioritySpeakerDimmModificator = oval; } break; - case "virtualserver_hostbutton_tooltip": HostbuttonTooltip = Ts3String.Unescape(value); break; - case "virtualserver_hostbutton_url": HostbuttonUrl = Ts3String.Unescape(value); break; - case "virtualserver_hostbutton_gfx_url": HostbuttonGfxUrl = Ts3String.Unescape(value); break; - case "virtualserver_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "virtualserver_priority_speaker_dimm_modificator": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PrioritySpeakerDimmModificator = (f32)oval; } break; + case "virtualserver_hostbutton_tooltip": HostbuttonTooltip = (str)TsString.Unescape(value); break; + case "virtualserver_hostbutton_url": HostbuttonUrl = (str)TsString.Unescape(value); break; + case "virtualserver_hostbutton_gfx_url": HostbuttonGfxUrl = (str)TsString.Unescape(value); break; + case "virtualserver_name_phonetic": PhoneticName = (str)TsString.Unescape(value); break; case "virtualserver_icon_id": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; case "virtualserver_hostbanner_mode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) HostbannerMode = (HostBannerMode)oval; } break; case "virtualserver_channel_temp_delete_delay_default": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) TempChannelDefaultDeleteDelay = TimeSpan.FromSeconds(oval); } break; @@ -7791,7 +7883,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupAdd : INotification + public sealed partial class ServerGroupAdd : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupAdd; @@ -7804,7 +7896,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "name": Name = Ts3String.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; case "type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) GroupType = (GroupType)oval; } break; } @@ -7827,7 +7919,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupAddClient : INotification + public sealed partial class ServerGroupAddClient : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupAddClient; @@ -7840,8 +7932,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -7863,7 +7955,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupAddPerm : INotification + public sealed partial class ServerGroupAddPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupAddPerm; @@ -7880,10 +7972,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; case "permnegated": PermissionNegated = value.Length > 0 && value[0] != '0'; break; case "permskip": PermissionSkip = value.Length > 0 && value[0] != '0'; break; @@ -7911,7 +8003,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupAddResponse : IResponse + public sealed partial class ServerGroupAddResponse : IResponse { public string ReturnCode { get; set; } @@ -7923,8 +8015,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -7944,7 +8036,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupAutoAddPerm : INotification + public sealed partial class ServerGroupAutoAddPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupAutoAddPerm; @@ -7961,10 +8053,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgtype": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerGroupType = oval; } break; + case "sgtype": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerGroupType = (u32)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; case "permnegated": PermissionNegated = value.Length > 0 && value[0] != '0'; break; case "permskip": PermissionSkip = value.Length > 0 && value[0] != '0'; break; @@ -7992,7 +8084,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupAutoDelPerm : INotification + public sealed partial class ServerGroupAutoDelPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupAutoDelPerm; @@ -8006,9 +8098,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgtype": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerGroupType = oval; } break; + case "sgtype": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerGroupType = (u32)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; } @@ -8031,7 +8123,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupClientList : INotification, IResponse + public sealed partial class ServerGroupClientList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ServerGroupClientList; public string ReturnCode { get; set; } @@ -8046,11 +8138,11 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "client_nickname": Name = Ts3String.Unescape(value); break; - case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; + case "client_unique_identifier": Uid = (Uid)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -8073,7 +8165,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupClientListRequest : INotification + public sealed partial class ServerGroupClientListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupClientListRequest; @@ -8085,7 +8177,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; } @@ -8106,7 +8198,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupCopy : INotification + public sealed partial class ServerGroupCopy : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupCopy; @@ -8121,9 +8213,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "ssgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) SourceServerGroupId = oval; } break; - case "tsgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) TargetServerGroupId = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; + case "ssgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) SourceServerGroupId = (ServerGroupId)oval; } break; + case "tsgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetServerGroupId = (ServerGroupId)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; case "type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) GroupType = (GroupType)oval; } break; } @@ -8148,7 +8240,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupDel : INotification + public sealed partial class ServerGroupDel : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupDel; @@ -8161,7 +8253,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; case "force": Force = value.Length > 0 && value[0] != '0'; break; } @@ -8184,7 +8276,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupDelClient : INotification + public sealed partial class ServerGroupDelClient : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupDelClient; @@ -8197,8 +8289,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -8220,7 +8312,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupDelPerm : INotification + public sealed partial class ServerGroupDelPerm : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupDelPerm; @@ -8234,9 +8326,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; } @@ -8259,7 +8351,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupList : INotification, IResponse + public sealed partial class ServerGroupList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ServerGroupList; public string ReturnCode { get; set; } @@ -8280,17 +8372,17 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; case "type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) GroupType = (GroupType)oval; } break; case "iconid": { if(!value.IsEmpty && value[0] == (u8)'-') { if(Utf8Parser.TryParse(value, out i32 oval, out _)) IconId = oval; } else { if(Utf8Parser.TryParse(value, out u64 oval, out _)) IconId = unchecked((i32)oval); } } break; case "savedb": IsPermanent = value.Length > 0 && value[0] != '0'; break; - case "sortid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) SortId = oval; } break; + case "sortid": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) SortId = (i32)oval; } break; case "namemode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NamingMode = (GroupNamingMode)oval; } break; - case "n_modifyp": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededModifyPower = oval; } break; - case "n_member_addp": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededMemberAddPower = oval; } break; - case "n_member_remove_p": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededMemberRemovePower = oval; } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "n_modifyp": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededModifyPower = (i32)oval; } break; + case "n_member_addp": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededMemberAddPower = (i32)oval; } break; + case "n_member_remove_p": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) NeededMemberRemovePower = (i32)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -8319,7 +8411,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupListRequest : INotification + public sealed partial class ServerGroupListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupListRequest; @@ -8334,7 +8426,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupPermList : INotification, IResponse + public sealed partial class ServerGroupPermList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ServerGroupPermList; public string ReturnCode { get; set; } @@ -8351,13 +8443,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; case "permid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) PermissionId = ser.PermissionTransform.GetName(oval); } break; - case "permsid": PermissionNameId = Ts3String.Unescape(value); break; - case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = oval; } break; + case "permsid": PermissionNameId = (str)TsString.Unescape(value); break; + case "permvalue": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) PermissionValue = (i32)oval; } break; case "permnegated": PermissionNegated = value.Length > 0 && value[0] != '0'; break; case "permskip": PermissionSkip = value.Length > 0 && value[0] != '0'; break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -8382,7 +8474,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupPermListRequest : INotification + public sealed partial class ServerGroupPermListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupPermListRequest; @@ -8394,7 +8486,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; } @@ -8415,7 +8507,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupRename : INotification + public sealed partial class ServerGroupRename : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupRename; @@ -8428,8 +8520,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; - case "name": Name = Ts3String.Unescape(value); break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; + case "name": Name = (str)TsString.Unescape(value); break; } @@ -8451,7 +8543,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupsByClientId : INotification, IResponse + public sealed partial class ServerGroupsByClientId : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ServerGroupsByClientId; public string ReturnCode { get; set; } @@ -8465,10 +8557,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "name": Name = Ts3String.Unescape(value); break; - case "sgid": { if(Utf8Parser.TryParse(value, out ServerGroupId oval, out _)) ServerGroupId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "name": Name = (str)TsString.Unescape(value); break; + case "sgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ServerGroupId = (ServerGroupId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -8490,7 +8582,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerGroupsByClientIdRequest : INotification + public sealed partial class ServerGroupsByClientIdRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerGroupsByClientIdRequest; @@ -8502,7 +8594,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -8523,7 +8615,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerIdGetByPort : INotification + public sealed partial class ServerIdGetByPort : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerIdGetByPort; @@ -8535,7 +8627,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "virtualserver_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) VirtualServerPort = oval; } break; + case "virtualserver_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) VirtualServerPort = (u16)oval; } break; } @@ -8556,7 +8648,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerInfo : INotification + public sealed partial class ServerInfo : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerInfo; @@ -8571,9 +8663,9 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerList : INotification + public sealed partial class ServerListRequest : INotification { - public NotificationType NotifyType { get; } = NotificationType.ServerList; + public NotificationType NotifyType { get; } = NotificationType.ServerListRequest; @@ -8586,7 +8678,70 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerLog : INotification, IResponse + public sealed partial class ServerListResponse : IResponse + { + + public string ReturnCode { get; set; } + + public u64 VirtualServerId { get; set; } + public u16 VirtualServerPort { get; set; } + public str VirtualServerStatus { get; set; } + public u16? ClientsOnline { get; set; } + public u16? QueriesOnline { get; set; } + public u16? MaxClients { get; set; } + public DurationSeconds? Uptime { get; set; } + public bool? Autostart { get; set; } + public str MachineId { get; set; } + public str Name { get; set; } + public Uid VirtualServerUid { get; set; } + + public void SetField(string name, ReadOnlySpan value, Deserializer ser) + { + switch(name) + { + + case "virtualserver_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) VirtualServerId = (u64)oval; } break; + case "virtualserver_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) VirtualServerPort = (u16)oval; } break; + case "virtualserver_status": VirtualServerStatus = (str)TsString.Unescape(value); break; + case "virtualserver_clientsonline": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientsOnline = (u16)oval; } break; + case "virtualserver_queryclientsonline": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) QueriesOnline = (u16)oval; } break; + case "virtualserver_maxclients": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) MaxClients = (u16)oval; } break; + case "virtualserver_uptime": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Uptime = TimeSpan.FromSeconds(oval); } break; + case "virtualserver_autostart": Autostart = value.Length > 0 && value[0] != '0'; break; + case "virtualserver_machine_id": MachineId = (str)TsString.Unescape(value); break; + case "virtualserver_name": Name = (str)TsString.Unescape(value); break; + case "virtualserver_unique_identifier": VirtualServerUid = (Uid)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; + } + + } + + public void Expand(IMessage[] to, IEnumerable flds) + { + var toc = (ServerListResponse[])to; + foreach (var fld in flds) + { + switch(fld) + { + + case "virtualserver_id": foreach(var toi in toc) { toi.VirtualServerId = VirtualServerId; } break; + case "virtualserver_port": foreach(var toi in toc) { toi.VirtualServerPort = VirtualServerPort; } break; + case "virtualserver_status": foreach(var toi in toc) { toi.VirtualServerStatus = VirtualServerStatus; } break; + case "virtualserver_clientsonline": foreach(var toi in toc) { toi.ClientsOnline = ClientsOnline; } break; + case "virtualserver_queryclientsonline": foreach(var toi in toc) { toi.QueriesOnline = QueriesOnline; } break; + case "virtualserver_maxclients": foreach(var toi in toc) { toi.MaxClients = MaxClients; } break; + case "virtualserver_uptime": foreach(var toi in toc) { toi.Uptime = Uptime; } break; + case "virtualserver_autostart": foreach(var toi in toc) { toi.Autostart = Autostart; } break; + case "virtualserver_machine_id": foreach(var toi in toc) { toi.MachineId = MachineId; } break; + case "virtualserver_name": foreach(var toi in toc) { toi.Name = Name; } break; + case "virtualserver_unique_identifier": foreach(var toi in toc) { toi.VirtualServerUid = VirtualServerUid; } break; + } + } + + } + } + + public sealed partial class ServerLog : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.ServerLog; public string ReturnCode { get; set; } @@ -8600,10 +8755,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "last_pos": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) LastOffset = oval; } break; - case "file_size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FileSize = oval; } break; - case "l": License = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "last_pos": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) LastOffset = (u64)oval; } break; + case "file_size": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) FileSize = (u64)oval; } break; + case "l": License = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -8625,7 +8780,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerNotifyRegister : INotification + public sealed partial class ServerNotifyRegister : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerNotifyRegister; @@ -8638,8 +8793,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "event": EventType = Ts3String.Unescape(value); break; - case "id": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) Id = oval; } break; + case "event": EventType = (str)TsString.Unescape(value); break; + case "id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) Id = (ChannelId)oval; } break; } @@ -8661,7 +8816,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerNotifyUnregister : INotification + public sealed partial class ServerNotifyUnregister : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerNotifyUnregister; @@ -8676,7 +8831,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerProcessStop : INotification + public sealed partial class ServerProcessStop : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerProcessStop; @@ -8688,7 +8843,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "reasonmsg": ReasonMessage = Ts3String.Unescape(value); break; + case "reasonmsg": ReasonMessage = (str)TsString.Unescape(value); break; } @@ -8709,7 +8864,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerSnapshotCreate : INotification + public sealed partial class ServerSnapshotCreate : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerSnapshotCreate; @@ -8724,7 +8879,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerSnapshotDeploy : INotification + public sealed partial class ServerSnapshotDeploy : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerSnapshotDeploy; @@ -8739,7 +8894,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerStart : INotification + public sealed partial class ServerStart : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerStart; @@ -8751,7 +8906,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = oval; } break; + case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = (u32)oval; } break; } @@ -8772,7 +8927,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerStop : INotification + public sealed partial class ServerStop : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerStop; @@ -8785,8 +8940,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = oval; } break; - case "reasonmsg": ReasonMessage = Ts3String.Unescape(value); break; + case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = (u32)oval; } break; + case "reasonmsg": ReasonMessage = (str)TsString.Unescape(value); break; } @@ -8808,7 +8963,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerTempPasswordAdd : INotification + public sealed partial class ServerTempPasswordAdd : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerTempPasswordAdd; @@ -8824,11 +8979,11 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "pw": Password = Ts3String.Unescape(value); break; - case "desc": Description = Ts3String.Unescape(value); break; + case "pw": Password = (str)TsString.Unescape(value); break; + case "desc": Description = (str)TsString.Unescape(value); break; case "duration": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Duration = TimeSpan.FromSeconds(oval); } break; - case "tcid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) TargetChannelId = oval; } break; - case "tcpw": TargetChannelPassword = Ts3String.Unescape(value); break; + case "tcid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetChannelId = (ChannelId)oval; } break; + case "tcpw": TargetChannelPassword = (str)TsString.Unescape(value); break; } @@ -8853,7 +9008,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerTempPasswordDel : INotification + public sealed partial class ServerTempPasswordDel : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerTempPasswordDel; @@ -8865,7 +9020,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "pw": Password = Ts3String.Unescape(value); break; + case "pw": Password = (str)TsString.Unescape(value); break; } @@ -8886,7 +9041,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerTempPasswordList : INotification + public sealed partial class ServerTempPasswordList : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerTempPasswordList; @@ -8905,14 +9060,14 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "nickname": Nickname = Ts3String.Unescape(value); break; - case "uid": Uid = Ts3String.Unescape(value); break; - case "desc": Description = Ts3String.Unescape(value); break; - case "pw_clear": PasswordClear = Ts3String.Unescape(value); break; - case "start": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Start = Util.UnixTimeStart.AddSeconds(oval); } break; - case "end": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) End = Util.UnixTimeStart.AddSeconds(oval); } break; - case "tcid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) TargetChannelId = oval; } break; - case "tcpw": TargetChannelPassword = Ts3String.Unescape(value); break; + case "nickname": Nickname = (str)TsString.Unescape(value); break; + case "uid": Uid = (Uid)TsString.Unescape(value); break; + case "desc": Description = (str)TsString.Unescape(value); break; + case "pw_clear": PasswordClear = (str)TsString.Unescape(value); break; + case "start": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) Start = Tools.FromUnix(oval); } break; + case "end": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) End = Tools.FromUnix(oval); } break; + case "tcid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TargetChannelId = (ChannelId)oval; } break; + case "tcpw": TargetChannelPassword = (str)TsString.Unescape(value); break; } @@ -8940,7 +9095,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerTempPasswordListRequest : INotification + public sealed partial class ServerTempPasswordListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerTempPasswordListRequest; @@ -8955,7 +9110,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerUpdated : INotification + public sealed partial class ServerUpdated : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerUpdated; @@ -9014,54 +9169,54 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "virtualserver_welcomemessage": WelcomeMessage = Ts3String.Unescape(value); break; - case "virtualserver_maxclients": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) MaxClients = oval; } break; - case "virtualserver_clientsonline": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientsOnline = oval; } break; - case "virtualserver_channelsonline": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelsOnline = oval; } break; + case "virtualserver_welcomemessage": WelcomeMessage = (str)TsString.Unescape(value); break; + case "virtualserver_maxclients": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) MaxClients = (u16)oval; } break; + case "virtualserver_clientsonline": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientsOnline = (u16)oval; } break; + case "virtualserver_channelsonline": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelsOnline = (u64)oval; } break; case "virtualserver_uptime": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) Uptime = TimeSpan.FromSeconds(oval); } break; - case "virtualserver_hostmessage": Hostmessage = Ts3String.Unescape(value); break; + case "virtualserver_hostmessage": Hostmessage = (str)TsString.Unescape(value); break; case "virtualserver_hostmessage_mode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) HostmessageMode = (HostMessageMode)oval; } break; case "virtualserver_flag_password": HasPassword = value.Length > 0 && value[0] != '0'; break; - case "virtualserver_default_channel_admin_group": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) DefaultChannelAdminGroup = oval; } break; - case "virtualserver_max_download_total_bandwidth": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) MaxDownloadTotalBandwidth = oval; } break; - case "virtualserver_max_upload_total_bandwidth": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) MaxUploadTotalBandwidth = oval; } break; - case "virtualserver_complain_autoban_count": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ComplainAutobanCount = oval; } break; + case "virtualserver_default_channel_admin_group": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DefaultChannelAdminGroup = (ChannelGroupId)oval; } break; + case "virtualserver_max_download_total_bandwidth": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) MaxDownloadTotalBandwidth = (u64)oval; } break; + case "virtualserver_max_upload_total_bandwidth": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) MaxUploadTotalBandwidth = (u64)oval; } break; + case "virtualserver_complain_autoban_count": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ComplainAutobanCount = (u32)oval; } break; case "virtualserver_complain_autoban_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ComplainAutobanTime = TimeSpan.FromSeconds(oval); } break; case "virtualserver_complain_remove_time": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) ComplainRemoveTime = TimeSpan.FromSeconds(oval); } break; - case "virtualserver_min_clients_in_channel_before_forced_silence": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinClientsInChannelBeforeForcedSilence = oval; } break; - case "virtualserver_antiflood_points_tick_reduce": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsTickReduce = oval; } break; - case "virtualserver_antiflood_points_needed_command_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToCommandBlock = oval; } break; - case "virtualserver_antiflood_points_needed_ip_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToIpBlock = oval; } break; - case "virtualserver_client_connections": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientConnections = oval; } break; - case "virtualserver_query_client_connections": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) QueryConnections = oval; } break; - case "virtualserver_queryclientsonline": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) QueriesOnline = oval; } break; - case "virtualserver_download_quota": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DownloadQuota = oval; } break; - case "virtualserver_upload_quota": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) UploadQuota = oval; } break; - case "virtualserver_month_bytes_downloaded": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesDownloadedMonth = oval; } break; - case "virtualserver_month_bytes_uploaded": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesUploadedMonth = oval; } break; - case "virtualserver_total_bytes_downloaded": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesDownloadedTotal = oval; } break; - case "virtualserver_total_bytes_uploaded": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesUploadedTotal = oval; } break; - case "virtualserver_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) VirtualServerPort = oval; } break; + case "virtualserver_min_clients_in_channel_before_forced_silence": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinClientsInChannelBeforeForcedSilence = (u32)oval; } break; + case "virtualserver_antiflood_points_tick_reduce": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsTickReduce = (u32)oval; } break; + case "virtualserver_antiflood_points_needed_command_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToCommandBlock = (u32)oval; } break; + case "virtualserver_antiflood_points_needed_ip_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToIpBlock = (u32)oval; } break; + case "virtualserver_client_connections": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientConnections = (u64)oval; } break; + case "virtualserver_query_client_connections": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) QueryConnections = (u64)oval; } break; + case "virtualserver_queryclientsonline": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) QueriesOnline = (u16)oval; } break; + case "virtualserver_download_quota": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DownloadQuota = (u64)oval; } break; + case "virtualserver_upload_quota": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) UploadQuota = (u64)oval; } break; + case "virtualserver_month_bytes_downloaded": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesDownloadedMonth = (u64)oval; } break; + case "virtualserver_month_bytes_uploaded": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesUploadedMonth = (u64)oval; } break; + case "virtualserver_total_bytes_downloaded": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesDownloadedTotal = (u64)oval; } break; + case "virtualserver_total_bytes_uploaded": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) BytesUploadedTotal = (u64)oval; } break; + case "virtualserver_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) VirtualServerPort = (u16)oval; } break; case "virtualserver_autostart": Autostart = value.Length > 0 && value[0] != '0'; break; - case "virtualserver_machine_id": MachineId = Ts3String.Unescape(value); break; - case "virtualserver_needed_identity_security_level": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) IdentitySecurityLevel = oval; } break; + case "virtualserver_machine_id": MachineId = (str)TsString.Unescape(value); break; + case "virtualserver_needed_identity_security_level": { if(Utf8Parser.TryParse(value, out u8 oval, out _)) IdentitySecurityLevel = (u8)oval; } break; case "virtualserver_log_client": LogClient = value.Length > 0 && value[0] != '0'; break; case "virtualserver_log_query": LogQuery = value.Length > 0 && value[0] != '0'; break; case "virtualserver_log_channel": LogChannel = value.Length > 0 && value[0] != '0'; break; case "virtualserver_log_permissions": LogPermissions = value.Length > 0 && value[0] != '0'; break; case "virtualserver_log_server": LogServer = value.Length > 0 && value[0] != '0'; break; case "virtualserver_log_filetransfer": LogFileTransfer = value.Length > 0 && value[0] != '0'; break; - case "virtualserver_min_client_version": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinClientVersion = oval; } break; - case "virtualserver_reserved_slots": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ReservedSlots = oval; } break; - case "virtualserver_total_packetloss_speech": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotalSpeech = oval; } break; - case "virtualserver_total_packetloss_keepalive": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotalKeepalive = oval; } break; - case "virtualserver_total_packetloss_control": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotalControl = oval; } break; - case "virtualserver_total_packetloss_total": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotal = oval; } break; - case "virtualserver_total_ping": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PingTotal = oval; } break; + case "virtualserver_min_client_version": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinClientVersion = (u32)oval; } break; + case "virtualserver_reserved_slots": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ReservedSlots = (u16)oval; } break; + case "virtualserver_total_packetloss_speech": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotalSpeech = (f32)oval; } break; + case "virtualserver_total_packetloss_keepalive": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotalKeepalive = (f32)oval; } break; + case "virtualserver_total_packetloss_control": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotalControl = (f32)oval; } break; + case "virtualserver_total_packetloss_total": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PacketlossTotal = (f32)oval; } break; + case "virtualserver_total_ping": { if(Utf8Parser.TryParse(value, out f32 oval, out _)) PingTotal = (f32)oval; } break; case "virtualserver_weblist_enabled": WeblistEnabled = value.Length > 0 && value[0] != '0'; break; - case "virtualserver_min_android_version": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinAndroidVersion = oval; } break; - case "virtualserver_min_ios_version": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinIosVersion = oval; } break; - case "virtualserver_antiflood_points_needed_plugin_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToPluginBlock = oval; } break; + case "virtualserver_min_android_version": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinAndroidVersion = (u32)oval; } break; + case "virtualserver_min_ios_version": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) MinIosVersion = (u32)oval; } break; + case "virtualserver_antiflood_points_needed_plugin_block": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) AntifloodPointsToPluginBlock = (u32)oval; } break; } @@ -9129,7 +9284,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class ServerVariablesRequest : INotification + public sealed partial class ServerVariablesRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.ServerVariablesRequest; @@ -9144,7 +9299,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class SetClientChannelGroup : INotification + public sealed partial class SetClientChannelGroup : INotification { public NotificationType NotifyType { get; } = NotificationType.SetClientChannelGroup; @@ -9158,9 +9313,9 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "cgid": { if(Utf8Parser.TryParse(value, out ChannelGroupId oval, out _)) ChannelGroup = oval; } break; - case "cid": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; + case "cgid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelGroup = (ChannelGroupId)oval; } break; + case "cid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; } @@ -9183,7 +9338,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class TextMessage : INotification, IResponse + public sealed partial class TextMessage : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.TextMessage; public string ReturnCode { get; set; } @@ -9201,12 +9356,12 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "targetmode": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) Target = (TextMessageTargetMode)oval; } break; - case "msg": Message = Ts3String.Unescape(value); break; - case "target": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) TargetClientId = oval; } break; - case "invokerid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) InvokerId = oval; } break; - case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "msg": Message = (str)TsString.Unescape(value); break; + case "target": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) TargetClientId = (ClientId)oval; } break; + case "invokerid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) InvokerId = (ClientId)oval; } break; + case "invokername": InvokerName = (str)TsString.Unescape(value); break; + case "invokeruid": InvokerUid = (Uid)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -9231,7 +9386,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class TokenAdd : INotification, IResponse + public sealed partial class TokenAdd : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.TokenAdd; public string ReturnCode { get; set; } @@ -9243,8 +9398,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "token": Token = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "token": Token = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -9264,7 +9419,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class TokenAddRequest : INotification + public sealed partial class TokenAddRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.TokenAddRequest; @@ -9281,10 +9436,10 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) { case "tokentype": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TokenType = (TokenType)oval; } break; - case "tokenid1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TokenId1 = oval; } break; - case "tokenid2": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) TokenId2 = oval; } break; - case "tokendescription": TokenDescription = Ts3String.Unescape(value); break; - case "tokencustomset": TokenCustomSet = Ts3String.Unescape(value); break; + case "tokenid1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TokenId1 = (u64)oval; } break; + case "tokenid2": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TokenId2 = (ChannelId)oval; } break; + case "tokendescription": TokenDescription = (str)TsString.Unescape(value); break; + case "tokencustomset": TokenCustomSet = (str)TsString.Unescape(value); break; } @@ -9309,7 +9464,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class TokenDelete : INotification + public sealed partial class TokenDelete : INotification { public NotificationType NotifyType { get; } = NotificationType.TokenDelete; @@ -9321,7 +9476,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "token": Token = Ts3String.Unescape(value); break; + case "token": Token = (str)TsString.Unescape(value); break; } @@ -9342,7 +9497,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class TokenList : INotification, IResponse + public sealed partial class TokenList : INotification, IResponse { public NotificationType NotifyType { get; } = NotificationType.TokenList; public string ReturnCode { get; set; } @@ -9359,13 +9514,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "token": Token = Ts3String.Unescape(value); break; + case "token": Token = (str)TsString.Unescape(value); break; case "token_type": { if(Utf8Parser.TryParse(value, out i32 oval, out _)) TokenType = (TokenType)oval; } break; - case "token_id1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TokenId1 = oval; } break; - case "token_id2": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) TokenId2 = oval; } break; - case "token_created": { if(Utf8Parser.TryParse(value, out f64 oval, out _)) TokenCreateTime = Util.UnixTimeStart.AddSeconds(oval); } break; - case "token_description": TokenDescription = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "token_id1": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TokenId1 = (u64)oval; } break; + case "token_id2": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) TokenId2 = (ChannelId)oval; } break; + case "token_created": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) TokenCreateTime = Tools.FromUnix(oval); } break; + case "token_description": TokenDescription = (str)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -9390,7 +9545,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class TokenListRequest : INotification + public sealed partial class TokenListRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.TokenListRequest; @@ -9405,7 +9560,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class TokenUse : INotification + public sealed partial class TokenUse : INotification { public NotificationType NotifyType { get; } = NotificationType.TokenUse; @@ -9417,7 +9572,7 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "token": Token = Ts3String.Unescape(value); break; + case "token": Token = (str)TsString.Unescape(value); break; } @@ -9438,7 +9593,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class TokenUsed : INotification + public sealed partial class TokenUsed : INotification { public NotificationType NotifyType { get; } = NotificationType.TokenUsed; @@ -9456,13 +9611,13 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "token": Token = Ts3String.Unescape(value); break; - case "tokencustomset": TokenCustomSet = Ts3String.Unescape(value); break; - case "token1": Token1 = Ts3String.Unescape(value); break; - case "token2": Token2 = Ts3String.Unescape(value); break; - case "clid": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "cldbid": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) ClientDbId = oval; } break; - case "cluid": ClientUid = Ts3String.Unescape(value); break; + case "token": Token = (str)TsString.Unescape(value); break; + case "tokencustomset": TokenCustomSet = (str)TsString.Unescape(value); break; + case "token1": Token1 = (str)TsString.Unescape(value); break; + case "token2": Token2 = (str)TsString.Unescape(value); break; + case "clid": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "cldbid": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ClientDbId = (ClientDbId)oval; } break; + case "cluid": ClientUid = (Uid)TsString.Unescape(value); break; } @@ -9489,7 +9644,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class Use : INotification + public sealed partial class Use : INotification { public NotificationType NotifyType { get; } = NotificationType.Use; @@ -9502,8 +9657,8 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = oval; } break; - case "port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) Port = oval; } break; + case "sid": { if(Utf8Parser.TryParse(value, out u32 oval, out _)) ServerId = (u32)oval; } break; + case "port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) Port = (u16)oval; } break; } @@ -9525,7 +9680,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class VersionRequest : INotification + public sealed partial class VersionRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.VersionRequest; @@ -9540,7 +9695,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class WhoAmI : IResponse + public sealed partial class WhoAmI : IResponse { public string ReturnCode { get; set; } @@ -9562,18 +9717,18 @@ public void SetField(string name, ReadOnlySpan value, Deserializer ser) switch(name) { - case "client_id": { if(Utf8Parser.TryParse(value, out ClientId oval, out _)) ClientId = oval; } break; - case "client_channel_id": { if(Utf8Parser.TryParse(value, out ChannelId oval, out _)) ChannelId = oval; } break; - case "client_nickname": Name = Ts3String.Unescape(value); break; - case "client_database_id": { if(Utf8Parser.TryParse(value, out ClientDbId oval, out _)) DatabaseId = oval; } break; - case "client_login_name": LoginName = Ts3String.Unescape(value); break; - case "client_origin_server_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) OriginServerId = oval; } break; - case "virtualserver_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) VirtualServerId = oval; } break; - case "virtualserver_unique_identifier": VirtualServerUid = Ts3String.Unescape(value); break; - case "virtualserver_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) VirtualServerPort = oval; } break; - case "virtualserver_status": VirtualServerStatus = Ts3String.Unescape(value); break; - case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "client_id": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) ClientId = (ClientId)oval; } break; + case "client_channel_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) ChannelId = (ChannelId)oval; } break; + case "client_nickname": Name = (str)TsString.Unescape(value); break; + case "client_database_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) DatabaseId = (ClientDbId)oval; } break; + case "client_login_name": LoginName = (str)TsString.Unescape(value); break; + case "client_origin_server_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) OriginServerId = (u64)oval; } break; + case "virtualserver_id": { if(Utf8Parser.TryParse(value, out u64 oval, out _)) VirtualServerId = (u64)oval; } break; + case "virtualserver_unique_identifier": VirtualServerUid = (Uid)TsString.Unescape(value); break; + case "virtualserver_port": { if(Utf8Parser.TryParse(value, out u16 oval, out _)) VirtualServerPort = (u16)oval; } break; + case "virtualserver_status": VirtualServerStatus = (str)TsString.Unescape(value); break; + case "client_unique_identifier": Uid = (Uid)TsString.Unescape(value); break; + case "return_code": ReturnCode = (str)TsString.Unescape(value); break; } } @@ -9603,7 +9758,7 @@ public void Expand(IMessage[] to, IEnumerable flds) } } - public sealed class WhoAmIRequest : INotification + public sealed partial class WhoAmIRequest : INotification { public NotificationType NotifyType { get; } = NotificationType.WhoAmIRequest; @@ -10000,7 +10155,7 @@ public enum NotificationType ///[C2S] ntfy:serverinfo ServerInfo, ///[C2S] ntfy:serverlist - ServerList, + ServerListRequest, ///[S2C] ntfy:notifyserverlog ServerLog, ///[C2S] ntfy:servernotifyregister @@ -10265,7 +10420,7 @@ public static NotificationType GetToServerNotificationType(string name) case "servergroupsbyclientid": return NotificationType.ServerGroupsByClientIdRequest; case "serveridgetbyport": return NotificationType.ServerIdGetByPort; case "serverinfo": return NotificationType.ServerInfo; - case "serverlist": return NotificationType.ServerList; + case "serverlist": return NotificationType.ServerListRequest; case "servernotifyregister": return NotificationType.ServerNotifyRegister; case "servernotifyunregister": return NotificationType.ServerNotifyUnregister; case "serverprocessstop": return NotificationType.ServerProcessStop; @@ -10482,7 +10637,7 @@ public static INotification GenerateNotificationType(NotificationType name) case NotificationType.ServerGroupsByClientIdRequest: return new ServerGroupsByClientIdRequest(); case NotificationType.ServerIdGetByPort: return new ServerIdGetByPort(); case NotificationType.ServerInfo: return new ServerInfo(); - case NotificationType.ServerList: return new ServerList(); + case NotificationType.ServerListRequest: return new ServerListRequest(); case NotificationType.ServerLog: return new ServerLog(); case NotificationType.ServerNotifyRegister: return new ServerNotifyRegister(); case NotificationType.ServerNotifyUnregister: return new ServerNotifyUnregister(); @@ -10510,7 +10665,7 @@ public static INotification GenerateNotificationType(NotificationType name) case NotificationType.VersionRequest: return new VersionRequest(); case NotificationType.WhoAmIRequest: return new WhoAmIRequest(); case NotificationType.Unknown: - default: throw Util.UnhandledDefault(name); + default: throw Tools.UnhandledDefault(name); } } @@ -10707,7 +10862,7 @@ public static INotification[] InstatiateNotificationArray(NotificationType name, case NotificationType.ServerGroupsByClientIdRequest: { var arr = new ServerGroupsByClientIdRequest[len]; for (int i = 0; i < len; i++) arr[i] = new ServerGroupsByClientIdRequest(); return arr; } case NotificationType.ServerIdGetByPort: { var arr = new ServerIdGetByPort[len]; for (int i = 0; i < len; i++) arr[i] = new ServerIdGetByPort(); return arr; } case NotificationType.ServerInfo: { var arr = new ServerInfo[len]; for (int i = 0; i < len; i++) arr[i] = new ServerInfo(); return arr; } - case NotificationType.ServerList: { var arr = new ServerList[len]; for (int i = 0; i < len; i++) arr[i] = new ServerList(); return arr; } + case NotificationType.ServerListRequest: { var arr = new ServerListRequest[len]; for (int i = 0; i < len; i++) arr[i] = new ServerListRequest(); return arr; } case NotificationType.ServerLog: { var arr = new ServerLog[len]; for (int i = 0; i < len; i++) arr[i] = new ServerLog(); return arr; } case NotificationType.ServerNotifyRegister: { var arr = new ServerNotifyRegister[len]; for (int i = 0; i < len; i++) arr[i] = new ServerNotifyRegister(); return arr; } case NotificationType.ServerNotifyUnregister: { var arr = new ServerNotifyUnregister[len]; for (int i = 0; i < len; i++) arr[i] = new ServerNotifyUnregister(); return arr; } @@ -10735,7 +10890,7 @@ public static INotification[] InstatiateNotificationArray(NotificationType name, case NotificationType.VersionRequest: { var arr = new VersionRequest[len]; for (int i = 0; i < len; i++) arr[i] = new VersionRequest(); return arr; } case NotificationType.WhoAmIRequest: { var arr = new WhoAmIRequest[len]; for (int i = 0; i < len; i++) arr[i] = new WhoAmIRequest(); return arr; } case NotificationType.Unknown: - default: throw Util.UnhandledDefault(name); + default: throw Tools.UnhandledDefault(name); } } } diff --git a/TS3Client/Generated/Messages.tt b/TSLib/Generated/Messages.tt similarity index 87% rename from TS3Client/Generated/Messages.tt rename to TSLib/Generated/Messages.tt index 387bc9f8..dd0ff0bc 100644 --- a/TS3Client/Generated/Messages.tt +++ b/TSLib/Generated/Messages.tt @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -15,16 +15,16 @@ <#@ import namespace="System.IO" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> +using System; +using System.Collections.Generic; +using System.Buffers.Text; +using TSLib.Commands; +using TSLib.Helper; -namespace TS3Client.Messages -{ - using Commands; - using Helper; - using System; - using System.Collections.Generic; - using System.Buffers.Text; +<#= ConversionSet #> - <#= ConversionSet #> +namespace TSLib.Messages +{ <# var gen = Messages.Parse(Host.ResolvePath("../Declarations/Messages.toml")); @@ -41,6 +41,12 @@ string GenerateDeserializer(Messages.Field fld) } Dictionary BackingTypes = new Dictionary() { + { "Uid", "str" }, + { "ClientDbId", "u64" }, + { "ClientId", "u16" }, + { "ChannelId", "u64" }, + { "ServerGroupId", "u64" }, + { "ChannelGroupId", "u64" }, { "Codec", "u8" }, { "Ts3ErrorCode", "u32" }, { "LicenseType", "u16" }, @@ -68,17 +74,19 @@ string GenerateSingleDeserializer(Messages.Field fld, string input, string outpu case "ChannelId": case "ServerGroupId": case "ChannelGroupId": - return $"{{ if(Utf8Parser.TryParse({input}, out {fld.type} oval, out _)) {output} = oval; }}"; + if(!BackingTypes.TryGetValue(fld.type, out var backType)) + backType = fld.type; + return $"{{ if(Utf8Parser.TryParse({input}, out {backType} oval, out _)) {output} = ({fld.type})oval; }}"; case "DurationSeconds": return $"{{ if(Utf8Parser.TryParse({input}, out f64 oval, out _)) {output} = TimeSpan.FromSeconds(oval); }}"; case "DurationMilliseconds": return $"{{ if(Utf8Parser.TryParse({input}, out f64 oval, out _)) {output} = TimeSpan.FromMilliseconds(oval); }}"; case "DateTime": - return $"{{ if(Utf8Parser.TryParse({input}, out f64 oval, out _)) {output} = Util.UnixTimeStart.AddSeconds(oval); }}"; + return $"{{ if(Utf8Parser.TryParse({input}, out u32 oval, out _)) {output} = Tools.FromUnix(oval); }}"; case "str": case "Uid": case "IpAddr": - return $"{output} = Ts3String.Unescape({input});"; + return $"{output} = ({fld.type})TsString.Unescape({input});"; case "HostMessageMode": case "CodecEncryptionMode": case "HostBannerMode": @@ -96,7 +104,7 @@ string GenerateSingleDeserializer(Messages.Field fld, string input, string outpu case "PermissionType": case "ChannelPermissionHint": case "ClientPermissionHint": - if(!BackingTypes.TryGetValue(fld.type, out var backType)) + if(!BackingTypes.TryGetValue(fld.type, out backType)) backType = "i32"; return $"{{ if(Utf8Parser.TryParse({input}, out {backType} oval, out _)) {output} = ({fld.type})oval; }}"; case "IconHash": @@ -113,7 +121,7 @@ foreach(var msg in gen.GetOrderedMsg()) { //if(!msg.s2c.Value) continue; #> - public sealed class <#= msg.name #><# + public sealed partial class <#= msg.name #><# bool isNotify = msg.notify != null; bool isResponse = msg.response.Value; if (isNotify && isResponse) Write(" : INotification, IResponse"); @@ -214,7 +222,7 @@ foreach(var msg in gen.GetOrderedMsg()) } #> case NotificationType.Unknown: - default: throw Util.UnhandledDefault(name); + default: throw Tools.UnhandledDefault(name); } } @@ -230,7 +238,7 @@ foreach(var msg in gen.GetOrderedMsg()) } #> case NotificationType.Unknown: - default: throw Util.UnhandledDefault(name); + default: throw Tools.UnhandledDefault(name); } } } diff --git a/TS3Client/Generated/Errors.cs b/TSLib/Generated/TsErrorCode.cs similarity index 99% rename from TS3Client/Generated/Errors.cs rename to TSLib/Generated/TsErrorCode.cs index 0aa0614c..621e13fb 100644 --- a/TS3Client/Generated/Errors.cs +++ b/TSLib/Generated/TsErrorCode.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -18,10 +18,10 @@ -namespace TS3Client +namespace TSLib { // Source: http://forum.teamspeak.com/threads/102276-Server-query-error-id-list - public enum Ts3ErrorCode : uint + public enum TsErrorCode : uint { // ReSharper disable InconsistentNaming, UnusedMember.Global /// unknown error code diff --git a/TS3Client/Generated/Errors.tt b/TSLib/Generated/TsErrorCode.tt similarity index 85% rename from TS3Client/Generated/Errors.tt rename to TSLib/Generated/TsErrorCode.tt index d3b87245..c69b8d0a 100644 --- a/TS3Client/Generated/Errors.tt +++ b/TSLib/Generated/TsErrorCode.tt @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -13,10 +13,10 @@ <#@ output extension=".cs" #> <# var errors = ParseErrors(); #> -namespace TS3Client +namespace TSLib { // Source: http://forum.teamspeak.com/threads/102276-Server-query-error-id-list - public enum Ts3ErrorCode : uint + public enum TsErrorCode : uint { // ReSharper disable InconsistentNaming, UnusedMember.Global<# foreach (var line in errors) { #> /// <#= line.Doc #> diff --git a/TSLib/Generated/TsPermission.cs b/TSLib/Generated/TsPermission.cs new file mode 100644 index 00000000..4cdb1488 --- /dev/null +++ b/TSLib/Generated/TsPermission.cs @@ -0,0 +1,789 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . +// + + + + + + + + + +using TSLib.Helper; + +namespace TSLib +{ + // Source: https://www.tsviewer.com/index.php?page=faq&id=12&newlanguage=en + public enum TsPermission + { + // ReSharper disable InconsistentNaming, UnusedMember.Global + undefined, + /// May occour on error returns with no associated permission + unknown, + /// Retrieve information about ServerQuery commands + b_serverinstance_help_view, + /// Retrieve global server version (including platform and build number) + b_serverinstance_version_view, + /// Retrieve global server information + b_serverinstance_info_view, + /// List virtual servers stored in the database + b_serverinstance_virtualserver_list, + /// List active IP bindings on multi-homed machines + b_serverinstance_binding_list, + /// List permissions available available on the server instance + b_serverinstance_permission_list, + /// Search permission assignments by name or ID + b_serverinstance_permission_find, + /// Create virtual servers + b_virtualserver_create, + /// Delete virtual servers + b_virtualserver_delete, + /// Start any virtual server in the server instance + b_virtualserver_start_any, + /// Stop any virtual server in the server instance + b_virtualserver_stop_any, + /// Change a virtual servers machine ID + b_virtualserver_change_machine_id, + /// Edit virtual server default template values + b_virtualserver_change_template, + /// Login to ServerQuery + b_serverquery_login, + /// Send text messages to all virtual servers at once + b_serverinstance_textmessage_send, + /// Retrieve global server log + b_serverinstance_log_view, + /// Write to global server log + b_serverinstance_log_add, + /// Shutdown the server process + b_serverinstance_stop, + /// Edit global settings + b_serverinstance_modify_settings, + /// Edit global ServerQuery groups + b_serverinstance_modify_querygroup, + /// Edit global template groups + b_serverinstance_modify_templates, + /// Select a virtual server + b_virtualserver_select, + /// Retrieve virtual server information + b_virtualserver_info_view, + /// Retrieve virtual server connection information + b_virtualserver_connectioninfo_view, + /// List channels on a virtual server + b_virtualserver_channel_list, + /// Search for channels on a virtual server + b_virtualserver_channel_search, + /// List clients online on a virtual server + b_virtualserver_client_list, + /// Search for clients online on a virtual server + b_virtualserver_client_search, + /// List client identities known by the virtual server + b_virtualserver_client_dblist, + /// Search for client identities known by the virtual server + b_virtualserver_client_dbsearch, + /// Retrieve client information + b_virtualserver_client_dbinfo, + /// Find permissions + b_virtualserver_permission_find, + /// Find custom fields + b_virtualserver_custom_search, + /// Start own virtual server + b_virtualserver_start, + /// Stop own virtual server + b_virtualserver_stop, + /// List privilege keys available + b_virtualserver_token_list, + /// Create new privilege keys + b_virtualserver_token_add, + /// Use a privilege keys to gain access to groups + b_virtualserver_token_use, + /// Delete a privilege key + b_virtualserver_token_delete, + /// Retrieve virtual server log + b_virtualserver_log_view, + /// Write to virtual server log + b_virtualserver_log_add, + /// Join virtual server ignoring its password + b_virtualserver_join_ignore_password, + /// Register for server notifications + b_virtualserver_notify_register, + /// Unregister from server notifications + b_virtualserver_notify_unregister, + /// Create server snapshots + b_virtualserver_snapshot_create, + /// Deploy server snapshots + b_virtualserver_snapshot_deploy, + /// Reset the server permission settings to default values + b_virtualserver_permission_reset, + /// Modify server name + b_virtualserver_modify_name, + /// Modify welcome message + b_virtualserver_modify_welcomemessage, + /// Modify servers max clients + b_virtualserver_modify_maxclients, + /// Modify reserved slots + b_virtualserver_modify_reserved_slots, + /// Modify server password + b_virtualserver_modify_password, + /// Modify default Server Group + b_virtualserver_modify_default_servergroup, + /// Modify default Channel Group + b_virtualserver_modify_default_channelgroup, + /// Modify default Channel Admin Group + b_virtualserver_modify_default_channeladmingroup, + /// Modify channel force silence value + b_virtualserver_modify_channel_forced_silence, + /// Modify individual complain settings + b_virtualserver_modify_complain, + /// Modify individual antiflood settings + b_virtualserver_modify_antiflood, + /// Modify file transfer settings + b_virtualserver_modify_ft_settings, + /// Modify file transfer quotas + b_virtualserver_modify_ft_quotas, + /// Modify individual hostmessage settings + b_virtualserver_modify_hostmessage, + /// Modify individual hostbanner settings + b_virtualserver_modify_hostbanner, + /// Modify individual hostbutton settings + b_virtualserver_modify_hostbutton, + /// Modify server port + b_virtualserver_modify_port, + /// Modify server autostart + b_virtualserver_modify_autostart, + /// Modify required identity security level + b_virtualserver_modify_needed_identity_security_level, + /// Modify priority speaker dimm modificator + b_virtualserver_modify_priority_speaker_dimm_modificator, + /// Modify log settings + b_virtualserver_modify_log_settings, + /// Modify min client version + b_virtualserver_modify_min_client_version, + /// Modify server icon + b_virtualserver_modify_icon_id, + /// Modify web server list reporting settings + b_virtualserver_modify_weblist, + /// Modify codec encryption mode + b_virtualserver_modify_codec_encryption_mode, + /// Modify temporary serverpasswords + b_virtualserver_modify_temporary_passwords, + /// Modify own temporary serverpasswords + b_virtualserver_modify_temporary_passwords_own, + /// Modify default temporary channel delete delay + b_virtualserver_modify_channel_temp_delete_delay_default, + /// Modify server nicknames + b_virtualserver_modify_nickname, + /// Modify integrations + b_virtualserver_modify_integrations, + /// Min channel creation depth in hierarchy + i_channel_min_depth, + /// Max channel creation depth in hierarchy + i_channel_max_depth, + /// Stop inheritance of channel group permissions + b_channel_group_inheritance_end, + /// Modify channel permission power + i_channel_permission_modify_power, + /// Needed modify channel permission power + i_channel_needed_permission_modify_power, + /// Retrieve channel information + b_channel_info_view, + /// Create sub-channels + b_channel_create_child, + /// Create permanent channels + b_channel_create_permanent, + /// Create semi-permanent channels + b_channel_create_semi_permanent, + /// Create temporary channels + b_channel_create_temporary, + /// Create private channel + b_channel_create_private, + /// Create channels with a topic + b_channel_create_with_topic, + /// Create channels with a description + b_channel_create_with_description, + /// Create password protected channels + b_channel_create_with_password, + /// Create channels using Speex Narrowband (8 kHz) codecs + b_channel_create_modify_with_codec_speex8, + /// Create channels using Speex Wideband (16 kHz) codecs + b_channel_create_modify_with_codec_speex16, + /// Create channels using Speex Ultra-Wideband (32 kHz) codecs + b_channel_create_modify_with_codec_speex32, + /// Create channels using the CELT Mono (48 kHz) codec + b_channel_create_modify_with_codec_celtmono48, + /// Create channels using OPUS (voice) codec + b_channel_create_modify_with_codec_opusvoice, + /// Create channels using OPUS (music) codec + b_channel_create_modify_with_codec_opusmusic, + /// Create channels with custom codec quality + i_channel_create_modify_with_codec_maxquality, + /// Create channels with minimal custom codec latency factor + i_channel_create_modify_with_codec_latency_factor_min, + /// Create channels with custom max clients + b_channel_create_with_maxclients, + /// Create channels with custom max family clients + b_channel_create_with_maxfamilyclients, + /// Create channels with custom sort order + b_channel_create_with_sortorder, + /// Create default channels + b_channel_create_with_default, + /// Create channels with needed talk power + b_channel_create_with_needed_talk_power, + /// Create new channels only with password + b_channel_create_modify_with_force_password, + /// Max delete delay for temporary channels + i_channel_create_modify_with_temp_delete_delay, + /// Move channels + b_channel_modify_parent, + /// Make channel default + b_channel_modify_make_default, + /// Make channel permanent + b_channel_modify_make_permanent, + /// Make channel semi-permanent + b_channel_modify_make_semi_permanent, + /// Make channel temporary + b_channel_modify_make_temporary, + /// Modify channel name + b_channel_modify_name, + /// Modify channel topic + b_channel_modify_topic, + /// Modify channel description + b_channel_modify_description, + /// Modify channel password + b_channel_modify_password, + /// Modify channel codec + b_channel_modify_codec, + /// Modify channel codec quality + b_channel_modify_codec_quality, + /// Modify channel codec latency factor + b_channel_modify_codec_latency_factor, + /// Modify channels max clients + b_channel_modify_maxclients, + /// Modify channels max family clients + b_channel_modify_maxfamilyclients, + /// Modify channel sort order + b_channel_modify_sortorder, + /// Change needed channel talk power + b_channel_modify_needed_talk_power, + /// Channel modify power + i_channel_modify_power, + /// Needed channel modify power + i_channel_needed_modify_power, + /// Make channel codec encrypted + b_channel_modify_make_codec_encrypted, + /// Modify temporary channel delete delay + b_channel_modify_temp_delete_delay, + /// Delete permanent channels + b_channel_delete_permanent, + /// Delete semi-permanent channels + b_channel_delete_semi_permanent, + /// Delete temporary channels + b_channel_delete_temporary, + /// Force channel delete + b_channel_delete_flag_force, + /// Delete channel power + i_channel_delete_power, + /// Needed delete channel power + i_channel_needed_delete_power, + /// Join permanent channels + b_channel_join_permanent, + /// Join semi-permanent channels + b_channel_join_semi_permanent, + /// Join temporary channels + b_channel_join_temporary, + /// Join channel ignoring its password + b_channel_join_ignore_password, + /// Ignore channels max clients limit + b_channel_join_ignore_maxclients, + /// Channel join power + i_channel_join_power, + /// Needed channel join power + i_channel_needed_join_power, + /// Channel subscribe power + i_channel_subscribe_power, + /// Needed channel subscribe power + i_channel_needed_subscribe_power, + /// Channel description view power + i_channel_description_view_power, + /// Needed channel needed description view power + i_channel_needed_description_view_power, + /// Group icon identifier + i_icon_id, + /// Max icon filesize in bytes + i_max_icon_filesize, + /// Enables icon management + b_icon_manage, + /// Group is permanent + b_group_is_permanent, + /// Group auto-update type + i_group_auto_update_type, + /// Group auto-update max value + i_group_auto_update_max_value, + /// Group sort id + i_group_sort_id, + /// Show group name in tree depending on selected mode + i_group_show_name_in_tree, + /// List server groups + b_virtualserver_servergroup_list, + /// List server group permissions + b_virtualserver_servergroup_permission_list, + /// List clients from a server group + b_virtualserver_servergroup_client_list, + /// List channel groups + b_virtualserver_channelgroup_list, + /// List channel group permissions + b_virtualserver_channelgroup_permission_list, + /// List clients from a channel group + b_virtualserver_channelgroup_client_list, + /// List client permissions + b_virtualserver_client_permission_list, + /// List channel permissions + b_virtualserver_channel_permission_list, + /// List channel client permissions + b_virtualserver_channelclient_permission_list, + /// Create server groups + b_virtualserver_servergroup_create, + /// Create channel groups + b_virtualserver_channelgroup_create, + /// Group modify power + i_group_modify_power, + /// Needed group modify power + i_group_needed_modify_power, + /// Group member add power + i_group_member_add_power, + /// Needed group member add power + i_group_needed_member_add_power, + /// Group member delete power + i_group_member_remove_power, + /// Needed group member delete power + i_group_needed_member_remove_power, + /// Permission modify power + i_permission_modify_power, + /// Ignore needed permission modify power + b_permission_modify_power_ignore, + /// Delete server groups + b_virtualserver_servergroup_delete, + /// Delete channel groups + b_virtualserver_channelgroup_delete, + /// Client permission modify power + i_client_permission_modify_power, + /// Needed client permission modify power + i_client_needed_permission_modify_power, + /// Max additional connections per client identity + i_client_max_clones_uid, + /// Max idle time in seconds + i_client_max_idletime, + /// Max avatar filesize in bytes + i_client_max_avatar_filesize, + /// Max channel subscriptions + i_client_max_channel_subscriptions, + /// Client is priority speaker + b_client_is_priority_speaker, + /// Ignore channel group permissions + b_client_skip_channelgroup_permissions, + /// Force Push-To-Talk capture mode + b_client_force_push_to_talk, + /// Ignore bans + b_client_ignore_bans, + /// Ignore antiflood measurements + b_client_ignore_antiflood, + /// Issue query commands from client + b_client_issue_client_query_command, + /// Use an reserved slot + b_client_use_reserved_slot, + /// Use channel commander + b_client_use_channel_commander, + /// Allow to request talk power + b_client_request_talker, + /// Allow deletion of avatars from other clients + b_client_avatar_delete_other, + /// Client will be sticked to current channel + b_client_is_sticky, + /// Client ignores sticky flag + b_client_ignore_sticky, + /// Retrieve client information + b_client_info_view, + /// Retrieve client permissions overview + b_client_permissionoverview_view, + /// Retrieve clients own permissions overview + b_client_permissionoverview_own, + /// View client IP address and port + b_client_remoteaddress_view, + /// ServerQuery view power + i_client_serverquery_view_power, + /// Needed ServerQuery view power + i_client_needed_serverquery_view_power, + /// View custom fields + b_client_custom_info_view, + /// Client kick power from server + i_client_kick_from_server_power, + /// Needed client kick power from server + i_client_needed_kick_from_server_power, + /// Client kick power from channel + i_client_kick_from_channel_power, + /// Needed client kick power from channel + i_client_needed_kick_from_channel_power, + /// Client ban power + i_client_ban_power, + /// Needed client ban power + i_client_needed_ban_power, + /// Client move power + i_client_move_power, + /// Needed client move power + i_client_needed_move_power, + /// Complain power + i_client_complain_power, + /// Needed complain power + i_client_needed_complain_power, + /// Show complain list + b_client_complain_list, + /// Delete own complains + b_client_complain_delete_own, + /// Delete complains + b_client_complain_delete, + /// Show banlist + b_client_ban_list, + /// Add a ban + b_client_ban_create, + /// Delete own bans + b_client_ban_delete_own, + /// Delete bans + b_client_ban_delete, + /// Max bantime + i_client_ban_max_bantime, + /// Client private message power + i_client_private_textmessage_power, + /// Needed client private message power + i_client_needed_private_textmessage_power, + /// Send text messages to virtual server + b_client_server_textmessage_send, + /// Send text messages to channel + b_client_channel_textmessage_send, + /// Send offline messages to clients + b_client_offline_textmessage_send, + /// Client talk power + i_client_talk_power, + /// Needed client talk power + i_client_needed_talk_power, + /// Client poke power + i_client_poke_power, + /// Needed client poke power + i_client_needed_poke_power, + /// Set the talker flag for clients and allow them to speak + b_client_set_flag_talker, + /// Client whisper power + i_client_whisper_power, + /// Client needed whisper power + i_client_needed_whisper_power, + /// Edit a clients description + b_client_modify_description, + /// Allow client to edit own description + b_client_modify_own_description, + /// Edit a clients properties in the database + b_client_modify_dbproperties, + /// Delete a clients properties in the database + b_client_delete_dbproperties, + /// Create or modify own ServerQuery account + b_client_create_modify_serverquery_login, + /// Browse files without channel password + b_ft_ignore_password, + /// Retrieve list of running filetransfers + b_ft_transfer_list, + /// File upload power + i_ft_file_upload_power, + /// Needed file upload power + i_ft_needed_file_upload_power, + /// File download power + i_ft_file_download_power, + /// Needed file download power + i_ft_needed_file_download_power, + /// File delete power + i_ft_file_delete_power, + /// Needed file delete power + i_ft_needed_file_delete_power, + /// File rename power + i_ft_file_rename_power, + /// Needed file rename power + i_ft_needed_file_rename_power, + /// File browse power + i_ft_file_browse_power, + /// Needed file browse power + i_ft_needed_file_browse_power, + /// Create directory power + i_ft_directory_create_power, + /// Needed create directory power + i_ft_needed_directory_create_power, + /// Download quota per client in MByte + i_ft_quota_mb_download_per_client, + /// Upload quota per client in MByte + i_ft_quota_mb_upload_per_client, + // ReSharper restore InconsistentNaming, UnusedMember.Global + } + + public static partial class TsPermissionHelper + { + public static string GetDescription(TsPermission permid) + { + switch (permid) + { + case TsPermission.undefined: return "Undefined permission"; + case TsPermission.unknown: return "May occour on error returns with no associated permission"; + case TsPermission.b_serverinstance_help_view: return "Retrieve information about ServerQuery commands"; + case TsPermission.b_serverinstance_version_view: return "Retrieve global server version (including platform and build number)"; + case TsPermission.b_serverinstance_info_view: return "Retrieve global server information"; + case TsPermission.b_serverinstance_virtualserver_list: return "List virtual servers stored in the database"; + case TsPermission.b_serverinstance_binding_list: return "List active IP bindings on multi-homed machines"; + case TsPermission.b_serverinstance_permission_list: return "List permissions available available on the server instance"; + case TsPermission.b_serverinstance_permission_find: return "Search permission assignments by name or ID"; + case TsPermission.b_virtualserver_create: return "Create virtual servers"; + case TsPermission.b_virtualserver_delete: return "Delete virtual servers"; + case TsPermission.b_virtualserver_start_any: return "Start any virtual server in the server instance"; + case TsPermission.b_virtualserver_stop_any: return "Stop any virtual server in the server instance"; + case TsPermission.b_virtualserver_change_machine_id: return "Change a virtual servers machine ID"; + case TsPermission.b_virtualserver_change_template: return "Edit virtual server default template values"; + case TsPermission.b_serverquery_login: return "Login to ServerQuery"; + case TsPermission.b_serverinstance_textmessage_send: return "Send text messages to all virtual servers at once"; + case TsPermission.b_serverinstance_log_view: return "Retrieve global server log"; + case TsPermission.b_serverinstance_log_add: return "Write to global server log"; + case TsPermission.b_serverinstance_stop: return "Shutdown the server process"; + case TsPermission.b_serverinstance_modify_settings: return "Edit global settings"; + case TsPermission.b_serverinstance_modify_querygroup: return "Edit global ServerQuery groups"; + case TsPermission.b_serverinstance_modify_templates: return "Edit global template groups"; + case TsPermission.b_virtualserver_select: return "Select a virtual server"; + case TsPermission.b_virtualserver_info_view: return "Retrieve virtual server information"; + case TsPermission.b_virtualserver_connectioninfo_view: return "Retrieve virtual server connection information"; + case TsPermission.b_virtualserver_channel_list: return "List channels on a virtual server"; + case TsPermission.b_virtualserver_channel_search: return "Search for channels on a virtual server"; + case TsPermission.b_virtualserver_client_list: return "List clients online on a virtual server"; + case TsPermission.b_virtualserver_client_search: return "Search for clients online on a virtual server"; + case TsPermission.b_virtualserver_client_dblist: return "List client identities known by the virtual server"; + case TsPermission.b_virtualserver_client_dbsearch: return "Search for client identities known by the virtual server"; + case TsPermission.b_virtualserver_client_dbinfo: return "Retrieve client information"; + case TsPermission.b_virtualserver_permission_find: return "Find permissions"; + case TsPermission.b_virtualserver_custom_search: return "Find custom fields"; + case TsPermission.b_virtualserver_start: return "Start own virtual server"; + case TsPermission.b_virtualserver_stop: return "Stop own virtual server"; + case TsPermission.b_virtualserver_token_list: return "List privilege keys available"; + case TsPermission.b_virtualserver_token_add: return "Create new privilege keys"; + case TsPermission.b_virtualserver_token_use: return "Use a privilege keys to gain access to groups"; + case TsPermission.b_virtualserver_token_delete: return "Delete a privilege key"; + case TsPermission.b_virtualserver_log_view: return "Retrieve virtual server log"; + case TsPermission.b_virtualserver_log_add: return "Write to virtual server log"; + case TsPermission.b_virtualserver_join_ignore_password: return "Join virtual server ignoring its password"; + case TsPermission.b_virtualserver_notify_register: return "Register for server notifications"; + case TsPermission.b_virtualserver_notify_unregister: return "Unregister from server notifications"; + case TsPermission.b_virtualserver_snapshot_create: return "Create server snapshots"; + case TsPermission.b_virtualserver_snapshot_deploy: return "Deploy server snapshots"; + case TsPermission.b_virtualserver_permission_reset: return "Reset the server permission settings to default values"; + case TsPermission.b_virtualserver_modify_name: return "Modify server name"; + case TsPermission.b_virtualserver_modify_welcomemessage: return "Modify welcome message"; + case TsPermission.b_virtualserver_modify_maxclients: return "Modify servers max clients"; + case TsPermission.b_virtualserver_modify_reserved_slots: return "Modify reserved slots"; + case TsPermission.b_virtualserver_modify_password: return "Modify server password"; + case TsPermission.b_virtualserver_modify_default_servergroup: return "Modify default Server Group"; + case TsPermission.b_virtualserver_modify_default_channelgroup: return "Modify default Channel Group"; + case TsPermission.b_virtualserver_modify_default_channeladmingroup: return "Modify default Channel Admin Group"; + case TsPermission.b_virtualserver_modify_channel_forced_silence: return "Modify channel force silence value"; + case TsPermission.b_virtualserver_modify_complain: return "Modify individual complain settings"; + case TsPermission.b_virtualserver_modify_antiflood: return "Modify individual antiflood settings"; + case TsPermission.b_virtualserver_modify_ft_settings: return "Modify file transfer settings"; + case TsPermission.b_virtualserver_modify_ft_quotas: return "Modify file transfer quotas"; + case TsPermission.b_virtualserver_modify_hostmessage: return "Modify individual hostmessage settings"; + case TsPermission.b_virtualserver_modify_hostbanner: return "Modify individual hostbanner settings"; + case TsPermission.b_virtualserver_modify_hostbutton: return "Modify individual hostbutton settings"; + case TsPermission.b_virtualserver_modify_port: return "Modify server port"; + case TsPermission.b_virtualserver_modify_autostart: return "Modify server autostart"; + case TsPermission.b_virtualserver_modify_needed_identity_security_level: return "Modify required identity security level"; + case TsPermission.b_virtualserver_modify_priority_speaker_dimm_modificator: return "Modify priority speaker dimm modificator"; + case TsPermission.b_virtualserver_modify_log_settings: return "Modify log settings"; + case TsPermission.b_virtualserver_modify_min_client_version: return "Modify min client version"; + case TsPermission.b_virtualserver_modify_icon_id: return "Modify server icon"; + case TsPermission.b_virtualserver_modify_weblist: return "Modify web server list reporting settings"; + case TsPermission.b_virtualserver_modify_codec_encryption_mode: return "Modify codec encryption mode"; + case TsPermission.b_virtualserver_modify_temporary_passwords: return "Modify temporary serverpasswords"; + case TsPermission.b_virtualserver_modify_temporary_passwords_own: return "Modify own temporary serverpasswords"; + case TsPermission.b_virtualserver_modify_channel_temp_delete_delay_default: return "Modify default temporary channel delete delay"; + case TsPermission.b_virtualserver_modify_nickname: return "Modify server nicknames"; + case TsPermission.b_virtualserver_modify_integrations: return "Modify integrations"; + case TsPermission.i_channel_min_depth: return "Min channel creation depth in hierarchy"; + case TsPermission.i_channel_max_depth: return "Max channel creation depth in hierarchy"; + case TsPermission.b_channel_group_inheritance_end: return "Stop inheritance of channel group permissions"; + case TsPermission.i_channel_permission_modify_power: return "Modify channel permission power"; + case TsPermission.i_channel_needed_permission_modify_power: return "Needed modify channel permission power"; + case TsPermission.b_channel_info_view: return "Retrieve channel information"; + case TsPermission.b_channel_create_child: return "Create sub-channels"; + case TsPermission.b_channel_create_permanent: return "Create permanent channels"; + case TsPermission.b_channel_create_semi_permanent: return "Create semi-permanent channels"; + case TsPermission.b_channel_create_temporary: return "Create temporary channels"; + case TsPermission.b_channel_create_private: return "Create private channel"; + case TsPermission.b_channel_create_with_topic: return "Create channels with a topic"; + case TsPermission.b_channel_create_with_description: return "Create channels with a description"; + case TsPermission.b_channel_create_with_password: return "Create password protected channels"; + case TsPermission.b_channel_create_modify_with_codec_speex8: return "Create channels using Speex Narrowband (8 kHz) codecs"; + case TsPermission.b_channel_create_modify_with_codec_speex16: return "Create channels using Speex Wideband (16 kHz) codecs"; + case TsPermission.b_channel_create_modify_with_codec_speex32: return "Create channels using Speex Ultra-Wideband (32 kHz) codecs"; + case TsPermission.b_channel_create_modify_with_codec_celtmono48: return "Create channels using the CELT Mono (48 kHz) codec"; + case TsPermission.b_channel_create_modify_with_codec_opusvoice: return "Create channels using OPUS (voice) codec"; + case TsPermission.b_channel_create_modify_with_codec_opusmusic: return "Create channels using OPUS (music) codec"; + case TsPermission.i_channel_create_modify_with_codec_maxquality: return "Create channels with custom codec quality"; + case TsPermission.i_channel_create_modify_with_codec_latency_factor_min: return "Create channels with minimal custom codec latency factor"; + case TsPermission.b_channel_create_with_maxclients: return "Create channels with custom max clients"; + case TsPermission.b_channel_create_with_maxfamilyclients: return "Create channels with custom max family clients"; + case TsPermission.b_channel_create_with_sortorder: return "Create channels with custom sort order"; + case TsPermission.b_channel_create_with_default: return "Create default channels"; + case TsPermission.b_channel_create_with_needed_talk_power: return "Create channels with needed talk power"; + case TsPermission.b_channel_create_modify_with_force_password: return "Create new channels only with password"; + case TsPermission.i_channel_create_modify_with_temp_delete_delay: return "Max delete delay for temporary channels"; + case TsPermission.b_channel_modify_parent: return "Move channels"; + case TsPermission.b_channel_modify_make_default: return "Make channel default"; + case TsPermission.b_channel_modify_make_permanent: return "Make channel permanent"; + case TsPermission.b_channel_modify_make_semi_permanent: return "Make channel semi-permanent"; + case TsPermission.b_channel_modify_make_temporary: return "Make channel temporary"; + case TsPermission.b_channel_modify_name: return "Modify channel name"; + case TsPermission.b_channel_modify_topic: return "Modify channel topic"; + case TsPermission.b_channel_modify_description: return "Modify channel description"; + case TsPermission.b_channel_modify_password: return "Modify channel password"; + case TsPermission.b_channel_modify_codec: return "Modify channel codec"; + case TsPermission.b_channel_modify_codec_quality: return "Modify channel codec quality"; + case TsPermission.b_channel_modify_codec_latency_factor: return "Modify channel codec latency factor"; + case TsPermission.b_channel_modify_maxclients: return "Modify channels max clients"; + case TsPermission.b_channel_modify_maxfamilyclients: return "Modify channels max family clients"; + case TsPermission.b_channel_modify_sortorder: return "Modify channel sort order"; + case TsPermission.b_channel_modify_needed_talk_power: return "Change needed channel talk power"; + case TsPermission.i_channel_modify_power: return "Channel modify power"; + case TsPermission.i_channel_needed_modify_power: return "Needed channel modify power"; + case TsPermission.b_channel_modify_make_codec_encrypted: return "Make channel codec encrypted"; + case TsPermission.b_channel_modify_temp_delete_delay: return "Modify temporary channel delete delay"; + case TsPermission.b_channel_delete_permanent: return "Delete permanent channels"; + case TsPermission.b_channel_delete_semi_permanent: return "Delete semi-permanent channels"; + case TsPermission.b_channel_delete_temporary: return "Delete temporary channels"; + case TsPermission.b_channel_delete_flag_force: return "Force channel delete"; + case TsPermission.i_channel_delete_power: return "Delete channel power"; + case TsPermission.i_channel_needed_delete_power: return "Needed delete channel power"; + case TsPermission.b_channel_join_permanent: return "Join permanent channels"; + case TsPermission.b_channel_join_semi_permanent: return "Join semi-permanent channels"; + case TsPermission.b_channel_join_temporary: return "Join temporary channels"; + case TsPermission.b_channel_join_ignore_password: return "Join channel ignoring its password"; + case TsPermission.b_channel_join_ignore_maxclients: return "Ignore channels max clients limit"; + case TsPermission.i_channel_join_power: return "Channel join power"; + case TsPermission.i_channel_needed_join_power: return "Needed channel join power"; + case TsPermission.i_channel_subscribe_power: return "Channel subscribe power"; + case TsPermission.i_channel_needed_subscribe_power: return "Needed channel subscribe power"; + case TsPermission.i_channel_description_view_power: return "Channel description view power"; + case TsPermission.i_channel_needed_description_view_power: return "Needed channel needed description view power"; + case TsPermission.i_icon_id: return "Group icon identifier"; + case TsPermission.i_max_icon_filesize: return "Max icon filesize in bytes"; + case TsPermission.b_icon_manage: return "Enables icon management"; + case TsPermission.b_group_is_permanent: return "Group is permanent"; + case TsPermission.i_group_auto_update_type: return "Group auto-update type"; + case TsPermission.i_group_auto_update_max_value: return "Group auto-update max value"; + case TsPermission.i_group_sort_id: return "Group sort id"; + case TsPermission.i_group_show_name_in_tree: return "Show group name in tree depending on selected mode"; + case TsPermission.b_virtualserver_servergroup_list: return "List server groups"; + case TsPermission.b_virtualserver_servergroup_permission_list: return "List server group permissions"; + case TsPermission.b_virtualserver_servergroup_client_list: return "List clients from a server group"; + case TsPermission.b_virtualserver_channelgroup_list: return "List channel groups"; + case TsPermission.b_virtualserver_channelgroup_permission_list: return "List channel group permissions"; + case TsPermission.b_virtualserver_channelgroup_client_list: return "List clients from a channel group"; + case TsPermission.b_virtualserver_client_permission_list: return "List client permissions"; + case TsPermission.b_virtualserver_channel_permission_list: return "List channel permissions"; + case TsPermission.b_virtualserver_channelclient_permission_list: return "List channel client permissions"; + case TsPermission.b_virtualserver_servergroup_create: return "Create server groups"; + case TsPermission.b_virtualserver_channelgroup_create: return "Create channel groups"; + case TsPermission.i_group_modify_power: return "Group modify power"; + case TsPermission.i_group_needed_modify_power: return "Needed group modify power"; + case TsPermission.i_group_member_add_power: return "Group member add power"; + case TsPermission.i_group_needed_member_add_power: return "Needed group member add power"; + case TsPermission.i_group_member_remove_power: return "Group member delete power"; + case TsPermission.i_group_needed_member_remove_power: return "Needed group member delete power"; + case TsPermission.i_permission_modify_power: return "Permission modify power"; + case TsPermission.b_permission_modify_power_ignore: return "Ignore needed permission modify power"; + case TsPermission.b_virtualserver_servergroup_delete: return "Delete server groups"; + case TsPermission.b_virtualserver_channelgroup_delete: return "Delete channel groups"; + case TsPermission.i_client_permission_modify_power: return "Client permission modify power"; + case TsPermission.i_client_needed_permission_modify_power: return "Needed client permission modify power"; + case TsPermission.i_client_max_clones_uid: return "Max additional connections per client identity"; + case TsPermission.i_client_max_idletime: return "Max idle time in seconds"; + case TsPermission.i_client_max_avatar_filesize: return "Max avatar filesize in bytes"; + case TsPermission.i_client_max_channel_subscriptions: return "Max channel subscriptions"; + case TsPermission.b_client_is_priority_speaker: return "Client is priority speaker"; + case TsPermission.b_client_skip_channelgroup_permissions: return "Ignore channel group permissions"; + case TsPermission.b_client_force_push_to_talk: return "Force Push-To-Talk capture mode"; + case TsPermission.b_client_ignore_bans: return "Ignore bans"; + case TsPermission.b_client_ignore_antiflood: return "Ignore antiflood measurements"; + case TsPermission.b_client_issue_client_query_command: return "Issue query commands from client"; + case TsPermission.b_client_use_reserved_slot: return "Use an reserved slot"; + case TsPermission.b_client_use_channel_commander: return "Use channel commander"; + case TsPermission.b_client_request_talker: return "Allow to request talk power"; + case TsPermission.b_client_avatar_delete_other: return "Allow deletion of avatars from other clients"; + case TsPermission.b_client_is_sticky: return "Client will be sticked to current channel"; + case TsPermission.b_client_ignore_sticky: return "Client ignores sticky flag"; + case TsPermission.b_client_info_view: return "Retrieve client information"; + case TsPermission.b_client_permissionoverview_view: return "Retrieve client permissions overview"; + case TsPermission.b_client_permissionoverview_own: return "Retrieve clients own permissions overview"; + case TsPermission.b_client_remoteaddress_view: return "View client IP address and port"; + case TsPermission.i_client_serverquery_view_power: return "ServerQuery view power"; + case TsPermission.i_client_needed_serverquery_view_power: return "Needed ServerQuery view power"; + case TsPermission.b_client_custom_info_view: return "View custom fields"; + case TsPermission.i_client_kick_from_server_power: return "Client kick power from server"; + case TsPermission.i_client_needed_kick_from_server_power: return "Needed client kick power from server"; + case TsPermission.i_client_kick_from_channel_power: return "Client kick power from channel"; + case TsPermission.i_client_needed_kick_from_channel_power: return "Needed client kick power from channel"; + case TsPermission.i_client_ban_power: return "Client ban power"; + case TsPermission.i_client_needed_ban_power: return "Needed client ban power"; + case TsPermission.i_client_move_power: return "Client move power"; + case TsPermission.i_client_needed_move_power: return "Needed client move power"; + case TsPermission.i_client_complain_power: return "Complain power"; + case TsPermission.i_client_needed_complain_power: return "Needed complain power"; + case TsPermission.b_client_complain_list: return "Show complain list"; + case TsPermission.b_client_complain_delete_own: return "Delete own complains"; + case TsPermission.b_client_complain_delete: return "Delete complains"; + case TsPermission.b_client_ban_list: return "Show banlist"; + case TsPermission.b_client_ban_create: return "Add a ban"; + case TsPermission.b_client_ban_delete_own: return "Delete own bans"; + case TsPermission.b_client_ban_delete: return "Delete bans"; + case TsPermission.i_client_ban_max_bantime: return "Max bantime"; + case TsPermission.i_client_private_textmessage_power: return "Client private message power"; + case TsPermission.i_client_needed_private_textmessage_power: return "Needed client private message power"; + case TsPermission.b_client_server_textmessage_send: return "Send text messages to virtual server"; + case TsPermission.b_client_channel_textmessage_send: return "Send text messages to channel"; + case TsPermission.b_client_offline_textmessage_send: return "Send offline messages to clients"; + case TsPermission.i_client_talk_power: return "Client talk power"; + case TsPermission.i_client_needed_talk_power: return "Needed client talk power"; + case TsPermission.i_client_poke_power: return "Client poke power"; + case TsPermission.i_client_needed_poke_power: return "Needed client poke power"; + case TsPermission.b_client_set_flag_talker: return "Set the talker flag for clients and allow them to speak"; + case TsPermission.i_client_whisper_power: return "Client whisper power"; + case TsPermission.i_client_needed_whisper_power: return "Client needed whisper power"; + case TsPermission.b_client_modify_description: return "Edit a clients description"; + case TsPermission.b_client_modify_own_description: return "Allow client to edit own description"; + case TsPermission.b_client_modify_dbproperties: return "Edit a clients properties in the database"; + case TsPermission.b_client_delete_dbproperties: return "Delete a clients properties in the database"; + case TsPermission.b_client_create_modify_serverquery_login: return "Create or modify own ServerQuery account"; + case TsPermission.b_ft_ignore_password: return "Browse files without channel password"; + case TsPermission.b_ft_transfer_list: return "Retrieve list of running filetransfers"; + case TsPermission.i_ft_file_upload_power: return "File upload power"; + case TsPermission.i_ft_needed_file_upload_power: return "Needed file upload power"; + case TsPermission.i_ft_file_download_power: return "File download power"; + case TsPermission.i_ft_needed_file_download_power: return "Needed file download power"; + case TsPermission.i_ft_file_delete_power: return "File delete power"; + case TsPermission.i_ft_needed_file_delete_power: return "Needed file delete power"; + case TsPermission.i_ft_file_rename_power: return "File rename power"; + case TsPermission.i_ft_needed_file_rename_power: return "Needed file rename power"; + case TsPermission.i_ft_file_browse_power: return "File browse power"; + case TsPermission.i_ft_needed_file_browse_power: return "Needed file browse power"; + case TsPermission.i_ft_directory_create_power: return "Create directory power"; + case TsPermission.i_ft_needed_directory_create_power: return "Needed create directory power"; + case TsPermission.i_ft_quota_mb_download_per_client: return "Download quota per client in MByte"; + case TsPermission.i_ft_quota_mb_upload_per_client: return "Upload quota per client in MByte"; + default: throw Tools.UnhandledDefault(permid); + } + } + } +} \ No newline at end of file diff --git a/TS3Client/Generated/Permissions.tt b/TSLib/Generated/TsPermission.tt similarity index 72% rename from TS3Client/Generated/Permissions.tt rename to TSLib/Generated/TsPermission.tt index c5124fe8..62d4f4e7 100644 --- a/TS3Client/Generated/Permissions.tt +++ b/TSLib/Generated/TsPermission.tt @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -26,12 +26,12 @@ using (TextFieldParser parser = new TextFieldParser(declFilePath)) data.Add(parser.ReadFields()); } #> -namespace TS3Client -{ - using Helper; +using TSLib.Helper; +namespace TSLib +{ // Source: https://www.tsviewer.com/index.php?page=faq&id=12&newlanguage=en - public enum Ts3Permission + public enum TsPermission { // ReSharper disable InconsistentNaming, UnusedMember.Global undefined,<# foreach (var line in data.Skip(1)) { #> @@ -40,15 +40,15 @@ namespace TS3Client // ReSharper restore InconsistentNaming, UnusedMember.Global } - public static partial class Ts3PermissionHelper + public static partial class TsPermissionHelper { - public static string GetDescription(Ts3Permission permid) + public static string GetDescription(TsPermission permid) { switch (permid) { - case Ts3Permission.undefined: return "Undefined permission";<# foreach (var line in data.Skip(1)) { #> - case Ts3Permission.<#= line[0] #> : return "<#= line[1] #>";<# } #> - default: throw Util.UnhandledDefault(permid); + case TsPermission.undefined: return "Undefined permission";<# foreach (var line in data.Skip(1)) { #> + case TsPermission.<#= line[0] #>: return "<#= line[1] #>";<# } #> + default: throw Tools.UnhandledDefault(permid); } } } diff --git a/TS3Client/Generated/Util.ttinclude b/TSLib/Generated/Util.ttinclude similarity index 57% rename from TS3Client/Generated/Util.ttinclude rename to TSLib/Generated/Util.ttinclude index b03a20f9..70a08208 100644 --- a/TS3Client/Generated/Util.ttinclude +++ b/TSLib/Generated/Util.ttinclude @@ -38,32 +38,28 @@ public static bool NullType(string type) { const string ConversionSet = @"#pragma warning disable CS8019 // Ignore unused imports - using i8 = System.SByte; - using u8 = System.Byte; - using i16 = System.Int16; - using u16 = System.UInt16; - using i32 = System.Int32; - using u32 = System.UInt32; - using i64 = System.Int64; - using u64 = System.UInt64; - using f32 = System.Single; - using f64 = System.Double; - using str = System.String; +using i8 = System.SByte; +using u8 = System.Byte; +using i16 = System.Int16; +using u16 = System.UInt16; +using i32 = System.Int32; +using u32 = System.UInt32; +using i64 = System.Int64; +using u64 = System.UInt64; +using f32 = System.Single; +using f64 = System.Double; +using str = System.String; - using DateTime = System.DateTime; - using Duration = System.TimeSpan; - using DurationSeconds = System.TimeSpan; - using DurationMilliseconds = System.TimeSpan; - using SocketAddr = System.String; - using IpAddr = System.String; +using DateTime = System.DateTime; +using Duration = System.TimeSpan; +using DurationSeconds = System.TimeSpan; +using DurationMilliseconds = System.TimeSpan; +using SocketAddr = System.String; +using IpAddr = System.String; +using Ts3ErrorCode = TSLib.TsErrorCode; +using Ts3Permission = TSLib.TsPermission; - using Uid = System.String; - using ClientDbId = System.UInt64; - using ClientId = System.UInt16; - using ChannelId = System.UInt64; - using ServerGroupId = System.UInt64; - using ChannelGroupId = System.UInt64; - using IconHash = System.Int32; - using ConnectionId = System.UInt32; +using IconHash = System.Int32; +using ConnectionId = System.UInt32; #pragma warning restore CS8019"; #> \ No newline at end of file diff --git a/TS3Client/Generated/Versions.cs b/TSLib/Generated/Versions.cs similarity index 96% rename from TS3Client/Generated/Versions.cs rename to TSLib/Generated/Versions.cs index e1ee132a..a2c5ca2d 100644 --- a/TS3Client/Generated/Versions.cs +++ b/TSLib/Generated/Versions.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -17,11 +17,10 @@ +using System.Collections.Generic; -namespace TS3Client.Full +namespace TSLib.Full { - using System.Collections.Generic; - /// /// Describes a triple of version, platform and a cryptographical signature (usually distributed by "TeamSpeak Systems"). /// Each triple has to match and is not interchangeable with other triple parts. @@ -56,7 +55,7 @@ public VersionSign(string name, string platform, ClientPlatform platformType, st PlatformName = platform; } - public bool CheckValid() => Ts3Crypt.EdCheck(this); + public bool CheckValid() => TsCrypt.EdCheck(this); // ReSharper disable InconsistentNaming, UnusedMember.Global public static VersionSign VER_WIN_3_X_X { get; } = new VersionSign("3.?.? [Build: 5680278000] DEBUG", "Windows", ClientPlatform.Windows, "FaA10gndI44f19ZThYJX2OSM8ESrx9/vJ2uY+rON4enzzPsPR9kiPhsUXtMcC5szzTu6F/vLCiNNEhCr70CKCQ=="); @@ -64,12 +63,12 @@ public VersionSign(string name, string platform, ClientPlatform platformType, st public static VersionSign VER_LIN_3_X_X { get; } = new VersionSign("3.?.? [Build: 5680278000]", "Linux", ClientPlatform.Linux, "Hjd+N58Gv3ENhoKmGYy2bNRBsNNgm5kpiaQWxOj5HN2DXttG6REjymSwJtpJ8muC2gSwRuZi0R+8Laan5ts5CQ=="); public static VersionSign VER_IOS_3_X_X { get; } = new VersionSign("3.?.? [Build: 5680278000]", "iOS", ClientPlatform.Ios, "XrAf+Buq6Eb0ehEW/niFp06YX+nGGOS0Ke4MoUBzn+cX9q6G5C0A/d5XtgcNMe8r9jJgV/adIYVpsGS3pVlSAA=="); public static VersionSign VER_AND_3_X_X { get; } = new VersionSign("3.?.? [Build: 5680278000]", "Android", ClientPlatform.Android, "AWb948BY32Z7bpIyoAlQguSmxOGcmjESPceQe1DpW5IZ4+AW1KfTk2VUIYNfUPsxReDJMCtlhVKslzhR2lf0AA=="); - public static VersionSign VER_WIN_5_0_0 { get; } = new VersionSign("5.0.0-beta.9 [Build: 1571949734]", "Windows", ClientPlatform.Windows, "m04/1RI/r2RM6aHwRrvENlB0/Px6UnZwE2Lqo7IgQiB4MpFRmAgMu7g/gVB9NdWeMdhm5ulsM5lyYrcu7dyfBA=="); - public static VersionSign VER_MAC_5_0_0 { get; } = new VersionSign("5.0.0-beta.9 [Build: 1571949734]", "macOS", ClientPlatform.MacOs, "Y+qroeb12dDsCO+OTuoERTLgTUWihZ076cODxmo6iyNPPVS9xy1zq8hv1lEtc4UcC3EAqusBRS2ikKY6PnsdAQ=="); - public static VersionSign VER_LIN_5_0_0 { get; } = new VersionSign("5.0.0-beta.8 [Build: 1571671044]", "Linux", ClientPlatform.Linux, "ANXPHY1ZJlhEPAxngYChDm3XRU0XZntITzHlnZmBGRQOednkyAeZV+/DthWmYmgd0diJt1+qTnI3LNyB8PcUBA=="); - public static VersionSign VER_WIN_3_5_0 { get; } = new VersionSign("3.5.0 [Build: 1571080050]", "Windows", ClientPlatform.Windows, "4XIHXqnhLM6dRNFzdkNcbWxK/nX6iLBECc+6UJPX4PooBDyWOzul6yF1NKe/fg7bZDigZir1+OENu+O/tLwHDw=="); - public static VersionSign VER_MAC_3_5_0 { get; } = new VersionSign("3.5.0 [Build: 1571080050]", "OS X", ClientPlatform.MacOs, "64wKoleYoqOwO2t4bCHaQevSlv6HyvU5vpOF+yjuIrbqT1um1VoIQuL3L9BKt10Em2sd6VR1/IciCVuTSNd+BQ=="); - public static VersionSign VER_LIN_3_5_0 { get; } = new VersionSign("3.5.0 [Build: 1571080050]", "Linux", ClientPlatform.Linux, "glUVQf9FU2STvK2jzW328FkF1AhrPLj27sVo9TzEC+2Qg7phv5UpczW9SGTyp3H0KCofYfmTR6OW/W/1tCoeBQ=="); + public static VersionSign VER_MAC_5_0_0 { get; } = new VersionSign("5.0.0-test.3 [Build: 1573037288]", "macOS", ClientPlatform.MacOs, "+AqLKfemvASC0sYMz7/G1J5Yhf2/M3Te4/kYrsCgsq39zUxcViLJoJ/B0Poz3R5Q6vpugSpYvzGM4g2x3eBWCg=="); + public static VersionSign VER_WIN_5_0_0 { get; } = new VersionSign("5.0.0-beta.12 [Build: 1572868115]", "Windows", ClientPlatform.Windows, "w8i8mSIJ3y6wnFxSp8Gh6CRl3i8Q5owzchsHefG3kKYqzm2oyYsMDOsX61FVELbyiQoQea+QJZgGHY+MZCTCAQ=="); + public static VersionSign VER_WIN_3_5_0 { get; } = new VersionSign("3.5.0 [Build: 1572447082]", "Windows", ClientPlatform.Windows, "igVHTt+UUyZDHQpj8sEm3fGw+r+MDVkspQKPS/+fsxiVJr1vy2LpDQlpeZ7Yr6C6eECtCx7He58DXALTQD7JAg=="); + public static VersionSign VER_MAC_3_5_0 { get; } = new VersionSign("3.5.0 [Build: 1572447082]", "OS X", ClientPlatform.MacOs, "H20O0S/45t4TerVsFDNAOXr5yD5VrI7Ps3Nx0RFyPwubYUWXDiIj+LO4wu8wbuxag9V2VJKxtAax1OVWYZssAw=="); + public static VersionSign VER_LIN_3_5_0 { get; } = new VersionSign("3.5.0 [Build: 1572447082]", "Linux", ClientPlatform.Linux, "/5nJG7aByRNQKpUapx+cd7EQS80iEA9B+8z1bQgkrkopYfpYJD3PioUaI4oNCvdyq5I2ROheOcaHMTI9M7duAw=="); + public static VersionSign VER_LIN_5_0_0 { get; } = new VersionSign("5.0.0-beta.9 [Build: 1571949734]", "Linux", ClientPlatform.Linux, "b6ksNapJZndbf5qa1dcvqRgCdcgay0KQrnw8IYkPAXY/OvccuoJ/LUfg/a01nXbxbh45kp7h5gTk9l0L9NVPDQ=="); public static VersionSign VER_IOS_3_5_0 { get; } = new VersionSign("3.5.0 [Build: 1570810399]", "iOS", ClientPlatform.Ios, "RdN1+nQ2YRxJi66Yd8ta7CXUMsisWTU64qGNDTyuV2vdO/+vYQz6qOUaWjwDxzw3JAXr8FfCPcPaPo3suJj7AA=="); public static VersionSign VER_WIN_3_3_2 { get; } = new VersionSign("3.3.2 [Build: 1566767614]", "Windows", ClientPlatform.Windows, "fAjM3pPl95eGXrhFcsvCXgdI6Epr8XpafOJZlYbFws2VMllYy/4DUleSVzPuVqu/TCgrTaqb0wP+KANse3/vAw=="); public static VersionSign VER_MAC_3_3_2 { get; } = new VersionSign("3.3.2 [Build: 1566767614]", "OS X", ClientPlatform.MacOs, "JiKoykMS5BExaAEMpIYB3JRH+YturPua/Q+VTQxJGRi002zni9b8ReitLaJJQt2EMcNAbDpl57um4mekqFvGBA=="); diff --git a/TS3Client/Generated/Versions.tt b/TSLib/Generated/Versions.tt similarity index 94% rename from TS3Client/Generated/Versions.tt rename to TSLib/Generated/Versions.tt index 6eb7e064..170f0f88 100644 --- a/TS3Client/Generated/Versions.tt +++ b/TSLib/Generated/Versions.tt @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -41,11 +41,10 @@ string BuildToFld(string build) return string.Join("_", m.Groups[1].Value.Split('.').Select(x => x.Replace("?", "X"))); } #> +using System.Collections.Generic; -namespace TS3Client.Full +namespace TSLib.Full { - using System.Collections.Generic; - /// /// Describes a triple of version, platform and a cryptographical signature (usually distributed by "TeamSpeak Systems"). /// Each triple has to match and is not interchangeable with other triple parts. @@ -80,7 +79,7 @@ namespace TS3Client.Full PlatformName = platform; } - public bool CheckValid() => Ts3Crypt.EdCheck(this); + public bool CheckValid() => TsCrypt.EdCheck(this); // ReSharper disable InconsistentNaming, UnusedMember.Global<# var header = data[0]; diff --git a/TSLib/Helper/CommandErrorExtensions.cs b/TSLib/Helper/CommandErrorExtensions.cs new file mode 100644 index 00000000..8b1cfd8f --- /dev/null +++ b/TSLib/Helper/CommandErrorExtensions.cs @@ -0,0 +1,67 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace TSLib.Messages +{ + public partial class CommandError + { + public static CommandError TimeOut { get; } = Custom("Connection closed"); + + public static CommandError NoResult { get; } = Custom("Result is empty"); + + public static CommandError Parser { get; } = Custom("Result could not be parsed"); + + public static CommandError Custom(string message) => new CommandError { Id = TsErrorCode.custom_error, Message = message }; + + public string ErrorFormat() + { + if (MissingPermissionId != TsPermission.unknown && MissingPermissionId != TsPermission.undefined) + return $"{Id}: the command failed to execute: {Message} (missing permission:{MissingPermissionId})"; + else + return $"{Id}: the command failed to execute: {Message}"; + } + } + + /// Provides useful extension methods for error formatting. + public static class CommandErrorExtensions + { + public static R WrapSingle(in this R result) where T : IMessage + { + if (result.Ok) + return WrapSingle(result.Value); + return R.Err(result.Error); + } + + internal static R WrapSingle(this IEnumerable enu) where T : IMessage + { + var first = enu.FirstOrDefault(); + if (first != null) + return R.OkR(first); + return R.Err(CommandError.NoResult); + } + + public static R UnwrapNotification(in this R result) where T : class, IMessage + { + if (!result.Ok) + return result.Error; + return R.OkR((T[])result.Value.Notifications); + } + + public static R WrapInterface(in this R result) where TC : class, IMessage, TI + { + if (!result.Ok) + return result.Error; + return result.Value; + } + } +} diff --git a/TSLib/Helper/DebugUtil.cs b/TSLib/Helper/DebugUtil.cs new file mode 100644 index 00000000..4cdee43b --- /dev/null +++ b/TSLib/Helper/DebugUtil.cs @@ -0,0 +1,38 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Linq; + +namespace TSLib.Helper +{ + internal static class DebugUtil + { + public static string DebugToHex(byte[] bytes) => bytes is null ? "" : DebugToHex(bytes.AsSpan()); + + public static string DebugToHex(ReadOnlySpan bytes) + { + char[] c = new char[bytes.Length * 3]; + for (int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx) + { + byte b = (byte)(bytes[bx] >> 4); + c[cx] = (char)(b > 9 ? b - 10 + 'A' : b + '0'); + + b = (byte)(bytes[bx] & 0x0F); + c[++cx] = (char)(b > 9 ? b - 10 + 'A' : b + '0'); + c[++cx] = ' '; + } + return new string(c); + } + + public static byte[] DebugFromHex(string hex) + => hex.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => Convert.ToByte(x, 16)).ToArray(); + } +} diff --git a/TS3Client/Helper/LogId.cs b/TSLib/Helper/LogId.cs similarity index 78% rename from TS3Client/Helper/LogId.cs rename to TSLib/Helper/LogId.cs index 6cd26d5d..b8bb208d 100644 --- a/TS3Client/Helper/LogId.cs +++ b/TSLib/Helper/LogId.cs @@ -1,5 +1,5 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,21 +7,16 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Helper -{ - using System; +using System; +namespace TSLib.Helper +{ public readonly struct Id : IEquatable { - public static readonly Id Null = new Id(null); + public static readonly Id Null = new Id(-1); public int Value { get; } - private Id(object _) - { - Value = -1; - } - public Id(int id) { Value = id; diff --git a/TSLib/Helper/MissingEnumCaseException.cs b/TSLib/Helper/MissingEnumCaseException.cs new file mode 100644 index 00000000..6e84ec02 --- /dev/null +++ b/TSLib/Helper/MissingEnumCaseException.cs @@ -0,0 +1,22 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Runtime.Serialization; + +namespace TSLib.Helper +{ + [Serializable] + public sealed class MissingEnumCaseException : Exception + { + public MissingEnumCaseException(string enumTypeName, string valueName) : base($"The switch does not handle the value \"{valueName}\" from \"{enumTypeName}\".") { } + public MissingEnumCaseException(string message, Exception inner) : base(message, inner) { } + private MissingEnumCaseException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/TS3Client/Helper/NativeLibraryLoader.cs b/TSLib/Helper/NativeLibraryLoader.cs similarity index 86% rename from TS3Client/Helper/NativeLibraryLoader.cs rename to TSLib/Helper/NativeLibraryLoader.cs index c0a6191f..5a075224 100644 --- a/TS3Client/Helper/NativeLibraryLoader.cs +++ b/TSLib/Helper/NativeLibraryLoader.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Helper -{ - using System; - using System.IO; - using System.Runtime.InteropServices; +using System; +using System.IO; +using System.Runtime.InteropServices; +namespace TSLib.Helper +{ internal static class NativeLibraryLoader { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); @@ -22,7 +22,7 @@ internal static class NativeLibraryLoader public static bool DirectLoadLibrary(string lib, Action dummyLoad = null) { - if (Util.IsLinux) + if (Tools.IsLinux) { if (dummyLoad != null) { diff --git a/TS3Client/Helper/R.cs b/TSLib/Helper/R.cs similarity index 93% rename from TS3Client/Helper/R.cs rename to TSLib/Helper/R.cs index d09f9b1f..25314890 100644 --- a/TS3Client/Helper/R.cs +++ b/TSLib/Helper/R.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System.Diagnostics; + namespace System { - using System.Diagnostics; - /// /// Provides a safe alternative to Exceptions for error and result wrapping. /// This type represents either success or an error + message. @@ -184,4 +184,21 @@ public R WithValue(TSuccess value) return new R(isError, value, Error); } } + + public static class RExtensions + { + public static R Flat(this R, TError> boxedR) + { + if (!boxedR.Ok) + return boxedR.Error; + return boxedR.Value; + } + + public static E Flat(this R, TError> boxedR) + { + if (!boxedR.Ok) + return boxedR.Error; + return boxedR.Value; + } + } } diff --git a/TSLib/Helper/SpanExtensions.cs b/TSLib/Helper/SpanExtensions.cs new file mode 100644 index 00000000..83a30c25 --- /dev/null +++ b/TSLib/Helper/SpanExtensions.cs @@ -0,0 +1,52 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Text; + +namespace TSLib.Helper +{ + public static class SpanExtensions + { + public static string NewUtf8String(this ReadOnlySpan span) + { +#if NETCOREAPP2_2 || NETCOREAPP3_0 + return Encoding.UTF8.GetString(span); +#else + return Encoding.UTF8.GetString(span.ToArray()); +#endif + } + + public static string NewUtf8String(this Span span) => ((ReadOnlySpan)span).NewUtf8String(); + + public static ReadOnlySpan Trim(this ReadOnlySpan span, byte elem) => span.TrimStart(elem).TrimEnd(elem); + + public static ReadOnlySpan TrimStart(this ReadOnlySpan span, byte elem) + { + int start = 0; + for (; start < span.Length; start++) + { + if (span[start] != elem) + break; + } + return span.Slice(start); + } + + public static ReadOnlySpan TrimEnd(this ReadOnlySpan span, byte elem) + { + int end = span.Length - 1; + for (; end >= 0; end--) + { + if (span[end] != elem) + break; + } + return span.Slice(0, end + 1); + } + } +} diff --git a/TS3Client/Helper/SpanSplitter.cs b/TSLib/Helper/SpanSplitter.cs similarity index 85% rename from TS3Client/Helper/SpanSplitter.cs rename to TSLib/Helper/SpanSplitter.cs index 2ab233b5..f70dcb59 100644 --- a/TS3Client/Helper/SpanSplitter.cs +++ b/TSLib/Helper/SpanSplitter.cs @@ -1,5 +1,5 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Helper -{ - using System; - using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; +namespace TSLib.Helper +{ internal struct SpanSplitter where T : IEquatable { public bool HasNext => NextIndex >= 0; diff --git a/TSLib/Helper/Tools.cs b/TSLib/Helper/Tools.cs new file mode 100644 index 00000000..0036872c --- /dev/null +++ b/TSLib/Helper/Tools.cs @@ -0,0 +1,73 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TSLib.Helper +{ + public static class Tools + { + public static bool IsLinux + { + get + { + int p = (int)Environment.OSVersion.Platform; + return p == 4 || p == 6 || p == 128; + } + } + + public static IEnumerable GetFlags(this Enum input) => Enum.GetValues(input.GetType()).Cast().Where(input.HasFlag); + + // Encoding + + public static Encoding Utf8Encoder { get; } = new UTF8Encoding(false, false); + + // Time + + public static readonly DateTime UnixTimeStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + public static uint ToUnix(this DateTime dateTime) => (uint)(dateTime - UnixTimeStart).TotalSeconds; + + public static DateTime FromUnix(uint unixTimestamp) => UnixTimeStart.AddSeconds(unixTimestamp); + + public static uint UnixNow => (uint)(DateTime.UtcNow - UnixTimeStart).TotalSeconds; + + public static DateTime Now => DateTime.UtcNow; + + // Random + + public static Random Random { get; } = new Random(); + + public static T PickRandom(IReadOnlyList collection) + { + int pick = Random.Next(0, collection.Count); + return collection[pick]; + } + + // Math + + public static TimeSpan Min(TimeSpan a, TimeSpan b) => a < b ? a : b; + public static TimeSpan Max(TimeSpan a, TimeSpan b) => a > b ? a : b; + + public static int MathMod(int x, int mod) => (x % mod + mod) % mod; + + public static float Clamp(float value, float min, float max) => Math.Min(Math.Max(value, min), max); + public static int Clamp(int value, int min, int max) => Math.Min(Math.Max(value, min), max); + + // Generic + + public static void SetLogId(Id id) => SetLogId(id.ToString()); + public static void SetLogId(string id) => NLog.MappedDiagnosticsContext.Set("BotId", id); + + public static Exception UnhandledDefault(T value) where T : struct { return new MissingEnumCaseException(typeof(T).Name, value.ToString()); } + } +} diff --git a/TS3Client/LazyNotification.cs b/TSLib/LazyNotification.cs similarity index 81% rename from TS3Client/LazyNotification.cs rename to TSLib/LazyNotification.cs index 6de90a81..6c997709 100644 --- a/TS3Client/LazyNotification.cs +++ b/TSLib/LazyNotification.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using Messages; - using System; - using System.Linq; +using System; +using System.Linq; +using TSLib.Messages; +namespace TSLib +{ public readonly struct LazyNotification { public readonly INotification[] Notifications; diff --git a/TS3Client/MessageProcessor.cs b/TSLib/MessageProcessor.cs similarity index 91% rename from TS3Client/MessageProcessor.cs rename to TSLib/MessageProcessor.cs index ecbd0e09..75e8150b 100644 --- a/TS3Client/MessageProcessor.cs +++ b/TSLib/MessageProcessor.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using Helper; - using Messages; - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using TSLib.Helper; +using TSLib.Messages; +namespace TSLib +{ internal abstract class BaseMessageProcessor { protected static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); @@ -92,7 +92,7 @@ protected BaseMessageProcessor(Func findTypeOfNotifica } var result = Deserializer.GenerateSingleNotification(lineDataPart, NotificationType.CommandError); - var errorStatus = result.Ok ? (CommandError)result.Value : Util.CustomError("Invalid Error code"); + var errorStatus = result.Ok ? (CommandError)result.Value : CommandError.Custom("Invalid Error code"); return PushMessageInternal(errorStatus, ntfyType); } @@ -157,12 +157,12 @@ public override void DropQueue() lock (waitBlockLock) { foreach (var wb in requestDict.Values) - wb.SetAnswer(Util.TimeOutCommandError); + wb.SetAnswer(CommandError.TimeOut); requestDict.Clear(); foreach (var block in dependingBlocks) { - block?.ForEach(wb => wb.SetAnswer(Util.TimeOutCommandError)); + block?.ForEach((Action)(wb => wb.SetAnswer(CommandError.TimeOut))); block?.Clear(); } } @@ -198,7 +198,7 @@ public void EnqueueRequest(WaitBlock waitBlock) public override void DropQueue() { while (!requestQueue.IsEmpty && requestQueue.TryDequeue(out var waitBlock)) - waitBlock.SetAnswer(Util.TimeOutCommandError); + waitBlock.SetAnswer(CommandError.TimeOut); } } } diff --git a/TS3Client/Messages/BaseTypes.cs b/TSLib/Messages/BaseTypes.cs similarity index 77% rename from TS3Client/Messages/BaseTypes.cs rename to TSLib/Messages/BaseTypes.cs index 31063aed..dee4d32d 100644 --- a/TS3Client/Messages/BaseTypes.cs +++ b/TSLib/Messages/BaseTypes.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,11 +7,11 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Messages -{ - using System; - using System.Collections.Generic; +using System; +using System.Collections.Generic; +namespace TSLib.Messages +{ public interface IMessage { void SetField(string name, ReadOnlySpan value, Deserializer ser); diff --git a/TS3Client/Messages/Deserializer.cs b/TSLib/Messages/Deserializer.cs similarity index 95% rename from TS3Client/Messages/Deserializer.cs rename to TSLib/Messages/Deserializer.cs index 85fc987f..361553da 100644 --- a/TS3Client/Messages/Deserializer.cs +++ b/TSLib/Messages/Deserializer.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,12 +7,12 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Messages -{ - using Helper; - using System; - using System.Collections.Generic; +using System; +using System.Collections.Generic; +using TSLib.Helper; +namespace TSLib.Messages +{ public class Deserializer { protected static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); diff --git a/TSLib/Messages/MessageAdditions.cs b/TSLib/Messages/MessageAdditions.cs new file mode 100644 index 00000000..d1d122c4 --- /dev/null +++ b/TSLib/Messages/MessageAdditions.cs @@ -0,0 +1,20 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +namespace TSLib.Messages +{ + public interface IChannelCreateResponse + { + ChannelId ChannelId { get; set; } + } + + partial class ChannelCreateResponse : IChannelCreateResponse { } + + partial class ChannelCreated : IChannelCreateResponse { } +} diff --git a/TS3Client/Messages/PermissionTransform.cs b/TSLib/Messages/PermissionTransform.cs similarity index 50% rename from TS3Client/Messages/PermissionTransform.cs rename to TSLib/Messages/PermissionTransform.cs index 6e342c48..aba40624 100644 --- a/TS3Client/Messages/PermissionTransform.cs +++ b/TSLib/Messages/PermissionTransform.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,40 +7,40 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Messages -{ - using System; +using System; +namespace TSLib.Messages +{ public interface IPermissionTransform { - ushort GetId(Ts3Permission name); - Ts3Permission GetName(ushort id); + ushort GetId(TsPermission name); + TsPermission GetName(ushort id); } public class DummyPermissionTransform : IPermissionTransform { public static readonly IPermissionTransform Instance = new DummyPermissionTransform(); - public ushort GetId(Ts3Permission name) => 0; - public Ts3Permission GetName(ushort id) => Ts3Permission.undefined; + public ushort GetId(TsPermission name) => 0; + public TsPermission GetName(ushort id) => TsPermission.undefined; } public class TablePermissionTransform : IPermissionTransform { - private readonly Ts3Permission[] nameTable; + private readonly TsPermission[] nameTable; private readonly ushort[] idTable; - public TablePermissionTransform(Ts3Permission[] nameTable) + public TablePermissionTransform(TsPermission[] nameTable) { this.nameTable = nameTable; - idTable = new ushort[Enum.GetValues(typeof(Ts3Permission)).Length]; + idTable = new ushort[Enum.GetValues(typeof(TsPermission)).Length]; for (ushort i = 0; i < nameTable.Length; i++) { idTable[(int)nameTable[i]] = i; } } - public ushort GetId(Ts3Permission name) => (int)name < idTable.Length ? idTable[(int)name] : (ushort)0; - public Ts3Permission GetName(ushort id) => id < nameTable.Length ? nameTable[id] : Ts3Permission.undefined; + public ushort GetId(TsPermission name) => (int)name < idTable.Length ? idTable[(int)name] : (ushort)0; + public TsPermission GetName(ushort id) => id < nameTable.Length ? nameTable[id] : TsPermission.undefined; } } diff --git a/TS3Client/Messages/ResponseDictionary.cs b/TSLib/Messages/ResponseDictionary.cs similarity index 90% rename from TS3Client/Messages/ResponseDictionary.cs rename to TSLib/Messages/ResponseDictionary.cs index 519641c4..ef6a3f79 100644 --- a/TS3Client/Messages/ResponseDictionary.cs +++ b/TSLib/Messages/ResponseDictionary.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,15 +7,15 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Messages -{ - using Helper; - using System; - using System.Collections; - using System.Collections.Generic; - using KeyType = System.String; - using ValueType = System.String; +using System; +using System.Collections; +using System.Collections.Generic; +using TSLib.Helper; +using KeyType = System.String; +using ValueType = System.String; +namespace TSLib.Messages +{ public class ResponseDictionary : IDictionary, IResponse { private readonly IDictionary data; diff --git a/TS3Client/Properties.cs b/TSLib/Properties.cs similarity index 100% rename from TS3Client/Properties.cs rename to TSLib/Properties.cs diff --git a/TS3Client/Query/Ts3QueryClient.cs b/TSLib/Query/TsQueryClient.cs similarity index 73% rename from TS3Client/Query/Ts3QueryClient.cs rename to TSLib/Query/TsQueryClient.cs index b6614013..1deb669a 100644 --- a/TS3Client/Query/Ts3QueryClient.cs +++ b/TSLib/Query/TsQueryClient.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,23 +7,23 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Query +using System; +using System.Buffers; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using TSLib.Commands; +using TSLib.Full.Book; +using TSLib.Helper; +using TSLib.Messages; +using CmdR = System.E; + +namespace TSLib.Query { - using Commands; - using Helper; - using Messages; - using System; - using System.Buffers; - using System.IO; - using System.IO.Pipelines; - using System.Linq; - using System.Net.Sockets; - using System.Threading; - using System.Threading.Tasks; - using ChannelIdT = System.UInt64; - using CmdR = System.E; - - public sealed class Ts3QueryClient : Ts3BaseFunctions + public sealed class TsQueryClient : TsBaseFunctions { private readonly object sendQueueLock = new object(); private TcpClient tcpClient; @@ -47,7 +47,7 @@ public sealed class Ts3QueryClient : Ts3BaseFunctions public override event EventHandler OnConnected; public override event EventHandler OnDisconnected; - public Ts3QueryClient() + public TsQueryClient() { connecting = false; msgProc = new SyncMessageProcessor(MessageHelper.GetToClientNotificationType); @@ -56,8 +56,8 @@ public Ts3QueryClient() public override void Connect(ConnectionData conData) { - if (!TsDnsResolver.TryResolve(conData.Address, out remoteAddress, TsDnsResolver.Ts3QueryDefaultPort)) - throw new Ts3Exception("Could not read or resolve address."); + if (!TsDnsResolver.TryResolve(conData.Address, out remoteAddress, TsDnsResolver.TsQueryDefaultPort)) + throw new TsException("Could not read or resolve address."); try { @@ -69,15 +69,15 @@ public override void Connect(ConnectionData conData) ConnectionData = conData; tcpStream = tcpClient.GetStream(); - tcpReader = new StreamReader(tcpStream, Util.Encoder); - tcpWriter = new StreamWriter(tcpStream, Util.Encoder) { NewLine = "\n" }; + tcpReader = new StreamReader(tcpStream, Tools.Utf8Encoder); + tcpWriter = new StreamWriter(tcpStream, Tools.Utf8Encoder) { NewLine = "\n" }; - if(tcpReader.ReadLine() != "TS3") - throw new Ts3Exception("Protocol violation. The stream must start with 'TS3'"); + if (tcpReader.ReadLine() != "TS3") + throw new TsException("Protocol violation. The stream must start with 'TS3'"); if (string.IsNullOrEmpty(tcpReader.ReadLine())) tcpReader.ReadLine(); } - catch (SocketException ex) { throw new Ts3Exception("Could not connect.", ex); } + catch (SocketException ex) { throw new TsException("Could not connect.", ex); } finally { connecting = false; } cts = new CancellationTokenSource(); @@ -188,11 +188,11 @@ private void InvokeEvent(LazyNotification lazyNotification) // special case NotificationType.CommandError: break; case NotificationType.Unknown: - default: throw Util.UnhandledDefault(lazyNotification.NotifyType); + default: throw Tools.UnhandledDefault(lazyNotification.NotifyType); } } - public override R Send(Ts3Command com) // Synchronous + public override R Send(TsCommand com) // Synchronous { using (var wb = new WaitBlock(msgProc.Deserializer, false)) { @@ -206,7 +206,7 @@ public override R Send(Ts3Command com) // Synchronous } } - public override R SendHybrid(Ts3Command com, NotificationType type) + public override R SendHybrid(TsCommand com, NotificationType type) => Send(com); private void SendRaw(string data) @@ -224,45 +224,58 @@ private void SendRaw(string data) public CmdR RegisterNotification(TextMessageTargetMode target) => RegisterNotification(TargetTypeString[(int)target], null); - public CmdR RegisterNotificationChannel(ChannelIdT? channel = null) + public CmdR RegisterNotificationChannel(ChannelId? channel = null) => RegisterNotification(TargetTypeString[(int)ReasonIdentifier.Channel], channel); public CmdR RegisterNotificationServer() => RegisterNotification(TargetTypeString[(int)ReasonIdentifier.Server], null); - private CmdR RegisterNotification(string target, ChannelIdT? channel) - => Send(new Ts3Command("servernotifyregister") { + private CmdR RegisterNotification(string target, ChannelId? channel) + => SendVoid(new TsCommand("servernotifyregister") { { "event", target }, { "id", channel }, }); public CmdR Login(string username, string password) - => Send(new Ts3Command("login") { + => SendVoid(new TsCommand("login") { { "client_login_name", username }, { "client_login_password", password }, }); public CmdR UseServer(int serverId) - => Send(new Ts3Command("use") { + => SendVoid(new TsCommand("use") { { "sid", serverId }, }); public CmdR UseServerPort(ushort port) - => Send(new Ts3Command("use") { + => SendVoid(new TsCommand("use") { { "port", port }, }); // Splitted base commands + public override R ChannelCreate(string name, + string namePhonetic = null, string topic = null, string description = null, string password = null, + Codec? codec = null, int? codecQuality = null, int? codecLatencyFactor = null, bool? codecEncrypted = null, + int? maxClients = null, int? maxFamilyClients = null, bool? maxClientsUnlimited = null, + bool? maxFamilyClientsUnlimited = null, bool? maxFamilyClientsInherited = null, ChannelId? order = null, + ChannelId? parent = null, ChannelType? type = null, TimeSpan? deleteDelay = null, int? neededTalkPower = null) + => Send(ChannelOp("channelcreate", null, name, namePhonetic, topic, description, + password, codec, codecQuality, codecLatencyFactor, codecEncrypted, + maxClients, maxFamilyClients, maxClientsUnlimited, maxFamilyClientsUnlimited, + maxFamilyClientsInherited, order, parent, type, deleteDelay, neededTalkPower)) + .WrapSingle() + .WrapInterface(); + public override R ServerGroupAdd(string name, GroupType? type = null) - => Send(new Ts3Command("servergroupadd") { + => Send(new TsCommand("servergroupadd") { { "name", name }, { "type", (int?)type } }).WrapSingle(); - public override R FileTransferInitUpload(ChannelIdT channelId, string path, string channelPassword, + public override R FileTransferInitUpload(ChannelId channelId, string path, string channelPassword, ushort clientTransferId, long fileSize, bool overwrite, bool resume) - => Send(new Ts3Command("ftinitupload") { + => Send(new TsCommand("ftinitupload") { { "cid", channelId }, { "name", path }, { "cpw", channelPassword }, @@ -272,9 +285,9 @@ public override R FileTransferInitUpload(ChannelIdT ch { "resume", resume } }).WrapSingle(); - public override R FileTransferInitDownload(ChannelIdT channelId, string path, string channelPassword, + public override R FileTransferInitDownload(ChannelId channelId, string path, string channelPassword, ushort clientTransferId, long seek) - => Send(new Ts3Command("ftinitdownload") { + => Send(new TsCommand("ftinitdownload") { { "cid", channelId }, { "name", path }, { "cpw", channelPassword }, diff --git a/TSLib/ReSpeak.png b/TSLib/ReSpeak.png new file mode 100644 index 00000000..90a2915b Binary files /dev/null and b/TSLib/ReSpeak.png differ diff --git a/TS3Client/TS3Client.csproj b/TSLib/TSLib.csproj similarity index 55% rename from TS3Client/TS3Client.csproj rename to TSLib/TSLib.csproj index cb089ad3..80cc2456 100644 --- a/TS3Client/TS3Client.csproj +++ b/TSLib/TSLib.csproj @@ -3,8 +3,8 @@ Library net472;netcoreapp2.2;netcoreapp3.0;netstandard2.0 7.3 - TS3Client - TS3Client + TSLib + TSLib AnyCPU false @@ -14,31 +14,48 @@ https://github.com/Splamy/TS3AudioBot.git git true + false + Splamy.TSLib + Splamy, Flakebi, TSLib Contributors + TSLib + + A free and open source TeamSpeak 3 and 5 client library. + Can connect as normal voice or query client and is optimized for performance. + + Splamy, Flakebi, TSLib Contributors + OSL-3.0 + https://github.com/Splamy/TS3AudioBot + TeamSpeak, TS3, TS5, Voice, Client, Query, Normal, Communication + 1.0.1 + ReSpeak.png + + https://splamy.de/static/ReSpeak.png - + - + + - + TextTemplatingFileGenerator - Ts3CommandSugar.cs + TsCommand.gen.cs TextTemplatingFileGenerator Book.cs - + TextTemplatingFileGenerator - Errors.cs + TsErrorCode.cs TextTemplatingFileGenerator @@ -48,34 +65,47 @@ TextTemplatingFileGenerator Messages.cs - + TextTemplatingFileGenerator - Permissions.cs + TsPermission.cs - + TextTemplatingFileGenerator - Ts3FullEvents.cs + TsFullClient.gen.cs + + + TextTemplatingFileGenerator + Types.gen.cs TextTemplatingFileGenerator Versions.cs + + TextTemplatingFileGenerator + TsBaseFunctions.gen.cs + - + + True + True + TsCommand.gen.tt + + True True - Ts3CommandSugar.tt + TsFullClient.gen.tt True True Book.tt - + True True - Errors.tt + TsErrorCode.tt True @@ -87,20 +117,25 @@ True Messages.tt - + True True - Permissions.tt + TsPermission.tt - + True True - Ts3FullEvents.tt + Versions.tt - + True True - Versions.tt + TsBaseFunctions.gen.tt + + + True + True + Types.gen.tt diff --git a/TS3Client/Ts3BaseClient.cs b/TSLib/TsBaseFunctions.cs similarity index 54% rename from TS3Client/Ts3BaseClient.cs rename to TSLib/TsBaseFunctions.cs index 62495734..2e5bb40b 100644 --- a/TS3Client/Ts3BaseClient.cs +++ b/TSLib/TsBaseFunctions.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,26 +7,22 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using TSLib.Commands; +using TSLib.Full.Book; +using TSLib.Messages; +using CmdR = System.E; + +namespace TSLib { - using Commands; - using Helper; - using Messages; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using ChannelIdT = System.UInt64; - using ClientDbIdT = System.UInt64; - using ClientIdT = System.UInt16; - using CmdR = System.E; - using ServerGroupIdT = System.UInt64; - using Uid = System.String; - public delegate void NotifyEventHandler(object sender, IEnumerable e) where TEventArgs : INotification; /// A shared function base between the query and full client. - public abstract class Ts3BaseFunctions : IDisposable + public abstract partial class TsBaseFunctions : IDisposable { protected readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); /// When this client receives any visible message. @@ -59,72 +55,50 @@ public abstract class Ts3BaseFunctions : IDisposable public abstract void Dispose(); #region NETWORK SEND - - /// Creates a new command. - /// The command name. - public R Send(string command) - => Send(new Ts3Command(command)); - - /// Creates a new command. - /// The command name. - /// The parameters to be added to this command. - /// See , or for more information. - public R Send(string command, params ICommandPart[] parameter) - => Send(new Ts3Command(command, parameter.ToList())); - - /// Creates a new command. - /// The type to deserialize the response to. - /// The command name. - /// Returns an enumeration of the deserialized and split up in objects data. - public R Send(string command) where T : IResponse, new() - => Send(new Ts3Command(command)); - - /// Creates a new command. - /// The type to deserialize the response to. - /// The command name. - /// The parameters to be added to this command. - /// Returns an enumeration of the deserialized and split up in objects data. - public R Send(string command, params ICommandPart[] parameter) where T : IResponse, new() - => Send(command, parameter.ToList()); - - /// Creates a new command. - /// The type to deserialize the response to. - /// The command name. - /// The parameters to be added to this command. - /// Returns an enumeration of the deserialized and split up in objects data. - public R Send(string command, List parameter) where T : IResponse, new() - => Send(new Ts3Command(command, parameter)); - /// Sends a command to the server. Commands look exactly like query commands and mostly also behave identically. /// The type to deserialize the response to. Use for unknown response data. /// The raw command to send. /// Returns an enumeration of the deserialized and split up in objects data. - public abstract R Send(Ts3Command com) where T : IResponse, new(); + public abstract R Send(TsCommand com) where T : IResponse, new(); /// /// Sends a command and depending on the client type waits for a response or notification. - /// NOTE: Do not use this method unless you are sure the ts3 command fits the criteria. + /// NOTE: Do not use this method unless you are sure the ts command fits the criteria. /// /// The command to send. /// The notification type to wait for and serialize to. - public abstract R SendHybrid(Ts3Command com, NotificationType type) where T : class, IResponse, new(); - + public abstract R SendHybrid(TsCommand com, NotificationType type) where T : class, IResponse, new(); + +#pragma warning disable CS1998 + public virtual async Task> SendAsync(TsCommand com) where T : IResponse, new() + => throw new NotImplementedException(); + public virtual async Task> SendHybridAsync(TsCommand com, NotificationType type) where T : class, IResponse, new() + => throw new NotImplementedException(); +#pragma warning restore CS1998 #endregion + private string GenPassword(string password) + { + if (ClientType == ClientType.Full && password != null) + return Full.TsCrypt.HashPassword(password); + else + return password; + } + #region UNIVERSAL COMMANDS public CmdR ChangeName(string newName) - => Send(new Ts3Command("clientupdate") { + => SendVoid(new TsCommand("clientupdate") { { "client_nickname", newName }, }); public CmdR ChangeBadges(string newBadges) - => Send(new Ts3Command("clientupdate") { + => SendVoid(new TsCommand("clientupdate") { { "client_badges", newBadges }, }); - public CmdR ChangeDescription(string newDescription, ClientIdT clientId) - => Send(new Ts3Command("clientedit") { + public CmdR ChangeDescription(string newDescription, ClientId clientId) + => SendVoid(new TsCommand("clientedit") { { "clid", clientId }, { "client_description", newDescription }, }); @@ -133,8 +107,8 @@ public CmdR ChangeDescription(string newDescription, ClientIdT clientId) public R WhoAmI() // Q ? => Send("whoami").WrapSingle(); - public CmdR SendPrivateMessage(string message, ClientIdT clientId) - => SendMessage(message, TextMessageTargetMode.Private, clientId); + public CmdR SendPrivateMessage(string message, ClientId clientId) + => SendMessage(message, TextMessageTargetMode.Private, clientId.Value); public CmdR SendChannelMessage(string message) => SendMessage(message, TextMessageTargetMode.Channel, 0); @@ -147,7 +121,7 @@ public CmdR SendServerMessage(string message, ulong serverId) /// If targetmode is set to or , /// the target parameter will be ignored and a message is sent to the current channel or server respectively. public CmdR SendMessage(string message, TextMessageTargetMode target, ulong id) - => Send(new Ts3Command("sendtextmessage") { + => SendVoid(new TsCommand("sendtextmessage") { { "targetmode", (int)target }, { "target", id }, { "msg", message }, @@ -155,49 +129,124 @@ public CmdR SendMessage(string message, TextMessageTargetMode target, ulong id) /// Sends a text message to all clients on all virtual servers in the TeamSpeak 3 Server instance. public CmdR SendGlobalMessage(string message) - => Send(new Ts3Command("gm") { + => SendVoid(new TsCommand("gm") { { "msg", message }, }); - public CmdR KickClientFromServer(ClientIdT clientId, string reasonMsg = null) + public CmdR KickClientFromServer(ClientId clientId, string reasonMsg = null) => KickClient(new[] { clientId }, ReasonIdentifier.Server, reasonMsg); - public CmdR KickClientFromServer(ClientIdT[] clientIds, string reasonMsg = null) + public CmdR KickClientFromServer(ClientId[] clientIds, string reasonMsg = null) => KickClient(clientIds, ReasonIdentifier.Server, reasonMsg); - public CmdR KickClientFromChannel(ClientIdT clientId, string reasonMsg = null) + public CmdR KickClientFromChannel(ClientId clientId, string reasonMsg = null) => KickClient(new[] { clientId }, ReasonIdentifier.Channel, reasonMsg); - public CmdR KickClientFromChannel(ClientIdT[] clientIds, string reasonMsg = null) + public CmdR KickClientFromChannel(ClientId[] clientIds, string reasonMsg = null) => KickClient(clientIds, ReasonIdentifier.Channel, reasonMsg); /// Kicks one or more clients specified with clid from their currently joined channel or from the server, depending on . /// The reasonmsg parameter specifies a text message sent to the kicked clients. /// This parameter is optional and may only have a maximum of 40 characters. - public CmdR KickClient(ClientIdT[] clientIds, ReasonIdentifier reasonId, string reasonMsg = null) - => Send(new Ts3Command("clientkick") { + public CmdR KickClient(ClientId[] clientIds, ReasonIdentifier reasonId, string reasonMsg = null) + => SendVoid(new TsCommand("clientkick") { { "reasonid", (int)reasonId }, { "clid", clientIds }, { "reasonmsg", reasonMsg }, }); - public CmdR BanClient(ClientIdT clientId, TimeSpan? duration = null, string reasonMsg = null) + public CmdR BanClient(ushort clientId, TimeSpan? duration = null, string reasonMsg = null) => BanClient(new CommandParameter("clid", clientId), reasonMsg, duration); - public CmdR BanClient(Uid clientUid = null, TimeSpan? duration = null, string reasonMsg = null) + public CmdR BanClient(Uid clientUid = default, TimeSpan? duration = null, string reasonMsg = null) => BanClient(new CommandParameter("uid", clientUid), reasonMsg, duration); private CmdR BanClient(ICommandPart clientIdentifier, string reasonMsg = null, TimeSpan? duration = null) - => Send(new Ts3Command("banclient") { + => SendVoid(new TsCommand("banclient") { clientIdentifier, { "banreason", reasonMsg }, { "time", duration?.TotalSeconds }, }); + public CmdR ChannelEdit(ChannelId channelId, + string name = null, string namePhonetic = null, string topic = null, string description = null, + string password = null, Codec? codec = null, int? codecQuality = null, int? codecLatencyFactor = null, + bool? codecEncrypted = null, int? maxClients = null, int? maxFamilyClients = null, bool? maxClientsUnlimited = null, + bool? maxFamilyClientsUnlimited = null, bool? maxFamilyClientsInherited = null, ChannelId? order = null, + ChannelType? type = null, TimeSpan? deleteDelay = null, int? neededTalkPower = null) + => SendVoid(ChannelOp("channeledit", channelId, name, namePhonetic, topic, description, + password, codec, codecQuality, codecLatencyFactor, codecEncrypted, + maxClients, maxFamilyClients, maxClientsUnlimited, maxFamilyClientsUnlimited, + maxFamilyClientsInherited, order, null, type, deleteDelay, neededTalkPower)); + + public abstract R ChannelCreate(string name, + string namePhonetic = null, string topic = null, string description = null, string password = null, + Codec? codec = null, int? codecQuality = null, int? codecLatencyFactor = null, bool? codecEncrypted = null, + int? maxClients = null, int? maxFamilyClients = null, bool? maxClientsUnlimited = null, + bool? maxFamilyClientsUnlimited = null, bool? maxFamilyClientsInherited = null, ChannelId? order = null, + ChannelId? parent = null, ChannelType? type = null, TimeSpan? deleteDelay = null, int? neededTalkPower = null); + + protected TsCommand ChannelOp(string op, ChannelId? channelId, + string name, string namePhonetic, string topic, string description, string password, + Codec? codec, int? codecQuality, int? codecLatencyFactor, bool? codecEncrypted, int? maxClients, + int? maxFamilyClients, bool? maxClientsUnlimited, bool? maxFamilyClientsUnlimited, bool? maxFamilyClientsInherited, + ChannelId? order, ChannelId? parent, ChannelType? type, TimeSpan? deleteDelay, int? neededTalkPower) + => new TsCommand(op) { + { "cid", channelId }, + { "cpid", parent }, + { "channel_name", name }, + { "channel_name_phonetic", namePhonetic }, + { "channel_topic", topic }, + { "channel_description", description }, + { "channel_password", GenPassword(password) }, + { "channel_codec", (byte?)codec }, + { "channel_codec_quality", codecQuality }, + { "channel_codec_latency_factor", codecLatencyFactor }, + { "channel_codec_is_unencrypted", !codecEncrypted }, + { "channel_maxclients", maxClients }, + { "channel_maxfamilyclients", maxFamilyClients }, + { "channel_flag_maxclients_unlimited", maxClientsUnlimited }, + { "channel_flag_maxfamilyclients_unlimited", maxFamilyClientsUnlimited }, + { "channel_flag_maxfamilyclients_inherited", maxFamilyClientsInherited }, + { "channel_order", order }, + { "channel_flag_permanent", type == null ? (bool?)null : type == ChannelType.Permanent }, + { "channel_flag_semi_permanent", type == null ? (bool?)null : type == ChannelType.SemiPermanent }, + { "channel_delete_delay", (ulong?)deleteDelay?.TotalSeconds }, // TODO Check + { "channel_needed_talk_power", neededTalkPower }, + }; + + /// Displays detailed configuration information about a channel including ID, topic, description, etc. + /// For detailed information, see Channel Properties. + public R ChannelInfo(ChannelId channelId) + => Send(new TsCommand("channelinfo") { + { "cid", channelId }, + }); + + /// Displays a list of channels created on a virtual server including their ID, order, name, etc. + /// The output can be modified using several command options. public R ChannelList(ChannelListOptions options = 0) => Send("channellist", new CommandOption(options)); + public CmdR ChannelMove(ChannelId channelId, ChannelId? parent = null, ChannelId? order = null) + => SendVoid(new TsCommand("channelmove") { + { "cid", channelId }, + { "cpid", parent }, + { "order", order }, + }); + + public CmdR ChannelDelete(ChannelId channelId, bool force = false) + => SendVoid(new TsCommand("channeldelete") { + { "cid", channelId }, + { "force", force }, + }); + + /// Displays detailed database information about a client including unique ID, creation date, etc. + public R ClientDbInfo(ClientDbId clientDbId) + => Send(new TsCommand("clientdbinfo") { + { "cldbid", clientDbId }, + }).WrapSingle(); + /// Displays a list of clients online on a virtual server including their ID, nickname, status flags, etc. /// The output can be modified using several command options. /// Please note that the output will only contain clients which are currently in channels you're able to subscribe to. @@ -205,87 +254,81 @@ public R ClientList(ClientListOptions options = 0) => Send("clientlist", new CommandOption(options)); - /// Displays detailed database information about a client including unique ID, creation date, etc. - public R ClientDbInfo(ClientDbIdT clientDbId) - => Send(new Ts3Command("clientdbinfo") { - { "cldbid", clientDbId }, - }).WrapSingle(); - /// Displays detailed configuration information about a client including unique ID, nickname, client version, etc. - public R ClientInfo(ClientIdT clientId) - => Send(new Ts3Command("clientinfo") { + public R ClientInfo(ClientId clientId) + => Send(new TsCommand("clientinfo") { { "clid", clientId }, }).WrapSingle(); /// Use a token key and gain access to a server or channel group. /// Please note that the server will automatically delete the token after it has been used. public CmdR PrivilegeKeyUse(string key) - => Send(new Ts3Command("privilegekeyuse") { + => SendVoid(new TsCommand("privilegekeyuse") { { "token", key }, }); /// Adds a set of specified permissions to the server group specified with . /// Multiple permissions can be added by providing the four parameters of each permission. - public CmdR ServerGroupAddPerm(ServerGroupIdT serverGroupId, Ts3Permission permission, int permissionValue, + public CmdR ServerGroupAddPerm(ServerGroupId serverGroupId, TsPermission permission, int permissionValue, bool permissionNegated, bool permissionSkip) - => Send(new Ts3Command("servergroupaddperm") { + => SendVoid(new TsCommand("servergroupaddperm") { { "sgid", serverGroupId }, { "permvalue", permissionValue }, { "permnegated", permissionNegated }, { "permskip", permissionSkip }, - Ts3PermissionHelper.GetAsParameter(Deserializer.PermissionTransform, permission), + TsPermissionHelper.GetAsParameter(Deserializer.PermissionTransform, permission), }); /// Adds a set of specified permissions to the server group specified with . /// Multiple permissions can be added by providing the four parameters of each permission. - public CmdR ServerGroupAddPerm(ServerGroupIdT serverGroupId, Ts3Permission[] permission, int[] permissionValue, + public CmdR ServerGroupAddPerm(ServerGroupId serverGroupId, TsPermission[] permission, int[] permissionValue, bool[] permissionNegated, bool[] permissionSkip) - => Send(new Ts3Command("servergroupaddperm") { + => SendVoid(new TsCommand("servergroupaddperm") { { "sgid", serverGroupId }, { "permvalue", permissionValue }, { "permnegated", permissionNegated }, { "permskip", permissionSkip }, - Ts3PermissionHelper.GetAsMultiParameter(Deserializer.PermissionTransform, permission), + TsPermissionHelper.GetAsMultiParameter(Deserializer.PermissionTransform, permission), }); /// Adds a client to the server group specified with . Please note that a /// client cannot be added to default groups or template groups. - public CmdR ServerGroupAddClient(ServerGroupIdT serverGroupId, ClientDbIdT clientDbId) - => Send(new Ts3Command("servergroupaddclient") { + public CmdR ServerGroupAddClient(ServerGroupId serverGroupId, ClientDbId clientDbId) + => SendVoid(new TsCommand("servergroupaddclient") { { "sgid", serverGroupId }, { "cldbid", clientDbId }, }); /// Removes a client specified with cldbid from the server group specified with . - public CmdR ServerGroupDelClient(ServerGroupIdT serverGroupId, ClientDbIdT clientDbId) - => Send(new Ts3Command("servergroupdelclient") { + public CmdR ServerGroupDelClient(ServerGroupId serverGroupId, ClientDbId clientDbId) + => SendVoid(new TsCommand("servergroupdelclient") { { "sgid", serverGroupId }, { "cldbid", clientDbId }, }); public CmdR FileTransferStop(ushort serverTransferId, bool delete) - => Send(new Ts3Command("ftstop") { + => SendVoid(new TsCommand("ftstop") { { "serverftfid", serverTransferId }, { "delete", delete }, }); - public CmdR FileTransferDeleteFile(ChannelIdT channelId, string[] path, string channelPassword = "") - => Send(new Ts3Command("ftdeletefile") { + public CmdR FileTransferDeleteFile(ChannelId channelId, string[] path, string channelPassword = "") + => SendVoid(new TsCommand("ftdeletefile") { { "cid", channelId }, { "cpw", channelPassword }, { "name", path }, }); - public CmdR FileTransferCreateDirectory(ChannelIdT channelId, string path, string channelPassword = "") - => Send(new Ts3Command("ftcreatedir") { + public CmdR FileTransferCreateDirectory(ChannelId channelId, string path, string channelPassword = "") + => SendVoid(new TsCommand("ftcreatedir") { { "cid", channelId }, { "dirname", path }, { "cpw", channelPassword }, }); - public CmdR FileTransferRenameFile(ChannelIdT channelId, string oldName, string channelPassword, string newName, - ChannelIdT? targetChannel = null, string targetChannelPassword = "") - => Send(new Ts3Command("ftrenamefile") { + public CmdR FileTransferRenameFile(ChannelId channelId, string oldName, string channelPassword, string newName, + ChannelId? targetChannel = null, string targetChannelPassword = "") + => SendVoid(new TsCommand("ftrenamefile") { { "cid", channelId }, { "oldname", oldName }, { "newname", newName }, @@ -296,30 +339,30 @@ public CmdR FileTransferRenameFile(ChannelIdT channelId, string oldName, string public CmdR UploadAvatar(System.IO.Stream image) { - var token = FileTransferManager.UploadFile(image, 0, "/avatar", overwrite: true, createMd5: true); + var token = FileTransferManager.UploadFile(image, ChannelId.Null, "/avatar", overwrite: true, createMd5: true); if (!token.Ok) return token.Error; token.Value.Wait(); if (token.Value.Status != TransferStatus.Done) - return Util.CustomError("Avatar upload failed"); + return CommandError.Custom("Avatar upload failed"); var md5 = string.Concat(token.Value.Md5Sum.Select(x => x.ToString("x2"))); - return Send(new Ts3Command("clientupdate") { { "client_flag_avatar", md5 } }); + return SendVoid(new TsCommand("clientupdate") { { "client_flag_avatar", md5 } }); } /// Deletes the avatar of a user. /// Can be called without uid to delete own avatar. /// The client uid where the avatar should be deleted. - public CmdR DeleteAvatar(string clientUid = null) + public CmdR DeleteAvatar(Uid? clientUid = null) { string path = "/avatar_" + clientUid; - return FileTransferDeleteFile(0, new[] { path }); + return FileTransferDeleteFile(ChannelId.Null, new[] { path }); } - public CmdR ClientMove(ClientIdT clientId, ChannelIdT channelId, string channelPassword = null) - => Send(new Ts3Command("clientmove") { + public CmdR ClientMove(ClientId clientId, ChannelId channelId, string channelPassword = null) + => SendVoid(new TsCommand("clientmove") { { "clid", clientId }, { "cid", channelId }, - { "cpw", ClientType == ClientType.Full && channelPassword != null ? Full.Ts3Crypt.HashPassword(channelPassword) : channelPassword }, + { "cpw", GenPassword(channelPassword) }, }); /// Creates a new server group using the name specified with and return its ID. @@ -327,70 +370,70 @@ public CmdR ClientMove(ClientIdT clientId, ChannelIdT channelId, string channelP public abstract R ServerGroupAdd(string name, GroupType? type = null); /// Displays all server groups the client specified with is currently residing in. - public R ServerGroupsByClientDbId(ClientDbIdT clDbId) - => SendHybrid(new Ts3Command("servergroupsbyclientid") + public R ServerGroupsByClientDbId(ClientDbId clDbId) + => SendHybrid(new TsCommand("servergroupsbyclientid") { { "cldbid", clDbId } }, NotificationType.ServerGroupsByClientId); - public abstract R FileTransferInitUpload(ChannelIdT channelId, string path, string channelPassword, + public abstract R FileTransferInitUpload(ChannelId channelId, string path, string channelPassword, ushort clientTransferId, long fileSize, bool overwrite, bool resume); - public abstract R FileTransferInitDownload(ChannelIdT channelId, string path, string channelPassword, + public abstract R FileTransferInitDownload(ChannelId channelId, string path, string channelPassword, ushort clientTransferId, long seek); public R FileTransferList() - => SendHybrid(new Ts3Command("ftlist"), + => SendHybrid(new TsCommand("ftlist"), NotificationType.FileTransfer); - public R FileTransferGetFileList(ChannelIdT channelId, string path, string channelPassword = "") - => SendHybrid(new Ts3Command("ftgetfilelist") { + public R FileTransferGetFileList(ChannelId channelId, string path, string channelPassword = "") + => SendHybrid(new TsCommand("ftgetfilelist") { { "cid", channelId }, { "path", path }, - { "cpw", channelPassword } + { "cpw", channelPassword } // TODO CHECK ? }, NotificationType.FileList); - public R FileTransferGetFileInfo(ChannelIdT channelId, string[] path, string channelPassword = "") - => SendHybrid(new Ts3Command("ftgetfileinfo") { + public R FileTransferGetFileInfo(ChannelId channelId, string[] path, string channelPassword = "") + => SendHybrid(new TsCommand("ftgetfileinfo") { { "cid", channelId }, - { "cpw", channelPassword }, + { "cpw", channelPassword }, // TODO CHECK ? { "name", path } }, NotificationType.FileInfo); public R GetClientDbIdFromUid(Uid clientUid) - => SendHybrid(new Ts3Command("clientgetdbidfromuid") { + => SendHybrid(new TsCommand("clientgetdbidfromuid") { { "cluid", clientUid } }, NotificationType.ClientDbIdFromUid).WrapSingle(); - public R GetClientUidFromClientId(ClientIdT clientId) - => SendHybrid(new Ts3Command("clientgetuidfromclid") { - { "clid", clientId } + public R GetClientUidFromClientId(ClientId clientId) + => SendHybrid(new TsCommand("clientgetuidfromclid") { + { "clid", clientId } }, NotificationType.ClientUidFromClid).WrapSingle(); public R GetClientNameFromUid(Uid clientUid) - => SendHybrid(new Ts3Command("clientgetnamefromuid") + => SendHybrid(new TsCommand("clientgetnamefromuid") { { "cluid", clientUid } }, NotificationType.ClientNameFromUid).WrapSingle(); public R GetClientIds(Uid clientUid) - => SendHybrid(new Ts3Command("clientgetids") { + => SendHybrid(new TsCommand("clientgetids") { { "cluid", clientUid } }, NotificationType.ClientIds); - public R PermOverview(ClientDbIdT clientDbId, ChannelIdT channelId, params Ts3Permission[] permission) - => SendHybrid(new Ts3Command("permoverview") { + public R PermOverview(ClientDbId clientDbId, ChannelId channelId, params TsPermission[] permission) + => SendHybrid(new TsCommand("permoverview") { { "cldbid", clientDbId }, { "cid", channelId }, - Ts3PermissionHelper.GetAsMultiParameter(Deserializer.PermissionTransform, permission) + TsPermissionHelper.GetAsMultiParameter(Deserializer.PermissionTransform, permission) }, NotificationType.PermOverview); public R PermissionList() - => SendHybrid(new Ts3Command("permissionlist"), + => SendHybrid(new TsCommand("permissionlist"), NotificationType.PermList); public R GetServerConnectionInfo() - => SendHybrid(new Ts3Command("serverrequestconnectioninfo"), + => SendHybrid(new TsCommand("serverrequestconnectioninfo"), NotificationType.ServerConnectionInfo).WrapSingle(); #endregion diff --git a/TSLib/TsBaseFunctions.gen.cs b/TSLib/TsBaseFunctions.gen.cs new file mode 100644 index 00000000..bf6171a1 --- /dev/null +++ b/TSLib/TsBaseFunctions.gen.cs @@ -0,0 +1,184 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . +// + + + + + + + +using System; +using System.Threading.Tasks; +using TSLib.Commands; +using TSLib.Messages; + +namespace TSLib +{ + partial class TsBaseFunctions + { + + /// + /// Sends a TS-command. + /// + /// + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// + /// + public R Send(string command, params ICommandPart[] parameter) + => Send(new TsCommand(command, parameter)); + + /// + /// Sends a TS-command. + /// + /// + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// + /// Returns an enumeration of the deserialized and split up in objects data. + public R Send(string command, params ICommandPart[] parameter) where T : IResponse, new() + => Send(new TsCommand(command, parameter)); + + /// + /// Sends a TS-command. + /// + /// The response (if any) is not deserialized and is discarded. + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// + /// + public E SendVoid(string command, params ICommandPart[] parameter) + => Send(new TsCommand(command, parameter)); + + /// + /// Sends a TS-command. + /// + /// The response (if any) is not deserialized and is discarded. + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// + /// + public E SendVoid(TsCommand com) + => Send(com); + + /// + /// Sends a TS-command. + /// This will send a normal query-command when connected via query client. When connected as a full client the first specified notification is used as the response. + /// + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// The notification type to wait for and serialize to when called from the full client. + /// + public R SendHybrid(string command, NotificationType type, params ICommandPart[] parameter) + => SendHybrid(new TsCommand(command, parameter), type); + + /// + /// Sends a TS-command. + /// This will send a normal query-command when connected via query client. When connected as a full client the first specified notification is used as the response. + /// + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// The notification type to wait for and serialize to when called from the full client. + /// Returns an enumeration of the deserialized and split up in objects data. + public R SendHybrid(string command, NotificationType type, params ICommandPart[] parameter) where T : class, IResponse, new() + => SendHybrid(new TsCommand(command, parameter), type); + + /// + /// Sends a TS-command. + /// + /// + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// + /// + public async Task> SendAsync(string command, params ICommandPart[] parameter) + => await SendAsync(new TsCommand(command, parameter)).ConfigureAwait(false); + + /// + /// Sends a TS-command. + /// + /// + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// + /// Returns an enumeration of the deserialized and split up in objects data. + public async Task> SendAsync(string command, params ICommandPart[] parameter) where T : IResponse, new() + => await SendAsync(new TsCommand(command, parameter)).ConfigureAwait(false); + + /// + /// Sends a TS-command. + /// + /// The response (if any) is not deserialized and is discarded. + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// + /// + public async Task> SendVoidAsync(string command, params ICommandPart[] parameter) + => await SendAsync(new TsCommand(command, parameter)).ConfigureAwait(false); + + /// + /// Sends a TS-command. + /// + /// The response (if any) is not deserialized and is discarded. + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// + /// + public async Task> SendVoidAsync(TsCommand com) + => await SendAsync(com).ConfigureAwait(false); + + /// + /// Sends a TS-command. + /// This will send a normal query-command when connected via query client. When connected as a full client the first specified notification is used as the response. + /// + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// The notification type to wait for and serialize to when called from the full client. + /// + public async Task> SendHybridAsync(string command, NotificationType type, params ICommandPart[] parameter) + => await SendHybridAsync(new TsCommand(command, parameter), type).ConfigureAwait(false); + + /// + /// Sends a TS-command. + /// This will send a normal query-command when connected via query client. When connected as a full client the first specified notification is used as the response. + /// + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// The notification type to wait for and serialize to when called from the full client. + /// Returns an enumeration of the deserialized and split up in objects data. + public async Task> SendHybridAsync(string command, NotificationType type, params ICommandPart[] parameter) where T : class, IResponse, new() + => await SendHybridAsync(new TsCommand(command, parameter), type).ConfigureAwait(false); + + } +} \ No newline at end of file diff --git a/TSLib/TsBaseFunctions.gen.tt b/TSLib/TsBaseFunctions.gen.tt new file mode 100644 index 00000000..798a91c3 --- /dev/null +++ b/TSLib/TsBaseFunctions.gen.tt @@ -0,0 +1,87 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . +// + +<#@ template debug="true" hostSpecific="true" language="C#" #> +<#@ output extension=".cs" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.IO" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +using System; +using System.Threading.Tasks; +using TSLib.Commands; +using TSLib.Messages; + +namespace TSLib +{ + partial class TsBaseFunctions + { +<# + foreach(var isAsync in new[]{ false, true }) + foreach(var isHybrid in new[]{ false, true }) + foreach(var hasReturn in new[]{ "dict", "T", "void" }) + { + if (isHybrid && hasReturn == "void") continue; + + Func Ret = (a, b, c) => { + return + hasReturn == "dict" ? a : + hasReturn == "T" ? b : + hasReturn == "void" ? c : + "!ERR!"; + }; + + var retSuffix = Ret("", "", "Void"); + var callTitle = "Send" + retSuffix + (isHybrid ? "Hybrid" : "") + (isAsync ? "Async" : ""); + var retType = Ret( + "R", + "R", + "E"); + if (isAsync) retType = $"async Task<{retType}>"; + var genMod = Ret("", "", ""); + var genSuffix = Ret("", $" where T : {(isHybrid ? "class, " : "")}IResponse, new()", ""); + var fwdPrefix = isAsync ? "await " : ""; + var fwdModInfix = Ret("", "", ""); + var fwdTitle = "Send" + (isHybrid ? "Hybrid" : "") + (isAsync ? "Async" : ""); + var fwdSuffix = isAsync ? ".ConfigureAwait(false)" : ""; + foreach(var vparam in new[]{ "cp", "com" }) + { + if(vparam == "com" && callTitle == fwdTitle) continue; + + Func Par = (a, b) => { + return + vparam == "cp" ? a : + vparam == "com" ? b : + "!ERR!"; + }; + var hybridParam = isHybrid ? ", NotificationType type" : ""; + var callParam = Par( + $"string command{hybridParam}, params ICommandPart[] parameter", + $"TsCommand com{hybridParam}"); + var fwdParam = Par( + $"new TsCommand(command, parameter)", + $"com"); + if (isHybrid) fwdParam += ", type"; +#> + /// + /// Sends a TS-command. + /// <# if(isHybrid) { #>This will send a normal query-command when connected via query client. When connected as a full client the first specified notification is used as the response.<# } #> + /// <# if(hasReturn == "void") { #>The response (if any) is not deserialized and is discarded.<# } #> + /// + /// The command name. + /// The parameters to be added to this command. + /// See , or for more information. + /// <# if(isHybrid) { #>The notification type to wait for and serialize to when called from the full client.<# } #> + /// <# if(hasReturn == "T") { #>Returns an enumeration of the deserialized and split up in objects data.<# } #> + public <#= retType #> <#= callTitle #><#= genMod #>(<#= callParam #>)<#= genSuffix #> + => <#= fwdPrefix #><#= fwdTitle #><#= fwdModInfix #>(<#= fwdParam #>)<#= fwdSuffix #>; +<# }} #> + } +} \ No newline at end of file diff --git a/TS3Client/TsDnsResolver.cs b/TSLib/TsDnsResolver.cs similarity index 94% rename from TS3Client/TsDnsResolver.cs rename to TSLib/TsDnsResolver.cs index 93653e85..d10e9306 100644 --- a/TS3Client/TsDnsResolver.cs +++ b/TSLib/TsDnsResolver.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,24 +7,24 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client +using Heijden.Dns.Portable; +using Heijden.DNS; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; + +namespace TSLib { - using Heijden.Dns.Portable; - using Heijden.DNS; - using System; - using System.Collections.Generic; - using System.IO; - using System.Net; - using System.Net.Sockets; - using System.Text; - using System.Text.RegularExpressions; - /// Provides methods to resolve TSDNS, SRV redirects and nicknames public static class TsDnsResolver { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); - public const ushort Ts3VoiceDefaultPort = 9987; - public const ushort Ts3QueryDefaultPort = 10011; + public const ushort TsVoiceDefaultPort = 9987; + public const ushort TsQueryDefaultPort = 10011; private const ushort TsDnsDefaultPort = 41144; private const string DnsPrefixTcp = "_tsdns._tcp."; private const string DnsPrefixUdp = "_ts3._udp."; @@ -51,7 +51,7 @@ public static class TsDnsResolver /// The ip address if successfully resolved. Otherwise a dummy. /// The default port when no port is specified with the address or the resolved address. /// Whether the resolve was succesful. - public static bool TryResolve(string address, out IPEndPoint endPoint, ushort defaultPort = Ts3VoiceDefaultPort) + public static bool TryResolve(string address, out IPEndPoint endPoint, ushort defaultPort = TsVoiceDefaultPort) { if (address is null) throw new ArgumentNullException(nameof(address)); @@ -86,7 +86,7 @@ public static bool TryResolve(string address, out IPEndPoint endPoint, ushort de var hasUriPort = !string.IsNullOrEmpty(uri.GetComponents(UriComponents.Port, UriFormat.Unescaped)); // Try resolve udp prefix - // Under this address we'll get ts3 voice server + // Under this address we'll get ts voice server var srvEndPoint = ResolveSrv(Resolver, DnsPrefixUdp + uri.Host); if (srvEndPoint != null) { diff --git a/TS3Client/Ts3Enums.cs b/TSLib/TsEnums.cs similarity index 96% rename from TS3Client/Ts3Enums.cs rename to TSLib/TsEnums.cs index c584d0ac..f6df3a1c 100644 --- a/TS3Client/Ts3Enums.cs +++ b/TSLib/TsEnums.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,10 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using System; +using System; +namespace TSLib +{ /* * Most important Id datatypes: * @@ -82,6 +82,8 @@ public enum Reason ChannelUpdated, ServerOrChannelEdited, ServerShutdown, + + SocketError = 1000, } public enum GroupWhisperType : byte @@ -164,7 +166,7 @@ public enum Codec : byte ///stereo, 16bit, 48kHz, optimized for music OpusMusic, - /// PCM S16LE 1/2 Channel (TS3Client extension; not supported by normal TeamSpeak 3 clients!) + /// PCM S16LE 1/2 Channel (TSLib extension; not supported by normal TeamSpeak clients!) Raw = 127, } diff --git a/TS3Client/Ts3Exceptions.cs b/TSLib/TsException.cs similarity index 52% rename from TS3Client/Ts3Exceptions.cs rename to TSLib/TsException.cs index 7fac36da..3dc107f1 100644 --- a/TS3Client/Ts3Exceptions.cs +++ b/TSLib/TsException.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,14 +7,14 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using System; +using System; +namespace TSLib +{ /// Generel exeption when methods within the client fail. - public class Ts3Exception : Exception + public class TsException : Exception { - public Ts3Exception(string message) : base(message) { } - public Ts3Exception(string message, Exception innerException) : base(message, innerException) { } + public TsException(string message) : base(message) { } + public TsException(string message, Exception innerException) : base(message, innerException) { } } } diff --git a/TS3Client/Ts3Permission.cs b/TSLib/TsPermissionHelper.cs similarity index 83% rename from TS3Client/Ts3Permission.cs rename to TSLib/TsPermissionHelper.cs index 39553274..9c7e6841 100644 --- a/TS3Client/Ts3Permission.cs +++ b/TSLib/TsPermissionHelper.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,16 +7,16 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using System.Linq; - using TS3Client.Commands; - using TS3Client.Helper; - using TS3Client.Messages; +using System.Linq; +using TSLib.Commands; +using TSLib.Helper; +using TSLib.Messages; - public static partial class Ts3PermissionHelper +namespace TSLib +{ + public static partial class TsPermissionHelper { - public static ICommandPart GetAsParameter(IPermissionTransform permissionTransform, Ts3Permission permission) + public static ICommandPart GetAsParameter(IPermissionTransform permissionTransform, TsPermission permission) { if (permissionTransform is null || permissionTransform == DummyPermissionTransform.Instance) return new CommandParameter("permsid", permission.ToString()); @@ -24,7 +24,7 @@ public static ICommandPart GetAsParameter(IPermissionTransform permissionTransfo return new CommandParameter("permid", permissionTransform.GetId(permission)); } - public static ICommandPart GetAsMultiParameter(IPermissionTransform permissionTransform, params Ts3Permission[] permission) + public static ICommandPart GetAsMultiParameter(IPermissionTransform permissionTransform, params TsPermission[] permission) { if (permissionTransform is null || permissionTransform == DummyPermissionTransform.Instance) return new CommandMultiParameter("permsid", permission.Select(x => x.ToString())); @@ -60,7 +60,7 @@ public static PermOverview Combine(this PermOverview perm, PermOverview other) return other; default: - throw Util.UnhandledDefault(perm.PermissionType); + throw Tools.UnhandledDefault(perm.PermissionType); } case PermissionType.GlobalClient: @@ -83,7 +83,7 @@ public static PermOverview Combine(this PermOverview perm, PermOverview other) return other; default: - throw Util.UnhandledDefault(perm.PermissionType); + throw Tools.UnhandledDefault(perm.PermissionType); } case PermissionType.Channel: @@ -101,7 +101,7 @@ public static PermOverview Combine(this PermOverview perm, PermOverview other) return other; default: - throw Util.UnhandledDefault(perm.PermissionType); + throw Tools.UnhandledDefault(perm.PermissionType); } case PermissionType.ChannelGroup: @@ -119,7 +119,7 @@ public static PermOverview Combine(this PermOverview perm, PermOverview other) return other; default: - throw Util.UnhandledDefault(perm.PermissionType); + throw Tools.UnhandledDefault(perm.PermissionType); } case PermissionType.ChannelClient: @@ -135,11 +135,11 @@ public static PermOverview Combine(this PermOverview perm, PermOverview other) return perm.PermissionValue > other.PermissionValue ? perm : other; default: - throw Util.UnhandledDefault(perm.PermissionType); + throw Tools.UnhandledDefault(perm.PermissionType); } default: - throw Util.UnhandledDefault(perm.PermissionType); + throw Tools.UnhandledDefault(perm.PermissionType); } } } diff --git a/TSLib/Types.cs b/TSLib/Types.cs new file mode 100644 index 00000000..6c139880 --- /dev/null +++ b/TSLib/Types.cs @@ -0,0 +1,29 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . + +using TSLib.Full; + +namespace TSLib +{ + public partial struct Uid + { + public static bool IsValid(string uid) + { + if (uid == "anonymous" || uid == "serveradmin") + return true; + var result = TsCrypt.Base64Decode(uid); + return result.Ok && result.Value.Length == 20; + } + } + + public partial struct ChannelId + { + public string ToPath() => $"/{Value}"; + } +} diff --git a/TSLib/Types.gen.cs b/TSLib/Types.gen.cs new file mode 100644 index 00000000..54d7a65b --- /dev/null +++ b/TSLib/Types.gen.cs @@ -0,0 +1,199 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . +// + + + + + + + + +using Newtonsoft.Json; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace TSLib +{ + + [StructLayout(LayoutKind.Sequential, Pack=0)] + [DebuggerDisplay("{Value, nq}")] + [JsonConverter(typeof(Uid.Converter))] + public readonly partial struct Uid : IEquatable + { + public static readonly Uid Null = default; + public static Uid To(string v) => new Uid(v); + + public string Value { get; } + public Uid(string value) { if (value == null) throw new ArgumentNullException(nameof(value)); Value = value; } + public static explicit operator Uid(string v) => new Uid(v); + public static bool operator ==(Uid a, Uid b) => a.Value == b.Value; + public static bool operator !=(Uid a, Uid b) => a.Value != b.Value; + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => obj is Uid c && Value.Equals(c.Value); + public override string ToString() => Value.ToString(); + + public bool Equals(Uid other) => Value.Equals(other.Value, StringComparison.Ordinal); + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, Uid value, JsonSerializer serializer) + => writer.WriteValue(value.Value); + public override Uid ReadJson(JsonReader reader, Type objectType, Uid existingValue, bool hasExistingValue, JsonSerializer serializer) + => new Uid(reader.ReadAsString()); + } + } + + [StructLayout(LayoutKind.Sequential, Pack=0)] + [DebuggerDisplay("{Value, nq}")] + [JsonConverter(typeof(ClientDbId.Converter))] + public readonly partial struct ClientDbId : IFormattable, IEquatable + { + public static readonly ClientDbId Null = default; + public static ClientDbId To(ulong v) => new ClientDbId(v); + + public ulong Value { get; } + public ClientDbId(ulong value) { Value = value; } + public static explicit operator ClientDbId(ulong v) => new ClientDbId(v); + public static bool operator ==(ClientDbId a, ClientDbId b) => a.Value == b.Value; + public static bool operator !=(ClientDbId a, ClientDbId b) => a.Value != b.Value; + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => obj is ClientDbId c && Value.Equals(c.Value); + public override string ToString() => Value.ToString(); + + public bool Equals(ClientDbId other) => Value.Equals(other.Value); + public string ToString(string format, IFormatProvider formatProvider) => Value.ToString(format, formatProvider); + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, ClientDbId value, JsonSerializer serializer) + => writer.WriteValue(value.Value); + public override ClientDbId ReadJson(JsonReader reader, Type objectType, ClientDbId existingValue, bool hasExistingValue, JsonSerializer serializer) + => new ClientDbId(ulong.Parse(reader.ReadAsString())); + } + } + + [StructLayout(LayoutKind.Sequential, Pack=0)] + [DebuggerDisplay("{Value, nq}")] + [JsonConverter(typeof(ClientId.Converter))] + public readonly partial struct ClientId : IFormattable, IEquatable + { + public static readonly ClientId Null = default; + public static ClientId To(ushort v) => new ClientId(v); + + public ushort Value { get; } + public ClientId(ushort value) { Value = value; } + public static explicit operator ClientId(ushort v) => new ClientId(v); + public static bool operator ==(ClientId a, ClientId b) => a.Value == b.Value; + public static bool operator !=(ClientId a, ClientId b) => a.Value != b.Value; + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => obj is ClientId c && Value.Equals(c.Value); + public override string ToString() => Value.ToString(); + + public bool Equals(ClientId other) => Value.Equals(other.Value); + public string ToString(string format, IFormatProvider formatProvider) => Value.ToString(format, formatProvider); + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, ClientId value, JsonSerializer serializer) + => writer.WriteValue(value.Value); + public override ClientId ReadJson(JsonReader reader, Type objectType, ClientId existingValue, bool hasExistingValue, JsonSerializer serializer) + => new ClientId(ushort.Parse(reader.ReadAsString())); + } + } + + [StructLayout(LayoutKind.Sequential, Pack=0)] + [DebuggerDisplay("{Value, nq}")] + [JsonConverter(typeof(ChannelId.Converter))] + public readonly partial struct ChannelId : IFormattable, IEquatable + { + public static readonly ChannelId Null = default; + public static ChannelId To(ulong v) => new ChannelId(v); + + public ulong Value { get; } + public ChannelId(ulong value) { Value = value; } + public static explicit operator ChannelId(ulong v) => new ChannelId(v); + public static bool operator ==(ChannelId a, ChannelId b) => a.Value == b.Value; + public static bool operator !=(ChannelId a, ChannelId b) => a.Value != b.Value; + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => obj is ChannelId c && Value.Equals(c.Value); + public override string ToString() => Value.ToString(); + + public bool Equals(ChannelId other) => Value.Equals(other.Value); + public string ToString(string format, IFormatProvider formatProvider) => Value.ToString(format, formatProvider); + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, ChannelId value, JsonSerializer serializer) + => writer.WriteValue(value.Value); + public override ChannelId ReadJson(JsonReader reader, Type objectType, ChannelId existingValue, bool hasExistingValue, JsonSerializer serializer) + => new ChannelId(ulong.Parse(reader.ReadAsString())); + } + } + + [StructLayout(LayoutKind.Sequential, Pack=0)] + [DebuggerDisplay("{Value, nq}")] + [JsonConverter(typeof(ServerGroupId.Converter))] + public readonly partial struct ServerGroupId : IFormattable, IEquatable + { + public static readonly ServerGroupId Null = default; + public static ServerGroupId To(ulong v) => new ServerGroupId(v); + + public ulong Value { get; } + public ServerGroupId(ulong value) { Value = value; } + public static explicit operator ServerGroupId(ulong v) => new ServerGroupId(v); + public static bool operator ==(ServerGroupId a, ServerGroupId b) => a.Value == b.Value; + public static bool operator !=(ServerGroupId a, ServerGroupId b) => a.Value != b.Value; + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => obj is ServerGroupId c && Value.Equals(c.Value); + public override string ToString() => Value.ToString(); + + public bool Equals(ServerGroupId other) => Value.Equals(other.Value); + public string ToString(string format, IFormatProvider formatProvider) => Value.ToString(format, formatProvider); + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, ServerGroupId value, JsonSerializer serializer) + => writer.WriteValue(value.Value); + public override ServerGroupId ReadJson(JsonReader reader, Type objectType, ServerGroupId existingValue, bool hasExistingValue, JsonSerializer serializer) + => new ServerGroupId(ulong.Parse(reader.ReadAsString())); + } + } + + [StructLayout(LayoutKind.Sequential, Pack=0)] + [DebuggerDisplay("{Value, nq}")] + [JsonConverter(typeof(ChannelGroupId.Converter))] + public readonly partial struct ChannelGroupId : IFormattable, IEquatable + { + public static readonly ChannelGroupId Null = default; + public static ChannelGroupId To(ulong v) => new ChannelGroupId(v); + + public ulong Value { get; } + public ChannelGroupId(ulong value) { Value = value; } + public static explicit operator ChannelGroupId(ulong v) => new ChannelGroupId(v); + public static bool operator ==(ChannelGroupId a, ChannelGroupId b) => a.Value == b.Value; + public static bool operator !=(ChannelGroupId a, ChannelGroupId b) => a.Value != b.Value; + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => obj is ChannelGroupId c && Value.Equals(c.Value); + public override string ToString() => Value.ToString(); + + public bool Equals(ChannelGroupId other) => Value.Equals(other.Value); + public string ToString(string format, IFormatProvider formatProvider) => Value.ToString(format, formatProvider); + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, ChannelGroupId value, JsonSerializer serializer) + => writer.WriteValue(value.Value); + public override ChannelGroupId ReadJson(JsonReader reader, Type objectType, ChannelGroupId existingValue, bool hasExistingValue, JsonSerializer serializer) + => new ChannelGroupId(ulong.Parse(reader.ReadAsString())); + } + } + +} \ No newline at end of file diff --git a/TSLib/Types.gen.tt b/TSLib/Types.gen.tt new file mode 100644 index 00000000..bb165c35 --- /dev/null +++ b/TSLib/Types.gen.tt @@ -0,0 +1,69 @@ +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the Open Software License v. 3.0 +// +// You should have received a copy of the Open Software License along with this +// program. If not, see . +// + +<#@ template debug="true" hostSpecific="true" language="C#" #> +<#@ output extension=".cs" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.IO" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<# +var types = new (string alias, string backing)[] { + ("Uid", "string"), + ("ClientDbId", "ulong"), + ("ClientId", "ushort"), + ("ChannelId", "ulong"), + ("ServerGroupId", "ulong"), + ("ChannelGroupId", "ulong"), + //("IconHash", "int"), + //("ConnectionId", "uint"), +}; +#> +using Newtonsoft.Json; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace TSLib +{ + <# foreach(var type in types) { + var isStr = type.backing == "string"; #> + [StructLayout(LayoutKind.Sequential, Pack=0)] + [DebuggerDisplay("{Value, nq}")] + [JsonConverter(typeof(<#= type.alias #>.Converter))] + public readonly partial struct <#= type.alias #> :<#= isStr ? "" : " IFormattable," #> IEquatable<<#= type.alias #>> + { + public static readonly <#= type.alias #> Null = default; + public static <#= type.alias #> To(<#= type.backing #> v) => new <#= type.alias #>(v); + + public <#= type.backing #> Value { get; } + public <#= type.alias #>(<#= type.backing #> value) { <#= isStr ? "if (value == null) throw new ArgumentNullException(nameof(value));" : "" #> Value = value; } + public static explicit operator <#= type.alias #>(<#= type.backing #> v) => new <#= type.alias #>(v); + public static bool operator ==(<#= type.alias #> a, <#= type.alias #> b) => a.Value == b.Value; + public static bool operator !=(<#= type.alias #> a, <#= type.alias #> b) => a.Value != b.Value; + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => obj is <#= type.alias #> c && Value.Equals(c.Value); + public override string ToString() => Value.ToString(); + <# if (isStr) { #> + public bool Equals(<#= type.alias #> other) => Value.Equals(other.Value, StringComparison.Ordinal); + <# } else { #> + public bool Equals(<#= type.alias #> other) => Value.Equals(other.Value); + public string ToString(string format, IFormatProvider formatProvider) => Value.ToString(format, formatProvider); + <# } #> + private class Converter : JsonConverter<<#= type.alias #>> + { + public override void WriteJson(JsonWriter writer, <#= type.alias #> value, JsonSerializer serializer) + => writer.WriteValue(value.Value); + public override <#= type.alias #> ReadJson(JsonReader reader, Type objectType, <#= type.alias #> existingValue, bool hasExistingValue, JsonSerializer serializer) + => new <#= type.alias #>(<#= isStr ? "reader.ReadAsString()" : type.backing + ".Parse(reader.ReadAsString())" #>); + } + } + <# } #> +} \ No newline at end of file diff --git a/TS3Client/WaitBlock.cs b/TSLib/WaitBlock.cs similarity index 86% rename from TS3Client/WaitBlock.cs rename to TSLib/WaitBlock.cs index 68470271..e551ab0d 100644 --- a/TS3Client/WaitBlock.cs +++ b/TSLib/WaitBlock.cs @@ -1,5 +1,5 @@ -// TS3Client - A free TeamSpeak3 client implementation -// Copyright (C) 2017 TS3Client contributors +// TSLib - A free TeamSpeak 3 and 5 client library +// Copyright (C) 2017 TSLib contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the Open Software License v. 3.0 @@ -7,14 +7,13 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client -{ - using Helper; - using Messages; - using System; - using System.Threading; - using System.Threading.Tasks; +using System; +using System.Threading; +using System.Threading.Tasks; +using TSLib.Messages; +namespace TSLib +{ // TODO check maybe splittable into 'WaitBlockSync' and 'WaitBlockAsync' internal sealed class WaitBlock : IDisposable { @@ -53,15 +52,15 @@ public WaitBlock(Deserializer deserializer, bool async, NotificationType[] depen if (isDisposed) throw new ObjectDisposedException(nameof(WaitBlock)); if (!answerWaiter.WaitOne(CommandTimeout)) - return Util.TimeOutCommandError; - if (commandError.Id != Ts3ErrorCode.ok) + return CommandError.TimeOut; + if (commandError.Id != TsErrorCode.ok) return commandError; var result = deserializer.GenerateResponse(commandLine.Value.Span); if (result.Ok) return result.Value; else - return Util.ParserCommandError; + return CommandError.Parser; } public async Task> WaitForMessageAsync() where T : IResponse, new() @@ -71,15 +70,15 @@ public WaitBlock(Deserializer deserializer, bool async, NotificationType[] depen var timeOut = Task.Delay(CommandTimeout); var res = await Task.WhenAny(answerWaiterAsync.Task, timeOut).ConfigureAwait(false); if (res == timeOut) - return Util.TimeOutCommandError; - if (commandError.Id != Ts3ErrorCode.ok) + return CommandError.TimeOut; + if (commandError.Id != TsErrorCode.ok) return commandError; var result = deserializer.GenerateResponse(commandLine.Value.Span); if (result.Ok) return result.Value; else - return Util.ParserCommandError; + return CommandError.Parser; } public R WaitForNotification() @@ -89,11 +88,11 @@ public R WaitForNotification() if (DependsOn is null) throw new InvalidOperationException("This waitblock has no dependent Notification"); if (!answerWaiter.WaitOne(CommandTimeout)) - return Util.TimeOutCommandError; - if (commandError.Id != Ts3ErrorCode.ok) + return CommandError.TimeOut; + if (commandError.Id != TsErrorCode.ok) return commandError; if (!notificationWaiter.WaitOne(CommandTimeout)) - return Util.TimeOutCommandError; + return CommandError.TimeOut; return notification; } diff --git a/WebInterface/package-lock.json b/WebInterface/package-lock.json index de624518..0e6e3301 100644 --- a/WebInterface/package-lock.json +++ b/WebInterface/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@mdi/font": { - "version": "4.5.95", - "resolved": "https://registry.npmjs.org/@mdi/font/-/font-4.5.95.tgz", - "integrity": "sha512-AjR2Zgu1feBXWlTfEjD6JQqLAMCqYn2Gzia5PWqFnysvz5F6JmPHtQFldIHXqyv2s/FwME7ZDBc5N86NEHbyvQ==" + "version": "4.9.95", + "resolved": "https://registry.npmjs.org/@mdi/font/-/font-4.9.95.tgz", + "integrity": "sha512-m2sbAs+SMwRnWpkMriBxEulwuhmqRyh6X+hdOZlqSxYZUM2C2TaDnQ4gcilzdoAgru2XYnWViZ/xPuSDGgRXVw==" }, "@types/events": { "version": "3.0.0", @@ -48,15 +48,15 @@ "dev": true }, "@types/node": { - "version": "12.7.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", - "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==", + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz", + "integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==", "dev": true }, "@vue/component-compiler-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.0.0.tgz", - "integrity": "sha512-am+04/0UX7ektcmvhYmrf84BDVAD8afFOf4asZjN84q8xzxFclbk5x0MtxuKGfp+zjN5WWPJn3fjFAWtDdIGSw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.1.1.tgz", + "integrity": "sha512-+lN3nsfJJDGMNz7fCpcoYIORrXo0K3OTsdr8jCM7FuqdI4+70TY6gxY6viJ2Xi1clqyPg7LpeOWwjF31vSMmUw==", "dev": true, "requires": { "consolidate": "^0.15.1", @@ -64,18 +64,12 @@ "lru-cache": "^4.1.2", "merge-source-map": "^1.1.0", "postcss": "^7.0.14", - "postcss-selector-parser": "^5.0.0", - "prettier": "1.16.3", + "postcss-selector-parser": "^6.0.2", + "prettier": "^1.18.2", "source-map": "~0.6.1", "vue-template-es2015-compiler": "^1.9.0" }, "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "dev": true - }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -86,17 +80,6 @@ "yallist": "^2.1.2" } }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "dev": true, - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", @@ -515,9 +498,9 @@ "dev": true }, "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, "array-union": { @@ -610,10 +593,13 @@ "dev": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } }, "async-each": { "version": "1.0.3", @@ -761,6 +747,16 @@ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bluebird": { "version": "3.5.5", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", @@ -791,12 +787,6 @@ "type-is": "~1.6.17" }, "dependencies": { - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -817,6 +807,14 @@ "dns-txt": "^2.0.2", "multicast-dns": "^6.0.1", "multicast-dns-service-types": "^1.1.0" + }, + "dependencies": { + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + } } }, "boolbase": { @@ -922,17 +920,17 @@ } }, "buefy": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/buefy/-/buefy-0.8.5.tgz", - "integrity": "sha512-yGQUhIsZWTodCx1rpfDTA32v5OjILpDIDAP+X6KoE6du3F3EZwJ/k5aT8D6Ba6AxNzVdDa2M7f0hzMddLbm38A==", + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/buefy/-/buefy-0.8.10.tgz", + "integrity": "sha512-Lw/UP3Ku7o+oqam9TIoRMG5SrytGQwXWAoxAtqt6Wb9eSsMEqp/5o+jZnz8oteR06YWgjdSIfOv2YeEdjEkQCg==", "requires": { "bulma": "0.7.5" } }, "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "requires": { "base64-js": "^1.0.2", @@ -970,9 +968,9 @@ "integrity": "sha512-cX98TIn0I6sKba/DhW0FBjtaDpxTelU166pf7ICXpCCuplHWyu6C9LYZmL5PEsnePIeJaiorsTEzzNk3Tsm1hw==" }, "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", "dev": true }, "cacache": { @@ -996,22 +994,6 @@ "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } } }, "cache-base": { @@ -1161,9 +1143,9 @@ } }, "chownr": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", - "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", "dev": true }, "chrome-trace-event": { @@ -1293,9 +1275,9 @@ } }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "commondir": { @@ -1311,12 +1293,20 @@ "dev": true }, "compressible": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", - "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, "requires": { - "mime-db": ">= 1.40.0 < 2" + "mime-db": ">= 1.43.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "dev": true + } } }, "compression": { @@ -1332,6 +1322,14 @@ "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + } } }, "concat-map": { @@ -1353,9 +1351,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -1385,13 +1383,10 @@ "dev": true }, "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "^0.1.4" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true }, "consolidate": { "version": "0.15.1", @@ -1456,12 +1451,12 @@ "dev": true }, "copy-webpack-plugin": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz", - "integrity": "sha512-YBuYGpSzoCHSSDGyHy6VJ7SHojKp6WHT4D7ItcQFNAYx2hrwkMe56e97xfVR0/ovDuMTrMffXUiltvQljtAGeg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", + "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", "dev": true, "requires": { - "cacache": "^11.3.3", + "cacache": "^12.0.3", "find-cache-dir": "^2.1.0", "glob-parent": "^3.1.0", "globby": "^7.1.1", @@ -1469,9 +1464,9 @@ "loader-utils": "^1.2.3", "minimatch": "^3.0.4", "normalize-path": "^3.0.0", - "p-limit": "^2.2.0", + "p-limit": "^2.2.1", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.7.0", + "serialize-javascript": "^2.1.2", "webpack-log": "^2.0.0" }, "dependencies": { @@ -1481,56 +1476,6 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, - "cacache": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", - "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -1556,12 +1501,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true } } }, @@ -1649,9 +1588,9 @@ } }, "css-loader": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.2.0.tgz", - "integrity": "sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.4.2.tgz", + "integrity": "sha512-jYq4zdZT0oS0Iykt+fqnzVLRIeiPWhka+7BqPn+oSIpWJAHak5tmB/WZrJ2a21JhCeFyNnnlroSl8c+MtVndzA==", "dev": true, "requires": { "camelcase": "^5.3.1", @@ -1659,13 +1598,13 @@ "icss-utils": "^4.1.1", "loader-utils": "^1.2.3", "normalize-path": "^3.0.0", - "postcss": "^7.0.17", + "postcss": "^7.0.23", "postcss-modules-extract-imports": "^2.0.0", "postcss-modules-local-by-default": "^3.0.2", - "postcss-modules-scope": "^2.1.0", + "postcss-modules-scope": "^2.1.1", "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.0.0", - "schema-utils": "^2.0.0" + "postcss-value-parser": "^4.0.2", + "schema-utils": "^2.6.0" }, "dependencies": { "big.js": { @@ -1701,9 +1640,9 @@ "dev": true }, "schema-utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.2.0.tgz", - "integrity": "sha512-5EwsCNhfFTZvUreQhx/4vVQpJ/lnCAkgoIHLhSpp4ZirE+4hzFvdJi0FMub6hxbFVBJYSpeVVmon+2e7uEGRrA==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", "dev": true, "requires": { "ajv": "^6.10.2", @@ -1752,12 +1691,6 @@ "assert-plus": "^1.0.0" } }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, "de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -1786,9 +1719,9 @@ "dev": true }, "deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.0.tgz", - "integrity": "sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", "dev": true, "requires": { "is-arguments": "^1.0.4", @@ -1872,6 +1805,29 @@ "p-map": "^2.0.0", "pify": "^4.0.1", "rimraf": "^2.6.3" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + } } }, "delayed-stream": { @@ -1888,9 +1844,9 @@ "dev": true }, "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -2037,9 +1993,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -2086,9 +2042,9 @@ "dev": true }, "elliptic": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", - "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -2232,9 +2188,9 @@ "dev": true }, "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", "dev": true }, "eventsource": { @@ -2353,12 +2309,6 @@ "vary": "~1.1.2" }, "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -2548,6 +2498,13 @@ } } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, "filesize": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", @@ -2726,9 +2683,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -2752,9 +2709,9 @@ } }, "follow-redirects": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", - "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz", + "integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==", "dev": true, "requires": { "debug": "^3.0.0" @@ -2834,9 +2791,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -2872,9 +2829,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -2904,14 +2861,15 @@ "dev": true }, "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", + "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", "dev": true, "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" + "node-pre-gyp": "*" }, "dependencies": { "abbrev": { @@ -2959,7 +2917,7 @@ } }, "chownr": { - "version": "1.1.1", + "version": "1.1.3", "bundled": true, "dev": true, "optional": true @@ -2989,7 +2947,7 @@ "optional": true }, "debug": { - "version": "4.1.1", + "version": "3.2.6", "bundled": true, "dev": true, "optional": true, @@ -3016,12 +2974,12 @@ "optional": true }, "fs-minipass": { - "version": "1.2.5", + "version": "1.2.7", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.6.0" } }, "fs.realpath": { @@ -3047,7 +3005,7 @@ } }, "glob": { - "version": "7.1.3", + "version": "7.1.6", "bundled": true, "dev": true, "optional": true, @@ -3076,7 +3034,7 @@ } }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "dev": true, "optional": true, @@ -3095,7 +3053,7 @@ } }, "inherits": { - "version": "2.0.3", + "version": "2.0.4", "bundled": true, "dev": true, "optional": true @@ -3137,7 +3095,7 @@ "optional": true }, "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, "dev": true, "optional": true, @@ -3147,12 +3105,12 @@ } }, "minizlib": { - "version": "1.2.1", + "version": "1.3.3", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { @@ -3165,24 +3123,24 @@ } }, "ms": { - "version": "2.1.1", + "version": "2.1.2", "bundled": true, "dev": true, "optional": true }, "needle": { - "version": "2.3.0", + "version": "2.4.0", "bundled": true, "dev": true, "optional": true, "requires": { - "debug": "^4.1.0", + "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.12.0", + "version": "0.14.0", "bundled": true, "dev": true, "optional": true, @@ -3196,7 +3154,7 @@ "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^4" + "tar": "^4.4.2" } }, "nopt": { @@ -3210,13 +3168,22 @@ } }, "npm-bundled": { - "version": "1.0.6", + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { - "version": "1.4.1", + "version": "1.4.7", "bundled": true, "dev": true, "optional": true, @@ -3287,7 +3254,7 @@ "optional": true }, "process-nextick-args": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "dev": true, "optional": true @@ -3328,7 +3295,7 @@ } }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, "dev": true, "optional": true, @@ -3355,7 +3322,7 @@ "optional": true }, "semver": { - "version": "5.7.0", + "version": "5.7.1", "bundled": true, "dev": true, "optional": true @@ -3408,18 +3375,18 @@ "optional": true }, "tar": { - "version": "4.4.8", + "version": "4.4.13", "bundled": true, "dev": true, "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "util-deprecate": { @@ -3444,7 +3411,7 @@ "optional": true }, "yallist": { - "version": "3.0.3", + "version": "3.1.1", "bundled": true, "dev": true, "optional": true @@ -3489,9 +3456,9 @@ } }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3559,22 +3526,23 @@ } }, "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", "dev": true, "requires": { "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" }, "dependencies": { "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -3763,9 +3731,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -4595,9 +4563,9 @@ "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" }, "loglevel": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz", - "integrity": "sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.6.tgz", + "integrity": "sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ==", "dev": true }, "lower-case": { @@ -4802,9 +4770,9 @@ "dev": true }, "mini-css-extract-plugin": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", - "integrity": "sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.2.tgz", + "integrity": "sha512-a3Y4of27Wz+mqK3qrcd3VhYz6cU0iW5x3Sgvqzbj+XmlrSizmvu8QQMl5oMYJjgHOC4iyt+w7l4umP+dQeW3bw==", "dev": true, "requires": { "loader-utils": "^1.1.0", @@ -5055,9 +5023,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -5175,9 +5143,9 @@ "dev": true }, "object-is": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", - "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", "dev": true }, "object-keys": { @@ -5195,6 +5163,18 @@ "isobject": "^3.0.0" } }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -5343,9 +5323,9 @@ "dev": true }, "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, "parallel-transform": { @@ -5360,9 +5340,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -5506,9 +5486,9 @@ "optional": true }, "picomatch": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", - "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.0.tgz", + "integrity": "sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw==", "dev": true }, "pify": { @@ -5542,14 +5522,31 @@ } }, "portfinder": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.24.tgz", - "integrity": "sha512-ekRl7zD2qxYndYflwiryJwMioBI7LI7rVXg3EnLK3sjkouT5eOuhS3gS255XxBksa30VG8UPZYZCdgfGOfkSUg==", + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", + "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", "dev": true, "requires": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "posix-character-classes": { @@ -5559,9 +5556,9 @@ "dev": true }, "postcss": { - "version": "7.0.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.18.tgz", - "integrity": "sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==", + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", + "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -5602,9 +5599,9 @@ } }, "postcss-modules-scope": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz", - "integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.1.tgz", + "integrity": "sha512-OXRUPecnHCg8b9xWvldG/jUpRIGPNRka0r4D4j0ESUU2/5IOnpsjfPPmDprM3Ih8CgZ8FXjWqaniK5v4rWt3oQ==", "dev": true, "requires": { "postcss": "^7.0.6", @@ -5645,9 +5642,9 @@ "dev": true }, "prettier": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.3.tgz", - "integrity": "sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", "dev": true }, "pretty-error": { @@ -5840,14 +5837,6 @@ "http-errors": "1.7.2", "iconv-lite": "0.4.24", "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true - } } }, "readable-stream": { @@ -5966,9 +5955,9 @@ } }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -6012,12 +6001,92 @@ } }, "regexp.prototype.flags": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", - "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", "dev": true, "requires": { - "define-properties": "^1.1.2" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } } }, "relateurl": { @@ -6271,9 +6340,9 @@ } }, "serialize-javascript": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", - "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", "dev": true }, "serve-index": { @@ -6605,9 +6674,9 @@ } }, "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -6755,9 +6824,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -6804,9 +6873,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -6830,9 +6899,9 @@ } }, "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, "strict-uri-encode": { @@ -6922,13 +6991,13 @@ "dev": true }, "style-loader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.0.0.tgz", - "integrity": "sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.3.tgz", + "integrity": "sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw==", "dev": true, "requires": { "loader-utils": "^1.2.3", - "schema-utils": "^2.0.1" + "schema-utils": "^2.6.4" }, "dependencies": { "big.js": { @@ -6964,9 +7033,9 @@ "dev": true }, "schema-utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.2.0.tgz", - "integrity": "sha512-5EwsCNhfFTZvUreQhx/4vVQpJ/lnCAkgoIHLhSpp4ZirE+4hzFvdJi0FMub6hxbFVBJYSpeVVmon+2e7uEGRrA==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", "dev": true, "requires": { "ajv": "^6.10.2", @@ -6996,9 +7065,9 @@ } }, "swagger-ui-dist": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.24.0.tgz", - "integrity": "sha512-5uAjeEqV+zbtalBDXAIrkqUZwsUHYwvBSeGYlFcLj1ERS3jfprL4OPLSSriDoeXCtNmWzpz5aooV2qJW+DqdUQ==" + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.25.0.tgz", + "integrity": "sha512-vwvJPPbdooTvDwLGzjIXinOXizDJJ6U1hxnJL3y6U3aL1d2MSXDmKg2139XaLBhsVZdnQJV2bOkX4reB+RXamg==" }, "tapable": { "version": "1.1.3", @@ -7007,9 +7076,9 @@ "dev": true }, "terser": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.8.tgz", - "integrity": "sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz", + "integrity": "sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==", "dev": true, "requires": { "commander": "^2.20.0", @@ -7018,16 +7087,16 @@ } }, "terser-webpack-plugin": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", - "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", + "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", "dev": true, "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.7.0", + "serialize-javascript": "^2.1.2", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", @@ -7045,9 +7114,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -7071,9 +7140,9 @@ } }, "thunky": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", - "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, "timers-browserify": { @@ -7171,9 +7240,9 @@ "dev": true }, "ts-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.0.tgz", - "integrity": "sha512-Da8h3fD+HiZ9GvZJydqzk3mTC9nuOKYlJcpuk+Zv6Y1DPaMvBL+56GRzZFypx2cWrZFMsQr869+Ua2slGoLxvQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.1.tgz", + "integrity": "sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g==", "dev": true, "requires": { "chalk": "^2.3.0", @@ -7263,9 +7332,9 @@ "dev": true }, "typescript": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", - "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", "dev": true }, "uglify-es": { @@ -7497,29 +7566,29 @@ } }, "vm-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", - "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, "vue": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz", - "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==" + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", + "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" }, "vue-hot-reload-api": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz", - "integrity": "sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", + "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", "dev": true }, "vue-loader": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.7.1.tgz", - "integrity": "sha512-fwIKtA23Pl/rqfYP5TSGK7gkEuLhoTvRYW+TU7ER3q9GpNLt/PjG5NLv3XHRDiTg7OPM1JcckBgds+VnAc+HbA==", + "version": "15.8.3", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.8.3.tgz", + "integrity": "sha512-yFksTFbhp+lxlm92DrKdpVIWMpranXnTEuGSc0oW+Gk43M9LWaAmBTnfj5+FCdve715mTHvo78IdaXf5TbiTJg==", "dev": true, "requires": { - "@vue/component-compiler-utils": "^3.0.0", + "@vue/component-compiler-utils": "^3.1.0", "hash-sum": "^1.0.2", "loader-utils": "^1.1.0", "vue-hot-reload-api": "^2.3.0", @@ -7561,9 +7630,9 @@ } }, "vue-router": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.1.3.tgz", - "integrity": "sha512-8iSa4mGNXBjyuSZFCCO4fiKfvzqk+mhL0lnKuGcQtO1eoj8nq3CmbEG8FwK5QqoqwDgsjsf1GDuisDX4cdb/aQ==" + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.1.5.tgz", + "integrity": "sha512-BszkPvhl7I9h334GjckCh7sVFyjTPMMJFJ4Bsrem/Ik+B/9gt5tgrk8k4gGLO4ZpdvciVdg7O41gW4DisQWurg==" }, "vue-style-loader": { "version": "4.1.2", @@ -7610,9 +7679,9 @@ } }, "vue-template-compiler": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz", - "integrity": "sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz", + "integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==", "dev": true, "requires": { "de-indent": "^1.0.2", @@ -7646,9 +7715,9 @@ } }, "webpack": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.1.tgz", - "integrity": "sha512-ak7u4tUu/U63sCVxA571IuPZO/Q0pZ9cEXKg+R/woxkDzVovq57uB6L2Hlg/pC8LCU+TWpvtcYwsstivQwMJmw==", + "version": "4.41.5", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.5.tgz", + "integrity": "sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", @@ -7671,7 +7740,7 @@ "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.1", + "terser-webpack-plugin": "^1.4.3", "watchpack": "^1.6.0", "webpack-sources": "^1.4.1" }, @@ -7812,9 +7881,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -7848,9 +7917,9 @@ } }, "webpack-bundle-analyzer": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.5.2.tgz", - "integrity": "sha512-g9spCNe25QYUVqHRDkwG414GTok2m7pTTP0wr6l0J50Z3YLS04+BGodTqqoVBL7QfU/U/9p/oiI5XFOyfZ7S/A==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz", + "integrity": "sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g==", "dev": true, "requires": { "acorn": "^6.0.7", @@ -7869,9 +7938,9 @@ } }, "webpack-cli": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.9.tgz", - "integrity": "sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A==", + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.10.tgz", + "integrity": "sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg==", "dev": true, "requires": { "chalk": "2.4.2", @@ -8005,9 +8074,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8031,9 +8100,9 @@ } }, "webpack-dev-server": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.8.2.tgz", - "integrity": "sha512-0xxogS7n5jHDQWy0WST0q6Ykp7UGj4YvWh+HVN71JoE7BwPxMZrwgraBvmdEMbDVMBzF0u+mEzn8TQzBm5NYJQ==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz", + "integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==", "dev": true, "requires": { "ansi-html": "0.0.7", @@ -8051,10 +8120,10 @@ "ip": "^1.1.5", "is-absolute-url": "^3.0.3", "killable": "^1.0.1", - "loglevel": "^1.6.4", + "loglevel": "^1.6.6", "opn": "^5.5.0", "p-retry": "^3.0.1", - "portfinder": "^1.0.24", + "portfinder": "^1.0.25", "schema-utils": "^1.0.0", "selfsigned": "^1.10.7", "semver": "^6.3.0", @@ -8346,9 +8415,9 @@ "dev": true }, "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, "yargs": { diff --git a/WebInterface/package.json b/WebInterface/package.json index 378a8756..7de987fb 100644 --- a/WebInterface/package.json +++ b/WebInterface/package.json @@ -21,35 +21,35 @@ "author": "Splamy", "license": "OSL-3.0", "dependencies": { - "@mdi/font": "^4.5.95", - "buefy": "^0.8.5", + "@mdi/font": "^4.9.95", + "buefy": "^0.8.10", "lodash-es": "^4.17.15", - "swagger-ui-dist": "^3.24.0", - "vue": "^2.6.10", - "vue-router": "^3.1.3" + "swagger-ui-dist": "^3.25.0", + "vue": "^2.6.11", + "vue-router": "^3.1.5" }, "devDependencies": { "@types/lodash-es": "^4.17.3", - "copy-webpack-plugin": "^5.0.4", - "css-loader": "^3.2.0", + "copy-webpack-plugin": "^5.1.1", + "css-loader": "^3.4.2", "file-loader": "^4.2.0", "html-webpack-plugin": "^3.2.0", "less": "^3.9.0", "less-loader": "^5.0.0", "material-design-icons-iconfont": "^5.0.1", - "mini-css-extract-plugin": "^0.8.0", - "style-loader": "^1.0.0", + "mini-css-extract-plugin": "^0.8.2", + "style-loader": "^1.1.3", "svg-inline-loader": "^0.8.0", - "ts-loader": "^6.2.0", - "typescript": "^3.6.4", + "ts-loader": "^6.2.1", + "typescript": "^3.7.5", "uglify-es": "^3.3.9", - "vue-loader": "^15.7.1", + "vue-loader": "^15.8.3", "vue-style-loader": "^4.1.2", - "vue-template-compiler": "^2.6.10", - "webpack": "^4.41.1", - "webpack-bundle-analyzer": "^3.5.2", - "webpack-cli": "^3.3.9", - "webpack-dev-server": "^3.8.2", + "vue-template-compiler": "^2.6.11", + "webpack": "^4.41.5", + "webpack-bundle-analyzer": "^3.6.0", + "webpack-cli": "^3.3.10", + "webpack-dev-server": "^3.10.3", "webpack-merge": "^4.2.2" } } diff --git a/WebInterface/src/ts/Api.ts b/WebInterface/src/ts/Api.ts index 27a4fc52..f6c6f302 100644 --- a/WebInterface/src/ts/Api.ts +++ b/WebInterface/src/ts/Api.ts @@ -1,5 +1,4 @@ import { ApiAuth } from "./ApiAuth"; -import { ApiEndpoint } from "./ApiEndpoint"; import { ApiError } from "./ApiObjects"; export class ErrorObject { @@ -8,7 +7,8 @@ export class ErrorObject { export class Get { public static AuthData: ApiAuth = ApiAuth.Anonymous; - public static EndpointData: ApiEndpoint = ApiEndpoint.SameAddress; + public static readonly SameAddressEndpoint: string = "/api"; + public static Endpoint: string = Get.SameAddressEndpoint; public static async site(site: string): Promise { const response = await fetch(site); @@ -18,7 +18,7 @@ export class Get { public static async api( site: Api, login: ApiAuth = this.AuthData, - ep: ApiEndpoint = this.EndpointData): Promise { + ep: string = this.Endpoint): Promise { // TODO endpoint parameter const requestData: RequestInit = { @@ -34,7 +34,7 @@ export class Get { }); } - const apiSite = ep.baseAddress + site.done(); + const apiSite = ep + site.done(); let response: Response; try { response = await fetch(apiSite, requestData); diff --git a/WebInterface/src/ts/ApiEndpoint.ts b/WebInterface/src/ts/ApiEndpoint.ts deleted file mode 100644 index 86f1692e..00000000 --- a/WebInterface/src/ts/ApiEndpoint.ts +++ /dev/null @@ -1,13 +0,0 @@ -export class ApiEndpoint { - public baseAddress: string; - public sameAddress: boolean; - - public static SameAddress: ApiEndpoint = new ApiEndpoint("/api", true); - - public static Localhost: ApiEndpoint = new ApiEndpoint("http://localhost:58913/api", true); - - constructor(baseAddress: string, sameAddress: boolean = false) { - this.baseAddress = baseAddress; - this.sameAddress = sameAddress; - } -} diff --git a/WebInterface/src/ts/ApiObjects.ts b/WebInterface/src/ts/ApiObjects.ts index 1e53b5cc..5da2694e 100644 --- a/WebInterface/src/ts/ApiObjects.ts +++ b/WebInterface/src/ts/ApiObjects.ts @@ -64,6 +64,7 @@ export interface CmdServerTreeChannel { Parent: number; Order: number; HasPassword: boolean; + Subscribed: boolean; // ... } @@ -110,6 +111,17 @@ export class Empty { }; } + public static CmdServerTreeChannel(): CmdServerTreeChannel { + return { + Id: 0, + Name: "", + Order: 0, + Parent: -1, + HasPassword: false, + Subscribed: false, + }; + } + public static CmdPlaylistInfo(): CmdPlaylistInfo { return { Id: "", diff --git a/WebInterface/src/ts/Components/Navbar.vue b/WebInterface/src/ts/Components/Navbar.vue index 8cbf7ef4..3ac7f830 100644 --- a/WebInterface/src/ts/Components/Navbar.vue +++ b/WebInterface/src/ts/Components/Navbar.vue @@ -61,7 +61,7 @@ - + Site Settings @@ -79,8 +79,25 @@