From 5beac642923e304f3df969fb6ab48eb62fb69011 Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 8 Nov 2017 01:57:47 +0100 Subject: [PATCH 01/48] Multi instance! --- TS3ABotUnitTests/BotCommandTests.cs | 13 +- TS3AudioBot/Bot.cs | 288 ++++++ TS3AudioBot/BotManager.cs | 103 ++ TS3AudioBot/CommandSystem/CommandManager.cs | 11 +- .../CommandResults/JsonCommandResult.cs | 2 +- .../CommandSystem/ExecutionInformation.cs | 10 +- TS3AudioBot/{MainBot.cs => Commands.cs} | 927 +++++------------- TS3AudioBot/Core.cs | 267 +++++ TS3AudioBot/DocGen.cs | 7 +- TS3AudioBot/Helper/ConfigFile.cs | 3 + TS3AudioBot/Helper/ImageUtil.cs | 11 +- TS3AudioBot/History/HistoryManager.cs | 37 +- TS3AudioBot/PlayManager.cs | 8 +- TS3AudioBot/Plugins/ITabPlugin.cs | 2 +- TS3AudioBot/Plugins/Plugin.cs | 21 +- TS3AudioBot/Plugins/PluginManager.cs | 8 +- .../ResourceFactoryManager.cs | 16 +- TS3AudioBot/Rights/RightsManager.cs | 12 +- TS3AudioBot/Sessions/SessionManager.cs | 2 +- TS3AudioBot/Sessions/UserSession.cs | 4 +- TS3AudioBot/TS3AudioBot.csproj | 7 +- TS3AudioBot/TargetScript.cs | 14 +- TS3AudioBot/Web/Api/WebApi.cs | 8 +- TS3AudioBot/Web/Interface/WebDisplay.cs | 55 +- TS3AudioBot/Web/WebComponent.cs | 6 +- TS3AudioBot/Web/WebManager.cs | 10 +- TS3Client/Full/Ts3FullClient.cs | 1 + TS3Client/MessageProcessor.cs | 29 +- TS3Client/ts3protocol.md | 4 +- 29 files changed, 1034 insertions(+), 852 deletions(-) create mode 100644 TS3AudioBot/Bot.cs create mode 100644 TS3AudioBot/BotManager.cs rename TS3AudioBot/{MainBot.cs => Commands.cs} (55%) create mode 100644 TS3AudioBot/Core.cs diff --git a/TS3ABotUnitTests/BotCommandTests.cs b/TS3ABotUnitTests/BotCommandTests.cs index 1f0010ac..d724aae5 100644 --- a/TS3ABotUnitTests/BotCommandTests.cs +++ b/TS3ABotUnitTests/BotCommandTests.cs @@ -24,21 +24,18 @@ namespace TS3ABotUnitTests [TestFixture] public class BotCommandTests { - private readonly MainBot bot; + private readonly CommandManager cmdMgr; public BotCommandTests() { - bot = new MainBot(); - var prop = typeof(MainBot).GetProperty(nameof(MainBot.CommandManager)); - if (prop != null) - prop.SetValue(bot, new CommandManager()); - bot.CommandManager.RegisterMain(bot); + cmdMgr = new CommandManager(); + cmdMgr.RegisterMain(); } private string CallCommand(string command) { - var info = new ExecutionInformation(null, new InvokerData("InvokerUid"), null) { SkipRightsChecks = true }; - return bot.CommandManager.CommandSystem.ExecuteCommand(info, command); + var info = new ExecutionInformation(null, null, new InvokerData("InvokerUid"), null) { SkipRightsChecks = true }; + return cmdMgr.CommandSystem.ExecuteCommand(info, command); } [Test] diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs new file mode 100644 index 00000000..be49b538 --- /dev/null +++ b/TS3AudioBot/Bot.cs @@ -0,0 +1,288 @@ +// 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 +{ + using CommandSystem; + using Helper; + using History; + using Sessions; + using System; + using System.IO; + using TS3Client; + using TS3Client.Messages; + + /// Core class managing all bots and utility modules. + public sealed class Bot : IDisposable + { + private bool isDisposed; + private readonly Core core; + private MainBotData mainBotData; + + internal TargetScript TargetScript { get; set; } + /// Mangement for playlists. + public PlaylistManager PlaylistManager { get; private set; } + /// Connection object for the current client. + public TeamspeakControl QueryConnection { get; private set; } + /// Management for clients talking with the bot. + public SessionManager SessionManager { get; private set; } + private HistoryManager historyManager = null; + /// Stores all played songs. Can be used to search and restore played songs. + public HistoryManager HistoryManager => historyManager ?? throw new CommandException("History has not been enabled", CommandExceptionReason.NotSupported); + /// Redirects playing, enqueing and song events. + public PlayManager PlayManager { get; private set; } + /// Used to specify playing mode and active targets to send to. + public ITargetManager TargetManager { get; private set; } + /// Slim interface to control the audio player. + public IPlayerConnection PlayerConnection { get; private set; } + + public bool QuizMode { get; set; } + + public Bot(Core core) + { + this.core = core; + } + + public bool InitializeBot() + { + // Read Config File + var conf = core.ConfigManager; + var afd = conf.GetDataStruct("AudioFramework", true); + var tfcd = conf.GetDataStruct("QueryConnection", true); + var hmd = conf.GetDataStruct("HistoryManager", true); + var pld = conf.GetDataStruct("PlaylistManager", true); + mainBotData = conf.GetDataStruct("MainBot", true); + + Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); + AudioValues.audioFrameworkData = afd; + var teamspeakClient = new Ts3Full(tfcd); + QueryConnection = teamspeakClient; + PlayerConnection = teamspeakClient; + PlaylistManager = new PlaylistManager(pld); + SessionManager = new SessionManager(core.Database); + if (hmd.EnableHistory) + historyManager = new HistoryManager(hmd, core.Database); + PlayManager = new PlayManager(core, this); + TargetManager = teamspeakClient; + TargetScript = new TargetScript(core, this); + + Log.Write(Log.Level.Info, "[=========== Registering callbacks ============]"); + PlayerConnection.OnSongEnd += PlayManager.SongStoppedHook; + PlayManager.BeforeResourceStarted += TargetScript.BeforeResourceStarted; + // In own favor update the own status text to the current song title + PlayManager.AfterResourceStarted += LoggedUpdateBotStatus; + PlayManager.AfterResourceStopped += LoggedUpdateBotStatus; + // Log our resource in the history + if (hmd.EnableHistory) + PlayManager.AfterResourceStarted += (s, e) => HistoryManager.LogAudioResource(new HistorySaveData(e.PlayResource.BaseData, e.Owner)); + // Update our thumbnail + PlayManager.AfterResourceStarted += GenerateStatusImage; + PlayManager.AfterResourceStopped += GenerateStatusImage; + // Register callback for all messages happening + QueryConnection.OnMessageReceived += TextCallback; + // Register callback to remove open private sessions, when user disconnects + QueryConnection.OnClientDisconnect += OnClientDisconnect; + QueryConnection.OnBotDisconnect += (s, e) => Dispose(); + + // Connect the query after everyting is set up + try { QueryConnection.Connect(); } + catch (Ts3Exception qcex) + { + Log.Write(Log.Level.Error, "There is either a problem with your connection configuration, or the query has not all permissions it needs. ({0})", qcex); + return false; + } + + Log.Write(Log.Level.Info, "[==================== Done ====================]"); + return true; + } + + private void TextCallback(object sender, TextMessage textMessage) + { + Log.Write(Log.Level.Debug, "MB Got message from {0}: {1}", textMessage.InvokerName, textMessage.Message); + + textMessage.Message = textMessage.Message.TrimStart(' '); + if (!textMessage.Message.StartsWith("!", StringComparison.Ordinal)) + return; + + var refreshResult = QueryConnection.RefreshClientBuffer(true); + if (!refreshResult.Ok) + Log.Write(Log.Level.Warning, "Bot is not correctly set up. Some requests might fail or are slower. ({0})", refreshResult.Message); + + var clientResult = QueryConnection.GetClientById(textMessage.InvokerId); + + // get the current session + UserSession session; + var result = SessionManager.GetSession(textMessage.InvokerId); + if (result.Ok) + { + session = result.Value; + } + else + { + if (!clientResult.Ok) + { + Log.Write(Log.Level.Error, clientResult.Message); + return; + } + session = SessionManager.CreateSession(this, clientResult.Value); + } + + using (session.GetLock()) + { + var invoker = new InvokerData(textMessage.InvokerUid) + { + ClientId = textMessage.InvokerId, + IsApi = false, + Visibiliy = textMessage.Target, + NickName = textMessage.InvokerName, + }; + if (clientResult.Ok) + { + invoker.ChannelId = clientResult.Value.ChannelId; + invoker.DatabaseId = clientResult.Value.DatabaseId; + } + var execInfo = new ExecutionInformation(core, this, invoker, textMessage.Message, session); + + // check if the user has an open request + if (session.ResponseProcessor != null) + { + var msg = session.ResponseProcessor(execInfo); + session.ClearResponse(); + if (!string.IsNullOrEmpty(msg)) + execInfo.Write(msg); + return; + } + + try + { + // parse (and execute) the command + var res = core.CommandManager.CommandSystem.Execute(execInfo, textMessage.Message); + // Write result to user + if (res.ResultType == CommandResultType.String) + { + var sRes = (StringCommandResult)res; + if (!string.IsNullOrEmpty(sRes.Content)) + execInfo.Write(sRes.Content); + } + else if (res.ResultType == CommandResultType.Json) + { + var sRes = (JsonCommandResult)res; + execInfo.Write("\nJson str: \n" + sRes.JsonObject.AsStringResult); + execInfo.Write("\nJson val: \n" + Util.Serializer.Serialize(sRes.JsonObject)); + } + } + catch (CommandException ex) + { + execInfo.Write("Error: " + ex.Message); + } + catch (Exception ex) + { + Log.Write(Log.Level.Error, "MB Unexpected command error: {0}", ex.UnrollException()); + execInfo.Write("An unexpected error occured: " + ex.Message); + } + } + } + + private void OnClientDisconnect(object sender, ClientLeftView eventArgs) + { + TargetManager.WhisperClientUnsubscribe(eventArgs.ClientId); + SessionManager.RemoveSession(eventArgs.ClientId); + } + + private void LoggedUpdateBotStatus(object sender, EventArgs e) + { + var result = UpdateBotStatus(); + if (!result) + Log.Write(Log.Level.Warning, result.Message); + } + + public R UpdateBotStatus(string overrideStr = null) + { + string setString; + if (overrideStr != null) + { + setString = overrideStr; + } + else if (PlayManager.IsPlaying) + { + setString = QuizMode + ? "" + : PlayManager.CurrentPlayData.ResourceData.ResourceTitle; + } + else + { + setString = ""; + } + + return QueryConnection.ChangeDescription(setString); + } + + private void GenerateStatusImage(object sender, EventArgs e) + { + if (!mainBotData.GenerateStatusAvatar) + return; + + if (e is PlayInfoEventArgs startEvent) + { + var thumresult = core.FactoryManager.GetThumbnail(startEvent.PlayResource); + if (!thumresult.Ok) + return; + + using (var bmp = ImageUtil.BuildStringImage("Now playing: " + startEvent.ResourceData.ResourceTitle, thumresult.Value)) + { + using (var mem = new MemoryStream()) + { + bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg); + var result = QueryConnection.UploadAvatar(mem); + if (!result.Ok) + Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Message); + } + } + } + else + { + using (var sleepPic = Util.GetEmbeddedFile("TS3AudioBot.Media.SleepingKitty.png")) + { + var result = QueryConnection.UploadAvatar(sleepPic); + if (!result.Ok) + Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Message); + } + } + } + + public void Dispose() + { + if (!isDisposed) isDisposed = true; + else return; + Log.Write(Log.Level.Info, "Bot disconnecting."); + + core.Bots.StopBot(this); + + PlayManager?.Stop(); + + PlayerConnection?.Dispose(); // before: logStream, + PlayerConnection = null; + + QueryConnection?.Dispose(); // before: logStream, + QueryConnection = null; + } + } + +#pragma warning disable CS0649 + internal class MainBotData : ConfigData + { + [Info("Path to the logfile", "ts3audiobot.log")] + public string LogFile { get; set; } + [Info("Teamspeak group id giving the Bot enough power to do his job", "0")] + public ulong BotGroupId { get; set; } + [Info("Generate fancy status images as avatar", "true")] + public bool GenerateStatusAvatar { get; set; } + } +#pragma warning restore CS0649 +} diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs new file mode 100644 index 00000000..7e5c3281 --- /dev/null +++ b/TS3AudioBot/BotManager.cs @@ -0,0 +1,103 @@ +// 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 +{ + using Helper; + using System; + using System.Collections.Generic; + using System.Threading; + + public class BotManager : IDisposable + { + private bool isRunning; + private readonly Core core; + private readonly List activeBots; + + public BotManager(Core core) + { + isRunning = true; + this.core = core; + Util.Init(ref activeBots); + } + + public void WatchBots() + { + while (isRunning) + { + lock (activeBots) + { + if (activeBots.Count == 0) + { + if (!CreateBot()) + { + Thread.Sleep(1000); + } + } + } + Thread.Sleep(100); + } + } + + public bool CreateBot(/*Ts3FullClientData bot*/) + { + var bot = new Bot(core); + try + { + if (bot.InitializeBot()) + { + lock (activeBots) + { + activeBots.Add(bot); + } + return true; + } + } + catch (Exception) { } + + bot.Dispose(); + Log.Write(Log.Level.Warning, "Could not create new Bot"); + return false; + } + + public Bot GetBot(int id) + { + lock (activeBots) + { + return id < activeBots.Count + ? activeBots[id] + : null; + } + } + + public void StopBot(Bot bot) + { + lock (activeBots) + { + if (activeBots.Remove(bot)) + { + bot.Dispose(); + } + } + } + + public void Dispose() + { + isRunning = false; + lock (activeBots) + { + var bots = activeBots.ToArray(); + foreach (var bot in bots) + { + StopBot(bot); + } + } + } + } +} diff --git a/TS3AudioBot/CommandSystem/CommandManager.cs b/TS3AudioBot/CommandSystem/CommandManager.cs index 21b77787..0e3512ba 100644 --- a/TS3AudioBot/CommandSystem/CommandManager.cs +++ b/TS3AudioBot/CommandSystem/CommandManager.cs @@ -54,12 +54,12 @@ public IEnumerable AllCommands public IEnumerable AllRights => AllCommands.Select(x => x.RequiredRight); - public void RegisterMain(MainBot main) + public void RegisterMain() { if (baseCommands.Count > 0) throw new InvalidOperationException("Operation can only be executed once."); - foreach (var com in GetBotCommands(GetCommandMethods(main))) + foreach (var com in GetBotCommands(GetCommandMethods(null, typeof(Commands)))) { LoadCommand(com); baseCommands.Add(com); @@ -118,9 +118,12 @@ public static IEnumerable GetBotCommands(IEnumerable GetCommandMethods(object obj) + public static IEnumerable GetCommandMethods(object obj, Type type = null) { - var objType = obj.GetType(); + if (obj == null && type == null) + throw new ArgumentNullException(nameof(type), "No type information given."); + var objType = type ?? obj.GetType(); + foreach (var method in objType.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance)) { var comAtt = method.GetCustomAttribute(); diff --git a/TS3AudioBot/CommandSystem/CommandResults/JsonCommandResult.cs b/TS3AudioBot/CommandSystem/CommandResults/JsonCommandResult.cs index 5812a2df..6e3e69f4 100644 --- a/TS3AudioBot/CommandSystem/CommandResults/JsonCommandResult.cs +++ b/TS3AudioBot/CommandSystem/CommandResults/JsonCommandResult.cs @@ -22,6 +22,6 @@ public JsonCommandResult(JsonObject jsonObj) JsonObject = jsonObj; } - public override string ToString() => "JsonCommandResult can't be converted into a string"; // TODO: why? + public override string ToString() => "JsonCommandResult can't be converted into a string"; } } diff --git a/TS3AudioBot/CommandSystem/ExecutionInformation.cs b/TS3AudioBot/CommandSystem/ExecutionInformation.cs index c0245602..c1bbae04 100644 --- a/TS3AudioBot/CommandSystem/ExecutionInformation.cs +++ b/TS3AudioBot/CommandSystem/ExecutionInformation.cs @@ -14,7 +14,8 @@ namespace TS3AudioBot.CommandSystem public class ExecutionInformation { - public MainBot Bot { get; } + public Core Core { get; } + public Bot Bot { get; } // TODO session as R ? public UserSession Session { get; internal set; } public InvokerData InvokerData { get; internal set; } @@ -22,9 +23,10 @@ public class ExecutionInformation public bool ApiCall => InvokerData.IsApi; public bool SkipRightsChecks { get; set; } - private ExecutionInformation() : this(null, null, null) { } - public ExecutionInformation(MainBot bot, InvokerData invoker, string textMessage, UserSession userSession = null) + private ExecutionInformation() : this(null, null, null, null) { } + public ExecutionInformation(Core core, Bot bot, InvokerData invoker, string textMessage, UserSession userSession = null) { + Core = core; Bot = bot; TextMessage = textMessage; InvokerData = invoker; @@ -35,7 +37,7 @@ public bool HasRights(params string[] rights) { if (SkipRightsChecks) return true; - return Bot.RightsManager.HasAllRights(InvokerData, rights); + return Core.RightsManager.HasAllRights(InvokerData, rights); } public R Write(string message) diff --git a/TS3AudioBot/MainBot.cs b/TS3AudioBot/Commands.cs similarity index 55% rename from TS3AudioBot/MainBot.cs rename to TS3AudioBot/Commands.cs index 8099dc51..75c2f403 100644 --- a/TS3AudioBot/MainBot.cs +++ b/TS3AudioBot/Commands.cs @@ -13,381 +13,21 @@ namespace TS3AudioBot using Helper; using History; using Plugins; - using ResourceFactories; - using Rights; - using Sessions; using System; using System.Collections.Generic; using System.Globalization; - using System.IO; using System.Linq; using System.Text; - using System.Threading; using TS3Client; using TS3Client.Messages; - using Web; using Web.Api; - /// Core class managing all bots and utility modules. - public sealed class MainBot : IDisposable + public static class Commands { - internal static void Main(string[] args) - { - Thread.CurrentThread.Name = "TAB Main"; - - var bot = new MainBot(); - - AppDomain.CurrentDomain.UnhandledException += (s, e) => - { - Log.Write(Log.Level.Error, "Critical program failure!. Exception:\n{0}", (e.ExceptionObject as Exception).UnrollException()); - bot.Dispose(); - }; - - Console.CancelKeyPress += (s, e) => - { - if (e.SpecialKey == ConsoleSpecialKey.ControlC) - { - e.Cancel = true; - bot.Dispose(); - } - }; - - if (!bot.ReadParameter(args)) - return; - - if (!bot.InitializeBot()) - bot.Dispose(); - - while (!bot.isDisposed) - Thread.Sleep(10); - } - - private bool isDisposed; - private bool consoleOutput; - private bool writeLog; - private bool writeLogStack; - private string configFilePath; - private MainBotData mainBotData; - - private StreamWriter logStream; - - private DbStore Database { get; set; } // G - private TargetScript TargetScript { get; set; } // L - private PluginManager PluginManager { get; set; } // G - /// Mangement for the bot command system. - public CommandManager CommandManager { get; private set; } // G|L - /// Mangement for playlists. - public PlaylistManager PlaylistManager { get; private set; } // L|G+ - /// Connection object for the current client. - public TeamspeakControl QueryConnection { get; private set; } // L - /// Management for clients talking with the bot. - public SessionManager SessionManager { get; private set; } // G|L+ - private HistoryManager historyManager = null; // G+|L - /// Stores all played songs. Can be used to search and restore played songs. - public HistoryManager HistoryManager => historyManager ?? throw new CommandException("History has not been enabled", CommandExceptionReason.NotSupported); - /// Manages factories which can load resources. - public ResourceFactoryManager FactoryManager { get; private set; } // G - /// Minimalistic webserver hosting the api and web-interface. - public WebManager WebManager { get; private set; } // G+ - /// Redirects playing, enqueing and song events. - public PlayManager PlayManager { get; private set; } // L - /// Used to specify playing mode and active targets to send to. - public ITargetManager TargetManager { get; private set; } // L - /// Slim interface to control the audio player. - public IPlayerConnection PlayerConnection { get; private set; } // L - /// Minimalistic config store for automatically serialized classes. - public ConfigFile ConfigManager { get; private set; } // G - /// Permission system of the bot. - public RightsManager RightsManager { get; private set; } // G|L+ - - public bool QuizMode { get; set; } - - public MainBot() - { - // setting defaults - isDisposed = false; - consoleOutput = true; - writeLog = true; - writeLogStack = false; - configFilePath = "configTS3AudioBot.cfg"; - } - - private bool ReadParameter(string[] args) - { - for (int i = 0; i < args.Length; i++) - { - switch (args[i]) - { - case "-h": - case "--help": - Console.WriteLine(" --quiet -q Deactivates all output to stdout."); - Console.WriteLine(" --no-log -L Deactivates writing to the logfile."); - Console.WriteLine(" --stack -s Adds the stacktrace to all log writes."); - Console.WriteLine(" --config -c Specifies the path to the config file."); - Console.WriteLine(" --version -V Gets the bot version."); - Console.WriteLine(" --help -h Prints this help...."); - return false; - - case "-q": - case "--quiet": - consoleOutput = false; - break; - - case "-L": - case "--no-log": - writeLog = false; - break; - - case "-s": - case "--stack": - writeLogStack = true; - break; - - case "-c": - case "--config": - if (i >= args.Length - 1) - { - Console.WriteLine("No config file specified after \"{0}\"", args[i]); - return false; - } - configFilePath = args[++i]; - break; - - case "-V": - case "--version": - Console.WriteLine(Util.GetAssemblyData().ToLongString()); - return false; - - default: - Console.WriteLine("Unrecognized parameter: {0}", args[i]); - return false; - } - } - return true; - } - - private bool InitializeBot() - { - // Read Config File - ConfigManager = ConfigFile.OpenOrCreate(configFilePath) ?? ConfigFile.CreateDummy(); - var afd = ConfigManager.GetDataStruct("AudioFramework", true); - var tfcd = ConfigManager.GetDataStruct("QueryConnection", true); - var hmd = ConfigManager.GetDataStruct("HistoryManager", true); - var pmd = ConfigManager.GetDataStruct("PluginManager", true); - var pld = ConfigManager.GetDataStruct("PlaylistManager", true); - var yfd = ConfigManager.GetDataStruct("YoutubeFactory", true); - var mfd = ConfigManager.GetDataStruct("MediaFactory", true); - var webd = ConfigManager.GetDataStruct("WebData", true); - var rmd = ConfigManager.GetDataStruct("RightsManager", true); - mainBotData = ConfigManager.GetDataStruct("MainBot", true); - ConfigManager.Close(); - - if (consoleOutput) - { - void ColorLog(string msg, Log.Level lvl) - { - switch (lvl) - { - case Log.Level.Debug: break; - case Log.Level.Info: Console.ForegroundColor = ConsoleColor.Cyan; break; - case Log.Level.Warning: Console.ForegroundColor = ConsoleColor.Yellow; break; - case Log.Level.Error: Console.ForegroundColor = ConsoleColor.Red; break; - default: throw new ArgumentOutOfRangeException(nameof(lvl), lvl, null); - } - Console.WriteLine(msg); - Console.ResetColor(); - } - - Log.RegisterLogger("[%T]%L: %M", 19, ColorLog); - Log.RegisterLogger("Error call Stack:\n%S", 19, ColorLog, Log.Level.Error); - } - - if (writeLog && !string.IsNullOrEmpty(mainBotData.LogFile)) - { - logStream = new StreamWriter(File.Open(mainBotData.LogFile, FileMode.Append, FileAccess.Write, FileShare.Read), Util.Utf8Encoder); - Log.RegisterLogger("[%T]%L: %M\n" + (writeLogStack ? "%S\n" : ""), 19, (msg, lvl) => - { - if (logStream == null) return; - try - { - logStream.Write(msg); - logStream.Flush(); - } - catch (IOException) { } - }); - } - - Log.Write(Log.Level.Info, "[============ TS3AudioBot started =============]"); - Log.Write(Log.Level.Info, "[=== Date/Time: {0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString()); - Log.Write(Log.Level.Info, "[=== Version: {0}", Util.GetAssemblyData().ToString()); - Log.Write(Log.Level.Info, "[==============================================]"); - - Log.Write(Log.Level.Info, "[============ Initializing Commands ===========]"); - CommandManager = new CommandManager(); - CommandManager.RegisterMain(this); - - Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); - AudioValues.audioFrameworkData = afd; - Database = new DbStore(hmd); - var teamspeakClient = new Ts3Full(tfcd); - QueryConnection = teamspeakClient; - PlayerConnection = teamspeakClient; - PlaylistManager = new PlaylistManager(pld); - SessionManager = new SessionManager(Database); - if (hmd.EnableHistory) - historyManager = new HistoryManager(hmd, Database); - PluginManager = new PluginManager(this, pmd); - PlayManager = new PlayManager(this); - WebManager = new WebManager(this, webd); - RightsManager = new RightsManager(this, rmd); - TargetManager = teamspeakClient; - TargetScript = new TargetScript(this); - - Log.Write(Log.Level.Info, "[=========== Initializing Factories ===========]"); - YoutubeDlHelper.DataObj = yfd; - FactoryManager = new ResourceFactoryManager(this); - FactoryManager.AddFactory(new MediaFactory(mfd)); - FactoryManager.AddFactory(new YoutubeFactory(yfd)); - FactoryManager.AddFactory(new SoundcloudFactory()); - FactoryManager.AddFactory(new TwitchFactory()); - - Log.Write(Log.Level.Info, "[=========== Registering callbacks ============]"); - PlayerConnection.OnSongEnd += PlayManager.SongStoppedHook; - PlayManager.BeforeResourceStarted += TargetScript.BeforeResourceStarted; - // In own favor update the own status text to the current song title - PlayManager.AfterResourceStarted += LoggedUpdateBotStatus; - PlayManager.AfterResourceStopped += LoggedUpdateBotStatus; - // Log our resource in the history - if (hmd.EnableHistory) - PlayManager.AfterResourceStarted += (s, e) => HistoryManager.LogAudioResource(new HistorySaveData(e.PlayResource.BaseData, e.Owner)); - // Update our thumbnail - PlayManager.AfterResourceStarted += GenerateStatusImage; - PlayManager.AfterResourceStopped += GenerateStatusImage; - // Register callback for all messages happening - QueryConnection.OnMessageReceived += TextCallback; - // Register callback to remove open private sessions, when user disconnects - QueryConnection.OnClientDisconnect += OnClientDisconnect; - QueryConnection.OnBotDisconnect += (s, e) => Dispose(); - - Log.Write(Log.Level.Info, "[================= Finalizing =================]"); - PluginManager.RestorePlugins(); - - RightsManager.RegisterRights(CommandManager.AllRights); - RightsManager.RegisterRights(RightHighVolume, RightDeleteAllPlaylists); - if (!RightsManager.ReadFile()) - return false; - - WebManager.StartServerAsync(); - - // Connect the query after everyting is set up - try { QueryConnection.Connect(); } - catch (Ts3Exception qcex) - { - Log.Write(Log.Level.Error, "There is either a problem with your connection configuration, or the query has not all permissions it needs. ({0})", qcex); - return false; - } - - Log.Write(Log.Level.Info, "[==================== Done ====================]"); - return true; - } - - private void TextCallback(object sender, TextMessage textMessage) - { - Log.Write(Log.Level.Debug, "MB Got message from {0}: {1}", textMessage.InvokerName, textMessage.Message); - - textMessage.Message = textMessage.Message.TrimStart(' '); - if (!textMessage.Message.StartsWith("!", StringComparison.Ordinal)) - return; - - var refreshResult = QueryConnection.RefreshClientBuffer(true); - if (!refreshResult.Ok) - Log.Write(Log.Level.Warning, "Bot is not correctly set up. Some requests might fail or are slower. ({0})", refreshResult.Message); - - var clientResult = QueryConnection.GetClientById(textMessage.InvokerId); - - // get the current session - UserSession session; - var result = SessionManager.GetSession(textMessage.InvokerId); - if (result.Ok) - { - session = result.Value; - } - else - { - if (!clientResult.Ok) - { - Log.Write(Log.Level.Error, clientResult.Message); - return; - } - session = SessionManager.CreateSession(this, clientResult.Value); - } - - using (session.GetLock()) - { - var invoker = new InvokerData(textMessage.InvokerUid) - { - ClientId = textMessage.InvokerId, - IsApi = false, - Visibiliy = textMessage.Target, - NickName = textMessage.InvokerName, - }; - if (clientResult.Ok) - { - invoker.ChannelId = clientResult.Value.ChannelId; - invoker.DatabaseId = clientResult.Value.DatabaseId; - } - var execInfo = new ExecutionInformation(this, invoker, textMessage.Message, session); - - // check if the user has an open request - if (session.ResponseProcessor != null) - { - var msg = session.ResponseProcessor(execInfo); - session.ClearResponse(); - if (!string.IsNullOrEmpty(msg)) - execInfo.Write(msg); - return; - } - - try - { - // parse (and execute) the command - var res = CommandManager.CommandSystem.Execute(execInfo, textMessage.Message); - // Write result to user - if (res.ResultType == CommandResultType.String) - { - var sRes = (StringCommandResult)res; - if (!string.IsNullOrEmpty(sRes.Content)) - execInfo.Write(sRes.Content); - } - else if (res.ResultType == CommandResultType.Json) - { - var sRes = (JsonCommandResult)res; - execInfo.Write("\nJson str: \n" + sRes.JsonObject.AsStringResult); - execInfo.Write("\nJson val: \n" + Util.Serializer.Serialize(sRes.JsonObject)); - } - } - catch (CommandException ex) - { - execInfo.Write("Error: " + ex.Message); - } - catch (Exception ex) - { - Log.Write(Log.Level.Error, "MB Unexpected command error: {0}", ex.UnrollException()); - execInfo.Write("An unexpected error occured: " + ex.Message); - } - } - } - - private void OnClientDisconnect(object sender, ClientLeftView eventArgs) - { - TargetManager.WhisperClientUnsubscribe(eventArgs.ClientId); - SessionManager.RemoveSession(eventArgs.ClientId); - } - #region COMMANDS - private const string RightHighVolume = "ts3ab.admin.volume"; - private const string RightDeleteAllPlaylists = "ts3ab.admin.list"; + public const string RightHighVolume = "ts3ab.admin.volume"; + public const string RightDeleteAllPlaylists = "ts3ab.admin.list"; // [...] = Optional // = Placeholder for a text @@ -397,13 +37,13 @@ private void OnClientDisconnect(object sender, ClientLeftView eventArgs) // ReSharper disable UnusedMember.Global [Command("add", "Adds a new song to the queue.")] [Usage("", "Any link that is also recognized by !play")] - public void CommandAdd(ExecutionInformation info, string parameter) - => PlayManager.Enqueue(info.InvokerData, parameter).UnwrapThrow(); + public static void CommandAdd(ExecutionInformation info, string parameter) + => info.Bot.PlayManager.Enqueue(info.InvokerData, parameter).UnwrapThrow(); [Command("api token", "Generates an api token.")] [Usage("[]", "Optionally specifies a duration this key is valid in hours.")] [RequiredParameters(0)] - public JsonObject CommandApiToken(ExecutionInformation info, double? validHours) + public static JsonObject CommandApiToken(ExecutionInformation info, double? validHours) { if (info.InvokerData.Visibiliy.HasValue && info.InvokerData.Visibiliy != TextMessageTargetMode.Private) throw new CommandException("Please use this command in a private session.", CommandExceptionReason.CommandError); @@ -419,18 +59,18 @@ public JsonObject CommandApiToken(ExecutionInformation info, double? validHours) { throw new CommandException("Invalid token-valid duration.", oex, CommandExceptionReason.CommandError); } - var token = SessionManager.GenerateToken(info.InvokerData.ClientUid, validSpan).UnwrapThrow(); + var token = info.Bot.SessionManager.GenerateToken(info.InvokerData.ClientUid, validSpan).UnwrapThrow(); return new JsonSingleValue(token); } [Command("api nonce", "Generates an api nonce.")] - public JsonObject CommandApiNonce(ExecutionInformation info) + public static JsonObject CommandApiNonce(ExecutionInformation info) { if (info.InvokerData.Visibiliy.HasValue && info.InvokerData.Visibiliy != TextMessageTargetMode.Private) throw new CommandException("Please use this command in a private session.", CommandExceptionReason.CommandError); if (info.InvokerData.ClientUid == null) throw new CommandException("No Uid found to register token for.", CommandExceptionReason.CommandError); - var result = SessionManager.GetToken(info.InvokerData.ClientUid); + var result = info.Bot.SessionManager.GetToken(info.InvokerData.ClientUid); if (!result.Ok) throw new CommandException("No active token found.", CommandExceptionReason.CommandError); @@ -439,51 +79,65 @@ public JsonObject CommandApiNonce(ExecutionInformation info) } [Command("bot commander", "Gets the status of the channel commander mode.")] - public JsonObject CommandBotCommander() + public static JsonObject CommandBotCommander(ExecutionInformation info) { - var value = QueryConnection.IsChannelCommander().UnwrapThrow(); + var value = info.Bot.QueryConnection.IsChannelCommander().UnwrapThrow(); return new JsonSingleValue("Channel commander is " + (value ? "on" : "off"), value); } [Command("bot commander on", "Enables channel commander.")] - public void CommandBotCommanderOn() => QueryConnection.SetChannelCommander(true); + public static void CommandBotCommanderOn(ExecutionInformation info) => info.Bot.QueryConnection.SetChannelCommander(true); [Command("bot commander off", "Disables channel commander.")] - public void CommandBotCommanderOff() => QueryConnection.SetChannelCommander(false); + public static void CommandBotCommanderOff(ExecutionInformation info) => info.Bot.QueryConnection.SetChannelCommander(false); [Command("bot come", "Moves the bot to you or a specified channel.")] [RequiredParameters(0)] - public void CommandBotCome(ExecutionInformation info, string password = null) => CommandBotMove(info, null, password); + public static void CommandBotCome(ExecutionInformation info, string password = null) => CommandBotMove(info, null, password); [Command("bot move", "Moves the bot to you or a specified channel.")] [RequiredParameters(1)] - public void CommandBotMove(ExecutionInformation info, ulong? channel, string password = null) + public static void CommandBotMove(ExecutionInformation info, ulong? channel, string password = null) { if (!channel.HasValue) channel = (CommandGetChannel(info) as JsonSingleValue)?.Value; if (!channel.HasValue) throw new CommandException("No target channel found"); - QueryConnection.MoveTo(channel.Value, password).UnwrapThrow(); + info.Bot.QueryConnection.MoveTo(channel.Value, password).UnwrapThrow(); } [Command("bot name", "Gives the bot a new name.")] - public void CommandBotName(string name) => QueryConnection.ChangeName(name).UnwrapThrow(); + public static void CommandBotName(ExecutionInformation info, string name) => info.Bot.QueryConnection.ChangeName(name).UnwrapThrow(); [Command("bot setup", "Sets all teamspeak rights for the bot to be fully functional.")] [RequiredParameters(0)] - public void CommandBotSetup(string adminToken) + public static void CommandBotSetup(ExecutionInformation info, string adminToken) { - QueryConnection.SetupRights(adminToken, mainBotData).UnwrapThrow(); + var mbd = info.Core.ConfigManager.GetDataStruct("MainBot", true); + info.Bot.QueryConnection.SetupRights(adminToken, mbd).UnwrapThrow(); } [Command("clear", "Removes all songs from the current playlist.")] - public void CommandClear() + public static void CommandClear(ExecutionInformation info) + { + info.Bot.PlaylistManager.ClearFreelist(); + } + + [Command("connect", "Start a new bot instance.")] + public static void CommandFork(ExecutionInformation info) { - PlaylistManager.ClearFreelist(); + if (!info.Core.Bots.CreateBot()) + throw new CommandException("Could not create new instance"); + } + + [Command("disconnect", "Start a new bot instance.")] + public static void CommandDisconnect(ExecutionInformation info) + { + info.Core.Bots.StopBot(info.Bot); } [Command("eval", "Executes a given command or string")] [Usage(" ", "Executes the given command on arguments")] [Usage("", "Concat the strings and execute them with the command system")] - public ICommandResult CommandEval(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public static ICommandResult CommandEval(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { // Evaluate the first argument on the rest of the arguments if (arguments.Count == 0) @@ -500,73 +154,73 @@ public ICommandResult CommandEval(ExecutionInformation info, IReadOnlyList ((StringCommandResult)a.Execute(info, StaticList.Empty(), new[] { CommandResultType.String })).Content)); - var cmd = CommandManager.CommandSystem.AstToCommandResult(CommandParser.ParseCommandRequest(args)); + var cmd = info.Core.CommandManager.CommandSystem.AstToCommandResult(CommandParser.ParseCommandRequest(args)); return cmd.Execute(info, leftArguments, returnTypes); } [Command("getuser id", "Gets your id.")] - public JsonObject CommandGetId(ExecutionInformation info) + public static JsonObject CommandGetId(ExecutionInformation info) => info.InvokerData.ClientId.HasValue ? new JsonSingleValue(info.InvokerData.ClientId.Value) : (JsonObject)new JsonError("Not found.", CommandExceptionReason.CommandError); [Command("getuser uid", "Gets your unique id.")] - public JsonObject CommandGetUid(ExecutionInformation info) + public static JsonObject CommandGetUid(ExecutionInformation info) => info.InvokerData.ClientUid != null ? new JsonSingleValue(info.InvokerData.ClientUid) : (JsonObject)new JsonError("Not found.", CommandExceptionReason.CommandError); [Command("getuser name", "Gets your nickname.")] - public JsonObject CommandGetName(ExecutionInformation info) + public static JsonObject CommandGetName(ExecutionInformation info) => info.InvokerData.NickName != null ? new JsonSingleValue(info.InvokerData.NickName) : (JsonObject)new JsonError("Not found.", CommandExceptionReason.CommandError); [Command("getuser dbid", "Gets your database id.")] - public JsonObject CommandGetDbId(ExecutionInformation info) + public static JsonObject CommandGetDbId(ExecutionInformation info) => info.InvokerData.DatabaseId.HasValue ? new JsonSingleValue(info.InvokerData.DatabaseId.Value) : (JsonObject)new JsonError("Not found.", CommandExceptionReason.CommandError); [Command("getuser channel", "Gets your channel id you are currently in.")] - public JsonObject CommandGetChannel(ExecutionInformation info) + public static JsonObject CommandGetChannel(ExecutionInformation info) => info.InvokerData.ChannelId.HasValue ? new JsonSingleValue(info.InvokerData.ChannelId.Value) : (JsonObject)new JsonError("Not found.", CommandExceptionReason.CommandError); [Command("getuser all", "Gets all information about you.")] - public JsonObject CommandGetUser(ExecutionInformation info) + public static JsonObject CommandGetUser(ExecutionInformation info) { var client = info.InvokerData; return new JsonSingleObject($"Client: Id:{client.ClientId} DbId:{client.DatabaseId} ChanId:{client.ChannelId} Uid:{client.ClientUid}", client); } [Command("getuser uid byid", "Gets the unique id of a user, searching with his id.")] - public JsonObject CommandGetUidById(ushort id) => new JsonSingleValue(QueryConnection.GetClientById(id).UnwrapThrow().Uid); + public static JsonObject CommandGetUidById(ExecutionInformation info, ushort id) => new JsonSingleValue(info.Bot.QueryConnection.GetClientById(id).UnwrapThrow().Uid); [Command("getuser name byid", "Gets the nickname of a user, searching with his id.")] - public JsonObject CommandGetNameById(ushort id) => new JsonSingleValue(QueryConnection.GetClientById(id).UnwrapThrow().NickName); + public static JsonObject CommandGetNameById(ExecutionInformation info, ushort id) => new JsonSingleValue(info.Bot.QueryConnection.GetClientById(id).UnwrapThrow().NickName); [Command("getuser dbid byid", "Gets the database id of a user, searching with his id.")] - public JsonObject CommandGetDbIdById(ushort id) => new JsonSingleValue(QueryConnection.GetClientById(id).UnwrapThrow().DatabaseId); + public static JsonObject CommandGetDbIdById(ExecutionInformation info, ushort id) => new JsonSingleValue(info.Bot.QueryConnection.GetClientById(id).UnwrapThrow().DatabaseId); [Command("getuser channel byid", "Gets the channel id a user is currently in, searching with his id.")] - public JsonObject CommandGetChannelById(ushort id) => new JsonSingleValue(QueryConnection.GetClientById(id).UnwrapThrow().ChannelId); + public static JsonObject CommandGetChannelById(ExecutionInformation info, ushort id) => new JsonSingleValue(info.Bot.QueryConnection.GetClientById(id).UnwrapThrow().ChannelId); [Command("getuser all byid", "Gets all information about a user, searching with his id.")] - public JsonObject CommandGetUserById(ushort id) + public static JsonObject CommandGetUserById(ExecutionInformation info, ushort id) { - var client = QueryConnection.GetClientById(id).UnwrapThrow(); + var client = info.Bot.QueryConnection.GetClientById(id).UnwrapThrow(); return new JsonSingleObject($"Client: Id:{client.ClientId} DbId:{client.DatabaseId} ChanId:{client.ChannelId} Uid:{client.Uid}", client); } [Command("getuser id byname", "Gets the id of a user, searching with his name.")] - public JsonObject CommandGetIdByName(string username) => new JsonSingleValue(QueryConnection.GetClientByName(username).UnwrapThrow().ClientId); + public static JsonObject CommandGetIdByName(ExecutionInformation info, string username) => new JsonSingleValue(info.Bot.QueryConnection.GetClientByName(username).UnwrapThrow().ClientId); [Command("getuser all byname", "Gets all information of a user, searching with his name.")] - public JsonObject CommandGetUserByName(string username) + public static JsonObject CommandGetUserByName(ExecutionInformation info, string username) { - var client = QueryConnection.GetClientByName(username).UnwrapThrow(); + var client = info.Bot.QueryConnection.GetClientByName(username).UnwrapThrow(); return new JsonSingleObject($"Client: Id:{client.ClientId} DbId:{client.DatabaseId} ChanId:{client.ChannelId} Uid:{client.Uid}", client); } [Command("getuser name bydbid", "Gets the user name by dbid, searching with his database id.")] - public JsonObject CommandGetNameByDbId(ulong dbId) => new JsonSingleValue(QueryConnection.GetDbClientByDbId(dbId).UnwrapThrow().NickName ?? string.Empty); + public static JsonObject CommandGetNameByDbId(ExecutionInformation info, ulong dbId) => new JsonSingleValue(info.Bot.QueryConnection.GetDbClientByDbId(dbId).UnwrapThrow().NickName ?? string.Empty); [Command("getuser uid bydbid", "Gets the unique id of a user, searching with his database id.")] - public JsonObject CommandGetUidByDbId(ulong dbId) => new JsonSingleValue(QueryConnection.GetDbClientByDbId(dbId).UnwrapThrow().Uid); + public static JsonObject CommandGetUidByDbId(ExecutionInformation info, ulong dbId) => new JsonSingleValue(info.Bot.QueryConnection.GetDbClientByDbId(dbId).UnwrapThrow().Uid); [Command("help", "Shows all commands or detailed help about a specific command.")] [Usage("[]", "Any currently accepted command")] [RequiredParameters(0)] - public JsonObject CommandHelp(params string[] parameter) + public static JsonObject CommandHelp(ExecutionInformation info, params string[] parameter) { if (parameter.Length == 0) { @@ -574,14 +228,14 @@ public JsonObject CommandHelp(params string[] parameter) strb.Append("\n========= Welcome to the TS3AudioBot =========" + "\nIf you need any help with a special command use !help ." + "\nHere are all possible commands:\n"); - var botComList = CommandManager.AllCommands.Select(c => c.InvokeName).GroupBy(n => n.Split(' ')[0]).Select(x => x.Key).ToArray(); + var botComList = info.Core.CommandManager.AllCommands.Select(c => c.InvokeName).GroupBy(n => n.Split(' ')[0]).Select(x => x.Key).ToArray(); foreach (var botCom in botComList) strb.Append(botCom).Append(", "); strb.Length -= 2; return new JsonArray(strb.ToString(), botComList); } - CommandGroup group = CommandManager.CommandSystem.RootCommand; + CommandGroup group = info.Core.CommandManager.CommandSystem.RootCommand; ICommand target = null; for (int i = 0; i < parameter.Length; i++) { @@ -622,15 +276,15 @@ public JsonObject CommandHelp(params string[] parameter) } [Command("history add", " Adds the song with to the queue")] - public void CommandHistoryQueue(ExecutionInformation info, uint id) - => PlayManager.Enqueue(info.InvokerData, id).UnwrapThrow(); + public static void CommandHistoryQueue(ExecutionInformation info, uint id) + => info.Bot.PlayManager.Enqueue(info.InvokerData, id).UnwrapThrow(); [Command("history clean", "Cleans up the history file for better startup performance.")] - public string CommandHistoryClean(ExecutionInformation info) + public static string CommandHistoryClean(ExecutionInformation info) { if (info.ApiCall) { - Database.CleanFile(); + info.Core.Database.CleanFile(); return null; } info.Session.SetResponse(ResponseHistoryClean, null); @@ -640,11 +294,11 @@ public string CommandHistoryClean(ExecutionInformation info) [Command("history clean removedefective", "Cleans up the history file for better startup performance. " + "Also checks for all links in the history which cannot be opened anymore")] - public string CommandHistoryCleanRemove(ExecutionInformation info) + public static string CommandHistoryCleanRemove(ExecutionInformation info) { if (info.ApiCall) { - HistoryManager.RemoveBrokenLinks(info); + info.Bot.HistoryManager.RemoveBrokenLinks(info); return null; } info.Session.SetResponse(ResponseHistoryClean, "removedefective"); @@ -653,13 +307,13 @@ public string CommandHistoryCleanRemove(ExecutionInformation info) } [Command("history delete", " Removes the entry with from the history")] - public string CommandHistoryDelete(ExecutionInformation info, uint id) + public static string CommandHistoryDelete(ExecutionInformation info, uint id) { - var ale = HistoryManager.GetEntryById(id).UnwrapThrow(); + var ale = info.Bot.HistoryManager.GetEntryById(id).UnwrapThrow(); if (info.ApiCall) { - HistoryManager.RemoveEntry(ale); + info.Bot.HistoryManager.RemoveEntry(ale); return null; } info.Session.SetResponse(ResponseHistoryDelete, ale); @@ -671,32 +325,32 @@ public string CommandHistoryDelete(ExecutionInformation info, uint id) [Command("history from", "Gets the last songs from the user with the given ")] [RequiredParameters(1)] - public JsonObject CommandHistoryFrom(uint userDbId, int? amount) + public static JsonObject CommandHistoryFrom(ExecutionInformation info, uint userDbId, int? amount) { var query = new SeachQuery { UserId = userDbId }; if (amount.HasValue) query.MaxResults = amount.Value; - var results = HistoryManager.Search(query).ToArray(); - return new JsonArray(HistoryManager.Format(results), results); + var results = info.Bot.HistoryManager.Search(query).ToArray(); + return new JsonArray(info.Bot.HistoryManager.Format(results), results); } [Command("history id", " Displays all saved informations about the song with ")] - public JsonObject CommandHistoryId(uint id) + public static JsonObject CommandHistoryId(ExecutionInformation info, uint id) { - var result = HistoryManager.GetEntryById(id); + var result = info.Bot.HistoryManager.GetEntryById(id); if (!result) return new JsonEmpty("No entry found..."); - return new JsonSingleObject(HistoryManager.Format(result.Value), result.Value); + return new JsonSingleObject(info.Bot.HistoryManager.Format(result.Value), result.Value); } [Command("history id", "(last|next) Gets the highest|next song id")] - public JsonObject CommandHistoryId(string special) + public static JsonObject CommandHistoryId(ExecutionInformation info, string special) { if (special == "last") - return new JsonSingleValue($"{HistoryManager.HighestId} is the currently highest song id.", HistoryManager.HighestId); + return new JsonSingleValue($"{info.Bot.HistoryManager.HighestId} is the currently highest song id.", info.Bot.HistoryManager.HighestId); else if (special == "next") - return new JsonSingleValue($"{HistoryManager.HighestId + 1} will be the next song id.", HistoryManager.HighestId + 1); + return new JsonSingleValue($"{info.Bot.HistoryManager.HighestId + 1} will be the next song id.", info.Bot.HistoryManager.HighestId + 1); else throw new CommandException("Unrecognized name descriptor", CommandExceptionReason.CommandError); } @@ -704,20 +358,20 @@ public JsonObject CommandHistoryId(string special) [Command("history last", "Plays the last song again")] [Usage("", "Gets the last played songs.")] [RequiredParameters(0)] - public JsonObject CommandHistoryLast(ExecutionInformation info, int? amount) + public static JsonObject CommandHistoryLast(ExecutionInformation info, int? amount) { if (amount.HasValue) { var query = new SeachQuery { MaxResults = amount.Value }; - var results = HistoryManager.Search(query).ToArray(); - return new JsonArray(HistoryManager.Format(results), results); + var results = info.Bot.HistoryManager.Search(query).ToArray(); + return new JsonArray(info.Bot.HistoryManager.Format(results), results); } else { - var ale = HistoryManager.Search(new SeachQuery { MaxResults = 1 }).FirstOrDefault(); + var ale = info.Bot.HistoryManager.Search(new SeachQuery { MaxResults = 1 }).FirstOrDefault(); if (ale != null) { - PlayManager.Play(info.InvokerData, ale.AudioResource).UnwrapThrow(); + info.Bot.PlayManager.Play(info.InvokerData, ale.AudioResource).UnwrapThrow(); return null; } else return new JsonEmpty("There is no song in the history"); @@ -725,30 +379,30 @@ public JsonObject CommandHistoryLast(ExecutionInformation info, int? amount) } [Command("history play", " Playes the song with ")] - public void CommandHistoryPlay(ExecutionInformation info, uint id) - => PlayManager.Play(info.InvokerData, id).UnwrapThrow(); + public static void CommandHistoryPlay(ExecutionInformation info, uint id) + => info.Bot.PlayManager.Play(info.InvokerData, id).UnwrapThrow(); [Command("history rename", " Sets the name of the song with to ")] - public void CommandHistoryRename(uint id, string newName) + public static void CommandHistoryRename(ExecutionInformation info, uint id, string newName) { - var ale = HistoryManager.GetEntryById(id).UnwrapThrow(); + var ale = info.Bot.HistoryManager.GetEntryById(id).UnwrapThrow(); if (string.IsNullOrWhiteSpace(newName)) throw new CommandException("The new name must not be empty or only whitespaces", CommandExceptionReason.CommandError); - HistoryManager.RenameEntry(ale, newName); + info.Bot.HistoryManager.RenameEntry(ale, newName); } [Command("history till", " Gets all songs played until .")] - public JsonObject CommandHistoryTill(DateTime time) + public static JsonObject CommandHistoryTill(ExecutionInformation info, DateTime time) { var query = new SeachQuery { LastInvokedAfter = time }; - var results = HistoryManager.Search(query).ToArray(); - return new JsonArray(HistoryManager.Format(results), results); + var results = info.Bot.HistoryManager.Search(query).ToArray(); + return new JsonArray(info.Bot.HistoryManager.Format(results), results); } [Command("history till", " Any of those desciptors: (hour|today|yesterday|week)")] - public JsonObject CommandHistoryTill(string time) + public static JsonObject CommandHistoryTill(ExecutionInformation info, string time) { DateTime tillTime; switch (time.ToLower(CultureInfo.InvariantCulture)) @@ -760,22 +414,22 @@ public JsonObject CommandHistoryTill(string time) default: throw new CommandException("Not recognized time desciption.", CommandExceptionReason.CommandError); } var query = new SeachQuery { LastInvokedAfter = tillTime }; - var results = HistoryManager.Search(query).ToArray(); - return new JsonArray(HistoryManager.Format(results), results); + var results = info.Bot.HistoryManager.Search(query).ToArray(); + return new JsonArray(info.Bot.HistoryManager.Format(results), results); } [Command("history title", "Gets all songs which title contains ")] - public JsonObject CommandHistoryTitle(string part) + public static JsonObject CommandHistoryTitle(ExecutionInformation info, string part) { var query = new SeachQuery { TitlePart = part }; - var results = HistoryManager.Search(query).ToArray(); - return new JsonArray(HistoryManager.Format(results), results); + var results = info.Bot.HistoryManager.Search(query).ToArray(); + return new JsonArray(info.Bot.HistoryManager.Format(results), results); } [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 ICommandResult CommandIf(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public static ICommandResult CommandIf(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { if (arguments.Count < 4) throw new CommandException("Expected at least 4 arguments", CommandExceptionReason.MissingParameter); @@ -817,7 +471,7 @@ public ICommandResult CommandIf(ExecutionInformation info, IReadOnlyList arguments) + public static JsonObject CommandJsonMerge(ExecutionInformation info, IReadOnlyList arguments) { if (arguments.Count == 0) return new JsonEmpty(string.Empty); @@ -835,7 +489,7 @@ public JsonObject CommandJsonMerge(ExecutionInformation info, IReadOnlyList(link); } } [Command("list add", "Adds a link to your private playlist.")] [Usage("", "Any link that is also recognized by !play")] - public void CommandListAdd(ExecutionInformation info, string link) + public static void CommandListAdd(ExecutionInformation info, string link) { var plist = AutoGetPlaylist(info); - var playResource = FactoryManager.Load(link).UnwrapThrow(); + var playResource = info.Core.FactoryManager.Load(link).UnwrapThrow(); plist.AddItem(new PlaylistItem(playResource.BaseData, new MetaData() { ResourceOwnerDbId = info.InvokerData.DatabaseId })); } [Command("list add", " Adds a link to your private playlist from the history by .")] - public void CommandListAdd(ExecutionInformation info, uint hid) + public static void CommandListAdd(ExecutionInformation info, uint hid) { var plist = AutoGetPlaylist(info); - if (!HistoryManager.GetEntryById(hid)) + if (!info.Bot.HistoryManager.GetEntryById(hid)) throw new CommandException("History entry not found", CommandExceptionReason.CommandError); plist.AddItem(new PlaylistItem(hid, new MetaData() { ResourceOwnerDbId = info.InvokerData.DatabaseId })); } [Command("list clear", "Clears your private playlist.")] - public void CommandListClear(ExecutionInformation info) => AutoGetPlaylist(info).Clear(); + public static void CommandListClear(ExecutionInformation info) => AutoGetPlaylist(info).Clear(); [Command("list delete", " Deletes the playlist with the name . You can only delete playlists which you also have created. Admins can delete every playlist.")] - public JsonObject CommandListDelete(ExecutionInformation info, string name) + public static JsonObject CommandListDelete(ExecutionInformation info, string name) { if (info.ApiCall) - PlaylistManager.DeletePlaylist(name, info.InvokerData.DatabaseId ?? 0, info.HasRights(RightDeleteAllPlaylists)).UnwrapThrow(); + info.Bot.PlaylistManager.DeletePlaylist(name, info.InvokerData.DatabaseId ?? 0, info.HasRights(RightDeleteAllPlaylists)).UnwrapThrow(); - var hresult = PlaylistManager.LoadPlaylist(name, true); + var hresult = info.Bot.PlaylistManager.LoadPlaylist(name, true); if (!hresult) { info.Session.SetResponse(ResponseListDelete, name); @@ -918,9 +572,9 @@ public JsonObject CommandListDelete(ExecutionInformation info, string name) } [Command("list get", " Imports a playlist form an other plattform like youtube etc.")] - public JsonObject CommandListGet(ExecutionInformation info, string link) + public static JsonObject CommandListGet(ExecutionInformation info, string link) { - var playlist = info.Session.Bot.FactoryManager.LoadPlaylistFrom(link).UnwrapThrow(); + var playlist = info.Core.FactoryManager.LoadPlaylistFrom(link).UnwrapThrow(); playlist.CreatorDbId = info.InvokerData.DatabaseId; info.Session.Set(playlist); @@ -928,7 +582,7 @@ public JsonObject CommandListGet(ExecutionInformation info, string link) } [Command("list item move", " Moves a item in a playlist position.")] - public void CommandListMove(ExecutionInformation info, int from, int to) + public static void CommandListMove(ExecutionInformation info, int from, int to) { var plist = AutoGetPlaylist(info); @@ -945,7 +599,7 @@ public void CommandListMove(ExecutionInformation info, int from, int to) } [Command("list item delete", " Removes the item at .")] - public string CommandListRemove(ExecutionInformation info, int index) + public static string CommandListRemove(ExecutionInformation info, int index) { var plist = AutoGetPlaylist(info); @@ -962,9 +616,9 @@ public string CommandListRemove(ExecutionInformation info, int index) [Command("list list", "Displays all available playlists from all users.")] [Usage("", "Filters all lists cantaining the given pattern.")] [RequiredParameters(0)] - public JsonObject CommandListList(string pattern) + public static JsonObject CommandListList(ExecutionInformation info, string pattern) { - var files = PlaylistManager.GetAvailablePlaylists(pattern).ToArray(); + var files = info.Bot.PlaylistManager.GetAvailablePlaylists(pattern).ToArray(); if (files.Length <= 0) return new JsonArray("No playlists found", files); @@ -988,11 +642,11 @@ public JsonObject CommandListList(string pattern) } [Command("list load", "Opens a playlist to be editable for you. This replaces your current worklist with the opened playlist.")] - public JsonObject CommandListLoad(ExecutionInformation info, string name) + public static JsonObject CommandListLoad(ExecutionInformation info, string name) { Playlist loadList = AutoGetPlaylist(info); - var playList = PlaylistManager.LoadPlaylist(name).UnwrapThrow(); + var playList = info.Bot.PlaylistManager.LoadPlaylist(name).UnwrapThrow(); loadList.Clear(); loadList.AddRange(playList.AsEnumerable()); @@ -1001,11 +655,11 @@ public JsonObject CommandListLoad(ExecutionInformation info, string name) } [Command("list merge", "Appends another playlist to yours.")] - public void CommandListMerge(ExecutionInformation info, string name) + public static void CommandListMerge(ExecutionInformation info, string name) { var plist = AutoGetPlaylist(info); - var lresult = PlaylistManager.LoadPlaylist(name); + var lresult = info.Bot.PlaylistManager.LoadPlaylist(name); if (!lresult) throw new CommandException("The other playlist could not be found", CommandExceptionReason.CommandError); @@ -1014,7 +668,7 @@ public void CommandListMerge(ExecutionInformation info, string name) [Command("list name", "Displays the name of the playlist you are currently working on.")] [Usage("", "Changes the playlist name to .")] - public JsonObject CommandListName(ExecutionInformation info, string name) + public static JsonObject CommandListName(ExecutionInformation info, string name) { var plist = AutoGetPlaylist(info); @@ -1030,36 +684,36 @@ public JsonObject CommandListName(ExecutionInformation info, string name) [Command("list play", "Replaces the current freelist with your workinglist and plays from the beginning.")] [Usage("", "Lets you specify the starting song index.")] [RequiredParameters(0)] - public void CommandListPlay(ExecutionInformation info, int? index) + public static void CommandListPlay(ExecutionInformation info, int? index) { var plist = AutoGetPlaylist(info); if (!index.HasValue || (index.Value >= 0 && index.Value < plist.Count)) { - PlaylistManager.PlayFreelist(plist); - PlaylistManager.Index = index ?? 0; + info.Bot.PlaylistManager.PlayFreelist(plist); + info.Bot.PlaylistManager.Index = index ?? 0; } else throw new CommandException("Invalid starting index", CommandExceptionReason.CommandError); - PlaylistItem item = PlaylistManager.Current(); + PlaylistItem item = info.Bot.PlaylistManager.Current(); if (item != null) - PlayManager.Play(info.InvokerData, item).UnwrapThrow(); + info.Bot.PlayManager.Play(info.InvokerData, item).UnwrapThrow(); else throw new CommandException("Nothing to play...", CommandExceptionReason.CommandError); } [Command("list queue", "Appends your playlist to the freelist.")] - public void CommandListQueue(ExecutionInformation info) + public static void CommandListQueue(ExecutionInformation info) { var plist = AutoGetPlaylist(info); - PlayManager.Enqueue(plist.AsEnumerable()); + info.Bot.PlayManager.Enqueue(plist.AsEnumerable()); } [Command("list save", "Stores your current workinglist to disk.")] [Usage("", "Changes the playlist name to before saving.")] [RequiredParameters(0)] - public JsonObject CommandListSave(ExecutionInformation info, string optNewName) + public static JsonObject CommandListSave(ExecutionInformation info, string optNewName) { var plist = AutoGetPlaylist(info); if (!string.IsNullOrEmpty(optNewName)) @@ -1068,23 +722,23 @@ public JsonObject CommandListSave(ExecutionInformation info, string optNewName) plist.Name = optNewName; } - PlaylistManager.SavePlaylist(plist).UnwrapThrow(); + info.Bot.PlaylistManager.SavePlaylist(plist).UnwrapThrow(); return new JsonEmpty("Ok"); } [Command("list show", "Displays all songs currently in the playlists you are working on")] [Usage("", "Lets you specify the staring index from which songs should be listed.")] [RequiredParameters(0)] - public JsonObject CommandListShow(ExecutionInformation info, int? offset) => CommandListShow(info, null, offset); + public static JsonObject CommandListShow(ExecutionInformation info, int? offset) => CommandListShow(info, null, offset); [Command("list show", " Displays all songs currently in the playlists with the name ")] [Usage(" ", "Lets you specify the starting index from which songs should be listed.")] [RequiredParameters(0)] - public JsonObject CommandListShow(ExecutionInformation info, string name, int? offset) + public static JsonObject CommandListShow(ExecutionInformation info, string name, int? offset) { Playlist plist; if (!string.IsNullOrEmpty(name)) - plist = PlaylistManager.LoadPlaylist(name).UnwrapThrow(); + plist = info.Bot.PlaylistManager.LoadPlaylist(name).UnwrapThrow(); else plist = AutoGetPlaylist(info); @@ -1099,20 +753,20 @@ public JsonObject CommandListShow(ExecutionInformation info, string name, int? o } [Command("loop", "Gets whether or not to loop the entire playlist.")] - public JsonObject CommandLoop() => new JsonSingleValue("Loop is " + (PlaylistManager.Loop ? "on" : "off"), PlaylistManager.Loop); + public static JsonObject CommandLoop(ExecutionInformation info) => new JsonSingleValue("Loop is " + (info.Bot.PlaylistManager.Loop ? "on" : "off"), info.Bot.PlaylistManager.Loop); [Command("loop on", "Enables looping the entire playlist.")] - public void CommandLoopOn() => PlaylistManager.Loop = true; + public static void CommandLoopOn(ExecutionInformation info) => info.Bot.PlaylistManager.Loop = true; [Command("loop off", "Disables looping the entire playlist.")] - public void CommandLoopOff() => PlaylistManager.Loop = false; + public static void CommandLoopOff(ExecutionInformation info) => info.Bot.PlaylistManager.Loop = false; [Command("next", "Plays the next song in the playlist.")] - public void CommandNext(ExecutionInformation info) + public static void CommandNext(ExecutionInformation info) { - PlayManager.Next(info.InvokerData).UnwrapThrow(); + info.Bot.PlayManager.Next(info.InvokerData).UnwrapThrow(); } [Command("pm", "Requests a private session with the ServerBot so you can be intimate.")] - public string CommandPm(ExecutionInformation info) + public static string CommandPm(ExecutionInformation info) { if (info.ApiCall) throw new CommandException("This command is not available as API", CommandExceptionReason.NotSupported); @@ -1122,7 +776,7 @@ public string CommandPm(ExecutionInformation info) [Command("parse command", "Displays the AST of the requested command.")] [Usage("", "The comand to be parsed")] - public JsonObject CommandParse(string parameter) + public static JsonObject CommandParse(string parameter) { if (!parameter.TrimStart().StartsWith("!", StringComparison.Ordinal)) throw new CommandException("This is not a command", CommandExceptionReason.CommandError); @@ -1141,51 +795,49 @@ public JsonObject CommandParse(string parameter) } [Command("pause", "Well, pauses the song. Undo with !play.")] - public void CommandPause() => PlayerConnection.Paused = true; + public static void CommandPause(ExecutionInformation info) => info.Bot.PlayerConnection.Paused = true; [Command("play", "Automatically tries to decide whether the link is a special resource (like youtube) or a direct resource (like ./hello.mp3) and starts it.")] [Usage("", "Youtube, Soundcloud, local path or file link")] [RequiredParameters(0)] - public void CommandPlay(ExecutionInformation info, string parameter) + public static void CommandPlay(ExecutionInformation info, string parameter) { if (string.IsNullOrEmpty(parameter)) - PlayerConnection.Paused = false; + info.Bot.PlayerConnection.Paused = false; else - PlayManager.Play(info.InvokerData, parameter).UnwrapThrow(); + info.Bot.PlayManager.Play(info.InvokerData, parameter).UnwrapThrow(); } [Command("plugin list", "Lists all found plugins.")] - public JsonArray CommandPluginList() + public static JsonArray CommandPluginList(ExecutionInformation info) { - var overview = PluginManager.GetPluginOverview(); - return new JsonArray( - PluginManager.FormatOverview(overview), - overview); + var overview = info.Core.PluginManager.GetPluginOverview(); + return new JsonArray(PluginManager.FormatOverview(overview), overview); } [Command("plugin unload", "Unloads a plugin.")] - public void CommandPluginUnload(string identifier) + public static void CommandPluginUnload(ExecutionInformation info, string identifier) { - var result = PluginManager.StopPlugin(identifier); + var result = info.Core.PluginManager.StopPlugin(identifier); if (result != PluginResponse.Ok) throw new CommandException("Plugin error: " + result, CommandExceptionReason.CommandError); } [Command("plugin load", "Unloads a plugin.")] - public void CommandPluginLoad(string identifier) + public static void CommandPluginLoad(ExecutionInformation info, string identifier) { - var result = PluginManager.StartPlugin(identifier); + var result = info.Core.PluginManager.StartPlugin(identifier); if (result != PluginResponse.Ok) throw new CommandException("Plugin error: " + result, CommandExceptionReason.CommandError); } [Command("previous", "Plays the previous song in the playlist.")] - public void CommandPrevious(ExecutionInformation info) - => PlayManager.Previous(info.InvokerData).UnwrapThrow(); + public static void CommandPrevious(ExecutionInformation info) + => info.Bot.PlayManager.Previous(info.InvokerData).UnwrapThrow(); [Command("print", "Lets you format multiple parameter to one.")] [RequiredParameters(0)] - public JsonObject CommandPrint(params string[] parameter) + public static JsonObject CommandPrint(params string[] parameter) { // << Desing changes expected >> var strb = new StringBuilder(); @@ -1196,18 +848,18 @@ public JsonObject CommandPrint(params string[] parameter) [Command("quit", "Closes the TS3AudioBot application.")] [RequiredParameters(0)] - public string CommandQuit(ExecutionInformation info, string param) + public static string CommandQuit(ExecutionInformation info, string param) { if (info.ApiCall) { - Dispose(); + info.Bot.Dispose(); return null; } if (param == "force") { - QueryConnection.OnMessageReceived -= TextCallback; - Dispose(); + // TODO necessary?: info.Bot.QueryConnection.OnMessageReceived -= TextCallback; + info.Bot.Dispose(); return null; } else @@ -1218,56 +870,56 @@ public string CommandQuit(ExecutionInformation info, string param) } [Command("quiz", "Shows the quizmode status.")] - public JsonObject CommandQuiz() => new JsonSingleValue("Quizmode is " + (QuizMode ? "on" : "off"), QuizMode); + public static JsonObject CommandQuiz(ExecutionInformation info) => new JsonSingleValue("Quizmode is " + (info.Bot.QuizMode ? "on" : "off"), info.Bot.QuizMode); [Command("quiz on", "Enable to hide the songnames and let your friends guess the title.")] - public void CommandQuizOn() + public static void CommandQuizOn(ExecutionInformation info) { - QuizMode = true; - UpdateBotStatus().UnwrapThrow(); + info.Bot.QuizMode = true; + info.Bot.UpdateBotStatus().UnwrapThrow(); } [Command("quiz off", "Disable to show the songnames again.")] - public void CommandQuizOff(ExecutionInformation info) + public static void CommandQuizOff(ExecutionInformation info) { if (!info.ApiCall && info.InvokerData.Visibiliy.HasValue && info.InvokerData.Visibiliy != TextMessageTargetMode.Private) throw new CommandException("No cheatig! Everybody has to see it!", CommandExceptionReason.CommandError); - QuizMode = false; - UpdateBotStatus().UnwrapThrow(); + info.Bot.QuizMode = false; + info.Bot.UpdateBotStatus().UnwrapThrow(); } [Command("random", "Gets whether or not to play playlists in random order.")] - public JsonObject CommandRandom() => new JsonSingleValue("Random is " + (PlaylistManager.Random ? "on" : "off"), PlaylistManager.Random); + public static JsonObject CommandRandom(ExecutionInformation info) => new JsonSingleValue("Random is " + (info.Bot.PlaylistManager.Random ? "on" : "off"), info.Bot.PlaylistManager.Random); [Command("random on", "Enables random playlist playback")] - public void CommandRandomOn() => PlaylistManager.Random = true; + public static void CommandRandomOn(ExecutionInformation info) => info.Bot.PlaylistManager.Random = true; [Command("random off", "Disables random playlist playback")] - public void CommandRandomOff() => PlaylistManager.Random = false; + public static void CommandRandomOff(ExecutionInformation info) => info.Bot.PlaylistManager.Random = false; [Command("random seed", "Gets the unique seed for a certain playback order")] - public JsonObject CommandRandomSeed() + public static JsonObject CommandRandomSeed(ExecutionInformation info) { - string seed = Util.FromSeed(PlaylistManager.Seed); + string seed = Util.FromSeed(info.Bot.PlaylistManager.Seed); string strseed = string.IsNullOrEmpty(seed) ? "" : seed; return new JsonSingleValue(strseed); } [Command("random seed", "Sets the unique seed for a certain playback order")] - public void CommandRandomSeed(string newSeed) + public static void CommandRandomSeed(ExecutionInformation info, string newSeed) { if (newSeed.Any(c => !char.IsLetter(c))) throw new CommandException("Only letters allowed", CommandExceptionReason.CommandError); - PlaylistManager.Seed = Util.ToSeed(newSeed.ToLowerInvariant()); + info.Bot.PlaylistManager.Seed = Util.ToSeed(newSeed.ToLowerInvariant()); } [Command("random seed", "Sets the unique seed for a certain playback order")] - public void CommandRandomSeed(int newSeed) => PlaylistManager.Seed = newSeed; + public static void CommandRandomSeed(ExecutionInformation info, int newSeed) => info.Bot.PlaylistManager.Seed = newSeed; [Command("repeat", "Gets whether or not to loop a single song.")] - public JsonObject CommandRepeat() => new JsonSingleValue("Repeat is " + (PlayerConnection.Repeated ? "on" : "off"), PlayerConnection.Repeated); + public static JsonObject CommandRepeat(ExecutionInformation info) => new JsonSingleValue("Repeat is " + (info.Bot.PlayerConnection.Repeated ? "on" : "off"), info.Bot.PlayerConnection.Repeated); [Command("repeat on", "Enables single song repeat.")] - public void CommandRepeatOn() => PlayerConnection.Repeated = true; + public static void CommandRepeatOn(ExecutionInformation info) => info.Bot.PlayerConnection.Repeated = true; [Command("repeat off", "Disables single song repeat.")] - public void CommandRepeatOff() => PlayerConnection.Repeated = false; + public static void CommandRepeatOff(ExecutionInformation info) => info.Bot.PlayerConnection.Repeated = false; [Command("rights can", "Returns the subset of allowed commands the caller (you) can execute.")] - public JsonObject CommandRightsCan(ExecutionInformation info, params string[] rights) + public static JsonObject CommandRightsCan(ExecutionInformation info, params string[] rights) { - var result = RightsManager.GetRightsSubset(info.InvokerData, rights); + var result = info.Core.RightsManager.GetRightsSubset(info.InvokerData, rights); if (result.Length > 0) return new JsonArray(string.Join(", ", result), result); else @@ -1275,9 +927,9 @@ public JsonObject CommandRightsCan(ExecutionInformation info, params string[] ri } [Command("rights reload", "Reloads the rights configuration from file.")] - public JsonObject CommandRightsReload() + public static JsonObject CommandRightsReload(ExecutionInformation info) { - if (RightsManager.ReadFile()) + if (info.Core.RightsManager.ReadFile()) return new JsonEmpty("Ok"); else // TODO: this can be done nicer by returning the errors and warnings from parsing @@ -1289,7 +941,7 @@ public JsonObject CommandRightsReload() [Usage("", "Gets a number between 0 and ")] [Usage(" ", "Gets a number between and ")] [RequiredParameters(0)] - public JsonObject CommandRng(int? first, int? second) + public static JsonObject CommandRng(int? first, int? second) { int num; if (first.HasValue && second.HasValue) @@ -1308,7 +960,7 @@ public JsonObject CommandRng(int? first, int? second) [Command("seek", "Jumps to a timemark within the current song.")] [Usage("", "Time in seconds")] [Usage("", "Time in Minutes:Seconds")] - public void CommandSeek(string parameter) + public static void CommandSeek(ExecutionInformation info, string parameter) { TimeSpan span; bool parsed = false; @@ -1333,19 +985,19 @@ public void CommandSeek(string parameter) if (!parsed) throw new CommandException("The time was not in a correct format, see !help seek for more information.", CommandExceptionReason.CommandError); - else if (span < TimeSpan.Zero || span > PlayerConnection.Length) + else if (span < TimeSpan.Zero || span > info.Bot.PlayerConnection.Length) throw new CommandException("The point of time is not within the songlenth.", CommandExceptionReason.CommandError); else - PlayerConnection.Position = span; + info.Bot.PlayerConnection.Position = span; } [Command("settings", "Changes values from the settigns. Not all changes can be applied immediately.")] [Usage("", "Get the value of a setting")] [Usage(" ", "Set the value of a setting")] [RequiredParameters(0)] - public JsonObject CommandSettings(string key, string value) + public static JsonObject CommandSettings(ExecutionInformation info, string key, string value) { - var configMap = ConfigManager.GetConfigMap(); + var configMap = info.Core.ConfigManager.GetConfigMap(); if (string.IsNullOrEmpty(key)) throw new CommandException("Please specify a key like: \n " + string.Join("\n ", configMap.Take(3).Select(kvp => kvp.Key)), CommandExceptionReason.MissingParameter); @@ -1365,7 +1017,7 @@ public JsonObject CommandSettings(string key, string value) } else { - var result = ConfigManager.SetSetting(filteredArr[0].Key, value); + var result = info.Core.ConfigManager.SetSetting(filteredArr[0].Key, value); if (result.Ok) return null; else throw new CommandException(result.Message, CommandExceptionReason.CommandError); } @@ -1378,54 +1030,54 @@ public JsonObject CommandSettings(string key, string value) } [Command("song", "Tells you the name of the current song.")] - public JsonObject CommandSong(ExecutionInformation info) + public static JsonObject CommandSong(ExecutionInformation info) { - if (PlayManager.CurrentPlayData == null) + if (info.Bot.PlayManager.CurrentPlayData == null) return new JsonEmpty("There is nothing on right now..."); - else if (QuizMode && PlayManager.CurrentPlayData.Invoker.ClientId != info.InvokerData.ClientId && !info.ApiCall) + else if (info.Bot.QuizMode && info.Bot.PlayManager.CurrentPlayData.Invoker.ClientId != info.InvokerData.ClientId && !info.ApiCall) return new JsonEmpty("Sorry, you have to guess!"); else return new JsonSingleValue( - $"[url={FactoryManager.RestoreLink(PlayManager.CurrentPlayData.ResourceData)}]{PlayManager.CurrentPlayData.ResourceData.ResourceTitle}[/url]", - PlayManager.CurrentPlayData.ResourceData.ResourceTitle); + $"[url={info.Core.FactoryManager.RestoreLink(info.Bot.PlayManager.CurrentPlayData.ResourceData)}]{info.Bot.PlayManager.CurrentPlayData.ResourceData.ResourceTitle}[/url]", + info.Bot.PlayManager.CurrentPlayData.ResourceData.ResourceTitle); } [Command("stop", "Stops the current song.")] - public void CommandStop() + public static void CommandStop(ExecutionInformation info) { - PlayManager.Stop(); + info.Bot.PlayManager.Stop(); } [Command("subscribe", "Lets you hear the music independent from the channel you are in.")] - public void CommandSubscribe(ExecutionInformation info) + public static void CommandSubscribe(ExecutionInformation info) { if (info.InvokerData.ClientId.HasValue) - TargetManager.WhisperClientSubscribe(info.InvokerData.ClientId.Value); + info.Bot.TargetManager.WhisperClientSubscribe(info.InvokerData.ClientId.Value); } [Command("subscribe tempchannel", "Adds your current channel to the music playback.")] [RequiredParameters(0)] - public void CommandSubscribeTempChannel(ExecutionInformation info, ulong? channel) + public static void CommandSubscribeTempChannel(ExecutionInformation info, ulong? channel) { var subChan = channel ?? info.InvokerData.ChannelId ?? 0; if (subChan != 0) - TargetManager.WhisperChannelSubscribe(subChan, true); + info.Bot.TargetManager.WhisperChannelSubscribe(subChan, true); } [Command("subscribe channel", "Adds your current channel to the music playback.")] [RequiredParameters(0)] - public void CommandSubscribeChannel(ExecutionInformation info, ulong? channel) + public static void CommandSubscribeChannel(ExecutionInformation info, ulong? channel) { var subChan = channel ?? info.InvokerData.ChannelId ?? 0; if (subChan != 0) - TargetManager.WhisperChannelSubscribe(subChan, false); + info.Bot.TargetManager.WhisperChannelSubscribe(subChan, false); } [Command("take", "Take a substring from a string.")] [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 ICommandResult CommandTake(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) + public static ICommandResult CommandTake(ExecutionInformation info, IReadOnlyList arguments, IReadOnlyList returnTypes) { if (arguments.Count < 2) throw new CommandException("Expected at least 2 parameters", CommandExceptionReason.MissingParameter); @@ -1473,7 +1125,7 @@ public ICommandResult CommandTake(ExecutionInformation info, IReadOnlyList(data.ToLongString(), data); @@ -1534,10 +1186,10 @@ public JsonObject CommandVersion() [Usage("", "A new volume level between 0 and 100.")] [Usage("+/-", "Adds or subtracts a value form the current volume.")] [RequiredParameters(0)] - public JsonObject CommandVolume(ExecutionInformation info, string parameter) + public static JsonObject CommandVolume(ExecutionInformation info, string parameter) { if (string.IsNullOrEmpty(parameter)) - return new JsonSingleValue("Current volume: " + PlayerConnection.Volume, PlayerConnection.Volume); + return new JsonSingleValue("Current volume: " + info.Bot.PlayerConnection.Volume, info.Bot.PlayerConnection.Volume); bool relPos = parameter.StartsWith("+", StringComparison.Ordinal); bool relNeg = parameter.StartsWith("-", StringComparison.Ordinal); @@ -1547,15 +1199,15 @@ public JsonObject CommandVolume(ExecutionInformation info, string parameter) throw new CommandException("The new volume could not be parsed", CommandExceptionReason.CommandError); int newVolume; - if (relPos) newVolume = PlayerConnection.Volume + volume; - else if (relNeg) newVolume = PlayerConnection.Volume - volume; + if (relPos) newVolume = info.Bot.PlayerConnection.Volume + volume; + else if (relNeg) newVolume = info.Bot.PlayerConnection.Volume - volume; else newVolume = volume; if (newVolume < 0 || newVolume > AudioValues.MaxVolume) throw new CommandException("The volume level must be between 0 and " + AudioValues.MaxVolume, CommandExceptionReason.CommandError); - if (newVolume <= AudioValues.MaxUserVolume || newVolume < PlayerConnection.Volume || info.ApiCall) - PlayerConnection.Volume = newVolume; + if (newVolume <= AudioValues.MaxUserVolume || newVolume < info.Bot.PlayerConnection.Volume || info.ApiCall) + info.Bot.PlayerConnection.Volume = newVolume; else if (newVolume <= AudioValues.MaxVolume) { info.Session.SetResponse(ResponseVolume, newVolume); @@ -1565,36 +1217,36 @@ public JsonObject CommandVolume(ExecutionInformation info, string parameter) } [Command("whisper off", "Enables normal voice mode.")] - public void CommandWhisperOff() => TargetManager.SendMode = TargetSendMode.Voice; + public static void CommandWhisperOff(ExecutionInformation info) => info.Bot.TargetManager.SendMode = TargetSendMode.Voice; [Command("whisper subscription", "Enables default whisper subsciption mode.")] - public void CommandWhisperSubsription() => TargetManager.SendMode = TargetSendMode.Whisper; + public static void CommandWhisperSubsription(ExecutionInformation info) => info.Bot.TargetManager.SendMode = TargetSendMode.Whisper; [Command("whisper all", "Set how to send music.")] - public void CommandWhisperAll() => CommandWhisperGroup(GroupWhisperType.AllClients, GroupWhisperTarget.AllChannels); + public static void CommandWhisperAll(ExecutionInformation info) => CommandWhisperGroup(info, GroupWhisperType.AllClients, GroupWhisperTarget.AllChannels); [Command("whisper group", "Set a specific teamspeak whisper group.")] [RequiredParameters(2)] - public void CommandWhisperGroup(GroupWhisperType type, GroupWhisperTarget target, ulong? targetId = null) + public static void CommandWhisperGroup(ExecutionInformation info, GroupWhisperType type, GroupWhisperTarget target, ulong? targetId = null) { if (type == GroupWhisperType.ServerGroup || type == GroupWhisperType.ChannelGroup) { if (!targetId.HasValue) throw new CommandException("This type required an additional target", CommandExceptionReason.CommandError); - TargetManager.SetGroupWhisper(type, target, targetId.Value); - TargetManager.SendMode = TargetSendMode.WhisperGroup; + info.Bot.TargetManager.SetGroupWhisper(type, target, targetId.Value); + info.Bot.TargetManager.SendMode = TargetSendMode.WhisperGroup; } else { if (targetId.HasValue) throw new CommandException("This type does not take an additional target", CommandExceptionReason.CommandError); - TargetManager.SetGroupWhisper(type, target, 0); - TargetManager.SendMode = TargetSendMode.WhisperGroup; + info.Bot.TargetManager.SetGroupWhisper(type, target, 0); + info.Bot.TargetManager.SendMode = TargetSendMode.WhisperGroup; } } [Command("xecute", "Evaluates all parameter.")] - public void CommandXecute(ExecutionInformation info, IReadOnlyList arguments) + public static void CommandXecute(ExecutionInformation info, IReadOnlyList arguments) { var retType = new[] { CommandResultType.Empty, CommandResultType.String, CommandResultType.Json }; foreach (var arg in arguments) @@ -1606,7 +1258,7 @@ public void CommandXecute(ExecutionInformation info, IReadOnlyList arg #region RESPONSES - private string ResponseVolume(ExecutionInformation info) + private static string ResponseVolume(ExecutionInformation info) { Answer answer = TextUtil.GetAnswer(info.TextMessage); if (answer == Answer.Yes) @@ -1615,7 +1267,7 @@ private string ResponseVolume(ExecutionInformation info) { if (info.Session.ResponseData is int respInt) { - PlayerConnection.Volume = respInt; + info.Bot.PlayerConnection.Volume = respInt; } else { @@ -1631,7 +1283,7 @@ private string ResponseVolume(ExecutionInformation info) return null; } - private string ResponseQuit(ExecutionInformation info) + private static string ResponseQuit(ExecutionInformation info) { Answer answer = TextUtil.GetAnswer(info.TextMessage); if (answer == Answer.Yes) @@ -1644,7 +1296,7 @@ private string ResponseQuit(ExecutionInformation info) return null; } - private string ResponseHistoryDelete(ExecutionInformation info) + private static string ResponseHistoryDelete(ExecutionInformation info) { Answer answer = TextUtil.GetAnswer(info.TextMessage); if (answer == Answer.Yes) @@ -1653,7 +1305,7 @@ private string ResponseHistoryDelete(ExecutionInformation info) { if (info.Session.ResponseData is AudioLogEntry ale) { - HistoryManager.RemoveEntry(ale); + info.Bot.HistoryManager.RemoveEntry(ale); } else { @@ -1669,7 +1321,7 @@ private string ResponseHistoryDelete(ExecutionInformation info) return null; } - private string ResponseHistoryClean(ExecutionInformation info) + private static string ResponseHistoryClean(ExecutionInformation info) { Answer answer = TextUtil.GetAnswer(info.TextMessage); if (answer == Answer.Yes) @@ -1679,12 +1331,12 @@ private string ResponseHistoryClean(ExecutionInformation info) string param = info.Session.ResponseData as string; if (string.IsNullOrEmpty(param)) { - Database.CleanFile(); + info.Core.Database.CleanFile(); return "Cleanup done!"; } else if (param == "removedefective") { - HistoryManager.RemoveBrokenLinks(info); + info.Bot.HistoryManager.RemoveBrokenLinks(info); return "Cleanup done!"; } else @@ -1696,13 +1348,13 @@ private string ResponseHistoryClean(ExecutionInformation info) return null; } - private string ResponseListDelete(ExecutionInformation info) + private static string ResponseListDelete(ExecutionInformation info) { Answer answer = TextUtil.GetAnswer(info.TextMessage); if (answer == Answer.Yes) { var name = info.Session.ResponseData as string; - var result = PlaylistManager.DeletePlaylist(name, info.InvokerData.DatabaseId ?? 0, info.HasRights(RightDeleteAllPlaylists)); + var result = info.Bot.PlaylistManager.DeletePlaylist(name, info.InvokerData.DatabaseId ?? 0, info.HasRights(RightDeleteAllPlaylists)); if (!result) return result.Message; else return "Ok"; } @@ -1711,67 +1363,6 @@ private string ResponseListDelete(ExecutionInformation info) #endregion - private void LoggedUpdateBotStatus(object sender, EventArgs e) - { - var result = UpdateBotStatus(); - if (!result) - Log.Write(Log.Level.Warning, result.Message); - } - - private R UpdateBotStatus(string overrideStr = null) - { - string setString; - if (overrideStr != null) - { - setString = overrideStr; - } - else if (PlayManager.IsPlaying) - { - setString = QuizMode - ? "" - : PlayManager.CurrentPlayData.ResourceData.ResourceTitle; - } - else - { - setString = ""; - } - - return QueryConnection.ChangeDescription(setString); - } - - private void GenerateStatusImage(object sender, EventArgs e) - { - if (!mainBotData.GenerateStatusAvatar) - return; - - if (e is PlayInfoEventArgs startEvent) - { - var thumresult = FactoryManager.GetThumbnail(startEvent.PlayResource); - if (!thumresult.Ok) - return; - - using (var bmp = ImageUtil.BuildStringImage("Now playing: " + startEvent.ResourceData.ResourceTitle, thumresult.Value)) - { - using (var mem = new MemoryStream()) - { - bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg); - var result = QueryConnection.UploadAvatar(mem); - if (!result.Ok) - Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Message); - } - } - } - else - { - using (var sleepPic = Util.GetEmbeddedFile("TS3AudioBot.Media.SleepingKitty.png")) - { - var result = QueryConnection.UploadAvatar(sleepPic); - if (!result.Ok) - Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Message); - } - } - } - private static Playlist AutoGetPlaylist(ExecutionInformation info) { var result = info.Session.Get(); @@ -1782,49 +1373,5 @@ private static Playlist AutoGetPlaylist(ExecutionInformation info) info.Session.Set(newPlist); return newPlist; } - - public void Dispose() - { - if (!isDisposed) isDisposed = true; - else return; - Log.Write(Log.Level.Info, "Exiting..."); - - WebManager?.Dispose(); // before: logStream, - WebManager = null; - - PluginManager?.Dispose(); // before: SessionManager, logStream, - PluginManager = null; - - PlayManager?.Stop(); - - PlayerConnection?.Dispose(); // before: logStream, - PlayerConnection = null; - - QueryConnection?.Dispose(); // before: logStream, - QueryConnection = null; - - Database?.Dispose(); // before: logStream, - Database = null; - - TickPool.Close(); // before: - - FactoryManager?.Dispose(); // before: - FactoryManager = null; - - logStream?.Dispose(); // before: - logStream = null; - } - } - -#pragma warning disable CS0649 - internal class MainBotData : ConfigData - { - [Info("Path to the logfile", "ts3audiobot.log")] - public string LogFile { get; set; } - [Info("Teamspeak group id giving the Bot enough power to do his job", "0")] - public ulong BotGroupId { get; set; } - [Info("Generate fancy status images as avatar", "true")] - public bool GenerateStatusAvatar { get; set; } } -#pragma warning restore CS0649 } diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs new file mode 100644 index 00000000..a26abaac --- /dev/null +++ b/TS3AudioBot/Core.cs @@ -0,0 +1,267 @@ +// 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 +{ + using CommandSystem; + using Helper; + using History; + using Plugins; + using ResourceFactories; + using Rights; + using System; + using System.IO; + using System.Threading; + using Web; + + public class Core : IDisposable + { + private bool isRunning; + private bool consoleOutput; + private bool writeLog; + private bool writeLogStack; + internal string configFilePath; + private StreamWriter logStream; + private MainBotData mainBotData; + + internal static void Main(string[] args) + { + Thread.CurrentThread.Name = "TAB Main"; + + var core = new Core(); + + AppDomain.CurrentDomain.UnhandledException += (s, e) => + { + Log.Write(Log.Level.Error, "Critical program failure!. Exception:\n{0}", (e.ExceptionObject as Exception).UnrollException()); + core.Dispose(); + }; + + Console.CancelKeyPress += (s, e) => + { + if (e.SpecialKey == ConsoleSpecialKey.ControlC) + { + e.Cancel = true; + core.Dispose(); + } + }; + + if (!core.ReadParameter(args)) + return; + + if (!core.Initialize()) + core.Dispose(); + + core.Run(); + } + + /// General purpose persistant storage for internal modules. + internal DbStore Database { get; set; } + /// Manges plugins, provides various loading and unloading mechanisms. + internal PluginManager PluginManager { get; set; } + /// Mangement for the bot command system. + public CommandManager CommandManager { get; private set; } + /// Manages factories which can load resources. + public ResourceFactoryManager FactoryManager { get; private set; } + /// Minimalistic webserver hosting the api and web-interface. + public WebManager WebManager { get; private set; } + /// Minimalistic config store for automatically serialized classes. + public ConfigFile ConfigManager { get; private set; } + /// Permission system of the bot. + public RightsManager RightsManager { get; private set; } + /// Permission system of the bot. + public BotManager Bots { get; private set; } + + public Core() + { + // setting defaults + isRunning = true; + consoleOutput = true; + writeLog = true; + writeLogStack = false; + configFilePath = "configTS3AudioBot.cfg"; + } + + private bool ReadParameter(string[] args) + { + for (int i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "-h": + case "--help": + Console.WriteLine(" --quiet -q Deactivates all output to stdout."); + Console.WriteLine(" --no-log -L Deactivates writing to the logfile."); + Console.WriteLine(" --stack -s Adds the stacktrace to all log writes."); + Console.WriteLine(" --config -c Specifies the path to the config file."); + Console.WriteLine(" --version -V Gets the bot version."); + Console.WriteLine(" --help -h Prints this help...."); + return false; + + case "-q": + case "--quiet": + consoleOutput = false; + break; + + case "-L": + case "--no-log": + writeLog = false; + break; + + case "-s": + case "--stack": + writeLogStack = true; + break; + + case "-c": + case "--config": + if (i >= args.Length - 1) + { + Console.WriteLine("No config file specified after \"{0}\"", args[i]); + return false; + } + configFilePath = args[++i]; + break; + + case "-V": + case "--version": + Console.WriteLine(Util.GetAssemblyData().ToLongString()); + return false; + + default: + Console.WriteLine("Unrecognized parameter: {0}", args[i]); + return false; + } + } + return true; + } + + private bool Initialize() + { + ConfigManager = ConfigFile.OpenOrCreate(configFilePath) ?? ConfigFile.CreateDummy(); + var pmd = ConfigManager.GetDataStruct("PluginManager", true); + var webd = ConfigManager.GetDataStruct("WebData", true); + var rmd = ConfigManager.GetDataStruct("RightsManager", true); + var hmd = ConfigManager.GetDataStruct("HistoryManager", true); + + var yfd = ConfigManager.GetDataStruct("YoutubeFactory", true); + var mfd = ConfigManager.GetDataStruct("MediaFactory", true); + + // TODO: DUMMY REQUESTS + ConfigManager.GetDataStruct("AudioFramework", true); + ConfigManager.GetDataStruct("QueryConnection", true); + ConfigManager.GetDataStruct("PlaylistManager", true); + mainBotData = ConfigManager.GetDataStruct("MainBot", true); + // END TODO + ConfigManager.Close(); + + mainBotData = ConfigManager.GetDataStruct("MainBot", true); + ConfigManager.Close(); + + if (consoleOutput) + { + void ColorLog(string msg, Log.Level lvl) + { + switch (lvl) + { + case Log.Level.Debug: break; + case Log.Level.Info: Console.ForegroundColor = ConsoleColor.Cyan; break; + case Log.Level.Warning: Console.ForegroundColor = ConsoleColor.Yellow; break; + case Log.Level.Error: Console.ForegroundColor = ConsoleColor.Red; break; + default: throw new ArgumentOutOfRangeException(nameof(lvl), lvl, null); + } + Console.WriteLine(msg); + Console.ResetColor(); + } + + Log.RegisterLogger("[%T]%L: %M", 19, ColorLog); + Log.RegisterLogger("Error call Stack:\n%S", 19, ColorLog, Log.Level.Error); + } + + if (writeLog && !string.IsNullOrEmpty(mainBotData.LogFile)) + { + logStream = new StreamWriter(File.Open(mainBotData.LogFile, FileMode.Append, FileAccess.Write, FileShare.Read), Util.Utf8Encoder); + Log.RegisterLogger("[%T]%L: %M\n" + (writeLogStack ? "%S\n" : ""), 19, (msg, lvl) => + { + if (logStream == null) return; + try + { + logStream.Write(msg); + logStream.Flush(); + } + catch (IOException) { } + }); + } + + Log.Write(Log.Level.Info, "[============ TS3AudioBot started =============]"); + Log.Write(Log.Level.Info, "[=== Date/Time: {0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString()); + Log.Write(Log.Level.Info, "[=== Version: {0}", Util.GetAssemblyData().ToString()); + Log.Write(Log.Level.Info, "[==============================================]"); + + Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); + CommandManager = new CommandManager(); + CommandManager.RegisterMain(); + Database = new DbStore(hmd); + RightsManager = new RightsManager(this, rmd); + WebManager = new WebManager(this, webd); + PluginManager = new PluginManager(this, pmd); + Bots = new BotManager(this); + + RightsManager.RegisterRights(CommandManager.AllRights); + RightsManager.RegisterRights(Commands.RightHighVolume, Commands.RightDeleteAllPlaylists); + if (!RightsManager.ReadFile()) + return false; + + Log.Write(Log.Level.Info, "[=========== Initializing Factories ===========]"); + YoutubeDlHelper.DataObj = yfd; + FactoryManager = new ResourceFactoryManager(this); + FactoryManager.AddFactory(new MediaFactory(mfd)); + FactoryManager.AddFactory(new YoutubeFactory(yfd)); + FactoryManager.AddFactory(new SoundcloudFactory()); + FactoryManager.AddFactory(new TwitchFactory()); + + Log.Write(Log.Level.Info, "[================= Finalizing =================]"); + PluginManager.RestorePlugins(); + + WebManager.StartServerAsync(); + + return true; + } + + private void Run() + { + Bots.WatchBots(); + } + + public void Dispose() + { + Log.Write(Log.Level.Info, "TS3AudioBot shutting down."); + isRunning = false; + + Bots?.Dispose(); + Bots = null; + + PluginManager?.Dispose(); // before: SessionManager, logStream, + PluginManager = null; + + WebManager?.Dispose(); // before: logStream, + WebManager = null; + + Database?.Dispose(); // before: logStream, + Database = null; + + FactoryManager?.Dispose(); // before: + FactoryManager = null; + + TickPool.Close(); + + logStream?.Dispose(); + logStream = null; + } + } +} diff --git a/TS3AudioBot/DocGen.cs b/TS3AudioBot/DocGen.cs index 0ed25129..d9415d67 100644 --- a/TS3AudioBot/DocGen.cs +++ b/TS3AudioBot/DocGen.cs @@ -23,13 +23,12 @@ private static void Main(string[] args) { const bool writeSubCommands = false; - var bot = new MainBot(); - typeof(MainBot).GetProperty(nameof(MainBot.CommandManager)).SetValue(bot, new CommandManager()); - bot.CommandManager.RegisterMain(bot); + var cmdMgr = new CommandManager(); + cmdMgr.RegisterMain(); var lines = new List(); - foreach (var com in bot.CommandManager.AllCommands) + foreach (var com in cmdMgr.AllCommands) { if (!writeSubCommands && com.InvokeName.Contains(" ")) continue; diff --git a/TS3AudioBot/Helper/ConfigFile.cs b/TS3AudioBot/Helper/ConfigFile.cs index cbefc838..4b2000da 100644 --- a/TS3AudioBot/Helper/ConfigFile.cs +++ b/TS3AudioBot/Helper/ConfigFile.cs @@ -58,6 +58,9 @@ public static ConfigFile CreateDummy() if (string.IsNullOrEmpty(associatedClass)) throw new ArgumentNullException(nameof(associatedClass)); + if (confObjects.TryGetValue(associatedClass, out var co)) + return (T)co; + var dataStruct = new T(); var fields = typeof(T).GetProperties(); foreach (var field in fields) diff --git a/TS3AudioBot/Helper/ImageUtil.cs b/TS3AudioBot/Helper/ImageUtil.cs index 919d7338..4e2f8973 100644 --- a/TS3AudioBot/Helper/ImageUtil.cs +++ b/TS3AudioBot/Helper/ImageUtil.cs @@ -18,7 +18,6 @@ namespace TS3AudioBot.Helper internal static class ImageUtil { private static readonly StringFormat AvatarTextFormat = new StringFormat { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Near }; - private static readonly Pen AvatarTextOutline = new Pen(Color.Black, 4) { LineJoin = LineJoin.Round }; public static Image BuildStringImage(string str, Image img, int resizeMaxWidth = 320) { @@ -43,7 +42,10 @@ public static Image BuildStringImage(string str, Image img, int resizeMaxWidth = graphics.TextRenderingHint = TextRenderingHint.AntiAlias; graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawPath(AvatarTextOutline, gp); + using (Pen AvatarTextOutline = new Pen(Color.Black, 4) {LineJoin = LineJoin.Round}) + { + graphics.DrawPath(AvatarTextOutline, gp); + } graphics.FillPath(Brushes.White, gp); } } @@ -129,7 +131,10 @@ private static void BuildStringImageLinux(string str, Graphics target, Rectangle gp.AddString(part, FontFamily.GenericMonospace, 0, 15, buildRect, AvatarTextFormat); bg.Clear(Color.Transparent); - bg.DrawPath(AvatarTextOutline, gp); + using (Pen AvatarTextOutline = new Pen(Color.Black, 4) {LineJoin = LineJoin.Round}) + { + bg.DrawPath(AvatarTextOutline, gp); + } bg.FillPath(Brushes.White, gp); target.DrawImageUnscaled(builder, (int)(rect.X + j * (maxMonoBugWidth - 5)), (int)(rect.Y + i * charH)); diff --git a/TS3AudioBot/History/HistoryManager.cs b/TS3AudioBot/History/HistoryManager.cs index 1e424a88..7bd5dab1 100644 --- a/TS3AudioBot/History/HistoryManager.cs +++ b/TS3AudioBot/History/HistoryManager.cs @@ -25,6 +25,7 @@ public sealed class HistoryManager private readonly LiteCollection audioLogEntries; private readonly HistoryManagerData historyManagerData; private readonly LinkedList unusedIds; + private readonly object dbLock = new object(); public IHistoryFormatter Formatter { get; private set; } public uint HighestId => (uint)audioLogEntries.Max().AsInt32; @@ -98,18 +99,22 @@ private AudioLogEntry Store(HistorySaveData saveData) if (saveData == null) throw new ArgumentNullException(nameof(saveData)); - var ale = FindByUniqueId(saveData.Resource.UniqueId); - if (ale == null) + lock (dbLock) { - ale = CreateLogEntry(saveData); + var ale = FindByUniqueId(saveData.Resource.UniqueId); if (ale == null) - Log.Write(Log.Level.Error, "AudioLogEntry could not be created!"); - } - else - { - LogEntryPlay(ale); + { + ale = CreateLogEntry(saveData); + if (ale == null) + Log.Write(Log.Level.Error, "AudioLogEntry could not be created!"); + } + else + { + LogEntryPlay(ale); + } + + return ale; } - return ale; } /// Increases the playcount and updates the last playtime. @@ -211,11 +216,11 @@ public R GetEntryById(uint id) /// Removes the from the Database. /// The to delete. - public void RemoveEntry(AudioLogEntry ale) + public bool RemoveEntry(AudioLogEntry ale) { if (ale == null) throw new ArgumentNullException(nameof(ale)); - audioLogEntries.Delete(ale.Id); + return audioLogEntries.Delete(ale.Id); } /// Sets the name of a . @@ -246,9 +251,11 @@ public void RemoveBrokenLinks(CommandSystem.ExecutionInformation info) foreach (var entry in currentIter) { - RemoveEntry(entry); - info.Bot.PlaylistManager.AddToTrash(new PlaylistItem(entry.AudioResource)); - info.Write($"Removed: {entry.Id} - {entry.AudioResource.ResourceTitle}"); + if (RemoveEntry(entry)) + { + info.Bot.PlaylistManager.AddToTrash(new PlaylistItem(entry.AudioResource)); + info.Write($"Removed: {entry.Id} - {entry.AudioResource.ResourceTitle}"); + } } } @@ -265,7 +272,7 @@ private static List FilterList(CommandSystem.ExecutionInformation var nextIter = new List(); foreach (var entry in list) { - var result = info.Bot.FactoryManager.Load(entry.AudioResource); + var result = info.Core.FactoryManager.Load(entry.AudioResource); if (!result) { info.Write($"//DEBUG// ({entry.AudioResource.UniqueId}) Reason: {result.Message}"); diff --git a/TS3AudioBot/PlayManager.cs b/TS3AudioBot/PlayManager.cs index 572e7b80..b7fa6622 100644 --- a/TS3AudioBot/PlayManager.cs +++ b/TS3AudioBot/PlayManager.cs @@ -17,10 +17,11 @@ namespace TS3AudioBot public class PlayManager { - private readonly MainBot botParent; + private readonly Core core; + private readonly Bot botParent; private IPlayerConnection PlayerConnection => botParent.PlayerConnection; private PlaylistManager PlaylistManager => botParent.PlaylistManager; - private ResourceFactoryManager ResourceFactoryManager => botParent.FactoryManager; + private ResourceFactoryManager ResourceFactoryManager => core.FactoryManager; private HistoryManager HistoryManager => botParent.HistoryManager; public PlayInfoEventArgs CurrentPlayData { get; private set; } @@ -31,8 +32,9 @@ public class PlayManager public event EventHandler BeforeResourceStopped; public event EventHandler AfterResourceStopped; - public PlayManager(MainBot parent) + public PlayManager(Core core, Bot parent) { + this.core = core; botParent = parent; } diff --git a/TS3AudioBot/Plugins/ITabPlugin.cs b/TS3AudioBot/Plugins/ITabPlugin.cs index 140f62fc..69e75851 100644 --- a/TS3AudioBot/Plugins/ITabPlugin.cs +++ b/TS3AudioBot/Plugins/ITabPlugin.cs @@ -13,6 +13,6 @@ namespace TS3AudioBot.Plugins public interface ITabPlugin : IDisposable { - void Initialize(MainBot bot); + void Initialize(Core core); } } diff --git a/TS3AudioBot/Plugins/Plugin.cs b/TS3AudioBot/Plugins/Plugin.cs index 46248c10..5afcdbc0 100644 --- a/TS3AudioBot/Plugins/Plugin.cs +++ b/TS3AudioBot/Plugins/Plugin.cs @@ -23,7 +23,7 @@ namespace TS3AudioBot.Plugins internal class Plugin : ICommandBag { - private readonly MainBot mainBot; + private readonly Core core; private Assembly assembly; private byte[] md5CacheSum; @@ -32,9 +32,9 @@ internal class Plugin : ICommandBag private Type coreType; private PluginType corePluginType; - public Plugin(FileInfo pluginFile, MainBot parent, int id) + public Plugin(FileInfo pluginFile, Core core, int id) { - mainBot = parent; + this.core = core; PluginFile = pluginFile; Id = id; Status = PluginStatus.Off; @@ -82,7 +82,6 @@ public bool PersistentEnabled public IEnumerable ExposedCommands { get; private set; } public IEnumerable ExposedRights => ExposedCommands.Select(x => x.RequiredRight); - public PluginResponse Load() { try @@ -273,14 +272,14 @@ public void Start() pluginObject = (ITabPlugin)Activator.CreateInstance(coreType); var comBuilds = CommandManager.GetCommandMethods(pluginObject); ExposedCommands = CommandManager.GetBotCommands(comBuilds).ToList(); - mainBot.RightsManager.RegisterRights(ExposedRights); - mainBot.CommandManager.RegisterCollection(this); - pluginObject.Initialize(mainBot); + core.RightsManager.RegisterRights(ExposedRights); + core.CommandManager.RegisterCollection(this); + pluginObject.Initialize(core); break; case PluginType.Factory: factoryObject = (IFactory)Activator.CreateInstance(coreType); - mainBot.FactoryManager.AddFactory(factoryObject); + core.FactoryManager.AddFactory(factoryObject); break; default: @@ -312,15 +311,15 @@ public void Stop() break; case PluginType.Plugin: - mainBot.CommandManager.UnregisterCollection(this); - mainBot.RightsManager.UnregisterRights(ExposedRights); + core.CommandManager.UnregisterCollection(this); + core.RightsManager.UnregisterRights(ExposedRights); ExposedCommands = null; pluginObject.Dispose(); pluginObject = null; break; case PluginType.Factory: - mainBot.FactoryManager.RemoveFactory(factoryObject); + core.FactoryManager.RemoveFactory(factoryObject); break; default: diff --git a/TS3AudioBot/Plugins/PluginManager.cs b/TS3AudioBot/Plugins/PluginManager.cs index 90cb4522..cf7935d7 100644 --- a/TS3AudioBot/Plugins/PluginManager.cs +++ b/TS3AudioBot/Plugins/PluginManager.cs @@ -32,14 +32,14 @@ namespace TS3AudioBot.Plugins internal class PluginManager : IDisposable { - private readonly MainBot mainBot; + private readonly Core core; private readonly PluginManagerData pluginManagerData; private readonly Dictionary plugins; private readonly HashSet usedIds; - public PluginManager(MainBot bot, PluginManagerData pmd) + public PluginManager(Core core, PluginManagerData pmd) { - mainBot = bot ?? throw new ArgumentNullException(nameof(bot)); + this.core = core ?? throw new ArgumentNullException(nameof(core)); pluginManagerData = pmd; Util.Init(ref plugins); Util.Init(ref usedIds); @@ -81,7 +81,7 @@ private void CheckLocalPlugins() if (IgnoreFile(file)) continue; - plugin = new Plugin(file, mainBot, GetFreeId()); + plugin = new Plugin(file, core, GetFreeId()); if (plugin.Load() == PluginResponse.Disabled) { diff --git a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs index efcb35ba..e4b5468a 100644 --- a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs +++ b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs @@ -23,14 +23,14 @@ public sealed class ResourceFactoryManager : IDisposable private const string CmdResPrepath = "from "; private const string CmdListPrepath = "list from "; - private readonly MainBot mainBot; + private readonly Core core; private readonly Dictionary allFacories; private readonly List listFactories; private readonly List resFactories; - public ResourceFactoryManager(MainBot bot) + public ResourceFactoryManager(Core core) { - mainBot = bot; + this.core = core; Util.Init(ref allFacories); Util.Init(ref resFactories); Util.Init(ref listFactories); @@ -180,8 +180,8 @@ public void AddFactory(IFactory factory) var factoryInfo = new FactoryData(factory, commands.ToArray()); allFacories.Add(factory.FactoryFor, factoryInfo); - mainBot.CommandManager.RegisterCollection(factoryInfo); - mainBot.RightsManager.RegisterRights(factoryInfo.ExposedRights); + core.CommandManager.RegisterCollection(factoryInfo); + core.RightsManager.RegisterRights(factoryInfo.ExposedRights); } public void RemoveFactory(IFactory factory) @@ -196,8 +196,8 @@ public void RemoveFactory(IFactory factory) if (factory is IPlaylistFactory listFactory) listFactories.Remove(listFactory); - mainBot.CommandManager.UnregisterCollection(factoryInfo); - mainBot.RightsManager.UnregisterRights(factoryInfo.ExposedRights); + core.CommandManager.UnregisterCollection(factoryInfo); + core.RightsManager.UnregisterRights(factoryInfo.ExposedRights); } @@ -269,7 +269,7 @@ public PlayListCommand(IPlaylistFactory factory, string cmdPath) public string PropagiateLoad(ExecutionInformation info, string parameter) { - var result = info.Session.Bot.FactoryManager.LoadPlaylistFrom(parameter, factory); + var result = info.Core.FactoryManager.LoadPlaylistFrom(parameter, factory); if (!result) return result; diff --git a/TS3AudioBot/Rights/RightsManager.cs b/TS3AudioBot/Rights/RightsManager.cs index 69bafe9c..0490db6a 100644 --- a/TS3AudioBot/Rights/RightsManager.cs +++ b/TS3AudioBot/Rights/RightsManager.cs @@ -22,7 +22,7 @@ public class RightsManager { private const int RuleLevelSize = 2; - private readonly MainBot botParent; + private readonly Core core; private bool needsRecalculation; private readonly Cache cachedRights; @@ -39,9 +39,9 @@ public class RightsManager private bool needsAvailableGroups = true; private bool needsAvailableChanGroups = true; - public RightsManager(MainBot bot, RightsManagerData rmd) + public RightsManager(Core core, RightsManagerData rmd) { - botParent = bot; + this.core = core; rightsManagerData = rmd; cachedRights = new Cache(); registeredRights = new HashSet(); @@ -102,7 +102,8 @@ private ExecuteContext GetRightsContext(InvokerData inv) ((needsAvailableGroups && execCtx.AvailableGroups == null) || (needsAvailableChanGroups && !execCtx.ChannelGroupId.HasValue))) { - var result = botParent.QueryConnection.GetClientInfoById(inv.ClientId.Value); + // TODO fixme !!!!!!!! + var result = core.Bots.GetBot(0)?.QueryConnection.GetClientInfoById(inv.ClientId.Value) ?? R.Err("No bot"); if (result.Ok) { if (execCtx.AvailableGroups == null) @@ -114,7 +115,8 @@ private ExecuteContext GetRightsContext(InvokerData inv) if (needsAvailableGroups && inv.DatabaseId.HasValue && execCtx.AvailableGroups == null) { - var result = botParent.QueryConnection.GetClientServerGroups(inv.DatabaseId.Value); + // TODO fixme !!!!!!!! + var result = core.Bots.GetBot(0)?.QueryConnection.GetClientServerGroups(inv.DatabaseId.Value) ?? R.Err(""); if (result.Ok) execCtx.AvailableGroups = result.Value; } diff --git a/TS3AudioBot/Sessions/SessionManager.cs b/TS3AudioBot/Sessions/SessionManager.cs index 7ffe2803..28a10be3 100644 --- a/TS3AudioBot/Sessions/SessionManager.cs +++ b/TS3AudioBot/Sessions/SessionManager.cs @@ -39,7 +39,7 @@ public SessionManager(DbStore database) database.GetMetaData(ApiTokenTable); } - public UserSession CreateSession(MainBot bot, ClientData client) + public UserSession CreateSession(Bot bot, ClientData client) { if (bot == null) throw new ArgumentNullException(nameof(bot)); diff --git a/TS3AudioBot/Sessions/UserSession.cs b/TS3AudioBot/Sessions/UserSession.cs index 7fa17c02..94285bb7 100644 --- a/TS3AudioBot/Sessions/UserSession.cs +++ b/TS3AudioBot/Sessions/UserSession.cs @@ -26,9 +26,9 @@ public sealed class UserSession public Response ResponseProcessor { get; private set; } public object ResponseData { get; private set; } - public MainBot Bot { get; } + public Bot Bot { get; } - public UserSession(MainBot bot, ClientData client) + public UserSession(Bot bot, ClientData client) { this.client = client; Bot = bot; diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index a3da22a6..55e848cc 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -32,7 +32,7 @@ Media\favicon.ico - TS3AudioBot.MainBot + TS3AudioBot.Core true @@ -81,6 +81,8 @@ + + @@ -92,6 +94,7 @@ + @@ -139,7 +142,7 @@ - + diff --git a/TS3AudioBot/TargetScript.cs b/TS3AudioBot/TargetScript.cs index cfd59bc7..a8b23126 100644 --- a/TS3AudioBot/TargetScript.cs +++ b/TS3AudioBot/TargetScript.cs @@ -12,17 +12,19 @@ namespace TS3AudioBot using CommandSystem; using System; - class TargetScript + internal class TargetScript { private const string DefaultVoiceScript = "!whisper off"; private const string DefaultWhisperScript = "!xecute (!whisper subscription) (!unsubscribe temporary) (!subscribe channeltemp (!getuser channel))"; - private readonly MainBot parent; - private CommandManager CommandManager => parent.CommandManager; + private readonly Core core; + private readonly Bot bot; + private CommandManager CommandManager => core.CommandManager; - public TargetScript(MainBot bot) + public TargetScript(Core core, Bot bot) { - parent = bot; + this.core = core; + this.bot = bot; } public void BeforeResourceStarted(object sender, PlayInfoEventArgs e) @@ -47,7 +49,7 @@ private void CallScript(string script, InvokerData invoker) { try { - var info = new ExecutionInformation(parent, invoker, null) { SkipRightsChecks = true }; + var info = new ExecutionInformation(core, bot, invoker, null) { SkipRightsChecks = true }; CommandManager.CommandSystem.Execute(info, script); } catch (CommandException) { } diff --git a/TS3AudioBot/Web/Api/WebApi.cs b/TS3AudioBot/Web/Api/WebApi.cs index a41c8a6f..16d6f6d8 100644 --- a/TS3AudioBot/Web/Api/WebApi.cs +++ b/TS3AudioBot/Web/Api/WebApi.cs @@ -25,7 +25,7 @@ public sealed class WebApi : WebComponent private static readonly Regex DigestMatch = new Regex(@"\s*(\w+)\s*=\s*""([^""]*)""\s*,?", Util.DefaultRegexConfig); private static readonly MD5 Md5Hash = MD5.Create(); - public WebApi(MainBot bot) : base(bot) { } + public WebApi(Core core) : base(core) { } public override void DispatchCall(HttpListenerContext context) { @@ -52,10 +52,10 @@ private void ProcessApiV1Call(Uri uri, HttpListenerResponse response, InvokerDat var ast = CommandParser.ParseCommandRequest(apirequest, '/', '/'); UnescapeAstTree(ast); - var command = MainBot.CommandManager.CommandSystem.AstToCommandResult(ast); + var command = Core.CommandManager.CommandSystem.AstToCommandResult(ast); invoker.IsApi = true; - var execInfo = new ExecutionInformation(MainBot, invoker, null); + var execInfo = new ExecutionInformation(Core, null, invoker, null); // TODO Mainbot !!! try { var res = command.Execute(execInfo, StaticList.Empty(), @@ -153,7 +153,7 @@ private InvokerData Authenticate(HttpListenerContext context) if (identity == null) return null; - var result = MainBot.SessionManager.GetToken(identity.Name); + var result = R.Err(""); // TODO !!! MainBot.SessionManager.GetToken(identity.Name); if (!result.Ok) return null; diff --git a/TS3AudioBot/Web/Interface/WebDisplay.cs b/TS3AudioBot/Web/Interface/WebDisplay.cs index 2eb1fe78..34789b17 100644 --- a/TS3AudioBot/Web/Interface/WebDisplay.cs +++ b/TS3AudioBot/Web/Interface/WebDisplay.cs @@ -33,7 +33,7 @@ public sealed class WebDisplay : WebComponent, IDisposable { ".less", "text/css" }, }; - public WebDisplay(MainBot bot) : base(bot) + public WebDisplay(Core core) : base(core) { var baseDir = new DirectoryInfo(Path.Combine("..", "..", "Web", "Interface")); var dir = new FolderProvider(baseDir); @@ -81,57 +81,4 @@ public void Dispose() { } } - - /* - abstract class WebEvent : WebStaticSite - { - public override string MimeType { set { throw new InvalidOperationException(); } } - private List response; - - public WebEvent(string sitePath) : base(sitePath) - { - response = new List(); - mimeType = "text/event-stream"; - } - - public sealed override PreparedData PrepareSite(UriExt url) => new PreparedData(long.MaxValue, null); - public sealed override void PrepareHeader(HttpListenerContext context, PreparedData callData) - { - base.PrepareHeader(context, callData); - context.Response.KeepAlive = true; - } - public sealed override void GenerateSite(HttpListenerContext context, PreparedData callData) - { - response.Add(context.Response); - InvokeEvent(); - } - - public void InvokeEvent() - { - string eventText = "data: " + GetData() + "\n\n"; - var data = Encoding.GetBytes(eventText); - for (int i = 0; i < response.Count; i++) - { - try - { - response[i].OutputStream.Write(data, 0, data.Length); - response[i].OutputStream.Flush(); - } - catch (Exception ex) - when (ex is HttpListenerException || ex is InvalidOperationException || ex is IOException) - { - response.RemoveAt(i); - i--; - } - } - } - - protected abstract string GetData(); - - public override void FinalizeResponse(HttpListenerContext context) - { - context.Response.OutputStream.Flush(); - } - } - */ } diff --git a/TS3AudioBot/Web/WebComponent.cs b/TS3AudioBot/Web/WebComponent.cs index 9c3daff6..111b207b 100644 --- a/TS3AudioBot/Web/WebComponent.cs +++ b/TS3AudioBot/Web/WebComponent.cs @@ -15,11 +15,11 @@ namespace TS3AudioBot.Web public abstract class WebComponent { protected static readonly Uri Dummy = new Uri("http://dummy/"); - protected MainBot MainBot { get; } + protected Core Core { get; } - protected WebComponent(MainBot mainBot) + protected WebComponent(Core core) { - MainBot = mainBot; + Core = core; } public abstract void DispatchCall(HttpListenerContext context); diff --git a/TS3AudioBot/Web/WebManager.cs b/TS3AudioBot/Web/WebManager.cs index bcd47998..37898c2b 100644 --- a/TS3AudioBot/Web/WebManager.cs +++ b/TS3AudioBot/Web/WebManager.cs @@ -30,7 +30,7 @@ public sealed class WebManager : IDisposable public Api.WebApi Api { get; private set; } public Interface.WebDisplay Display { get; private set; } - public WebManager(MainBot mainBot, WebData webd) + public WebManager(Core core, WebData webd) { webData = webd; webListener = new HttpListener @@ -40,20 +40,20 @@ public WebManager(MainBot mainBot, WebData webd) AuthenticationSchemeSelectorDelegate = AuthenticationSchemeSelector, }; - InitializeSubcomponents(mainBot); + InitializeSubcomponents(core); } - private void InitializeSubcomponents(MainBot mainBot) + private void InitializeSubcomponents(Core core) { startWebServer = false; if (webData.EnableApi) { - Api = new Api.WebApi(mainBot); + Api = new Api.WebApi(core); startWebServer = true; } if (webData.EnableWebinterface) { - Display = new Interface.WebDisplay(mainBot); + Display = new Interface.WebDisplay(core); startWebServer = true; } } diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index f93e95a3..b4e23801 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -194,6 +194,7 @@ private void InvokeEvent(LazyNotification lazyNotification) case NotificationType.ClientNeededPermissions: break; case NotificationType.ClientChannelGroupChanged: break; case NotificationType.ClientServerGroupAdded: break; + case NotificationType.ConnectionInfo: break; case NotificationType.ConnectionInfoRequest: ProcessConnectionInfoRequest(); break; case NotificationType.ChannelSubscribed: break; case NotificationType.ChannelUnsubscribed: break; diff --git a/TS3Client/MessageProcessor.cs b/TS3Client/MessageProcessor.cs index af791dee..acb087ed 100644 --- a/TS3Client/MessageProcessor.cs +++ b/TS3Client/MessageProcessor.cs @@ -20,7 +20,7 @@ internal class MessageProcessor private readonly ConcurrentDictionary requestDict; private readonly ConcurrentQueue requestQueue; private readonly bool synchronQueue; - private readonly object dependantBlockLock = new object(); + private readonly object waitBlockLock = new object(); private readonly List[] dependingBlocks; private string cmdLineBuffer; @@ -62,10 +62,10 @@ public MessageProcessor(bool synchronQueue) { var notification = CommandDeserializer.GenerateNotification(lineDataPart, ntfyType); var lazyNotification = new LazyNotification(notification, ntfyType); - var dependantList = dependingBlocks[(int)ntfyType]; - if (dependantList != null) + lock (waitBlockLock) { - lock (dependantBlockLock) + var dependantList = dependingBlocks[(int)ntfyType]; + if (dependantList != null) { foreach (var item in dependantList) { @@ -109,12 +109,15 @@ public MessageProcessor(bool synchronQueue) } // otherwise it is the result status code to a request - if (requestDict.TryRemove(errorStatus.ReturnCode, out var waitBlock)) + lock (waitBlockLock) { - waitBlock.SetAnswer(errorStatus, cmdLineBuffer); - cmdLineBuffer = null; + if (requestDict.TryRemove(errorStatus.ReturnCode, out var waitBlock)) + { + waitBlock.SetAnswer(errorStatus, cmdLineBuffer); + cmdLineBuffer = null; + } + else { /* ??? */ } } - else { /* ??? */ } } return null; @@ -124,12 +127,14 @@ public void EnqueueRequest(string returnCode, WaitBlock waitBlock) { if (synchronQueue) throw new InvalidOperationException(); - if (!requestDict.TryAdd(returnCode, waitBlock)) - throw new InvalidOperationException("Trying to add alreading existing WaitBlock returnCode"); - if (waitBlock.DependsOn != null) + + lock (waitBlockLock) { - lock (dependantBlockLock) + if (!requestDict.TryAdd(returnCode, waitBlock)) + throw new InvalidOperationException("Trying to add alreading existing WaitBlock returnCode"); + if (waitBlock.DependsOn != null) { + foreach (var dependantType in waitBlock.DependsOn) { var depentantList = dependingBlocks[(int)dependantType]; diff --git a/TS3Client/ts3protocol.md b/TS3Client/ts3protocol.md index 889c8c93..86bb11c3 100644 --- a/TS3Client/ts3protocol.md +++ b/TS3Client/ts3protocol.md @@ -351,7 +351,7 @@ All high level packets specified in this chapter are sent as `Command` Type packets as explained in (see 2.8.3). Additionally the `Newprotocol` flag (see 2.3) must be set on all `Command`, `CommandLow` and `Init1` packets. -The packet header values for (see 3.1) and (see 3.2) are as following: +The packet header/encryption values for (see 3.1) and (see 3.2) are as following: | Parameter | Value | |-----------|------------------------------------------------------------------------------------------------------| @@ -372,7 +372,7 @@ omega ASN.1 encoding here)_ ## 3.1 clientinitiv (Client -> Server) The first packet is sent (Client -> Server) although this is only sent for legacy reasons since newer servers (at least 3.0.13.0?) use the data part -embedded in the last `Init1` packet from the low-level handshake (see 1.5). +embedded in the last `Init1` packet from the low-level handshake (see 2.5). The ip parameter is added but left without value for legacy reasons. From 6d14eea776e969748c9167d9e37fa7ab83f8b5a7 Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 8 Nov 2017 02:14:30 +0100 Subject: [PATCH 02/48] Fixed failed unit test --- TS3ABotUnitTests/BotCommandTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/TS3ABotUnitTests/BotCommandTests.cs b/TS3ABotUnitTests/BotCommandTests.cs index d724aae5..96574ab1 100644 --- a/TS3ABotUnitTests/BotCommandTests.cs +++ b/TS3ABotUnitTests/BotCommandTests.cs @@ -25,16 +25,20 @@ namespace TS3ABotUnitTests public class BotCommandTests { private readonly CommandManager cmdMgr; + private readonly Core core; public BotCommandTests() { cmdMgr = new CommandManager(); cmdMgr.RegisterMain(); + + core = new Core(); + typeof(Core).GetProperty(nameof(Core.CommandManager)).SetValue(core, cmdMgr); } private string CallCommand(string command) { - var info = new ExecutionInformation(null, null, new InvokerData("InvokerUid"), null) { SkipRightsChecks = true }; + var info = new ExecutionInformation(core, null, new InvokerData("InvokerUid"), null) { SkipRightsChecks = true }; return cmdMgr.CommandSystem.ExecuteCommand(info, command); } From 6e7a1d23e900c77b80bcb3a67218c86e02495d9c Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 8 Nov 2017 04:52:09 +0100 Subject: [PATCH 03/48] Added option to better specify versions --- TS3AudioBot/Bot.cs | 8 ++--- TS3AudioBot/Commands.cs | 4 +-- TS3AudioBot/TeamspeakControl.cs | 38 ++++++++++++--------- TS3AudioBot/Ts3Full.cs | 60 ++++++++++++++++++++++++--------- TS3Client/Full/VersionSign.cs | 19 +++++++++-- 5 files changed, 87 insertions(+), 42 deletions(-) diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index be49b538..206e558a 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -51,6 +51,8 @@ public Bot(Core core) public bool InitializeBot() { + Log.Write(Log.Level.Info, "Bot connecting..."); + // Read Config File var conf = core.ConfigManager; var afd = conf.GetDataStruct("AudioFramework", true); @@ -59,7 +61,6 @@ public bool InitializeBot() var pld = conf.GetDataStruct("PlaylistManager", true); mainBotData = conf.GetDataStruct("MainBot", true); - Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); AudioValues.audioFrameworkData = afd; var teamspeakClient = new Ts3Full(tfcd); QueryConnection = teamspeakClient; @@ -71,8 +72,7 @@ public bool InitializeBot() PlayManager = new PlayManager(core, this); TargetManager = teamspeakClient; TargetScript = new TargetScript(core, this); - - Log.Write(Log.Level.Info, "[=========== Registering callbacks ============]"); + PlayerConnection.OnSongEnd += PlayManager.SongStoppedHook; PlayManager.BeforeResourceStarted += TargetScript.BeforeResourceStarted; // In own favor update the own status text to the current song title @@ -97,8 +97,6 @@ public bool InitializeBot() Log.Write(Log.Level.Error, "There is either a problem with your connection configuration, or the query has not all permissions it needs. ({0})", qcex); return false; } - - Log.Write(Log.Level.Info, "[==================== Done ====================]"); return true; } diff --git a/TS3AudioBot/Commands.cs b/TS3AudioBot/Commands.cs index 75c2f403..294e70a3 100644 --- a/TS3AudioBot/Commands.cs +++ b/TS3AudioBot/Commands.cs @@ -85,9 +85,9 @@ public static JsonObject CommandBotCommander(ExecutionInformation info) return new JsonSingleValue("Channel commander is " + (value ? "on" : "off"), value); } [Command("bot commander on", "Enables channel commander.")] - public static void CommandBotCommanderOn(ExecutionInformation info) => info.Bot.QueryConnection.SetChannelCommander(true); + public static void CommandBotCommanderOn(ExecutionInformation info) => info.Bot.QueryConnection.SetChannelCommander(true).UnwrapThrow(); [Command("bot commander off", "Disables channel commander.")] - public static void CommandBotCommanderOff(ExecutionInformation info) => info.Bot.QueryConnection.SetChannelCommander(false); + public static void CommandBotCommanderOff(ExecutionInformation info) => info.Bot.QueryConnection.SetChannelCommander(false).UnwrapThrow(); [Command("bot come", "Moves the bot to you or a specified channel.")] [RequiredParameters(0)] diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index 4e247d6f..59ac40ce 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -27,7 +27,8 @@ private void ExtendedTextMessage(object sender, IEnumerable eventAr if (OnMessageReceived == null) return; foreach (var evData in eventArgs) { - if (evData.InvokerId == me.ClientId) + var me = GetSelf(); + if (me.Ok && evData.InvokerId == me.Value.ClientId) continue; OnMessageReceived?.Invoke(sender, evData); } @@ -64,7 +65,6 @@ private void ExtendedClientLeftView(object sender, IEnumerable e private readonly Cache clientDbNames; protected Ts3BaseFunctions tsBaseClient; - protected ClientData me; protected TeamspeakControl(ClientType connectionType) { @@ -79,7 +79,6 @@ protected TeamspeakControl(ClientType connectionType) tsBaseClient.OnClientLeftView += ExtendedClientLeftView; tsBaseClient.OnClientEnterView += ExtendedClientEnterView; tsBaseClient.OnTextMessageReceived += ExtendedTextMessage; - tsBaseClient.OnConnected += OnConnected; tsBaseClient.OnDisconnected += OnDisconnected; } @@ -91,10 +90,6 @@ public virtual T GetLowLibrary() where T : class } public abstract void Connect(); - protected virtual void OnConnected(object sender, EventArgs e) - { - me = GetSelf(); - } private void OnDisconnected(object sender, DisconnectEventArgs e) { @@ -131,10 +126,11 @@ public R SendServerMessage(string message) public R ChangeDescription(string description) { - if (me == null) + var me = GetSelf(); + if (!me.Ok) return "Internal error (me==null)"; - try { tsBaseClient.ChangeDescription(description, me.ClientId); return R.OkR; } + try { tsBaseClient.ChangeDescription(description, me.Value.ClientId); return R.OkR; } catch (Ts3CommandException ex) { return ex.ErrorStatus.ErrorFormat(); } } @@ -192,7 +188,7 @@ private R ClientBufferRequest(Func pred) return clientData; } - public abstract ClientData GetSelf(); + public abstract R GetSelf(); public R RefreshClientBuffer(bool force) { @@ -235,8 +231,12 @@ internal R SetupRights(string key, MainBotData mainBotData) { try { + var me = GetSelf(); + if (!me.Ok) + return me.Message; + // Check all own server groups - var result = GetClientServerGroups(me.DatabaseId); + var result = GetClientServerGroups(me.Value.DatabaseId); var groups = result.Ok ? result.Value : new ulong[0]; // Add self to master group (via token) @@ -245,7 +245,7 @@ internal R SetupRights(string key, MainBotData mainBotData) // Remember new group (or check if in new group at all) if (result.Ok) - result = GetClientServerGroups(me.DatabaseId); + result = GetClientServerGroups(me.Value.DatabaseId); var groupsNew = result.Ok ? result.Value : new ulong[0]; var groupDiff = groupsNew.Except(groups).ToArray(); @@ -256,7 +256,7 @@ internal R SetupRights(string key, MainBotData mainBotData) mainBotData.BotGroupId = botGroup.ServerGroupId; // Add self to new group - tsBaseClient.ServerGroupAddClient(botGroup.ServerGroupId, me.DatabaseId); + tsBaseClient.ServerGroupAddClient(botGroup.ServerGroupId, me.Value.DatabaseId); } const int max = 75; @@ -330,7 +330,7 @@ internal R SetupRights(string key, MainBotData mainBotData) if (groupDiff.Length > 0) { foreach (var grp in groupDiff) - tsBaseClient.ServerGroupDelClient(grp, me.DatabaseId); + tsBaseClient.ServerGroupDelClient(grp, me.Value.DatabaseId); } return R.OkR; @@ -352,7 +352,10 @@ public R MoveTo(ulong channelId, string password = null) { try { - tsBaseClient.ClientMove(me.ClientId, channelId, password); + var me = GetSelf(); + if (!me.Ok) + return me.Message; + tsBaseClient.ClientMove(me.Value.ClientId, channelId, password); return R.OkR; } catch (Ts3CommandException) { return "Cannot move there."; } @@ -371,7 +374,10 @@ public R SetChannelCommander(bool isCommander) } public R IsChannelCommander() { - var getInfoResult = GetClientInfoById(me.ClientId); + var me = GetSelf(); + if (!me.Ok) + return me.Message; + var getInfoResult = GetClientInfoById(me.Value.ClientId); if (!getInfoResult.Ok) return getInfoResult.Message; return getInfoResult.Value.IsChannelCommander; diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index 4dca2034..aee98135 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -16,6 +16,7 @@ namespace TS3AudioBot using System.Diagnostics; using System.Globalization; using System.Linq; + using System.Reflection; using System.Text.RegularExpressions; using TS3Client; using TS3Client.Full; @@ -24,6 +25,7 @@ namespace TS3AudioBot internal sealed class Ts3Full : TeamspeakControl, IPlayerConnection, ITargetManager { private readonly Ts3FullClient tsFullClient; + private ClientData self; private const Codec SendCodec = Codec.OpusMusic; private readonly TimeSpan sendCheckInterval = TimeSpan.FromMilliseconds(5); @@ -155,12 +157,27 @@ public override void Connect() private void ConnectClient() { - VersionSign verionSign; + VersionSign verionSign = null; if (!string.IsNullOrEmpty(ts3FullClientData.ClientVersion)) { var splitData = ts3FullClientData.ClientVersion.Split('|').Select(x => x.Trim()).ToArray(); - var plattform = (ClientPlattform)Enum.Parse(typeof(ClientPlattform), splitData[1], true); - verionSign = new VersionSign(splitData[0], plattform, splitData[2]); + if (splitData.Length == 3) + { + verionSign = new VersionSign(splitData[0], splitData[1], splitData[2]); + } + else if (splitData.Length == 1) + { + var signType = typeof(VersionSign).GetField("VER_" + ts3FullClientData.ClientVersion, + BindingFlags.IgnoreCase | BindingFlags.Static | BindingFlags.Public); + if (signType != null) + verionSign = (VersionSign)signType.GetValue(null); + } + + if(verionSign == null) + { + Log.Write(Log.Level.Warning, "Invalid version sign, falling back to unknown :P"); + verionSign = VersionSign.VER_WIN_3_UNKNOWN; + } } else if (Util.IsLinux) verionSign = VersionSign.VER_LIN_3_0_19_4; @@ -211,25 +228,36 @@ private void TsFullClient_OnErrorEvent(object sender, CommandError e) } } - public override ClientData GetSelf() + public override R GetSelf() { - var data = tsBaseClient.WhoAmI(); - var cd = new ClientData - { - Uid = identity.ClientUid, - ChannelId = data.ChannelId, - ClientId = tsFullClient.ClientId, - NickName = data.NickName, - ClientType = tsBaseClient.ClientType - }; + if (self != null) + return self; + try { - var response = tsBaseClient.Send("clientgetdbidfromuid", new TS3Client.Commands.CommandParameter("cluid", identity.ClientUid)).FirstOrDefault(); + var data = tsBaseClient.WhoAmI(); + var cd = new ClientData + { + Uid = identity.ClientUid, + ChannelId = data.ChannelId, + ClientId = tsFullClient.ClientId, + NickName = data.NickName, + ClientType = tsBaseClient.ClientType + }; + + var response = tsBaseClient + .Send("clientgetdbidfromuid", new TS3Client.Commands.CommandParameter("cluid", identity.ClientUid)) + .FirstOrDefault(); if (response != null && ulong.TryParse(response["cldbid"], out var dbId)) cd.DatabaseId = dbId; + + self = cd; + return cd; + } + catch (Ts3CommandException) + { + return "Could not get self"; } - catch (Ts3CommandException) { } - return cd; } private void AudioSend() diff --git a/TS3Client/Full/VersionSign.cs b/TS3Client/Full/VersionSign.cs index 3e1269b7..f20f1de9 100644 --- a/TS3Client/Full/VersionSign.cs +++ b/TS3Client/Full/VersionSign.cs @@ -9,13 +9,15 @@ namespace TS3Client.Full { + using System; + /// /// Describes a triple of version, plattform and a crytographical signature (usually distributed by "TeamSpeak Systems"). /// Each triple has to match and is not interchangeable with other triple parts. /// public class VersionSign { - private static readonly string[] Plattforms = { "Windows", "Linux", "OS X", "Android" }; + private static readonly string[] Plattforms = { null, "Windows", "Linux", "OS X", "Android" }; public string Sign { get; } public string Name { get; } @@ -24,12 +26,22 @@ public class VersionSign public VersionSign(string name, ClientPlattform plattform, string sign) { + if (plattform == ClientPlattform.Other) + throw new ArgumentException(nameof(plattform)); Name = name; Sign = sign; Plattform = plattform; PlattformName = Plattforms[(int)plattform]; } + public VersionSign(string name, string plattform, string sign) + { + Name = name; + Sign = sign; + Plattform = ClientPlattform.Other; + PlattformName = plattform; + } + // Many ids implemented from here: https://r4p3.net/threads/client-builds.499/ public static readonly VersionSign VER_WIN_3_0_11 @@ -53,14 +65,15 @@ public static readonly VersionSign VER_WIN_3_UNKNOWN public static readonly VersionSign VER_AND_3_UNKNOWN = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Android, "AWb948BY32Z7bpIyoAlQguSmxOGcmjESPceQe1DpW5IZ4+AW1KfTk2VUIYNfUPsxReDJMCtlhVKslzhR2lf0AA=="); public static readonly VersionSign VER_WIN_3_1_6 - = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Windows, "o+l92HKfiUF+THx2rBsuNjj/S1QpxG1fd5o3Q7qtWxkviR3LI3JeWyc26eTmoQoMTgI3jjHV7dCwHsK1BVu6Aw=="); + = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Windows, "73fB82Jt1lmIRHKBFaE8h1JKPGFbnt6/yrXOHwTS93Oo7Adx1usY5TzNg+8BKy9nmmA2FEBnRmz5cRfXDghnBA=="); public static readonly VersionSign VER_LIN_3_1_6 = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Linux, "o+l92HKfiUF+THx2rBsuNjj/S1QpxG1fd5o3Q7qtWxkviR3LI3JeWyc26eTmoQoMTgI3jjHV7dCwHsK1BVu6Aw=="); } public enum ClientPlattform { - Windows = 0, + Other = 0, + Windows, Linux, Osx, Android, From e513cc9115724bcabd209d117bcf87ebacd7ae33 Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 8 Nov 2017 16:26:21 +0100 Subject: [PATCH 04/48] Fixing some race conditions --- README.md | 17 +++++++++++------ TS3AudioBot/Core.cs | 2 +- TS3Client/MessageProcessor.cs | 28 ++++++++++++++-------------- TS3Client/WaitBlock.cs | 18 ++++++++++++++---- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 06e33f3a..c4046399 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,17 @@ Done: * (un)subscribe to the Bob to hear music in any channel * (un)subscribe the Bob to certain channels * Playlist management for all users -* *broken* | Basic plugin support - -In progress: +* Advanced permission configuration +* Extensive plugin support * Web API -In planning: -* Create multiple client instances automatically for different channels -* (Improved) Rights system +In progress: * Own web-interface +* (Improved) Rights system +* Multi-instance + +In planning: +*See issues* ## Bot Commands All in all, the bot is fully operable only via chat (and actually only via chat). @@ -109,3 +111,6 @@ 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. - If you create plugins you do not have to make them public like in GPL. (Although we would be happier 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. + +# Badges +[![forthebadge](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/built-by-developers.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/contains-cat-gifs.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/made-with-c-sharp.svg)](http://forthebadge.com) \ No newline at end of file diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index a26abaac..32d095b9 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -20,7 +20,7 @@ namespace TS3AudioBot using System.Threading; using Web; - public class Core : IDisposable + public sealed class Core : IDisposable { private bool isRunning; private bool consoleOutput; diff --git a/TS3Client/MessageProcessor.cs b/TS3Client/MessageProcessor.cs index acb087ed..2fe2bc7e 100644 --- a/TS3Client/MessageProcessor.cs +++ b/TS3Client/MessageProcessor.cs @@ -15,7 +15,7 @@ namespace TS3Client using System.Collections.Concurrent; using System.Collections.Generic; - internal class MessageProcessor + internal sealed class MessageProcessor { private readonly ConcurrentDictionary requestDict; private readonly ConcurrentQueue requestQueue; @@ -69,8 +69,7 @@ public MessageProcessor(bool synchronQueue) { foreach (var item in dependantList) { - if (!item.Closed) - item.SetNotification(lazyNotification); + item.SetNotification(lazyNotification); if (item.DependsOn != null) { foreach (var otherDepType in item.DependsOn) @@ -163,18 +162,19 @@ public void DropQueue() } else { - var arr = requestDict.ToArray(); - requestDict.Clear(); - foreach (var block in dependingBlocks) - block?.Clear(); - foreach (var val in arr) - val.Value.SetAnswer(Util.TimeOutCommandError); + lock (waitBlockLock) + { + foreach (var wb in requestDict.Values) + wb.SetAnswer(Util.TimeOutCommandError); + requestDict.Clear(); + + foreach (var block in dependingBlocks) + { + block.ForEach(wb => wb.SetAnswer(Util.TimeOutCommandError)); + block?.Clear(); + } + } } } } - - /*internal class AsyncMessageProcessor : MessageProcessor - { - - }*/ } diff --git a/TS3Client/WaitBlock.cs b/TS3Client/WaitBlock.cs index 9aae268d..3ff10361 100644 --- a/TS3Client/WaitBlock.cs +++ b/TS3Client/WaitBlock.cs @@ -15,7 +15,7 @@ namespace TS3Client using System.Collections.Generic; using System.Threading; - internal class WaitBlock : IDisposable + internal sealed class WaitBlock : IDisposable { private readonly ManualResetEvent answerWaiter; private readonly ManualResetEvent notificationWaiter; @@ -23,12 +23,12 @@ internal class WaitBlock : IDisposable private string commandLine; public NotificationType[] DependsOn { get; } private LazyNotification notification; - public bool Closed { get; private set; } + public bool isDisposed; private static readonly TimeSpan CommandTimeout = TimeSpan.FromSeconds(15); public WaitBlock(NotificationType[] dependsOn = null) { - Closed = false; + isDisposed = false; answerWaiter = new ManualResetEvent(false); DependsOn = dependsOn; if (DependsOn != null) @@ -41,6 +41,8 @@ public WaitBlock(NotificationType[] dependsOn = null) public IEnumerable WaitForMessage() where T : IResponse, new() { + if (isDisposed) + throw new ObjectDisposedException(nameof(WaitBlock)); if (!answerWaiter.WaitOne(CommandTimeout)) throw new Ts3CommandException(Util.TimeOutCommandError); if (commandError.Id != Ts3ErrorCode.ok) @@ -51,6 +53,8 @@ public WaitBlock(NotificationType[] dependsOn = null) public LazyNotification WaitForNotification() { + if (isDisposed) + throw new ObjectDisposedException(nameof(WaitBlock)); if (DependsOn == null) throw new InvalidOperationException("This waitblock has no dependent Notification"); if (!answerWaiter.WaitOne(CommandTimeout)) @@ -65,6 +69,8 @@ public LazyNotification WaitForNotification() public void SetAnswer(CommandError commandError, string commandLine = null) { + if (isDisposed) + return; this.commandError = commandError ?? throw new ArgumentNullException(nameof(commandError)); this.commandLine = commandLine; answerWaiter.Set(); @@ -72,6 +78,8 @@ public void SetAnswer(CommandError commandError, string commandLine = null) public void SetNotification(LazyNotification notification) { + if (isDisposed) + return; if (DependsOn != null && Array.IndexOf(DependsOn, notification.NotifyType) < 0) throw new ArgumentException("The notification does not match this waitblock"); this.notification = notification; @@ -80,7 +88,9 @@ public void SetNotification(LazyNotification notification) public void Dispose() { - Closed = true; + if (isDisposed) + return; + isDisposed = true; answerWaiter.Set(); answerWaiter.Dispose(); From 0324e1837d59553ee742385ce3d4441a16c9307b Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 8 Nov 2017 19:36:43 +0100 Subject: [PATCH 05/48] Small bugfix on disconnect --- TS3Client/MessageProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TS3Client/MessageProcessor.cs b/TS3Client/MessageProcessor.cs index 2fe2bc7e..35e8ac8f 100644 --- a/TS3Client/MessageProcessor.cs +++ b/TS3Client/MessageProcessor.cs @@ -157,7 +157,7 @@ public void DropQueue() { if (synchronQueue) { - while (!requestQueue.IsEmpty && requestQueue.TryDequeue(out WaitBlock waitBlock)) + while (!requestQueue.IsEmpty && requestQueue.TryDequeue(out var waitBlock)) waitBlock.SetAnswer(Util.TimeOutCommandError); } else @@ -170,7 +170,7 @@ public void DropQueue() foreach (var block in dependingBlocks) { - block.ForEach(wb => wb.SetAnswer(Util.TimeOutCommandError)); + block?.ForEach(wb => wb.SetAnswer(Util.TimeOutCommandError)); block?.Clear(); } } From 5c805f78fdda5b2c905edc50f75983a694e506ac Mon Sep 17 00:00:00 2001 From: Bluscream Date: Fri, 10 Nov 2017 19:44:25 +0100 Subject: [PATCH 06/48] added 3.1.7 alpha --- TS3Client/Full/VersionSign.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/TS3Client/Full/VersionSign.cs b/TS3Client/Full/VersionSign.cs index f20f1de9..f22bd3e3 100644 --- a/TS3Client/Full/VersionSign.cs +++ b/TS3Client/Full/VersionSign.cs @@ -17,7 +17,7 @@ namespace TS3Client.Full /// public class VersionSign { - private static readonly string[] Plattforms = { null, "Windows", "Linux", "OS X", "Android" }; + private static readonly string[] Plattforms = { null, "Windows", "Linux", "OS X", "Android", "iOS" }; public string Sign { get; } public string Name { get; } @@ -60,14 +60,20 @@ public static readonly VersionSign VER_AND_3_0_23 = new VersionSign("3.0.23 [Build: 1463662487]", ClientPlattform.Android, "RN+cwFI+jSHJEhggucIuUyEteWNVFy4iw0QDp3qn2UzfopypFVE9BPZqJjBUGeoCN7Q/SfYL4RNIRzJEQaZUCA=="); public static readonly VersionSign VER_WIN_3_1 = new VersionSign("3.1 [Build: 1471417187]", ClientPlattform.Windows, "Vr9F7kbVorcrkV5b/Iw+feH9qmDGvfsW8tpa737zhc1fDpK5uaEo6M5l2DzgaGqqOr3GKl5A7PF9Sj6eTM26Aw=="); - public static readonly VersionSign VER_WIN_3_UNKNOWN - = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Windows, "DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="); - public static readonly VersionSign VER_AND_3_UNKNOWN - = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Android, "AWb948BY32Z7bpIyoAlQguSmxOGcmjESPceQe1DpW5IZ4+AW1KfTk2VUIYNfUPsxReDJMCtlhVKslzhR2lf0AA=="); public static readonly VersionSign VER_WIN_3_1_6 = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Windows, "73fB82Jt1lmIRHKBFaE8h1JKPGFbnt6/yrXOHwTS93Oo7Adx1usY5TzNg+8BKy9nmmA2FEBnRmz5cRfXDghnBA=="); public static readonly VersionSign VER_LIN_3_1_6 = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Linux, "o+l92HKfiUF+THx2rBsuNjj/S1QpxG1fd5o3Q7qtWxkviR3LI3JeWyc26eTmoQoMTgI3jjHV7dCwHsK1BVu6Aw=="); + public static readonly VersionSign VER_WIN_3_1_7_ALPHA + = new VersionSign("3.1.7 [Build: 1507896705]", ClientPlattform.Windows, "Iks42KIMcmFv5vzPLhziqahcPD2AHygkepr8xHNCbqx+li5n7Htbq5LE9e1YYhRhLoS4e2HqOpKkt+/+LC8EDA=="); + public static readonly VersionSign VER_OSX_3_1_7_ALPHA + = new VersionSign("3.1.7 [Build: 1507896705]", ClientPlattform.Osx, "iM0IyUpaH9ak0gTtrHlRT0VGZa4rC51iZwSFwifK6iFqciSba/WkIQDWk9GUJN0OCCfatoc/fmlq8TPBnE5XCA=="); + public static readonly VersionSign VER_WIN_3_UNKNOWN + = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Windows, "DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="); + public static readonly VersionSign VER_AND_3_UNKNOWN + = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Android, "AWb948BY32Z7bpIyoAlQguSmxOGcmjESPceQe1DpW5IZ4+AW1KfTk2VUIYNfUPsxReDJMCtlhVKslzhR2lf0AA=="); + public static readonly VersionSign VER_IOS_3_UNKNOWN + = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Ios, "XrAf+Buq6Eb0ehEW/niFp06YX+nGGOS0Ke4MoUBzn+cX9q6G5C0A/d5XtgcNMe8r9jJgV/adIYVpsGS3pVlSAA=="); } public enum ClientPlattform @@ -77,5 +83,6 @@ public enum ClientPlattform Linux, Osx, Android, + Ios, } } From 48248478aaa82192f7af931dfdfabc756183fe2b Mon Sep 17 00:00:00 2001 From: Splamy Date: Sun, 19 Nov 2017 21:56:09 +0100 Subject: [PATCH 07/48] Added version info + some small logging additions --- TS3AudioBot/Audio/Opus/NativeMethods.cs | 2 + TS3AudioBot/BotManager.cs | 5 ++- TS3AudioBot/Core.cs | 15 +++++-- TS3AudioBot/Helper/Util.cs | 54 ++++++++++++++++++++++++- 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/TS3AudioBot/Audio/Opus/NativeMethods.cs b/TS3AudioBot/Audio/Opus/NativeMethods.cs index e1994ada..1e46b40d 100644 --- a/TS3AudioBot/Audio/Opus/NativeMethods.cs +++ b/TS3AudioBot/Audio/Opus/NativeMethods.cs @@ -38,6 +38,8 @@ static NativeMethods() Log.Write(Log.Level.Info, "Using opus version: {0} ({1})", verString, NativeWinDllLoader.ArchFolder); } + public static void DummyLoad() { } + // ReSharper disable EnumUnderlyingTypeIsInt, InconsistentNaming #pragma warning disable IDE1006 [DllImport("libopus", CallingConvention = CallingConvention.Cdecl)] diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index 7e5c3281..8a8eff32 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -47,6 +47,7 @@ public void WatchBots() public bool CreateBot(/*Ts3FullClientData bot*/) { + string error = string.Empty; var bot = new Bot(core); try { @@ -59,10 +60,10 @@ public bool CreateBot(/*Ts3FullClientData bot*/) return true; } } - catch (Exception) { } + catch (Exception ex) { error = ex.ToString(); } bot.Dispose(); - Log.Write(Log.Level.Warning, "Could not create new Bot"); + Log.Write(Log.Level.Warning, "Could not create new Bot ({0})", error); return false; } diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index 32d095b9..c5b3bf89 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -54,8 +54,13 @@ internal static void Main(string[] args) if (!core.ReadParameter(args)) return; - if (!core.Initialize()) + var initResult = core.Initialize(); + if (!initResult) + { + Log.Write(Log.Level.Error, "Core initialization failed: {0}", initResult.Message); core.Dispose(); + return; + } core.Run(); } @@ -141,7 +146,7 @@ private bool ReadParameter(string[] args) return true; } - private bool Initialize() + private R Initialize() { ConfigManager = ConfigFile.OpenOrCreate(configFilePath) ?? ConfigFile.CreateDummy(); var pmd = ConfigManager.GetDataStruct("PluginManager", true); @@ -201,9 +206,11 @@ void ColorLog(string msg, Log.Level lvl) Log.Write(Log.Level.Info, "[============ TS3AudioBot started =============]"); Log.Write(Log.Level.Info, "[=== Date/Time: {0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString()); Log.Write(Log.Level.Info, "[=== Version: {0}", Util.GetAssemblyData().ToString()); + Log.Write(Log.Level.Info, "[=== Plattform: {0}", Util.GetPlattformData()); Log.Write(Log.Level.Info, "[==============================================]"); Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); + Audio.Opus.NativeMethods.DummyLoad(); CommandManager = new CommandManager(); CommandManager.RegisterMain(); Database = new DbStore(hmd); @@ -215,7 +222,7 @@ void ColorLog(string msg, Log.Level lvl) RightsManager.RegisterRights(CommandManager.AllRights); RightsManager.RegisterRights(Commands.RightHighVolume, Commands.RightDeleteAllPlaylists); if (!RightsManager.ReadFile()) - return false; + return "Could not read Permission file."; Log.Write(Log.Level.Info, "[=========== Initializing Factories ===========]"); YoutubeDlHelper.DataObj = yfd; @@ -230,7 +237,7 @@ void ColorLog(string msg, Log.Level lvl) WebManager.StartServerAsync(); - return true; + return R.OkR; } private void Run() diff --git a/TS3AudioBot/Helper/Util.cs b/TS3AudioBot/Helper/Util.cs index fb76c6e9..ee68ebbb 100644 --- a/TS3AudioBot/Helper/Util.cs +++ b/TS3AudioBot/Helper/Util.cs @@ -11,6 +11,7 @@ namespace TS3AudioBot.Helper { using CommandSystem; using System; + using System.Diagnostics; using System.IO; using System.Reflection; using System.Security.Principal; @@ -200,11 +201,60 @@ public class BuildData public string ToLongString() => $"\nVersion: {Version}\nBranch: {Branch}\nCommitHash: {CommitSha}"; public override string ToString() => $"{Version}/{Branch}/{(CommitSha.Length > 8 ? CommitSha.Substring(0, 8) : CommitSha)}"; } + + static readonly Regex PlattformRegex = new Regex(@"(\w+)=(.*)", DefaultRegexConfig | RegexOptions.Multiline); + + public static string GetPlattformData() + { + string plattform = null; + string version = ""; + string bitness = Environment.Is64BitProcess ? "64bit" : "32bit"; + + if (IsLinux) + { + try + { + var p = new Process() + { + StartInfo = new ProcessStartInfo() + { + FileName = "bash", + Arguments = "-c \"cat / etc/*[_-]release\"", + RedirectStandardOutput = true, + } + }; + p.Start(); + p.WaitForExit(); + + var info = p.StandardOutput.ReadToEnd(); + var match = PlattformRegex.Match(info); + + while (match.Success) + { + switch (match.Groups[1].Value.ToUpper()) + { + case "DISTRIB_ID": plattform = match.Groups[2].Value; break; + case "DISTRIB_RELEASE": version = match.Groups[2].Value; break; + } + } + } + catch (Exception) { } + if (plattform == null) + plattform = "Linux"; + } + else + { + plattform = "Windows"; + version = Environment.OSVersion.Version.ToString(); + } + + return $"{plattform} {version} ({bitness})"; + } } public class MissingEnumCaseException : Exception { - public MissingEnumCaseException(string enumTypeName, string valueName) : base($"The the switch does not handle the value \"{valueName}\" from \"{enumTypeName}\".") { } - public MissingEnumCaseException(string message, Exception inner) : base(message, inner) { } + public MissingEnumCaseException(string enumTypeName, string valueName) : base($"The the switch does not handle the value \"{valueName}\" from \"{enumTypeName}\".") { } + public MissingEnumCaseException(string message, Exception inner) : base(message, inner) { } } } From 0af7f6eea16f3b19e9bc0178f716a6545f6be140 Mon Sep 17 00:00:00 2001 From: Splamy Date: Mon, 20 Nov 2017 03:34:18 +0100 Subject: [PATCH 08/48] Fixed plattform log for linux --- TS3AudioBot/Helper/Util.cs | 20 +++++++++++++------- TS3AudioBot/Ts3Full.cs | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/TS3AudioBot/Helper/Util.cs b/TS3AudioBot/Helper/Util.cs index ee68ebbb..f3561749 100644 --- a/TS3AudioBot/Helper/Util.cs +++ b/TS3AudioBot/Helper/Util.cs @@ -219,18 +219,24 @@ public static string GetPlattformData() StartInfo = new ProcessStartInfo() { FileName = "bash", - Arguments = "-c \"cat / etc/*[_-]release\"", + Arguments = "-c \"cat /etc/*[_-]release\"", + CreateNoWindow = true, + UseShellExecute = false, RedirectStandardOutput = true, } }; p.Start(); - p.WaitForExit(); - - var info = p.StandardOutput.ReadToEnd(); - var match = PlattformRegex.Match(info); - - while (match.Success) + p.WaitForExit(100); + + while (p.StandardOutput.Peek() > -1) { + var infoLine = p.StandardOutput.ReadLine(); + if (string.IsNullOrEmpty(infoLine)) + continue; + var match = PlattformRegex.Match(infoLine); + if (!match.Success) + continue; + switch (match.Groups[1].Value.ToUpper()) { case "DISTRIB_ID": plattform = match.Groups[2].Value; break; diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index aee98135..d872310b 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -223,7 +223,7 @@ private void TsFullClient_OnErrorEvent(object sender, CommandError e) break; default: - Log.Write(Log.Level.Debug, e.ErrorFormat()); + Log.Write(Log.Level.Debug, "Got ts3 error event: {0}", e.ErrorFormat()); break; } } From ab7113ec171bfc9d4cd3cf0aafbf312e573a6c55 Mon Sep 17 00:00:00 2001 From: VerHext Date: Tue, 21 Nov 2017 19:51:40 +0100 Subject: [PATCH 09/48] Add badges to the bot --- TS3AudioBot/Bot.cs | 12 ++++++++++++ TS3AudioBot/Commands.cs | 3 +++ TS3AudioBot/TeamspeakControl.cs | 6 ++++++ TS3AudioBot/Ts3Full.cs | 2 ++ TS3Client/Ts3BaseClient.cs | 4 ++++ 5 files changed, 27 insertions(+) diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index 206e558a..f8c6f583 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -43,6 +43,7 @@ public sealed class Bot : IDisposable public IPlayerConnection PlayerConnection { get; private set; } public bool QuizMode { get; set; } + public string BadgesString { get; set; } public Bot(Core core) { @@ -89,6 +90,8 @@ public bool InitializeBot() // Register callback to remove open private sessions, when user disconnects QueryConnection.OnClientDisconnect += OnClientDisconnect; QueryConnection.OnBotDisconnect += (s, e) => Dispose(); + QueryConnection.OnClientConnect += OnClientConnect; + BadgesString = tfcd.ClientBadges; // Connect the query after everyting is set up try { QueryConnection.Connect(); } @@ -100,6 +103,15 @@ public bool InitializeBot() return true; } + private void OnClientConnect(object sender, ClientEnterView e) + { + var me = QueryConnection.GetSelf(); + if (e.ClientId == me.Value.ClientId) + { + QueryConnection.ChangeBadges(BadgesString); + } + } + private void TextCallback(object sender, TextMessage textMessage) { Log.Write(Log.Level.Debug, "MB Got message from {0}: {1}", textMessage.InvokerName, textMessage.Message); diff --git a/TS3AudioBot/Commands.cs b/TS3AudioBot/Commands.cs index 294e70a3..974d833c 100644 --- a/TS3AudioBot/Commands.cs +++ b/TS3AudioBot/Commands.cs @@ -107,6 +107,9 @@ public static void CommandBotMove(ExecutionInformation info, ulong? channel, str [Command("bot name", "Gives the bot a new name.")] public static void CommandBotName(ExecutionInformation info, string name) => info.Bot.QueryConnection.ChangeName(name).UnwrapThrow(); + [Command("bot badges", "Set your bot a badge. The badges string starts with 'overwolf=0:badges='")] + public static void CommandBotBadges(ExecutionInformation info, string badgesString) => info.Bot.QueryConnection.ChangeBadges(badgesString).UnwrapThrow(); + [Command("bot setup", "Sets all teamspeak rights for the bot to be fully functional.")] [RequiredParameters(0)] public static void CommandBotSetup(ExecutionInformation info, string adminToken) diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index 59ac40ce..fe84bae6 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -134,6 +134,12 @@ public R ChangeDescription(string description) catch (Ts3CommandException ex) { return ex.ErrorStatus.ErrorFormat(); } } + public R ChangeBadges(string badgesString) + { + try { tsBaseClient.ChangeBadges(badgesString); return R.OkR; } + catch (Ts3CommandException ex) { return ex.ErrorStatus.ErrorFormat(); } + } + public R ChangeName(string name) { try diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index d872310b..4a351dce 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -651,5 +651,7 @@ public class Ts3FullClientData : ConfigData public string DefaultChannel { get; set; } [Info("The password for the default channel. Leave empty for none. Not required with permission b_channel_join_ignore_password", "")] public string DefaultChannelPassword { get; set; } + [Info("The client badges. You can set a comma seperate string with max three GUID's. Here is a list: http://yat.qa/ressourcen/abzeichen-badges/", "overwolf=0:badges=")] + public string ClientBadges { get; set; } } } diff --git a/TS3Client/Ts3BaseClient.cs b/TS3Client/Ts3BaseClient.cs index 1c06f403..223370ae 100644 --- a/TS3Client/Ts3BaseClient.cs +++ b/TS3Client/Ts3BaseClient.cs @@ -106,6 +106,10 @@ public void ChangeName(string newName) => Send("clientupdate", new CommandParameter("client_nickname", newName)); + public void ChangeBadges(string newBadges) + => Send("clientupdate", + new CommandParameter("client_badges", newBadges)); + public void ChangeDescription(string newDescription, ClientIdT clientId) => Send("clientedit", new CommandParameter("clid", clientId), From f3a14e061de24dbfcf3abf32ac3659b6baa32cb0 Mon Sep 17 00:00:00 2001 From: Splamy Date: Sun, 26 Nov 2017 12:41:06 +0100 Subject: [PATCH 10/48] Added dependency injection base --- TS3ABotUnitTests/UnitTests.cs | 36 ++-- TS3AudioBot/Algorithm/DependencyGraph.cs | 24 ++- TS3AudioBot/Audio/AudioEncoder.cs | 4 +- TS3AudioBot/BotManager.cs | 13 +- TS3AudioBot/CommandSystem/CommandManager.cs | 15 +- TS3AudioBot/Core.cs | 84 ++++---- TS3AudioBot/DbStore.cs | 14 +- TS3AudioBot/Dependency/DependencyInjector.cs | 181 ++++++++++++++++++ TS3AudioBot/Dependency/ITabModule.cs | 21 ++ TS3AudioBot/Helper/ConfigFile.cs | 6 +- TS3AudioBot/Helper/TickPool.cs | 4 +- TS3AudioBot/Helper/Util.cs | 2 +- TS3AudioBot/History/HistoryManager.cs | 2 +- TS3AudioBot/PlaylistManager.cs | 2 +- TS3AudioBot/Plugins/PluginManager.cs | 25 ++- .../ResourceFactoryManager.cs | 35 ++-- TS3AudioBot/Rights/RightsManager.cs | 29 ++- TS3AudioBot/Sessions/ApiToken.cs | 2 +- TS3AudioBot/Sessions/SessionManager.cs | 4 +- TS3AudioBot/Sessions/UserSession.cs | 2 +- TS3AudioBot/TS3AudioBot.csproj | 4 + TS3AudioBot/TeamspeakControl.cs | 4 +- TS3AudioBot/Ts3Full.cs | 4 +- TS3AudioBot/Web/Interface/SiteMapper.cs | 6 +- TS3AudioBot/Web/WebManager.cs | 39 ++-- TS3Client/ColorDbg.cs | 8 + TS3Client/FileTransferManager.cs | 2 +- TS3Client/Full/Ts3Crypt.cs | 2 +- TS3Client/Full/Ts3FullClient.cs | 5 +- TS3Client/Ts3BaseClient.cs | 6 +- TS3Client/Util.cs | 2 +- TS3Client/ts3protocol.md | 8 +- 32 files changed, 434 insertions(+), 161 deletions(-) create mode 100644 TS3AudioBot/Dependency/DependencyInjector.cs create mode 100644 TS3AudioBot/Dependency/ITabModule.cs diff --git a/TS3ABotUnitTests/UnitTests.cs b/TS3ABotUnitTests/UnitTests.cs index b8c452e1..3e7499fa 100644 --- a/TS3ABotUnitTests/UnitTests.cs +++ b/TS3ABotUnitTests/UnitTests.cs @@ -58,9 +58,21 @@ public void HistoryFileIntergrityTest() var data2 = new HistorySaveData(ar2, inv2.DatabaseId); var data3 = new HistorySaveData(ar3, 103); - var hmf = new HistoryManagerData {HistoryFile = testFile, FillDeletedIds = false}; - var db = new DbStore(hmf); - var hf = new HistoryManager(hmf, db); + var memcfg = ConfigFile.CreateDummy(); + var hmf = memcfg.GetDataStruct("HistoryManager", true); + hmf.HistoryFile = testFile; + hmf.FillDeletedIds = false; + + DbStore db = null; + HistoryManager hf = null; + Action createDbStore = () => + { + db = new DbStore() { Config = memcfg }; + db.Initialize(); + hf = new HistoryManager(hmf, db); + }; + + createDbStore(); hf.LogAudioResource(data1); @@ -71,8 +83,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); - db = new DbStore(hmf); - hf = new HistoryManager(hmf, db); + createDbStore(); lastXEntries = hf.GetLastXEntrys(1); Assert.True(lastXEntries.Any()); lastEntry = lastXEntries.First(); @@ -89,8 +100,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // store and order check - db = new DbStore(hmf); - hf = new HistoryManager(hmf, db); + createDbStore(); var lastXEntriesArray = hf.GetLastXEntrys(2).ToArray(); Assert.AreEqual(2, lastXEntriesArray.Length); Assert.AreEqual(ar2, lastXEntriesArray[0].AudioResource); @@ -104,8 +114,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // check entry renaming - db = new DbStore(hmf); - hf = new HistoryManager(hmf, db); + createDbStore(); lastXEntriesArray = hf.GetLastXEntrys(2).ToArray(); Assert.AreEqual(2, lastXEntriesArray.Length); Assert.AreEqual(ar1, lastXEntriesArray[0].AudioResource); @@ -125,8 +134,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // recheck order - db = new DbStore(hmf); - hf = new HistoryManager(hmf, db); + createDbStore(); lastXEntriesArray = hf.GetLastXEntrys(2).ToArray(); Assert.AreEqual(2, lastXEntriesArray.Length); Assert.AreEqual(ar2, lastXEntriesArray[0].AudioResource); @@ -134,8 +142,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // delete entry 1 - db = new DbStore(hmf); - hf = new HistoryManager(hmf, db); + createDbStore(); hf.RemoveEntry(hf.FindEntryByResource(ar1)); lastXEntriesArray = hf.GetLastXEntrys(3).ToArray(); @@ -149,8 +156,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // delete entry 2 - db = new DbStore(hmf); - hf = new HistoryManager(hmf, db); + createDbStore(); // .. check integrity from previous store lastXEntriesArray = hf.GetLastXEntrys(3).ToArray(); Assert.AreEqual(2, lastXEntriesArray.Length); diff --git a/TS3AudioBot/Algorithm/DependencyGraph.cs b/TS3AudioBot/Algorithm/DependencyGraph.cs index 7fae530a..9202658c 100644 --- a/TS3AudioBot/Algorithm/DependencyGraph.cs +++ b/TS3AudioBot/Algorithm/DependencyGraph.cs @@ -5,35 +5,41 @@ namespace TS3AudioBot.Algorithm { - public class DependencyGraph where T : IEquatable + public class DependencyGraph where T : class { - List DependencyObjects { get; } + private Dictionary DependencyObjects { get; } public List DependencyList { get; } public DependencyGraph() { - DependencyObjects = new List(); + DependencyObjects = new Dictionary(); DependencyList = new List(); } + public bool Contains(T obj) + { + return DependencyObjects.ContainsKey(obj); + } + public void Map(T obj, T[] depentants) { var node = new Node(obj, depentants); - DependencyObjects.Add(node); + DependencyObjects.Add(obj, node); } public void BuildList() { DependencyList.Clear(); - for (int i = 0; i < DependencyObjects.Count; i++) - DependencyObjects[i].Id = i; - foreach (var node in DependencyObjects) - node.DIdList = node.DList.Select(x => DependencyObjects.First(y => y.DObject.Equals(x)).Id).ToArray(); + int id = 0; + foreach (var node in DependencyObjects.Values) + node.Id = id++; + foreach (var node in DependencyObjects.Values) + node.DIdList = node.DList.Select(x => DependencyObjects[x].Id).ToArray(); var reachable = new BitArray(DependencyObjects.Count, false); - var remainingObjects = DependencyObjects.ToList(); + var remainingObjects = DependencyObjects.Values.ToList(); while (remainingObjects.Count > 0) { bool hasChanged = false; diff --git a/TS3AudioBot/Audio/AudioEncoder.cs b/TS3AudioBot/Audio/AudioEncoder.cs index 116c90e1..58292b59 100644 --- a/TS3AudioBot/Audio/AudioEncoder.cs +++ b/TS3AudioBot/Audio/AudioEncoder.cs @@ -42,8 +42,8 @@ internal class AudioEncoder : IDisposable public AudioEncoder(Codec codec) { - Util.Init(ref opusQueue); - Util.Init(ref freeArrays); + Util.Init(out opusQueue); + Util.Init(out freeArrays); Codec = codec; switch (codec) diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index 8a8eff32..2066bfac 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -14,19 +14,20 @@ namespace TS3AudioBot using System.Collections.Generic; using System.Threading; - public class BotManager : IDisposable + public class BotManager : Dependency.ICoreModule, IDisposable { private bool isRunning; - private readonly Core core; + public Core Core { get; set; } private readonly List activeBots; - public BotManager(Core core) + public BotManager() { isRunning = true; - this.core = core; - Util.Init(ref activeBots); + Util.Init(out activeBots); } + public void Initialize() { } + public void WatchBots() { while (isRunning) @@ -48,7 +49,7 @@ public void WatchBots() public bool CreateBot(/*Ts3FullClientData bot*/) { string error = string.Empty; - var bot = new Bot(core); + var bot = new Bot(Core); try { if (bot.InitializeBot()) diff --git a/TS3AudioBot/CommandSystem/CommandManager.cs b/TS3AudioBot/CommandSystem/CommandManager.cs index 0e3512ba..17d128be 100644 --- a/TS3AudioBot/CommandSystem/CommandManager.cs +++ b/TS3AudioBot/CommandSystem/CommandManager.cs @@ -16,7 +16,7 @@ namespace TS3AudioBot.CommandSystem using System.Reflection; using System.Text.RegularExpressions; - public class CommandManager + public class CommandManager : Dependency.ICoreModule { private static readonly Regex CommandNamespaceValidator = new Regex(@"^[a-z]+( [a-z]+)*$", Util.DefaultRegexConfig & ~RegexOptions.IgnoreCase); @@ -29,10 +29,15 @@ public class CommandManager public CommandManager() { CommandSystem = new XCommandSystem(); - Util.Init(ref baseCommands); - Util.Init(ref commandPaths); - Util.Init(ref dynamicCommands); - Util.Init(ref pluginCommands); + Util.Init(out baseCommands); + Util.Init(out commandPaths); + Util.Init(out dynamicCommands); + Util.Init(out pluginCommands); + } + + public void Initialize() + { + RegisterMain(); } public XCommandSystem CommandSystem { get; } diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index c5b3bf89..587d2ceb 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -20,9 +20,8 @@ namespace TS3AudioBot using System.Threading; using Web; - public sealed class Core : IDisposable + public sealed class Core : IDisposable, Dependency.ICoreModule { - private bool isRunning; private bool consoleOutput; private bool writeLog; private bool writeLogStack; @@ -54,7 +53,7 @@ internal static void Main(string[] args) if (!core.ReadParameter(args)) return; - var initResult = core.Initialize(); + var initResult = core.InitializeCore(); if (!initResult) { Log.Write(Log.Level.Error, "Core initialization failed: {0}", initResult.Message); @@ -70,22 +69,21 @@ internal static void Main(string[] args) /// Manges plugins, provides various loading and unloading mechanisms. internal PluginManager PluginManager { get; set; } /// Mangement for the bot command system. - public CommandManager CommandManager { get; private set; } + public CommandManager CommandManager { get; set; } /// Manages factories which can load resources. - public ResourceFactoryManager FactoryManager { get; private set; } + public ResourceFactoryManager FactoryManager { get; set; } /// Minimalistic webserver hosting the api and web-interface. - public WebManager WebManager { get; private set; } + public WebManager WebManager { get; set; } /// Minimalistic config store for automatically serialized classes. - public ConfigFile ConfigManager { get; private set; } + public ConfigFile ConfigManager { get; set; } /// Permission system of the bot. - public RightsManager RightsManager { get; private set; } + public RightsManager RightsManager { get; set; } /// Permission system of the bot. - public BotManager Bots { get; private set; } + public BotManager Bots { get; set; } public Core() { // setting defaults - isRunning = true; consoleOutput = true; writeLog = true; writeLogStack = false; @@ -146,24 +144,24 @@ private bool ReadParameter(string[] args) return true; } - private R Initialize() + public void Initialize() { } + + private R InitializeCore() { ConfigManager = ConfigFile.OpenOrCreate(configFilePath) ?? ConfigFile.CreateDummy(); - var pmd = ConfigManager.GetDataStruct("PluginManager", true); var webd = ConfigManager.GetDataStruct("WebData", true); var rmd = ConfigManager.GetDataStruct("RightsManager", true); - var hmd = ConfigManager.GetDataStruct("HistoryManager", true); - - var yfd = ConfigManager.GetDataStruct("YoutubeFactory", true); - var mfd = ConfigManager.GetDataStruct("MediaFactory", true); // TODO: DUMMY REQUESTS + YoutubeDlHelper.DataObj = ConfigManager.GetDataStruct("YoutubeFactory", true); + ConfigManager.GetDataStruct("PluginManager", true); + ConfigManager.GetDataStruct("MediaFactory", true); + ConfigManager.GetDataStruct("HistoryManager", true); ConfigManager.GetDataStruct("AudioFramework", true); ConfigManager.GetDataStruct("QueryConnection", true); ConfigManager.GetDataStruct("PlaylistManager", true); mainBotData = ConfigManager.GetDataStruct("MainBot", true); // END TODO - ConfigManager.Close(); mainBotData = ConfigManager.GetDataStruct("MainBot", true); ConfigManager.Close(); @@ -211,32 +209,33 @@ void ColorLog(string msg, Log.Level lvl) Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); Audio.Opus.NativeMethods.DummyLoad(); - CommandManager = new CommandManager(); - CommandManager.RegisterMain(); - Database = new DbStore(hmd); - RightsManager = new RightsManager(this, rmd); - WebManager = new WebManager(this, webd); - PluginManager = new PluginManager(this, pmd); - Bots = new BotManager(this); - - RightsManager.RegisterRights(CommandManager.AllRights); - RightsManager.RegisterRights(Commands.RightHighVolume, Commands.RightDeleteAllPlaylists); - if (!RightsManager.ReadFile()) - return "Could not read Permission file."; - - Log.Write(Log.Level.Info, "[=========== Initializing Factories ===========]"); - YoutubeDlHelper.DataObj = yfd; - FactoryManager = new ResourceFactoryManager(this); - FactoryManager.AddFactory(new MediaFactory(mfd)); - FactoryManager.AddFactory(new YoutubeFactory(yfd)); - FactoryManager.AddFactory(new SoundcloudFactory()); - FactoryManager.AddFactory(new TwitchFactory()); - - Log.Write(Log.Level.Info, "[================= Finalizing =================]"); - PluginManager.RestorePlugins(); - - WebManager.StartServerAsync(); + var d = new Dependency.DependencyRealm(); + d.RegisterModule(this); // OK + d.RegisterModule(ConfigManager); // OK + Database = d.Create(); // OK + PluginManager = d.Create(); // OK + CommandManager = d.Create(); // OK + FactoryManager = d.Create(); // OK + WebManager = d.Create(); // OK + RightsManager = d.Create(); // OK + Bots = d.Create(); // OK + + d.SkipInitialized(this); + + if (!d.AllResolved()) + { + // TODO detailed log + for inner if + Log.Write(Log.Level.Warning, "Cyclic module dependency"); + d.ForceCyclicResolve(); + if (!d.AllResolved()) + { + Log.Write(Log.Level.Error, "Missing module dependency"); + return "Could not load all modules"; + } + } + + Log.Write(Log.Level.Info, "[==================== Done ====================]"); return R.OkR; } @@ -248,7 +247,6 @@ private void Run() public void Dispose() { Log.Write(Log.Level.Info, "TS3AudioBot shutting down."); - isRunning = false; Bots?.Dispose(); Bots = null; diff --git a/TS3AudioBot/DbStore.cs b/TS3AudioBot/DbStore.cs index c8545d08..194ff993 100644 --- a/TS3AudioBot/DbStore.cs +++ b/TS3AudioBot/DbStore.cs @@ -14,16 +14,20 @@ namespace TS3AudioBot using System; using System.IO; - public class DbStore : IDisposable + public class DbStore : Dependency.ICoreModule, IDisposable { private const string DbMetaInformationTable = "dbmeta"; - private readonly LiteDatabase database; - private readonly LiteCollection metaTable; + public Helper.ConfigFile Config { get; set; } - // TODO rework config class with config rewok - public DbStore(HistoryManagerData hmd) + private LiteDatabase database; + private LiteCollection metaTable; + + public DbStore() { } + + public void Initialize() { + var hmd = Config.GetDataStruct("HistoryManager", true); var historyFile = new FileInfo(hmd.HistoryFile); database = new LiteDatabase(historyFile.FullName); diff --git a/TS3AudioBot/Dependency/DependencyInjector.cs b/TS3AudioBot/Dependency/DependencyInjector.cs new file mode 100644 index 00000000..7840f478 --- /dev/null +++ b/TS3AudioBot/Dependency/DependencyInjector.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq; +using System.Reflection; + +namespace TS3AudioBot.Dependency +{ + using Helper; + + public class Injector : DependencyRealm, ICoreModule + { + public Injector() + { + } + + public void Initialize() { } + + public T GetCoreModule() where T : ICoreModule + { + throw new NotImplementedException(); + } + } + + public class Module + { + private static readonly ConcurrentDictionary typeData; + + public bool IsInitialized { get; set; } + public object Obj { get; } + public Type BaseType { get; } + // object SyncContext; + + static Module() + { + Util.Init(out typeData); + } + + public Module(object obj, Type baseType) + { + IsInitialized = false; + Obj = obj; + BaseType = baseType; + } + + public Type[] GetDependants() => GetDependants(Obj.GetType()); + + public IEnumerable GetModuleProperties() => GetModuleProperties(Obj.GetType()); + + private static Type[] GetDependants(Type type) + { + if (!typeData.TryGetValue(type, out var depArr)) + { + depArr = GetModuleProperties(type).Select(p => p.PropertyType).ToArray(); + typeData[type] = depArr; + } + return depArr; + } + + private static IEnumerable GetModuleProperties(Type type) => type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(p => p.CanRead && p.CanWrite && typeof(ITabModule).IsAssignableFrom(p.PropertyType)); + } + + public class DependencyRealm + { + protected ConcurrentDictionary loaded; + protected List waiting; + + public DependencyRealm() + { + Util.Init(out loaded); + Util.Init(out waiting); + } + + public T Create() where T : ICoreModule => (T)CreateFromType(typeof(T)); + public object CreateFromType(Type type) + { + var obj = Activator.CreateInstance(type); + RegisterInjectable(obj, false); + return obj; + } + public void RegisterModule(T obj, bool initialized = false) where T : ICoreModule => RegisterInjectable(obj, initialized, typeof(T)); + public void RegisterInjectable(object obj, bool initialized = false, Type baseType = null) + { + var modType = baseType ?? obj.GetType(); + var mod = new Module(obj, modType) { IsInitialized = initialized }; + if (initialized) + SetInitalized(mod); + else + waiting.Add(mod); + DoQueueInitialize(); + } + + public void SkipInitialized(object obj) + { + var index = waiting.Select((m, i) => new Tuple(m, i)).FirstOrDefault(t => t.Item1.Obj == obj); + if (index == null) + return; + + waiting.RemoveAt(index.Item2); + SetInitalized(index.Item1); + + DoQueueInitialize(); + } + + public void ForceCyclicResolve() + { + + } + + protected bool SetInitalized(Module module) + { + if (!module.IsInitialized && module.Obj is ITabModule tabModule) + { + tabModule.Initialize(); + } + module.IsInitialized = true; + loaded[module.BaseType] = module; + return true; + } + + protected void DoQueueInitialize() + { + bool changed; + do + { + changed = false; + for (int i = 0; i < waiting.Count; i++) + { + var mod = waiting[i]; + if (IsResolvable(mod)) + { + if (!DoResolve(mod)) + { + // TODO warn + continue; + } + changed = true; + waiting.RemoveAt(i); + i--; + } + } + } while (changed); + } + + protected bool IsResolvable(Module module) + { + var deps = module.GetDependants(); + foreach (var depeningType in deps) + { + if (!loaded.ContainsKey(depeningType)) // todo maybe to some linear inheritance checking + return false; + } + return true; + } + + protected bool DoResolve(Module module) + { + var props = module.GetModuleProperties(); + foreach (var prop in props) + { + if (loaded.TryGetValue(prop.PropertyType, out var depModule)) + { + prop.SetValue(module.Obj, depModule.Obj); + } + else + { + return false; + } + } + return SetInitalized(module); + } + + public bool AllResolved() => waiting.Count == 0; + + public void Unregister(Type type) + { + throw new NotImplementedException(); + } + } +} diff --git a/TS3AudioBot/Dependency/ITabModule.cs b/TS3AudioBot/Dependency/ITabModule.cs new file mode 100644 index 00000000..69e2b537 --- /dev/null +++ b/TS3AudioBot/Dependency/ITabModule.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TS3AudioBot.Dependency +{ + public interface ITabModule + { + void Initialize(); + } + + public interface ICoreModule : ITabModule + { + } + + public interface IBotModule : ICoreModule + { + } +} diff --git a/TS3AudioBot/Helper/ConfigFile.cs b/TS3AudioBot/Helper/ConfigFile.cs index 4b2000da..454df5f1 100644 --- a/TS3AudioBot/Helper/ConfigFile.cs +++ b/TS3AudioBot/Helper/ConfigFile.cs @@ -18,7 +18,7 @@ namespace TS3AudioBot.Helper using System.Reflection; using System.Linq; - public abstract class ConfigFile + public abstract class ConfigFile : Dependency.ICoreModule { private const char SplitChar = '='; private static readonly char[] SplitCharArr = { SplitChar }; @@ -28,9 +28,11 @@ public abstract class ConfigFile private bool changed; private readonly Dictionary confObjects; + public void Initialize() { } + protected ConfigFile() { - Util.Init(ref confObjects); + Util.Init(out confObjects); } public static ConfigFile OpenOrCreate(string path) diff --git a/TS3AudioBot/Helper/TickPool.cs b/TS3AudioBot/Helper/TickPool.cs index 7c0527bb..d98bf20b 100644 --- a/TS3AudioBot/Helper/TickPool.cs +++ b/TS3AudioBot/Helper/TickPool.cs @@ -25,7 +25,7 @@ public static class TickPool static TickPool() { run = false; - Util.Init(ref workList); + Util.Init(out workList); tickThread = new Thread(Tick) {Name = "TickPool"}; } @@ -110,7 +110,7 @@ private static void Tick() public static void Close() { run = false; - Util.WaitForThreadEnd(tickThread, TimeSpan.FromMilliseconds(100)); + Util.WaitForThreadEnd(tickThread, MinTick + MinTick); tickThread = null; } } diff --git a/TS3AudioBot/Helper/Util.cs b/TS3AudioBot/Helper/Util.cs index f3561749..d2f4fb2d 100644 --- a/TS3AudioBot/Helper/Util.cs +++ b/TS3AudioBot/Helper/Util.cs @@ -58,7 +58,7 @@ public static void WaitForThreadEnd(Thread thread, TimeSpan timeout) public static DateTime GetNow() => DateTime.Now; - public static void Init(ref T obj) where T : new() => obj = new T(); + public static void Init(out T obj) where T : new() => obj = new T(); public static Random Random { get; } = new Random(); diff --git a/TS3AudioBot/History/HistoryManager.cs b/TS3AudioBot/History/HistoryManager.cs index 7bd5dab1..bd88809d 100644 --- a/TS3AudioBot/History/HistoryManager.cs +++ b/TS3AudioBot/History/HistoryManager.cs @@ -41,7 +41,7 @@ public HistoryManager(HistoryManagerData hmd, DbStore database) Formatter = new SmartHistoryFormatter(); historyManagerData = hmd; - Util.Init(ref unusedIds); + Util.Init(out unusedIds); audioLogEntries = database.GetCollection(AudioLogEntriesTable); audioLogEntries.EnsureIndex(x => x.AudioResource.UniqueId, true); diff --git a/TS3AudioBot/PlaylistManager.cs b/TS3AudioBot/PlaylistManager.cs index 398b0da6..da4ac13d 100644 --- a/TS3AudioBot/PlaylistManager.cs +++ b/TS3AudioBot/PlaylistManager.cs @@ -463,7 +463,7 @@ public class Playlist public Playlist(string name, ulong? creatorDbId = null) { - Util.Init(ref resources); + Util.Init(out resources); CreatorDbId = creatorDbId; Name = name; } diff --git a/TS3AudioBot/Plugins/PluginManager.cs b/TS3AudioBot/Plugins/PluginManager.cs index cf7935d7..7f952915 100644 --- a/TS3AudioBot/Plugins/PluginManager.cs +++ b/TS3AudioBot/Plugins/PluginManager.cs @@ -30,19 +30,24 @@ namespace TS3AudioBot.Plugins // - Add commands to command manager // - Start config to system? - internal class PluginManager : IDisposable + internal class PluginManager : IDisposable, Dependency.ICoreModule { - private readonly Core core; - private readonly PluginManagerData pluginManagerData; + public Core Core { get; set; } + public ConfigFile Config { get; set; } + + private PluginManagerData pluginManagerData; private readonly Dictionary plugins; private readonly HashSet usedIds; - public PluginManager(Core core, PluginManagerData pmd) + public PluginManager() + { + Util.Init(out plugins); + Util.Init(out usedIds); + } + + public void Initialize() { - this.core = core ?? throw new ArgumentNullException(nameof(core)); - pluginManagerData = pmd; - Util.Init(ref plugins); - Util.Init(ref usedIds); + pluginManagerData = Config.GetDataStruct("PluginManager", true); } private void CheckAndClearPlugins() @@ -81,7 +86,7 @@ private void CheckLocalPlugins() if (IgnoreFile(file)) continue; - plugin = new Plugin(file, core, GetFreeId()); + plugin = new Plugin(file, Core, GetFreeId()); if (plugin.Load() == PluginResponse.Disabled) { @@ -181,7 +186,7 @@ private PluginResponse StartPlugin(Plugin plugin) StopPlugin(plugin); Log.Write(Log.Level.Warning, "Plugin \"{0}\" failed to load: {1}", plugin.PluginFile.Name, - ex.Message); + ex); return PluginResponse.UnknownError; } case PluginStatus.Active: diff --git a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs index e4b5468a..da39070d 100644 --- a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs +++ b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs @@ -18,22 +18,35 @@ namespace TS3AudioBot.ResourceFactories using System.Collections.Generic; using System.Reflection; - public sealed class ResourceFactoryManager : IDisposable + public sealed class ResourceFactoryManager : Dependency.ICoreModule, IDisposable { private const string CmdResPrepath = "from "; private const string CmdListPrepath = "list from "; - private readonly Core core; + public ConfigFile Config { get; set; } + public CommandManager CommandManager { get; set; } + public Rights.RightsManager RightsManager { get; set; } + private readonly Dictionary allFacories; private readonly List listFactories; private readonly List resFactories; - public ResourceFactoryManager(Core core) + public ResourceFactoryManager() { - this.core = core; - Util.Init(ref allFacories); - Util.Init(ref resFactories); - Util.Init(ref listFactories); + Util.Init(out allFacories); + Util.Init(out resFactories); + Util.Init(out listFactories); + } + + void Dependency.ITabModule.Initialize() + { + var yfd = Config.GetDataStruct("YoutubeFactory", true); + var mfd = Config.GetDataStruct("MediaFactory", true); + + AddFactory(new MediaFactory(mfd)); + AddFactory(new YoutubeFactory(yfd)); + AddFactory(new SoundcloudFactory()); + AddFactory(new TwitchFactory()); } // Load lookup stages @@ -180,8 +193,8 @@ public void AddFactory(IFactory factory) var factoryInfo = new FactoryData(factory, commands.ToArray()); allFacories.Add(factory.FactoryFor, factoryInfo); - core.CommandManager.RegisterCollection(factoryInfo); - core.RightsManager.RegisterRights(factoryInfo.ExposedRights); + CommandManager.RegisterCollection(factoryInfo); + RightsManager.RegisterRights(factoryInfo.ExposedRights); } public void RemoveFactory(IFactory factory) @@ -196,8 +209,8 @@ public void RemoveFactory(IFactory factory) if (factory is IPlaylistFactory listFactory) listFactories.Remove(listFactory); - core.CommandManager.UnregisterCollection(factoryInfo); - core.RightsManager.UnregisterRights(factoryInfo.ExposedRights); + CommandManager.UnregisterCollection(factoryInfo); + RightsManager.UnregisterRights(factoryInfo.ExposedRights); } diff --git a/TS3AudioBot/Rights/RightsManager.cs b/TS3AudioBot/Rights/RightsManager.cs index 0490db6a..eaed694f 100644 --- a/TS3AudioBot/Rights/RightsManager.cs +++ b/TS3AudioBot/Rights/RightsManager.cs @@ -18,15 +18,17 @@ namespace TS3AudioBot.Rights using System.Text; using TS3Client; - public class RightsManager + public class RightsManager : Dependency.ICoreModule { private const int RuleLevelSize = 2; - private readonly Core core; + public ConfigFile Config { get; set; } + public BotManager Bots { get; set; } + public CommandSystem.CommandManager CommandManager { get; set; } private bool needsRecalculation; private readonly Cache cachedRights; - private readonly RightsManagerData rightsManagerData; + private RightsManagerData rightsManagerData; private RightsRule rootRule; private RightsRule[] rules; private readonly HashSet registeredRights; @@ -39,12 +41,19 @@ public class RightsManager private bool needsAvailableGroups = true; private bool needsAvailableChanGroups = true; - public RightsManager(Core core, RightsManagerData rmd) + public RightsManager() { - this.core = core; - rightsManagerData = rmd; - cachedRights = new Cache(); - registeredRights = new HashSet(); + Util.Init(out cachedRights); + Util.Init(out registeredRights); + } + + public void Initialize() + { + rightsManagerData = Config.GetDataStruct("RightsManager", true); + RegisterRights(CommandManager.AllRights); + RegisterRights(Commands.RightHighVolume, Commands.RightDeleteAllPlaylists); + if (!ReadFile()) + Log.Write(Log.Level.Error, "Could not read Permission file."); } public void RegisterRights(params string[] rights) => RegisterRights((IEnumerable)rights); @@ -103,7 +112,7 @@ private ExecuteContext GetRightsContext(InvokerData inv) || (needsAvailableChanGroups && !execCtx.ChannelGroupId.HasValue))) { // TODO fixme !!!!!!!! - var result = core.Bots.GetBot(0)?.QueryConnection.GetClientInfoById(inv.ClientId.Value) ?? R.Err("No bot"); + var result = Bots.GetBot(0)?.QueryConnection.GetClientInfoById(inv.ClientId.Value) ?? R.Err("No bot"); if (result.Ok) { if (execCtx.AvailableGroups == null) @@ -116,7 +125,7 @@ private ExecuteContext GetRightsContext(InvokerData inv) if (needsAvailableGroups && inv.DatabaseId.HasValue && execCtx.AvailableGroups == null) { // TODO fixme !!!!!!!! - var result = core.Bots.GetBot(0)?.QueryConnection.GetClientServerGroups(inv.DatabaseId.Value) ?? R.Err(""); + var result = Bots.GetBot(0)?.QueryConnection.GetClientServerGroups(inv.DatabaseId.Value) ?? R.Err(""); if (result.Ok) execCtx.AvailableGroups = result.Value; } diff --git a/TS3AudioBot/Sessions/ApiToken.cs b/TS3AudioBot/Sessions/ApiToken.cs index 20208f6a..d8ea4feb 100644 --- a/TS3AudioBot/Sessions/ApiToken.cs +++ b/TS3AudioBot/Sessions/ApiToken.cs @@ -30,7 +30,7 @@ public ApiToken() { Value = null; Timeout = DateTime.MinValue; - Util.Init(ref nonceList); + Util.Init(out nonceList); } public ApiNonce UseNonce(string nonce) diff --git a/TS3AudioBot/Sessions/SessionManager.cs b/TS3AudioBot/Sessions/SessionManager.cs index 28a10be3..e2758a2f 100644 --- a/TS3AudioBot/Sessions/SessionManager.cs +++ b/TS3AudioBot/Sessions/SessionManager.cs @@ -29,8 +29,8 @@ public class SessionManager public SessionManager(DbStore database) { - Util.Init(ref openSessions); - Util.Init(ref liveTokenList); + Util.Init(out openSessions); + Util.Init(out liveTokenList); dbTokenList = database.GetCollection(ApiTokenTable); dbTokenList.EnsureIndex(x => x.UserUid, true); diff --git a/TS3AudioBot/Sessions/UserSession.cs b/TS3AudioBot/Sessions/UserSession.cs index 94285bb7..f4814fa0 100644 --- a/TS3AudioBot/Sessions/UserSession.cs +++ b/TS3AudioBot/Sessions/UserSession.cs @@ -104,7 +104,7 @@ public void Set(TData data) VerifyLock(); if (assocMap == null) - Util.Init(ref assocMap); + Util.Init(out assocMap); if (assocMap.ContainsKey(typeof(TAssoc))) assocMap[typeof(TAssoc)] = data; diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index 55e848cc..dbcb530a 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -45,6 +45,7 @@ + 7 bin\Release\ @@ -96,6 +97,8 @@ + + @@ -265,6 +268,7 @@ + diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index fe84bae6..9ab15f41 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -68,8 +68,8 @@ private void ExtendedClientLeftView(object sender, IEnumerable e protected TeamspeakControl(ClientType connectionType) { - Util.Init(ref clientDbNames); - Util.Init(ref clientbuffer); + Util.Init(out clientDbNames); + Util.Init(out clientbuffer); if (connectionType == ClientType.Full) tsBaseClient = new Ts3FullClient(EventDispatchType.DoubleThread); diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index 4a351dce..e8d51621 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -90,8 +90,8 @@ public Ts3Full(Ts3FullClientData tfcd) : base(ClientType.Full) stallCount = 0; identity = null; - Util.Init(ref channelSubscriptionsSetup); - Util.Init(ref clientSubscriptionsSetup); + Util.Init(out channelSubscriptionsSetup); + Util.Init(out clientSubscriptionsSetup); subscriptionSetupChanged = true; } diff --git a/TS3AudioBot/Web/Interface/SiteMapper.cs b/TS3AudioBot/Web/Interface/SiteMapper.cs index 02f74fc9..ce6b4093 100644 --- a/TS3AudioBot/Web/Interface/SiteMapper.cs +++ b/TS3AudioBot/Web/Interface/SiteMapper.cs @@ -74,9 +74,9 @@ public MapNode(string name, string fullPath) { Name = name; FullPath = fullPath; - Util.Init(ref childFolder); - Util.Init(ref fileMap); - Util.Init(ref childNodes); + Util.Init(out childFolder); + Util.Init(out fileMap); + Util.Init(out childNodes); } public ISiteProvider TryGetSite(string name) diff --git a/TS3AudioBot/Web/WebManager.cs b/TS3AudioBot/Web/WebManager.cs index 37898c2b..3f876c11 100644 --- a/TS3AudioBot/Web/WebManager.cs +++ b/TS3AudioBot/Web/WebManager.cs @@ -15,7 +15,7 @@ namespace TS3AudioBot.Web using System.Net; using System.Threading; - public sealed class WebManager : IDisposable + public sealed class WebManager : Dependency.ICoreModule, IDisposable { public const string WebRealm = "ts3ab"; @@ -24,38 +24,49 @@ public sealed class WebManager : IDisposable private HttpListener webListener; private Thread serverThread; - private readonly WebData webData; + private WebData webData; private bool startWebServer; + public ConfigFile Config { get; set; } + public Core Core { get; set; } + public Api.WebApi Api { get; private set; } public Interface.WebDisplay Display { get; private set; } - public WebManager(Core core, WebData webd) + public WebManager() { } + + public void Initialize() { - webData = webd; - webListener = new HttpListener - { - AuthenticationSchemes = AuthenticationSchemes.Anonymous | AuthenticationSchemes.Basic, - Realm = WebRealm, - AuthenticationSchemeSelectorDelegate = AuthenticationSchemeSelector, - }; + webData = Config.GetDataStruct("WebData", true); + + InitializeSubcomponents(); - InitializeSubcomponents(core); + StartServerAsync(); } - private void InitializeSubcomponents(Core core) + private void InitializeSubcomponents() { startWebServer = false; if (webData.EnableApi) { - Api = new Api.WebApi(core); + Api = new Api.WebApi(Core); startWebServer = true; } if (webData.EnableWebinterface) { - Display = new Interface.WebDisplay(core); + Display = new Interface.WebDisplay(Core); startWebServer = true; } + + if(startWebServer) + { + webListener = new HttpListener + { + AuthenticationSchemes = AuthenticationSchemes.Anonymous | AuthenticationSchemes.Basic, + Realm = WebRealm, + AuthenticationSchemeSelectorDelegate = AuthenticationSchemeSelector, + }; + } } private void ReloadHostPaths() diff --git a/TS3Client/ColorDbg.cs b/TS3Client/ColorDbg.cs index 1958d6b5..20ea6520 100644 --- a/TS3Client/ColorDbg.cs +++ b/TS3Client/ColorDbg.cs @@ -44,6 +44,14 @@ public static void WriteRtt(TimeSpan smoothedRtt, TimeSpan smoothedRttVar, TimeS Console.WriteLine(); } + [Conditional("COLOG_CMD")] + [MethodImpl(MethodImplOptions.Synchronized)] + public static void WriteCmd(string cmd, bool send) + { + WriteType(send ? "[O]" : "[I]"); + Console.WriteLine(cmd); + } + [Conditional("COLOG_RAWPKG")] [MethodImpl(MethodImplOptions.Synchronized)] public static void WritePkgOut(OutgoingPacket packet) diff --git a/TS3Client/FileTransferManager.cs b/TS3Client/FileTransferManager.cs index 91c56a18..419915ae 100644 --- a/TS3Client/FileTransferManager.cs +++ b/TS3Client/FileTransferManager.cs @@ -38,7 +38,7 @@ public FileTransferManager(Ts3BaseFunctions ts3Connection) { parent = ts3Connection; //ts3connection.OnFileTransferStatus += FileStatusNotification; - Util.Init(ref transferQueue); + Util.Init(out transferQueue); } /// Initiate a file upload to the server. diff --git a/TS3Client/Full/Ts3Crypt.cs b/TS3Client/Full/Ts3Crypt.cs index d0bc0eaa..f4c46780 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TS3Client/Full/Ts3Crypt.cs @@ -90,7 +90,7 @@ private static IdentityData LoadIdentity(Tuple pubPrivKey, LastCheckedKeyOffset = lastCheckedKeyOffset < keyOffset ? keyOffset : lastCheckedKeyOffset, }; } - + private static readonly ECKeyGenerationParameters KeyGenParams = new ECKeyGenerationParameters(X9ObjectIdentifiers.Prime256v1, new SecureRandom()); private static ECPoint ImportPublicKey(byte[] asnByteArray) diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index b4e23801..c328f668 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -252,6 +252,7 @@ private void NetworkLoop(object ctxObject) case PacketType.Command: case PacketType.CommandLow: string message = Util.Encoder.GetString(packet.Data, 0, packet.Data.Length); + ColorDbg.WriteCmd(message, false); var result = msgProc.PushMessage(message); if (result.HasValue) dispatcher.Invoke(result.Value); @@ -366,7 +367,9 @@ private void SendCommandBase(WaitBlock wb, Ts3Command com) msgProc.EnqueueRequest(retCodeParameter.Value, wb); } - byte[] data = Util.Encoder.GetBytes(com.ToString()); + var message = com.ToString(); + ColorDbg.WriteCmd(message, true); + byte[] data = Util.Encoder.GetBytes(message); packetHandler.AddOutgoingPacket(data, PacketType.Command); } } diff --git a/TS3Client/Ts3BaseClient.cs b/TS3Client/Ts3BaseClient.cs index 223370ae..135a13cf 100644 --- a/TS3Client/Ts3BaseClient.cs +++ b/TS3Client/Ts3BaseClient.cs @@ -107,9 +107,9 @@ public void ChangeName(string newName) new CommandParameter("client_nickname", newName)); public void ChangeBadges(string newBadges) - => Send("clientupdate", - new CommandParameter("client_badges", newBadges)); - + => Send("clientupdate", + new CommandParameter("client_badges", newBadges)); + public void ChangeDescription(string newDescription, ClientIdT clientId) => Send("clientedit", new CommandParameter("clid", clientId), diff --git a/TS3Client/Util.cs b/TS3Client/Util.cs index a8af5db7..bddd645a 100644 --- a/TS3Client/Util.cs +++ b/TS3Client/Util.cs @@ -22,7 +22,7 @@ internal static class Util public static IEnumerable GetFlags(this Enum input) => Enum.GetValues(input.GetType()).Cast().Where(input.HasFlag); - public static void Init(ref T fld) where T : new() => fld = new T(); + public static void Init(out T fld) where T : new() => fld = new T(); public static Encoding Encoder { get; } = new UTF8Encoding(false); diff --git a/TS3Client/ts3protocol.md b/TS3Client/ts3protocol.md index 86bb11c3..64ecf90c 100644 --- a/TS3Client/ts3protocol.md +++ b/TS3Client/ts3protocol.md @@ -82,11 +82,7 @@ The final byte then looks like this ## 1.4 Packet Compression To reduce a packet size the data can be compressed. When the data is compressed the `Compressed` flag must be set. -The algorithm "QuickLZ" is used for compression. -QuickLZ offers different compression levels. -The chosen level differs depending on the packet direction as following -- (Client -> Server) Level 1 -- (Client <- Server) Level 3 +The algorithm "QuickLZ" with Level 1 is used for compression. ## 1.5 Packet Splitting When the packet payload exceeds the maximum datablock size the data can be @@ -483,7 +479,7 @@ channel encryption or server wide encryption flag is set. - `acn` the accepted client nickname, this might differ from the desired nickname if it's already in use - `aclid` the assigned client Id -- `pv` ??? +- `pv` Protocol Version - `lt` License Type of the server - `client_talk_power` the initial talk power - `client_needed_serverquery_view_power` From b203479d37ac72d13cdd8be23a7c1788065b7377 Mon Sep 17 00:00:00 2001 From: Splamy Date: Mon, 27 Nov 2017 19:19:49 +0100 Subject: [PATCH 11/48] Reworked R wrapping --- TS3AudioBot.sln | 2 +- TS3AudioBot/Bot.cs | 10 +- TS3AudioBot/CommandSystem/CommandManager.cs | 4 +- .../CommandSystem/ExecutionInformation.cs | 1 - TS3AudioBot/Commands.cs | 32 +- TS3AudioBot/Core.cs | 53 ++- TS3AudioBot/Dependency/DependencyInjector.cs | 32 +- TS3AudioBot/Dependency/ITabModule.cs | 13 +- TS3AudioBot/Helper/R.cs | 70 ---- TS3AudioBot/Helper/Util.cs | 6 +- TS3AudioBot/History/HistoryManager.cs | 2 +- TS3AudioBot/PlayManager.cs | 18 +- TS3AudioBot/ResourceFactories/MediaFactory.cs | 8 +- .../ResourceFactoryManager.cs | 9 +- .../ResourceFactories/SoundcloudFactory.cs | 14 +- .../ResourceFactories/TwitchFactory.cs | 2 +- .../ResourceFactories/YoutubeDlHelper.cs | 25 +- .../ResourceFactories/YoutubeFactory.cs | 14 +- TS3AudioBot/Sessions/UserSession.cs | 39 +- TS3AudioBot/TS3AudioBot.csproj | 4 +- TS3AudioBot/TeamspeakControl.cs | 352 +++++++++--------- TS3AudioBot/Ts3Full.cs | 46 ++- TS3AudioBot/packages.config | 1 + TS3Client/FileTransferManager.cs | 50 +-- TS3Client/Full/Ts3FullClient.cs | 195 +++++----- TS3Client/LazyNotification.cs | 2 +- TS3Client/Query/Ts3QueryClient.cs | 22 +- TS3Client/R.cs | 129 +++++++ TS3Client/TS3Client.csproj | 4 + TS3Client/Ts3BaseClient.cs | 111 +++--- TS3Client/Ts3Exceptions.cs | 9 - TS3Client/Util.cs | 29 +- TS3Client/WaitBlock.cs | 16 +- TS3Client/packages.config | 1 + Ts3ClientTests/Program.cs | 6 +- 35 files changed, 735 insertions(+), 596 deletions(-) delete mode 100644 TS3AudioBot/Helper/R.cs create mode 100644 TS3Client/R.cs diff --git a/TS3AudioBot.sln b/TS3AudioBot.sln index 36bdb08a..9c79c42e 100644 --- a/TS3AudioBot.sln +++ b/TS3AudioBot.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.15 +VisualStudioVersion = 15.0.27004.2009 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TS3AudioBot", "TS3AudioBot\TS3AudioBot.csproj", "{0ECC38F3-DE6E-4D7F-81EB-58B15F584635}" EndProject diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index f8c6f583..461c1221 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -122,7 +122,7 @@ private void TextCallback(object sender, TextMessage textMessage) var refreshResult = QueryConnection.RefreshClientBuffer(true); if (!refreshResult.Ok) - Log.Write(Log.Level.Warning, "Bot is not correctly set up. Some requests might fail or are slower. ({0})", refreshResult.Message); + Log.Write(Log.Level.Warning, "Bot is not correctly set up. Some requests might fail or are slower. ({0})", refreshResult.Error); var clientResult = QueryConnection.GetClientById(textMessage.InvokerId); @@ -137,7 +137,7 @@ private void TextCallback(object sender, TextMessage textMessage) { if (!clientResult.Ok) { - Log.Write(Log.Level.Error, clientResult.Message); + Log.Write(Log.Level.Error, clientResult.Error); return; } session = SessionManager.CreateSession(this, clientResult.Value); @@ -209,7 +209,7 @@ private void LoggedUpdateBotStatus(object sender, EventArgs e) { var result = UpdateBotStatus(); if (!result) - Log.Write(Log.Level.Warning, result.Message); + Log.Write(Log.Level.Warning, result.Error); } public R UpdateBotStatus(string overrideStr = null) @@ -251,7 +251,7 @@ private void GenerateStatusImage(object sender, EventArgs e) bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg); var result = QueryConnection.UploadAvatar(mem); if (!result.Ok) - Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Message); + Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Error); } } } @@ -261,7 +261,7 @@ private void GenerateStatusImage(object sender, EventArgs e) { var result = QueryConnection.UploadAvatar(sleepPic); if (!result.Ok) - Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Message); + Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Error); } } } diff --git a/TS3AudioBot/CommandSystem/CommandManager.cs b/TS3AudioBot/CommandSystem/CommandManager.cs index 17d128be..24757f5b 100644 --- a/TS3AudioBot/CommandSystem/CommandManager.cs +++ b/TS3AudioBot/CommandSystem/CommandManager.cs @@ -168,11 +168,11 @@ private void LoadICommand(ICommand com, string path) var buildResult = BuildAndGet(comPath.Take(comPath.Length - 1)); if (!buildResult) - GenerateError(buildResult.Message, com as BotCommand); + GenerateError(buildResult.Error, com as BotCommand); var result = InsertInto(buildResult.Value, com, comPath.Last()); if (!result) - GenerateError(result.Message, com as BotCommand); + GenerateError(result.Error, com as BotCommand); } private R BuildAndGet(IEnumerable comPath) diff --git a/TS3AudioBot/CommandSystem/ExecutionInformation.cs b/TS3AudioBot/CommandSystem/ExecutionInformation.cs index c1bbae04..b04bae2d 100644 --- a/TS3AudioBot/CommandSystem/ExecutionInformation.cs +++ b/TS3AudioBot/CommandSystem/ExecutionInformation.cs @@ -10,7 +10,6 @@ namespace TS3AudioBot.CommandSystem { using Sessions; - using Helper; public class ExecutionInformation { diff --git a/TS3AudioBot/Commands.cs b/TS3AudioBot/Commands.cs index 974d833c..8e250123 100644 --- a/TS3AudioBot/Commands.cs +++ b/TS3AudioBot/Commands.cs @@ -497,20 +497,15 @@ public static void CommandKickme(ExecutionInformation info, string parameter) if (info.ApiCall) throw new CommandException("This command is not available as API", CommandExceptionReason.NotSupported); - try - { - if (info.InvokerData.ClientId.HasValue) - { - if (string.IsNullOrEmpty(parameter) || parameter == "near") - info.Bot.QueryConnection.KickClientFromChannel(info.InvokerData.ClientId.Value); - else if (parameter == "far") - info.Bot.QueryConnection.KickClientFromServer(info.InvokerData.ClientId.Value); - } - } - catch (Ts3CommandException ex) + if (info.InvokerData.ClientId.HasValue) { - Log.Write(Log.Level.Info, "Could not kick: {0}", ex); - throw new CommandException("I'm not strong enough, master!", ex, CommandExceptionReason.CommandError); + var result = R.OkR; + if (string.IsNullOrEmpty(parameter) || parameter == "near") + result = info.Bot.QueryConnection.KickClientFromChannel(info.InvokerData.ClientId.Value); + else if (parameter == "far") + result = info.Bot.QueryConnection.KickClientFromServer(info.InvokerData.ClientId.Value); + if (!result.Ok) + throw new CommandException("I'm not strong enough, master!", CommandExceptionReason.CommandError); } } @@ -561,7 +556,7 @@ public static JsonObject CommandListDelete(ExecutionInformation info, string nam if (!hresult) { info.Session.SetResponse(ResponseListDelete, name); - return new JsonEmpty($"Do you really want to delete the playlist \"{name}\" (error:{hresult.Message})"); + return new JsonEmpty($"Do you really want to delete the playlist \"{name}\" (error:{hresult.Error})"); } else { @@ -855,14 +850,13 @@ public static string CommandQuit(ExecutionInformation info, string param) { if (info.ApiCall) { - info.Bot.Dispose(); + info.Core.Dispose(); return null; } if (param == "force") { - // TODO necessary?: info.Bot.QueryConnection.OnMessageReceived -= TextCallback; - info.Bot.Dispose(); + info.Core.Dispose(); return null; } else @@ -1022,7 +1016,7 @@ public static JsonObject CommandSettings(ExecutionInformation info, string key, { var result = info.Core.ConfigManager.SetSetting(filteredArr[0].Key, value); if (result.Ok) return null; - else throw new CommandException(result.Message, CommandExceptionReason.CommandError); + else throw new CommandException(result.Error, CommandExceptionReason.CommandError); } } else @@ -1358,7 +1352,7 @@ private static string ResponseListDelete(ExecutionInformation info) { var name = info.Session.ResponseData as string; var result = info.Bot.PlaylistManager.DeletePlaylist(name, info.InvokerData.DatabaseId ?? 0, info.HasRights(RightDeleteAllPlaylists)); - if (!result) return result.Message; + if (!result) return result.Error; else return "Ok"; } return null; diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index 587d2ceb..62320618 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -10,6 +10,7 @@ namespace TS3AudioBot { using CommandSystem; + using Dependency; using Helper; using History; using Plugins; @@ -20,7 +21,7 @@ namespace TS3AudioBot using System.Threading; using Web; - public sealed class Core : IDisposable, Dependency.ICoreModule + public sealed class Core : IDisposable, ICoreModule { private bool consoleOutput; private bool writeLog; @@ -41,12 +42,21 @@ internal static void Main(string[] args) core.Dispose(); }; + bool forceNextExit = false; Console.CancelKeyPress += (s, e) => { if (e.SpecialKey == ConsoleSpecialKey.ControlC) { - e.Cancel = true; - core.Dispose(); + if (!forceNextExit) + { + e.Cancel = true; + core.Dispose(); + forceNextExit = true; + } + else + { + Environment.Exit(0); + } } }; @@ -56,7 +66,7 @@ internal static void Main(string[] args) var initResult = core.InitializeCore(); if (!initResult) { - Log.Write(Log.Level.Error, "Core initialization failed: {0}", initResult.Message); + Log.Write(Log.Level.Error, "Core initialization failed: {0}", initResult.Error); core.Dispose(); return; } @@ -68,6 +78,8 @@ internal static void Main(string[] args) internal DbStore Database { get; set; } /// Manges plugins, provides various loading and unloading mechanisms. internal PluginManager PluginManager { get; set; } + /// Manges plugins, provides various loading and unloading mechanisms. + internal Injector Injector { get; set; } /// Mangement for the bot command system. public CommandManager CommandManager { get; set; } /// Manages factories which can load resources. @@ -210,25 +222,26 @@ void ColorLog(string msg, Log.Level lvl) Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); Audio.Opus.NativeMethods.DummyLoad(); - var d = new Dependency.DependencyRealm(); - d.RegisterModule(this); // OK - d.RegisterModule(ConfigManager); // OK - Database = d.Create(); // OK - PluginManager = d.Create(); // OK - CommandManager = d.Create(); // OK - FactoryManager = d.Create(); // OK - WebManager = d.Create(); // OK - RightsManager = d.Create(); // OK - Bots = d.Create(); // OK - - d.SkipInitialized(this); - - if (!d.AllResolved()) + Injector = new Injector(); + Injector.RegisterModule(this); // OK + Injector.RegisterModule(ConfigManager); // OK + Injector.RegisterModule(Injector); // OK + Database = Injector.Create(); // OK + PluginManager = Injector.Create(); // OK + CommandManager = Injector.Create(); // OK + FactoryManager = Injector.Create(); // OK + WebManager = Injector.Create(); // OK + RightsManager = Injector.Create(); // OK + Bots = Injector.Create(); // OK + + Injector.SkipInitialized(this); + + if (!Injector.AllResolved()) { // TODO detailed log + for inner if Log.Write(Log.Level.Warning, "Cyclic module dependency"); - d.ForceCyclicResolve(); - if (!d.AllResolved()) + Injector.ForceCyclicResolve(); + if (!Injector.AllResolved()) { Log.Write(Log.Level.Error, "Missing module dependency"); return "Could not load all modules"; diff --git a/TS3AudioBot/Dependency/DependencyInjector.cs b/TS3AudioBot/Dependency/DependencyInjector.cs index 7840f478..fc9b6ec8 100644 --- a/TS3AudioBot/Dependency/DependencyInjector.cs +++ b/TS3AudioBot/Dependency/DependencyInjector.cs @@ -1,12 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Collections.Concurrent; -using System.Linq; -using System.Reflection; +// 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.Dependency { using Helper; + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; public class Injector : DependencyRealm, ICoreModule { @@ -16,9 +24,11 @@ public Injector() public void Initialize() { } - public T GetCoreModule() where T : ICoreModule + public R GetCoreModule() where T : ICoreModule { - throw new NotImplementedException(); + if(loaded.TryGetValue(typeof(T), out var mod)) + return (T)mod.Obj; + return "Module not found"; } } @@ -93,12 +103,12 @@ public void RegisterInjectable(object obj, bool initialized = false, Type baseTy public void SkipInitialized(object obj) { - var index = waiting.Select((m, i) => new Tuple(m, i)).FirstOrDefault(t => t.Item1.Obj == obj); - if (index == null) + var (mod, idx) = waiting.Select((m, i) => (mod: m, idx: i)).FirstOrDefault(t => t.mod.Obj == obj); + if (mod == null) return; - waiting.RemoveAt(index.Item2); - SetInitalized(index.Item1); + waiting.RemoveAt(idx); + SetInitalized(mod); DoQueueInitialize(); } diff --git a/TS3AudioBot/Dependency/ITabModule.cs b/TS3AudioBot/Dependency/ITabModule.cs index 69e2b537..e3d14ffe 100644 --- a/TS3AudioBot/Dependency/ITabModule.cs +++ b/TS3AudioBot/Dependency/ITabModule.cs @@ -1,8 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// 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.Dependency { diff --git a/TS3AudioBot/Helper/R.cs b/TS3AudioBot/Helper/R.cs deleted file mode 100644 index 8ad982dd..00000000 --- a/TS3AudioBot/Helper/R.cs +++ /dev/null @@ -1,70 +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.Helper -{ - /// - /// The R result wrapper. - /// The functionality is quite similar to the optional-pattern. - /// It either represents success or an error + message. - /// - public struct R - { - public static readonly R OkR = new R(); - - // using default false bool so Ok is true on default - private readonly bool isError; - public bool Ok => !isError; - public string Message { get; } - - private R(string message) { isError = true; Message = message; } - /// Creates a new failed result with a message - /// The message - public static R Err(string message) => new R(message); - - public static implicit operator bool(R result) => result.Ok; - public static implicit operator string(R result) => result.Message; - - public static implicit operator R(string message) => new R(message); - - public override string ToString() => Message; - } - - /// - /// The R<T> result wrapper. - /// The functionality is quite similar to the optional-pattern. - /// It either represents success + value or an error + message. - /// The value is guaranteed to be non-null when successful. - /// - public struct R - { - private readonly bool isError; - public bool Ok => !isError; - public string Message { get; } - public T Value { get; } - - private R(T value) { isError = false; Message = null; if (value == null) throw new System.ArgumentNullException(nameof(value), "Return of ok must not be null."); Value = value; } - private R(string message) { isError = true; Message = message; Value = default(T); } - - /// Creates a new failed result with a message - /// The message - public static R Err(string message) => new R(message); - /// Creates a new successful result with a value - /// The value - public static R OkR(T value) => new R(value); - - public static implicit operator bool(R result) => result.Ok; - public static implicit operator string(R result) => result.Message; - - public static implicit operator R(T result) => new R(result); - public static implicit operator R(string message) => new R(message); - - public override string ToString() => Message; - } -} diff --git a/TS3AudioBot/Helper/Util.cs b/TS3AudioBot/Helper/Util.cs index d2f4fb2d..2150679f 100644 --- a/TS3AudioBot/Helper/Util.cs +++ b/TS3AudioBot/Helper/Util.cs @@ -139,7 +139,7 @@ public static int ToSeed(string seed) public static void UnwrapThrow(this R r) { if (!r.Ok) - throw new CommandException(r.Message, CommandExceptionReason.CommandError); + throw new CommandException(r.Error, CommandExceptionReason.CommandError); } public static T UnwrapThrow(this R r) @@ -147,9 +147,9 @@ public static T UnwrapThrow(this R r) if (r.Ok) return r.Value; else - throw new CommandException(r.Message, CommandExceptionReason.CommandError); + throw new CommandException(r.Error, CommandExceptionReason.CommandError); } - + public static string UnrollException(this Exception ex) { var strb = new StringBuilder(); diff --git a/TS3AudioBot/History/HistoryManager.cs b/TS3AudioBot/History/HistoryManager.cs index bd88809d..09409827 100644 --- a/TS3AudioBot/History/HistoryManager.cs +++ b/TS3AudioBot/History/HistoryManager.cs @@ -275,7 +275,7 @@ private static List FilterList(CommandSystem.ExecutionInformation var result = info.Core.FactoryManager.Load(entry.AudioResource); if (!result) { - info.Write($"//DEBUG// ({entry.AudioResource.UniqueId}) Reason: {result.Message}"); + info.Write($"//DEBUG// ({entry.AudioResource.UniqueId}) Reason: {result.Error}"); nextIter.Add(entry); } diff --git a/TS3AudioBot/PlayManager.cs b/TS3AudioBot/PlayManager.cs index b7fa6622..ac612bc1 100644 --- a/TS3AudioBot/PlayManager.cs +++ b/TS3AudioBot/PlayManager.cs @@ -43,7 +43,7 @@ public R Enqueue(InvokerData invoker, string message, string audioType = null) { var result = ResourceFactoryManager.Load(message, audioType); if (!result) - return result.Message; + return result.Error; return EnqueueInternal(invoker, new PlaylistItem(result.Value.BaseData)); } public R Enqueue(InvokerData invoker, uint historyId) => EnqueueInternal(invoker, new PlaylistItem(historyId)); @@ -70,7 +70,7 @@ public R Play(InvokerData invoker, AudioResource ar, MetaData meta = null) { var result = ResourceFactoryManager.Load(ar); if (!result) - return result.Message; + return result.Error; return Play(invoker, result.Value, meta ?? new MetaData()); } /// Tries to play the passed link. @@ -83,18 +83,18 @@ public R Play(InvokerData invoker, string link, string audioType = null, MetaDat { var result = ResourceFactoryManager.Load(link, audioType); if (!result) - return result.Message; + return result.Error; return Play(invoker, result.Value, meta ?? new MetaData()); } public R Play(InvokerData invoker, uint historyId, MetaData meta = null) { var getresult = HistoryManager.GetEntryById(historyId); if (!getresult) - return getresult.Message; + return getresult.Error; var loadresult = ResourceFactoryManager.Load(getresult.Value.AudioResource); if (!loadresult) - return loadresult.Message; + return loadresult.Error; return Play(invoker, loadresult.Value, meta ?? new MetaData()); } @@ -124,7 +124,7 @@ public R Play(InvokerData invoker, PlaylistItem item) if (lastResult) return R.OkR; } - return $"Playlist item could not be played ({lastResult.Message})"; + return $"Playlist item could not be played ({lastResult.Error})"; } /// Plays the passed /// The invoker of this resource. Used for responses and association. @@ -167,8 +167,8 @@ private R StartResource(PlayResource playResource, MetaData config) var result = PlayerConnection.AudioStart(playResource.PlayUri); if (!result) { - Log.Write(Log.Level.Error, "Error return from player: {0}", result.Message); - return $"Internal player error ({result.Message})"; + Log.Write(Log.Level.Error, "Error return from player: {0}", result.Error); + return $"Internal player error ({result.Error})"; } PlayerConnection.Volume = config.Volume ?? AudioValues.DefaultVolume; @@ -220,7 +220,7 @@ private void StopInternal(bool songEndedByCallback) R result = Next(CurrentPlayData.Invoker); if (result) return; - Log.Write(Log.Level.Warning, nameof(SongStoppedHook) + " could not play Next: " + result.Message); + Log.Write(Log.Level.Warning, nameof(SongStoppedHook) + " could not play Next: " + result.Error); } else { diff --git a/TS3AudioBot/ResourceFactories/MediaFactory.cs b/TS3AudioBot/ResourceFactories/MediaFactory.cs index 6962de6f..86f82a5c 100644 --- a/TS3AudioBot/ResourceFactories/MediaFactory.cs +++ b/TS3AudioBot/ResourceFactories/MediaFactory.cs @@ -49,7 +49,7 @@ public R GetResourceById(AudioResource resource) if (!result) { - return result.Message; + return result.Error; } else { @@ -93,7 +93,7 @@ private static R ValidateWeb(Uri link) { var result = WebWrapper.GetResponseUnsafe(link); if (!result.Ok) - return result.Message; + return result.Error; using (var stream = result.Value) return new ResData(link.AbsoluteUri, GetStreamName(stream)); @@ -184,7 +184,7 @@ select result.Value into val using (var stream = status.Value) m3uResult = M3uReader.TryGetData(stream); else - return status.Message; + return status.Error; } if (m3uResult) @@ -214,7 +214,7 @@ public R GetThumbnail(PlayResource playResource) var uri = new Uri(playResource.PlayUri); var result = GetStreamFromUriUnsafe(uri); if (!result) - return result.Message; + return result.Error; using (var stream = result.Value) { diff --git a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs index da39070d..e187f4e3 100644 --- a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs +++ b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.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.Linq; - namespace TS3AudioBot.ResourceFactories { using CommandSystem; using Helper; using System; - using System.Drawing; using System.Collections.Generic; + using System.Drawing; + using System.Linq; using System.Reflection; public sealed class ResourceFactoryManager : Dependency.ICoreModule, IDisposable @@ -91,7 +90,7 @@ public R Load(AudioResource resource) var result = factory.GetResourceById(resource); if (!result) - return $"Could not load ({result.Message})"; + return $"Could not load ({result.Error})"; return result; } @@ -118,7 +117,7 @@ public R Load(string message, string audioType = null) var result = factory.GetResource(netlinkurl); if (!result) - return $"Could not load ({result.Message})"; + return $"Could not load ({result.Error})"; return result; } diff --git a/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs b/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs index 912e4694..77bdc6e0 100644 --- a/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs +++ b/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs @@ -9,13 +9,13 @@ namespace TS3AudioBot.ResourceFactories { + using Helper; using System; - using System.Linq; using System.Collections.Generic; + using System.Drawing; using System.Globalization; + using System.Linq; using System.Text.RegularExpressions; - using Helper; - using System.Drawing; public sealed class SoundcloudFactory : IResourceFactory, IPlaylistFactory, IThumbnailFactory { @@ -92,12 +92,12 @@ private R YoutubeDlWrapped(string link) var result = YoutubeDlHelper.FindAndRunYoutubeDl(link); if (!result.Ok) - return result.Message; + return result.Error; var response = result.Value; - string title = response.Item1; - string url = response.Item2.FirstOrDefault(); - if (response.Item2.Count == 0 || string.IsNullOrEmpty(title) || string.IsNullOrEmpty(url)) + string title = response.title; + string url = response.links.FirstOrDefault(); + if (response.links.Count == 0 || string.IsNullOrEmpty(title) || string.IsNullOrEmpty(url)) return "No youtube-dl response"; Log.Write(Log.Level.Debug, "SC Saved!"); diff --git a/TS3AudioBot/ResourceFactories/TwitchFactory.cs b/TS3AudioBot/ResourceFactories/TwitchFactory.cs index 633fa88a..b3fd9a66 100644 --- a/TS3AudioBot/ResourceFactories/TwitchFactory.cs +++ b/TS3AudioBot/ResourceFactories/TwitchFactory.cs @@ -9,11 +9,11 @@ namespace TS3AudioBot.ResourceFactories { + using Helper; using System; using System.Collections.Generic; using System.Globalization; using System.Text.RegularExpressions; - using Helper; public sealed class TwitchFactory : IResourceFactory { diff --git a/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs b/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs index 49fc7619..0c3ba39a 100644 --- a/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs +++ b/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs @@ -9,35 +9,34 @@ namespace TS3AudioBot.ResourceFactories { - using Helper; using System; using System.Collections.Generic; - using System.IO; - using System.Diagnostics; using System.ComponentModel; + using System.Diagnostics; + using System.IO; static class YoutubeDlHelper { public static YoutubeFactoryData DataObj { private get; set; } private static string YoutubeDlPath => DataObj?.YoutubedlPath; - public static R>> FindAndRunYoutubeDl(string id) + public static R<(string title, IList links)> FindAndRunYoutubeDl(string id) { var ytdlPath = FindYoutubeDl(id); if (ytdlPath == null) return "Youtube-Dl could not be found. The song/video cannot be played due to restrictions"; - return RunYoutubeDl(ytdlPath.Item1, ytdlPath.Item2); + return RunYoutubeDl(ytdlPath.Value.ytdlpath, ytdlPath.Value.param); } - public static Tuple FindYoutubeDl(string id) + public static (string ytdlpath, string param)? FindYoutubeDl(string id) { string param = $"--no-warnings --get-title --get-url --format bestaudio/best --id -- {id}"; // Default path youtube-dl is suggesting to install const string defaultYtDlPath = "/usr/local/bin/youtube-dl"; if (File.Exists(defaultYtDlPath)) - return new Tuple(defaultYtDlPath, param); + return (defaultYtDlPath, param); if (YoutubeDlPath == null) return null; @@ -52,22 +51,22 @@ public static Tuple FindYoutubeDl(string id) // Example: /home/teamspeak/youtube-dl where 'youtube-dl' is the binary if (File.Exists(fullCustomPath) || File.Exists(fullCustomPath + ".exe")) - return new Tuple(fullCustomPath, param); + return (fullCustomPath, param); // 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 new Tuple(fullCustomPathWithoutFile, param); + return (fullCustomPathWithoutFile, param); // 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 new Tuple("python", $"\"{fullCustomPathGhProject}\" {param}"); + return ("python", $"\"{fullCustomPathGhProject}\" {param}"); return null; } - public static R>> RunYoutubeDl(string path, string args) + public static R<(string title, IList links)> RunYoutubeDl(string path, string args) { try { @@ -98,7 +97,7 @@ public static R>> RunYoutubeDl(string path, string a catch (Win32Exception) { return "Failed to run youtube-dl"; } } - public static Tuple> ParseResponse(StreamReader stream) + public static (string title, IList links) ParseResponse(StreamReader stream) { string title = stream.ReadLine(); @@ -107,7 +106,7 @@ public static Tuple> ParseResponse(StreamReader stream) while ((line = stream.ReadLine()) != null) urlOptions.Add(line); - return new Tuple>(title, urlOptions); + return (title, urlOptions); } } } diff --git a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs index 9eb679d0..278497c9 100644 --- a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs +++ b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs @@ -13,18 +13,18 @@ namespace TS3AudioBot.ResourceFactories using System; using System.Collections.Generic; using System.Collections.Specialized; + using System.Drawing; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Web; - using System.Drawing; public sealed class YoutubeFactory : IResourceFactory, IPlaylistFactory, IThumbnailFactory { - private static readonly Regex IdMatch = new Regex(@"((&|\?)v=|youtu\.be\/)([a-zA-Z0-9\-_]+)", Util.DefaultRegexConfig); + private static readonly Regex IdMatch = new Regex(@"((&|\?)v=|youtu\.be\/)([\w\-_]+)", Util.DefaultRegexConfig); private static readonly Regex LinkMatch = new Regex(@"^(https?\:\/\/)?(www\.|m\.)?(youtube\.|youtu\.be)", Util.DefaultRegexConfig); - private static readonly Regex ListMatch = new Regex(@"(&|\?)list=([\w-]+)", Util.DefaultRegexConfig); + private static readonly Regex ListMatch = new Regex(@"(&|\?)list=([\w\-_]+)", Util.DefaultRegexConfig); private readonly YoutubeFactoryData data; @@ -141,7 +141,7 @@ public R GetResourceById(AudioResource resource) if (!result) { if (string.IsNullOrWhiteSpace(data.YoutubedlPath)) - return result.Message; + return result.Error; return YoutubeDlWrapped(resource); } @@ -315,11 +315,11 @@ private static R YoutubeDlWrapped(AudioResource resource) var result = YoutubeDlHelper.FindAndRunYoutubeDl(resource.ResourceId); if (!result.Ok) - return result.Message; + return result.Error; var response = result.Value; - var title = response.Item1; - var urlOptions = response.Item2; + var title = response.title; + var urlOptions = response.links; string url = null; if (urlOptions.Count == 1) diff --git a/TS3AudioBot/Sessions/UserSession.cs b/TS3AudioBot/Sessions/UserSession.cs index f4814fa0..8f04db10 100644 --- a/TS3AudioBot/Sessions/UserSession.cs +++ b/TS3AudioBot/Sessions/UserSession.cs @@ -40,31 +40,24 @@ public void Write(string message, TextMessageTargetMode targetMode) { VerifyLock(); - try + R result; + switch (targetMode) { - R result; - switch (targetMode) - { - case TextMessageTargetMode.Private: - result = Bot.QueryConnection.SendMessage(message, client.ClientId); - break; - case TextMessageTargetMode.Channel: - result = Bot.QueryConnection.SendChannelMessage(message); - break; - case TextMessageTargetMode.Server: - result = Bot.QueryConnection.SendServerMessage(message); - break; - default: - throw new InvalidOperationException(); - } - - if (!result) - Log.Write(Log.Level.Error, "Could not write message (Err:{0}) (Msg:{1})", result.Message, message); - } - catch (Ts3CommandException ex) - { - Log.Write(Log.Level.Error, "Could not write message (Ex:{0}) (Msg:{1})", ex.UnrollException(), message); + case TextMessageTargetMode.Private: + result = Bot.QueryConnection.SendMessage(message, client.ClientId); + break; + case TextMessageTargetMode.Channel: + result = Bot.QueryConnection.SendChannelMessage(message); + break; + case TextMessageTargetMode.Server: + result = Bot.QueryConnection.SendServerMessage(message); + break; + default: + throw Util.UnhandledDefault(targetMode); } + + if (!result) + Log.Write(Log.Level.Error, "Could not write message (Err:{0}) (Msg:{1})", result.Error, message); } public void SetResponse(Response responseProcessor, object responseData) diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index dbcb530a..410f6629 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -70,6 +70,9 @@ + + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll + @@ -134,7 +137,6 @@ - diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index 9ab15f41..094a0693 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -9,6 +9,7 @@ namespace TS3AudioBot { + using RExtensions; using Helper; using System; using System.Collections.Generic; @@ -101,28 +102,25 @@ public R SendMessage(string message, ushort clientId) { if (Ts3String.TokenLength(message) > Ts3String.MaxMsgLength) return "The message to send is longer than the maximum of " + Ts3String.MaxMsgLength + " characters"; - try { tsBaseClient.SendPrivateMessage(message, clientId); return R.OkR; } - catch (Ts3CommandException ex) { return ex.ErrorStatus.ErrorFormat(); } + return tsBaseClient.SendPrivateMessage(message, clientId).ToR(Extensions.ErrorFormat); } public R SendChannelMessage(string message) { if (Ts3String.TokenLength(message) > Ts3String.MaxMsgLength) return "The message to send is longer than the maximum of " + Ts3String.MaxMsgLength + " characters"; - try { tsBaseClient.SendChannelMessage(message); return R.OkR; } - catch (Ts3CommandException ex) { return ex.ErrorStatus.ErrorFormat(); } + return tsBaseClient.SendChannelMessage(message).ToR(Extensions.ErrorFormat); } public R SendServerMessage(string message) { if (Ts3String.TokenLength(message) > Ts3String.MaxMsgLength) return "The message to send is longer than the maximum of " + Ts3String.MaxMsgLength + " characters"; - try { tsBaseClient.SendServerMessage(message, 1); return R.OkR; } - catch (Ts3CommandException ex) { return ex.ErrorStatus.ErrorFormat(); } + return tsBaseClient.SendServerMessage(message, 1).ToR(Extensions.ErrorFormat); } - public void KickClientFromServer(ushort clientId) => tsBaseClient.KickClientFromServer(new[] { clientId }); - public void KickClientFromChannel(ushort clientId) => tsBaseClient.KickClientFromChannel(new[] { clientId }); + public R KickClientFromServer(ushort clientId) => tsBaseClient.KickClientFromServer(new[] { clientId }).ToR(Extensions.ErrorFormat); + public R KickClientFromChannel(ushort clientId) => tsBaseClient.KickClientFromChannel(new[] { clientId }).ToR(Extensions.ErrorFormat); public R ChangeDescription(string description) { @@ -130,30 +128,21 @@ public R ChangeDescription(string description) if (!me.Ok) return "Internal error (me==null)"; - try { tsBaseClient.ChangeDescription(description, me.Value.ClientId); return R.OkR; } - catch (Ts3CommandException ex) { return ex.ErrorStatus.ErrorFormat(); } + return tsBaseClient.ChangeDescription(description, me.Value.ClientId).ToR(Extensions.ErrorFormat); } - public R ChangeBadges(string badgesString) - { - try { tsBaseClient.ChangeBadges(badgesString); return R.OkR; } - catch (Ts3CommandException ex) { return ex.ErrorStatus.ErrorFormat(); } - } + public R ChangeBadges(string badgesString) => tsBaseClient.ChangeBadges(badgesString).ToR(Extensions.ErrorFormat); public R ChangeName(string name) { - try - { - tsBaseClient.ChangeName(name); + var result = tsBaseClient.ChangeName(name); + if (result.Ok) return R.OkR; - } - catch (Ts3CommandException ex) - { - if (ex.ErrorStatus.Id == Ts3ErrorCode.parameter_invalid_size) - return "The new name is too long or invalid"; - else - return ex.ErrorStatus.ErrorFormat(); - } + + if (result.Error.Id == Ts3ErrorCode.parameter_invalid_size) + return "The new name is too long or invalid"; + else + return result.Error.ErrorFormat(); } public R GetClientById(ushort id) @@ -161,33 +150,32 @@ public R GetClientById(ushort id) var result = ClientBufferRequest(client => client.ClientId == id); if (result.Ok) return result; Log.Write(Log.Level.Debug, "Slow double request, due to missing or wrong permission confinguration!"); - ClientData cd; - try { cd = tsBaseClient.Send("clientinfo", new CommandParameter("clid", id)).FirstOrDefault(); } - catch (Ts3CommandException) { cd = null; } - if (cd == null) + var result2 = tsBaseClient.Send("clientinfo", new CommandParameter("clid", id)).WrapSingle(); + if (!result2.Ok) return "No client found"; + ClientData cd = result2.Value; cd.ClientId = id; clientbuffer.Add(cd); - return R.OkR(cd); + return cd; } public R GetClientByName(string name) { var refreshResult = RefreshClientBuffer(false); if (!refreshResult) - return refreshResult.Message; + return refreshResult.Error; var clients = CommandSystem.XCommandSystem.FilterList( clientbuffer.Select(cb => new KeyValuePair(cb.NickName, cb)), name).ToArray(); if (clients.Length <= 0) return "No client found"; - return R.OkR(clients[0].Value); + return clients[0].Value; } private R ClientBufferRequest(Func pred) { var refreshResult = RefreshClientBuffer(false); if (!refreshResult) - return refreshResult.Message; + return refreshResult.Error; var clientData = clientbuffer.FirstOrDefault(pred); if (clientData == null) return "No client found"; @@ -200,8 +188,10 @@ public R RefreshClientBuffer(bool force) { if (clientbufferOutdated || force) { - try { clientbuffer = tsBaseClient.ClientList(ClientListOptions.uid).ToList(); } - catch (Ts3CommandException ex) { return "Clientlist failed: " + ex.Message; } + var result = tsBaseClient.ClientList(ClientListOptions.uid); + if (!result) + return $"Clientlist failed ({result.Error.ErrorFormat()})"; + clientbuffer = result.Value.ToList(); clientbufferOutdated = false; } return R.OkR; @@ -209,8 +199,10 @@ public R RefreshClientBuffer(bool force) public R GetClientServerGroups(ulong dbId) { - try { return tsBaseClient.ServerGroupsByClientDbId(dbId).Select(csg => csg.ServerGroupId).ToArray(); } - catch (Ts3CommandException) { return "No client found."; } + var result = tsBaseClient.ServerGroupsByClientDbId(dbId); + if (!result.Ok) + return "No client found."; + return result.Value.Select(csg => csg.ServerGroupId).ToArray(); } public R GetDbClientByDbId(ulong clientDbId) @@ -218,174 +210,156 @@ public R GetDbClientByDbId(ulong clientDbId) if (clientDbNames.TryGetValue(clientDbId, out var clientData)) return clientData; - try - { - clientData = tsBaseClient.ClientDbInfo(clientDbId); - clientDbNames.Store(clientDbId, clientData); - return clientData; - } - catch (Ts3CommandException) { return "No client found."; } + var result = tsBaseClient.ClientDbInfo(clientDbId); + if (!result.Ok) + return "No client found."; + clientData = result.Value; + clientDbNames.Store(clientDbId, clientData); + return clientData; } - public R GetClientInfoById(ushort id) - { - try { return tsBaseClient.ClientInfo(id); } - catch (Ts3CommandException) { return "No client found."; } - } + public R GetClientInfoById(ushort id) => tsBaseClient.ClientInfo(id).ToR("No client found."); internal R SetupRights(string key, MainBotData mainBotData) { - try - { - var me = GetSelf(); - if (!me.Ok) - return me.Message; + var me = GetSelf(); + if (!me.Ok) + return me.Error; - // Check all own server groups - var result = GetClientServerGroups(me.Value.DatabaseId); - var groups = result.Ok ? result.Value : new ulong[0]; + // Check all own server groups + var result = GetClientServerGroups(me.Value.DatabaseId); + var groups = result.Ok ? result.Value : new ulong[0]; - // Add self to master group (via token) - if (!string.IsNullOrEmpty(key)) - tsBaseClient.PrivilegeKeyUse(key); + // Add self to master group (via token) + if (!string.IsNullOrEmpty(key)) + tsBaseClient.PrivilegeKeyUse(key); - // Remember new group (or check if in new group at all) - if (result.Ok) - result = GetClientServerGroups(me.Value.DatabaseId); - var groupsNew = result.Ok ? result.Value : new ulong[0]; - var groupDiff = groupsNew.Except(groups).ToArray(); + // Remember new group (or check if in new group at all) + if (result.Ok) + result = GetClientServerGroups(me.Value.DatabaseId); + var groupsNew = result.Ok ? result.Value : new ulong[0]; + var groupDiff = groupsNew.Except(groups).ToArray(); - if (mainBotData.BotGroupId == 0) + if (mainBotData.BotGroupId == 0) + { + // Create new Bot group + var botGroup = tsBaseClient.ServerGroupAdd("ServerBot"); + if (botGroup.Ok) { - // Create new Bot group - var botGroup = tsBaseClient.ServerGroupAdd("ServerBot"); - mainBotData.BotGroupId = botGroup.ServerGroupId; + mainBotData.BotGroupId = botGroup.Value.ServerGroupId; // Add self to new group - tsBaseClient.ServerGroupAddClient(botGroup.ServerGroupId, me.Value.DatabaseId); - } - - const int max = 75; - const int ava = 500000; // max size in bytes for the avatar - - // Add various rights to the bot group - tsBaseClient.ServerGroupAddPerm(mainBotData.BotGroupId, - new[] { - PermissionId.i_client_whisper_power, // + Required for whisper channel playing - PermissionId.i_client_private_textmessage_power, // + Communication - PermissionId.b_client_server_textmessage_send, // + Communication - PermissionId.b_client_channel_textmessage_send, // + Communication, could be used but not yet - - PermissionId.b_client_modify_dbproperties, // ? Dont know but seems also required for the next one - PermissionId.b_client_modify_description, // + Used to change the description of our bot - PermissionId.b_client_info_view, // (+) only used as fallback usually - PermissionId.b_virtualserver_client_list, // ? Dont know but seems also required for the next one - - PermissionId.i_channel_subscribe_power, // + Required to find user to communicate - PermissionId.b_virtualserver_client_dbinfo, // + Required to get basic user information for history, api, etc... - PermissionId.i_client_talk_power, // + Required for normal channel playing - PermissionId.b_client_modify_own_description, // ? not sure if this makes b_client_modify_description superfluous - - PermissionId.b_group_is_permanent, // + Group should stay even if bot disconnects - PermissionId.i_client_kick_from_channel_power, // + Optional for kicking - PermissionId.i_client_kick_from_server_power, // + Optional for kicking - PermissionId.i_client_max_clones_uid, // + In case that bot times out and tries to join again - - PermissionId.b_client_ignore_antiflood, // + The bot should be resistent to forced spam attacks - PermissionId.b_channel_join_ignore_password, // + The noble bot will not abuse this power - PermissionId.b_channel_join_permanent, // + Allow joining to all channel even on strict servers - PermissionId.b_channel_join_semi_permanent, // + Allow joining to all channel even on strict servers - - PermissionId.b_channel_join_temporary, // + Allow joining to all channel even on strict servers - PermissionId.b_channel_join_ignore_maxclients, // + Allow joining full channels - PermissionId.i_channel_join_power, // + Allow joining to all channel even on strict servers - PermissionId.b_client_permissionoverview_view, // + Scanning though given perms for rights system - - PermissionId.i_client_max_avatar_filesize, // + Uploading thumbnails as avatar - PermissionId.b_client_use_channel_commander, // + Enable channel commander - }, - new[] { - max, max, 1, 1, - 1, 1, 1, 1, - max, 1, max, 1, - 1, max, max, 4, - 1, 1, 1, 1, - 1, 1, max, 1, - ava, 1, - }, - new[] { - false, false, false, false, - false, false, false, false, - false, false, false, false, - false, false, false, false, - false, false, false, false, - false, false, false, false, - false, false, - }, - new[] { - false, false, false, false, - false, false, false, false, - false, false, false, false, - false, false, false, false, - false, false, false, false, - false, false, false, false, - false, false, - }); - - // Leave master group again - if (groupDiff.Length > 0) - { - foreach (var grp in groupDiff) - tsBaseClient.ServerGroupDelClient(grp, me.Value.DatabaseId); + tsBaseClient.ServerGroupAddClient(botGroup.Value.ServerGroupId, me.Value.DatabaseId); } + } - return R.OkR; + const int max = 75; + const int ava = 500000; // max size in bytes for the avatar + + // Add various rights to the bot group + var permresult = tsBaseClient.ServerGroupAddPerm(mainBotData.BotGroupId, + new[] { + PermissionId.i_client_whisper_power, // + Required for whisper channel playing + PermissionId.i_client_private_textmessage_power, // + Communication + PermissionId.b_client_server_textmessage_send, // + Communication + PermissionId.b_client_channel_textmessage_send, // + Communication, could be used but not yet + + PermissionId.b_client_modify_dbproperties, // ? Dont know but seems also required for the next one + PermissionId.b_client_modify_description, // + Used to change the description of our bot + PermissionId.b_client_info_view, // (+) only used as fallback usually + PermissionId.b_virtualserver_client_list, // ? Dont know but seems also required for the next one + + PermissionId.i_channel_subscribe_power, // + Required to find user to communicate + PermissionId.b_virtualserver_client_dbinfo, // + Required to get basic user information for history, api, etc... + PermissionId.i_client_talk_power, // + Required for normal channel playing + PermissionId.b_client_modify_own_description, // ? not sure if this makes b_client_modify_description superfluous + + PermissionId.b_group_is_permanent, // + Group should stay even if bot disconnects + PermissionId.i_client_kick_from_channel_power, // + Optional for kicking + PermissionId.i_client_kick_from_server_power, // + Optional for kicking + PermissionId.i_client_max_clones_uid, // + In case that bot times out and tries to join again + + PermissionId.b_client_ignore_antiflood, // + The bot should be resistent to forced spam attacks + PermissionId.b_channel_join_ignore_password, // + The noble bot will not abuse this power + PermissionId.b_channel_join_permanent, // + Allow joining to all channel even on strict servers + PermissionId.b_channel_join_semi_permanent, // + Allow joining to all channel even on strict servers + + PermissionId.b_channel_join_temporary, // + Allow joining to all channel even on strict servers + PermissionId.b_channel_join_ignore_maxclients, // + Allow joining full channels + PermissionId.i_channel_join_power, // + Allow joining to all channel even on strict servers + PermissionId.b_client_permissionoverview_view, // + Scanning though given perms for rights system + + PermissionId.i_client_max_avatar_filesize, // + Uploading thumbnails as avatar + PermissionId.b_client_use_channel_commander, // + Enable channel commander + }, + new[] { + max, max, 1, 1, + 1, 1, 1, 1, + max, 1, max, 1, + 1, max, max, 4, + 1, 1, 1, 1, + 1, 1, max, 1, + ava, 1, + }, + new[] { + false, false, false, false, + false, false, false, false, + false, false, false, false, + false, false, false, false, + false, false, false, false, + false, false, false, false, + false, false, + }, + new[] { + false, false, false, false, + false, false, false, false, + false, false, false, false, + false, false, false, false, + false, false, false, false, + false, false, false, false, + false, false, + }); + + // Leave master group again + if (groupDiff.Length > 0) + { + foreach (var grp in groupDiff) + tsBaseClient.ServerGroupDelClient(grp, me.Value.DatabaseId); } - catch (Ts3CommandException cex) + + if (!result) { - Log.Write(Log.Level.Warning, cex.ErrorStatus.ErrorFormat()); + Log.Write(Log.Level.Warning, permresult.Error.ErrorFormat()); return "Auto setup failed! (See logs for more details)"; } - } - public R UploadAvatar(System.IO.Stream stream) - { - try { tsBaseClient.UploadAvatar(stream); return R.OkR; } - catch (Ts3CommandException ts3ex) { return ts3ex.ErrorStatus.ErrorFormat(); } + return R.OkR; } + public R UploadAvatar(System.IO.Stream stream) => tsBaseClient.UploadAvatar(stream).ToR(Extensions.ErrorFormat); + public R MoveTo(ulong channelId, string password = null) { - try - { - var me = GetSelf(); - if (!me.Ok) - return me.Message; - tsBaseClient.ClientMove(me.Value.ClientId, channelId, password); - return R.OkR; - } - catch (Ts3CommandException) { return "Cannot move there."; } + var me = GetSelf(); + if (!me.Ok) + return me.Error; + return tsBaseClient.ClientMove(me.Value.ClientId, channelId, password).ToR("Cannot move there."); } public R SetChannelCommander(bool isCommander) { if (!(tsBaseClient is Ts3FullClient tsFullClient)) return "Commander mode not available"; - try - { - tsFullClient.ChangeIsChannelCommander(isCommander); - return R.OkR; - } - catch (Ts3CommandException) { return "Cannot set commander mode"; } + return tsFullClient.ChangeIsChannelCommander(isCommander).ToR("Cannot set commander mode"); } public R IsChannelCommander() { var me = GetSelf(); if (!me.Ok) - return me.Message; + return me.Error; var getInfoResult = GetClientInfoById(me.Value.ClientId); if (!getInfoResult.Ok) - return getInfoResult.Message; + return getInfoResult.Error; return getInfoResult.Value.IsChannelCommander; } @@ -399,4 +373,42 @@ public void Dispose() } } } + + namespace RExtensions + { + static class RExtentions + { + public static R ToR(this E result, string message) + { + if (!result.Ok) + return R.Err(message); + else + return R.OkR; + } + + public static R ToR(this E result, Func fromError) + { + if (!result.Ok) + return R.Err(fromError(result.Error)); + else + return R.OkR; + } + + public static R ToR(this R result, string message) + { + if (!result.Ok) + return R.Err(message); + else + return R.OkR(result.Value); + } + + public static R ToR(this R result, Func fromError) + { + if (!result.Ok) + return R.Err(fromError(result.Error)); + else + return R.OkR(result.Value); + } + } + } } diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index e8d51621..e75b1cfa 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -173,7 +173,7 @@ private void ConnectClient() verionSign = (VersionSign)signType.GetValue(null); } - if(verionSign == null) + if (verionSign == null) { Log.Write(Log.Level.Warning, "Invalid version sign, falling back to unknown :P"); verionSign = VersionSign.VER_WIN_3_UNKNOWN; @@ -233,31 +233,27 @@ public override R GetSelf() if (self != null) return self; - try + var result = tsBaseClient.WhoAmI(); + if (!result.Ok) + return $"Could not get self ({result.Error.ErrorFormat()})"; + var data = result.Value; + var cd = new ClientData { - var data = tsBaseClient.WhoAmI(); - var cd = new ClientData - { - Uid = identity.ClientUid, - ChannelId = data.ChannelId, - ClientId = tsFullClient.ClientId, - NickName = data.NickName, - ClientType = tsBaseClient.ClientType - }; - - var response = tsBaseClient - .Send("clientgetdbidfromuid", new TS3Client.Commands.CommandParameter("cluid", identity.ClientUid)) - .FirstOrDefault(); - if (response != null && ulong.TryParse(response["cldbid"], out var dbId)) - cd.DatabaseId = dbId; - - self = cd; - return cd; - } - catch (Ts3CommandException) - { - return "Could not get self"; - } + Uid = identity.ClientUid, + ChannelId = data.ChannelId, + ClientId = tsFullClient.ClientId, + NickName = data.NickName, + ClientType = tsBaseClient.ClientType + }; + + var response = tsBaseClient + .Send("clientgetdbidfromuid", new TS3Client.Commands.CommandParameter("cluid", identity.ClientUid)) + .WrapSingle(); + if (response.Ok && ulong.TryParse(response.Value["cldbid"], out var dbId)) + cd.DatabaseId = dbId; + + self = cd; + return cd; } private void AudioSend() diff --git a/TS3AudioBot/packages.config b/TS3AudioBot/packages.config index e4c51842..d38e88d0 100644 --- a/TS3AudioBot/packages.config +++ b/TS3AudioBot/packages.config @@ -5,4 +5,5 @@ + \ No newline at end of file diff --git a/TS3Client/FileTransferManager.cs b/TS3Client/FileTransferManager.cs index 419915ae..b73bbc95 100644 --- a/TS3Client/FileTransferManager.cs +++ b/TS3Client/FileTransferManager.cs @@ -49,7 +49,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 FileTransferToken UploadFile(FileInfo file, ChannelIdT channel, string path, bool overwrite = false, string channelPassword = "") + public R UploadFile(FileInfo file, ChannelIdT 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. @@ -61,13 +61,13 @@ public FileTransferToken UploadFile(FileInfo file, ChannelIdT channel, string pa /// The password for the channel. /// True will the stream after the upload is finished. /// A token to track the file transfer. - public FileTransferToken UploadFile(Stream stream, ChannelIdT channel, string path, bool overwrite = false, string channelPassword = "", bool closeStream = false) + public R UploadFile(Stream stream, ChannelIdT channel, string path, bool overwrite = false, string channelPassword = "", bool closeStream = false) { ushort cftid = GetFreeTransferId(); var request = parent.FileTransferInitUpload(channel, path, channelPassword, cftid, stream.Length, overwrite, false); - if (!string.IsNullOrEmpty(request.Message)) - throw new Ts3Exception(request.Message); - var token = new FileTransferToken(stream, request, channel, path, channelPassword, stream.Length) { CloseStreamWhenDone = closeStream }; + if (!request.Ok) + return request.Error; + var token = new FileTransferToken(stream, request.Value, channel, path, channelPassword, stream.Length) { CloseStreamWhenDone = closeStream }; StartWorker(token); return token; } @@ -78,7 +78,7 @@ public FileTransferToken UploadFile(Stream stream, ChannelIdT channel, string pa /// 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 FileTransferToken DownloadFile(FileInfo file, ChannelIdT channel, string path, string channelPassword = "") + public R DownloadFile(FileInfo file, ChannelIdT channel, string path, string channelPassword = "") => DownloadFile(file.Open(FileMode.Create, FileAccess.Write), channel, path, channelPassword, true); /// Initiate a file download from the server. @@ -88,13 +88,13 @@ public FileTransferToken DownloadFile(FileInfo file, ChannelIdT channel, string /// The password for the channel. /// True will the stream after the download is finished. /// A token to track the file transfer. - public FileTransferToken DownloadFile(Stream stream, ChannelIdT channel, string path, string channelPassword = "", bool closeStream = false) + public R DownloadFile(Stream stream, ChannelIdT channel, string path, string channelPassword = "", bool closeStream = false) { ushort cftid = GetFreeTransferId(); var request = parent.FileTransferInitDownload(channel, path, channelPassword, cftid, 0); - if (!string.IsNullOrEmpty(request.Message)) - throw new Ts3Exception(request.Message); - var token = new FileTransferToken(stream, request, channel, path, channelPassword, 0) { CloseStreamWhenDone = closeStream }; + if (!request.Ok) + return request.Error; + var token = new FileTransferToken(stream, request.Value, channel, path, channelPassword, 0) { CloseStreamWhenDone = closeStream }; StartWorker(token); return token; } @@ -121,18 +121,19 @@ private ushort GetFreeTransferId() /// Resumes a download from a previously stopped position. /// The aborted token. - public void Resume(FileTransferToken token) + public E Resume(FileTransferToken token) { lock (token) { if (token.Status != TransferStatus.Cancelled) - throw new Ts3Exception("Only cancelled transfers can be resumed"); + return Util.CustomError("Only cancelled transfers can be resumed"); if (token.Direction == TransferDirection.Upload) { - var request = parent.FileTransferInitUpload(token.ChannelId, token.Path, token.ChannelPassword, token.ClientTransferId, token.Size, false, true); - if (!string.IsNullOrEmpty(request.Message)) - throw new Ts3Exception(request.Message); + var result = parent.FileTransferInitUpload(token.ChannelId, token.Path, token.ChannelPassword, token.ClientTransferId, token.Size, false, true); + if (!result.Ok) + return result.Error; + var request = result.Value; token.ServerTransferId = request.ServerFileTransferId; token.SeekPosition = request.SeekPosistion; token.Port = request.Port; @@ -140,9 +141,10 @@ public void Resume(FileTransferToken token) } else // Download { - var request = parent.FileTransferInitDownload(token.ChannelId, token.Path, token.ChannelPassword, token.ClientTransferId, token.LocalStream.Position); - if (!string.IsNullOrEmpty(request.Message)) - throw new Ts3Exception(request.Message); + var result = parent.FileTransferInitDownload(token.ChannelId, token.Path, token.ChannelPassword, token.ClientTransferId, token.LocalStream.Position); + if (!result.Ok) + return result.Error; + var request = result.Value; token.ServerTransferId = request.ServerFileTransferId; token.SeekPosition = -1; token.Port = request.Port; @@ -151,6 +153,7 @@ public void Resume(FileTransferToken token) token.Status = TransferStatus.Waiting; } StartWorker(token); + return E.OkR; } /// Stops an active transfer. @@ -175,16 +178,17 @@ public void Abort(FileTransferToken token, bool delete = false) /// Gets information about the current transfer status. /// The transfer to check. /// Returns an information object or null when not available. - public FileTransfer GetStats(FileTransferToken token) + public R GetStats(FileTransferToken token) { lock (token) { if (token.Status != TransferStatus.Trasfering) - return null; + return Util.CustomError("No transfer found"); } - try { return parent.FileTransferList().FirstOrDefault(x => x.ServerFileTransferId == token.ServerTransferId); } - // catch case when transfer is not found (probably already over or not yet started) - catch (Ts3CommandException ts3ex) when (ts3ex.ErrorStatus.Id == Ts3ErrorCode.database_empty_result) { return null; } + var result = parent.FileTransferList(); + if (result.Ok) + return result.Value.Where(x => x.ServerFileTransferId == token.ServerTransferId).WrapSingle(); + return R.Err(result.Error); } private void TransferLoop() diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index c328f668..22e8fdeb 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -16,6 +16,8 @@ namespace TS3Client.Full using System.Linq; using System.Threading; + using CmdR = E; + using ClientUidT = System.String; using ClientDbIdT = System.UInt64; using ClientIdT = System.UInt16; @@ -190,7 +192,7 @@ private void InvokeEvent(LazyNotification lazyNotification) case NotificationType.InitIvExpand: ProcessInitIvExpand((InitIvExpand)notification.FirstOrDefault()); break; case NotificationType.InitServer: ProcessInitServer((InitServer)notification.FirstOrDefault()); break; case NotificationType.ChannelList: break; - case NotificationType.ChannelListFinished: try { ChannelSubscribeAll(); } catch (Ts3CommandException) { } break; + case NotificationType.ChannelListFinished: ChannelSubscribeAll(); break; case NotificationType.ClientNeededPermissions: break; case NotificationType.ClientChannelGroupChanged: break; case NotificationType.ClientServerGroupAdded: break; @@ -289,17 +291,13 @@ private void ProcessInitIvExpand(InitIvExpand initIvExpand) ts3Crypt.CryptoInit(initIvExpand.Alpha, initIvExpand.Beta, initIvExpand.Omega); packetHandler.CryptoInitDone(); - try - { - ClientInit( - connectionDataFull.Username, - true, true, - connectionDataFull.DefaultChannel, - Ts3Crypt.HashPassword(connectionDataFull.DefaultChannelPassword), - password, string.Empty, string.Empty, string.Empty, - "123,456", VersionSign); - } - catch (Ts3CommandException) { } + ClientInit( + connectionDataFull.Username, + true, true, + connectionDataFull.DefaultChannel, + Ts3Crypt.HashPassword(connectionDataFull.DefaultChannelPassword), + password, string.Empty, string.Empty, string.Empty, + "123,456", VersionSign); } private void ProcessInitServer(InitServer initServer) @@ -328,36 +326,42 @@ private void ProcessConnectionInfoRequest() /// Returns an enumeration of the deserialized and split up in objects data. /// Or null if no reponse is expected. /// When the response has an error code. - public override IEnumerable SendCommand(Ts3Command com) + public override R, CommandError> SendCommand(Ts3Command com) { using (var wb = new WaitBlock()) { - SendCommandBase(wb, com); + var result = SendCommandBase(wb, com); + if (!result.Ok) + return result.Error; if (com.ExpectResponse) return wb.WaitForMessage(); else - return null; + // 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 LazyNotification SendSpecialCommand(Ts3Command com, params NotificationType[] dependsOn) + public R SendNotifyCommand(Ts3Command com, params NotificationType[] dependsOn) { if (!com.ExpectResponse) throw new ArgumentException("A special command must take a response"); using (var wb = new WaitBlock(dependsOn)) { - SendCommandBase(wb, com); + var result = SendCommandBase(wb, com); + if (!result.Ok) + return result.Error; return wb.WaitForNotification(); } } - private void SendCommandBase(WaitBlock wb, Ts3Command com) + private E SendCommandBase(WaitBlock wb, Ts3Command com) { lock (statusLock) { if (context.WasExit || (!Connected && com.ExpectResponse)) - throw new Ts3CommandException(Util.TimeOutCommandError); + return Util.TimeOutCommandError; if (com.ExpectResponse) { @@ -372,6 +376,7 @@ private void SendCommandBase(WaitBlock wb, Ts3Command com) byte[] data = Util.Encoder.GetBytes(message); packetHandler.AddOutgoingPacket(data, PacketType.Command); } + return E.OkR; } /// Release all resources. Will try to disconnect before disposing. @@ -383,11 +388,11 @@ public override void Dispose() #region FULLCLIENT SPECIFIC COMMANDS - public void ChangeIsChannelCommander(bool isChannelCommander) + public CmdR ChangeIsChannelCommander(bool isChannelCommander) => Send("clientupdate", new CommandParameter("client_is_channel_commander", isChannelCommander)); - public void ClientInit(string nickname, bool inputHardware, bool outputHardware, + 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( @@ -407,16 +412,16 @@ public void ClientInit(string nickname, bool inputHardware, bool outputHardware, new CommandParameter("client_default_token", defaultToken), new CommandParameter("hwid", hwid) })); - public void ClientDisconnect(MoveReason reason, string reasonMsg) + public CmdR ClientDisconnect(MoveReason reason, string reasonMsg) => SendNoResponsed( new Ts3Command("clientdisconnect", new List() { new CommandParameter("reasonid", (int)reason), new CommandParameter("reasonmsg", reasonMsg) })); - public void ChannelSubscribeAll() + public CmdR ChannelSubscribeAll() => Send("channelsubscribeall"); - public void ChannelUnsubscribeAll() + public CmdR ChannelUnsubscribeAll() => Send("channelunsubscribeall"); public void SendAudio(byte[] buffer, int length, Codec codec) @@ -478,15 +483,17 @@ public void SendAudioGroupWhisper(byte[] buffer, int length, Codec codec, GroupW packetHandler.AddOutgoingPacket(buffer, PacketType.VoiceWhisper, PacketFlags.Newprotocol); } - public ConnectionInfo GetClientConnectionInfo(ClientIdT clientId) + public R GetClientConnectionInfo(ClientIdT clientId) { - return SendSpecialCommand( - new Ts3Command("getconnectioninfo", - new List { new CommandParameter("clid", clientId) }), - NotificationType.ConnectionInfo) - .Notifications + var result = SendNotifyCommand(new Ts3Command("getconnectioninfo", new List { + new CommandParameter("clid", clientId) }), + NotificationType.ConnectionInfo); + if (!result.Ok) + return result.Error; + return result.Value.Notifications .Cast() - .FirstOrDefault(x => x.ClientId == clientId); + .Where(x => x.ClientId == clientId) + .WrapSingle(); } // serverrequestconnectioninfo @@ -494,88 +501,100 @@ public ConnectionInfo GetClientConnectionInfo(ClientIdT clientId) // Splitted base commands - public override ServerGroupAddResponse ServerGroupAdd(string name, PermissionGroupDatabaseType? type = null) + public override R ServerGroupAdd(string name, PermissionGroupDatabaseType? type = null) { var cmd = new Ts3Command("servergroupadd", new List { new CommandParameter("name", name) }); if (type.HasValue) cmd.AppendParameter(new CommandParameter("type", (int)type.Value)); - var answer = SendSpecialCommand(cmd, NotificationType.ServerGroupList).Notifications + var result = SendNotifyCommand(cmd, NotificationType.ServerGroupList); + if (!result.Ok) + return result.Error; + return result.Value.Notifications .Cast() - .FirstOrDefault(x => x.Name == name); - if (answer == null) - throw new Ts3CommandException(new CommandError() { Id = Ts3ErrorCode.custom_error, Message = "Missing answer" }); - else - return new ServerGroupAddResponse() { ServerGroupId = answer.ServerGroupId }; + .Where(x => x.Name == name) + .Take(1) + .Select(x => new ServerGroupAddResponse() { ServerGroupId = x.ServerGroupId }) + .WrapSingle(); } - public override IEnumerable ServerGroupsByClientDbId(ClientDbIdT clDbId) + public override R, CommandError> ServerGroupsByClientDbId(ClientDbIdT clDbId) { - return SendSpecialCommand( - new Ts3Command("servergroupsbyclientid", - new List { new CommandParameter("cldbid", clDbId) }), - NotificationType.ServerGroupsByClientId) - .Notifications + var result = SendNotifyCommand(new Ts3Command("servergroupsbyclientid", new List { + new CommandParameter("cldbid", clDbId) }), + NotificationType.ServerGroupsByClientId); + if (!result.Ok) + return result.Error; + + return R, CommandError>.OkR( + result.Value.Notifications .Cast() - .Where(x => x.ClientDbId == clDbId); + .Where(x => x.ClientDbId == clDbId)); } - public override FileUpload FileTransferInitUpload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, + public override R FileTransferInitUpload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, long fileSize, bool overwrite, bool resume) { - var lazyNot = SendSpecialCommand(new Ts3Command("ftinitupload", new List() { - new CommandParameter("cid", channelId), - new CommandParameter("name", path), - new CommandParameter("cpw", channelPassword), - new CommandParameter("clientftfid", clientTransferId), - new CommandParameter("size", fileSize), - new CommandParameter("overwrite", overwrite), - new CommandParameter("resume", resume) }), NotificationType.StartUpload, NotificationType.FileTransferStatus); - if (lazyNot.NotifyType == NotificationType.StartUpload) - return lazyNot.Notifications.Cast().First(); + var result = SendNotifyCommand(new Ts3Command("ftinitupload", new List() { + new CommandParameter("cid", channelId), + new CommandParameter("name", path), + new CommandParameter("cpw", channelPassword), + new CommandParameter("clientftfid", clientTransferId), + new CommandParameter("size", fileSize), + new CommandParameter("overwrite", overwrite), + new CommandParameter("resume", resume) }), + NotificationType.StartUpload, NotificationType.FileTransferStatus); + if (!result.Ok) + return result.Error; + if (result.Value.NotifyType == NotificationType.StartUpload) + return result.UnwrapNotification().WrapSingle(); else { - var ft = lazyNot.Notifications.Cast().First(); - throw new Ts3CommandException(new CommandError() { Id = ft.Status, Message = ft.Message }); + var ftresult = result.UnwrapNotification().WrapSingle(); + if (!ftresult) + return ftresult.Error; + return new CommandError() { Id = ftresult.Value.Status, Message = ftresult.Value.Message }; } } - public override FileDownload FileTransferInitDownload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, + public override R FileTransferInitDownload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, long seek) { - var lazyNot = SendSpecialCommand(new Ts3Command("ftinitdownload", new List() { - new CommandParameter("cid", channelId), - new CommandParameter("name", path), - new CommandParameter("cpw", channelPassword), - new CommandParameter("clientftfid", clientTransferId), - new CommandParameter("seekpos", seek) }), NotificationType.StartDownload, NotificationType.FileTransferStatus); - if (lazyNot.NotifyType == NotificationType.StartDownload) - return lazyNot.Notifications.Cast().First(); + var result = SendNotifyCommand(new Ts3Command("ftinitdownload", new List() { + new CommandParameter("cid", channelId), + new CommandParameter("name", path), + new CommandParameter("cpw", channelPassword), + new CommandParameter("clientftfid", clientTransferId), + new CommandParameter("seekpos", seek) }), NotificationType.StartDownload, NotificationType.FileTransferStatus); + if (!result.Ok) + return result.Error; + if (result.Value.NotifyType == NotificationType.StartDownload) + return result.UnwrapNotification().WrapSingle(); else { - var ft = lazyNot.Notifications.Cast().First(); - throw new Ts3CommandException(new CommandError() { Id = ft.Status, Message = ft.Message }); + var ftresult = result.UnwrapNotification().WrapSingle(); + if (!ftresult) + return ftresult.Error; + return new CommandError() { Id = ftresult.Value.Status, Message = ftresult.Value.Message }; } } - public override IEnumerable FileTransferList() - => SendSpecialCommand(new Ts3Command("ftlist"), NotificationType.FileTransfer) - .Notifications.Cast(); - - public override IEnumerable FileTransferGetFileList(ChannelIdT channelId, string path, string channelPassword = "") - => SendSpecialCommand(new Ts3Command("ftgetfilelist", new List() { - new CommandParameter("cid", channelId), - new CommandParameter("path", path), - new CommandParameter("cpw", channelPassword) }), NotificationType.FileList) - .Notifications - .Cast(); - - public override IEnumerable FileTransferGetFileInfo(ChannelIdT channelId, string[] path, string channelPassword = "") - => SendSpecialCommand(new Ts3Command("ftgetfileinfo", new List() { - new CommandParameter("cid", channelId), - new CommandParameter("cpw", channelPassword), - new CommandMultiParameter("name", path) }), NotificationType.FileInfo) - .Notifications - .Cast(); + public override R, CommandError> FileTransferList() + => SendNotifyCommand(new Ts3Command("ftlist"), + NotificationType.FileTransfer).UnwrapNotification(); + + public override R, CommandError> FileTransferGetFileList(ChannelIdT channelId, string path, string channelPassword = "") + => SendNotifyCommand(new Ts3Command("ftgetfilelist", new List() { + new CommandParameter("cid", channelId), + new CommandParameter("path", path), + new CommandParameter("cpw", channelPassword) }), + NotificationType.FileList).UnwrapNotification(); + + public override R, CommandError> FileTransferGetFileInfo(ChannelIdT channelId, string[] path, string channelPassword = "") + => SendNotifyCommand(new Ts3Command("ftgetfileinfo", new List() { + new CommandParameter("cid", channelId), + new CommandParameter("cpw", channelPassword), + new CommandMultiParameter("name", path) }), + NotificationType.FileInfo).UnwrapNotification(); #endregion diff --git a/TS3Client/LazyNotification.cs b/TS3Client/LazyNotification.cs index 3a0a6170..7ad3899a 100644 --- a/TS3Client/LazyNotification.cs +++ b/TS3Client/LazyNotification.cs @@ -12,7 +12,7 @@ namespace TS3Client using Messages; using System.Collections.Generic; - internal struct LazyNotification + public struct LazyNotification { public readonly IEnumerable Notifications; public readonly NotificationType NotifyType; diff --git a/TS3Client/Query/Ts3QueryClient.cs b/TS3Client/Query/Ts3QueryClient.cs index cbfee8e2..6fbbcf18 100644 --- a/TS3Client/Query/Ts3QueryClient.cs +++ b/TS3Client/Query/Ts3QueryClient.cs @@ -121,7 +121,7 @@ private void InvokeEvent(LazyNotification lazyNotification) } } - public override IEnumerable SendCommand(Ts3Command com) // Synchronous + public override R, CommandError> SendCommand(Ts3Command com) // Synchronous { using (var wb = new WaitBlock()) { @@ -173,17 +173,17 @@ public void UseServer(int serverId) // Splitted base commands - public override ServerGroupAddResponse ServerGroupAdd(string name, PermissionGroupDatabaseType? type = null) + public override R ServerGroupAdd(string name, PermissionGroupDatabaseType? type = null) => Send("servergroupadd", type.HasValue ? new List { new CommandParameter("name", name), new CommandParameter("type", (int)type.Value) } - : new List { new CommandParameter("name", name) }).FirstOrDefault(); + : new List { new CommandParameter("name", name) }).WrapSingle(); - public override IEnumerable ServerGroupsByClientDbId(ulong clDbId) + public override R, CommandError> ServerGroupsByClientDbId(ulong clDbId) => Send("servergroupsbyclientid", new CommandParameter("cldbid", clDbId)); - public override FileUpload FileTransferInitUpload(ChannelIdT channelId, string path, string channelPassword, + public override R FileTransferInitUpload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, long fileSize, bool overwrite, bool resume) => Send("ftinitupload", new CommandParameter("cid", channelId), @@ -192,27 +192,27 @@ public override FileUpload FileTransferInitUpload(ChannelIdT channelId, string p new CommandParameter("clientftfid", clientTransferId), new CommandParameter("size", fileSize), new CommandParameter("overwrite", overwrite), - new CommandParameter("resume", resume)).First(); + new CommandParameter("resume", resume)).WrapSingle(); - public override FileDownload FileTransferInitDownload(ChannelIdT channelId, string path, string channelPassword, + public override R FileTransferInitDownload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, long seek) => Send("ftinitdownload", new CommandParameter("cid", channelId), new CommandParameter("name", path), new CommandParameter("cpw", channelPassword), new CommandParameter("clientftfid", clientTransferId), - new CommandParameter("seekpos", seek)).First(); + new CommandParameter("seekpos", seek)).WrapSingle(); - public override IEnumerable FileTransferList() + public override R, CommandError> FileTransferList() => Send("ftlist"); - public override IEnumerable FileTransferGetFileList(ChannelIdT channelId, string path, string channelPassword = "") + public override R, CommandError> FileTransferGetFileList(ChannelIdT channelId, string path, string channelPassword = "") => Send("ftgetfilelist", new CommandParameter("cid", channelId), new CommandParameter("path", path), new CommandParameter("cpw", channelPassword)); - public override IEnumerable FileTransferGetFileInfo(ChannelIdT channelId, string[] path, string channelPassword = "") + public override R, CommandError> FileTransferGetFileInfo(ChannelIdT channelId, string[] path, string channelPassword = "") => Send("ftgetfileinfo", new CommandParameter("cid", channelId), new CommandParameter("cpw", channelPassword), diff --git a/TS3Client/R.cs b/TS3Client/R.cs new file mode 100644 index 00000000..0ad6943f --- /dev/null +++ b/TS3Client/R.cs @@ -0,0 +1,129 @@ +// 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 . + +/// +/// Provides a safe alternative to Exceptions for error and result wrapping. +/// This type represents either success or an error + message. +/// +public struct R +{ + public static readonly R OkR = new R(); + + // using default false bool so Ok is true on default + private readonly bool isError; + public bool Ok => !isError; + public string Error { get; } + + private R(string error) { isError = true; Error = error; } + /// Creates a new failed result with a message + /// The message + public static R Err(string error) => new R(error); + + public static implicit operator bool(R result) => result.Ok; + public static implicit operator string(R result) => result.Error; + + public static implicit operator R(string error) => new R(error); + + public override string ToString() => Error; +} + +/// +/// Provides a safe alternative to Exceptions for error and result wrapping. +/// This type represents either success + value or an error + message. +/// The value is guaranteed to be non-null when successful. +/// +/// The type of the success value. +public struct R +{ + private readonly bool isError; + public bool Ok => !isError; + public string Error { get; } + public TSuccess Value { get; } + + private R(TSuccess value) { isError = false; Error = null; if (value == null) throw new System.ArgumentNullException(nameof(value), "Return of ok must not be null."); Value = value; } + private R(string error) { isError = true; Error = error; Value = default(TSuccess); } + //internal R(bool isError, TSuccess value) + + /// Creates a new failed result with a message + /// The message + public static R Err(string error) => new R(error); + /// Creates a new successful result with a value + /// The value + public static R OkR(TSuccess value) => new R(value); + + public static implicit operator bool(R result) => result.Ok; + public static implicit operator string(R result) => result.Error; + + public static implicit operator R(TSuccess result) => new R(result); + public static implicit operator R(string error) => new R(error); + + public override string ToString() => Error; +} + +/// +/// Provides a safe alternative to Exceptions for error and result wrapping. +/// This type represents either success + value or an error + error-object. +/// The value is guaranteed to be non-null when successful. +/// +/// The type of the success value. +/// The error type. +public struct R +{ + private readonly bool isError; + public bool Ok => !isError; + public TError Error { get; } + public TSuccess Value { get; } + + private R(TSuccess value) { isError = false; Error = default(TError); if (value == null) throw new System.ArgumentNullException(nameof(value), "Return of ok must not be null."); Value = value; } + private R(TError error) { isError = true; Error = error; Value = default(TSuccess); } + + /// Creates a new failed result with an error object + /// The error + public static R Err(TError error) => new R(error); + /// Creates a new successful result with a value + /// The value + public static R OkR(TSuccess value) => new R(value); + + public static implicit operator bool(R result) => result.Ok; + public static implicit operator TError(R result) => result.Error; + + public static implicit operator R(TSuccess result) => new R(result); + public static implicit operator R(TError error) => new R(error); + + // Downwrapping + public E OnlyError() => new E(isError, Error); + public static implicit operator E(R result) => result.OnlyError(); +} + +/// +/// Provides a safe alternative to Exceptions for error and result wrapping. +/// This type represents either success or an error + error object. +/// +/// The type of the error value. +public struct E +{ + /// Represents a successful state. + public static E OkR { get; } = new E(); + + private readonly bool isError; + public bool Ok => !isError; + public TError Error { get; } + + private E(TError error) { isError = true; Error = error; } + internal E(bool isError, TError error) { this.isError = isError; Error = error; } + + /// Creates a new failed result with a message + /// The message + public static E Err(TError error) => new E(error); + + public static implicit operator bool(E result) => result.Ok; + public static implicit operator TError(E result) => result.Error; + + public static implicit operator E(TError result) => new E(result); +} diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index fac3b26e..2766b7c6 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -48,6 +48,9 @@ ..\packages\Heijden.Dns.2.0.0\lib\net35\Heijden.Dns.dll + + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll + @@ -79,6 +82,7 @@ True MessageTemplates.tt + diff --git a/TS3Client/Ts3BaseClient.cs b/TS3Client/Ts3BaseClient.cs index 135a13cf..45b7953a 100644 --- a/TS3Client/Ts3BaseClient.cs +++ b/TS3Client/Ts3BaseClient.cs @@ -1,3 +1,12 @@ +// 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 Commands; @@ -8,6 +17,8 @@ namespace TS3Client using System.Linq; using System.Net; + using CmdR = E; + using ClientUidT = System.String; using ClientDbIdT = System.UInt64; using ClientIdT = System.UInt16; @@ -51,7 +62,7 @@ public abstract class Ts3BaseFunctions : IDisposable /// Creates a new command. /// The command name. [DebuggerStepThrough] - public IEnumerable Send(string command) + public R, CommandError> Send(string command) => SendCommand(new Ts3Command(command)); /// Creates a new command. @@ -59,7 +70,7 @@ public IEnumerable Send(string command) /// The parameters to be added to this command. /// See , or for more information. [DebuggerStepThrough] - public IEnumerable Send(string command, params ICommandPart[] parameter) + public R, CommandError> Send(string command, params ICommandPart[] parameter) => SendCommand(new Ts3Command(command, parameter.ToList())); /// Creates a new command. @@ -67,7 +78,7 @@ public IEnumerable Send(string command, params ICommandPart[ /// The command name. /// Returns an enumeration of the deserialized and split up in objects data. [DebuggerStepThrough] - public IEnumerable Send(string command) where T : IResponse, new() + public R, CommandError> Send(string command) where T : IResponse, new() => SendCommand(new Ts3Command(command)); /// Creates a new command. @@ -76,7 +87,7 @@ public IEnumerable Send(string command, params ICommandPart[ /// The parameters to be added to this command. /// Returns an enumeration of the deserialized and split up in objects data. [DebuggerStepThrough] - public IEnumerable Send(string command, params ICommandPart[] parameter) where T : IResponse, new() + public R, CommandError> Send(string command, params ICommandPart[] parameter) where T : IResponse, new() => Send(command, parameter.ToList()); /// Creates a new command. @@ -85,78 +96,78 @@ public IEnumerable Send(string command, params ICommandPart[ /// The parameters to be added to this command. /// Returns an enumeration of the deserialized and split up in objects data. [DebuggerStepThrough] - public IEnumerable Send(string command, List parameter) where T : IResponse, new() + public R, CommandError> Send(string command, List parameter) where T : IResponse, new() => SendCommand(new Ts3Command(command, parameter)); [DebuggerStepThrough] - protected void SendNoResponsed(Ts3Command command) + protected CmdR SendNoResponsed(Ts3Command command) => SendCommand(command.ExpectsResponse(false)); /// 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 unknow response data. /// The raw command to send. /// Returns an enumeration of the deserialized and split up in objects data. - public abstract IEnumerable SendCommand(Ts3Command com) where T : IResponse, new(); + public abstract R, CommandError> SendCommand(Ts3Command com) where T : IResponse, new(); #endregion #region UNIVERSAL COMMANDS - public void ChangeName(string newName) + public CmdR ChangeName(string newName) => Send("clientupdate", new CommandParameter("client_nickname", newName)); - public void ChangeBadges(string newBadges) + public CmdR ChangeBadges(string newBadges) => Send("clientupdate", new CommandParameter("client_badges", newBadges)); - public void ChangeDescription(string newDescription, ClientIdT clientId) + public CmdR ChangeDescription(string newDescription, ClientIdT clientId) => Send("clientedit", new CommandParameter("clid", clientId), new CommandParameter("client_description", newDescription)); /// Displays information about your current ServerQuery connection including your loginname, etc. - public WhoAmI WhoAmI() // Q ? - => Send("whoami").FirstOrDefault(); + public R WhoAmI() // Q ? + => Send("whoami").WrapSingle(); - public void SendMessage(string message, ClientData client) + public CmdR SendMessage(string message, ClientData client) => SendPrivateMessage(message, client.ClientId); - public void SendPrivateMessage(string message, ushort clientId) + public CmdR SendPrivateMessage(string message, ushort clientId) => SendMessage(message, TextMessageTargetMode.Private, clientId); - public void SendChannelMessage(string message) + public CmdR SendChannelMessage(string message) => SendMessage(message, TextMessageTargetMode.Channel, 0); - public void SendMessage(string message, ServerData server) + public CmdR SendMessage(string message, ServerData server) => SendServerMessage(message, server.VirtualServerId); - public void SendServerMessage(string message, ulong serverId) + public CmdR SendServerMessage(string message, ulong serverId) => SendMessage(message, TextMessageTargetMode.Server, serverId); /// Sends a text message to a specified target. /// If targetmode is set to , a message is sent to the client with the ID specified by target. /// 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 void SendMessage(string message, TextMessageTargetMode target, ulong id) + public CmdR SendMessage(string message, TextMessageTargetMode target, ulong id) => Send("sendtextmessage", new CommandParameter("targetmode", (int)target), new CommandParameter("target", id), new CommandParameter("msg", message)); /// Sends a text message to all clients on all virtual servers in the TeamSpeak 3 Server instance. - public void SendGlobalMessage(string message) + public CmdR SendGlobalMessage(string message) => Send("gm", new CommandParameter("msg", message)); - public void KickClientFromServer(ClientIdT[] clientIds) + public CmdR KickClientFromServer(ClientIdT[] clientIds) => KickClient(clientIds, ReasonIdentifier.Server); - public void KickClientFromChannel(ClientIdT[] clientIds) + public CmdR KickClientFromChannel(ClientIdT[] clientIds) => KickClient(clientIds, ReasonIdentifier.Channel); /// 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 void KickClient(ClientIdT[] clientIds, ReasonIdentifier reasonId, string reasonMsg = null) + public CmdR KickClient(ClientIdT[] clientIds, ReasonIdentifier reasonId, string reasonMsg = null) => Send("clientkick", new CommandParameter("reasonid", (int)reasonId), new CommandMultiParameter("clid", clientIds)); @@ -164,29 +175,29 @@ public void KickClient(ClientIdT[] clientIds, ReasonIdentifier reasonId, string /// 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. - public IEnumerable ClientList(ClientListOptions options = 0) + public R, CommandError> ClientList(ClientListOptions options = 0) => Send("clientlist", new CommandOption(options)); /// Displays detailed database information about a client including unique ID, creation date, etc. - public ClientDbData ClientDbInfo(ClientDbIdT clDbId) + public R ClientDbInfo(ClientDbIdT clDbId) => Send("clientdbinfo", - new CommandParameter("cldbid", clDbId)).FirstOrDefault(); + new CommandParameter("cldbid", clDbId)).WrapSingle(); /// Displays detailed configuration information about a client including unique ID, nickname, client version, etc. - public ClientInfo ClientInfo(ClientIdT clientId) + public R ClientInfo(ClientIdT clientId) => Send("clientinfo", - new CommandParameter("clid", clientId)).FirstOrDefault(); + new CommandParameter("clid", clientId)).WrapSingle(); /// Use a token key gain access to a server or channel group. /// Please note that the server will automatically delete the token after it has been used. - public void PrivilegeKeyUse(string key) + public CmdR PrivilegeKeyUse(string key) => Send("privilegekeyuse", new CommandParameter("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 void ServerGroupAddPerm(ServerGroupIdT serverGroupId, PermissionId permissionId, int permissionValue, + public CmdR ServerGroupAddPerm(ServerGroupIdT serverGroupId, PermissionId permissionId, int permissionValue, bool permissionNegated, bool permissionSkip) => Send("servergroupaddperm", new CommandParameter("sgid", serverGroupId), @@ -197,7 +208,7 @@ public void ServerGroupAddPerm(ServerGroupIdT serverGroupId, PermissionId permis /// 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 void ServerGroupAddPerm(ServerGroupIdT serverGroupId, PermissionId[] permissionId, int[] permissionValue, + public CmdR ServerGroupAddPerm(ServerGroupIdT serverGroupId, PermissionId[] permissionId, int[] permissionValue, bool[] permissionNegated, bool[] permissionSkip) => Send("servergroupaddperm", new CommandParameter("sgid", serverGroupId), @@ -208,35 +219,35 @@ public void ServerGroupAddPerm(ServerGroupIdT serverGroupId, PermissionId[] perm /// Adds a client to the server group specified with . Please note that a /// client cannot be added to default groups or template groups. - public void ServerGroupAddClient(ServerGroupIdT serverGroupId, ClientDbIdT clientDbId) + public CmdR ServerGroupAddClient(ServerGroupIdT serverGroupId, ClientDbIdT clientDbId) => Send("servergroupaddclient", new CommandParameter("sgid", serverGroupId), new CommandParameter("cldbid", clientDbId)); /// Removes a client specified with cldbid from the server group specified with . - public void ServerGroupDelClient(ServerGroupIdT serverGroupId, ClientDbIdT clientDbId) + public CmdR ServerGroupDelClient(ServerGroupIdT serverGroupId, ClientDbIdT clientDbId) => Send("servergroupdelclient", new CommandParameter("sgid", serverGroupId), new CommandParameter("cldbid", clientDbId)); - public void FileTransferStop(ushort serverTransferId, bool delete) + public CmdR FileTransferStop(ushort serverTransferId, bool delete) => Send("ftstop", new CommandParameter("serverftfid", serverTransferId), new CommandParameter("delete", delete)); - public void FileTransferDeleteFile(ChannelIdT channelId, string[] path, string channelPassword = "") + public CmdR FileTransferDeleteFile(ChannelIdT channelId, string[] path, string channelPassword = "") => Send("ftdeletefile", new CommandParameter("cid", channelId), new CommandParameter("cpw", channelPassword), new CommandMultiParameter("name", path)); - public void FileTransferCreateDirectory(ChannelIdT channelId, string path, string channelPassword = "") + public CmdR FileTransferCreateDirectory(ChannelIdT channelId, string path, string channelPassword = "") => Send("ftcreatedir", new CommandParameter("cid", channelId), new CommandParameter("dirname", path), new CommandParameter("cpw", channelPassword)); - public void FileTransferRenameFile(ChannelIdT channelId, string oldName, string channelPassword, string newName, + public CmdR FileTransferRenameFile(ChannelIdT channelId, string oldName, string channelPassword, string newName, ChannelIdT? targetChannel = null, string targetChannelPassword = "") { var cmd = new Ts3Command("ftrenamefile", new List { @@ -249,23 +260,25 @@ public void FileTransferRenameFile(ChannelIdT channelId, string oldName, string cmd.AppendParameter(new CommandParameter("tcid", targetChannel.Value)); cmd.AppendParameter(new CommandParameter("tcpw", targetChannelPassword)); } - SendCommand(cmd); + return SendCommand(cmd); } - public void UploadAvatar(System.IO.Stream image) + public CmdR UploadAvatar(System.IO.Stream image) { var token = FileTransferManager.UploadFile(image, 0, "/avatar", true); - token.Wait(); + if (!token.Ok) + return token.Error; + token.Value.Wait(); image.Seek(0, System.IO.SeekOrigin.Begin); using (var md5Dig = System.Security.Cryptography.MD5.Create()) { var md5Bytes = md5Dig.ComputeHash(image); var md5 = string.Join("", md5Bytes.Select(x => x.ToString("x2"))); - Send("clientupdate", new CommandParameter("client_flag_avatar", md5)); + return Send("clientupdate", new CommandParameter("client_flag_avatar", md5)); } } - public void ClientMove(ClientIdT clientId, ChannelIdT channelId, string channelPassword = null) + public CmdR ClientMove(ClientIdT clientId, ChannelIdT channelId, string channelPassword = null) { var cmd = new Ts3Command("clientmove", new List { new CommandParameter("clid", clientId), @@ -273,7 +286,7 @@ public void ClientMove(ClientIdT clientId, ChannelIdT channelId, string channelP if (channelPassword != null) cmd.AppendParameter(new CommandParameter("cpw", ClientType == ClientType.Full ? Full.Ts3Crypt.HashPassword(channelPassword) : channelPassword)); - SendCommand(cmd); + return SendCommand(cmd); } // Base Stuff for splitted up commands @@ -281,22 +294,22 @@ public void ClientMove(ClientIdT clientId, ChannelIdT channelId, string channelP /// Creates a new server group using the name specified with and return its ID. /// The optional parameter can be used to create ServerQuery groups and template groups. - public abstract ServerGroupAddResponse ServerGroupAdd(string name, PermissionGroupDatabaseType? type = null); + public abstract R ServerGroupAdd(string name, PermissionGroupDatabaseType? type = null); /// Displays all server groups the client specified with is currently residing in. - public abstract IEnumerable ServerGroupsByClientDbId(ClientDbIdT clDbId); + public abstract R, CommandError> ServerGroupsByClientDbId(ClientDbIdT clDbId); - public abstract FileUpload FileTransferInitUpload(ChannelIdT channelId, string path, string channelPassword, + public abstract R FileTransferInitUpload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, long fileSize, bool overwrite, bool resume); - public abstract FileDownload FileTransferInitDownload(ChannelIdT channelId, string path, string channelPassword, + public abstract R FileTransferInitDownload(ChannelIdT channelId, string path, string channelPassword, ushort clientTransferId, long seek); - public abstract IEnumerable FileTransferList(); + public abstract R, CommandError> FileTransferList(); - public abstract IEnumerable FileTransferGetFileList(ChannelIdT channelId, string path, string channelPassword = ""); + public abstract R, CommandError> FileTransferGetFileList(ChannelIdT channelId, string path, string channelPassword = ""); - public abstract IEnumerable FileTransferGetFileInfo(ChannelIdT channelId, string[] path, string channelPassword = ""); + public abstract R, CommandError> FileTransferGetFileInfo(ChannelIdT channelId, string[] path, string channelPassword = ""); #endregion } diff --git a/TS3Client/Ts3Exceptions.cs b/TS3Client/Ts3Exceptions.cs index 86eb91b7..d21ef286 100644 --- a/TS3Client/Ts3Exceptions.cs +++ b/TS3Client/Ts3Exceptions.cs @@ -18,13 +18,4 @@ public class Ts3Exception : Exception public Ts3Exception(string message) : base(message) { } public Ts3Exception(string message, Exception innerException) : base(message, innerException) { } } - - /// Exception when commands return an error code from the server. - public class Ts3CommandException : Ts3Exception - { - public CommandError ErrorStatus { get; } - - internal Ts3CommandException(CommandError message) : base(message.ErrorFormat()) { ErrorStatus = message; } - internal Ts3CommandException(CommandError message, Exception inner) : base(message.ErrorFormat(), inner) { ErrorStatus = message; } - } } diff --git a/TS3Client/Util.cs b/TS3Client/Util.cs index bddd645a..1d36abfc 100644 --- a/TS3Client/Util.cs +++ b/TS3Client/Util.cs @@ -39,8 +39,11 @@ internal static class Util public static Exception UnhandledDefault(T value) where T : struct { return new MissingEnumCaseException(typeof(T).Name, value.ToString()); } - public static CommandError TimeOutCommandError { get; } = - new CommandError {Id = Ts3ErrorCode.custom_error, Message = "Connection closed"}; + public static CommandError TimeOutCommandError { get; } = CustomError("Connection closed"); + + public static CommandError NoResultCommandError { get; } = CustomError("Result is empty"); + + public static CommandError CustomError(string message) => new CommandError { Id = Ts3ErrorCode.custom_error, Message = message }; } internal sealed class MissingEnumCaseException : Exception @@ -59,6 +62,28 @@ public static string ErrorFormat(this CommandError error) else return $"{error.Id}: the command failed to execute: {error.Message}"; } + + public static R WrapSingle(this R, CommandError> 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); + } + + internal static R, CommandError> UnwrapNotification(this R result) where T : class + { + if (!result.Ok) + return result.Error; + return R, CommandError>.OkR(result.Value.Notifications.Cast()); + } } internal static class DebugUtil diff --git a/TS3Client/WaitBlock.cs b/TS3Client/WaitBlock.cs index 3ff10361..854d0c01 100644 --- a/TS3Client/WaitBlock.cs +++ b/TS3Client/WaitBlock.cs @@ -39,30 +39,30 @@ public WaitBlock(NotificationType[] dependsOn = null) } } - public IEnumerable WaitForMessage() where T : IResponse, new() + public R, CommandError> WaitForMessage() where T : IResponse, new() { if (isDisposed) throw new ObjectDisposedException(nameof(WaitBlock)); if (!answerWaiter.WaitOne(CommandTimeout)) - throw new Ts3CommandException(Util.TimeOutCommandError); + return Util.TimeOutCommandError; if (commandError.Id != Ts3ErrorCode.ok) - throw new Ts3CommandException(commandError); + return commandError; - return CommandDeserializer.GenerateResponse(commandLine); + return R, CommandError>.OkR(CommandDeserializer.GenerateResponse(commandLine)); } - public LazyNotification WaitForNotification() + public R WaitForNotification() { if (isDisposed) throw new ObjectDisposedException(nameof(WaitBlock)); if (DependsOn == null) throw new InvalidOperationException("This waitblock has no dependent Notification"); if (!answerWaiter.WaitOne(CommandTimeout)) - throw new Ts3CommandException(Util.TimeOutCommandError); + return Util.TimeOutCommandError; if (commandError.Id != Ts3ErrorCode.ok) - throw new Ts3CommandException(commandError); + return commandError; if (!notificationWaiter.WaitOne(CommandTimeout)) - throw new Ts3CommandException(Util.TimeOutCommandError); + return Util.TimeOutCommandError; return notification; } diff --git a/TS3Client/packages.config b/TS3Client/packages.config index bb4defa6..766a6c93 100644 --- a/TS3Client/packages.config +++ b/TS3Client/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/Ts3ClientTests/Program.cs b/Ts3ClientTests/Program.cs index 2e722b28..eb2cf449 100644 --- a/Ts3ClientTests/Program.cs +++ b/Ts3ClientTests/Program.cs @@ -66,8 +66,10 @@ private static void Client_OnTextMessageReceived(object sender, IEnumerable Date: Wed, 29 Nov 2017 16:43:13 +0100 Subject: [PATCH 12/48] Fixed wrong address (port) resolve with srv/tsdns records --- TS3Client/TsDnsResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TS3Client/TsDnsResolver.cs b/TS3Client/TsDnsResolver.cs index 7508df62..3eedfb98 100644 --- a/TS3Client/TsDnsResolver.cs +++ b/TS3Client/TsDnsResolver.cs @@ -48,7 +48,7 @@ public static bool TryResolve(string address, out IPEndPoint endPoint) if (!Uri.TryCreate("http://" + address, UriKind.Absolute, out var uri)) return false; - var hasUriPort = string.IsNullOrEmpty(uri.GetComponents(UriComponents.Port, UriFormat.Unescaped)); + var hasUriPort = string.IsNullOrEmpty(uri.GetComponents(UriComponents.StrongPort, UriFormat.Unescaped)); // host is a dns name var resolver = new Resolver From b3a052daa9e366ccde7cb3faa70f12a3317843bb Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 29 Nov 2017 17:48:06 +0100 Subject: [PATCH 13/48] Ts3 constants. Closes #166 --- TS3AudioBot/Commands.cs | 2 +- TS3AudioBot/History/SmartHistoryFormatter.cs | 8 ++-- TS3AudioBot/TeamspeakControl.cs | 12 ++--- TS3AudioBot/Ts3Full.cs | 9 ++-- TS3Client/Commands/Ts3Const.cs | 49 ++++++++++++++++++++ TS3Client/Commands/Ts3String.cs | 2 - TS3Client/Full/Ts3Crypt.cs | 8 ++++ TS3Client/TS3Client.csproj | 1 + 8 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 TS3Client/Commands/Ts3Const.cs diff --git a/TS3AudioBot/Commands.cs b/TS3AudioBot/Commands.cs index 8e250123..a9475991 100644 --- a/TS3AudioBot/Commands.cs +++ b/TS3AudioBot/Commands.cs @@ -625,7 +625,7 @@ public static JsonObject CommandListList(ExecutionInformation info, string patte foreach (var file in files) { int newTokenLen = tokenLen + TS3Client.Commands.Ts3String.TokenLength(file) + 3; - if (newTokenLen < TS3Client.Commands.Ts3String.MaxMsgLength) + if (newTokenLen < TS3Client.Commands.Ts3Const.MaxSizeTextMessage) { strb.Append(file).Append(", "); tokenLen = newTokenLen; diff --git a/TS3AudioBot/History/SmartHistoryFormatter.cs b/TS3AudioBot/History/SmartHistoryFormatter.cs index 016610be..b94e70df 100644 --- a/TS3AudioBot/History/SmartHistoryFormatter.cs +++ b/TS3AudioBot/History/SmartHistoryFormatter.cs @@ -27,7 +27,7 @@ public class SmartHistoryFormatter : IHistoryFormatter public string ProcessQuery(AudioLogEntry entry, Func format) { - return SubstringToken(format(entry), Ts3String.MaxMsgLength); + return SubstringToken(format(entry), Ts3Const.MaxSizeTextMessage); } public string ProcessQuery(IEnumerable entries, Func format) @@ -46,7 +46,7 @@ public string ProcessQuery(IEnumerable entries, Func entries, Func entries, Func= 0; i--) { var eL = useList[i]; diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index 094a0693..fd9d34a7 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -100,22 +100,22 @@ private void OnDisconnected(object sender, DisconnectEventArgs e) public R SendMessage(string message, ushort clientId) { - if (Ts3String.TokenLength(message) > Ts3String.MaxMsgLength) - return "The message to send is longer than the maximum of " + Ts3String.MaxMsgLength + " characters"; + if (Ts3String.TokenLength(message) > Ts3Const.MaxSizeTextMessage) + return "The message to send is longer than the maximum of " + Ts3Const.MaxSizeTextMessage + " characters"; return tsBaseClient.SendPrivateMessage(message, clientId).ToR(Extensions.ErrorFormat); } public R SendChannelMessage(string message) { - if (Ts3String.TokenLength(message) > Ts3String.MaxMsgLength) - return "The message to send is longer than the maximum of " + Ts3String.MaxMsgLength + " characters"; + if (Ts3String.TokenLength(message) > Ts3Const.MaxSizeTextMessage) + return "The message to send is longer than the maximum of " + Ts3Const.MaxSizeTextMessage + " characters"; return tsBaseClient.SendChannelMessage(message).ToR(Extensions.ErrorFormat); } public R SendServerMessage(string message) { - if (Ts3String.TokenLength(message) > Ts3String.MaxMsgLength) - return "The message to send is longer than the maximum of " + Ts3String.MaxMsgLength + " characters"; + if (Ts3String.TokenLength(message) > Ts3Const.MaxSizeTextMessage) + return "The message to send is longer than the maximum of " + Ts3Const.MaxSizeTextMessage + " characters"; return tsBaseClient.SendServerMessage(message, 1).ToR(Extensions.ErrorFormat); } diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index e75b1cfa..dad381ef 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -131,9 +131,12 @@ public override void Connect() if (ts3FullClientData.IdentityLevel == "auto") { } else if (int.TryParse(ts3FullClientData.IdentityLevel, out int targetLevel)) { - Log.Write(Log.Level.Info, "Calculating up to required security level: {0}", targetLevel); - Ts3Crypt.ImproveSecurity(identity, targetLevel); - ts3FullClientData.IdentityOffset = identity.ValidKeyOffset; + if(Ts3Crypt.GetSecurityLevel(identity) < targetLevel) + { + Log.Write(Log.Level.Info, "Calculating up to required security level: {0}", targetLevel); + Ts3Crypt.ImproveSecurity(identity, targetLevel); + ts3FullClientData.IdentityOffset = identity.ValidKeyOffset; + } } else { diff --git a/TS3Client/Commands/Ts3Const.cs b/TS3Client/Commands/Ts3Const.cs new file mode 100644 index 00000000..6de86eaa --- /dev/null +++ b/TS3Client/Commands/Ts3Const.cs @@ -0,0 +1,49 @@ +// 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 +{ + public static class Ts3Const + { + // Common Definitions + + //limited length, measured in characters + public const int MaxSizeChannelName = 40; + public const int MaxSizeVirtualserverName = 64; + public const int MaxSizeClientNicknameSdk = 64; + public const int MinSizeClientNicknameSdk = 3; + public const int MaxSizeReasonMessage = 80; + + //limited length, measured in bytes (utf8 encoded) + public const int MaxSizeTextMessage = 1024; + public const int MaxSizeChannelTopic = 255; + public const int MaxSizeChannelDescription = 8192; + public const int MaxSizeVirtualserverWelcomeMessage = 1024; + + // Rare Definitions + + //limited length, measured in characters + public const int MaxSizeClientNickname = 30; + public const int MinSizeClientNickname = 3; + public const int MaxSizeAwayMessage = 80; + public const int MaxSizeGroupName = 30; + public const int MaxSizeTalkRequestMessage = 50; + public const int MaxSizeComplainMessage = 200; + public const int MaxSizeClientDescription = 200; + public const int MaxSizeHostMessage = 200; + public const int MaxSizeHostbuttonTooltip = 50; + public const int MaxSizepokeMessage = 100; + public const int MaxSizeOfflineMessage = 4096; + public const int MaxSizeOfflineMessageSubject = 200; + + //limited length, measured in bytes (utf8 encoded) + public const int MaxSizePluginCommand = 1024 * 8; + public const int MaxSizeVirtualserverHostbannerGfxUrl = 2000; + } +} diff --git a/TS3Client/Commands/Ts3String.cs b/TS3Client/Commands/Ts3String.cs index a376c7d6..315134fd 100644 --- a/TS3Client/Commands/Ts3String.cs +++ b/TS3Client/Commands/Ts3String.cs @@ -15,8 +15,6 @@ namespace TS3Client.Commands public static class Ts3String { - public const int MaxMsgLength = 1024; - public static string Escape(string stringToEscape) { var strb = new StringBuilder(stringToEscape.Length); diff --git a/TS3Client/Full/Ts3Crypt.cs b/TS3Client/Full/Ts3Crypt.cs index f4c46780..45cd32b0 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TS3Client/Full/Ts3Crypt.cs @@ -538,6 +538,14 @@ public static void ImproveSecurity(IdentityData identity, int toLevel) } } + public static int GetSecurityLevel(IdentityData identity) + { + byte[] hashBuffer = new byte[identity.PublicKeyString.Length + MaxUlongStringLen]; + byte[] pubKeyBytes = Encoding.ASCII.GetBytes(identity.PublicKeyString); + Array.Copy(pubKeyBytes, 0, hashBuffer, 0, pubKeyBytes.Length); + return GetSecurityLevel(hashBuffer, pubKeyBytes.Length, identity.ValidKeyOffset); + } + /// Creates a new TeamSpeak3 identity. /// Minimum security level this identity will have. /// The identity information. diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index 2766b7c6..0b0b84c1 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -56,6 +56,7 @@ + From 88d8bed18eb245066fa4d9e776e9caea7f47603e Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 29 Nov 2017 20:54:09 +0100 Subject: [PATCH 14/48] Fixed youtube-dl not getting called correctly --- .../ResourceFactories/MatchCertainty.cs | 5 ++++ TS3AudioBot/ResourceFactories/RResultCode.cs | 3 -- .../ResourceFactoryManager.cs | 28 ++++++++++++++----- .../ResourceFactories/YoutubeFactory.cs | 23 ++++++++++----- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/TS3AudioBot/ResourceFactories/MatchCertainty.cs b/TS3AudioBot/ResourceFactories/MatchCertainty.cs index 80e1dbb2..05d50999 100644 --- a/TS3AudioBot/ResourceFactories/MatchCertainty.cs +++ b/TS3AudioBot/ResourceFactories/MatchCertainty.cs @@ -11,10 +11,15 @@ namespace TS3AudioBot.ResourceFactories { public enum MatchCertainty { + /// "Never" sais this factory cannot use this link. Never = 0, + /// "OnlyIfLast" Only gets selected if no higher match was found. OnlyIfLast, + /// "Maybe" has the lowest priority which also excludes all "OnlyIfLast" factories. Maybe, + /// "Probably" will get prioritized over "Maybe" and excludes all "OnlyIfLast" factories. Probably, + /// "Always" will reserve a link exclusively for all factories which also said "Always". Always, } diff --git a/TS3AudioBot/ResourceFactories/RResultCode.cs b/TS3AudioBot/ResourceFactories/RResultCode.cs index 14b0c33c..0ed2d55e 100644 --- a/TS3AudioBot/ResourceFactories/RResultCode.cs +++ b/TS3AudioBot/ResourceFactories/RResultCode.cs @@ -17,9 +17,6 @@ public enum RResultCode // Resource Result Code MediaUnknownUri, MediaNoWebResponse, MediaFileNotFound, - YtIdNotFound, - YtNoVideosExtracted, - YtNoFMTS, ScInvalidLink, TwitchInvalidUrl, TwitchMalformedM3u8File, diff --git a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs index e187f4e3..0bb3066f 100644 --- a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs +++ b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs @@ -60,20 +60,35 @@ private T GetFactoryByType(string audioType) where T : class, IFactory => ? factory : null; - private IEnumerable GetResFactoryByLink(string uri) => + 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; + select (fac, facCertain); - private IEnumerable GetListFactoryByLink(string uri) => + 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; + select (fac, facCertain); + private static IEnumerable FilterUsable(IEnumerable<(T, MatchCertainty)> enu) + { + MatchCertainty 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 @@ -121,7 +136,7 @@ public R Load(string message, string audioType = null) return result; } - var factories = GetResFactoryByLink(netlinkurl); + var factories = FilterUsable(GetResFactoryByLink(netlinkurl)); foreach (var factory in factories) { var result = factory.GetResource(netlinkurl); @@ -144,7 +159,7 @@ private R LoadPlaylistFrom(string message, IPlaylistFactory listFactor if (listFactory != null) return listFactory.GetPlaylist(netlinkurl); - var factories = GetListFactoryByLink(netlinkurl); + var factories = FilterUsable(GetListFactoryByLink(netlinkurl)); foreach (var factory in factories) { var result = factory.GetPlaylist(netlinkurl); @@ -170,7 +185,6 @@ public R GetThumbnail(PlayResource playResource) return factory.GetThumbnail(playResource); } - public void AddFactory(IFactory factory) { if (factory.FactoryFor.ToLowerInvariant() != factory.FactoryFor) diff --git a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs index 278497c9..0fe4a2da 100644 --- a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs +++ b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs @@ -48,14 +48,24 @@ public R GetResource(string ytLink) { Match matchYtId = IdMatch.Match(ytLink); if (!matchYtId.Success) - return RResultCode.YtIdNotFound.ToString(); + return "The youtube id could not get parsed."; return GetResourceById(new AudioResource(matchYtId.Groups[3].Value, null, FactoryFor)); } public R GetResourceById(AudioResource resource) + { + var result = ResolveResourceInternal(resource); + if (result.Ok) + return result; + + Log.Write(Log.Level.Debug, "YT Falling back to youtube-dl!"); + return YoutubeDlWrapped(resource); + } + + private R ResolveResourceInternal(AudioResource resource) { if (!WebWrapper.DownloadString(out string resulthtml, new Uri($"http://www.youtube.com/get_video_info?video_id={resource.ResourceId}&el=info"))) - return RResultCode.NoConnection.ToString(); + return "No connection to the youtube api could be established"; var videoTypes = new List(); NameValueCollection dataParse = HttpUtility.ParseQueryString(resulthtml); @@ -131,7 +141,7 @@ public R GetResourceById(AudioResource resource) // Validation Process if (videoTypes.Count <= 0) - return RResultCode.YtNoVideosExtracted.ToString(); + return "No video streams extracted."; int codec = SelectStream(videoTypes); if (codec < 0) @@ -140,6 +150,7 @@ public R GetResourceById(AudioResource resource) var result = ValidateMedia(videoTypes[codec]); if (!result) { + return result.Error; if (string.IsNullOrWhiteSpace(data.YoutubedlPath)) return result.Error; @@ -171,7 +182,7 @@ private static int SelectStream(List list) private static R ValidateMedia(VideoData media) { - var vcode = WebWrapper.GetResponse(new Uri(media.Link), TimeSpan.FromSeconds(1)); + var vcode = WebWrapper.GetResponse(new Uri(media.Link), TimeSpan.FromSeconds(3)); switch (vcode) { @@ -311,8 +322,6 @@ public static string LoadAlternative(string id) private static R YoutubeDlWrapped(AudioResource resource) { - Log.Write(Log.Level.Debug, "YT Ruined!"); - var result = YoutubeDlHelper.FindAndRunYoutubeDl(resource.ResourceId); if (!result.Ok) return result.Error; @@ -339,7 +348,7 @@ private static R YoutubeDlWrapped(AudioResource resource) if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(url)) return "No youtube-dl response"; - Log.Write(Log.Level.Debug, "YT Saved!"); + Log.Write(Log.Level.Debug, "YT youtube-dl succeeded!"); return new PlayResource(url, resource.WithName(title)); } From 3fe723429949f182b18255420dad1c333a7c7143 Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 30 Nov 2017 10:07:42 +0100 Subject: [PATCH 15/48] Improved TickPool closing --- TS3AudioBot/BotManager.cs | 2 +- TS3AudioBot/Core.cs | 24 +++++++++---------- TS3AudioBot/Helper/TickPool.cs | 42 ++++++++++++++++++++++------------ 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index 2066bfac..8a46fd30 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -42,7 +42,7 @@ public void WatchBots() } } } - Thread.Sleep(100); + Thread.Sleep(200); } } diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index 62320618..678f21d9 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -76,9 +76,9 @@ internal static void Main(string[] args) /// General purpose persistant storage for internal modules. internal DbStore Database { get; set; } - /// Manges plugins, provides various loading and unloading mechanisms. + /// Manages plugins, provides various loading and unloading mechanisms. internal PluginManager PluginManager { get; set; } - /// Manges plugins, provides various loading and unloading mechanisms. + /// Manages a dependency hierachy and injects required modules at runtime. internal Injector Injector { get; set; } /// Mangement for the bot command system. public CommandManager CommandManager { get; set; } @@ -223,16 +223,16 @@ void ColorLog(string msg, Log.Level lvl) Audio.Opus.NativeMethods.DummyLoad(); Injector = new Injector(); - Injector.RegisterModule(this); // OK - Injector.RegisterModule(ConfigManager); // OK - Injector.RegisterModule(Injector); // OK - Database = Injector.Create(); // OK - PluginManager = Injector.Create(); // OK - CommandManager = Injector.Create(); // OK - FactoryManager = Injector.Create(); // OK - WebManager = Injector.Create(); // OK - RightsManager = Injector.Create(); // OK - Bots = Injector.Create(); // OK + Injector.RegisterModule(this); + Injector.RegisterModule(ConfigManager); + Injector.RegisterModule(Injector); + Database = Injector.Create(); + PluginManager = Injector.Create(); + CommandManager = Injector.Create(); + FactoryManager = Injector.Create(); + WebManager = Injector.Create(); + RightsManager = Injector.Create(); + Bots = Injector.Create(); Injector.SkipInitialized(this); diff --git a/TS3AudioBot/Helper/TickPool.cs b/TS3AudioBot/Helper/TickPool.cs index d98bf20b..429de5cb 100644 --- a/TS3AudioBot/Helper/TickPool.cs +++ b/TS3AudioBot/Helper/TickPool.cs @@ -17,10 +17,12 @@ namespace TS3AudioBot.Helper [Serializable] public static class TickPool { - private static Thread tickThread; + private static bool run; + private static readonly Thread tickThread; + private static readonly object tickLock = new object(); private static readonly TimeSpan MinTick = TimeSpan.FromMilliseconds(1000); private static readonly List workList; - private static bool run; + private static readonly AutoResetEvent tickLoopPulse = new AutoResetEvent(false); static TickPool() { @@ -45,7 +47,7 @@ public static TickWorker RegisterTick(Action method, TimeSpan interval, bool act private static void AddWorker(TickWorker worker) { - lock (workList) + lock (tickLock) { workList.Add(worker); worker.Timer.Start(); @@ -60,13 +62,11 @@ private static void AddWorker(TickWorker worker) public static void UnregisterTicker(TickWorker worker) { if (worker == null) throw new ArgumentNullException(nameof(worker)); - lock (workList) { RemoveUnlocked(worker); } - } - - private static void RemoveUnlocked(TickWorker worker) - { - workList.Remove(worker); - worker.Timer.Stop(); + lock (tickLock) + { + workList.Remove(worker); + worker.Timer.Stop(); + } } private static void Tick() @@ -75,7 +75,7 @@ private static void Tick() { var curSleep = MinTick; - lock (workList) + lock (tickLock) { for (int i = 0; i < workList.Count; i++) { @@ -89,7 +89,7 @@ private static void Tick() if (worker.TickOnce) { - RemoveUnlocked(worker); + UnregisterTicker(worker); i--; } else @@ -103,15 +103,27 @@ private static void Tick() } } - Thread.Sleep(curSleep); + tickLoopPulse.WaitOne(curSleep); } } public static void Close() { run = false; - Util.WaitForThreadEnd(tickThread, MinTick + MinTick); - tickThread = null; + tickLoopPulse.Set(); + bool lockTaken = false; + Monitor.TryEnter(workList, TimeSpan.FromSeconds(1), ref lockTaken); + if (lockTaken) + { + workList.Clear(); + Monitor.Exit(workList); + } + else + { + Log.Write(Log.Level.Warning, "TickPool could not close correctly."); + } + tickLoopPulse.Set(); + Util.WaitForThreadEnd(tickThread, MinTick); } } From 6dd7e65b29e55ba41de458c90da309cc1e3b6985 Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 30 Nov 2017 11:22:20 +0100 Subject: [PATCH 16/48] Fixed TsDnsResolver, added corecte localhost (port) resolve --- TS3Client/TsDnsResolver.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/TS3Client/TsDnsResolver.cs b/TS3Client/TsDnsResolver.cs index 3eedfb98..3473a298 100644 --- a/TS3Client/TsDnsResolver.cs +++ b/TS3Client/TsDnsResolver.cs @@ -48,7 +48,7 @@ public static bool TryResolve(string address, out IPEndPoint endPoint) if (!Uri.TryCreate("http://" + address, UriKind.Absolute, out var uri)) return false; - var hasUriPort = string.IsNullOrEmpty(uri.GetComponents(UriComponents.StrongPort, UriFormat.Unescaped)); + var hasUriPort = !string.IsNullOrEmpty(uri.GetComponents(UriComponents.Port, UriFormat.Unescaped)); // host is a dns name var resolver = new Resolver @@ -61,7 +61,6 @@ public static bool TryResolve(string address, out IPEndPoint endPoint) TransportType = Heijden.DNS.TransportType.Udp, }; - // Try resolve udp prefix // Under this address we'll get ts3 voice server var srvEndPoint = ResolveSrv(resolver, DnsPrefixUdp + uri.Host); @@ -81,7 +80,6 @@ public static bool TryResolve(string address, out IPEndPoint endPoint) var domainSplit = uri.Host.Split('.'); if (domainSplit.Length <= 1) return false; - var domainList = new List(); for (int i = 1; i < Math.Min(domainSplit.Length, 4); i++) domainList.Add(string.Join(".", domainSplit, (domainSplit.Length - (i + 1)), i + 1)); @@ -168,12 +166,18 @@ private static IPEndPoint ResolveTsDns(TcpClient client, IPEndPoint tsDnsAddress return ParseIpEndPoint(returnString); } - private static readonly Regex IpRegex = new Regex(@"(?(?:\d{1,3}\.){3}\d{1,3}|\[[0-9a-fA-F:]+\])(?::(?\d{1,5}))?", RegexOptions.ECMAScript | RegexOptions.Compiled); + private static readonly Regex IpRegex = new Regex(@"(?(?:\d{1,3}\.){3}\d{1,3}|\[[0-9a-fA-F:]+\]|localhost)(?::(?\d{1,5}))?", RegexOptions.ECMAScript | RegexOptions.Compiled); private static IPEndPoint ParseIpEndPoint(string address) { var match = IpRegex.Match(address); - if (!match.Success || !IPAddress.TryParse(match.Groups["ip"].Value, out IPAddress ipAddr)) + if (!match.Success) + return null; + + IPAddress ipAddr; + if (match.Groups["ip"].Value == "localhost") + ipAddr = IPAddress.Loopback; + else if (!IPAddress.TryParse(match.Groups["ip"].Value, out ipAddr)) return null; if (!match.Groups["port"].Success) From 655c93084a70622ec27c7277911a428078da3a35 Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 30 Nov 2017 12:06:30 +0100 Subject: [PATCH 17/48] Added bot cleaner This should help temporarily to prevent internal "ghost" bots --- TS3AudioBot/BotManager.cs | 21 +++++++++++++++ .../ResourceFactories/YoutubeFactory.cs | 8 +----- TS3Client/Full/Ts3FullClient.cs | 1 + TS3Client/Query/Ts3QueryClient.cs | 26 +++++++++++++------ TS3Client/R.cs | 24 ++++++++++------- TS3Client/Ts3BaseClient.cs | 2 ++ 6 files changed, 57 insertions(+), 25 deletions(-) diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index 8a46fd30..387456c1 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -41,11 +41,32 @@ public void WatchBots() Thread.Sleep(1000); } } + + CleanStrayBots(); } Thread.Sleep(200); } } + private void CleanStrayBots() + { + List strayList = null; + foreach (var bot in activeBots) + { + var client = bot.QueryConnection.GetLowLibrary(); + if (!client.Connected && !client.Connecting) + { + Log.Write(Log.Level.Warning, "Cleaning up stray bot."); + strayList = strayList ?? new List(); + strayList.Add(bot); + } + } + + if (strayList != null) + foreach (var bot in strayList) + StopBot(bot); + } + public bool CreateBot(/*Ts3FullClientData bot*/) { string error = string.Empty; diff --git a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs index 0fe4a2da..1201048d 100644 --- a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs +++ b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs @@ -148,14 +148,8 @@ private R ResolveResourceInternal(AudioResource resource) return "No playable codec found"; var result = ValidateMedia(videoTypes[codec]); - if (!result) - { + if (!result.Ok) return result.Error; - if (string.IsNullOrWhiteSpace(data.YoutubedlPath)) - return result.Error; - - return YoutubeDlWrapped(resource); - } return new PlayResource(videoTypes[codec].Link, resource.ResourceTitle != null ? resource : resource.WithName(dataParse["title"] ?? $"")); } diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index 22e8fdeb..783e46f2 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -47,6 +47,7 @@ public sealed class Ts3FullClient : Ts3BaseFunctions public VersionSign VersionSign { get; private set; } 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; } } private ConnectionDataFull connectionDataFull; public override event NotifyEventHandler OnTextMessageReceived; diff --git a/TS3Client/Query/Ts3QueryClient.cs b/TS3Client/Query/Ts3QueryClient.cs index 6fbbcf18..957a2925 100644 --- a/TS3Client/Query/Ts3QueryClient.cs +++ b/TS3Client/Query/Ts3QueryClient.cs @@ -36,6 +36,8 @@ public sealed class Ts3QueryClient : Ts3BaseFunctions public override ClientType ClientType => ClientType.Query; public override bool Connected => tcpClient.Connected; + private bool connecting; + public override bool Connecting => connecting && !Connected; public override event NotifyEventHandler OnTextMessageReceived; public override event NotifyEventHandler OnClientEnterView; @@ -45,6 +47,7 @@ public sealed class Ts3QueryClient : Ts3BaseFunctions public Ts3QueryClient(EventDispatchType dispatcherType) { + connecting = false; tcpClient = new TcpClient(); msgProc = new MessageProcessor(true); dispatcher = EventDispatcherHelper.Create(dispatcherType); @@ -55,16 +58,23 @@ public override void Connect(ConnectionData conData) if (!TsDnsResolver.TryResolve(conData.Address, out remoteAddress)) throw new Ts3Exception("Could not read or resolve address."); - try { tcpClient.Connect(remoteAddress); } - catch (SocketException ex) { throw new Ts3Exception("Could not connect.", ex); } - ConnectionData = conData; + try + { + connecting = true; + + tcpClient.Connect(remoteAddress); - tcpStream = tcpClient.GetStream(); - tcpReader = new StreamReader(tcpStream, Util.Encoder); - tcpWriter = new StreamWriter(tcpStream, Util.Encoder) { NewLine = "\n" }; + ConnectionData = conData; - for (int i = 0; i < 3; i++) - tcpReader.ReadLine(); + tcpStream = tcpClient.GetStream(); + tcpReader = new StreamReader(tcpStream, Util.Encoder); + tcpWriter = new StreamWriter(tcpStream, Util.Encoder) { NewLine = "\n" }; + + for (int i = 0; i < 3; i++) + tcpReader.ReadLine(); + } + catch (SocketException ex) { throw new Ts3Exception("Could not connect.", ex); } + finally { connecting = false; } dispatcher.Init(NetworkLoop, InvokeEvent, null); OnConnected?.Invoke(this, new EventArgs()); diff --git a/TS3Client/R.cs b/TS3Client/R.cs index 0ad6943f..06eacb1c 100644 --- a/TS3Client/R.cs +++ b/TS3Client/R.cs @@ -7,6 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using System; + +#pragma warning disable IDE0016 + /// /// Provides a safe alternative to Exceptions for error and result wrapping. /// This type represents either success or an error + message. @@ -15,12 +19,10 @@ public struct R { public static readonly R OkR = new R(); - // using default false bool so Ok is true on default - private readonly bool isError; - public bool Ok => !isError; + public bool Ok => Error == null; public string Error { get; } - private R(string error) { isError = true; Error = error; } + private R(string error) { Error = error ?? throw new ArgumentNullException(nameof(error), "Error must not be null."); } /// Creates a new failed result with a message /// The message public static R Err(string error) => new R(error); @@ -46,8 +48,8 @@ public struct R public string Error { get; } public TSuccess Value { get; } - private R(TSuccess value) { isError = false; Error = null; if (value == null) throw new System.ArgumentNullException(nameof(value), "Return of ok must not be null."); Value = value; } - private R(string error) { isError = true; Error = error; Value = default(TSuccess); } + private R(TSuccess value) { isError = false; Error = null; if (value == null) throw new ArgumentNullException(nameof(value), "Return of ok must not be null."); Value = value; } + private R(string error) { isError = true; Error = error ?? throw new ArgumentNullException(nameof(error), "Error must not be null."); Value = default(TSuccess); } //internal R(bool isError, TSuccess value) /// Creates a new failed result with a message @@ -80,8 +82,8 @@ public struct R public TError Error { get; } public TSuccess Value { get; } - private R(TSuccess value) { isError = false; Error = default(TError); if (value == null) throw new System.ArgumentNullException(nameof(value), "Return of ok must not be null."); Value = value; } - private R(TError error) { isError = true; Error = error; Value = default(TSuccess); } + private R(TSuccess value) { isError = false; Error = default(TError); if (value == null) throw new ArgumentNullException(nameof(value), "Return of ok must not be null."); Value = value; } + private R(TError error) { isError = true; Value = default(TSuccess); if (error == null) throw new ArgumentNullException(nameof(error), "Error must not be null."); Error = error; } /// Creates a new failed result with an error object /// The error @@ -115,8 +117,8 @@ public struct E public bool Ok => !isError; public TError Error { get; } - private E(TError error) { isError = true; Error = error; } - internal E(bool isError, TError error) { this.isError = isError; Error = error; } + private E(TError error) { isError = true; if (error == null) throw new ArgumentNullException(nameof(error), "Error must not be null."); Error = error; } + internal E(bool isError, TError error) { this.isError = isError; Error = error; } // No null check here, we already check cosistently. /// Creates a new failed result with a message /// The message @@ -127,3 +129,5 @@ public struct E public static implicit operator E(TError result) => new E(result); } + +#pragma warning restore IDE0016 diff --git a/TS3Client/Ts3BaseClient.cs b/TS3Client/Ts3BaseClient.cs index 45b7953a..574a744f 100644 --- a/TS3Client/Ts3BaseClient.cs +++ b/TS3Client/Ts3BaseClient.cs @@ -44,6 +44,8 @@ public abstract class Ts3BaseFunctions : IDisposable /// Get whether this client is currently connected. public abstract bool Connected { get; } + /// Get whether this client is currently trying to connect. + public abstract bool Connecting { get; } /// The derived client type. public abstract ClientType ClientType { get; } /// The connection data this client was last connected with (or is currenly connected to). From 6078a0f5ae69cecd8741355c4d38cc46af7a7c65 Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 30 Nov 2017 15:27:07 +0100 Subject: [PATCH 18/48] Fixed a bug causing stray bots --- TS3AudioBot/TeamspeakControl.cs | 10 +---- TS3AudioBot/Ts3Full.cs | 63 +++++++++++++++++++++---------- TS3Client/DisconnectEventArgs.cs | 5 ++- TS3Client/Full/Ts3FullClient.cs | 28 +++++++++----- TS3Client/LazyNotification.cs | 9 +++++ TS3Client/Query/Ts3QueryClient.cs | 2 +- TS3Client/Util.cs | 6 +-- 7 files changed, 79 insertions(+), 44 deletions(-) diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index fd9d34a7..50d58241 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -59,7 +59,7 @@ private void ExtendedClientLeftView(object sender, IEnumerable e } } - public event EventHandler OnBotDisconnect; + public abstract event EventHandler OnBotDisconnect; private List clientbuffer; private bool clientbufferOutdated = true; @@ -80,7 +80,6 @@ protected TeamspeakControl(ClientType connectionType) tsBaseClient.OnClientLeftView += ExtendedClientLeftView; tsBaseClient.OnClientEnterView += ExtendedClientEnterView; tsBaseClient.OnTextMessageReceived += ExtendedTextMessage; - tsBaseClient.OnDisconnected += OnDisconnected; } public virtual T GetLowLibrary() where T : class @@ -92,12 +91,6 @@ public virtual T GetLowLibrary() where T : class public abstract void Connect(); - private void OnDisconnected(object sender, DisconnectEventArgs e) - { - Log.Write(Log.Level.Debug, "Bot disconnected. Reason: {0}", e.ExitReason); - OnBotDisconnect?.Invoke(this, new EventArgs()); - } - public R SendMessage(string message, ushort clientId) { if (Ts3String.TokenLength(message) > Ts3Const.MaxSizeTextMessage) @@ -365,7 +358,6 @@ public R IsChannelCommander() public void Dispose() { - Log.Write(Log.Level.Info, "Closing QueryConnection..."); if (tsBaseClient != null) { tsBaseClient.Dispose(); diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index dad381ef..8ef875f5 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -51,6 +51,7 @@ internal sealed class Ts3Full : TeamspeakControl, IPlayerConnection, ITargetMana private readonly object ffmpegLock = new object(); private readonly TimeSpan retryOnDropBeforeEnd = TimeSpan.FromSeconds(10); private bool hasTriedToReconnectAudio; + public override event EventHandler OnBotDisconnect; private readonly Ts3FullClientData ts3FullClientData; private float volume = 1; @@ -131,7 +132,7 @@ public override void Connect() if (ts3FullClientData.IdentityLevel == "auto") { } else if (int.TryParse(ts3FullClientData.IdentityLevel, out int targetLevel)) { - if(Ts3Crypt.GetSecurityLevel(identity) < targetLevel) + if (Ts3Crypt.GetSecurityLevel(identity) < targetLevel) { Log.Write(Log.Level.Info, "Calculating up to required security level: {0}", targetLevel); Ts3Crypt.ImproveSecurity(identity, targetLevel); @@ -155,6 +156,7 @@ public override void Connect() tsFullClient.QuitMessage = QuitMessages[Util.Random.Next(0, QuitMessages.Length)]; tsFullClient.OnErrorEvent += TsFullClient_OnErrorEvent; + tsFullClient.OnDisconnected += TsFullClient_OnDisconnected; ConnectClient(); } @@ -199,36 +201,57 @@ private void ConnectClient() }); } - private void TsFullClient_OnErrorEvent(object sender, CommandError e) + private void TsFullClient_OnErrorEvent(object sender, CommandError error) { - switch (e.Id) + switch (error.Id) { case Ts3ErrorCode.whisper_no_targets: stallNoErrorCount = 0; isStall = true; break; - case Ts3ErrorCode.client_could_not_validate_identity: - if (ts3FullClientData.IdentityLevel == "auto") - { - int targetSecLevel = int.Parse(e.ExtraMessage); - Log.Write(Log.Level.Info, "Calculating up to required security level: {0}", targetSecLevel); - Ts3Crypt.ImproveSecurity(identity, targetSecLevel); - ts3FullClientData.IdentityOffset = identity.ValidKeyOffset; + default: + Log.Write(Log.Level.Debug, "Got ts3 error event: {0}", error.ErrorFormat()); + break; + } + } - ConnectClient(); - } - else + private void TsFullClient_OnDisconnected(object sender, DisconnectEventArgs e) + { + if (e.Error != null) + { + var error = e.Error; + switch (error.Id) { - Log.Write(Log.Level.Warning, "The server reported that the security level you set is not high enough." + - "Increase the value to \"{0}\" or set it to \"auto\" to generate it on demand when connecting.", e.ExtraMessage); - } - break; + case Ts3ErrorCode.client_could_not_validate_identity: + if (ts3FullClientData.IdentityLevel == "auto") + { + int targetSecLevel = int.Parse(error.ExtraMessage); + Log.Write(Log.Level.Info, "Calculating up to required security level: {0}", targetSecLevel); + Ts3Crypt.ImproveSecurity(identity, targetSecLevel); + ts3FullClientData.IdentityOffset = identity.ValidKeyOffset; - default: - Log.Write(Log.Level.Debug, "Got ts3 error event: {0}", e.ErrorFormat()); - break; + ConnectClient(); + return; // skip triggering event, we want to reconnect + } + else + { + Log.Write(Log.Level.Warning, "The server reported that the security level you set is not high enough." + + "Increase the value to \"{0}\" or set it to \"auto\" to generate it on demand when connecting.", error.ExtraMessage); + } + break; + + default: + Log.Write(Log.Level.Warning, "Could not connect: {0}", error.ErrorFormat()); + break; + } + } + else + { + Log.Write(Log.Level.Debug, "Bot disconnected. Reason: {0}", e.ExitReason); } + + OnBotDisconnect?.Invoke(this, new EventArgs()); } public override R GetSelf() diff --git a/TS3Client/DisconnectEventArgs.cs b/TS3Client/DisconnectEventArgs.cs index e7dce3f7..f936d10c 100644 --- a/TS3Client/DisconnectEventArgs.cs +++ b/TS3Client/DisconnectEventArgs.cs @@ -9,15 +9,18 @@ namespace TS3Client { + using Messages; using System; public class DisconnectEventArgs : EventArgs { public MoveReason ExitReason { get; } + public CommandError Error { get; } - public DisconnectEventArgs(MoveReason exitReason) + public DisconnectEventArgs(MoveReason exitReason, CommandError error = null) { ExitReason = exitReason; + Error = error; } } } diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index 783e46f2..a5fd1205 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -122,7 +122,7 @@ public override void Disconnect() } } - private void DisconnectInternal(ConnectionContext ctx, bool triggerEvent = true) + private void DisconnectInternal(ConnectionContext ctx, bool triggerEvent = true, CommandError error = null) { bool triggerEventSafe = false; @@ -154,7 +154,7 @@ private void DisconnectInternal(ConnectionContext ctx, bool triggerEvent = true) } if (triggerEventSafe) - OnDisconnected?.Invoke(this, new DisconnectEventArgs(packetHandler.ExitReason ?? MoveReason.LeftServer)); + OnDisconnected?.Invoke(this, new DisconnectEventArgs(packetHandler.ExitReason ?? MoveReason.LeftServer, error)); } private void InvokeEvent(LazyNotification lazyNotification) @@ -190,8 +190,8 @@ private void InvokeEvent(LazyNotification lazyNotification) case NotificationType.TextMessage: OnTextMessageReceived?.Invoke(this, notification.Cast()); break; case NotificationType.TokenUsed: break; // full client events - case NotificationType.InitIvExpand: ProcessInitIvExpand((InitIvExpand)notification.FirstOrDefault()); break; - case NotificationType.InitServer: ProcessInitServer((InitServer)notification.FirstOrDefault()); break; + case NotificationType.InitIvExpand: { var result = lazyNotification.WrapSingle(); if (result.Ok) ProcessInitIvExpand(result.Value); } break; + case NotificationType.InitServer: { var result = lazyNotification.WrapSingle(); if (result.Ok) ProcessInitServer(result.Value); } break; case NotificationType.ChannelList: break; case NotificationType.ChannelListFinished: ChannelSubscribeAll(); break; case NotificationType.ClientNeededPermissions: break; @@ -213,16 +213,24 @@ private void InvokeEvent(LazyNotification lazyNotification) case NotificationType.FileInfo: break; // special case NotificationType.Error: - lock (statusLock) { - if (status == Ts3ClientStatus.Connecting) + var result = lazyNotification.WrapSingle(); + var error = result.Ok ? result.Value : Util.CustomError("Got empty error while connecting."); + + bool skipError = false; + lock (statusLock) { - status = Ts3ClientStatus.Disconnected; - DisconnectInternal(context, false); + if (status == Ts3ClientStatus.Connecting) + { + skipError = true; + status = Ts3ClientStatus.Disconnected; + DisconnectInternal(context, true, error); + } } - } - OnErrorEvent?.Invoke(this, (CommandError)notification.First()); + if (!skipError) + OnErrorEvent?.Invoke(this, error); + } break; case NotificationType.Unknown: default: throw Util.UnhandledDefault(lazyNotification.NotifyType); diff --git a/TS3Client/LazyNotification.cs b/TS3Client/LazyNotification.cs index 7ad3899a..39df6723 100644 --- a/TS3Client/LazyNotification.cs +++ b/TS3Client/LazyNotification.cs @@ -11,6 +11,7 @@ namespace TS3Client { using Messages; using System.Collections.Generic; + using System.Linq; public struct LazyNotification { @@ -22,5 +23,13 @@ public LazyNotification(IEnumerable notifications, NotificationTy Notifications = notifications; NotifyType = notifyType; } + + public R WrapSingle() where T : INotification + { + var first = Notifications.FirstOrDefault(); + if (first == null) + return R.Err(Util.NoResultCommandError); + return R.OkR((T)first); + } } } diff --git a/TS3Client/Query/Ts3QueryClient.cs b/TS3Client/Query/Ts3QueryClient.cs index 957a2925..028809e8 100644 --- a/TS3Client/Query/Ts3QueryClient.cs +++ b/TS3Client/Query/Ts3QueryClient.cs @@ -104,7 +104,7 @@ private void NetworkLoop(object ctx) var message = line.Trim(); msgProc.PushMessage(message); } - OnDisconnected?.Invoke(this, new DisconnectEventArgs(MoveReason.LeftServer)); // TODO ?? + OnDisconnected?.Invoke(this, new DisconnectEventArgs(MoveReason.LeftServer)); } private void InvokeEvent(LazyNotification lazyNotification) diff --git a/TS3Client/Util.cs b/TS3Client/Util.cs index 1d36abfc..68f806b4 100644 --- a/TS3Client/Util.cs +++ b/TS3Client/Util.cs @@ -9,11 +9,11 @@ namespace TS3Client { + using Messages; + using System; using System.Collections.Generic; using System.Linq; using System.Text; - using System; - using Messages; internal static class Util { @@ -52,7 +52,7 @@ internal sealed class MissingEnumCaseException : Exception public MissingEnumCaseException(string message, Exception inner) : base(message, inner) { } } - /// Provides useful extension methods for error formatting. + /// Provides useful extension methods and error formatting. public static class Extensions { public static string ErrorFormat(this CommandError error) From 7c145e731587ef2f0f888425e1ecfb3c6358e445 Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 28 Dec 2017 12:19:32 +0100 Subject: [PATCH 19/48] Added audio routing framework + Added experimental Span for memory efficiency + Reworked a few methods for Span + Some refactoring --- .../Helper/AudioTags/AudioTagReader.cs | 2 +- TS3AudioBot/IPlayerConnection.cs | 1 - .../ResourceFactories/AudioResource.cs | 2 - .../ResourceFactories/IPlaylistFactory.cs | 2 - .../ResourceFactories/IResourceFactory.cs | 2 - .../ResourceFactories/IThumbnailFactory.cs | 3 +- TS3AudioBot/TS3AudioBot.csproj | 10 +- TS3AudioBot/Ts3Full.cs | 7 +- TS3AudioBot/packages.config | 4 +- TS3Client/Commands/Ts3Command.cs | 2 +- TS3Client/DocumentedEnums.cs | 5 +- TS3Client/EventDispatcher.cs | 1 + TS3Client/Full/Audio/AudioInterfaces.cs | 29 +++++ TS3Client/Full/Audio/AudioMeta.cs | 37 ++++++ TS3Client/Full/Audio/AudioPipeExtensions.cs | 31 +++++ TS3Client/Full/Audio/PreciseTimedPipe.cs | 31 +++++ TS3Client/Full/Audio/SplitterPipe.cs | 34 ++++++ TS3Client/Full/Audio/StaticMetaPipe.cs | 75 ++++++++++++ TS3Client/Full/Audio/StreamAudioProducer.cs | 16 +++ TS3Client/Full/IdentityData.cs | 22 +++- TS3Client/Full/PacketHandler.cs | 16 ++- TS3Client/Full/QuickerLz.cs | 36 ++---- TS3Client/Full/Ts3Crypt.cs | 107 ++++++++++-------- TS3Client/Full/Ts3FullClient.cs | 57 ++++++---- TS3Client/OwnEnums.cs | 8 +- TS3Client/R.cs | 2 +- TS3Client/TS3Client.csproj | 14 +++ TS3Client/Ts3Exceptions.cs | 1 - TS3Client/WaitBlock.cs | 2 +- TS3Client/packages.config | 2 + TS3Client/ts3protocol.md | 48 +++++++- 31 files changed, 476 insertions(+), 133 deletions(-) create mode 100644 TS3Client/Full/Audio/AudioInterfaces.cs create mode 100644 TS3Client/Full/Audio/AudioMeta.cs create mode 100644 TS3Client/Full/Audio/AudioPipeExtensions.cs create mode 100644 TS3Client/Full/Audio/PreciseTimedPipe.cs create mode 100644 TS3Client/Full/Audio/SplitterPipe.cs create mode 100644 TS3Client/Full/Audio/StaticMetaPipe.cs create mode 100644 TS3Client/Full/Audio/StreamAudioProducer.cs diff --git a/TS3AudioBot/Helper/AudioTags/AudioTagReader.cs b/TS3AudioBot/Helper/AudioTags/AudioTagReader.cs index ea49916b..7f779956 100644 --- a/TS3AudioBot/Helper/AudioTags/AudioTagReader.cs +++ b/TS3AudioBot/Helper/AudioTags/AudioTagReader.cs @@ -216,7 +216,7 @@ public override byte[] GetImage(BinaryReader fileStream) private static string DecodeString(byte type, byte[] textBuffer, int offset, int length) { - switch (textBuffer[0]) + switch (type) { case 0: return Encoding.GetEncoding(28591).GetString(textBuffer, offset, length); diff --git a/TS3AudioBot/IPlayerConnection.cs b/TS3AudioBot/IPlayerConnection.cs index 104db05f..269f61da 100644 --- a/TS3AudioBot/IPlayerConnection.cs +++ b/TS3AudioBot/IPlayerConnection.cs @@ -10,7 +10,6 @@ namespace TS3AudioBot { using System; - using Helper; public interface IPlayerConnection : IDisposable { diff --git a/TS3AudioBot/ResourceFactories/AudioResource.cs b/TS3AudioBot/ResourceFactories/AudioResource.cs index dac20b6e..04bddf54 100644 --- a/TS3AudioBot/ResourceFactories/AudioResource.cs +++ b/TS3AudioBot/ResourceFactories/AudioResource.cs @@ -7,8 +7,6 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -using System; - namespace TS3AudioBot.ResourceFactories { public sealed class PlayResource diff --git a/TS3AudioBot/ResourceFactories/IPlaylistFactory.cs b/TS3AudioBot/ResourceFactories/IPlaylistFactory.cs index 98019d6e..ac28fc9d 100644 --- a/TS3AudioBot/ResourceFactories/IPlaylistFactory.cs +++ b/TS3AudioBot/ResourceFactories/IPlaylistFactory.cs @@ -9,8 +9,6 @@ namespace TS3AudioBot.ResourceFactories { - using Helper; - public interface IPlaylistFactory : IFactory { MatchCertainty MatchPlaylist(string uri); diff --git a/TS3AudioBot/ResourceFactories/IResourceFactory.cs b/TS3AudioBot/ResourceFactories/IResourceFactory.cs index 4e7daba1..47e35a7d 100644 --- a/TS3AudioBot/ResourceFactories/IResourceFactory.cs +++ b/TS3AudioBot/ResourceFactories/IResourceFactory.cs @@ -9,8 +9,6 @@ namespace TS3AudioBot.ResourceFactories { - using Helper; - public interface IResourceFactory : IFactory { /// Check method to ask if a factory can load the given link. diff --git a/TS3AudioBot/ResourceFactories/IThumbnailFactory.cs b/TS3AudioBot/ResourceFactories/IThumbnailFactory.cs index b3de73c6..c14c92b7 100644 --- a/TS3AudioBot/ResourceFactories/IThumbnailFactory.cs +++ b/TS3AudioBot/ResourceFactories/IThumbnailFactory.cs @@ -9,11 +9,10 @@ namespace TS3AudioBot.ResourceFactories { - using Helper; using System.Drawing; public interface IThumbnailFactory : IFactory { R GetThumbnail(PlayResource playResource); } -} \ No newline at end of file +} diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index 410f6629..1edc22ee 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -58,8 +58,8 @@ - - ..\packages\LiteDB.4.0.0\lib\net40\LiteDB.dll + + ..\packages\LiteDB.4.1.0\lib\net40\LiteDB.dll ..\packages\Nett.0.8.0\lib\Net40\Nett.dll @@ -70,6 +70,12 @@ + + ..\packages\System.Memory.4.4.0-preview1-25305-02\lib\netstandard1.0\System.Memory.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index 8ef875f5..ee072c82 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -373,16 +373,17 @@ private void AudioSend() while (encoder.HasPacket) { var packet = encoder.GetPacket(); + var span = new ReadOnlySpan(packet.Array, 0, packet.Length); switch (SendMode) { case TargetSendMode.Voice: - tsFullClient.SendAudio(packet.Array, packet.Length, encoder.Codec); + tsFullClient.SendAudio(span, encoder.Codec); break; case TargetSendMode.Whisper: - tsFullClient.SendAudioWhisper(packet.Array, packet.Length, encoder.Codec, channelSubscriptionsCache, clientSubscriptionsCache); + tsFullClient.SendAudioWhisper(span, encoder.Codec, channelSubscriptionsCache, clientSubscriptionsCache); break; case TargetSendMode.WhisperGroup: - tsFullClient.SendAudioGroupWhisper(packet.Array, packet.Length, encoder.Codec, GroupWhisperType, GroupWhisperTarget); + tsFullClient.SendAudioGroupWhisper(span, encoder.Codec, GroupWhisperType, GroupWhisperTarget); break; } encoder.ReturnPacket(packet.Array); diff --git a/TS3AudioBot/packages.config b/TS3AudioBot/packages.config index d38e88d0..a8d94a4c 100644 --- a/TS3AudioBot/packages.config +++ b/TS3AudioBot/packages.config @@ -2,8 +2,10 @@ - + + + \ No newline at end of file diff --git a/TS3Client/Commands/Ts3Command.cs b/TS3Client/Commands/Ts3Command.cs index 0a885ffb..1fe311c8 100644 --- a/TS3Client/Commands/Ts3Command.cs +++ b/TS3Client/Commands/Ts3Command.cs @@ -37,7 +37,7 @@ public Ts3Command(string command) : this(command, NoParameter) { } public Ts3Command(string command, List parameter) { ExpectResponse = true; - this.Command = command; + Command = command; this.parameter = parameter; } diff --git a/TS3Client/DocumentedEnums.cs b/TS3Client/DocumentedEnums.cs index 3fa12d4e..29ed1eff 100644 --- a/TS3Client/DocumentedEnums.cs +++ b/TS3Client/DocumentedEnums.cs @@ -34,7 +34,7 @@ public enum HostBannerMode KeepAspect } - public enum Codec + public enum Codec : byte { ///mono, 16bit, 8kHz SpeexNarrowband = 0, @@ -48,6 +48,9 @@ public enum Codec OpusVoice, ///stereo, 16bit, 48kHz, optimized for music OpusMusic, + + /// Not encoded (Not supported by TeamSpeak) + Raw = 127, } public enum CodecEncryptionMode diff --git a/TS3Client/EventDispatcher.cs b/TS3Client/EventDispatcher.cs index 4f86bbfb..b9da879d 100644 --- a/TS3Client/EventDispatcher.cs +++ b/TS3Client/EventDispatcher.cs @@ -47,6 +47,7 @@ internal interface IEventDispatcher : IDisposable /// The main loop which will be receiving packets. /// The method to call asynchronously when a new /// notification comes in. + /// The current connection context. void Init(EvloopType eventLoop, Action dispatcher, ConnectionContext ctx); /// Dispatches the notification. /// diff --git a/TS3Client/Full/Audio/AudioInterfaces.cs b/TS3Client/Full/Audio/AudioInterfaces.cs new file mode 100644 index 00000000..3a8c93ac --- /dev/null +++ b/TS3Client/Full/Audio/AudioInterfaces.cs @@ -0,0 +1,29 @@ +namespace TS3Client.Full.Audio +{ + using System; + + public interface IAudioStream { } + + /// Passive producer will serve audio data that must be requested manually. + public interface IAudioPassiveProducer : IAudioStream + { + int Read(byte[] buffer, int offset, int length, out Meta meta); + } + /// Active producer will push audio to the out stream as soon as available. + public interface IAudioActiveProducer : IAudioStream + { + IAudioPassiveConsumer OutStream { get; set; } + } + /// Passive consumer will wait for manually passed audio data. + public interface IAudioPassiveConsumer : IAudioStream + { + void Write(ReadOnlySpan data, Meta meta); + } + /// Active consumer will pull audio data as soon as available. + public interface IAudioActiveConsumer : IAudioStream + { + IAudioPassiveProducer InStream { get; set; } + } + + public interface IAudioPipe : IAudioPassiveConsumer, IAudioActiveProducer { } +} diff --git a/TS3Client/Full/Audio/AudioMeta.cs b/TS3Client/Full/Audio/AudioMeta.cs new file mode 100644 index 00000000..28fe8317 --- /dev/null +++ b/TS3Client/Full/Audio/AudioMeta.cs @@ -0,0 +1,37 @@ +namespace TS3Client.Full.Audio +{ + using System.Collections.Generic; + using ClientIdT = System.UInt16; + using ChannelIdT = System.UInt64; + + public class Meta + { + public Codec? Codec; + public MetaIn In; + public MetaOut Out; + } + + public struct MetaIn + { + public ClientIdT Sender { get; set; } + public bool Whisper { get; set; } + } + + public class MetaOut + { + public TargetSendMode SendMode { get; set; } + 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 enum TargetSendMode + { + None, + Voice, + Whisper, + WhisperGroup, + } +} diff --git a/TS3Client/Full/Audio/AudioPipeExtensions.cs b/TS3Client/Full/Audio/AudioPipeExtensions.cs new file mode 100644 index 00000000..fb76cb92 --- /dev/null +++ b/TS3Client/Full/Audio/AudioPipeExtensions.cs @@ -0,0 +1,31 @@ +namespace TS3Client.Full.Audio +{ + public static class AudioPipeExtensions + { + public static TC Chain(this IAudioActiveProducer producer, TC addConsumer) where TC : IAudioPassiveConsumer + { + if (producer.OutStream == null) + { + producer.OutStream = addConsumer; + } + else if (producer is SplitterPipe splitter) + { + splitter.Add(addConsumer); + } + else + { + splitter = new SplitterPipe(); + splitter.Add(addConsumer); + splitter.Add(producer.OutStream); + producer.OutStream = splitter; + } + return addConsumer; + } + + public static T ChainNew(this IAudioActiveProducer producer) where T : IAudioPassiveConsumer, new() + { + var addConsumer = new T(); + return producer.Chain(addConsumer); + } + } +} diff --git a/TS3Client/Full/Audio/PreciseTimedPipe.cs b/TS3Client/Full/Audio/PreciseTimedPipe.cs new file mode 100644 index 00000000..af5d291e --- /dev/null +++ b/TS3Client/Full/Audio/PreciseTimedPipe.cs @@ -0,0 +1,31 @@ +namespace TS3Client.Full.Audio +{ + using System; + + public class PreciseTimedPipe : IAudioActiveConsumer, IAudioActiveProducer + { + public IAudioPassiveProducer InStream { get; set; } + public IAudioPassiveConsumer OutStream { get; set; } + + public PreciseTimedPipe() { } + + public PreciseTimedPipe(IAudioPassiveProducer inStream) + { + InStream = inStream; + } + + public void ReadTimed() + { + byte[] buffer = new byte[256]; + while (true) + { + System.Threading.Thread.Sleep(100); + var inStream = InStream; + if (inStream == null) + continue; + int read = inStream.Read(buffer, 0, buffer.Length, out var meta); + OutStream?.Write(new ReadOnlySpan(buffer, 0, read), meta); + } + } + } +} diff --git a/TS3Client/Full/Audio/SplitterPipe.cs b/TS3Client/Full/Audio/SplitterPipe.cs new file mode 100644 index 00000000..29439833 --- /dev/null +++ b/TS3Client/Full/Audio/SplitterPipe.cs @@ -0,0 +1,34 @@ +namespace TS3Client.Full.Audio +{ + using System; + using System.Collections.Generic; + + public class SplitterPipe : IAudioPassiveConsumer + { + private readonly ICollection consumerList = new List(); + + public bool CloneMeta { get; set; } = false; + + public IAudioPassiveConsumer OutStream + { + get => this; + set => Add(value); + } + + public void Add(IAudioPassiveConsumer addConsumer) + { + if (!consumerList.Contains(addConsumer) && addConsumer != this) + { + consumerList.Add(addConsumer); + } + } + + public void Write(ReadOnlySpan data, Meta meta) + { + foreach (var consumer in consumerList) + { + consumer.Write(data, meta); + } + } + } +} diff --git a/TS3Client/Full/Audio/StaticMetaPipe.cs b/TS3Client/Full/Audio/StaticMetaPipe.cs new file mode 100644 index 00000000..3d9b0759 --- /dev/null +++ b/TS3Client/Full/Audio/StaticMetaPipe.cs @@ -0,0 +1,75 @@ +namespace TS3Client.Full.Audio +{ + using System; + using System.Collections.Generic; + using ClientIdT = System.UInt16; + using ChannelIdT = System.UInt64; + + public class StaticMetaPipe : IAudioPipe + { + public IAudioPassiveConsumer OutStream { get; set; } + + private MetaOut setMeta = new MetaOut(); + public TargetSendMode SendMode { get; private set; } + + private void ClearData() + { + setMeta.ChannelIds = null; + setMeta.ClientIds = null; + } + + public void SetNone() + { + ClearData(); + SendMode = TargetSendMode.None; + } + + public void SetVoice() + { + ClearData(); + SendMode = TargetSendMode.Voice; + } + + public void SetWhisper(IReadOnlyList channelIds, IReadOnlyList clientIds) + { + ClearData(); + SendMode = TargetSendMode.Whisper; + setMeta.ChannelIds = channelIds; + setMeta.ClientIds = clientIds; + } + + public void SetWhisperGroup(GroupWhisperType type, GroupWhisperTarget target, ulong targetId = 0) + { + ClearData(); + SendMode = TargetSendMode.WhisperGroup; + setMeta.GroupWhisperType = type; + setMeta.GroupWhisperTarget = target; + setMeta.TargetId = targetId; + } + + public void Write(ReadOnlySpan data, Meta meta) + { + if (OutStream == null || SendMode == TargetSendMode.None) + return; + if (meta.Out == null) + meta.Out = new MetaOut(); + meta.Out.SendMode = SendMode; + switch (SendMode) + { + case TargetSendMode.None: break; + case TargetSendMode.Voice: break; + case TargetSendMode.Whisper: + meta.Out.ChannelIds = setMeta.ChannelIds; + meta.Out.ClientIds = setMeta.ClientIds; + break; + case TargetSendMode.WhisperGroup: + meta.Out.GroupWhisperTarget = setMeta.GroupWhisperTarget; + meta.Out.GroupWhisperType = setMeta.GroupWhisperType; + meta.Out.TargetId = setMeta.TargetId; + break; + default: + break; + } + } + } +} diff --git a/TS3Client/Full/Audio/StreamAudioProducer.cs b/TS3Client/Full/Audio/StreamAudioProducer.cs new file mode 100644 index 00000000..c9418ac5 --- /dev/null +++ b/TS3Client/Full/Audio/StreamAudioProducer.cs @@ -0,0 +1,16 @@ +namespace TS3Client.Full.Audio +{ + using System.IO; + + public class StreamAudioProducer : IAudioPassiveProducer + { + private readonly Stream stream; + + public int Read(byte[] buffer, int offset, int length, out Meta meta) + { + meta = default(Meta); + return stream.Read(buffer, offset, length); + } + public StreamAudioProducer(Stream stream) { this.stream = stream; } + } +} diff --git a/TS3Client/Full/IdentityData.cs b/TS3Client/Full/IdentityData.cs index 54e68748..37ecc5db 100644 --- a/TS3Client/Full/IdentityData.cs +++ b/TS3Client/Full/IdentityData.cs @@ -11,20 +11,28 @@ namespace TS3Client.Full { using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; + using System; /// Represents the identity of a user. /// To generate new identities use . /// To improve the security level of this identity use . public class IdentityData { + private string publicKeyString; + private string privateKeyString; + private string publicAndPrivateKeyString; + /// The public key encoded in base64. - public string PublicKeyString { get; set; } + public string PublicKeyString => publicKeyString ?? (publicKeyString = Ts3Crypt.ExportPublicKey(PublicKey)); /// The private key encoded in base64. - public string PrivateKeyString { get; set; } + public string PrivateKeyString => privateKeyString ?? (privateKeyString = Ts3Crypt.ExportPrivateKey(PrivateKey)); + /// The public and private key encoded in base64. + public string PublicAndPrivateKeyString => + publicAndPrivateKeyString ?? (publicAndPrivateKeyString = Ts3Crypt.ExportPublicAndPrivateKey(PublicKey, PrivateKey)); /// The public key represented as its cryptographic data structure. - public ECPoint PublicKey { get; set; } + public ECPoint PublicKey { get; } /// The private key represented as its cryptographic data structure. - public BigInteger PrivateKey { get; set; } + public BigInteger PrivateKey { get; } /// A number which is used to determine the security level of this identity. public ulong ValidKeyOffset { get; set; } /// When bruteforcing numbers linearly from 0, the last bruteforced number @@ -34,5 +42,11 @@ public class IdentityData private string clientUid; /// The client uid, which can be used in teamspeak for various features. public string ClientUid => clientUid ?? (clientUid = Ts3Crypt.GetUidFromPublicKey(PublicKeyString)); + + public IdentityData(BigInteger privateKey, ECPoint publicKey = null) + { + PrivateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey)); + PublicKey = publicKey ?? Ts3Crypt.RestorePublicFromPrivateKey(privateKey); + } } } diff --git a/TS3Client/Full/PacketHandler.cs b/TS3Client/Full/PacketHandler.cs index 67bc6704..8a030314 100644 --- a/TS3Client/Full/PacketHandler.cs +++ b/TS3Client/Full/PacketHandler.cs @@ -108,7 +108,7 @@ public void Connect(IPEndPoint address) resendThread.Start(); - AddOutgoingPacket(ts3Crypt.ProcessInit1(null), PacketType.Init1); + AddOutgoingPacket(ts3Crypt.ProcessInit1(null).Value, PacketType.Init1); } private void ConnectUdpClient(IPEndPoint address) @@ -136,7 +136,7 @@ public void Stop(MoveReason closeReason = MoveReason.LeftServer) } } - public void AddOutgoingPacket(byte[] packet, PacketType packetType, PacketFlags addFlags = PacketFlags.None) + public void AddOutgoingPacket(ReadOnlySpan packet, PacketType packetType, PacketFlags addFlags = PacketFlags.None) { lock (sendLoopLock) { @@ -152,7 +152,7 @@ public void AddOutgoingPacket(byte[] packet, PacketType packetType, PacketFlags var tmpCompress = QuickerLz.Compress(packet, 1); if (tmpCompress.Length < packet.Length) { - packet = tmpCompress.ToArr(); + packet = tmpCompress; addFlags |= PacketFlags.Compressed; } @@ -163,7 +163,7 @@ public void AddOutgoingPacket(byte[] packet, PacketType packetType, PacketFlags return; } } - AddOutgoingPacket(new OutgoingPacket(packet, packetType), addFlags); + AddOutgoingPacket(new OutgoingPacket(packet.ToArray(), packetType), addFlags); // TODO optimize to array here } } @@ -243,7 +243,7 @@ public void CryptoInitDone() IncPacketCounter(PacketType.Command); } - private static IEnumerable BuildSplitList(byte[] rawData, PacketType packetType) + private static IEnumerable BuildSplitList(ReadOnlySpan rawData, PacketType packetType) { int pos = 0; bool first = true; @@ -254,10 +254,8 @@ private static IEnumerable BuildSplitList(byte[] rawData, Packet { int blockSize = Math.Min(maxContent, rawData.Length - pos); if (blockSize <= 0) break; - - var tmpBuffer = new byte[blockSize]; - Array.Copy(rawData, pos, tmpBuffer, 0, blockSize); - var packet = new OutgoingPacket(tmpBuffer, packetType); + + var packet = new OutgoingPacket(rawData.Slice(pos, blockSize).ToArray(), packetType); // TODO optimize toarray call last = pos + blockSize == rawData.Length; if (first ^ last) diff --git a/TS3Client/Full/QuickerLz.cs b/TS3Client/Full/QuickerLz.cs index c79efe84..d61e420b 100644 --- a/TS3Client/Full/QuickerLz.cs +++ b/TS3Client/Full/QuickerLz.cs @@ -32,11 +32,11 @@ public static class QuickerLz [ThreadStatic] private static int[] cachetable; - public static ArrLim Compress(byte[] data, int level) + public static Span Compress(ReadOnlySpan data, int level) { if (level != 1) // && level != 3 throw new ArgumentException("This QuickLZ implementation supports only level 1 and 3 compress"); - if (data.LongLength >= int.MaxValue) + if (data.Length >= int.MaxValue) throw new ArgumentException($"This QuickLZ can only compress up to {int.MaxValue}"); int headerlen = data.Length < 216 ? 3 : 9; @@ -64,10 +64,11 @@ public static ArrLim Compress(byte[] data, int level) { if (sourcePos > data.Length / 2 && destPos > sourcePos - (sourcePos / 32)) { - Array.Copy(data, 0, dest, headerlen, data.Length); + data.CopyTo(new Span(dest, headerlen)); + //Array.Copy(data, 0, dest, headerlen, data.Length); destPos = headerlen + data.Length; WriteHeader(dest, destPos, data.Length, level, headerlen, false); - return new ArrLim(dest, destPos); + return new Span(dest, 0, destPos); } WriteU32(dest, controlPos, (control >> 1) | SetControl); // C controlPos = destPos; @@ -137,7 +138,7 @@ public static ArrLim Compress(byte[] data, int level) WriteU32(dest, controlPos, (control >> 1) | SetControl); // C WriteHeader(dest, destPos, data.Length, level, headerlen, true); - return new ArrLim(dest, destPos); + return new Span(dest, 0, destPos); } public static byte[] Decompress(byte[] data, int maxSize) @@ -295,7 +296,7 @@ private static ushort ReadU16(byte[] intArr, int inOff) => unchecked((ushort)(intArr[inOff] | (intArr[inOff + 1] << 8))); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Read24(byte[] intArr, int inOff) + private static int Read24(ReadOnlySpan intArr, int inOff) => unchecked(intArr[inOff] | (intArr[inOff + 1] << 8) | (intArr[inOff + 2] << 16)); [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -304,13 +305,13 @@ private static int ReadI32(byte[] intArr, int inOff) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint ReadU32(byte[] intArr, int inOff) - => unchecked ((uint)(intArr[inOff] | (intArr[inOff + 1] << 8) | (intArr[inOff + 2] << 16) | (intArr[inOff + 3] << 24))); + => unchecked((uint)(intArr[inOff] | (intArr[inOff + 1] << 8) | (intArr[inOff + 2] << 16) | (intArr[inOff + 3] << 24))); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Hash(int value) => ((value >> 12) ^ value) & 0xfff; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool Is6Same(byte[] arr, int i) + private static bool Is6Same(ReadOnlySpan arr, int i) { return arr[i + 0] == arr[i + 1] && arr[i + 1] == arr[i + 2] @@ -345,23 +346,4 @@ private static void UpdateHashtable(byte[] dest, int start, int end) } } } - - public struct ArrLim - { - public readonly byte[] Data; - public readonly int Length; - - public ArrLim(byte[] data, int length) - { - Data = data; - Length = length; - } - - public byte[] ToArr() - { - var res = new byte[Length]; - Array.Copy(Data, res, Length); - return res; - } - } } diff --git a/TS3Client/Full/Ts3Crypt.cs b/TS3Client/Full/Ts3Crypt.cs index 45cd32b0..28bdf88b 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TS3Client/Full/Ts3Crypt.cs @@ -32,7 +32,7 @@ public sealed class Ts3Crypt private const string DummyKeyAndNonceString = "c:\\windows\\system\\firewall32.cpl"; private static readonly byte[] DummyKey = Encoding.ASCII.GetBytes(DummyKeyAndNonceString.Substring(0, 16)); private static readonly byte[] DummyIv = Encoding.ASCII.GetBytes(DummyKeyAndNonceString.Substring(16, 16)); - private static readonly Tuple DummyKeyAndNonceTuple = new Tuple(DummyKey, DummyIv); + private static readonly (byte[], byte[]) DummyKeyAndNonceTuple = (DummyKey, DummyIv); private static readonly byte[] Ts3InitMac = Encoding.ASCII.GetBytes("TS3INIT1"); private static readonly byte[] Initversion = { 0x06, 0x3b, 0xec, 0xe9 }; private readonly EaxBlockCipher eaxCipher = new EaxBlockCipher(new AesEngine()); @@ -65,7 +65,8 @@ internal void Reset() #region KEY IMPORT/EXPROT /// This methods loads a secret identity. - /// The key stored in base64, encoded like the libtomcrypt export method (of a private key). + /// The key stored in base64, encoded like the libtomcrypt export method of a private key. + /// Or the TS3Client'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. @@ -74,23 +75,19 @@ public static IdentityData LoadIdentity(string key, ulong keyOffset, ulong lastC // Note: libtomcrypt stores the private AND public key when exporting a private key // This makes importing very convenient :) byte[] asnByteArray = Convert.FromBase64String(key); - var pubPrivKey = ImportPublicAndPrivateKey(asnByteArray); - return LoadIdentity(pubPrivKey, keyOffset, lastCheckedKeyOffset); + ImportKeyDynamic(asnByteArray, out var publicKey, out var privateKey); + return LoadIdentity(publicKey, privateKey, keyOffset, lastCheckedKeyOffset); } - private static IdentityData LoadIdentity(Tuple pubPrivKey, ulong keyOffset, ulong lastCheckedKeyOffset) + private static IdentityData LoadIdentity(ECPoint publicKey, BigInteger privateKey, ulong keyOffset, ulong lastCheckedKeyOffset) { - return new IdentityData + return new IdentityData(privateKey, publicKey) { - PublicKey = pubPrivKey.Item1, - PrivateKey = pubPrivKey.Item2, - PublicKeyString = ExportPublicKey(pubPrivKey.Item1), - PrivateKeyString = ExportPublicAndPrivateKey(pubPrivKey), ValidKeyOffset = keyOffset, LastCheckedKeyOffset = lastCheckedKeyOffset < keyOffset ? keyOffset : lastCheckedKeyOffset, }; } - + private static readonly ECKeyGenerationParameters KeyGenParams = new ECKeyGenerationParameters(X9ObjectIdentifiers.Prime256v1, new SecureRandom()); private static ECPoint ImportPublicKey(byte[] asnByteArray) @@ -103,37 +100,51 @@ private static ECPoint ImportPublicKey(byte[] asnByteArray) return ecPoint; } - private static Tuple ImportPublicAndPrivateKey(byte[] asnByteArray) + private static void ImportKeyDynamic(byte[] asnByteArray, out ECPoint publicKey, out BigInteger privateKey) { + privateKey = null; + publicKey = null; var asnKeyData = (DerSequence)Asn1Object.FromByteArray(asnByteArray); - var x = ((DerInteger)asnKeyData[2]).Value; - var y = ((DerInteger)asnKeyData[3]).Value; - var bigi = ((DerInteger)asnKeyData[4]).Value; - - var ecPoint = KeyGenParams.DomainParameters.Curve.CreatePoint(x, y); - return new Tuple(ecPoint, bigi); + var bitInfo = ((DerBitString)asnKeyData[0]).IntValue; + if (bitInfo == 0b0000_0000 || bitInfo == 0b1100_0000) + { + var x = ((DerInteger)asnKeyData[2]).Value; + var y = ((DerInteger)asnKeyData[3]).Value; + publicKey = KeyGenParams.DomainParameters.Curve.CreatePoint(x, y); + } + if (bitInfo == 0b1000_0000) + { + privateKey = ((DerInteger)asnKeyData[4]).Value; + } } - private static string ExportPublicKey(ECPoint publicKey) + internal static string ExportPublicKey(ECPoint publicKey) { var dataArray = new DerSequence( - new DerBitString(new byte[] { 0 }, 7), - new DerInteger(32), - new DerInteger(publicKey.AffineXCoord.ToBigInteger()), - new DerInteger(publicKey.AffineYCoord.ToBigInteger())).GetDerEncoded(); + new DerBitString(new byte[] { 0b0000_0000 }, 7), + new DerInteger(32), + new DerInteger(publicKey.AffineXCoord.ToBigInteger()), + new DerInteger(publicKey.AffineYCoord.ToBigInteger())).GetDerEncoded(); return Convert.ToBase64String(dataArray); } - // TODO Private only key + rework of identitydata public members + internal static string ExportPrivateKey(BigInteger privateKey) + { + var dataArray = new DerSequence( + new DerBitString(new byte[] { 0b1100_0000 }, 6), + new DerInteger(32), + new DerInteger(privateKey)).GetDerEncoded(); + return Convert.ToBase64String(dataArray); + } - private static string ExportPublicAndPrivateKey(Tuple pubPrivKey) + internal static string ExportPublicAndPrivateKey(ECPoint publicKey, BigInteger privateKey) { var dataArray = new DerSequence( - new DerBitString(new byte[] { 128 }, 7), - new DerInteger(32), - new DerInteger(pubPrivKey.Item1.AffineXCoord.ToBigInteger()), - new DerInteger(pubPrivKey.Item1.AffineYCoord.ToBigInteger()), - new DerInteger(pubPrivKey.Item2)).GetDerEncoded(); + new DerBitString(new byte[] { 0b1000_0000 }, 7), + new DerInteger(32), + new DerInteger(publicKey.AffineXCoord.ToBigInteger()), + new DerInteger(publicKey.AffineYCoord.ToBigInteger()), + new DerInteger(privateKey)).GetDerEncoded(); return Convert.ToBase64String(dataArray); } @@ -144,7 +155,7 @@ internal static string GetUidFromPublicKey(string publicKey) return Convert.ToBase64String(hashBytes); } - private static ECPoint RestorePublicFromPrivateKey(BigInteger privateKey) + internal static ECPoint RestorePublicFromPrivateKey(BigInteger privateKey) { var curve = ECNamedCurveTable.GetByOid(X9ObjectIdentifiers.Prime256v1); return curve.G.Multiply(privateKey).Normalize(); @@ -210,7 +221,7 @@ private void SetSharedSecret(byte[] alpha, byte[] beta, byte[] sharedKey) Array.Copy(buffer, 0, fakeSignature, 0, 8); } - internal byte[] ProcessInit1(byte[] data) + internal R ProcessInit1(byte[] data) { const int versionLen = 4; const int initTypeLen = 1; @@ -225,7 +236,7 @@ internal byte[] ProcessInit1(byte[] data) return sendData; } - if (data.Length < initTypeLen) return null; + if (data.Length < initTypeLen) return "Invalid init packet (too short)"; int type = data[0]; if (type == 1) { @@ -249,7 +260,9 @@ internal byte[] ProcessInit1(byte[] data) // Prepare solution int level = NetUtil.N2Hint(data, initTypeLen + 128); - byte[] y = SolveRsaChallange(data, initTypeLen, level); + var y = SolveRsaChallange(data, initTypeLen, level); + if (!y.Ok) + return y; // Copy bytes for this result: [Version..., InitType..., data..., y..., text...] var sendData = new byte[versionLen + initTypeLen + 232 + 64 + textBytes.Length]; @@ -260,14 +273,14 @@ internal byte[] ProcessInit1(byte[] data) // Copy data Array.Copy(data, initTypeLen, sendData, versionLen + initTypeLen, 232); // Copy y - Array.Copy(y, 0, sendData, versionLen + initTypeLen + 232 + (64 - y.Length), y.Length); + Array.Copy(y.Value, 0, sendData, versionLen + initTypeLen + 232 + (64 - y.Value.Length), y.Value.Length); // Copy text Array.Copy(textBytes, 0, sendData, versionLen + initTypeLen + 232 + 64, textBytes.Length); return sendData; } - else - return null; + + return "Got invalid init packet id"; } /// This method calculates x ^ (2^level) % n = y which is the solution to the server RSA puzzle. @@ -275,8 +288,11 @@ internal byte[] ProcessInit1(byte[] data) /// The offset of x and n in the data array. /// The exponent to x. /// The y value, unsigned, as a BigInteger bytearray. - private static byte[] SolveRsaChallange(byte[] data, int offset, int level) + private static R SolveRsaChallange(byte[] data, int offset, int level) { + if (level < 0 || level > 1_000_000) + return "RSA challange level is not within an acceptable range"; + // x is the base, n is the modulus. var x = new BigInteger(1, data, 00 + offset, 64); var n = new BigInteger(1, data, 64 + offset, 64); @@ -300,9 +316,9 @@ internal void Encrypt(OutgoingPacket packet) return; } - var keyNonce = GetKeyNonce(false, packet.PacketId, packet.GenerationId, packet.PacketType); + var (key, nonce) = GetKeyNonce(false, packet.PacketId, packet.GenerationId, packet.PacketType); packet.BuildHeader(); - ICipherParameters ivAndKey = new AeadParameters(new KeyParameter(keyNonce.Item1), 8 * MacLen, keyNonce.Item2, packet.Header); + ICipherParameters ivAndKey = new AeadParameters(new KeyParameter(key), 8 * MacLen, nonce, packet.Header); byte[] result; int len; @@ -371,10 +387,10 @@ internal bool Decrypt(IncomingPacket packet) private bool DecryptData(IncomingPacket packet) { Array.Copy(packet.Raw, MacLen, packet.Header, 0, InHeaderLen); - var keyNonce = GetKeyNonce(true, packet.PacketId, packet.GenerationId, packet.PacketType); + var (key, nonce) = GetKeyNonce(true, packet.PacketId, packet.GenerationId, packet.PacketType); int dataLen = packet.Raw.Length - (MacLen + InHeaderLen); - ICipherParameters ivAndKey = new AeadParameters(new KeyParameter(keyNonce.Item1), 8 * MacLen, keyNonce.Item2, packet.Header); + ICipherParameters ivAndKey = new AeadParameters(new KeyParameter(key), 8 * MacLen, nonce, packet.Header); try { byte[] result; @@ -410,7 +426,7 @@ private static bool FakeDecrypt(IncomingPacket packet, byte[] mac) /// Each time the packetId reaches 65535 the next packet will go on with 0 and the generationId will be increased by 1. /// The packetType. /// A tuple of (key, nonce) - private Tuple GetKeyNonce(bool fromServer, ushort packetId, uint generationId, PacketType packetType) + private (byte[] key, byte[] nonce) GetKeyNonce(bool fromServer, ushort packetId, uint generationId, PacketType packetType) { if (!CryptoInitComplete) return DummyKeyAndNonceTuple; @@ -449,7 +465,7 @@ private Tuple GetKeyNonce(bool fromServer, ushort packetId, uint key[0] ^= (byte)((packetId >> 8) & 0xFF); key[1] ^= (byte)((packetId) & 0xFF); - return new Tuple(key, nonce); + return (key, nonce); } #endregion @@ -561,8 +577,7 @@ public static IdentityData GenerateNewIdentity(int securityLevel = 8) var privateKey = (ECPrivateKeyParameters)keyPair.Private; var publicKey = (ECPublicKeyParameters)keyPair.Public; - var pubPrivKey = new Tuple(publicKey.Q.Normalize(), privateKey.D); - var identity = LoadIdentity(pubPrivKey, 0, 0); + var identity = LoadIdentity(publicKey.Q.Normalize(), privateKey.D, 0, 0); ImproveSecurity(identity, securityLevel); return identity; } diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index a5fd1205..569da9ce 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -9,6 +9,7 @@ namespace TS3Client.Full { + using Audio; using Commands; using Messages; using System; @@ -26,7 +27,7 @@ namespace TS3Client.Full using ChannelGroupIdT = System.UInt64; /// Creates a full TeampSpeak3 client with voice capabilities. - public sealed class Ts3FullClient : Ts3BaseFunctions + public sealed class Ts3FullClient : Ts3BaseFunctions, IAudioActiveProducer, IAudioPassiveConsumer { private readonly Ts3Crypt ts3Crypt; private readonly PacketHandler packetHandler; @@ -271,15 +272,20 @@ private void NetworkLoop(object ctxObject) case PacketType.Voice: case PacketType.VoiceWhisper: - // VOICE - + OutStream?.Write(packet.Data, new Meta + { + In = new MetaIn + { + Whisper = packet.PacketType == PacketType.VoiceWhisper + } + }); break; case PacketType.Init1: var forwardData = ts3Crypt.ProcessInit1(packet.Data); - if (forwardData == null) + if (!forwardData.Ok) break; - packetHandler.AddOutgoingPacket(forwardData, PacketType.Init1); + packetHandler.AddOutgoingPacket(forwardData.Value, PacketType.Init1); break; } } @@ -332,9 +338,8 @@ private void ProcessConnectionInfoRequest() /// The raw 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 null instead). - /// Returns an enumeration of the deserialized and split up in objects data. - /// Or null if no reponse is expected. - /// When the response has an error code. + /// Returns R(OK) with an enumeration of the deserialized and split up in objects data. + /// Or R(ERR) with the returned error if no reponse is expected. public override R, CommandError> SendCommand(Ts3Command com) { using (var wb = new WaitBlock()) @@ -395,6 +400,15 @@ public override void Dispose() dispatcher?.Dispose(); } + #region Audio + /// Incomming Voice data + public IAudioPassiveConsumer OutStream { get; set; } + public void Write(ReadOnlySpan data, Meta meta) + { + // TODO + } + #endregion + #region FULLCLIENT SPECIFIC COMMANDS public CmdR ChangeIsChannelCommander(bool isChannelCommander) @@ -433,21 +447,20 @@ public CmdR ChannelSubscribeAll() public CmdR ChannelUnsubscribeAll() => Send("channelunsubscribeall"); - public void SendAudio(byte[] buffer, int length, Codec codec) + public void SendAudio(ReadOnlySpan data, Codec codec) { // [X,X,Y,DATA] // > X is a ushort in H2N order of a own audio packet counter // it seems it can be the same as the packet counter so we will let the packethandler do it. // > Y is the codec byte (see Enum) - byte[] tmpBuffer = new byte[length + 3]; + byte[] tmpBuffer = new byte[data.Length + 3]; tmpBuffer[2] = (byte)codec; - Array.Copy(buffer, 0, tmpBuffer, 3, length); - buffer = tmpBuffer; + data.CopyTo(new Span(tmpBuffer, 3)); - packetHandler.AddOutgoingPacket(buffer, PacketType.Voice); + packetHandler.AddOutgoingPacket(tmpBuffer, PacketType.Voice); } - public void SendAudioWhisper(byte[] buffer, int length, Codec codec, IList channelIds, IList clientIds) + public void SendAudioWhisper(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 a own audio packet counter @@ -458,7 +471,7 @@ public void SendAudioWhisper(byte[] buffer, int length, Codec codec, IList U is a ulong in H2N order of each targeted channelId, (U...U) is repeated N times // > T is a ushort in H2N order of each targeted clientId, (T...T) is repeated M times int offset = 2 + 1 + 2 + channelIds.Count * 8 + clientIds.Count * 2; - byte[] tmpBuffer = new byte[length + offset]; + byte[] tmpBuffer = new byte[data.Length + offset]; tmpBuffer[2] = (byte)codec; tmpBuffer[3] = (byte)channelIds.Count; tmpBuffer[4] = (byte)clientIds.Count; @@ -466,13 +479,12 @@ public void SendAudioWhisper(byte[] buffer, int length, Codec codec, IList(tmpBuffer, offset)); - packetHandler.AddOutgoingPacket(buffer, PacketType.VoiceWhisper); + packetHandler.AddOutgoingPacket(tmpBuffer, PacketType.VoiceWhisper); } - public void SendAudioGroupWhisper(byte[] buffer, int length, Codec codec, GroupWhisperType type, GroupWhisperTarget target, ulong targetId = 0) + public void SendAudioGroupWhisper(ReadOnlySpan data, Codec codec, GroupWhisperType type, GroupWhisperTarget target, ulong targetId = 0) { // [X,X,Y,N,M,U,U,U,U,U,U,U,U,DATA] // > X is a ushort in H2N order of a own audio packet counter @@ -481,15 +493,14 @@ public void SendAudioGroupWhisper(byte[] buffer, int length, Codec codec, GroupW // > N is a byte, specifying the GroupWhisperType // > M is a byte, specifying the GroupWhisperTarget // > U is a ulong in H2N order for the targeted channelId or groupId (0 if not applicable) - byte[] tmpBuffer = new byte[length + 13]; + byte[] tmpBuffer = new byte[data.Length + 13]; tmpBuffer[2] = (byte)codec; tmpBuffer[3] = (byte)type; tmpBuffer[4] = (byte)target; NetUtil.H2N(targetId, tmpBuffer, 5); - Array.Copy(buffer, 0, tmpBuffer, 13, length); - buffer = tmpBuffer; + data.CopyTo(new Span(tmpBuffer, 13)); - packetHandler.AddOutgoingPacket(buffer, PacketType.VoiceWhisper, PacketFlags.Newprotocol); + packetHandler.AddOutgoingPacket(tmpBuffer, PacketType.VoiceWhisper, PacketFlags.Newprotocol); } public R GetClientConnectionInfo(ClientIdT clientId) diff --git a/TS3Client/OwnEnums.cs b/TS3Client/OwnEnums.cs index 1e386cc4..3e8c1650 100644 --- a/TS3Client/OwnEnums.cs +++ b/TS3Client/OwnEnums.cs @@ -121,11 +121,15 @@ public enum MoveReason public enum GroupWhisperType : byte { - /// Requires servergroup targetId + /// Targets all users in the specified server group. + /// (Requires servergroup targetId) ServerGroup = 0, - /// Requires channelgroup targetId + /// Targets all users in the specified channel group. + /// (Requires channelgroup targetId) ChannelGroup, + /// Targets all users with channel commander. ChannelCommander, + /// Targets all users on the server. AllClients, } diff --git a/TS3Client/R.cs b/TS3Client/R.cs index 06eacb1c..e45f9870 100644 --- a/TS3Client/R.cs +++ b/TS3Client/R.cs @@ -86,7 +86,7 @@ public struct R private R(TError error) { isError = true; Value = default(TSuccess); if (error == null) throw new ArgumentNullException(nameof(error), "Error must not be null."); Error = error; } /// Creates a new failed result with an error object - /// The error + /// The error public static R Err(TError error) => new R(error); /// Creates a new successful result with a value /// The value diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index 0b0b84c1..71193b74 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -26,6 +26,7 @@ true Auto + 7 pdbonly @@ -48,6 +49,12 @@ ..\packages\Heijden.Dns.2.0.0\lib\net35\Heijden.Dns.dll + + ..\packages\System.Memory.4.4.0-preview1-25305-02\lib\netstandard1.0\System.Memory.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll @@ -61,6 +68,13 @@ + + + + + + + diff --git a/TS3Client/Ts3Exceptions.cs b/TS3Client/Ts3Exceptions.cs index d21ef286..7fac36da 100644 --- a/TS3Client/Ts3Exceptions.cs +++ b/TS3Client/Ts3Exceptions.cs @@ -9,7 +9,6 @@ namespace TS3Client { - using Messages; using System; /// Generel exeption when methods within the client fail. diff --git a/TS3Client/WaitBlock.cs b/TS3Client/WaitBlock.cs index 854d0c01..450707aa 100644 --- a/TS3Client/WaitBlock.cs +++ b/TS3Client/WaitBlock.cs @@ -23,7 +23,7 @@ internal sealed class WaitBlock : IDisposable private string commandLine; public NotificationType[] DependsOn { get; } private LazyNotification notification; - public bool isDisposed; + private bool isDisposed; private static readonly TimeSpan CommandTimeout = TimeSpan.FromSeconds(15); public WaitBlock(NotificationType[] dependsOn = null) diff --git a/TS3Client/packages.config b/TS3Client/packages.config index 766a6c93..1b2310c8 100644 --- a/TS3Client/packages.config +++ b/TS3Client/packages.config @@ -2,5 +2,7 @@ + + \ No newline at end of file diff --git a/TS3Client/ts3protocol.md b/TS3Client/ts3protocol.md index 64ecf90c..2bf29bca 100644 --- a/TS3Client/ts3protocol.md +++ b/TS3Client/ts3protocol.md @@ -173,11 +173,14 @@ The following chapter describes the data structure for different packet types. | C | u8 | Codec Type | | Data | var | Voice Data | -### 1.8.2.1 VoiceWhisper +### 1.8.2.1 VoiceWhisper (Client -> Server) +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ | VId |C |N |M | U* | T* | Data | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ +For direct user/channel targeting +The `Newprotocol` Flag must be *unset* + | Name | Type | Explanation | |------|-------|---------------------------------------| | VId | u16 | Voice Packet Id | @@ -188,6 +191,49 @@ The following chapter describes the data structure for different packet types. | T | [u16] | Targeted ClientIds, repeated M times | | Data | var | Voice Data | +OR + + +--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ + | VId |C |TY|TA| U | Data | + +--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ + +For targeting special groups +The `Newprotocol` Flag must be *set* + +| Name | Type | Explanation | +|------|-------|---------------------------------------------------------| +| VId | u16 | Voice Packet Id | +| C | u8 | Codec Type | +| TY | u8 | GroupWhisperType (see below) | +| TA | u8 | GroupWhisperTarget (see below) | +| U | u64 | the targeted channelId or groupId (0 if not applicable) | +| Data | var | Voice Data | + +``` +enum GroupWhisperType : u8 +{ + // Targets all users in the specified server group. + ServerGroup = 0 /* U = servergroup targetId */, + // Targets all users in the specified channel group. + ChannelGroup = 1 /* U = channelgroup targetId */, + // Targets all users with channel commander. + ChannelCommander = 2, /* U = 0 (ignored) */, + // Targets all users on the server. + AllClients = 3, /* U = 0 (ignored) */, +} + +enum GroupWhisperTarget : u8 +{ + AllChannels = 0, + CurrentChannel = 1, + ParentChannel = 2, + AllParentChannel = 3, + ChannelFamily = 4, + CompleteChannelFamily = 5, + Subchannels = 6, +} +``` + ### 1.8.2.2 VoiceWhisper (Client <- Server) +--+--+--+--+--+---------//---------+ | VId | CId |C | Data | From b5316658293b4528aa1eabc79c8bf6398f539e67 Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 28 Dec 2017 15:35:17 +0100 Subject: [PATCH 20/48] Changed volume fo float --- TS3AudioBot/Audio/AudioEncoder.cs | 6 +++--- TS3AudioBot/Commands.cs | 10 +++++----- TS3AudioBot/IPlayerConnection.cs | 2 +- TS3AudioBot/PlayManager.cs | 12 ++++++------ TS3AudioBot/Ts3Full.cs | 8 ++++---- TS3Client/DocumentedEnums.cs | 2 +- TS3Client/Full/Audio/AudioInterfaces.cs | 2 ++ TS3Client/Full/Audio/AudioPacketReader.cs | 24 +++++++++++++++++++++++ TS3Client/Full/Audio/SplitterPipe.cs | 2 +- TS3Client/Full/Audio/StaticMetaPipe.cs | 8 ++++---- TS3Client/Full/NetUtil.cs | 7 +++++++ TS3Client/Full/Ts3FullClient.cs | 6 +++--- TS3Client/TS3Client.csproj | 1 + 13 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 TS3Client/Full/Audio/AudioPacketReader.cs diff --git a/TS3AudioBot/Audio/AudioEncoder.cs b/TS3AudioBot/Audio/AudioEncoder.cs index 58292b59..823fbf9a 100644 --- a/TS3AudioBot/Audio/AudioEncoder.cs +++ b/TS3AudioBot/Audio/AudioEncoder.cs @@ -88,15 +88,15 @@ private byte[] GetFreeArray() return new byte[opusEncoder.MaxDataBytes]; } - public void PushPcmAudio(byte[] buffer, int bufferlen) + public void PushPcmAudio(ReadOnlySpan buffer) { - int newSoundBufferLength = bufferlen + notEncodedBufferLength; + int newSoundBufferLength = buffer.Length + notEncodedBufferLength; if (newSoundBufferLength > soundBuffer.Length) soundBuffer = new byte[newSoundBufferLength]; soundBufferLength = newSoundBufferLength; Array.Copy(notEncodedBuffer, 0, soundBuffer, 0, notEncodedBufferLength); - Array.Copy(buffer, 0, soundBuffer, notEncodedBufferLength, bufferlen); + buffer.CopyTo(new Span(soundBuffer, notEncodedBufferLength)); int byteCap = OptimalPacketSize; int segmentCount = (int)Math.Floor((float)soundBufferLength / byteCap); diff --git a/TS3AudioBot/Commands.cs b/TS3AudioBot/Commands.cs index a9475991..9a111130 100644 --- a/TS3AudioBot/Commands.cs +++ b/TS3AudioBot/Commands.cs @@ -1186,16 +1186,16 @@ public static JsonObject CommandVersion() public static JsonObject CommandVolume(ExecutionInformation info, string parameter) { if (string.IsNullOrEmpty(parameter)) - return new JsonSingleValue("Current volume: " + info.Bot.PlayerConnection.Volume, info.Bot.PlayerConnection.Volume); + return new JsonSingleValue("Current volume: " + info.Bot.PlayerConnection.Volume, info.Bot.PlayerConnection.Volume); bool relPos = parameter.StartsWith("+", StringComparison.Ordinal); bool relNeg = parameter.StartsWith("-", StringComparison.Ordinal); string numberString = (relPos || relNeg) ? parameter.Remove(0, 1) : parameter; - if (!int.TryParse(numberString, out int volume)) + if (!float.TryParse(numberString, NumberStyles.Float, CultureInfo.InvariantCulture, out var volume)) throw new CommandException("The new volume could not be parsed", CommandExceptionReason.CommandError); - int newVolume; + float newVolume; if (relPos) newVolume = info.Bot.PlayerConnection.Volume + volume; else if (relNeg) newVolume = info.Bot.PlayerConnection.Volume - volume; else newVolume = volume; @@ -1262,13 +1262,13 @@ private static string ResponseVolume(ExecutionInformation info) { if (info.HasRights(RightHighVolume)) { - if (info.Session.ResponseData is int respInt) + if (info.Session.ResponseData is float respInt) { info.Bot.PlayerConnection.Volume = respInt; } else { - Log.Write(Log.Level.Error, "responseData is not an int."); + Log.Write(Log.Level.Error, "responseData is not an float."); return "Internal error"; } } diff --git a/TS3AudioBot/IPlayerConnection.cs b/TS3AudioBot/IPlayerConnection.cs index 269f61da..20e78159 100644 --- a/TS3AudioBot/IPlayerConnection.cs +++ b/TS3AudioBot/IPlayerConnection.cs @@ -15,7 +15,7 @@ public interface IPlayerConnection : IDisposable { event EventHandler OnSongEnd; - int Volume { get; set; } + float Volume { get; set; } TimeSpan Position { get; set; } bool Repeated { get; set; } bool Paused { get; set; } diff --git a/TS3AudioBot/PlayManager.cs b/TS3AudioBot/PlayManager.cs index ac612bc1..05629c7a 100644 --- a/TS3AudioBot/PlayManager.cs +++ b/TS3AudioBot/PlayManager.cs @@ -237,7 +237,7 @@ public sealed class MetaData /// Defaults to: invoker.DbId - Can be set if the owner of a song differs from the invoker. public ulong? ResourceOwnerDbId { get; set; } /// Defaults to: AudioFramwork.Defaultvolume - Overrides the starting volume. - public int? Volume { get; set; } = null; + public float? Volume { get; set; } = null; /// Default: false - Indicates whether the song has been requested from a playlist. public bool FromPlaylist { get; set; } @@ -314,20 +314,20 @@ public override bool Equals(object obj) public static class AudioValues { - public const int MaxVolume = 100; + public const float MaxVolume = 100; internal static AudioFrameworkData audioFrameworkData; - public static int MaxUserVolume => audioFrameworkData.MaxUserVolume; - public static int DefaultVolume => audioFrameworkData.DefaultVolume; + public static float MaxUserVolume => audioFrameworkData.MaxUserVolume; + public static float DefaultVolume => audioFrameworkData.DefaultVolume; } public class AudioFrameworkData : ConfigData { [Info("The default volume a song should start with", "10")] - public int DefaultVolume { get; set; } + public float DefaultVolume { get; set; } [Info("The maximum volume a normal user can request", "30")] - public int MaxUserVolume { get; set; } + public float MaxUserVolume { get; set; } [Info("How the bot should play music. Options are: whisper, voice, (!...)", "whisper")] public string AudioMode { get; set; } } diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index ee072c82..d4821842 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -368,7 +368,7 @@ private void AudioSend() break; AudioModifier.AdjustVolume(audioBuffer, read, volume); - encoder.PushPcmAudio(audioBuffer, read); + encoder.PushPcmAudio(new ReadOnlySpan(audioBuffer, 0, read)); while (encoder.HasPacket) { @@ -430,14 +430,14 @@ public TimeSpan Position } } - public int Volume + public float Volume { - get => (int)Math.Round(volume * AudioValues.MaxVolume); + get => volume * AudioValues.MaxVolume; set { if (value < 0 || value > AudioValues.MaxVolume) throw new ArgumentOutOfRangeException(nameof(value)); - volume = value / (float)AudioValues.MaxVolume; + volume = value / AudioValues.MaxVolume; } } diff --git a/TS3Client/DocumentedEnums.cs b/TS3Client/DocumentedEnums.cs index 29ed1eff..ece3736f 100644 --- a/TS3Client/DocumentedEnums.cs +++ b/TS3Client/DocumentedEnums.cs @@ -49,7 +49,7 @@ public enum Codec : byte ///stereo, 16bit, 48kHz, optimized for music OpusMusic, - /// Not encoded (Not supported by TeamSpeak) + /// PCM S16LE 2 Channel (TS3Client extension; not supported by normal TeamSpeak 3 Clients!) Raw = 127, } diff --git a/TS3Client/Full/Audio/AudioInterfaces.cs b/TS3Client/Full/Audio/AudioInterfaces.cs index 3a8c93ac..2894131b 100644 --- a/TS3Client/Full/Audio/AudioInterfaces.cs +++ b/TS3Client/Full/Audio/AudioInterfaces.cs @@ -4,6 +4,8 @@ namespace TS3Client.Full.Audio public interface IAudioStream { } + // TODO add static codec info + /// Passive producer will serve audio data that must be requested manually. public interface IAudioPassiveProducer : IAudioStream { diff --git a/TS3Client/Full/Audio/AudioPacketReader.cs b/TS3Client/Full/Audio/AudioPacketReader.cs new file mode 100644 index 00000000..f0b09a39 --- /dev/null +++ b/TS3Client/Full/Audio/AudioPacketReader.cs @@ -0,0 +1,24 @@ +namespace TS3Client.Full.Audio +{ + using System; + + class AudioPacketReader : IAudioPipe + { + public IAudioPassiveConsumer OutStream { get; set; } + + public void Write(ReadOnlySpan data, Meta meta) + { + if (OutStream == null) + return; + + if (data.Length < 5) // Invalid packet + return; + + // Skip [0,2) Voice Packet Id for now + // TODO add packet id order checking + meta.In.Sender = NetUtil.N2Hushort(data.Slice(2, 2)); + meta.Codec = (Codec)data[5]; + OutStream?.Write(data.Slice(5), meta); + } + } +} diff --git a/TS3Client/Full/Audio/SplitterPipe.cs b/TS3Client/Full/Audio/SplitterPipe.cs index 29439833..6b9fd47b 100644 --- a/TS3Client/Full/Audio/SplitterPipe.cs +++ b/TS3Client/Full/Audio/SplitterPipe.cs @@ -25,7 +25,7 @@ public void Add(IAudioPassiveConsumer addConsumer) public void Write(ReadOnlySpan data, Meta meta) { - foreach (var consumer in consumerList) + foreach (var consumer in consumerList) // TODO threadsafe { consumer.Write(data, meta); } diff --git a/TS3Client/Full/Audio/StaticMetaPipe.cs b/TS3Client/Full/Audio/StaticMetaPipe.cs index 3d9b0759..4de3f93f 100644 --- a/TS3Client/Full/Audio/StaticMetaPipe.cs +++ b/TS3Client/Full/Audio/StaticMetaPipe.cs @@ -51,8 +51,8 @@ public void Write(ReadOnlySpan data, Meta meta) { if (OutStream == null || SendMode == TargetSendMode.None) return; - if (meta.Out == null) - meta.Out = new MetaOut(); + + meta.Out = meta.Out ?? new MetaOut(); meta.Out.SendMode = SendMode; switch (SendMode) { @@ -67,9 +67,9 @@ public void Write(ReadOnlySpan data, Meta meta) meta.Out.GroupWhisperType = setMeta.GroupWhisperType; meta.Out.TargetId = setMeta.TargetId; break; - default: - break; + default: break; } + OutStream?.Write(data, meta); } } } diff --git a/TS3Client/Full/NetUtil.cs b/TS3Client/Full/NetUtil.cs index 651b7a4a..110d17d8 100644 --- a/TS3Client/Full/NetUtil.cs +++ b/TS3Client/Full/NetUtil.cs @@ -9,6 +9,7 @@ namespace TS3Client.Full { + using System; using System.Net; using System.Runtime.CompilerServices; @@ -23,6 +24,12 @@ public static ushort N2Hushort(byte[] intArr, int inOff) return unchecked((ushort)((intArr[inOff] << 8) | intArr[inOff + 1])); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort N2Hushort(ReadOnlySpan buf) + { + return unchecked((ushort)((buf[0] << 8) | buf[1])); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int N2Hint(byte[] intArr, int inOff) { diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index 569da9ce..f8bf131f 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -123,7 +123,7 @@ public override void Disconnect() } } - private void DisconnectInternal(ConnectionContext ctx, bool triggerEvent = true, CommandError error = null) + private void DisconnectInternal(ConnectionContext ctx, CommandError error = null) { bool triggerEventSafe = false; @@ -139,7 +139,7 @@ private void DisconnectInternal(ConnectionContext ctx, bool triggerEvent = true, packetHandler.Stop(); msgProc.DropQueue(); dispatcher.Dispose(); - triggerEventSafe = triggerEvent; + triggerEventSafe = true; break; case Ts3ClientStatus.Disconnecting: break; @@ -225,7 +225,7 @@ private void InvokeEvent(LazyNotification lazyNotification) { skipError = true; status = Ts3ClientStatus.Disconnected; - DisconnectInternal(context, true, error); + DisconnectInternal(context, error); } } diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index 71193b74..918702a0 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -70,6 +70,7 @@ + From 833a2162aad9406a1a053f6a90d37d994ef7e4a8 Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 28 Dec 2017 15:55:30 +0100 Subject: [PATCH 21/48] Fixed new dynamic key import --- TS3Client/Full/Ts3Crypt.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/TS3Client/Full/Ts3Crypt.cs b/TS3Client/Full/Ts3Crypt.cs index 28bdf88b..524f0aa8 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TS3Client/Full/Ts3Crypt.cs @@ -106,15 +106,20 @@ private static void ImportKeyDynamic(byte[] asnByteArray, out ECPoint publicKey, publicKey = null; var asnKeyData = (DerSequence)Asn1Object.FromByteArray(asnByteArray); var bitInfo = ((DerBitString)asnKeyData[0]).IntValue; - if (bitInfo == 0b0000_0000 || bitInfo == 0b1100_0000) + if (bitInfo == 0b0000_0000 || bitInfo == 0b1000_0000) { var x = ((DerInteger)asnKeyData[2]).Value; var y = ((DerInteger)asnKeyData[3]).Value; publicKey = KeyGenParams.DomainParameters.Curve.CreatePoint(x, y); + + if (bitInfo == 0b1000_0000) + { + privateKey = ((DerInteger)asnKeyData[4]).Value; + } } - if (bitInfo == 0b1000_0000) + else if (bitInfo == 0b1100_0000) { - privateKey = ((DerInteger)asnKeyData[4]).Value; + privateKey = ((DerInteger)asnKeyData[2]).Value; } } @@ -279,7 +284,7 @@ internal R ProcessInit1(byte[] data) return sendData; } - + return "Got invalid init packet id"; } From ae792a7d8a02b559f2ca377205589f9a62484723 Mon Sep 17 00:00:00 2001 From: Splamy Date: Sat, 30 Dec 2017 15:20:36 +0100 Subject: [PATCH 22/48] Moar audio pipes + Implemented I/O for ts3client + Done Splitter + Done Volume --- TS3AudioBot/Audio/AudioModifier.cs | 50 -------------------- TS3AudioBot/TS3AudioBot.csproj | 1 - TS3AudioBot/Ts3Full.cs | 5 +- TS3Client/Full/Audio/AudioInterfaces.cs | 2 +- TS3Client/Full/Audio/AudioPacketReader.cs | 7 +-- TS3Client/Full/Audio/AudioPipeExtensions.cs | 7 ++- TS3Client/Full/Audio/EncoderPipe.cs | 14 ++++++ TS3Client/Full/Audio/PreciseTimedPipe.cs | 2 +- TS3Client/Full/Audio/SplitterPipe.cs | 47 +++++++++++++++++-- TS3Client/Full/Audio/StaticMetaPipe.cs | 2 +- TS3Client/Full/Audio/VolumePipe.cs | 51 +++++++++++++++++++++ TS3Client/Full/Ts3FullClient.cs | 29 ++++++++++-- TS3Client/TS3Client.csproj | 2 + 13 files changed, 150 insertions(+), 69 deletions(-) delete mode 100644 TS3AudioBot/Audio/AudioModifier.cs create mode 100644 TS3Client/Full/Audio/EncoderPipe.cs create mode 100644 TS3Client/Full/Audio/VolumePipe.cs diff --git a/TS3AudioBot/Audio/AudioModifier.cs b/TS3AudioBot/Audio/AudioModifier.cs deleted file mode 100644 index c2f67b1a..00000000 --- a/TS3AudioBot/Audio/AudioModifier.cs +++ /dev/null @@ -1,50 +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.Audio -{ - using System; - - internal static class AudioModifier - { - public static void AdjustVolume(byte[] audioSamples, int length, float volume) - { - if (volume.IsAbout(1)) - return; - else if (volume.IsAbout(0)) - { - Array.Clear(audioSamples, 0, length); - return; - } - - if (BitConverter.IsLittleEndian) - { - for (int i = 0; i < length; i += 2) - { - short value = (short)((audioSamples[i + 1]) << 8 | audioSamples[i]); - var tmpshort = (short)(value * volume); - audioSamples[i + 0] = (byte)((tmpshort & 0x00FF) >> 0); - audioSamples[i + 1] = (byte)((tmpshort & 0xFF00) >> 8); - } - } - else - { - for (int i = 0; i < length; i += 2) - { - short value = (short)((audioSamples[i + 1]) | (audioSamples[i] << 8)); - var tmpshort = (short)(value * volume); - audioSamples[i + 0] = (byte)((tmpshort & 0xFF00) >> 8); - audioSamples[i + 1] = (byte)((tmpshort & 0x00FF) >> 0); - } - } - } - - private static bool IsAbout(this float value, float compare) => Math.Abs(value - compare) < 1E-03f; - } -} diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index 1edc22ee..38539200 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -86,7 +86,6 @@ - diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index d4821842..61e47d2c 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -367,8 +367,9 @@ private void AudioSend() if (!doSend) break; - AudioModifier.AdjustVolume(audioBuffer, read, volume); - encoder.PushPcmAudio(new ReadOnlySpan(audioBuffer, 0, read)); + var bufSpan = new Span(audioBuffer, 0, read); + TS3Client.Full.Audio.VolumePipe.AdjustVolume(bufSpan, volume); + encoder.PushPcmAudio(bufSpan); while (encoder.HasPacket) { diff --git a/TS3Client/Full/Audio/AudioInterfaces.cs b/TS3Client/Full/Audio/AudioInterfaces.cs index 2894131b..f89eac08 100644 --- a/TS3Client/Full/Audio/AudioInterfaces.cs +++ b/TS3Client/Full/Audio/AudioInterfaces.cs @@ -19,7 +19,7 @@ public interface IAudioActiveProducer : IAudioStream /// Passive consumer will wait for manually passed audio data. public interface IAudioPassiveConsumer : IAudioStream { - void Write(ReadOnlySpan data, Meta meta); + void Write(Span data, Meta meta); } /// Active consumer will pull audio data as soon as available. public interface IAudioActiveConsumer : IAudioStream diff --git a/TS3Client/Full/Audio/AudioPacketReader.cs b/TS3Client/Full/Audio/AudioPacketReader.cs index f0b09a39..1a57c3b4 100644 --- a/TS3Client/Full/Audio/AudioPacketReader.cs +++ b/TS3Client/Full/Audio/AudioPacketReader.cs @@ -2,11 +2,11 @@ namespace TS3Client.Full.Audio { using System; - class AudioPacketReader : IAudioPipe + public class AudioPacketReader : IAudioPipe { public IAudioPassiveConsumer OutStream { get; set; } - public void Write(ReadOnlySpan data, Meta meta) + public void Write(Span data, Meta meta) { if (OutStream == null) return; @@ -16,8 +16,9 @@ public void Write(ReadOnlySpan data, Meta meta) // Skip [0,2) Voice Packet Id for now // TODO add packet id order checking + // TODO add defragment start meta.In.Sender = NetUtil.N2Hushort(data.Slice(2, 2)); - meta.Codec = (Codec)data[5]; + meta.Codec = (Codec)data[4]; OutStream?.Write(data.Slice(5), meta); } } diff --git a/TS3Client/Full/Audio/AudioPipeExtensions.cs b/TS3Client/Full/Audio/AudioPipeExtensions.cs index fb76cb92..7b89aea5 100644 --- a/TS3Client/Full/Audio/AudioPipeExtensions.cs +++ b/TS3Client/Full/Audio/AudioPipeExtensions.cs @@ -1,8 +1,10 @@ namespace TS3Client.Full.Audio { + using System; + public static class AudioPipeExtensions { - public static TC Chain(this IAudioActiveProducer producer, TC addConsumer) where TC : IAudioPassiveConsumer + public static T Chain(this IAudioActiveProducer producer, T addConsumer) where T : IAudioPassiveConsumer { if (producer.OutStream == null) { @@ -22,9 +24,10 @@ public static TC Chain(this IAudioActiveProducer producer, TC addConsumer) w return addConsumer; } - public static T ChainNew(this IAudioActiveProducer producer) where T : IAudioPassiveConsumer, new() + public static T Chain(this IAudioActiveProducer producer, Action init = null) where T : IAudioPassiveConsumer, new() { var addConsumer = new T(); + init?.Invoke(addConsumer); return producer.Chain(addConsumer); } } diff --git a/TS3Client/Full/Audio/EncoderPipe.cs b/TS3Client/Full/Audio/EncoderPipe.cs new file mode 100644 index 00000000..4b38c6b2 --- /dev/null +++ b/TS3Client/Full/Audio/EncoderPipe.cs @@ -0,0 +1,14 @@ +namespace TS3Client.Full.Audio +{ + using System; + + public class EncoderPipe : IAudioPipe + { + public IAudioPassiveConsumer OutStream { get; set; } + + public void Write(Span data, Meta meta) + { + throw new NotImplementedException(); + } + } +} diff --git a/TS3Client/Full/Audio/PreciseTimedPipe.cs b/TS3Client/Full/Audio/PreciseTimedPipe.cs index af5d291e..42c77f90 100644 --- a/TS3Client/Full/Audio/PreciseTimedPipe.cs +++ b/TS3Client/Full/Audio/PreciseTimedPipe.cs @@ -24,7 +24,7 @@ public void ReadTimed() if (inStream == null) continue; int read = inStream.Read(buffer, 0, buffer.Length, out var meta); - OutStream?.Write(new ReadOnlySpan(buffer, 0, read), meta); + OutStream?.Write(new Span(buffer, 0, read), meta); } } } diff --git a/TS3Client/Full/Audio/SplitterPipe.cs b/TS3Client/Full/Audio/SplitterPipe.cs index 6b9fd47b..5e61d370 100644 --- a/TS3Client/Full/Audio/SplitterPipe.cs +++ b/TS3Client/Full/Audio/SplitterPipe.cs @@ -5,7 +5,11 @@ namespace TS3Client.Full.Audio public class SplitterPipe : IAudioPassiveConsumer { - private readonly ICollection consumerList = new List(); + private readonly List safeConsumerList = new List(); + private readonly List consumerList = new List(); + private bool changed = false; + private readonly object listLock = new object(); + private byte[] buffer = new byte[0]; public bool CloneMeta { get; set; } = false; @@ -19,16 +23,49 @@ public void Add(IAudioPassiveConsumer addConsumer) { if (!consumerList.Contains(addConsumer) && addConsumer != this) { - consumerList.Add(addConsumer); + lock (listLock) + { + consumerList.Add(addConsumer); + changed = true; + } } } - public void Write(ReadOnlySpan data, Meta meta) + public void Write(Span data, Meta meta) { - foreach (var consumer in consumerList) // TODO threadsafe + if (changed) { - consumer.Write(data, meta); + lock (listLock) + { + if (changed) + { + safeConsumerList.Clear(); + safeConsumerList.AddRange(consumerList); + changed = false; + } + } } + + if (safeConsumerList.Count == 0) + return; + + if (safeConsumerList.Count == 1) + { + safeConsumerList[0].Write(data, meta); + return; + } + + if(buffer.Length < data.Length) + buffer = new byte[data.Length]; + + var bufSpan = new Span(buffer, 0, data.Length); + for (int i = 0; i < safeConsumerList.Count - 1; i++) + { + data.CopyTo(bufSpan); + safeConsumerList[i].Write(bufSpan, meta); + } + // safe one memcopy call by passing the last one our original data + safeConsumerList[safeConsumerList.Count - 1].Write(data, meta); } } } diff --git a/TS3Client/Full/Audio/StaticMetaPipe.cs b/TS3Client/Full/Audio/StaticMetaPipe.cs index 4de3f93f..b935ca62 100644 --- a/TS3Client/Full/Audio/StaticMetaPipe.cs +++ b/TS3Client/Full/Audio/StaticMetaPipe.cs @@ -47,7 +47,7 @@ public void SetWhisperGroup(GroupWhisperType type, GroupWhisperTarget target, ul setMeta.TargetId = targetId; } - public void Write(ReadOnlySpan data, Meta meta) + public void Write(Span data, Meta meta) { if (OutStream == null || SendMode == TargetSendMode.None) return; diff --git a/TS3Client/Full/Audio/VolumePipe.cs b/TS3Client/Full/Audio/VolumePipe.cs new file mode 100644 index 00000000..47d3d93f --- /dev/null +++ b/TS3Client/Full/Audio/VolumePipe.cs @@ -0,0 +1,51 @@ +namespace TS3Client.Full.Audio +{ + using System; + + public class VolumePipe : IAudioPipe + { + public float Volume { get; set; } = 1; + public IAudioPassiveConsumer OutStream { get; set; } + + public static void AdjustVolume(Span audioSamples, float volume) + { + if (IsAbout(volume, 1)) { /* Do nothing */ } + else if (IsAbout(volume, 0)) + { + audioSamples.Fill(0); + } + else if (IsAbout(volume, 0.5f)) + { + // fast calculation for *0.5 volume + for (int i = 0; i < audioSamples.Length; i += 2) + { + short value = (short)((audioSamples[i + 1]) << 8 | audioSamples[i]); + var tmpshort = value >> 1; + audioSamples[i + 0] = (byte)(tmpshort >> 0); + audioSamples[i + 1] = (byte)(tmpshort >> 8); + } + } + else + { + for (int i = 0; i < audioSamples.Length; i += 2) + { + short value = (short)((audioSamples[i + 1]) << 8 | audioSamples[i]); + var tmpshort = (short)(value * volume); + audioSamples[i + 0] = (byte)(tmpshort >> 0); + audioSamples[i + 1] = (byte)(tmpshort >> 8); + } + } + } + + private static bool IsAbout(float value, float compare) => Math.Abs(value - compare) < 1E-04f; + + public void Write(Span data, Meta meta) + { + if (OutStream == null) return; + + AdjustVolume(data, Volume); + + OutStream?.Write(data, meta); + } + } +} diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index f8bf131f..b14ae991 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -401,11 +401,34 @@ public override void Dispose() } #region Audio - /// Incomming Voice data + /// Incomming voice packets. public IAudioPassiveConsumer OutStream { get; set; } - public void Write(ReadOnlySpan data, Meta meta) + /// Outgoing voice data. + /// The encoded audio buffer. + /// The metadata where to send the packet. + public void Write(Span data, Meta meta) { - // TODO + if (meta.Out == null + || meta.Out.SendMode == TargetSendMode.None + || !meta.Codec.HasValue + || meta.Codec.Value == Codec.Raw) + return; + + switch (meta.Out.SendMode) + { + case TargetSendMode.None: + break; + case TargetSendMode.Voice: + SendAudio(data, meta.Codec.Value); + break; + case TargetSendMode.Whisper: + SendAudioWhisper(data, meta.Codec.Value, meta.Out.ChannelIds, meta.Out.ClientIds); + break; + case TargetSendMode.WhisperGroup: + SendAudioGroupWhisper(data, meta.Codec.Value, meta.Out.GroupWhisperType, meta.Out.GroupWhisperTarget, meta.Out.TargetId); + break; + default: break; + } } #endregion diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index 918702a0..039f5330 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -72,10 +72,12 @@ + + From f093b3da01e1c958e226fb0075ed96129dc16423 Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 4 Jan 2018 16:41:56 +0100 Subject: [PATCH 23/48] Integrated audio pipes into audiobot --- .travis.yml | 2 +- TS3ABotUnitTests/UnitTests.cs | 75 ---- TS3AudioBot/Audio/AudioEncoder.cs | 153 ------- TS3AudioBot/Audio/CustomTargetPipe.cs | 160 +++++++ TS3AudioBot/Audio/FfmpegProducer.cs | 193 ++++++++ TS3AudioBot/Bot.cs | 2 +- TS3AudioBot/Commands.cs | 1 + TS3AudioBot/Core.cs | 2 +- TS3AudioBot/Helper/PositionedStreamReader.cs | 189 -------- TS3AudioBot/ITargetManager.cs | 9 +- TS3AudioBot/TS3AudioBot.csproj | 11 +- TS3AudioBot/TeamspeakControl.cs | 1 + TS3AudioBot/Ts3Full.cs | 411 +++--------------- TS3Client/ColorDbg.cs | 3 +- TS3Client/Commands/CommandDeserializer.cs | 4 +- TS3Client/Commands/CommandOption.cs | 1 + TS3Client/Commands/CommandParameter.cs | 1 + TS3Client/Commands/Ts3Command.cs | 1 + TS3Client/DocumentedEnums.cs | 2 +- TS3Client/FileTransferManager.cs | 1 + TS3Client/Full/Audio/AudioInterfaces.cs | 7 + TS3Client/Full/Audio/EncoderPipe.cs | 103 ++++- .../Full}/Audio/Opus/LICENSE | 0 .../Full}/Audio/Opus/NativeMethods.cs | 17 +- .../Full}/Audio/Opus/OPUS_LICENSE | 0 .../Full}/Audio/Opus/OpusDecoder.cs | 2 +- .../Full}/Audio/Opus/OpusEncoder.cs | 10 +- .../Full}/Audio/Opus/README | 0 .../Full}/Audio/PreciseAudioTimer.cs | 13 +- TS3Client/Full/Audio/PreciseTimedPipe.cs | 113 ++++- TS3Client/Full/Audio/VolumePipe.cs | 14 +- TS3Client/Full/BasePacket.cs | 2 + TS3Client/Full/NetworkStats.cs | 1 + TS3Client/Full/PacketHandler.cs | 3 +- TS3Client/Full/Ts3Crypt.cs | 10 +- TS3Client/Full/Ts3FullClient.cs | 3 +- TS3Client/Helper/NativeWinDllLoader.cs | 42 ++ TS3Client/{ => Helper}/R.cs | 0 TS3Client/{ => Helper}/Util.cs | 12 +- TS3Client/LazyNotification.cs | 1 + TS3Client/MessageProcessor.cs | 1 + TS3Client/Messages/MessageTemplates.cs | 4 +- TS3Client/Messages/MessageTemplates.tt | 4 +- TS3Client/OwnEnums.cs | 1 + TS3Client/Query/Ts3QueryClient.cs | 1 + TS3Client/TS3Client.csproj | 12 +- TS3Client/Ts3BaseClient.cs | 1 + TS3Client/WaitBlock.cs | 1 + TS3Client/ts3protocol.md | 4 +- 49 files changed, 760 insertions(+), 844 deletions(-) delete mode 100644 TS3AudioBot/Audio/AudioEncoder.cs create mode 100644 TS3AudioBot/Audio/CustomTargetPipe.cs create mode 100644 TS3AudioBot/Audio/FfmpegProducer.cs delete mode 100644 TS3AudioBot/Helper/PositionedStreamReader.cs rename {TS3AudioBot => TS3Client/Full}/Audio/Opus/LICENSE (100%) rename {TS3AudioBot => TS3Client/Full}/Audio/Opus/NativeMethods.cs (93%) rename {TS3AudioBot => TS3Client/Full}/Audio/Opus/OPUS_LICENSE (100%) rename {TS3AudioBot => TS3Client/Full}/Audio/Opus/OpusDecoder.cs (99%) rename {TS3AudioBot => TS3Client/Full}/Audio/Opus/OpusEncoder.cs (95%) rename {TS3AudioBot => TS3Client/Full}/Audio/Opus/README (100%) rename {TS3AudioBot => TS3Client/Full}/Audio/PreciseAudioTimer.cs (88%) create mode 100644 TS3Client/Helper/NativeWinDllLoader.cs rename TS3Client/{ => Helper}/R.cs (100%) rename TS3Client/{ => Helper}/Util.cs (93%) diff --git a/.travis.yml b/.travis.yml index b7368e5e..269619fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ after_success: - export MAIN_DIR=`pwd` - cd ./TS3AudioBot/bin/Release - ls - - zip TS3AudioBot.zip TS3AudioBot.exe TS3Client.dll Nett.dll LiteDB.dll Heijden.Dns.dll BouncyCastle.Crypto.dll x64/* x86/* + - zip TS3AudioBot.zip *.exe *.dll x64/* x86/* - 'export version=`mono TS3AudioBot.exe --version | grep "Version: "`' - "curl -I -H \"Content-Type: application/zip\" -X PUT \"https://splamy.de/api/nightly/ts3ab/${TRAVIS_BRANCH}?token=${uploadkey}&filename=TS3AudioBot.zip&commit=${TRAVIS_COMMIT}&version=${version:9}\" --upload-file ./TS3AudioBot.zip" - cd "$MAIN_DIR" diff --git a/TS3ABotUnitTests/UnitTests.cs b/TS3ABotUnitTests/UnitTests.cs index 3e7499fa..e3bf554e 100644 --- a/TS3ABotUnitTests/UnitTests.cs +++ b/TS3ABotUnitTests/UnitTests.cs @@ -173,81 +173,6 @@ public void HistoryFileIntergrityTest() File.Delete(testFile); } - [Test] - public void PositionedStreamReaderLineEndings() - { - using (var memStream = new MemoryStream()) - { - // Setting streams up - var writer = new StreamWriter(memStream); - string[] values = { - "11\n", - "22\n", - "33\n", - "44\r", - "55\r", - "66\r", - "77\r\n", - "88\r\n", - "99\r\n", - "xx\n","\r", - "yy\n","\r", - "zz\n","\r", - "a\r", - "b\n", - "c\r\n", - "d\n","\r", - "e" }; - foreach (var val in values) - writer.Write(val); - writer.Flush(); - - memStream.Seek(0, SeekOrigin.Begin); - var reader = new PositionedStreamReader(memStream); - - int pos = 0; - foreach (var val in values) - { - var line = reader.ReadLine(); - pos += val.Length; - - Assert.AreEqual(val.TrimEnd('\r', '\n'), line); - Assert.AreEqual(pos, reader.ReadPosition); - } - } - } - - [Test] - public void PositionedStreamReaderBufferSize() - { - using (var memStream = new MemoryStream()) - { - // Setting streams up - var writer = new StreamWriter(memStream); - string[] values = { - new string('1', 1024) + '\n', // 1025: 1 over buffer size - new string('1', 1023) + '\n', // 1024: exactly the buffer size, but 1 over the 1024 line block due to the previous - new string('1', 1022) + '\n', // 1023: 1 less then the buffer size, should now match the line block again - new string('1', 1024) }; - foreach (var val in values) - writer.Write(val); - writer.Flush(); - - memStream.Seek(0, SeekOrigin.Begin); - var reader = new PositionedStreamReader(memStream); - - int pos = 0; - foreach (var val in values) - { - var line = reader.ReadLine(); - pos += val.Length; - - Assert.AreEqual(val.TrimEnd('\r', '\n'), line); - Assert.AreEqual(pos, reader.ReadPosition); - } - } - } - [Test] public void UtilSeedTest() { diff --git a/TS3AudioBot/Audio/AudioEncoder.cs b/TS3AudioBot/Audio/AudioEncoder.cs deleted file mode 100644 index 823fbf9a..00000000 --- a/TS3AudioBot/Audio/AudioEncoder.cs +++ /dev/null @@ -1,153 +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.Audio -{ - using Helper; - using Opus; - using System; - using System.Collections.Generic; - using TS3Client; - - // NOT Thread-Safe - internal class AudioEncoder : IDisposable - { - public Codec Codec { get; } - public int SampleRate { get; } - public int Channels { get; } - public int BitsPerSample { get; } - - public int OptimalPacketSize { get; } - public int Bitrate { get => opusEncoder.Bitrate; set => opusEncoder.Bitrate = value; } - - public bool HasPacket => opusQueue.Count > 0; - - // opus - private readonly OpusEncoder opusEncoder; - - private const int SegmentFrames = 960; - private byte[] soundBuffer = new byte[0]; - private int soundBufferLength; - private byte[] notEncodedBuffer = new byte[0]; - private int notEncodedBufferLength; - private readonly byte[] segment; - private readonly Queue opusQueue; - private readonly Queue freeArrays; - - public AudioEncoder(Codec codec) - { - Util.Init(out opusQueue); - Util.Init(out freeArrays); - Codec = codec; - - switch (codec) - { - case Codec.SpeexNarrowband: - throw new NotSupportedException(); - case Codec.SpeexWideband: - throw new NotSupportedException(); - case Codec.SpeexUltraWideband: - throw new NotSupportedException(); - case Codec.CeltMono: - throw new NotSupportedException(); - - case Codec.OpusVoice: - SampleRate = 48000; - Channels = 1; - opusEncoder = OpusEncoder.Create(SampleRate, Channels, Application.Voip); - Bitrate = 8192 * 2; - break; - - case Codec.OpusMusic: - SampleRate = 48000; - Channels = 2; - opusEncoder = OpusEncoder.Create(SampleRate, Channels, Application.Audio); - Bitrate = 8192 * 4; - break; - - default: - throw new ArgumentOutOfRangeException(nameof(codec)); - } - - BitsPerSample = 16; - OptimalPacketSize = opusEncoder.FrameByteCount(SegmentFrames); - segment = new byte[OptimalPacketSize]; - } - - private byte[] GetFreeArray() - { - if (freeArrays.Count > 0) - return freeArrays.Dequeue(); - else - return new byte[opusEncoder.MaxDataBytes]; - } - - public void PushPcmAudio(ReadOnlySpan buffer) - { - int newSoundBufferLength = buffer.Length + notEncodedBufferLength; - if (newSoundBufferLength > soundBuffer.Length) - soundBuffer = new byte[newSoundBufferLength]; - soundBufferLength = newSoundBufferLength; - - Array.Copy(notEncodedBuffer, 0, soundBuffer, 0, notEncodedBufferLength); - buffer.CopyTo(new Span(soundBuffer, notEncodedBufferLength)); - - int byteCap = OptimalPacketSize; - int segmentCount = (int)Math.Floor((float)soundBufferLength / byteCap); - int segmentsEnd = segmentCount * byteCap; - int newNotEncodedBufferLength = soundBufferLength - segmentsEnd; - if (newNotEncodedBufferLength > notEncodedBuffer.Length) - notEncodedBuffer = new byte[newNotEncodedBufferLength]; - notEncodedBufferLength = newNotEncodedBufferLength; - Array.Copy(soundBuffer, segmentsEnd, notEncodedBuffer, 0, notEncodedBufferLength); - - for (int i = 0; i < segmentCount; i++) - { - for (int j = 0; j < segment.Length; j++) - segment[j] = soundBuffer[(i * byteCap) + j]; - byte[] encodedBuffer = GetFreeArray(); - opusEncoder.Encode(segment, segment.Length, encodedBuffer, out int len); - opusQueue.Enqueue(new PartialArray { Array = encodedBuffer, Length = len }); - } - } - - public PartialArray GetPacket() - { - if (!HasPacket) - throw new InvalidOperationException(); - return opusQueue.Dequeue(); - } - - public void ReturnPacket(byte[] packet) - { - freeArrays.Enqueue(packet); - } - - public TimeSpan GetPlayLength(int bytes) - { - return TimeSpan.FromSeconds(bytes / (double)(SampleRate * (BitsPerSample / 8) * Channels)); - } - - public void Clear() - { - opusQueue.Clear(); - } - - public void Dispose() - { - opusEncoder?.Dispose(); - } - } - - internal struct PartialArray - { - public byte[] Array; - public int Length; - } -} diff --git a/TS3AudioBot/Audio/CustomTargetPipe.cs b/TS3AudioBot/Audio/CustomTargetPipe.cs new file mode 100644 index 00000000..7eba64cb --- /dev/null +++ b/TS3AudioBot/Audio/CustomTargetPipe.cs @@ -0,0 +1,160 @@ +namespace TS3AudioBot.Audio +{ + using Helper; + using TS3Client; + using TS3Client.Helper; + using TS3Client.Full; + using TS3Client.Full.Audio; + using TS3Client.Messages; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal class CustomTargetPipe : ITargetManager, IAudioPassiveConsumer + { + public TargetSendMode SendMode { get; set; } = TargetSendMode.None; + public ulong GroupWhisperTargetId { get; set; } + public GroupWhisperType GroupWhisperType { get; set; } + public GroupWhisperTarget GroupWhisperTarget { get; set; } + + private readonly Dictionary channelSubscriptionsSetup; + private readonly List clientSubscriptionsSetup; + private ulong[] channelSubscriptionsCache; + private ushort[] clientSubscriptionsCache; + private bool subscriptionSetupChanged; + private readonly object subscriptionLockObj = new object(); + + private readonly Ts3FullClient client; + + public CustomTargetPipe(Ts3FullClient client) + { + this.client = client; + Util.Init(out channelSubscriptionsSetup); + Util.Init(out clientSubscriptionsSetup); + subscriptionSetupChanged = true; + } + + public void Write(Span data, Meta meta) + { + UpdatedSubscriptionCache(); + + var codec = meta?.Codec ?? Codec.OpusMusic; // a bit hacky + switch (SendMode) + { + case TargetSendMode.None: + break; + case TargetSendMode.Voice: + client.SendAudio(data, codec); + break; + case TargetSendMode.Whisper: + client.SendAudioWhisper(data, codec, channelSubscriptionsCache, clientSubscriptionsCache); + break; + case TargetSendMode.WhisperGroup: + client.SendAudioGroupWhisper(data, codec, GroupWhisperType, GroupWhisperTarget, GroupWhisperTargetId); + break; + default: break; + } + } + + // TODO get this somehow to the front of the pipechain, to prevent further chain execution + // if (SendMode == TargetSendMode.Whisper) + // doSend &= channelSubscriptionsCache.Length > 0 || clientSubscriptionsCache.Length > 0; + + #region ITargetManager + + public void SetGroupWhisper(GroupWhisperType type, GroupWhisperTarget target, ulong targetId = 0) + { + GroupWhisperType = type; + GroupWhisperTarget = target; + GroupWhisperTargetId = targetId; + } + + public void WhisperChannelSubscribe(ulong channel, bool temp) + { + // TODO move to requested channel + // TODO spawn new client + lock (subscriptionLockObj) + { + if (channelSubscriptionsSetup.TryGetValue(channel, out var subscriptionTemp)) + channelSubscriptionsSetup[channel] = !subscriptionTemp || !temp; + else + { + channelSubscriptionsSetup[channel] = !temp; + subscriptionSetupChanged = true; + } + } + } + + public void WhisperChannelUnsubscribe(ulong channel, bool temp) + { + lock (subscriptionLockObj) + { + if (!temp) + { + subscriptionSetupChanged |= channelSubscriptionsSetup.Remove(channel); + } + else + { + if (channelSubscriptionsSetup.TryGetValue(channel, out bool subscriptionTemp) && subscriptionTemp) + { + channelSubscriptionsSetup.Remove(channel); + subscriptionSetupChanged = true; + } + } + } + } + + public void WhisperClientSubscribe(ushort userId) + { + lock (subscriptionLockObj) + { + if (!clientSubscriptionsSetup.Contains(userId)) + clientSubscriptionsSetup.Add(userId); + subscriptionSetupChanged = true; + } + } + + public void WhisperClientUnsubscribe(ushort userId) + { + lock (subscriptionLockObj) + { + clientSubscriptionsSetup.Remove(userId); + subscriptionSetupChanged = true; + } + } + + public void ClearTemporary() + { + lock (subscriptionLockObj) + { + ulong[] removeList = channelSubscriptionsSetup + .Where(kvp => kvp.Value) + .Select(kvp => kvp.Key) + .ToArray(); + foreach (var chan in removeList) + { + channelSubscriptionsSetup.Remove(chan); + subscriptionSetupChanged = true; + } + } + } + + private void UpdatedSubscriptionCache() + { + if (!subscriptionSetupChanged) + return; + lock (subscriptionLockObj) + { + if (!subscriptionSetupChanged) + return; + channelSubscriptionsCache = channelSubscriptionsSetup.Keys.ToArray(); + clientSubscriptionsCache = clientSubscriptionsSetup.ToArray(); + subscriptionSetupChanged = false; + } + } + + #endregion + } +} diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs new file mode 100644 index 00000000..8e875c09 --- /dev/null +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -0,0 +1,193 @@ +namespace TS3AudioBot.Audio +{ + using Helper; + using System; + using System.Diagnostics; + using System.Globalization; + using System.Text.RegularExpressions; + using TS3Client.Full.Audio; + + class FfmpegProducer : IAudioPassiveProducer, ISampleInfo + { + public event EventHandler OnSongEnd; + + private static readonly Regex FindDurationMatch = new Regex(@"^\s*Duration: (\d+):(\d\d):(\d\d).(\d\d)", Util.DefaultRegexConfig); + private const string PreLinkConf = "-hide_banner -nostats -i \""; + private const string PostLinkConf = "\" -ac 2 -ar 48000 -f s16le -acodec pcm_s16le pipe:1"; + private readonly TimeSpan retryOnDropBeforeEnd = TimeSpan.FromSeconds(10); + private readonly object ffmpegLock = new object(); + + private readonly Ts3FullClientData ts3FullClientData; + + private PreciseAudioTimer audioTimer; + private string lastLink; + private Process ffmpegProcess; + private TimeSpan? parsedSongLength; + private bool hasTriedToReconnectAudio; + + public int SampleRate { get; } = 48000; + public int Channels { get; } = 2; + public int BitsPerSample { get; } = 16; + + public FfmpegProducer(Ts3FullClientData tfcd) + { + ts3FullClientData = tfcd; + audioTimer = new PreciseAudioTimer(this); + } + + public R AudioStart(string url) => StartFfmpegProcess(url); + + public R AudioStop() + { + audioTimer.Stop(); + StopFfmpegProcess(); + return R.OkR; + } + + public TimeSpan Length => GetCurrentSongLength(); + + public TimeSpan Position + { + get => audioTimer.SongPosition; + set + { + if (value < TimeSpan.Zero || value > Length) + throw new ArgumentOutOfRangeException(nameof(value)); + AudioStop(); + StartFfmpegProcess(lastLink, + $"-ss {value.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture)}", + $"-ss {value.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture)}"); + audioTimer.SongPositionOffset = value; + } + } + + public int Read(byte[] buffer, int offset, int length, out Meta meta) + { + meta = null; + + lock (ffmpegLock) + { + if (ffmpegProcess == null) + return 0; + + ffmpegProcess.StandardOutput.BaseStream.ReadTimeout = 1000; + int read = ffmpegProcess.StandardOutput.BaseStream.Read(buffer, 0, length); + if (read == 0) + { + // check for premature connection drop + if (ffmpegProcess.HasExited && !hasTriedToReconnectAudio) + { + var expectedStopLength = GetCurrentSongLength(); + if (expectedStopLength != TimeSpan.Zero) + { + var actualStopPosition = audioTimer.SongPosition; + if (actualStopPosition + retryOnDropBeforeEnd < expectedStopLength) + { + Log.Write(Log.Level.Debug, "Connection to song lost, retrying at {0}", actualStopPosition); + hasTriedToReconnectAudio = true; + Position = actualStopPosition; + return 0; + } + } + } + + if (ffmpegProcess.HasExited) + { + AudioStop(); + OnSongEnd?.Invoke(this, new EventArgs()); + } + } + + hasTriedToReconnectAudio = false; + // TODO push bytes //audioTimer.PushBytes(read); + return read; + } + } + + public R StartFfmpegProcess(string url, string extraPreParam = null, string extraPostParam = null) + { + try + { + lock (ffmpegLock) + { + StopFfmpegProcess(); + + ffmpegProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = ts3FullClientData.FfmpegPath, + Arguments = string.Concat(extraPreParam, " ", PreLinkConf, url, PostLinkConf, " ", extraPostParam), + RedirectStandardOutput = true, + RedirectStandardInput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + } + }; + ffmpegProcess.Start(); + + lastLink = url; + parsedSongLength = null; + + audioTimer.SongPositionOffset = TimeSpan.Zero; + audioTimer.Start(); + return R.OkR; + } + } + catch (Exception ex) { return $"Unable to create stream ({ex.Message})"; } + } + + private void StopFfmpegProcess() + { + // TODO somehow bypass lock + lock (ffmpegLock) + { + if (ffmpegProcess == null) + return; + + try + { + if (!ffmpegProcess.HasExited) + ffmpegProcess.Kill(); + else + ffmpegProcess.Close(); + } + catch (InvalidOperationException) { } + ffmpegProcess = null; + } + } + + private TimeSpan GetCurrentSongLength() + { + lock (ffmpegLock) + { + if (ffmpegProcess == null) + return TimeSpan.Zero; + + if (parsedSongLength.HasValue) + return parsedSongLength.Value; + + Match match = null; + while (ffmpegProcess.StandardError.Peek() > -1) + { + var infoLine = ffmpegProcess.StandardError.ReadLine(); + if (string.IsNullOrEmpty(infoLine)) + continue; + match = FindDurationMatch.Match(infoLine); + if (match.Success) + break; + } + if (match == null || !match.Success) + return TimeSpan.Zero; + + int hours = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); + int minutes = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); + int seconds = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture); + int millisec = int.Parse(match.Groups[4].Value, CultureInfo.InvariantCulture) * 10; + parsedSongLength = new TimeSpan(0, hours, minutes, seconds, millisec); + return parsedSongLength.Value; + } + } + } +} diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index 461c1221..fb68ad32 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -71,7 +71,7 @@ public bool InitializeBot() if (hmd.EnableHistory) historyManager = new HistoryManager(hmd, core.Database); PlayManager = new PlayManager(core, this); - TargetManager = teamspeakClient; + TargetManager = teamspeakClient.TargetPipe; TargetScript = new TargetScript(core, this); PlayerConnection.OnSongEnd += PlayManager.SongStoppedHook; diff --git a/TS3AudioBot/Commands.cs b/TS3AudioBot/Commands.cs index 9a111130..4a22a53d 100644 --- a/TS3AudioBot/Commands.cs +++ b/TS3AudioBot/Commands.cs @@ -19,6 +19,7 @@ namespace TS3AudioBot using System.Linq; using System.Text; using TS3Client; + using TS3Client.Full.Audio; using TS3Client.Messages; using Web.Api; diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index 678f21d9..16ffd65c 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -220,7 +220,7 @@ void ColorLog(string msg, Log.Level lvl) Log.Write(Log.Level.Info, "[==============================================]"); Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); - Audio.Opus.NativeMethods.DummyLoad(); + Log.Write(Log.Level.Info, "Using opus version: {0}", TS3Client.Full.Audio.Opus.NativeMethods.Info); Injector = new Injector(); Injector.RegisterModule(this); diff --git a/TS3AudioBot/Helper/PositionedStreamReader.cs b/TS3AudioBot/Helper/PositionedStreamReader.cs deleted file mode 100644 index 71798298..00000000 --- a/TS3AudioBot/Helper/PositionedStreamReader.cs +++ /dev/null @@ -1,189 +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.Helper -{ - using System.IO; - using System.Text; - - public class PositionedStreamReader : TextReader - { - private const int BufferSize = 1 << 10; // 1024 - - private Stream stream; - private Encoding encoding; - private Decoder decoder; - private long baseStreamPosition; - private long baseStreamLength; - - private StringBuilder strb; - private byte[] byteBuffer; - private char[] charBuffer; - private int bufferpos; - private int readPosition; - private int charlen; - private int bytelen; - - public Stream BaseStream => stream; - public int ReadPosition => readPosition; - - public PositionedStreamReader(Stream stream) : this(stream, Encoding.UTF8) { } - - public PositionedStreamReader(Stream stream, Encoding encoding) - { - this.stream = stream; - this.encoding = encoding; - decoder = encoding.GetDecoder(); - bufferpos = 0; - readPosition = 0; - } - - public override string ReadLine() - { - if (byteBuffer == null || charBuffer == null) - { - byteBuffer = new byte[BufferSize]; - charBuffer = new char[BufferSize]; - bufferpos = 0; - } - - Endl endlStatus = Endl.Nothing; - while (true) - { - if (bufferpos >= BufferSize || bufferpos == 0 || stream.Position != baseStreamPosition || stream.Length != baseStreamLength) - { - bytelen = stream.Read(byteBuffer, 0, byteBuffer.Length); - baseStreamPosition = stream.Position; - baseStreamLength = stream.Length; - if (bytelen == 0) - return FinalizeEof(); - charlen = decoder.GetChars(byteBuffer, 0, bytelen, charBuffer, 0, false); - bufferpos = 0; - } - - int charReadLen = 0; - for (int i = bufferpos; i < charlen; i++) - { - if (charBuffer[i] == '\r') - { - if (endlStatus == Endl.Nothing) - { - charReadLen++; - endlStatus = Endl.CrFirst; - } - else if (endlStatus == Endl.CrFirst) - { - endlStatus = Endl.CrFinal; - break; - } - else if (endlStatus == Endl.Lf) - break; - } - else if (charBuffer[i] == '\n') - { - if (endlStatus == Endl.Nothing) - { - charReadLen++; - endlStatus = Endl.Lf; - break; - } - else if (endlStatus == Endl.CrFirst) - { - charReadLen++; - endlStatus = Endl.CrLf; - break; - } - else if (endlStatus == Endl.Lf) - break; - } - else - { - if (endlStatus == Endl.CrFirst) - { - endlStatus = Endl.CrFinal; - break; - } - else - charReadLen++; - } - } - - if (charReadLen == 0) - return FinalizeEof(); - - if (bytelen == charlen) - readPosition += charReadLen; - else - readPosition += encoding.GetByteCount(charBuffer, bufferpos, charReadLen); - - int readcnt; - switch (endlStatus) - { - case Endl.CrFirst: - case Endl.Nothing: - readcnt = charReadLen - (endlStatus == Endl.CrFirst ? 1 : 0); - if (strb == null) - strb = new StringBuilder(charReadLen); - if (readcnt > 0) - strb.Append(charBuffer, bufferpos, charReadLen); - bufferpos = 0; - break; - case Endl.CrFinal: - case Endl.Lf: - case Endl.CrLf: - readcnt = charReadLen - (endlStatus == Endl.CrLf ? 2 : 1); - string retStr; - if (strb == null) - { - if (readcnt > 0) - retStr = new string(charBuffer, bufferpos, charReadLen - (endlStatus == Endl.CrLf ? 2 : 1)); - else - retStr = string.Empty; - } - else - { - if (readcnt > 0) - strb.Append(charBuffer, bufferpos, readcnt); - retStr = strb.ToString(); - strb = null; - } - bufferpos += charReadLen; - return retStr; - } - } - } - - public void InvalidateBuffer() - { - baseStreamPosition = -1; - baseStreamLength = -1; - } - - private string FinalizeEof() - { - if (strb != null) - { - string retStr = strb.ToString(); - strb = null; - return retStr; - } - else - return null; - } - - private enum Endl - { - Nothing, - CrFirst, - CrFinal, - Lf, - CrLf, - } - } -} diff --git a/TS3AudioBot/ITargetManager.cs b/TS3AudioBot/ITargetManager.cs index 9f5cf859..bd6e81dc 100644 --- a/TS3AudioBot/ITargetManager.cs +++ b/TS3AudioBot/ITargetManager.cs @@ -10,6 +10,7 @@ namespace TS3AudioBot { using TS3Client; + using TS3Client.Full.Audio; public interface ITargetManager { @@ -30,12 +31,4 @@ public interface ITargetManager void WhisperClientSubscribe(ushort userId); void WhisperClientUnsubscribe(ushort userId); } - - public enum TargetSendMode - { - None, - Voice, - Whisper, - WhisperGroup, - } } diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index 38539200..1d6fd0b4 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -85,11 +85,8 @@ - - - - - + + @@ -164,7 +161,6 @@ - @@ -210,9 +206,6 @@ - - - diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index 50d58241..bb1fc8ea 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -17,6 +17,7 @@ namespace TS3AudioBot using TS3Client; using TS3Client.Commands; using TS3Client.Full; + using TS3Client.Helper; using TS3Client.Messages; using TS3Client.Query; diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index 61e47d2c..a20ace7f 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -9,27 +9,23 @@ namespace TS3AudioBot { - using Audio; using Helper; using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; + using Audio; using System.Linq; using System.Reflection; - using System.Text.RegularExpressions; using TS3Client; + using TS3Client.Helper; using TS3Client.Full; + using TS3Client.Full.Audio; using TS3Client.Messages; - internal sealed class Ts3Full : TeamspeakControl, IPlayerConnection, ITargetManager + internal sealed class Ts3Full : TeamspeakControl, IPlayerConnection { private readonly Ts3FullClient tsFullClient; private ClientData self; private const Codec SendCodec = Codec.OpusMusic; - private readonly TimeSpan sendCheckInterval = TimeSpan.FromMilliseconds(5); - private readonly TimeSpan audioBufferLength = TimeSpan.FromMilliseconds(20); private const uint StallCountInterval = 10; private const uint StallNoErrorCountMax = 5; private static readonly string[] QuitMessages = { @@ -43,39 +39,20 @@ internal sealed class Ts3Full : TeamspeakControl, IPlayerConnection, ITargetMana "Notice me, senpai", ":wq" }; - private const string PreLinkConf = "-hide_banner -nostats -i \""; - private const string PostLinkConf = "\" -ac 2 -ar 48000 -f s16le -acodec pcm_s16le pipe:1"; - private string lastLink; - private static readonly Regex FindDurationMatch = new Regex(@"^\s*Duration: (\d+):(\d\d):(\d\d).(\d\d)", Util.DefaultRegexConfig); - private TimeSpan? parsedSongLength; - private readonly object ffmpegLock = new object(); - private readonly TimeSpan retryOnDropBeforeEnd = TimeSpan.FromSeconds(10); - private bool hasTriedToReconnectAudio; public override event EventHandler OnBotDisconnect; private readonly Ts3FullClientData ts3FullClientData; - private float volume = 1; - public TargetSendMode SendMode { get; set; } = TargetSendMode.None; - public ulong GroupWhisperTargetId { get; set; } - public GroupWhisperType GroupWhisperType { get; set; } - public GroupWhisperTarget GroupWhisperTarget { get; set; } - - private TickWorker sendTick; - private Process ffmpegProcess; - private AudioEncoder encoder; - private readonly PreciseAudioTimer audioTimer; - private byte[] audioBuffer; + private bool isStall; private uint stallCount; private uint stallNoErrorCount; private IdentityData identity; - private readonly Dictionary channelSubscriptionsSetup; - private readonly List clientSubscriptionsSetup; - private ulong[] channelSubscriptionsCache; - private ushort[] clientSubscriptionsCache; - private bool subscriptionSetupChanged; - private readonly object subscriptionLockObj = new object(); + private VolumePipe volumePipe; + private FfmpegProducer ffmpegProducer; + private PreciseTimedPipe timePipe; + private EncoderPipe encoderPipe; + internal CustomTargetPipe TargetPipe { get; private set; } public Ts3Full(Ts3FullClientData tfcd) : base(ClientType.Full) { @@ -84,16 +61,20 @@ public Ts3Full(Ts3FullClientData tfcd) : base(ClientType.Full) ts3FullClientData = tfcd; tfcd.PropertyChanged += Tfcd_PropertyChanged; - sendTick = TickPool.RegisterTick(AudioSend, sendCheckInterval, false); - encoder = new AudioEncoder(SendCodec) { Bitrate = ts3FullClientData.AudioBitrate * 1000 }; - audioTimer = new PreciseAudioTimer(encoder.SampleRate, encoder.BitsPerSample, encoder.Channels); + ffmpegProducer = new FfmpegProducer(tfcd); + ffmpegProducer.OnSongEnd += OnSongEnd; + volumePipe = new VolumePipe(); + encoderPipe = new EncoderPipe(SendCodec) { Bitrate = ts3FullClientData.AudioBitrate * 1000 }; + timePipe = new PreciseTimedPipe { ReadBufferSize = encoderPipe.OptimalPacketSize }; + timePipe.Initialize(encoderPipe); + TargetPipe = new CustomTargetPipe(tsFullClient); + + timePipe.InStream = ffmpegProducer; + timePipe.Chain(volumePipe).Chain(encoderPipe).Chain(TargetPipe); + isStall = false; stallCount = 0; identity = null; - - Util.Init(out channelSubscriptionsSetup); - Util.Init(out clientSubscriptionsSetup); - subscriptionSetupChanged = true; } public override T GetLowLibrary() @@ -110,7 +91,7 @@ private void Tfcd_PropertyChanged(object sender, System.ComponentModel.PropertyC var value = (int)typeof(Ts3FullClientData).GetProperty(e.PropertyName).GetValue(sender); if (value <= 0 || value >= 256) return; - encoder.Bitrate = value * 1000; + encoderPipe.Bitrate = value * 1000; } } @@ -284,361 +265,89 @@ public override R GetSelf() private void AudioSend() { - lock (ffmpegLock) - { - if (ffmpegProcess == null) - return; + bool doSend = true; - if (audioBuffer == null || audioBuffer.Length < encoder.OptimalPacketSize) - audioBuffer = new byte[encoder.OptimalPacketSize]; - - UpdatedSubscriptionCache(); - - while (audioTimer.RemainingBufferDuration < audioBufferLength) + var SendMode = TargetSendMode.None; + switch (SendMode) + { + case TargetSendMode.None: + doSend = false; + break; + case TargetSendMode.Voice: + break; + case TargetSendMode.Whisper: + case TargetSendMode.WhisperGroup: + if (isStall) { - int read = ffmpegProcess.StandardOutput.BaseStream.Read(audioBuffer, 0, encoder.OptimalPacketSize); - if (read == 0) + if (++stallCount % StallCountInterval == 0) { - // check for premature connection drop - if (ffmpegProcess.HasExited && !hasTriedToReconnectAudio) - { - var expectedStopLength = GetCurrentSongLength(); - if (expectedStopLength != TimeSpan.Zero) - { - var actualStopPosition = audioTimer.SongPosition; - if (actualStopPosition + retryOnDropBeforeEnd < expectedStopLength) - { - Log.Write(Log.Level.Debug, "Connection to song lost, retrying at {0}", actualStopPosition); - hasTriedToReconnectAudio = true; - Position = actualStopPosition; - return; - } - } - } - - if (ffmpegProcess.HasExited - && audioTimer.RemainingBufferDuration < TimeSpan.Zero - && !encoder.HasPacket) + stallNoErrorCount++; + if (stallNoErrorCount > StallNoErrorCountMax) { - AudioStop(); - OnSongEnd?.Invoke(this, new EventArgs()); + stallCount = 0; + isStall = false; } - return; } - - hasTriedToReconnectAudio = false; - audioTimer.PushBytes(read); - - bool doSend = true; - - switch (SendMode) + else { - case TargetSendMode.None: doSend = false; - break; - case TargetSendMode.Voice: - break; - case TargetSendMode.Whisper: - case TargetSendMode.WhisperGroup: - if (isStall) - { - if (++stallCount % StallCountInterval == 0) - { - stallNoErrorCount++; - if (stallNoErrorCount > StallNoErrorCountMax) - { - stallCount = 0; - isStall = false; - } - } - else - { - doSend = false; - } - } - if (SendMode == TargetSendMode.Whisper) - doSend &= channelSubscriptionsCache.Length > 0 || clientSubscriptionsCache.Length > 0; - break; - default: - throw new InvalidOperationException(); - } - - // Save cpu when we know there is noone to send to - if (!doSend) - break; - - var bufSpan = new Span(audioBuffer, 0, read); - TS3Client.Full.Audio.VolumePipe.AdjustVolume(bufSpan, volume); - encoder.PushPcmAudio(bufSpan); - - while (encoder.HasPacket) - { - var packet = encoder.GetPacket(); - var span = new ReadOnlySpan(packet.Array, 0, packet.Length); - switch (SendMode) - { - case TargetSendMode.Voice: - tsFullClient.SendAudio(span, encoder.Codec); - break; - case TargetSendMode.Whisper: - tsFullClient.SendAudioWhisper(span, encoder.Codec, channelSubscriptionsCache, clientSubscriptionsCache); - break; - case TargetSendMode.WhisperGroup: - tsFullClient.SendAudioGroupWhisper(span, encoder.Codec, GroupWhisperType, GroupWhisperTarget); - break; - } - encoder.ReturnPacket(packet.Array); } } + break; + default: + throw new InvalidOperationException(); } + + // Save cpu when we know there is noone to send to } #region IPlayerConnection public event EventHandler OnSongEnd; - public void SetGroupWhisper(GroupWhisperType type, GroupWhisperTarget target, ulong targetId = 0) + public R AudioStart(string url) { - GroupWhisperType = type; - GroupWhisperTarget = target; - GroupWhisperTargetId = targetId; + var result = ffmpegProducer.AudioStart(url); + if (result) + timePipe.Paused = false; + return result; } - public R AudioStart(string url) => StartFfmpegProcess(url); - public R AudioStop() { - sendTick.Active = false; - audioTimer.Stop(); - StopFfmpegProcess(); - return R.OkR; + timePipe.Paused = true; + return ffmpegProducer.AudioStop(); } - public TimeSpan Length => GetCurrentSongLength(); + public TimeSpan Length => ffmpegProducer.Length; public TimeSpan Position { - get => audioTimer.SongPosition; - set - { - if (value < TimeSpan.Zero || value > Length) - throw new ArgumentOutOfRangeException(nameof(value)); - AudioStop(); - StartFfmpegProcess(lastLink, - $"-ss {value.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture)}", - $"-ss {value.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture)}"); - audioTimer.SongPositionOffset = value; - } + get => ffmpegProducer.Position; + set => ffmpegProducer.Position = value; } public float Volume { - get => volume * AudioValues.MaxVolume; + get => volumePipe.Volume * AudioValues.MaxVolume; set { if (value < 0 || value > AudioValues.MaxVolume) throw new ArgumentOutOfRangeException(nameof(value)); - volume = value / AudioValues.MaxVolume; + volumePipe.Volume = value / AudioValues.MaxVolume; } } public bool Paused { - get => sendTick.Active; - set - { - if (sendTick.Active == value) - { - sendTick.Active = !value; - if (value) - { - audioTimer.SongPositionOffset = audioTimer.SongPosition; - audioTimer.Stop(); - } - else - audioTimer.Start(); - } - } + get => timePipe.Paused; + set => timePipe.Paused = value; } - public bool Playing => sendTick.Active; + public bool Playing => !timePipe.Paused; public bool Repeated { get { return false; } set { } } - private R StartFfmpegProcess(string url, string extraPreParam = null, string extraPostParam = null) - { - try - { - lock (ffmpegLock) - { - StopFfmpegProcess(); - - ffmpegProcess = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = ts3FullClientData.FfmpegPath, - Arguments = string.Concat(extraPreParam, " ", PreLinkConf, url, PostLinkConf, " ", extraPostParam), - RedirectStandardOutput = true, - RedirectStandardInput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - } - }; - ffmpegProcess.Start(); - - lastLink = url; - parsedSongLength = null; - - audioTimer.SongPositionOffset = TimeSpan.Zero; - audioTimer.Start(); - sendTick.Active = true; - return R.OkR; - } - } - catch (Exception ex) { return $"Unable to create stream ({ex.Message})"; } - } - - private void StopFfmpegProcess() - { - lock (ffmpegLock) - { - if (ffmpegProcess == null) - return; - - try - { - if (!ffmpegProcess.HasExited) - ffmpegProcess.Kill(); - else - ffmpegProcess.Close(); - } - catch (InvalidOperationException) { } - ffmpegProcess = null; - } - } - - private TimeSpan GetCurrentSongLength() - { - lock (ffmpegLock) - { - if (ffmpegProcess == null) - return TimeSpan.Zero; - - if (parsedSongLength.HasValue) - return parsedSongLength.Value; - - Match match = null; - while (ffmpegProcess.StandardError.Peek() > -1) - { - var infoLine = ffmpegProcess.StandardError.ReadLine(); - if (string.IsNullOrEmpty(infoLine)) - continue; - match = FindDurationMatch.Match(infoLine); - if (match.Success) - break; - } - if (match == null || !match.Success) - return TimeSpan.Zero; - - int hours = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); - int minutes = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); - int seconds = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture); - int millisec = int.Parse(match.Groups[4].Value, CultureInfo.InvariantCulture) * 10; - parsedSongLength = new TimeSpan(0, hours, minutes, seconds, millisec); - return parsedSongLength.Value; - } - } - - #endregion - - #region ITargetManager - - public void WhisperChannelSubscribe(ulong channel, bool temp) - { - // TODO move to requested channel - // TODO spawn new client - lock (subscriptionLockObj) - { - if (channelSubscriptionsSetup.TryGetValue(channel, out var subscriptionTemp)) - channelSubscriptionsSetup[channel] = !subscriptionTemp || !temp; - else - { - channelSubscriptionsSetup[channel] = !temp; - subscriptionSetupChanged = true; - } - } - } - - public void WhisperChannelUnsubscribe(ulong channel, bool temp) - { - lock (subscriptionLockObj) - { - if (!temp) - { - subscriptionSetupChanged |= channelSubscriptionsSetup.Remove(channel); - } - else - { - if (channelSubscriptionsSetup.TryGetValue(channel, out bool subscriptionTemp) && subscriptionTemp) - { - channelSubscriptionsSetup.Remove(channel); - subscriptionSetupChanged = true; - } - } - } - } - - public void WhisperClientSubscribe(ushort userId) - { - lock (subscriptionLockObj) - { - if (!clientSubscriptionsSetup.Contains(userId)) - clientSubscriptionsSetup.Add(userId); - subscriptionSetupChanged = true; - } - } - - public void WhisperClientUnsubscribe(ushort userId) - { - lock (subscriptionLockObj) - { - clientSubscriptionsSetup.Remove(userId); - subscriptionSetupChanged = true; - } - } - - public void ClearTemporary() - { - lock (subscriptionLockObj) - { - ulong[] removeList = channelSubscriptionsSetup - .Where(kvp => kvp.Value) - .Select(kvp => kvp.Key) - .ToArray(); - foreach (var chan in removeList) - { - channelSubscriptionsSetup.Remove(chan); - subscriptionSetupChanged = true; - } - } - } - - private void UpdatedSubscriptionCache() - { - if (!subscriptionSetupChanged) - return; - lock (subscriptionLockObj) - { - if (!subscriptionSetupChanged) - return; - channelSubscriptionsCache = channelSubscriptionsSetup.Keys.ToArray(); - clientSubscriptionsCache = clientSubscriptionsSetup.ToArray(); - subscriptionSetupChanged = false; - } - } - #endregion } diff --git a/TS3Client/ColorDbg.cs b/TS3Client/ColorDbg.cs index 20ea6520..53cb0f7e 100644 --- a/TS3Client/ColorDbg.cs +++ b/TS3Client/ColorDbg.cs @@ -1,8 +1,9 @@ namespace TS3Client { + using Full; + using Helper; using System; using System.Diagnostics; - using Full; using System.Runtime.CompilerServices; internal static class ColorDbg diff --git a/TS3Client/Commands/CommandDeserializer.cs b/TS3Client/Commands/CommandDeserializer.cs index 062615de..eb4bc9c9 100644 --- a/TS3Client/Commands/CommandDeserializer.cs +++ b/TS3Client/Commands/CommandDeserializer.cs @@ -9,6 +9,7 @@ namespace TS3Client.Commands { + using Helper; using Messages; using System; using System.Collections.Generic; @@ -113,8 +114,7 @@ private static Dictionary ParseKeyValueLineDict(KVEnu data) public static DateTime DeserializeDateTime(string v) => Util.UnixTimeStart.AddSeconds(double.Parse(v, CultureInfo.InvariantCulture)); public static T DeserializeEnum(string v) where T : struct { - T val; - if (!Enum.TryParse(v, out val)) + if (!Enum.TryParse(v, out T val)) throw new FormatException(); return val; } diff --git a/TS3Client/Commands/CommandOption.cs b/TS3Client/Commands/CommandOption.cs index ad905f90..7b2aad92 100644 --- a/TS3Client/Commands/CommandOption.cs +++ b/TS3Client/Commands/CommandOption.cs @@ -9,6 +9,7 @@ namespace TS3Client.Commands { + using Helper; using System; using System.Linq; using System.Text; diff --git a/TS3Client/Commands/CommandParameter.cs b/TS3Client/Commands/CommandParameter.cs index f2381911..d7041eb0 100644 --- a/TS3Client/Commands/CommandParameter.cs +++ b/TS3Client/Commands/CommandParameter.cs @@ -9,6 +9,7 @@ namespace TS3Client.Commands { + using Helper; using System; using System.Diagnostics; using System.Globalization; diff --git a/TS3Client/Commands/Ts3Command.cs b/TS3Client/Commands/Ts3Command.cs index 1fe311c8..740db346 100644 --- a/TS3Client/Commands/Ts3Command.cs +++ b/TS3Client/Commands/Ts3Command.cs @@ -9,6 +9,7 @@ namespace TS3Client.Commands { + using Helper; using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/TS3Client/DocumentedEnums.cs b/TS3Client/DocumentedEnums.cs index ece3736f..6100be3c 100644 --- a/TS3Client/DocumentedEnums.cs +++ b/TS3Client/DocumentedEnums.cs @@ -49,7 +49,7 @@ public enum Codec : byte ///stereo, 16bit, 48kHz, optimized for music OpusMusic, - /// PCM S16LE 2 Channel (TS3Client extension; not supported by normal TeamSpeak 3 Clients!) + /// PCM S16LE 1/2 Channel (TS3Client extension; not supported by normal TeamSpeak 3 clients!) Raw = 127, } diff --git a/TS3Client/FileTransferManager.cs b/TS3Client/FileTransferManager.cs index b73bbc95..725906cf 100644 --- a/TS3Client/FileTransferManager.cs +++ b/TS3Client/FileTransferManager.cs @@ -9,6 +9,7 @@ namespace TS3Client { + using Helper; using Messages; using System; using System.Collections.Generic; diff --git a/TS3Client/Full/Audio/AudioInterfaces.cs b/TS3Client/Full/Audio/AudioInterfaces.cs index f89eac08..d21f8772 100644 --- a/TS3Client/Full/Audio/AudioInterfaces.cs +++ b/TS3Client/Full/Audio/AudioInterfaces.cs @@ -28,4 +28,11 @@ public interface IAudioActiveConsumer : IAudioStream } public interface IAudioPipe : IAudioPassiveConsumer, IAudioActiveProducer { } + + public interface ISampleInfo + { + int SampleRate { get; } + int Channels { get; } + int BitsPerSample { get; } + } } diff --git a/TS3Client/Full/Audio/EncoderPipe.cs b/TS3Client/Full/Audio/EncoderPipe.cs index 4b38c6b2..bb28f8e5 100644 --- a/TS3Client/Full/Audio/EncoderPipe.cs +++ b/TS3Client/Full/Audio/EncoderPipe.cs @@ -1,14 +1,113 @@ namespace TS3Client.Full.Audio { + using Opus; using System; - public class EncoderPipe : IAudioPipe + public class EncoderPipe : IAudioPipe, IDisposable, ISampleInfo { public IAudioPassiveConsumer OutStream { get; set; } + public Codec Codec { get; } + public int SampleRate { get; } + public int Channels { get; } + public int BitsPerSample { get; } + + public int OptimalPacketSize { get; } + public int Bitrate { get => opusEncoder.Bitrate; set => opusEncoder.Bitrate = value; } + + // opus + private readonly OpusEncoder opusEncoder; + + private const int SegmentFrames = 960; + private byte[] soundBuffer = new byte[0]; + private int soundBufferLength; + private byte[] notEncodedBuffer = new byte[0]; + private int notEncodedBufferLength; + private readonly byte[] segment; + private byte[] encodedBuffer; + + public EncoderPipe(Codec codec) + { + Codec = codec; + + switch (codec) + { + case Codec.Raw: + throw new InvalidOperationException("Raw is not a valid encoding target"); + case Codec.SpeexNarrowband: + throw new NotSupportedException(); + case Codec.SpeexWideband: + throw new NotSupportedException(); + case Codec.SpeexUltraWideband: + throw new NotSupportedException(); + case Codec.CeltMono: + throw new NotSupportedException(); + + case Codec.OpusVoice: + SampleRate = 48000; + Channels = 1; + opusEncoder = OpusEncoder.Create(SampleRate, Channels, Application.Voip); + Bitrate = 8192 * 2; + break; + + case Codec.OpusMusic: + SampleRate = 48000; + Channels = 2; + opusEncoder = OpusEncoder.Create(SampleRate, Channels, Application.Audio); + Bitrate = 8192 * 4; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(codec)); + } + + BitsPerSample = 16; + OptimalPacketSize = opusEncoder.FrameByteCount(SegmentFrames); + segment = new byte[OptimalPacketSize]; + encodedBuffer = new byte[opusEncoder.MaxDataBytes]; + } + public void Write(Span data, Meta meta) { - throw new NotImplementedException(); + if (OutStream == null) + return; + + int newSoundBufferLength = data.Length + notEncodedBufferLength; + if (newSoundBufferLength > soundBuffer.Length) + soundBuffer = new byte[newSoundBufferLength]; + soundBufferLength = newSoundBufferLength; + + Array.Copy(notEncodedBuffer, 0, soundBuffer, 0, notEncodedBufferLength); + data.CopyTo(new Span(soundBuffer, notEncodedBufferLength)); + + int byteCap = OptimalPacketSize; + int segmentCount = (int)Math.Floor((float)soundBufferLength / byteCap); + int segmentsEnd = segmentCount * byteCap; + int newNotEncodedBufferLength = soundBufferLength - segmentsEnd; + if (newNotEncodedBufferLength > notEncodedBuffer.Length) + notEncodedBuffer = new byte[newNotEncodedBufferLength]; + notEncodedBufferLength = newNotEncodedBufferLength; + Array.Copy(soundBuffer, segmentsEnd, notEncodedBuffer, 0, notEncodedBufferLength); + + for (int i = 0; i < segmentCount; i++) + { + for (int j = 0; j < segment.Length; j++) + segment[j] = soundBuffer[(i * byteCap) + j]; + var encodedData = opusEncoder.Encode(segment, segment.Length, encodedBuffer); + if (meta != null) + meta.Codec = Codec; // TODO copy ? + OutStream?.Write(encodedData, meta); + } + } + + public TimeSpan GetPlayLength(int bytes) + { + return TimeSpan.FromSeconds(bytes / (double)(SampleRate * (BitsPerSample / 8) * Channels)); + } + + public void Dispose() + { + opusEncoder?.Dispose(); } } } diff --git a/TS3AudioBot/Audio/Opus/LICENSE b/TS3Client/Full/Audio/Opus/LICENSE similarity index 100% rename from TS3AudioBot/Audio/Opus/LICENSE rename to TS3Client/Full/Audio/Opus/LICENSE diff --git a/TS3AudioBot/Audio/Opus/NativeMethods.cs b/TS3Client/Full/Audio/Opus/NativeMethods.cs similarity index 93% rename from TS3AudioBot/Audio/Opus/NativeMethods.cs rename to TS3Client/Full/Audio/Opus/NativeMethods.cs index 1e46b40d..fef9b27d 100644 --- a/TS3AudioBot/Audio/Opus/NativeMethods.cs +++ b/TS3Client/Full/Audio/Opus/NativeMethods.cs @@ -19,7 +19,7 @@ // 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 TS3AudioBot.Audio.Opus +namespace TS3Client.Full.Audio.Opus { using Helper; using System; @@ -28,17 +28,22 @@ namespace TS3AudioBot.Audio.Opus /// /// Wraps the Opus API. /// - internal static class NativeMethods + public static class NativeMethods { static NativeMethods() { NativeWinDllLoader.DirectLoadLibrary("libopus"); - var verStrPtr = opus_get_version_string(); - var verString = Marshal.PtrToStringAnsi(verStrPtr); - Log.Write(Log.Level.Info, "Using opus version: {0} ({1})", verString, NativeWinDllLoader.ArchFolder); } - public static void DummyLoad() { } + public static string Info + { + get + { + var verStrPtr = opus_get_version_string(); + var verString = Marshal.PtrToStringAnsi(verStrPtr); + return $"{verString} ({NativeWinDllLoader.ArchFolder})"; + } + } // ReSharper disable EnumUnderlyingTypeIsInt, InconsistentNaming #pragma warning disable IDE1006 diff --git a/TS3AudioBot/Audio/Opus/OPUS_LICENSE b/TS3Client/Full/Audio/Opus/OPUS_LICENSE similarity index 100% rename from TS3AudioBot/Audio/Opus/OPUS_LICENSE rename to TS3Client/Full/Audio/Opus/OPUS_LICENSE diff --git a/TS3AudioBot/Audio/Opus/OpusDecoder.cs b/TS3Client/Full/Audio/Opus/OpusDecoder.cs similarity index 99% rename from TS3AudioBot/Audio/Opus/OpusDecoder.cs rename to TS3Client/Full/Audio/Opus/OpusDecoder.cs index dbe5c39d..c9989d72 100644 --- a/TS3AudioBot/Audio/Opus/OpusDecoder.cs +++ b/TS3Client/Full/Audio/Opus/OpusDecoder.cs @@ -19,7 +19,7 @@ // 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 TS3AudioBot.Audio.Opus +namespace TS3Client.Full.Audio.Opus { using System; diff --git a/TS3AudioBot/Audio/Opus/OpusEncoder.cs b/TS3Client/Full/Audio/Opus/OpusEncoder.cs similarity index 95% rename from TS3AudioBot/Audio/Opus/OpusEncoder.cs rename to TS3Client/Full/Audio/Opus/OpusEncoder.cs index 6a986994..bf872d64 100644 --- a/TS3AudioBot/Audio/Opus/OpusEncoder.cs +++ b/TS3Client/Full/Audio/Opus/OpusEncoder.cs @@ -19,7 +19,7 @@ // 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 TS3AudioBot.Audio.Opus +namespace TS3Client.Full.Audio.Opus { using System; @@ -73,7 +73,7 @@ private OpusEncoder(IntPtr encoder, int inputSamplingRate, int inputChannels, Ap /// The encoded data is written to this buffer. /// Set to length of encoded audio. /// Opus encoded audio buffer. - public void Encode(byte[] inputPcmSamples, int sampleLength, byte[] outputEncodedBuffer, out int encodedLength) + public Span Encode(byte[] inputPcmSamples, int sampleLength, byte[] outputEncodedBuffer) { if (disposed) throw new ObjectDisposedException("OpusEncoder"); @@ -81,10 +81,12 @@ public void Encode(byte[] inputPcmSamples, int sampleLength, byte[] outputEncode throw new ArgumentException("Array must be at least MaxDataBytes long", nameof(outputEncodedBuffer)); int frames = FrameCount(inputPcmSamples); - encodedLength = NativeMethods.opus_encode(encoder, inputPcmSamples, frames, outputEncodedBuffer, sampleLength); - + int encodedLength = NativeMethods.opus_encode(encoder, inputPcmSamples, frames, outputEncodedBuffer, sampleLength); + if (encodedLength < 0) throw new Exception("Encoding failed - " + (Errors)encodedLength); + + return new Span(outputEncodedBuffer, 0, encodedLength); } /// diff --git a/TS3AudioBot/Audio/Opus/README b/TS3Client/Full/Audio/Opus/README similarity index 100% rename from TS3AudioBot/Audio/Opus/README rename to TS3Client/Full/Audio/Opus/README diff --git a/TS3AudioBot/Audio/PreciseAudioTimer.cs b/TS3Client/Full/Audio/PreciseAudioTimer.cs similarity index 88% rename from TS3AudioBot/Audio/PreciseAudioTimer.cs rename to TS3Client/Full/Audio/PreciseAudioTimer.cs index 6ab5af95..0fcf29ab 100644 --- a/TS3AudioBot/Audio/PreciseAudioTimer.cs +++ b/TS3Client/Full/Audio/PreciseAudioTimer.cs @@ -7,17 +7,17 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3AudioBot.Audio +namespace TS3Client.Full.Audio { using System; using System.Diagnostics; /// Provides a precise way to measure a playbackbuffer by tracking /// sent bytes and elapsed time. - internal class PreciseAudioTimer + public class PreciseAudioTimer : ISampleInfo { public int SampleRate { get; } - public int Channel { get; } + public int Channels { get; } public int BitsPerSample { get; } public int BytesPerSecond { get; } @@ -38,6 +38,9 @@ internal class PreciseAudioTimer /// The current playback position. public TimeSpan SongPosition => AbsoluteBufferDuration + SongPositionOffset; + public PreciseAudioTimer(ISampleInfo sampleInfo) + : this(sampleInfo.SampleRate, sampleInfo.BitsPerSample, sampleInfo.Channels) { } + public PreciseAudioTimer(int sampleRate, int bits, int channel) { if (bits != 8 && bits != 16 && bits != 24 && bits != 32) throw new ArgumentException(nameof(bits)); @@ -46,8 +49,8 @@ public PreciseAudioTimer(int sampleRate, int bits, int channel) SampleRate = sampleRate; BitsPerSample = bits; - Channel = channel; - BytesPerSecond = SampleRate * (BitsPerSample / 8) * Channel; + Channels = channel; + BytesPerSecond = SampleRate * (BitsPerSample / 8) * Channels; } public void Start() diff --git a/TS3Client/Full/Audio/PreciseTimedPipe.cs b/TS3Client/Full/Audio/PreciseTimedPipe.cs index 42c77f90..f1624e9d 100644 --- a/TS3Client/Full/Audio/PreciseTimedPipe.cs +++ b/TS3Client/Full/Audio/PreciseTimedPipe.cs @@ -1,30 +1,121 @@ namespace TS3Client.Full.Audio { using System; + using System.Threading; - public class PreciseTimedPipe : IAudioActiveConsumer, IAudioActiveProducer + public class PreciseTimedPipe : IAudioActiveConsumer, IAudioActiveProducer, IDisposable { + public PreciseAudioTimer AudioTimer { get; private set; } + public IAudioPassiveProducer InStream { get; set; } public IAudioPassiveConsumer OutStream { get; set; } + public TimeSpan AudioBufferLength { get; set; } = TimeSpan.FromMilliseconds(20); + public TimeSpan SendCheckInterval { get; set; } = TimeSpan.FromMilliseconds(5); + public int ReadBufferSize { get; set; } = 2048; + private byte[] readBuffer = new byte[0]; + private readonly object lockObject = new object(); + private Thread tickThread = null; + private bool running = false; + + private bool paused; + public bool Paused + { + get => paused; + set + { + if (paused != value) + { + paused = value; + if (value) + { + AudioTimer.SongPositionOffset = AudioTimer.SongPosition; + AudioTimer.Stop(); + } + else + AudioTimer.Start(); + } + } + } + public PreciseTimedPipe() { } - public PreciseTimedPipe(IAudioPassiveProducer inStream) + public PreciseTimedPipe(IAudioPassiveProducer inStream) : this() + { + InStream = inStream; + } + + public PreciseTimedPipe(IAudioPassiveConsumer outStream) : this() + { + OutStream = outStream; + } + + public PreciseTimedPipe(IAudioPassiveProducer inStream, IAudioPassiveConsumer outStream) : this() { InStream = inStream; + OutStream = outStream; + } + + private void ReadLoop() + { + while (running) + { + if (!Paused) + ReadTick(); + Thread.Sleep(SendCheckInterval); + } + } + + private void ReadTick() + { + var inStream = InStream; + if (inStream == null) + return; + + if (readBuffer.Length < ReadBufferSize) + readBuffer = new byte[ReadBufferSize]; + + while (AudioTimer.RemainingBufferDuration < AudioBufferLength) + { + int read = inStream.Read(readBuffer, 0, readBuffer.Length, out var meta); + if (read == 0) + { + // TODO do stuff + AudioTimer.Start(); + return; + } + + AudioTimer.PushBytes(read); + + OutStream?.Write(new Span(readBuffer, 0, read), meta); + } } - public void ReadTimed() + public void Initialize(ISampleInfo info) { - byte[] buffer = new byte[256]; - while (true) + lock (lockObject) { - System.Threading.Thread.Sleep(100); - var inStream = InStream; - if (inStream == null) - continue; - int read = inStream.Read(buffer, 0, buffer.Length, out var meta); - OutStream?.Write(new Span(buffer, 0, read), meta); + AudioTimer = new PreciseAudioTimer(info.SampleRate, info.BitsPerSample, info.Channels); + AudioTimer.Start(); + + if (running) + return; + + running = true; + tickThread = new Thread(ReadLoop); + tickThread.Start(); + } + } + + public void Dispose() + { + lock (lockObject) + { + if (!running) + return; + + running = false; + tickThread.Join(); } } } diff --git a/TS3Client/Full/Audio/VolumePipe.cs b/TS3Client/Full/Audio/VolumePipe.cs index 47d3d93f..359e30e8 100644 --- a/TS3Client/Full/Audio/VolumePipe.cs +++ b/TS3Client/Full/Audio/VolumePipe.cs @@ -19,20 +19,20 @@ public static void AdjustVolume(Span audioSamples, float volume) // fast calculation for *0.5 volume for (int i = 0; i < audioSamples.Length; i += 2) { - short value = (short)((audioSamples[i + 1]) << 8 | audioSamples[i]); + short value = unchecked((short)((audioSamples[i + 1]) << 8 | audioSamples[i])); var tmpshort = value >> 1; - audioSamples[i + 0] = (byte)(tmpshort >> 0); - audioSamples[i + 1] = (byte)(tmpshort >> 8); + audioSamples[i + 0] = unchecked((byte)(tmpshort >> 0)); + audioSamples[i + 1] = unchecked((byte)(tmpshort >> 8)); } } else { for (int i = 0; i < audioSamples.Length; i += 2) { - short value = (short)((audioSamples[i + 1]) << 8 | audioSamples[i]); - var tmpshort = (short)(value * volume); - audioSamples[i + 0] = (byte)(tmpshort >> 0); - audioSamples[i + 1] = (byte)(tmpshort >> 8); + short value = unchecked((short)((audioSamples[i + 1]) << 8 | audioSamples[i])); + var tmpshort = (short)Math.Max(Math.Min(value * volume, short.MaxValue), short.MinValue); + audioSamples[i + 0] = unchecked((byte)(tmpshort >> 0)); + audioSamples[i + 1] = unchecked((byte)(tmpshort >> 8)); } } } diff --git a/TS3Client/Full/BasePacket.cs b/TS3Client/Full/BasePacket.cs index 8d8d69fc..a9701628 100644 --- a/TS3Client/Full/BasePacket.cs +++ b/TS3Client/Full/BasePacket.cs @@ -9,6 +9,8 @@ namespace TS3Client.Full { + using Helper; + internal class BasePacket { public PacketType PacketType diff --git a/TS3Client/Full/NetworkStats.cs b/TS3Client/Full/NetworkStats.cs index 225b2f1f..86608f6d 100644 --- a/TS3Client/Full/NetworkStats.cs +++ b/TS3Client/Full/NetworkStats.cs @@ -10,6 +10,7 @@ namespace TS3Client.Full { using Commands; + using Helper; using System; using System.Collections.Generic; using System.Linq; diff --git a/TS3Client/Full/PacketHandler.cs b/TS3Client/Full/PacketHandler.cs index 8a030314..0e92cc19 100644 --- a/TS3Client/Full/PacketHandler.cs +++ b/TS3Client/Full/PacketHandler.cs @@ -9,9 +9,10 @@ namespace TS3Client.Full { + using Helper; using System; - using System.Diagnostics; using System.Collections.Generic; + using System.Diagnostics; using System.IO; using System.Linq; using System.Net; diff --git a/TS3Client/Full/Ts3Crypt.cs b/TS3Client/Full/Ts3Crypt.cs index 524f0aa8..55989d07 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TS3Client/Full/Ts3Crypt.cs @@ -10,6 +10,7 @@ namespace TS3Client.Full { using Commands; + using Helper; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; @@ -22,9 +23,8 @@ namespace TS3Client.Full using Org.BouncyCastle.Math.EC; using Org.BouncyCastle.Security; using System; - using System.Linq; - using System.Text; using System.Security.Cryptography; + using System.Text; /// Provides all cryptographic functions needed for the low- and high level TeamSpeak protocol usage. public sealed class Ts3Crypt @@ -456,7 +456,7 @@ private static bool FakeDecrypt(IncomingPacket packet, byte[] mac) Array.Copy(BitConverter.GetBytes(NetUtil.H2N(generationId)), 0, tmpToHash, 2, 4); Array.Copy(ivStruct, 0, tmpToHash, 6, 20); - var result = Hash256It(tmpToHash); + var result = Hash256It(tmpToHash).AsSpan(); cachedKeyNonces[cacheIndex] = new Tuple(result.Slice(0, 16).ToArray(), result.Slice(16, 16).ToArray(), generationId); } @@ -467,8 +467,8 @@ private static bool FakeDecrypt(IncomingPacket packet, byte[] mac) Array.Copy(cachedKeyNonces[cacheIndex].Item2, 0, nonce, 0, 16); // finally the first two bytes get xor'd with the packet id - key[0] ^= (byte)((packetId >> 8) & 0xFF); - key[1] ^= (byte)((packetId) & 0xFF); + key[0] ^= (byte)(packetId >> 8); + key[1] ^= (byte)(packetId >> 0); return (key, nonce); } diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index b14ae991..e50dc670 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -9,6 +9,7 @@ namespace TS3Client.Full { + using Helper; using Audio; using Commands; using Messages; @@ -26,7 +27,7 @@ namespace TS3Client.Full using ServerGroupIdT = System.UInt64; using ChannelGroupIdT = System.UInt64; - /// Creates a full TeampSpeak3 client with voice capabilities. + /// Creates a full TeamSpeak3 client with voice capabilities. public sealed class Ts3FullClient : Ts3BaseFunctions, IAudioActiveProducer, IAudioPassiveConsumer { private readonly Ts3Crypt ts3Crypt; diff --git a/TS3Client/Helper/NativeWinDllLoader.cs b/TS3Client/Helper/NativeWinDllLoader.cs new file mode 100644 index 00000000..0fa9e958 --- /dev/null +++ b/TS3Client/Helper/NativeWinDllLoader.cs @@ -0,0 +1,42 @@ +// 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 System; + using System.IO; + using System.Runtime.InteropServices; + + internal static class NativeWinDllLoader + { + [DllImport("kernel32.dll")] + private static extern IntPtr LoadLibrary(string dllToLoad); + + public static void DirectLoadLibrary(string lib) + { + if (Util.IsLinux) + return; + + string libPath = Path.Combine(ArchFolder, lib); + LoadLibrary(libPath); + } + + public static string ArchFolder + { + get + { + if (IntPtr.Size == 8) + return "x64"; + if (IntPtr.Size == 4) + return "x86"; + return "xOther"; + } + } + } +} diff --git a/TS3Client/R.cs b/TS3Client/Helper/R.cs similarity index 100% rename from TS3Client/R.cs rename to TS3Client/Helper/R.cs diff --git a/TS3Client/Util.cs b/TS3Client/Helper/Util.cs similarity index 93% rename from TS3Client/Util.cs rename to TS3Client/Helper/Util.cs index 68f806b4..ac86a995 100644 --- a/TS3Client/Util.cs +++ b/TS3Client/Helper/Util.cs @@ -7,7 +7,7 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client +namespace TS3Client.Helper { using Messages; using System; @@ -17,8 +17,14 @@ namespace TS3Client internal static class Util { - public static IEnumerable Slice(this IList arr, int from) => Slice(arr, from, arr.Count - from); - public static IEnumerable Slice(this IEnumerable arr, int from, int len) => arr.Skip(from).Take(len); + 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); diff --git a/TS3Client/LazyNotification.cs b/TS3Client/LazyNotification.cs index 39df6723..f59a8a6d 100644 --- a/TS3Client/LazyNotification.cs +++ b/TS3Client/LazyNotification.cs @@ -10,6 +10,7 @@ namespace TS3Client { using Messages; + using Helper; using System.Collections.Generic; using System.Linq; diff --git a/TS3Client/MessageProcessor.cs b/TS3Client/MessageProcessor.cs index 35e8ac8f..5917ba95 100644 --- a/TS3Client/MessageProcessor.cs +++ b/TS3Client/MessageProcessor.cs @@ -10,6 +10,7 @@ namespace TS3Client { using Commands; + using Helper; using Messages; using System; using System.Collections.Concurrent; diff --git a/TS3Client/Messages/MessageTemplates.cs b/TS3Client/Messages/MessageTemplates.cs index 45057d43..ce795ccd 100644 --- a/TS3Client/Messages/MessageTemplates.cs +++ b/TS3Client/Messages/MessageTemplates.cs @@ -16,8 +16,10 @@ namespace TS3Client.Messages { - using System; using Commands; + using Helper; + using System; + using ClientUidT = System.String; using ClientDbIdT = System.UInt64; using ClientIdT = System.UInt16; diff --git a/TS3Client/Messages/MessageTemplates.tt b/TS3Client/Messages/MessageTemplates.tt index ac061534..0790220d 100644 --- a/TS3Client/Messages/MessageTemplates.tt +++ b/TS3Client/Messages/MessageTemplates.tt @@ -16,8 +16,10 @@ namespace TS3Client.Messages { - using System; using Commands; + using Helper; + using System; + using ClientUidT = System.String; using ClientDbIdT = System.UInt64; using ClientIdT = System.UInt16; diff --git a/TS3Client/OwnEnums.cs b/TS3Client/OwnEnums.cs index 3e8c1650..8744ed07 100644 --- a/TS3Client/OwnEnums.cs +++ b/TS3Client/OwnEnums.cs @@ -9,6 +9,7 @@ namespace TS3Client { + using Helper; using System; /* diff --git a/TS3Client/Query/Ts3QueryClient.cs b/TS3Client/Query/Ts3QueryClient.cs index 028809e8..75d8ec1a 100644 --- a/TS3Client/Query/Ts3QueryClient.cs +++ b/TS3Client/Query/Ts3QueryClient.cs @@ -10,6 +10,7 @@ namespace TS3Client.Query { using Commands; + using Helper; using Messages; using System; using System.Linq; diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index 039f5330..fc0e33c6 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -73,6 +73,11 @@ + + + + + @@ -100,10 +105,10 @@ True MessageTemplates.tt - + - + @@ -117,6 +122,9 @@ + + + Designer diff --git a/TS3Client/Ts3BaseClient.cs b/TS3Client/Ts3BaseClient.cs index 574a744f..f846e668 100644 --- a/TS3Client/Ts3BaseClient.cs +++ b/TS3Client/Ts3BaseClient.cs @@ -10,6 +10,7 @@ namespace TS3Client { using Commands; + using Helper; using Messages; using System; using System.Collections.Generic; diff --git a/TS3Client/WaitBlock.cs b/TS3Client/WaitBlock.cs index 450707aa..15dd25de 100644 --- a/TS3Client/WaitBlock.cs +++ b/TS3Client/WaitBlock.cs @@ -10,6 +10,7 @@ namespace TS3Client { using Commands; + using Helper; using Messages; using System; using System.Collections.Generic; diff --git a/TS3Client/ts3protocol.md b/TS3Client/ts3protocol.md index 2bf29bca..b16c94d4 100644 --- a/TS3Client/ts3protocol.md +++ b/TS3Client/ts3protocol.md @@ -178,7 +178,7 @@ The following chapter describes the data structure for different packet types. | VId |C |N |M | U* | T* | Data | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ -For direct user/channel targeting +For direct user/channel targeting The `Newprotocol` Flag must be *unset* | Name | Type | Explanation | @@ -197,7 +197,7 @@ OR | VId |C |TY|TA| U | Data | +--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ -For targeting special groups +For targeting special groups The `Newprotocol` Flag must be *set* | Name | Type | Explanation | From 2456df2a0aad9e673df6637983631e55a3f3c32a Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 4 Jan 2018 17:14:22 +0100 Subject: [PATCH 24/48] Added missing license headers --- TS3AudioBot/Audio/CustomTargetPipe.cs | 9 +++++++++ TS3AudioBot/Audio/FfmpegProducer.cs | 9 +++++++++ TS3Client/Full/Audio/AudioInterfaces.cs | 9 +++++++++ TS3Client/Full/Audio/AudioMeta.cs | 9 +++++++++ TS3Client/Full/Audio/AudioPacketReader.cs | 9 +++++++++ TS3Client/Full/Audio/AudioPipeExtensions.cs | 9 +++++++++ TS3Client/Full/Audio/EncoderPipe.cs | 9 +++++++++ TS3Client/Full/Audio/PreciseAudioTimer.cs | 4 ++-- TS3Client/Full/Audio/PreciseTimedPipe.cs | 9 +++++++++ TS3Client/Full/Audio/SplitterPipe.cs | 9 +++++++++ TS3Client/Full/Audio/StaticMetaPipe.cs | 9 +++++++++ TS3Client/Full/Audio/StreamAudioProducer.cs | 9 +++++++++ TS3Client/Full/Audio/VolumePipe.cs | 9 +++++++++ TS3Client/Helper/NativeWinDllLoader.cs | 4 ++-- 14 files changed, 112 insertions(+), 4 deletions(-) diff --git a/TS3AudioBot/Audio/CustomTargetPipe.cs b/TS3AudioBot/Audio/CustomTargetPipe.cs index 7eba64cb..e257c2a2 100644 --- a/TS3AudioBot/Audio/CustomTargetPipe.cs +++ b/TS3AudioBot/Audio/CustomTargetPipe.cs @@ -1,3 +1,12 @@ +// 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.Audio { using Helper; diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs index 8e875c09..8c48d638 100644 --- a/TS3AudioBot/Audio/FfmpegProducer.cs +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -1,3 +1,12 @@ +// 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.Audio { using Helper; diff --git a/TS3Client/Full/Audio/AudioInterfaces.cs b/TS3Client/Full/Audio/AudioInterfaces.cs index d21f8772..fa7285e8 100644 --- a/TS3Client/Full/Audio/AudioInterfaces.cs +++ b/TS3Client/Full/Audio/AudioInterfaces.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using System; diff --git a/TS3Client/Full/Audio/AudioMeta.cs b/TS3Client/Full/Audio/AudioMeta.cs index 28fe8317..c88aed91 100644 --- a/TS3Client/Full/Audio/AudioMeta.cs +++ b/TS3Client/Full/Audio/AudioMeta.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using System.Collections.Generic; diff --git a/TS3Client/Full/Audio/AudioPacketReader.cs b/TS3Client/Full/Audio/AudioPacketReader.cs index 1a57c3b4..5346315b 100644 --- a/TS3Client/Full/Audio/AudioPacketReader.cs +++ b/TS3Client/Full/Audio/AudioPacketReader.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using System; diff --git a/TS3Client/Full/Audio/AudioPipeExtensions.cs b/TS3Client/Full/Audio/AudioPipeExtensions.cs index 7b89aea5..70b031da 100644 --- a/TS3Client/Full/Audio/AudioPipeExtensions.cs +++ b/TS3Client/Full/Audio/AudioPipeExtensions.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using System; diff --git a/TS3Client/Full/Audio/EncoderPipe.cs b/TS3Client/Full/Audio/EncoderPipe.cs index bb28f8e5..67eefe2a 100644 --- a/TS3Client/Full/Audio/EncoderPipe.cs +++ b/TS3Client/Full/Audio/EncoderPipe.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using Opus; diff --git a/TS3Client/Full/Audio/PreciseAudioTimer.cs b/TS3Client/Full/Audio/PreciseAudioTimer.cs index 0fcf29ab..223f0f05 100644 --- a/TS3Client/Full/Audio/PreciseAudioTimer.cs +++ b/TS3Client/Full/Audio/PreciseAudioTimer.cs @@ -1,5 +1,5 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors +// 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 diff --git a/TS3Client/Full/Audio/PreciseTimedPipe.cs b/TS3Client/Full/Audio/PreciseTimedPipe.cs index f1624e9d..90c496a3 100644 --- a/TS3Client/Full/Audio/PreciseTimedPipe.cs +++ b/TS3Client/Full/Audio/PreciseTimedPipe.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using System; diff --git a/TS3Client/Full/Audio/SplitterPipe.cs b/TS3Client/Full/Audio/SplitterPipe.cs index 5e61d370..42bf2f4d 100644 --- a/TS3Client/Full/Audio/SplitterPipe.cs +++ b/TS3Client/Full/Audio/SplitterPipe.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using System; diff --git a/TS3Client/Full/Audio/StaticMetaPipe.cs b/TS3Client/Full/Audio/StaticMetaPipe.cs index b935ca62..8bcfafe5 100644 --- a/TS3Client/Full/Audio/StaticMetaPipe.cs +++ b/TS3Client/Full/Audio/StaticMetaPipe.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using System; diff --git a/TS3Client/Full/Audio/StreamAudioProducer.cs b/TS3Client/Full/Audio/StreamAudioProducer.cs index c9418ac5..dc32f141 100644 --- a/TS3Client/Full/Audio/StreamAudioProducer.cs +++ b/TS3Client/Full/Audio/StreamAudioProducer.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using System.IO; diff --git a/TS3Client/Full/Audio/VolumePipe.cs b/TS3Client/Full/Audio/VolumePipe.cs index 359e30e8..5c2b13e7 100644 --- a/TS3Client/Full/Audio/VolumePipe.cs +++ b/TS3Client/Full/Audio/VolumePipe.cs @@ -1,3 +1,12 @@ +// 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.Full.Audio { using System; diff --git a/TS3Client/Helper/NativeWinDllLoader.cs b/TS3Client/Helper/NativeWinDllLoader.cs index 0fa9e958..a5c9031a 100644 --- a/TS3Client/Helper/NativeWinDllLoader.cs +++ b/TS3Client/Helper/NativeWinDllLoader.cs @@ -1,5 +1,5 @@ -// TS3AudioBot - An advanced Musicbot for Teamspeak 3 -// Copyright (C) 2017 TS3AudioBot contributors +// 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 From d01bc36e8e5f1f78966628f822839ef7a2fb61b8 Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 4 Jan 2018 19:48:43 +0100 Subject: [PATCH 25/48] Added new declarations source --- .gitignore | 2 + .gitmodules | 3 + README.md | 4 +- TS3AudioBot/Audio/FfmpegProducer.cs | 7 +- TS3AudioBot/TeamspeakControl.cs | 2 +- TS3AudioBot/Ts3Full.cs | 8 + TS3Client/Declarations | 1 + .../Messages.tt} | 120 +- TS3Client/Messages/MessageDeclarations.txt | 351 ---- .../MessageDeserializer.cs} | 29 +- TS3Client/Messages/MessageTemplates.cs | 1749 ----------------- TS3Client/TS3Client.csproj | 15 +- 12 files changed, 138 insertions(+), 2153 deletions(-) create mode 100644 .gitmodules create mode 160000 TS3Client/Declarations rename TS3Client/{Messages/MessageTemplates.tt => Generated/Messages.tt} (53%) delete mode 100644 TS3Client/Messages/MessageDeclarations.txt rename TS3Client/{Commands/CommandDeserializer.cs => Messages/MessageDeserializer.cs} (63%) delete mode 100644 TS3Client/Messages/MessageTemplates.cs diff --git a/.gitignore b/.gitignore index 8b7c6ad2..a5b97763 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,8 @@ latex/ # Keep precompiled libs !TS3AudioBot/lib/**/* +# Ignore auto-generated files +TS3Client/Generated/*.cs # Roslyn cache directories *.ide/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..3d0f5fc4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "TS3Client/Declarations"] + path = TS3Client/Declarations + url = https://github.com/ReSpeak/tsdeclarations diff --git a/README.md b/README.md index c4046399..3b724ec7 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ You can add a [youtube-dl](https://github.com/rg3/youtube-dl/) binary or source ### Compilation Before we start: _If you know what you are doing_ you can alternatively compile each dependency referenced here from source/git by yourself, but I won't add a tutorial for that. -Download the git repository with `git clone https://github.com/Splamy/TS3AudioBot.git`. +Download the git repository with `git clone --recurse-submodules https://github.com/Splamy/TS3AudioBot.git`. #### Linux 1. See if you have NuGet by just executing `nuget`. If not, get `NuGet.exe` with `wget https://dist.nuget.org/win-x86-commandline/latest/nuget.exe` @@ -113,4 +113,4 @@ Why OSL-3.0: - 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. # Badges -[![forthebadge](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/built-by-developers.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/contains-cat-gifs.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/made-with-c-sharp.svg)](http://forthebadge.com) \ No newline at end of file +[![forthebadge](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/built-by-developers.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/contains-cat-gifs.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/made-with-c-sharp.svg)](http://forthebadge.com) diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs index 8c48d638..1e5b4cbe 100644 --- a/TS3AudioBot/Audio/FfmpegProducer.cs +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -16,7 +16,7 @@ namespace TS3AudioBot.Audio using System.Text.RegularExpressions; using TS3Client.Full.Audio; - class FfmpegProducer : IAudioPassiveProducer, ISampleInfo + class FfmpegProducer : IAudioPassiveProducer, ISampleInfo, IDisposable { public event EventHandler OnSongEnd; @@ -198,5 +198,10 @@ private TimeSpan GetCurrentSongLength() return parsedSongLength.Value; } } + + public void Dispose() + { + // TODO close ffmpeg if open + } } } diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index bb1fc8ea..dc4a1c3f 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -357,7 +357,7 @@ public R IsChannelCommander() return getInfoResult.Value.IsChannelCommander; } - public void Dispose() + public virtual void Dispose() { if (tsBaseClient != null) { diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index a20ace7f..296e600f 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -349,6 +349,14 @@ public bool Paused public bool Repeated { get { return false; } set { } } #endregion + + public override void Dispose() + { + timePipe?.Dispose(); + ffmpegProducer?.Dispose(); + encoderPipe?.Dispose(); + base.Dispose(); + } } public class Ts3FullClientData : ConfigData diff --git a/TS3Client/Declarations b/TS3Client/Declarations new file mode 160000 index 00000000..bb104177 --- /dev/null +++ b/TS3Client/Declarations @@ -0,0 +1 @@ +Subproject commit bb104177b9cb88fd9a9e1966dbfc5cfd02e918eb diff --git a/TS3Client/Messages/MessageTemplates.tt b/TS3Client/Generated/Messages.tt similarity index 53% rename from TS3Client/Messages/MessageTemplates.tt rename to TS3Client/Generated/Messages.tt index 0790220d..bf505553 100644 --- a/TS3Client/Messages/MessageTemplates.tt +++ b/TS3Client/Generated/Messages.tt @@ -9,6 +9,7 @@ <#@ output extension=".cs" #> <#@ template debug="true" hostSpecific="true" #> +<#@ assembly name="System.Core" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> @@ -19,6 +20,7 @@ namespace TS3Client.Messages using Commands; using Helper; using System; + using System.Globalization; using ClientUidT = System.String; using ClientDbIdT = System.UInt64; @@ -26,18 +28,92 @@ namespace TS3Client.Messages using ChannelIdT = System.UInt64; using ServerGroupIdT = System.UInt64; using ChannelGroupIdT = System.UInt64; - using TimeSpanSecondsT = System.TimeSpan; - using TimeSpanMillisecT = System.TimeSpan; <# - string declFilePath = Host.ResolvePath("MessageDeclarations.txt"); + string declFilePath = Host.ResolvePath("../Declarations/Messages.txt"); string[] declLines = File.ReadAllLines(declFilePath); var fldDict = new Dictionary(); var msgDict = new Dictionary(); var ntfyDict = new Dictionary(); - var convDict = new Dictionary(); - + var convSet = new HashSet(); + + string GenerateDeserializer(GenField fld) + { + if(fld.isArray) + return $"{{ var t = value.Split(new[] {{ ',' }}, StringSplitOptions.RemoveEmptyEntries); {fld.fldName} = new {fld.fldType}[t.Length]; for(int i = 0; i < t.Length; i++) {GenerateSingleDeserializer(fld, "t[i]", fld.fldName + "[i]")} }}"; + else + return GenerateSingleDeserializer(fld, "value", fld.fldName); + } + + string GenerateSingleDeserializer(GenField fld, string input, string output) + { + if(!convSet.Contains(fld.fldType)) + throw new Exception("Unregistered type"); + switch (fld.fldType) + { + case "bool": + return $"{output} = {input} != \"0\";"; + case "sbyte": + case "byte": + case "short": + case "ushort": + case "int": + case "uint": + case "long": + case "ulong": + case "float": + case "double": + case "ClientDbIdT": + case "ClientIdT": + case "ChannelIdT": + case "ServerGroupIdT": + case "ChannelGroupIdT": + return $"{output} = {fld.fldType}.Parse({input}, CultureInfo.InvariantCulture);"; + case "TimeSpanSecondsT": + return $"{output} = TimeSpan.FromSeconds(double.Parse({input}, CultureInfo.InvariantCulture));"; + case "TimeSpanMillisecT": + return $"{output} = TimeSpan.FromMilliseconds(double.Parse({input}, CultureInfo.InvariantCulture));"; + case "DateTime": + return $"{output} = Util.UnixTimeStart.AddSeconds(double.Parse({input}, CultureInfo.InvariantCulture));"; + case "string": + case "ClientUidT": + return $"{output} = Ts3String.Unescape({input});"; + case "Codec": + case "HostMessageMode": + case "CodecEncryptionMode": + case "HostBannerMode": + case "MoveReason": + case "ClientType": + case "TextMessageTargetMode": + case "PermissionGroupDatabaseType": + case "GroupNamingMode": + return $"{{ if (!Enum.TryParse({input}, out {fld.fldType} val)) throw new FormatException(); {output} = val; }}"; + case "Ts3ErrorCode": + return $"{output} = (Ts3ErrorCode)ushort.Parse({input}, CultureInfo.InvariantCulture);"; + case "PermissionId": + return $"{output} = (PermissionId)int.Parse({input}, CultureInfo.InvariantCulture);"; + case "IconHash": + return $"{output} = unchecked((int)long.Parse({input}, CultureInfo.InvariantCulture));"; + default: + return "#error missing deserializer"; + } + } + + string GenerateType(GenField fld) + { + string final; + switch (fld.fldType) + { + case "TimeSpanSecondsT": final = "TimeSpan"; break; + case "TimeSpanMillisecT": final = "TimeSpan"; break; + case "IconHash": final = "int"; break; + default: final = fld.fldType; break; + } + + return final + (fld.isArray ? "[]" : ""); + } + foreach(var line in declLines) { if(string.IsNullOrWhiteSpace(line)) @@ -73,7 +149,7 @@ namespace TS3Client.Messages GenField genField; if(!fldDict.TryGetValue(param[i], out genField)) { this.Write(Environment.NewLine + "#warning Missing Field: " + param[i]); continue; } #> - public <#= genField.fldType #> <#= genField.fldName #> { get; set; }<# + public <#= genField.genType #> <#= genField.fldName #> { get; set; }<# } #> @@ -89,10 +165,10 @@ namespace TS3Client.Messages GenField genField; if(!fldDict.TryGetValue(param[i], out genField)) { this.Write(Environment.NewLine + "#warning Missing Field: " + param[i]); continue; } #> - case "<#= genField.tsName #>": <#= genField.fldName #> = <#= convDict[genField.fldType] #> break;<# + case "<#= genField.tsName #>": <#= GenerateDeserializer(genField) #> break;<# } #> - <#= isNotify ? "" : ("case \"return_code\": ReturnCode = " + convDict["string"] + " break;") #> + <#= isNotify ? "" : ("case \"return_code\": " + GenerateDeserializer(fldDict["return_code"]) + " break;") #> } <# }#> @@ -104,7 +180,19 @@ namespace TS3Client.Messages { var param = parts[1].Replace(" ", "").Split(','); if(param.Length < 4) { this.Write("#warning Invalid FIELD: " + line); continue; } - fldDict.Add(param[0], new GenField { tsName=param[1], fldName=param[2], fldType=param[3] } ); + var fld = new GenField { tsName=param[1], fldName=param[2], fldType=param[3], isArray = false, isOpt = false }; + if(fld.fldType.EndsWith("[]")) + { + fld.isArray = true; + fld.fldType = fld.fldType.Substring(0, fld.fldType.Length - 2); + } + if(fld.fldType.EndsWith("?")) + { + fld.isOpt = true; + fld.fldType = fld.fldType.Substring(0, fld.fldType.Length - 1); + } + fld.genType = GenerateType(fld); + fldDict.Add(param[0], fld ); } else if(parts[0] == "NOTIFY") { @@ -112,11 +200,10 @@ namespace TS3Client.Messages if(param.Length < 2) { this.Write("#warning Invalid NOTIFY: " + line); continue; } ntfyDict.Add(param[0], new GenNotify { enumName=param[1] } ); } - else if(parts[0] == "CONV") + else if(parts[0] == "TYPE") { - var param = parts[1].Replace(" ", "").Split(new[] { ',' }, 2); - if(param.Length < 2) { this.Write("#warning Invalid CONV: " + line); continue; } - convDict.Add(param[0], param[1] ); + if(parts[1].Contains(",")) { this.Write("#warning Invalid TYPE: " + line); continue; } + convSet.Add(parts[1].Trim()); } else if(parts[0] == "BREAK") { @@ -162,8 +249,11 @@ namespace TS3Client.Messages <#+ class GenField { public string tsName; - public string fldName; - public string fldType; + public string fldName; // propety name + public string fldType; // single type + public string genType; // final property Type + public bool isArray; + public bool isOpt; } class GenMsg { diff --git a/TS3Client/Messages/MessageDeclarations.txt b/TS3Client/Messages/MessageDeclarations.txt deleted file mode 100644 index a9921836..00000000 --- a/TS3Client/Messages/MessageDeclarations.txt +++ /dev/null @@ -1,351 +0,0 @@ -: , -Conv: bool , CommandDeserializer.DeserializeBool (value); -Conv: sbyte , CommandDeserializer.DeserializeInt8 (value); -Conv: byte , CommandDeserializer.DeserializeUInt8 (value); -Conv: short , CommandDeserializer.DeserializeInt16 (value); -Conv: ushort , CommandDeserializer.DeserializeUInt16 (value); -Conv: int , CommandDeserializer.DeserializeInt32 (value); -Conv: uint , CommandDeserializer.DeserializeUInt32 (value); -Conv: long , CommandDeserializer.DeserializeInt64 (value); -Conv: ulong , CommandDeserializer.DeserializeUInt64 (value); -Conv: float , CommandDeserializer.DeserializeSingle (value); -Conv: double , CommandDeserializer.DeserializeDouble (value); -Conv: string , CommandDeserializer.DeserializeString (value); -Conv: DateTime, CommandDeserializer.DeserializeDateTime(value); -: Enums -Conv: Codec , CommandDeserializer.DeserializeEnum(value); -Conv: HostMessageMode , CommandDeserializer.DeserializeEnum(value); -Conv: CodecEncryptionMode , CommandDeserializer.DeserializeEnum(value); -Conv: HostBannerMode , CommandDeserializer.DeserializeEnum(value); -Conv: MoveReason , CommandDeserializer.DeserializeEnum(value); -Conv: ClientType , CommandDeserializer.DeserializeEnum(value); -Conv: TextMessageTargetMode , CommandDeserializer.DeserializeEnum(value); -Conv: PermissionGroupDatabaseType, CommandDeserializer.DeserializeEnum(value); -Conv: GroupNamingMode , CommandDeserializer.DeserializeEnum(value); -Conv: Ts3ErrorCode , (Ts3ErrorCode)CommandDeserializer.DeserializeUInt16(value); -Conv: PermissionId , (PermissionId)CommandDeserializer.DeserializeInt32(value); -: Custom Id Names -Conv: ClientUidT , CommandDeserializer.DeserializeString(value); -Conv: ClientDbIdT , CommandDeserializer.DeserializeUInt64(value); -Conv: ClientIdT , CommandDeserializer.DeserializeUInt16(value); -Conv: ChannelIdT , CommandDeserializer.DeserializeUInt64(value); -Conv: ServerGroupIdT , CommandDeserializer.DeserializeUInt64(value); -Conv: ChannelGroupIdT , CommandDeserializer.DeserializeUInt64(value); -Conv: ServerGroupIdT[] , CommandDeserializer.DeserializeArray(value, CommandDeserializer.DeserializeUInt64); -Conv: TimeSpanSecondsT , CommandDeserializer.DeserializeTimeSpanSeconds(value); -Conv: TimeSpanMillisecT, CommandDeserializer.DeserializeTimeSpanMillisec(value); - - -: , , , -Field: aclid, aclid, ClientId, ClientIdT -Field: acn, acn, ClientName, string -Field: alpha, alpha, Alpha, string -Field: average_speed, average_speed, AverageSpeed, float -Field: bantime, bantime, BanTime, TimeSpanSecondsT -Field: beta, beta, Beta, string -Field: cfid, cfid, SourceChannelId, ChannelIdT -Field: cgi, cgi, ChannelGroupIndex, ChannelGroupIdT -Field: cgid, cgid, ChannelGroupId, ChannelGroupIdT -Field: channel_codec, channel_codec, Codec, Codec -Field: channel_codec_is_unencrypted, channel_codec_is_unencrypted, IsUnencrypted, bool -Field: channel_codec_latency_factor, channel_codec_latency_factor, CodecLatencyFactor, int -Field: channel_codec_quality, channel_codec_quality, CodecQuality, int -Field: channel_delete_delay, channel_delete_delay, DeleteDelay, TimeSpanSecondsT -Field: channel_flag_default, channel_flag_default, IsDefaultChannel, bool -Field: channel_flag_maxclients_unlimited, channel_flag_maxclients_unlimited, IsMaxClientsUnlimited, bool -Field: channel_flag_maxfamilyclients_inherited, channel_flag_maxfamilyclients_inherited, IsMaxFamilyClientsInherited, bool -Field: channel_flag_maxfamilyclients_unlimited, channel_flag_maxfamilyclients_unlimited, IsMaxFamilyClientsUnlimited, bool -Field: channel_flag_password, channel_flag_password, HasPassword, bool -Field: channel_flag_permanent, channel_flag_permanent, IsPermanent, bool -Field: channel_flag_private, channel_flag_private, IsPrivate, bool -Field: channel_flag_semi_permanent, channel_flag_semi_permanent, IsSemiPermanent, bool -Field: channel_forced_silence, channel_forced_silence, ForcedSilence, bool -Field: channel_icon_id, channel_icon_id, IconId, long -Field: channel_maxclients, channel_maxclients, MaxClients, int -Field: channel_maxfamilyclients, channel_maxfamilyclients, MaxFamilyClients, int -Field: channel_name, channel_name, Name, string -Field: channel_name_phonetic, channel_name_phonetic, PhoneticName, string -Field: channel_needed_subscribe_power, channel_needed_subscribe_power, NeededSubscribePower, int -Field: channel_needed_talk_power, channel_needed_talk_power, NeededTalkPower, int -Field: channel_order, channel_order, Order, int -Field: channel_topic, channel_topic, Topic, string -Field: cid, cid, ChannelId, ChannelIdT -Field: cldbid, cldbid, ClientDbId, ClientDbIdT -Field: clid, clid, ClientId, ClientIdT -Field: client_away, client_away, IsAway, bool -Field: client_away_message, client_away_message, AwayMessage, string -Field: client_badges, client_badges, Badges, string -Field: client_base64HashClientUID, client_base64HashClientUID, Base64HashClientUid, string -Field: client_channel_group_id, client_channel_group_id, ChannelGroupId, ChannelGroupIdT -Field: client_channel_group_inherited_channel_id, client_channel_group_inherited_channel_id, InheritedChannelGroupFromChannelId, ChannelGroupIdT -Field: client_channel_id, client_channel_id, ChannelId, ChannelIdT -Field: client_country, client_country, CountryCode, string -Field: client_created, client_created, CreationDate, DateTime -Field: client_database_id, client_database_id, DatabaseId, ClientDbIdT -Field: client_default_channel, client_default_channel, DefaultChannel, string -Field: client_default_token, client_default_token, DefaultToken, string -Field: client_description, client_description, Description, string -Field: client_flag_avatar, client_flag_avatar, AvatarFlag, string, ?? string ?? -Field: client_icon_id, client_icon_id, IconId, long -Field: client_id, client_id, ClientId, ClientIdT -Field: client_idle_time, client_idle_time, ClientIdleTime, TimeSpanMillisecT -Field: client_input_hardware, client_input_hardware, IsInputHardware, bool -Field: client_input_muted, client_input_muted, IsInputMuted, bool -Field: client_is_channel_commander, client_is_channel_commander, IsChannelCommander, bool -Field: client_is_priority_speaker, client_is_priority_speaker, IsPrioritySpeaker, bool -Field: client_is_recording, client_is_recording, IsRecording, bool -Field: client_is_talker, client_is_talker, IsTalker, bool -Field: client_lastconnected, client_lastconnected, LastConnected, DateTime -Field: client_lastip, client_lastip, LastIp, string -Field: client_login_name, client_login_name, LoginName, string -Field: client_meta_data, client_meta_data, Metadata, string -Field: client_month_bytes_downloaded, client_month_bytes_downloaded, MonthlyDownloadQuota, long -Field: client_month_bytes_uploaded, client_month_bytes_uploaded, MonthlyUploadQuota, long -Field: client_needed_serverquery_view_power, client_needed_serverquery_view_power, NeededServerQueryViewPower, int -Field: client_nickname, client_nickname, NickName, string -Field: client_nickname_phonetic, client_nickname_phonetic, PhoneticName, string -Field: client_origin_server_id, client_origin_server_id, OriginServerId, ulong -Field: client_output_hardware, client_output_hardware, IsClientOutputHardware, bool -Field: client_output_muted, client_output_muted, IsOutputMuted, bool -Field: client_outputonly_muted, client_outputonly_muted, IsOutputOnlyMuted, bool -Field: client_platform, client_platform, ClientPlattform, string -Field: client_security_hash, client_security_hash, SecurityHash, string -Field: client_servergroups, client_servergroups, ServerGroups, ServerGroupIdT[] -Field: client_talk_power, client_talk_power, TalkPower, int -Field: client_talk_request, client_talk_request, RequestedTalkPower, int -Field: client_talk_request_msg, client_talk_request_msg, TalkPowerRequestMessage, string -Field: client_total_bytes_downloaded, client_total_bytes_downloaded, TotalDownloadQuota, long -Field: client_total_bytes_uploaded, client_total_bytes_uploaded, TotalUploadQuota, long -Field: client_totalconnections, client_totalconnections, TotalConnections, int -Field: client_type, client_type, ClientType, ClientType -Field: client_unique_identifier, client_unique_identifier, Uid, ClientUidT -Field: client_unread_messages, client_unread_messages, UnreadMessages, int -Field: client_version, client_version, ClientVersion, string -Field: client_version_sign, client_version_sign, ClientVersionSign, string -Field: clientftfid, clientftfid, ClientFileTransferId, ushort -Field: cluid, cluid, ClientUid, ClientUidT -Field: connection_bandwidth_received_last_minute_control, connection_bandwidth_received_last_minute_control, ConnectionBandwidthReceivedLastMinuteControl, uint -Field: connection_bandwidth_received_last_minute_keepalive, connection_bandwidth_received_last_minute_keepalive, ConnectionBandwidthReceivedLastMinuteKeepalive, uint -Field: connection_bandwidth_received_last_minute_speech, connection_bandwidth_received_last_minute_speech, ConnectionBandwidthReceivedLastMinuteSpeech, uint -Field: connection_bandwidth_received_last_minute_total, connection_bandwidth_received_last_minute_total, ConnectionBandwidtReceivedLastMinute, long -Field: connection_bandwidth_received_last_second_control, connection_bandwidth_received_last_second_control, ConnectionBandwidthReceivedLastSecondControl, uint -Field: connection_bandwidth_received_last_second_keepalive, connection_bandwidth_received_last_second_keepalive, ConnectionBandwidthReceivedLastSecondKeepalive, uint -Field: connection_bandwidth_received_last_second_speech, connection_bandwidth_received_last_second_speech, ConnectionBandwidthReceivedLastSecondSpeech, uint -Field: connection_bandwidth_received_last_second_total, connection_bandwidth_received_last_second_total, ConnectionBandwidtReceivedLastSecond, long -Field: connection_bandwidth_sent_last_minute_control, connection_bandwidth_sent_last_minute_control, ConnectionBandwidthSentLastMinuteControl, uint -Field: connection_bandwidth_sent_last_minute_keepalive, connection_bandwidth_sent_last_minute_keepalive, ConnectionBandwidthSentLastMinuteKeepalive, uint -Field: connection_bandwidth_sent_last_minute_speech, connection_bandwidth_sent_last_minute_speech, ConnectionBandwidthSentLastMinuteSpeech, uint -Field: connection_bandwidth_sent_last_minute_total, connection_bandwidth_sent_last_minute_total, ConnectionBandwidtSentLastMinute, long -Field: connection_bandwidth_sent_last_second_control, connection_bandwidth_sent_last_second_control, ConnectionBandwidthSentLastSecondControl, uint -Field: connection_bandwidth_sent_last_second_keepalive, connection_bandwidth_sent_last_second_keepalive, ConnectionBandwidthSentLastSecondKeepalive, uint -Field: connection_bandwidth_sent_last_second_speech, connection_bandwidth_sent_last_second_speech, ConnectionBandwidthSentLastSecondSpeech, uint -Field: connection_bandwidth_sent_last_second_total, connection_bandwidth_sent_last_second_total, ConnectionBandwidtSentLastSecond, long -Field: connection_bytes_received_control, connection_bytes_received_control, ConnectionBytesReceivedControl, ulong -Field: connection_bytes_received_keepalive, connection_bytes_received_keepalive, ConnectionBytesReceivedKeepalive, ulong -Field: connection_bytes_received_speech, connection_bytes_received_speech, ConnectionBytesReceivedSpeech, ulong -Field: connection_bytes_received_total, connection_bytes_received_total, ConnectionBytesReceived, long -Field: connection_bytes_sent_control, connection_bytes_sent_control, ConnectionBytesSentControl, ulong -Field: connection_bytes_sent_keepalive, connection_bytes_sent_keepalive, ConnectionBytesSentKeepalive, ulong -Field: connection_bytes_sent_speech, connection_bytes_sent_speech, ConnectionBytesSentSpeech, ulong -Field: connection_bytes_sent_total, connection_bytes_sent_total, ConnectionBytesSent, long -Field: connection_client_ip, connection_client_ip, Ip, string -Field: connection_client_port, connection_client_port, ConnectionClientPort, ushort -Field: connection_client2server_packetloss_control, connection_client2server_packetloss_control, ConnectionClientToServerPacketlossControl, float -Field: connection_client2server_packetloss_keepalive, connection_client2server_packetloss_keepalive, ConnectionClientToServerPacketlossKeepalive, float -Field: connection_client2server_packetloss_speech, connection_client2server_packetloss_speech, ConnectionClientToServerPacketlossSpeech, float -Field: connection_client2server_packetloss_total, connection_client2server_packetloss_total, ConnectionClientToServerPacketlossTotal, float -Field: connection_connected_time, connection_connected_time, ConnectionTime, TimeSpanMillisecT -Field: connection_filetransfer_bandwidth_received, connection_filetransfer_bandwidth_received, ConnectionFiletransferReceived, long -Field: connection_filetransfer_bandwidth_sent, connection_filetransfer_bandwidth_sent, ConnectionFiletransferSent, long -Field: connection_idle_time, connection_idle_time, ConnectionIdleTime, TimeSpanMillisecT -Field: connection_packets_received_control, connection_packets_received_control, ConnectionPacketsReceivedControl, ulong -Field: connection_packets_received_keepalive, connection_packets_received_keepalive, ConnectionPacketsReceivedKeepalive, ulong -Field: connection_packets_received_speech, connection_packets_received_speech, ConnectionPacketsReceivedSpeech, ulong -Field: connection_packets_received_total, connection_packets_received_total, ConnectionPacketsReceived, long -Field: connection_packets_sent_control, connection_packets_sent_control, ConnectionPacketsSentControl, ulong -Field: connection_packets_sent_keepalive, connection_packets_sent_keepalive, ConnectionPacketsSentKeepalive, ulong -Field: connection_packets_sent_speech, connection_packets_sent_speech, ConnectionPacketsSentSpeech, long -Field: connection_packets_sent_total, connection_packets_sent_total, ConnectionPacketsSent, long -Field: connection_ping, connection_ping, ConnectionPing, float -Field: connection_ping_deviation, connection_ping_deviation, ConnectionPingDeviation, float -Field: connection_server2client_packetloss_control, connection_server2client_packetloss_control, ConnectionServerToClientPacketlossControl, float -Field: connection_server2client_packetloss_keepalive, connection_server2client_packetloss_keepalive, ConnectionServerToClientPacketlossKeepalive, float -Field: connection_server2client_packetloss_speech, connection_server2client_packetloss_speech, ConnectionServerToClientPacketlossSpeech, float -Field: connection_server2client_packetloss_total, connection_server2client_packetloss_total, ConnectionServerToClientPacketlossTotal, float -Field: cpid, cpid, ChannelParentId, ChannelIdT -Field: ctid, ctid, TargetChannelId, ChannelIdT -Field: current_speed, current_speed, CurrentSpeed, float -Field: datetime, datetime, DateTime, DateTime -:: ?? type, name -Field: es, es, EmptySince, TimeSpanSecondsT -Field: ft_status, status, Status, int -Field: ft_type, type, IsFile, bool -Field: ftkey, ftkey, FileTransferKey, string -Field: iconid, iconid, IconId, int -Field: id, id, Id, ChannelIdT -Field: invokerid, invokerid, InvokerId, ClientIdT -Field: invokername, invokername, InvokerName, string -Field: invokeruid, invokeruid, InvokerUid, ClientUidT -Field: lt, lt, LicenseType, ushort -Field: msg, msg, Message, string -Field: n_member_addp, n_member_addp, NeededMemberAddPower, int -Field: n_member_remove_p, n_member_remove_p, NeededMemberRemovePower, int -Field: n_modifyp, n_modifyp, NeededModifyPower, int -Field: name, name, Name, string -Field: namemode, namemode, NamingMode, GroupNamingMode -Field: omega, omega, Omega, string -Field: order, order, Order, int -Field: path, path, Path, string -Field: permid, permid, PermissionId, PermissionId -Field: permvalue, permvalue, PermissionValue, int -Field: pid, pid, ParentChannelId, ChannelIdT -Field: port, port, Port, ushort -Field: pv, pv, ProtocolVersion, ushort -Field: reasonid, reasonid, Reason, MoveReason -Field: reasonmsg, reasonmsg, ReasonMessage, string -: ?? type -Field: runtime, runtime, Runtime, TimeSpanSecondsT -Field: savedb, savedb, GroupIsPermanent, bool -Field: seconds_empty, seconds_empty, DurationEmpty, TimeSpanSecondsT -Field: seekpos, seekpos, SeekPosistion, long -: ?? type -Field: sender, sender, Sender, ulong -Field: serverftfid, serverftfid, ServerFileTransferId, ushort -Field: sgid, sgid, ServerGroupId, ServerGroupIdT -Field: size, size, Size, long -Field: sizedone, sizedone, SizeDone, long -Field: sortid, sortid, SortId, int -:: ?? type/enum -Field: status, status, Status, Ts3ErrorCode -Field: target, target, TargetClientId, ClientIdT -Field: targetmode, targetmode, Target, TextMessageTargetMode -Field: token, token, UsedToken, string -Field: token1, token1, Token1, string -Field: token2, token2, Token2, string -Field: tokencustomset, tokencustomset, TokenCustomSet, string -Field: total_clients, total_clients, TotalClients, int -Field: total_clients_family, total_clients_family, TotalFamilyClients, int -Field: type, type, GroupType, PermissionGroupDatabaseType -Field: virtualserver_ask_for_privilegekey, virtualserver_ask_for_privilegekey, AskForPrivilege, bool -Field: virtualserver_autostart, virtualserver_autostart, Autostart, bool -Field: virtualserver_channel_temp_delete_delay_default, virtualserver_channel_temp_delete_delay_default, DefaultTempChannelDeleteDelay, TimeSpanSecondsT -Field: virtualserver_clientsonline, virtualserver_clientsonline, ClientsOnline, int -Field: virtualserver_codec_encryption_mode, virtualserver_codec_encryption_mode, CodecEncryptionMode, CodecEncryptionMode -Field: virtualserver_created, virtualserver_created, ServerCreated, long -Field: virtualserver_default_channel_group, virtualserver_default_channel_group, DefaultChannelGroup, ChannelGroupIdT -Field: virtualserver_default_server_group, virtualserver_default_server_group, DefaultServerGroup, ServerGroupIdT -Field: virtualserver_hostbanner_gfx_interval, virtualserver_hostbanner_gfx_interval, HostbannerGfxInterval, TimeSpanSecondsT -Field: virtualserver_hostbanner_gfx_url, virtualserver_hostbanner_gfx_url, HostbannerGfxUrl, string -Field: virtualserver_hostbanner_mode, virtualserver_hostbanner_mode, HostbannerMode, HostBannerMode -Field: virtualserver_hostbanner_url, virtualserver_hostbanner_url, HostbannerUrl, string -Field: virtualserver_hostbutton_gfx_url, virtualserver_hostbutton_gfx_url, HostbuttonGfxUrl, string -Field: virtualserver_hostbutton_tooltip, virtualserver_hostbutton_tooltip, HostbuttonTooltip, string -Field: virtualserver_hostbutton_url, virtualserver_hostbutton_url, HostbuttonUrl, string -Field: virtualserver_hostmessage, virtualserver_hostmessage, Hostmessage, string -Field: virtualserver_hostmessage_mode, virtualserver_hostmessage_mode, HostmessageMode, HostMessageMode -Field: virtualserver_icon_id, virtualserver_icon_id, IconId, ulong -Field: virtualserver_id, virtualserver_id, VirtualServerId, ulong -Field: virtualserver_ip, virtualserver_ip, ServerIp, string -Field: virtualserver_machine_id, virtualserver_machine_id, MachineId, string -Field: virtualserver_maxclients, virtualserver_maxclients, MaxClients, int -Field: virtualserver_name, virtualserver_name, ServerName, string -Field: virtualserver_name_phonetic, virtualserver_name_phonetic, PhoneticName, string -Field: virtualserver_platform, virtualserver_platform, ServerPlatform, string -Field: virtualserver_port, virtualserver_port, VirtualServerPort, ushort -Field: virtualserver_priority_speaker_dimm_modificator, virtualserver_priority_speaker_dimm_modificator, PrioritySpeakerDimmModificator, float -Field: virtualserver_queryclientsonline, virtualserver_queryclientsonline, QueriesOnline, int -Field: virtualserver_status, virtualserver_status, VirtualServerStatus, string -Field: virtualserver_unique_identifier, virtualserver_unique_identifier, VirtualServerUid, ClientUidT -Field: virtualserver_uptime, virtualserver_uptime, Uptime, TimeSpanSecondsT -Field: virtualserver_version, virtualserver_version, ServerVersion, string -Field: virtualserver_welcomemessage, virtualserver_welcomemessage, WelcomeMessage, string -: Error stuff -Field: error_id, id, Id, Ts3ErrorCode -Field: failed_permid, failed_permid, MissingPermissionId, PermissionId -Field: return_code, return_code, ReturnCode, string -Field: extra_msg, extra_msg, ExtraMessage, string - - -: , -Notify: error, Error -Notify: notifychannelchanged, ChannelChanged -Notify: notifychannelcreated, ChannelCreated -Notify: notifychanneldeleted, ChannelDeleted -Notify: notifychanneledited, ChannelEdited -Notify: notifychannelmoved, ChannelMoved -Notify: notifychannelpasswordchanged, ChannelPasswordChanged -Notify: notifycliententerview, ClientEnterView -Notify: notifyclientleftview, ClientLeftView -Notify: notifyclientmoved, ClientMoved -Notify: notifyserveredited, ServerEdited -Notify: notifytextmessage, TextMessage -Notify: notifytokenused, TokenUsed - -Notify: channellist, ChannelList -Notify: channellistfinished, ChannelListFinished -Notify: initivexpand, InitIvExpand -Notify: initserver, InitServer -Notify: notifychannelsubscribed, ChannelSubscribed -Notify: notifychannelunsubscribed, ChannelUnsubscribed -Notify: notifyclientchannelgroupchanged, ClientChannelGroupChanged -Notify: notifyclientchatcomposing, ClientChatComposing -Notify: notifyclientneededpermissions, ClientNeededPermissions -Notify: notifyconnectioninfo, ConnectionInfo -Notify: notifyconnectioninforequest, ConnectionInfoRequest -Notify: notifyfileinfo, FileInfo -Notify: notifyfilelist, FileList -Notify: notifyfilelistfinished, FileListFinished -Notify: notifyfiletransferlist, FileTransfer -Notify: notifyservergroupclientadded, ClientServerGroupAdded -Notify: notifyservergrouplist, ServerGroupList -Notify: notifyservergroupsbyclientid, ServerGroupsByClientId -Notify: notifystartdownload, StartDownload -Notify: notifystartupload, StartUpload -Notify: notifystatusfiletransfer, FileTransferStatus - - -: , , * -: Notifications -Msg: ChannelChanged, notifychannelchanged, cid -Msg: ChannelCreated, notifychannelcreated, cid, invokerid, invokername, invokeruid, channel_order, channel_name, channel_topic, channel_flag_default, channel_flag_password, channel_flag_permanent, channel_flag_semi_permanent, channel_codec, channel_codec_quality, channel_needed_talk_power, channel_icon_id, channel_maxclients, channel_maxfamilyclients, channel_codec_latency_factor, channel_codec_is_unencrypted, channel_delete_delay, channel_flag_maxclients_unlimited, channel_flag_maxfamilyclients_unlimited, channel_flag_maxfamilyclients_inherited, channel_name_phonetic, cpid -Msg: ChannelDeleted, notifychanneldeleted, cid, invokerid, invokername, invokeruid -Msg: ChannelEdited, notifychanneledited, cid, invokerid, invokername, invokeruid, channel_order, channel_name, channel_topic, channel_flag_default, channel_flag_password, channel_flag_permanent, channel_flag_semi_permanent, channel_codec, channel_codec_quality, channel_needed_talk_power, channel_icon_id, channel_maxclients, channel_maxfamilyclients, channel_codec_latency_factor, channel_codec_is_unencrypted, channel_delete_delay, channel_flag_maxclients_unlimited, channel_flag_maxfamilyclients_unlimited, channel_flag_maxfamilyclients_inherited, channel_name_phonetic, reasonid -Msg: ChannelList, channellist, cid, cpid, channel_name, channel_topic, channel_codec, channel_codec_quality, channel_maxclients, channel_maxfamilyclients, channel_order, channel_flag_permanent, channel_flag_semi_permanent, channel_flag_default, channel_flag_password, channel_codec_latency_factor, channel_codec_is_unencrypted, channel_delete_delay, channel_flag_maxclients_unlimited, channel_flag_maxfamilyclients_unlimited, channel_flag_maxfamilyclients_inherited, channel_needed_talk_power, channel_forced_silence, channel_name_phonetic, channel_icon_id, channel_flag_private -Msg: ChannelListFinished, channellistfinished -Msg: ChannelMoved, notifychannelmoved, order, cid, invokerid, invokername, invokeruid, reasonid, cpid -Msg: ChannelPasswordChanged, notifychannelpasswordchanged, cid -Msg: ChannelSubscribed, notifychannelsubscribed, cid, es -Msg: ChannelUnsubscribed, notifychannelunsubscribed, cid -Msg: ClientChannelGroupChanged, notifyclientchannelgroupchanged, invokerid, invokername, cgid, cgi, cid, clid -Msg: ClientChatComposing, notifyclientchatcomposing, clid, cluid -Msg: ClientEnterView, notifycliententerview, reasonid, ctid, invokerid, invokername, invokeruid, clid, client_database_id, client_nickname, client_type, cfid, client_unique_identifier, client_flag_avatar, client_description, client_icon_id, client_input_muted, client_output_muted, client_outputonly_muted, client_input_hardware, client_output_hardware, client_meta_data, client_is_recording, client_channel_group_id, client_channel_group_inherited_channel_id, client_servergroups, client_away, client_away_message, client_talk_power, client_talk_request, client_talk_request_msg, client_is_talker, client_is_priority_speaker, client_unread_messages, client_nickname_phonetic, client_needed_serverquery_view_power, client_is_channel_commander, client_country, client_badges -Msg: ClientLeftView, notifyclientleftview, reasonmsg, bantime, reasonid, ctid, invokerid, invokername, invokeruid, clid, cfid -Msg: ClientMoved, notifyclientmoved, clid, reasonid, ctid, invokerid, invokername, invokeruid -Msg: ClientNeededPermissions, notifyclientneededpermissions, permid, permvalue -Msg: ClientServerGroupAdded, notifyservergroupclientadded, name, sgid, invokerid, invokername, invokeruid, clid, cluid -Msg: CommandError, error, error_id, msg, failed_permid, return_code, extra_msg -Msg: ConnectionInfo, notifyconnectioninfo, clid, connection_ping, connection_ping_deviation, connection_connected_time, connection_client_ip, connection_client_port, connection_packets_sent_speech, connection_packets_sent_keepalive, connection_packets_sent_control, connection_bytes_sent_speech, connection_bytes_sent_keepalive, connection_bytes_sent_control, connection_packets_received_speech, connection_packets_received_keepalive, connection_packets_received_control, connection_bytes_received_speech, connection_bytes_received_keepalive, connection_bytes_received_control, connection_server2client_packetloss_speech, connection_server2client_packetloss_keepalive, connection_server2client_packetloss_control, connection_server2client_packetloss_total, connection_client2server_packetloss_speech, connection_client2server_packetloss_keepalive, connection_client2server_packetloss_control, connection_client2server_packetloss_total, connection_bandwidth_sent_last_second_speech, connection_bandwidth_sent_last_second_keepalive, connection_bandwidth_sent_last_second_control, connection_bandwidth_sent_last_minute_speech, connection_bandwidth_sent_last_minute_keepalive, connection_bandwidth_sent_last_minute_control, connection_bandwidth_received_last_second_speech, connection_bandwidth_received_last_second_keepalive, connection_bandwidth_received_last_second_control, connection_bandwidth_received_last_minute_speech, connection_bandwidth_received_last_minute_keepalive, connection_bandwidth_received_last_minute_control, connection_filetransfer_bandwidth_sent, connection_filetransfer_bandwidth_received, connection_idle_time -Msg: ConnectionInfoRequest, notifyconnectioninforequest -Msg: FileListFinished, notifyfilelistfinished, cid, path -Msg: FileTransferStatus, notifystatusfiletransfer, clientftfid, status, msg, size -Msg: InitIvExpand, initivexpand, alpha, beta, omega -Msg: InitServer, initserver, virtualserver_welcomemessage, virtualserver_platform, virtualserver_version, virtualserver_maxclients, virtualserver_created, virtualserver_hostmessage, virtualserver_hostmessage_mode, virtualserver_id, virtualserver_ip, virtualserver_ask_for_privilegekey, acn, aclid, pv, lt, client_talk_power, client_needed_serverquery_view_power, virtualserver_name, virtualserver_codec_encryption_mode, virtualserver_default_server_group, virtualserver_default_channel_group, virtualserver_hostbanner_url, virtualserver_hostbanner_gfx_url, virtualserver_hostbanner_gfx_interval, virtualserver_priority_speaker_dimm_modificator, virtualserver_hostbutton_tooltip, virtualserver_hostbutton_url, virtualserver_hostbutton_gfx_url, virtualserver_name_phonetic, virtualserver_icon_id, virtualserver_hostbanner_mode, virtualserver_channel_temp_delete_delay_default -Msg: ServerEdited, notifyserveredited, invokerid, invokername, invokeruid, reasonid, virtualserver_name, virtualserver_codec_encryption_mode, virtualserver_default_server_group, virtualserver_default_channel_group, virtualserver_hostbanner_url, virtualserver_hostbanner_gfx_url, virtualserver_hostbanner_gfx_interval, virtualserver_priority_speaker_dimm_modificator, virtualserver_hostbutton_tooltip, virtualserver_hostbutton_url, virtualserver_hostbutton_gfx_url, virtualserver_name_phonetic, virtualserver_icon_id, virtualserver_hostbanner_mode, virtualserver_channel_temp_delete_delay_default -Msg: ServerGroupList, notifyservergrouplist, sgid, name, type, iconid, savedb, sortid, namemode, n_modifyp, n_member_addp, n_member_remove_p -Msg: TextMessage, notifytextmessage, targetmode, msg, target, invokerid, invokername, invokeruid -Msg: TokenUsed, notifytokenused, token, tokencustomset, token1, token2, clid, cldbid, cluid -:Responses -Msg: ChannelData,, id, pid, seconds_empty, total_clients_family, total_clients, channel_needed_subscribe_power, channel_order, channel_name, channel_topic, channel_flag_default, channel_flag_password, channel_flag_permanent, channel_flag_semi_permanent, channel_codec, channel_codec_quality, channel_needed_talk_power, channel_icon_id, channel_maxclients, channel_maxfamilyclients -Msg: ClientData,, clid, client_unique_identifier, cid, client_database_id, client_nickname, client_type -Msg: ClientDbData,, client_lastip, clid, client_unique_identifier, cid, client_database_id, client_nickname, client_type, client_flag_avatar, client_description, client_icon_id, client_created, client_lastconnected, client_totalconnections, client_month_bytes_uploaded, client_month_bytes_downloaded, client_total_bytes_uploaded, client_total_bytes_downloaded, client_base64HashClientUID -Msg: ClientInfo,, client_idle_time, client_version, client_version_sign, client_platform, client_default_channel, client_security_hash, client_login_name, client_default_token, connection_filetransfer_bandwidth_sent, connection_filetransfer_bandwidth_received, connection_packets_sent_total, connection_packets_received_total, connection_bytes_sent_total, connection_bytes_received_total, connection_bandwidth_sent_last_second_total, connection_bandwidth_received_last_second_total, connection_bandwidth_sent_last_minute_total, connection_bandwidth_received_last_minute_total, connection_connected_time, connection_client_ip, cid, client_unique_identifier, client_database_id, client_nickname, client_type, client_input_muted, client_output_muted, client_outputonly_muted, client_input_hardware, client_output_hardware, client_meta_data, client_is_recording, client_channel_group_id, client_channel_group_inherited_channel_id, client_servergroups, client_away, client_away_message, client_talk_power, client_talk_request, client_talk_request_msg, client_is_talker, client_is_priority_speaker, client_unread_messages, client_nickname_phonetic, client_needed_serverquery_view_power, client_is_channel_commander, client_country, client_badges, client_created, client_lastconnected, client_totalconnections, client_month_bytes_uploaded, client_month_bytes_downloaded, client_total_bytes_uploaded, client_total_bytes_downloaded, client_base64HashClientUID, client_flag_avatar, client_description, client_icon_id -Msg: ServerData,, virtualserver_clientsonline, virtualserver_queryclientsonline, virtualserver_maxclients, virtualserver_uptime, virtualserver_autostart, virtualserver_machine_id, virtualserver_name, virtualserver_id, virtualserver_unique_identifier, virtualserver_port, virtualserver_status -Msg: ServerGroupAddResponse,, sgid -Msg: WhoAmI,, client_id, client_channel_id, client_nickname, client_database_id, client_login_name, client_origin_server_id, virtualserver_id, virtualserver_unique_identifier, virtualserver_port, virtualserver_status, client_unique_identifier -:Hybrid -Msg: ClientServerGroup, +notifyservergroupsbyclientid, name, sgid, cldbid -Msg: FileDownload, +notifystartdownload, clientftfid, serverftfid, ftkey, port, size, msg -Msg: FileInfoTs, +notifyfileinfo, cid, path, name, size, datetime -Msg: FileList, +notifyfilelist, cid, path, name, size, datetime, ft_type -Msg: FileTransfer, +notifyfiletransferlist, clid, path, name, size, sizedone, clientftfid, serverftfid, sender, ft_status, current_speed, average_speed, runtime -Msg: FileUpload, +notifystartupload, clientftfid, serverftfid, ftkey, port, seekpos, msg diff --git a/TS3Client/Commands/CommandDeserializer.cs b/TS3Client/Messages/MessageDeserializer.cs similarity index 63% rename from TS3Client/Commands/CommandDeserializer.cs rename to TS3Client/Messages/MessageDeserializer.cs index eb4bc9c9..b15d3dcb 100644 --- a/TS3Client/Commands/CommandDeserializer.cs +++ b/TS3Client/Messages/MessageDeserializer.cs @@ -7,13 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -namespace TS3Client.Commands +namespace TS3Client.Messages { - using Helper; - using Messages; using System; using System.Collections.Generic; - using System.Globalization; using System.Linq; using KVEnu = System.Collections.Generic.IEnumerable>; @@ -96,29 +93,5 @@ private static Dictionary ParseKeyValueLineDict(string line, boo private static Dictionary ParseKeyValueLineDict(KVEnu data) => data.ToDictionary(pair => pair.Key, pair => pair.Value); - - public static bool DeserializeBool(string v) => v != "0"; - public static sbyte DeserializeInt8(string v) => sbyte.Parse(v, CultureInfo.InvariantCulture); - public static byte DeserializeUInt8(string v) => byte.Parse(v, CultureInfo.InvariantCulture); - public static short DeserializeInt16(string v) => short.Parse(v, CultureInfo.InvariantCulture); - public static ushort DeserializeUInt16(string v) => ushort.Parse(v, CultureInfo.InvariantCulture); - public static int DeserializeInt32(string v) => int.Parse(v, CultureInfo.InvariantCulture); - public static uint DeserializeUInt32(string v) => uint.Parse(v, CultureInfo.InvariantCulture); - public static long DeserializeInt64(string v) => long.Parse(v, CultureInfo.InvariantCulture); - public static ulong DeserializeUInt64(string v) => ulong.Parse(v, CultureInfo.InvariantCulture); - public static float DeserializeSingle(string v) => float.Parse(v, CultureInfo.InvariantCulture); - public static double DeserializeDouble(string v) => double.Parse(v, CultureInfo.InvariantCulture); - public static string DeserializeString(string v) => Ts3String.Unescape(v); - public static TimeSpan DeserializeTimeSpanSeconds(string v) => TimeSpan.FromSeconds(double.Parse(v, CultureInfo.InvariantCulture)); - public static TimeSpan DeserializeTimeSpanMillisec(string v) => TimeSpan.FromMilliseconds(double.Parse(v, CultureInfo.InvariantCulture)); - public static DateTime DeserializeDateTime(string v) => Util.UnixTimeStart.AddSeconds(double.Parse(v, CultureInfo.InvariantCulture)); - public static T DeserializeEnum(string v) where T : struct - { - if (!Enum.TryParse(v, out T val)) - throw new FormatException(); - return val; - } - public static T[] DeserializeArray(string v, Func converter) - => v.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).Select(converter).ToArray(); } } diff --git a/TS3Client/Messages/MessageTemplates.cs b/TS3Client/Messages/MessageTemplates.cs deleted file mode 100644 index ce795ccd..00000000 --- a/TS3Client/Messages/MessageTemplates.cs +++ /dev/null @@ -1,1749 +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 . - - - - - - -// *** DO NOT EDIT THIS FILE, IT HAS BEEN AUTO-GENERATED *** - -namespace TS3Client.Messages -{ - using Commands; - using Helper; - using System; - - using ClientUidT = System.String; - using ClientDbIdT = System.UInt64; - using ClientIdT = System.UInt16; - using ChannelIdT = System.UInt64; - using ServerGroupIdT = System.UInt64; - using ChannelGroupIdT = System.UInt64; - using TimeSpanSecondsT = System.TimeSpan; - using TimeSpanMillisecT = System.TimeSpan; - - - public sealed class ChannelChanged : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelChanged; - - - public ChannelIdT ChannelId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - - } - - } - } - - public sealed class ChannelCreated : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelCreated; - - - public ChannelIdT ChannelId { get; set; } - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - public int Order { get; set; } - public string Name { get; set; } - public string Topic { get; set; } - public bool IsDefaultChannel { get; set; } - public bool HasPassword { get; set; } - public bool IsPermanent { get; set; } - public bool IsSemiPermanent { get; set; } - public Codec Codec { get; set; } - public int CodecQuality { get; set; } - public int NeededTalkPower { get; set; } - public long IconId { get; set; } - public int MaxClients { get; set; } - public int MaxFamilyClients { get; set; } - public int CodecLatencyFactor { get; set; } - public bool IsUnencrypted { get; set; } - public TimeSpanSecondsT DeleteDelay { get; set; } - public bool IsMaxClientsUnlimited { get; set; } - public bool IsMaxFamilyClientsUnlimited { get; set; } - public bool IsMaxFamilyClientsInherited { get; set; } - public string PhoneticName { get; set; } - public ChannelIdT ChannelParentId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - case "channel_order": Order = CommandDeserializer.DeserializeInt32(value); break; - case "channel_name": Name = CommandDeserializer.DeserializeString(value); break; - case "channel_topic": Topic = CommandDeserializer.DeserializeString(value); break; - case "channel_flag_default": IsDefaultChannel = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_password": HasPassword = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_permanent": IsPermanent = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_semi_permanent": IsSemiPermanent = CommandDeserializer.DeserializeBool(value); break; - case "channel_codec": Codec = CommandDeserializer.DeserializeEnum(value); break; - case "channel_codec_quality": CodecQuality = CommandDeserializer.DeserializeInt32(value); break; - case "channel_needed_talk_power": NeededTalkPower = CommandDeserializer.DeserializeInt32(value); break; - case "channel_icon_id": IconId = CommandDeserializer.DeserializeInt64(value); break; - case "channel_maxclients": MaxClients = CommandDeserializer.DeserializeInt32(value); break; - case "channel_maxfamilyclients": MaxFamilyClients = CommandDeserializer.DeserializeInt32(value); break; - case "channel_codec_latency_factor": CodecLatencyFactor = CommandDeserializer.DeserializeInt32(value); break; - case "channel_codec_is_unencrypted": IsUnencrypted = CommandDeserializer.DeserializeBool(value); break; - case "channel_delete_delay": DeleteDelay = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_maxfamilyclients_inherited": IsMaxFamilyClientsInherited = CommandDeserializer.DeserializeBool(value); break; - case "channel_name_phonetic": PhoneticName = CommandDeserializer.DeserializeString(value); break; - case "cpid": ChannelParentId = CommandDeserializer.DeserializeUInt64(value); break; - - } - - } - } - - public sealed class ChannelDeleted : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelDeleted; - - - public ChannelIdT ChannelId { get; set; } - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class ChannelEdited : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelEdited; - - - public ChannelIdT ChannelId { get; set; } - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - public int Order { get; set; } - public string Name { get; set; } - public string Topic { get; set; } - public bool IsDefaultChannel { get; set; } - public bool HasPassword { get; set; } - public bool IsPermanent { get; set; } - public bool IsSemiPermanent { get; set; } - public Codec Codec { get; set; } - public int CodecQuality { get; set; } - public int NeededTalkPower { get; set; } - public long IconId { get; set; } - public int MaxClients { get; set; } - public int MaxFamilyClients { get; set; } - public int CodecLatencyFactor { get; set; } - public bool IsUnencrypted { get; set; } - public TimeSpanSecondsT DeleteDelay { get; set; } - public bool IsMaxClientsUnlimited { get; set; } - public bool IsMaxFamilyClientsUnlimited { get; set; } - public bool IsMaxFamilyClientsInherited { get; set; } - public string PhoneticName { get; set; } - public MoveReason Reason { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - case "channel_order": Order = CommandDeserializer.DeserializeInt32(value); break; - case "channel_name": Name = CommandDeserializer.DeserializeString(value); break; - case "channel_topic": Topic = CommandDeserializer.DeserializeString(value); break; - case "channel_flag_default": IsDefaultChannel = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_password": HasPassword = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_permanent": IsPermanent = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_semi_permanent": IsSemiPermanent = CommandDeserializer.DeserializeBool(value); break; - case "channel_codec": Codec = CommandDeserializer.DeserializeEnum(value); break; - case "channel_codec_quality": CodecQuality = CommandDeserializer.DeserializeInt32(value); break; - case "channel_needed_talk_power": NeededTalkPower = CommandDeserializer.DeserializeInt32(value); break; - case "channel_icon_id": IconId = CommandDeserializer.DeserializeInt64(value); break; - case "channel_maxclients": MaxClients = CommandDeserializer.DeserializeInt32(value); break; - case "channel_maxfamilyclients": MaxFamilyClients = CommandDeserializer.DeserializeInt32(value); break; - case "channel_codec_latency_factor": CodecLatencyFactor = CommandDeserializer.DeserializeInt32(value); break; - case "channel_codec_is_unencrypted": IsUnencrypted = CommandDeserializer.DeserializeBool(value); break; - case "channel_delete_delay": DeleteDelay = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_maxfamilyclients_inherited": IsMaxFamilyClientsInherited = CommandDeserializer.DeserializeBool(value); break; - case "channel_name_phonetic": PhoneticName = CommandDeserializer.DeserializeString(value); break; - case "reasonid": Reason = CommandDeserializer.DeserializeEnum(value); break; - - } - - } - } - - public sealed class ChannelList : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelList; - - - public ChannelIdT ChannelId { get; set; } - public ChannelIdT ChannelParentId { get; set; } - public string Name { get; set; } - public string Topic { get; set; } - public Codec Codec { get; set; } - public int CodecQuality { get; set; } - public int MaxClients { get; set; } - public int MaxFamilyClients { get; set; } - public int Order { get; set; } - public bool IsPermanent { get; set; } - public bool IsSemiPermanent { get; set; } - public bool IsDefaultChannel { get; set; } - public bool HasPassword { get; set; } - public int CodecLatencyFactor { get; set; } - public bool IsUnencrypted { get; set; } - public TimeSpanSecondsT DeleteDelay { get; set; } - public bool IsMaxClientsUnlimited { get; set; } - public bool IsMaxFamilyClientsUnlimited { get; set; } - public bool IsMaxFamilyClientsInherited { get; set; } - public int NeededTalkPower { get; set; } - public bool ForcedSilence { get; set; } - public string PhoneticName { get; set; } - public long IconId { get; set; } - public bool IsPrivate { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "cpid": ChannelParentId = CommandDeserializer.DeserializeUInt64(value); break; - case "channel_name": Name = CommandDeserializer.DeserializeString(value); break; - case "channel_topic": Topic = CommandDeserializer.DeserializeString(value); break; - case "channel_codec": Codec = CommandDeserializer.DeserializeEnum(value); break; - case "channel_codec_quality": CodecQuality = CommandDeserializer.DeserializeInt32(value); break; - case "channel_maxclients": MaxClients = CommandDeserializer.DeserializeInt32(value); break; - case "channel_maxfamilyclients": MaxFamilyClients = CommandDeserializer.DeserializeInt32(value); break; - case "channel_order": Order = CommandDeserializer.DeserializeInt32(value); break; - case "channel_flag_permanent": IsPermanent = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_semi_permanent": IsSemiPermanent = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_default": IsDefaultChannel = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_password": HasPassword = CommandDeserializer.DeserializeBool(value); break; - case "channel_codec_latency_factor": CodecLatencyFactor = CommandDeserializer.DeserializeInt32(value); break; - case "channel_codec_is_unencrypted": IsUnencrypted = CommandDeserializer.DeserializeBool(value); break; - case "channel_delete_delay": DeleteDelay = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_maxfamilyclients_inherited": IsMaxFamilyClientsInherited = CommandDeserializer.DeserializeBool(value); break; - case "channel_needed_talk_power": NeededTalkPower = CommandDeserializer.DeserializeInt32(value); break; - case "channel_forced_silence": ForcedSilence = CommandDeserializer.DeserializeBool(value); break; - case "channel_name_phonetic": PhoneticName = CommandDeserializer.DeserializeString(value); break; - case "channel_icon_id": IconId = CommandDeserializer.DeserializeInt64(value); break; - case "channel_flag_private": IsPrivate = CommandDeserializer.DeserializeBool(value); break; - - } - - } - } - - public sealed class ChannelListFinished : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelListFinished; - - - - public void SetField(string name, string value) - { - - } - } - - public sealed class ChannelMoved : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelMoved; - - - public int Order { get; set; } - public ChannelIdT ChannelId { get; set; } - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - public MoveReason Reason { get; set; } - public ChannelIdT ChannelParentId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "order": Order = CommandDeserializer.DeserializeInt32(value); break; - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - case "reasonid": Reason = CommandDeserializer.DeserializeEnum(value); break; - case "cpid": ChannelParentId = CommandDeserializer.DeserializeUInt64(value); break; - - } - - } - } - - public sealed class ChannelPasswordChanged : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelPasswordChanged; - - - public ChannelIdT ChannelId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - - } - - } - } - - public sealed class ChannelSubscribed : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelSubscribed; - - - public ChannelIdT ChannelId { get; set; } - public TimeSpanSecondsT EmptySince { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "es": EmptySince = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - - } - - } - } - - public sealed class ChannelUnsubscribed : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ChannelUnsubscribed; - - - public ChannelIdT ChannelId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - - } - - } - } - - public sealed class ClientChannelGroupChanged : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ClientChannelGroupChanged; - - - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ChannelGroupIdT ChannelGroupId { get; set; } - public ChannelGroupIdT ChannelGroupIndex { get; set; } - public ChannelIdT ChannelId { get; set; } - public ClientIdT ClientId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "cgid": ChannelGroupId = CommandDeserializer.DeserializeUInt64(value); break; - case "cgi": ChannelGroupIndex = CommandDeserializer.DeserializeUInt64(value); break; - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - - } - - } - } - - public sealed class ClientChatComposing : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ClientChatComposing; - - - public ClientIdT ClientId { get; set; } - public ClientUidT ClientUid { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "cluid": ClientUid = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class ClientEnterView : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ClientEnterView; - - - public MoveReason Reason { get; set; } - public ChannelIdT TargetChannelId { get; set; } - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - public ClientIdT ClientId { get; set; } - public ClientDbIdT DatabaseId { get; set; } - public string NickName { get; set; } - public ClientType ClientType { get; set; } - public ChannelIdT SourceChannelId { get; set; } - public ClientUidT Uid { get; set; } - public string AvatarFlag { get; set; } - public string Description { get; set; } - public long IconId { get; set; } - public bool IsInputMuted { get; set; } - public bool IsOutputMuted { get; set; } - public bool IsOutputOnlyMuted { get; set; } - public bool IsInputHardware { get; set; } - public bool IsClientOutputHardware { get; set; } - public string Metadata { get; set; } - public bool IsRecording { get; set; } - public ChannelGroupIdT ChannelGroupId { get; set; } - public ChannelGroupIdT InheritedChannelGroupFromChannelId { get; set; } - public ServerGroupIdT[] ServerGroups { get; set; } - public bool IsAway { get; set; } - public string AwayMessage { get; set; } - public int TalkPower { get; set; } - public int RequestedTalkPower { get; set; } - public string TalkPowerRequestMessage { get; set; } - public bool IsTalker { get; set; } - public bool IsPrioritySpeaker { get; set; } - public int UnreadMessages { get; set; } - public string PhoneticName { get; set; } - public int NeededServerQueryViewPower { get; set; } - public bool IsChannelCommander { get; set; } - public string CountryCode { get; set; } - public string Badges { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "reasonid": Reason = CommandDeserializer.DeserializeEnum(value); break; - case "ctid": TargetChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "client_database_id": DatabaseId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_nickname": NickName = CommandDeserializer.DeserializeString(value); break; - case "client_type": ClientType = CommandDeserializer.DeserializeEnum(value); break; - case "cfid": SourceChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_unique_identifier": Uid = CommandDeserializer.DeserializeString(value); break; - case "client_flag_avatar": AvatarFlag = CommandDeserializer.DeserializeString(value); break; - case "client_description": Description = CommandDeserializer.DeserializeString(value); break; - case "client_icon_id": IconId = CommandDeserializer.DeserializeInt64(value); break; - case "client_input_muted": IsInputMuted = CommandDeserializer.DeserializeBool(value); break; - case "client_output_muted": IsOutputMuted = CommandDeserializer.DeserializeBool(value); break; - case "client_outputonly_muted": IsOutputOnlyMuted = CommandDeserializer.DeserializeBool(value); break; - case "client_input_hardware": IsInputHardware = CommandDeserializer.DeserializeBool(value); break; - case "client_output_hardware": IsClientOutputHardware = CommandDeserializer.DeserializeBool(value); break; - case "client_meta_data": Metadata = CommandDeserializer.DeserializeString(value); break; - case "client_is_recording": IsRecording = CommandDeserializer.DeserializeBool(value); break; - case "client_channel_group_id": ChannelGroupId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_channel_group_inherited_channel_id": InheritedChannelGroupFromChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_servergroups": ServerGroups = CommandDeserializer.DeserializeArray(value,CommandDeserializer.DeserializeUInt64); break; - case "client_away": IsAway = CommandDeserializer.DeserializeBool(value); break; - case "client_away_message": AwayMessage = CommandDeserializer.DeserializeString(value); break; - case "client_talk_power": TalkPower = CommandDeserializer.DeserializeInt32(value); break; - case "client_talk_request": RequestedTalkPower = CommandDeserializer.DeserializeInt32(value); break; - case "client_talk_request_msg": TalkPowerRequestMessage = CommandDeserializer.DeserializeString(value); break; - case "client_is_talker": IsTalker = CommandDeserializer.DeserializeBool(value); break; - case "client_is_priority_speaker": IsPrioritySpeaker = CommandDeserializer.DeserializeBool(value); break; - case "client_unread_messages": UnreadMessages = CommandDeserializer.DeserializeInt32(value); break; - case "client_nickname_phonetic": PhoneticName = CommandDeserializer.DeserializeString(value); break; - case "client_needed_serverquery_view_power": NeededServerQueryViewPower = CommandDeserializer.DeserializeInt32(value); break; - case "client_is_channel_commander": IsChannelCommander = CommandDeserializer.DeserializeBool(value); break; - case "client_country": CountryCode = CommandDeserializer.DeserializeString(value); break; - case "client_badges": Badges = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class ClientLeftView : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ClientLeftView; - - - public string ReasonMessage { get; set; } - public TimeSpanSecondsT BanTime { get; set; } - public MoveReason Reason { get; set; } - public ChannelIdT TargetChannelId { get; set; } - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - public ClientIdT ClientId { get; set; } - public ChannelIdT SourceChannelId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "reasonmsg": ReasonMessage = CommandDeserializer.DeserializeString(value); break; - case "bantime": BanTime = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - case "reasonid": Reason = CommandDeserializer.DeserializeEnum(value); break; - case "ctid": TargetChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "cfid": SourceChannelId = CommandDeserializer.DeserializeUInt64(value); break; - - } - - } - } - - public sealed class ClientMoved : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ClientMoved; - - - public ClientIdT ClientId { get; set; } - public MoveReason Reason { get; set; } - public ChannelIdT TargetChannelId { get; set; } - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "reasonid": Reason = CommandDeserializer.DeserializeEnum(value); break; - case "ctid": TargetChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class ClientNeededPermissions : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ClientNeededPermissions; - - - public PermissionId PermissionId { get; set; } - public int PermissionValue { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "permid": PermissionId = (PermissionId)CommandDeserializer.DeserializeInt32(value); break; - case "permvalue": PermissionValue = CommandDeserializer.DeserializeInt32(value); break; - - } - - } - } - - public sealed class ClientServerGroupAdded : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ClientServerGroupAdded; - - - public string Name { get; set; } - public ServerGroupIdT ServerGroupId { get; set; } - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - public ClientIdT ClientId { get; set; } - public ClientUidT ClientUid { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "name": Name = CommandDeserializer.DeserializeString(value); break; - case "sgid": ServerGroupId = CommandDeserializer.DeserializeUInt64(value); break; - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "cluid": ClientUid = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class CommandError : INotification - { - public NotificationType NotifyType { get; } = NotificationType.Error; - - - public Ts3ErrorCode Id { get; set; } - public string Message { get; set; } - public PermissionId MissingPermissionId { get; set; } - public string ReturnCode { get; set; } - public string ExtraMessage { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "id": Id = (Ts3ErrorCode)CommandDeserializer.DeserializeUInt16(value); break; - case "msg": Message = CommandDeserializer.DeserializeString(value); break; - case "failed_permid": MissingPermissionId = (PermissionId)CommandDeserializer.DeserializeInt32(value); break; - case "return_code": ReturnCode = CommandDeserializer.DeserializeString(value); break; - case "extra_msg": ExtraMessage = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class ConnectionInfo : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ConnectionInfo; - - - public ClientIdT ClientId { get; set; } - public float ConnectionPing { get; set; } - public float ConnectionPingDeviation { get; set; } - public TimeSpanMillisecT ConnectionTime { get; set; } - public string Ip { get; set; } - public ushort ConnectionClientPort { get; set; } - public long ConnectionPacketsSentSpeech { get; set; } - public ulong ConnectionPacketsSentKeepalive { get; set; } - public ulong ConnectionPacketsSentControl { get; set; } - public ulong ConnectionBytesSentSpeech { get; set; } - public ulong ConnectionBytesSentKeepalive { get; set; } - public ulong ConnectionBytesSentControl { get; set; } - public ulong ConnectionPacketsReceivedSpeech { get; set; } - public ulong ConnectionPacketsReceivedKeepalive { get; set; } - public ulong ConnectionPacketsReceivedControl { get; set; } - public ulong ConnectionBytesReceivedSpeech { get; set; } - public ulong ConnectionBytesReceivedKeepalive { get; set; } - public ulong ConnectionBytesReceivedControl { get; set; } - public float ConnectionServerToClientPacketlossSpeech { get; set; } - public float ConnectionServerToClientPacketlossKeepalive { get; set; } - public float ConnectionServerToClientPacketlossControl { get; set; } - public float ConnectionServerToClientPacketlossTotal { get; set; } - public float ConnectionClientToServerPacketlossSpeech { get; set; } - public float ConnectionClientToServerPacketlossKeepalive { get; set; } - public float ConnectionClientToServerPacketlossControl { get; set; } - public float ConnectionClientToServerPacketlossTotal { get; set; } - public uint ConnectionBandwidthSentLastSecondSpeech { get; set; } - public uint ConnectionBandwidthSentLastSecondKeepalive { get; set; } - public uint ConnectionBandwidthSentLastSecondControl { get; set; } - public uint ConnectionBandwidthSentLastMinuteSpeech { get; set; } - public uint ConnectionBandwidthSentLastMinuteKeepalive { get; set; } - public uint ConnectionBandwidthSentLastMinuteControl { get; set; } - public uint ConnectionBandwidthReceivedLastSecondSpeech { get; set; } - public uint ConnectionBandwidthReceivedLastSecondKeepalive { get; set; } - public uint ConnectionBandwidthReceivedLastSecondControl { get; set; } - public uint ConnectionBandwidthReceivedLastMinuteSpeech { get; set; } - public uint ConnectionBandwidthReceivedLastMinuteKeepalive { get; set; } - public uint ConnectionBandwidthReceivedLastMinuteControl { get; set; } - public long ConnectionFiletransferSent { get; set; } - public long ConnectionFiletransferReceived { get; set; } - public TimeSpanMillisecT ConnectionIdleTime { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "connection_ping": ConnectionPing = CommandDeserializer.DeserializeSingle(value); break; - case "connection_ping_deviation": ConnectionPingDeviation = CommandDeserializer.DeserializeSingle(value); break; - case "connection_connected_time": ConnectionTime = CommandDeserializer.DeserializeTimeSpanMillisec(value); break; - case "connection_client_ip": Ip = CommandDeserializer.DeserializeString(value); break; - case "connection_client_port": ConnectionClientPort = CommandDeserializer.DeserializeUInt16(value); break; - case "connection_packets_sent_speech": ConnectionPacketsSentSpeech = CommandDeserializer.DeserializeInt64(value); break; - case "connection_packets_sent_keepalive": ConnectionPacketsSentKeepalive = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_packets_sent_control": ConnectionPacketsSentControl = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_bytes_sent_speech": ConnectionBytesSentSpeech = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_bytes_sent_keepalive": ConnectionBytesSentKeepalive = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_bytes_sent_control": ConnectionBytesSentControl = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_packets_received_speech": ConnectionPacketsReceivedSpeech = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_packets_received_keepalive": ConnectionPacketsReceivedKeepalive = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_packets_received_control": ConnectionPacketsReceivedControl = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_bytes_received_speech": ConnectionBytesReceivedSpeech = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_bytes_received_keepalive": ConnectionBytesReceivedKeepalive = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_bytes_received_control": ConnectionBytesReceivedControl = CommandDeserializer.DeserializeUInt64(value); break; - case "connection_server2client_packetloss_speech": ConnectionServerToClientPacketlossSpeech = CommandDeserializer.DeserializeSingle(value); break; - case "connection_server2client_packetloss_keepalive": ConnectionServerToClientPacketlossKeepalive = CommandDeserializer.DeserializeSingle(value); break; - case "connection_server2client_packetloss_control": ConnectionServerToClientPacketlossControl = CommandDeserializer.DeserializeSingle(value); break; - case "connection_server2client_packetloss_total": ConnectionServerToClientPacketlossTotal = CommandDeserializer.DeserializeSingle(value); break; - case "connection_client2server_packetloss_speech": ConnectionClientToServerPacketlossSpeech = CommandDeserializer.DeserializeSingle(value); break; - case "connection_client2server_packetloss_keepalive": ConnectionClientToServerPacketlossKeepalive = CommandDeserializer.DeserializeSingle(value); break; - case "connection_client2server_packetloss_control": ConnectionClientToServerPacketlossControl = CommandDeserializer.DeserializeSingle(value); break; - case "connection_client2server_packetloss_total": ConnectionClientToServerPacketlossTotal = CommandDeserializer.DeserializeSingle(value); break; - case "connection_bandwidth_sent_last_second_speech": ConnectionBandwidthSentLastSecondSpeech = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_sent_last_second_keepalive": ConnectionBandwidthSentLastSecondKeepalive = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_sent_last_second_control": ConnectionBandwidthSentLastSecondControl = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_sent_last_minute_speech": ConnectionBandwidthSentLastMinuteSpeech = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_sent_last_minute_keepalive": ConnectionBandwidthSentLastMinuteKeepalive = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_sent_last_minute_control": ConnectionBandwidthSentLastMinuteControl = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_received_last_second_speech": ConnectionBandwidthReceivedLastSecondSpeech = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_received_last_second_keepalive": ConnectionBandwidthReceivedLastSecondKeepalive = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_received_last_second_control": ConnectionBandwidthReceivedLastSecondControl = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_received_last_minute_speech": ConnectionBandwidthReceivedLastMinuteSpeech = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_received_last_minute_keepalive": ConnectionBandwidthReceivedLastMinuteKeepalive = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_bandwidth_received_last_minute_control": ConnectionBandwidthReceivedLastMinuteControl = CommandDeserializer.DeserializeUInt32(value); break; - case "connection_filetransfer_bandwidth_sent": ConnectionFiletransferSent = CommandDeserializer.DeserializeInt64(value); break; - case "connection_filetransfer_bandwidth_received": ConnectionFiletransferReceived = CommandDeserializer.DeserializeInt64(value); break; - case "connection_idle_time": ConnectionIdleTime = CommandDeserializer.DeserializeTimeSpanMillisec(value); break; - - } - - } - } - - public sealed class ConnectionInfoRequest : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ConnectionInfoRequest; - - - - public void SetField(string name, string value) - { - - } - } - - public sealed class FileListFinished : INotification - { - public NotificationType NotifyType { get; } = NotificationType.FileListFinished; - - - public ChannelIdT ChannelId { get; set; } - public string Path { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "path": Path = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class FileTransferStatus : INotification - { - public NotificationType NotifyType { get; } = NotificationType.FileTransferStatus; - - - public ushort ClientFileTransferId { get; set; } - public Ts3ErrorCode Status { get; set; } - public string Message { get; set; } - public long Size { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "clientftfid": ClientFileTransferId = CommandDeserializer.DeserializeUInt16(value); break; - case "status": Status = (Ts3ErrorCode)CommandDeserializer.DeserializeUInt16(value); break; - case "msg": Message = CommandDeserializer.DeserializeString(value); break; - case "size": Size = CommandDeserializer.DeserializeInt64(value); break; - - } - - } - } - - public sealed class InitIvExpand : INotification - { - public NotificationType NotifyType { get; } = NotificationType.InitIvExpand; - - - public string Alpha { get; set; } - public string Beta { get; set; } - public string Omega { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "alpha": Alpha = CommandDeserializer.DeserializeString(value); break; - case "beta": Beta = CommandDeserializer.DeserializeString(value); break; - case "omega": Omega = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class InitServer : INotification - { - public NotificationType NotifyType { get; } = NotificationType.InitServer; - - - public string WelcomeMessage { get; set; } - public string ServerPlatform { get; set; } - public string ServerVersion { get; set; } - public int MaxClients { get; set; } - public long ServerCreated { get; set; } - public string Hostmessage { get; set; } - public HostMessageMode HostmessageMode { get; set; } - public ulong VirtualServerId { get; set; } - public string ServerIp { get; set; } - public bool AskForPrivilege { get; set; } - public string ClientName { get; set; } - public ClientIdT ClientId { get; set; } - public ushort ProtocolVersion { get; set; } - public ushort LicenseType { get; set; } - public int TalkPower { get; set; } - public int NeededServerQueryViewPower { get; set; } - public string ServerName { get; set; } - public CodecEncryptionMode CodecEncryptionMode { get; set; } - public ServerGroupIdT DefaultServerGroup { get; set; } - public ChannelGroupIdT DefaultChannelGroup { get; set; } - public string HostbannerUrl { get; set; } - public string HostbannerGfxUrl { get; set; } - public TimeSpanSecondsT HostbannerGfxInterval { get; set; } - public float PrioritySpeakerDimmModificator { get; set; } - public string HostbuttonTooltip { get; set; } - public string HostbuttonUrl { get; set; } - public string HostbuttonGfxUrl { get; set; } - public string PhoneticName { get; set; } - public ulong IconId { get; set; } - public HostBannerMode HostbannerMode { get; set; } - public TimeSpanSecondsT DefaultTempChannelDeleteDelay { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "virtualserver_welcomemessage": WelcomeMessage = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_platform": ServerPlatform = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_version": ServerVersion = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_maxclients": MaxClients = CommandDeserializer.DeserializeInt32(value); break; - case "virtualserver_created": ServerCreated = CommandDeserializer.DeserializeInt64(value); break; - case "virtualserver_hostmessage": Hostmessage = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_hostmessage_mode": HostmessageMode = CommandDeserializer.DeserializeEnum(value); break; - case "virtualserver_id": VirtualServerId = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_ip": ServerIp = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_ask_for_privilegekey": AskForPrivilege = CommandDeserializer.DeserializeBool(value); break; - case "acn": ClientName = CommandDeserializer.DeserializeString(value); break; - case "aclid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "pv": ProtocolVersion = CommandDeserializer.DeserializeUInt16(value); break; - case "lt": LicenseType = CommandDeserializer.DeserializeUInt16(value); break; - case "client_talk_power": TalkPower = CommandDeserializer.DeserializeInt32(value); break; - case "client_needed_serverquery_view_power": NeededServerQueryViewPower = CommandDeserializer.DeserializeInt32(value); break; - case "virtualserver_name": ServerName = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_codec_encryption_mode": CodecEncryptionMode = CommandDeserializer.DeserializeEnum(value); break; - case "virtualserver_default_server_group": DefaultServerGroup = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_default_channel_group": DefaultChannelGroup = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_hostbanner_url": HostbannerUrl = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_hostbanner_gfx_interval": HostbannerGfxInterval = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - case "virtualserver_priority_speaker_dimm_modificator": PrioritySpeakerDimmModificator = CommandDeserializer.DeserializeSingle(value); break; - case "virtualserver_hostbutton_tooltip": HostbuttonTooltip = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_hostbutton_url": HostbuttonUrl = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_hostbutton_gfx_url": HostbuttonGfxUrl = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_name_phonetic": PhoneticName = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_icon_id": IconId = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_hostbanner_mode": HostbannerMode = CommandDeserializer.DeserializeEnum(value); break; - case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - - } - - } - } - - public sealed class ServerEdited : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ServerEdited; - - - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - public MoveReason Reason { get; set; } - public string ServerName { get; set; } - public CodecEncryptionMode CodecEncryptionMode { get; set; } - public ServerGroupIdT DefaultServerGroup { get; set; } - public ChannelGroupIdT DefaultChannelGroup { get; set; } - public string HostbannerUrl { get; set; } - public string HostbannerGfxUrl { get; set; } - public TimeSpanSecondsT HostbannerGfxInterval { get; set; } - public float PrioritySpeakerDimmModificator { get; set; } - public string HostbuttonTooltip { get; set; } - public string HostbuttonUrl { get; set; } - public string HostbuttonGfxUrl { get; set; } - public string PhoneticName { get; set; } - public ulong IconId { get; set; } - public HostBannerMode HostbannerMode { get; set; } - public TimeSpanSecondsT DefaultTempChannelDeleteDelay { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - case "reasonid": Reason = CommandDeserializer.DeserializeEnum(value); break; - case "virtualserver_name": ServerName = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_codec_encryption_mode": CodecEncryptionMode = CommandDeserializer.DeserializeEnum(value); break; - case "virtualserver_default_server_group": DefaultServerGroup = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_default_channel_group": DefaultChannelGroup = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_hostbanner_url": HostbannerUrl = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_hostbanner_gfx_interval": HostbannerGfxInterval = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - case "virtualserver_priority_speaker_dimm_modificator": PrioritySpeakerDimmModificator = CommandDeserializer.DeserializeSingle(value); break; - case "virtualserver_hostbutton_tooltip": HostbuttonTooltip = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_hostbutton_url": HostbuttonUrl = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_hostbutton_gfx_url": HostbuttonGfxUrl = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_name_phonetic": PhoneticName = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_icon_id": IconId = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_hostbanner_mode": HostbannerMode = CommandDeserializer.DeserializeEnum(value); break; - case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - - } - - } - } - - public sealed class ServerGroupList : INotification - { - public NotificationType NotifyType { get; } = NotificationType.ServerGroupList; - - - public ServerGroupIdT ServerGroupId { get; set; } - public string Name { get; set; } - public PermissionGroupDatabaseType GroupType { get; set; } - public int IconId { get; set; } - public bool GroupIsPermanent { get; set; } - public int SortId { get; set; } - public GroupNamingMode NamingMode { get; set; } - public int NeededModifyPower { get; set; } - public int NeededMemberAddPower { get; set; } - public int NeededMemberRemovePower { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "sgid": ServerGroupId = CommandDeserializer.DeserializeUInt64(value); break; - case "name": Name = CommandDeserializer.DeserializeString(value); break; - case "type": GroupType = CommandDeserializer.DeserializeEnum(value); break; - case "iconid": IconId = CommandDeserializer.DeserializeInt32(value); break; - case "savedb": GroupIsPermanent = CommandDeserializer.DeserializeBool(value); break; - case "sortid": SortId = CommandDeserializer.DeserializeInt32(value); break; - case "namemode": NamingMode = CommandDeserializer.DeserializeEnum(value); break; - case "n_modifyp": NeededModifyPower = CommandDeserializer.DeserializeInt32(value); break; - case "n_member_addp": NeededMemberAddPower = CommandDeserializer.DeserializeInt32(value); break; - case "n_member_remove_p": NeededMemberRemovePower = CommandDeserializer.DeserializeInt32(value); break; - - } - - } - } - - public sealed class TextMessage : INotification - { - public NotificationType NotifyType { get; } = NotificationType.TextMessage; - - - public TextMessageTargetMode Target { get; set; } - public string Message { get; set; } - public ClientIdT TargetClientId { get; set; } - public ClientIdT InvokerId { get; set; } - public string InvokerName { get; set; } - public ClientUidT InvokerUid { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "targetmode": Target = CommandDeserializer.DeserializeEnum(value); break; - case "msg": Message = CommandDeserializer.DeserializeString(value); break; - case "target": TargetClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokerid": InvokerId = CommandDeserializer.DeserializeUInt16(value); break; - case "invokername": InvokerName = CommandDeserializer.DeserializeString(value); break; - case "invokeruid": InvokerUid = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class TokenUsed : INotification - { - public NotificationType NotifyType { get; } = NotificationType.TokenUsed; - - - public string UsedToken { get; set; } - public string TokenCustomSet { get; set; } - public string Token1 { get; set; } - public string Token2 { get; set; } - public ClientIdT ClientId { get; set; } - public ClientDbIdT ClientDbId { get; set; } - public ClientUidT ClientUid { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "token": UsedToken = CommandDeserializer.DeserializeString(value); break; - case "tokencustomset": TokenCustomSet = CommandDeserializer.DeserializeString(value); break; - case "token1": Token1 = CommandDeserializer.DeserializeString(value); break; - case "token2": Token2 = CommandDeserializer.DeserializeString(value); break; - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "cldbid": ClientDbId = CommandDeserializer.DeserializeUInt64(value); break; - case "cluid": ClientUid = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class ChannelData : IResponse - { - - public string ReturnCode { get; set; } - - public ChannelIdT Id { get; set; } - public ChannelIdT ParentChannelId { get; set; } - public TimeSpanSecondsT DurationEmpty { get; set; } - public int TotalFamilyClients { get; set; } - public int TotalClients { get; set; } - public int NeededSubscribePower { get; set; } - public int Order { get; set; } - public string Name { get; set; } - public string Topic { get; set; } - public bool IsDefaultChannel { get; set; } - public bool HasPassword { get; set; } - public bool IsPermanent { get; set; } - public bool IsSemiPermanent { get; set; } - public Codec Codec { get; set; } - public int CodecQuality { get; set; } - public int NeededTalkPower { get; set; } - public long IconId { get; set; } - public int MaxClients { get; set; } - public int MaxFamilyClients { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "id": Id = CommandDeserializer.DeserializeUInt64(value); break; - case "pid": ParentChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "seconds_empty": DurationEmpty = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - case "total_clients_family": TotalFamilyClients = CommandDeserializer.DeserializeInt32(value); break; - case "total_clients": TotalClients = CommandDeserializer.DeserializeInt32(value); break; - case "channel_needed_subscribe_power": NeededSubscribePower = CommandDeserializer.DeserializeInt32(value); break; - case "channel_order": Order = CommandDeserializer.DeserializeInt32(value); break; - case "channel_name": Name = CommandDeserializer.DeserializeString(value); break; - case "channel_topic": Topic = CommandDeserializer.DeserializeString(value); break; - case "channel_flag_default": IsDefaultChannel = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_password": HasPassword = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_permanent": IsPermanent = CommandDeserializer.DeserializeBool(value); break; - case "channel_flag_semi_permanent": IsSemiPermanent = CommandDeserializer.DeserializeBool(value); break; - case "channel_codec": Codec = CommandDeserializer.DeserializeEnum(value); break; - case "channel_codec_quality": CodecQuality = CommandDeserializer.DeserializeInt32(value); break; - case "channel_needed_talk_power": NeededTalkPower = CommandDeserializer.DeserializeInt32(value); break; - case "channel_icon_id": IconId = CommandDeserializer.DeserializeInt64(value); break; - case "channel_maxclients": MaxClients = CommandDeserializer.DeserializeInt32(value); break; - case "channel_maxfamilyclients": MaxFamilyClients = CommandDeserializer.DeserializeInt32(value); break; - case "return_code": ReturnCode = CommandDeserializer.DeserializeString(value); break; - } - - } - } - - public sealed class ClientData : IResponse - { - - public string ReturnCode { get; set; } - - public ClientIdT ClientId { get; set; } - public ClientUidT Uid { get; set; } - public ChannelIdT ChannelId { get; set; } - public ClientDbIdT DatabaseId { get; set; } - public string NickName { get; set; } - public ClientType ClientType { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "client_unique_identifier": Uid = CommandDeserializer.DeserializeString(value); break; - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_database_id": DatabaseId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_nickname": NickName = CommandDeserializer.DeserializeString(value); break; - case "client_type": ClientType = CommandDeserializer.DeserializeEnum(value); break; - case "return_code": ReturnCode = CommandDeserializer.DeserializeString(value); break; - } - - } - } - - public sealed class ClientDbData : IResponse - { - - public string ReturnCode { get; set; } - - public string LastIp { get; set; } - public ClientIdT ClientId { get; set; } - public ClientUidT Uid { get; set; } - public ChannelIdT ChannelId { get; set; } - public ClientDbIdT DatabaseId { get; set; } - public string NickName { get; set; } - public ClientType ClientType { get; set; } - public string AvatarFlag { get; set; } - public string Description { get; set; } - public long IconId { get; set; } - public DateTime CreationDate { get; set; } - public DateTime LastConnected { get; set; } - public int TotalConnections { get; set; } - public long MonthlyUploadQuota { get; set; } - public long MonthlyDownloadQuota { get; set; } - public long TotalUploadQuota { get; set; } - public long TotalDownloadQuota { get; set; } - public string Base64HashClientUid { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "client_lastip": LastIp = CommandDeserializer.DeserializeString(value); break; - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "client_unique_identifier": Uid = CommandDeserializer.DeserializeString(value); break; - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_database_id": DatabaseId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_nickname": NickName = CommandDeserializer.DeserializeString(value); break; - case "client_type": ClientType = CommandDeserializer.DeserializeEnum(value); break; - case "client_flag_avatar": AvatarFlag = CommandDeserializer.DeserializeString(value); break; - case "client_description": Description = CommandDeserializer.DeserializeString(value); break; - case "client_icon_id": IconId = CommandDeserializer.DeserializeInt64(value); break; - case "client_created": CreationDate = CommandDeserializer.DeserializeDateTime(value); break; - case "client_lastconnected": LastConnected = CommandDeserializer.DeserializeDateTime(value); break; - case "client_totalconnections": TotalConnections = CommandDeserializer.DeserializeInt32(value); break; - case "client_month_bytes_uploaded": MonthlyUploadQuota = CommandDeserializer.DeserializeInt64(value); break; - case "client_month_bytes_downloaded": MonthlyDownloadQuota = CommandDeserializer.DeserializeInt64(value); break; - case "client_total_bytes_uploaded": TotalUploadQuota = CommandDeserializer.DeserializeInt64(value); break; - case "client_total_bytes_downloaded": TotalDownloadQuota = CommandDeserializer.DeserializeInt64(value); break; - case "client_base64HashClientUID": Base64HashClientUid = CommandDeserializer.DeserializeString(value); break; - case "return_code": ReturnCode = CommandDeserializer.DeserializeString(value); break; - } - - } - } - - public sealed class ClientInfo : IResponse - { - - public string ReturnCode { get; set; } - - public TimeSpanMillisecT ClientIdleTime { get; set; } - public string ClientVersion { get; set; } - public string ClientVersionSign { get; set; } - public string ClientPlattform { get; set; } - public string DefaultChannel { get; set; } - public string SecurityHash { get; set; } - public string LoginName { get; set; } - public string DefaultToken { get; set; } - public long ConnectionFiletransferSent { get; set; } - public long ConnectionFiletransferReceived { get; set; } - public long ConnectionPacketsSent { get; set; } - public long ConnectionPacketsReceived { get; set; } - public long ConnectionBytesSent { get; set; } - public long ConnectionBytesReceived { get; set; } - public long ConnectionBandwidtSentLastSecond { get; set; } - public long ConnectionBandwidtReceivedLastSecond { get; set; } - public long ConnectionBandwidtSentLastMinute { get; set; } - public long ConnectionBandwidtReceivedLastMinute { get; set; } - public TimeSpanMillisecT ConnectionTime { get; set; } - public string Ip { get; set; } - public ChannelIdT ChannelId { get; set; } - public ClientUidT Uid { get; set; } - public ClientDbIdT DatabaseId { get; set; } - public string NickName { get; set; } - public ClientType ClientType { get; set; } - public bool IsInputMuted { get; set; } - public bool IsOutputMuted { get; set; } - public bool IsOutputOnlyMuted { get; set; } - public bool IsInputHardware { get; set; } - public bool IsClientOutputHardware { get; set; } - public string Metadata { get; set; } - public bool IsRecording { get; set; } - public ChannelGroupIdT ChannelGroupId { get; set; } - public ChannelGroupIdT InheritedChannelGroupFromChannelId { get; set; } - public ServerGroupIdT[] ServerGroups { get; set; } - public bool IsAway { get; set; } - public string AwayMessage { get; set; } - public int TalkPower { get; set; } - public int RequestedTalkPower { get; set; } - public string TalkPowerRequestMessage { get; set; } - public bool IsTalker { get; set; } - public bool IsPrioritySpeaker { get; set; } - public int UnreadMessages { get; set; } - public string PhoneticName { get; set; } - public int NeededServerQueryViewPower { get; set; } - public bool IsChannelCommander { get; set; } - public string CountryCode { get; set; } - public string Badges { get; set; } - public DateTime CreationDate { get; set; } - public DateTime LastConnected { get; set; } - public int TotalConnections { get; set; } - public long MonthlyUploadQuota { get; set; } - public long MonthlyDownloadQuota { get; set; } - public long TotalUploadQuota { get; set; } - public long TotalDownloadQuota { get; set; } - public string Base64HashClientUid { get; set; } - public string AvatarFlag { get; set; } - public string Description { get; set; } - public long IconId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "client_idle_time": ClientIdleTime = CommandDeserializer.DeserializeTimeSpanMillisec(value); break; - case "client_version": ClientVersion = CommandDeserializer.DeserializeString(value); break; - case "client_version_sign": ClientVersionSign = CommandDeserializer.DeserializeString(value); break; - case "client_platform": ClientPlattform = CommandDeserializer.DeserializeString(value); break; - case "client_default_channel": DefaultChannel = CommandDeserializer.DeserializeString(value); break; - case "client_security_hash": SecurityHash = CommandDeserializer.DeserializeString(value); break; - case "client_login_name": LoginName = CommandDeserializer.DeserializeString(value); break; - case "client_default_token": DefaultToken = CommandDeserializer.DeserializeString(value); break; - case "connection_filetransfer_bandwidth_sent": ConnectionFiletransferSent = CommandDeserializer.DeserializeInt64(value); break; - case "connection_filetransfer_bandwidth_received": ConnectionFiletransferReceived = CommandDeserializer.DeserializeInt64(value); break; - case "connection_packets_sent_total": ConnectionPacketsSent = CommandDeserializer.DeserializeInt64(value); break; - case "connection_packets_received_total": ConnectionPacketsReceived = CommandDeserializer.DeserializeInt64(value); break; - case "connection_bytes_sent_total": ConnectionBytesSent = CommandDeserializer.DeserializeInt64(value); break; - case "connection_bytes_received_total": ConnectionBytesReceived = CommandDeserializer.DeserializeInt64(value); break; - case "connection_bandwidth_sent_last_second_total": ConnectionBandwidtSentLastSecond = CommandDeserializer.DeserializeInt64(value); break; - case "connection_bandwidth_received_last_second_total": ConnectionBandwidtReceivedLastSecond = CommandDeserializer.DeserializeInt64(value); break; - case "connection_bandwidth_sent_last_minute_total": ConnectionBandwidtSentLastMinute = CommandDeserializer.DeserializeInt64(value); break; - case "connection_bandwidth_received_last_minute_total": ConnectionBandwidtReceivedLastMinute = CommandDeserializer.DeserializeInt64(value); break; - case "connection_connected_time": ConnectionTime = CommandDeserializer.DeserializeTimeSpanMillisec(value); break; - case "connection_client_ip": Ip = CommandDeserializer.DeserializeString(value); break; - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_unique_identifier": Uid = CommandDeserializer.DeserializeString(value); break; - case "client_database_id": DatabaseId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_nickname": NickName = CommandDeserializer.DeserializeString(value); break; - case "client_type": ClientType = CommandDeserializer.DeserializeEnum(value); break; - case "client_input_muted": IsInputMuted = CommandDeserializer.DeserializeBool(value); break; - case "client_output_muted": IsOutputMuted = CommandDeserializer.DeserializeBool(value); break; - case "client_outputonly_muted": IsOutputOnlyMuted = CommandDeserializer.DeserializeBool(value); break; - case "client_input_hardware": IsInputHardware = CommandDeserializer.DeserializeBool(value); break; - case "client_output_hardware": IsClientOutputHardware = CommandDeserializer.DeserializeBool(value); break; - case "client_meta_data": Metadata = CommandDeserializer.DeserializeString(value); break; - case "client_is_recording": IsRecording = CommandDeserializer.DeserializeBool(value); break; - case "client_channel_group_id": ChannelGroupId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_channel_group_inherited_channel_id": InheritedChannelGroupFromChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_servergroups": ServerGroups = CommandDeserializer.DeserializeArray(value,CommandDeserializer.DeserializeUInt64); break; - case "client_away": IsAway = CommandDeserializer.DeserializeBool(value); break; - case "client_away_message": AwayMessage = CommandDeserializer.DeserializeString(value); break; - case "client_talk_power": TalkPower = CommandDeserializer.DeserializeInt32(value); break; - case "client_talk_request": RequestedTalkPower = CommandDeserializer.DeserializeInt32(value); break; - case "client_talk_request_msg": TalkPowerRequestMessage = CommandDeserializer.DeserializeString(value); break; - case "client_is_talker": IsTalker = CommandDeserializer.DeserializeBool(value); break; - case "client_is_priority_speaker": IsPrioritySpeaker = CommandDeserializer.DeserializeBool(value); break; - case "client_unread_messages": UnreadMessages = CommandDeserializer.DeserializeInt32(value); break; - case "client_nickname_phonetic": PhoneticName = CommandDeserializer.DeserializeString(value); break; - case "client_needed_serverquery_view_power": NeededServerQueryViewPower = CommandDeserializer.DeserializeInt32(value); break; - case "client_is_channel_commander": IsChannelCommander = CommandDeserializer.DeserializeBool(value); break; - case "client_country": CountryCode = CommandDeserializer.DeserializeString(value); break; - case "client_badges": Badges = CommandDeserializer.DeserializeString(value); break; - case "client_created": CreationDate = CommandDeserializer.DeserializeDateTime(value); break; - case "client_lastconnected": LastConnected = CommandDeserializer.DeserializeDateTime(value); break; - case "client_totalconnections": TotalConnections = CommandDeserializer.DeserializeInt32(value); break; - case "client_month_bytes_uploaded": MonthlyUploadQuota = CommandDeserializer.DeserializeInt64(value); break; - case "client_month_bytes_downloaded": MonthlyDownloadQuota = CommandDeserializer.DeserializeInt64(value); break; - case "client_total_bytes_uploaded": TotalUploadQuota = CommandDeserializer.DeserializeInt64(value); break; - case "client_total_bytes_downloaded": TotalDownloadQuota = CommandDeserializer.DeserializeInt64(value); break; - case "client_base64HashClientUID": Base64HashClientUid = CommandDeserializer.DeserializeString(value); break; - case "client_flag_avatar": AvatarFlag = CommandDeserializer.DeserializeString(value); break; - case "client_description": Description = CommandDeserializer.DeserializeString(value); break; - case "client_icon_id": IconId = CommandDeserializer.DeserializeInt64(value); break; - case "return_code": ReturnCode = CommandDeserializer.DeserializeString(value); break; - } - - } - } - - public sealed class ServerData : IResponse - { - - public string ReturnCode { get; set; } - - public int ClientsOnline { get; set; } - public int QueriesOnline { get; set; } - public int MaxClients { get; set; } - public TimeSpanSecondsT Uptime { get; set; } - public bool Autostart { get; set; } - public string MachineId { get; set; } - public string ServerName { get; set; } - public ulong VirtualServerId { get; set; } - public ClientUidT VirtualServerUid { get; set; } - public ushort VirtualServerPort { get; set; } - public string VirtualServerStatus { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "virtualserver_clientsonline": ClientsOnline = CommandDeserializer.DeserializeInt32(value); break; - case "virtualserver_queryclientsonline": QueriesOnline = CommandDeserializer.DeserializeInt32(value); break; - case "virtualserver_maxclients": MaxClients = CommandDeserializer.DeserializeInt32(value); break; - case "virtualserver_uptime": Uptime = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - case "virtualserver_autostart": Autostart = CommandDeserializer.DeserializeBool(value); break; - case "virtualserver_machine_id": MachineId = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_name": ServerName = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_id": VirtualServerId = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_unique_identifier": VirtualServerUid = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_port": VirtualServerPort = CommandDeserializer.DeserializeUInt16(value); break; - case "virtualserver_status": VirtualServerStatus = CommandDeserializer.DeserializeString(value); break; - case "return_code": ReturnCode = CommandDeserializer.DeserializeString(value); break; - } - - } - } - - public sealed class ServerGroupAddResponse : IResponse - { - - public string ReturnCode { get; set; } - - public ServerGroupIdT ServerGroupId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "sgid": ServerGroupId = CommandDeserializer.DeserializeUInt64(value); break; - case "return_code": ReturnCode = CommandDeserializer.DeserializeString(value); break; - } - - } - } - - public sealed class WhoAmI : IResponse - { - - public string ReturnCode { get; set; } - - public ClientIdT ClientId { get; set; } - public ChannelIdT ChannelId { get; set; } - public string NickName { get; set; } - public ClientDbIdT DatabaseId { get; set; } - public string LoginName { get; set; } - public ulong OriginServerId { get; set; } - public ulong VirtualServerId { get; set; } - public ClientUidT VirtualServerUid { get; set; } - public ushort VirtualServerPort { get; set; } - public string VirtualServerStatus { get; set; } - public ClientUidT Uid { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "client_id": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "client_channel_id": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_nickname": NickName = CommandDeserializer.DeserializeString(value); break; - case "client_database_id": DatabaseId = CommandDeserializer.DeserializeUInt64(value); break; - case "client_login_name": LoginName = CommandDeserializer.DeserializeString(value); break; - case "client_origin_server_id": OriginServerId = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_id": VirtualServerId = CommandDeserializer.DeserializeUInt64(value); break; - case "virtualserver_unique_identifier": VirtualServerUid = CommandDeserializer.DeserializeString(value); break; - case "virtualserver_port": VirtualServerPort = CommandDeserializer.DeserializeUInt16(value); break; - case "virtualserver_status": VirtualServerStatus = CommandDeserializer.DeserializeString(value); break; - case "client_unique_identifier": Uid = CommandDeserializer.DeserializeString(value); break; - case "return_code": ReturnCode = CommandDeserializer.DeserializeString(value); break; - } - - } - } - - public sealed class ClientServerGroup : INotification , IResponse - { - public NotificationType NotifyType { get; } = NotificationType.ServerGroupsByClientId; - public string ReturnCode { get; set; } - - public string Name { get; set; } - public ServerGroupIdT ServerGroupId { get; set; } - public ClientDbIdT ClientDbId { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "name": Name = CommandDeserializer.DeserializeString(value); break; - case "sgid": ServerGroupId = CommandDeserializer.DeserializeUInt64(value); break; - case "cldbid": ClientDbId = CommandDeserializer.DeserializeUInt64(value); break; - - } - - } - } - - public sealed class FileDownload : INotification , IResponse - { - public NotificationType NotifyType { get; } = NotificationType.StartDownload; - public string ReturnCode { get; set; } - - public ushort ClientFileTransferId { get; set; } - public ushort ServerFileTransferId { get; set; } - public string FileTransferKey { get; set; } - public ushort Port { get; set; } - public long Size { get; set; } - public string Message { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "clientftfid": ClientFileTransferId = CommandDeserializer.DeserializeUInt16(value); break; - case "serverftfid": ServerFileTransferId = CommandDeserializer.DeserializeUInt16(value); break; - case "ftkey": FileTransferKey = CommandDeserializer.DeserializeString(value); break; - case "port": Port = CommandDeserializer.DeserializeUInt16(value); break; - case "size": Size = CommandDeserializer.DeserializeInt64(value); break; - case "msg": Message = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public sealed class FileInfoTs : INotification , IResponse - { - public NotificationType NotifyType { get; } = NotificationType.FileInfo; - public string ReturnCode { get; set; } - - public ChannelIdT ChannelId { get; set; } - public string Path { get; set; } - public string Name { get; set; } - public long Size { get; set; } - public DateTime DateTime { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "path": Path = CommandDeserializer.DeserializeString(value); break; - case "name": Name = CommandDeserializer.DeserializeString(value); break; - case "size": Size = CommandDeserializer.DeserializeInt64(value); break; - case "datetime": DateTime = CommandDeserializer.DeserializeDateTime(value); break; - - } - - } - } - - public sealed class FileList : INotification , IResponse - { - public NotificationType NotifyType { get; } = NotificationType.FileList; - public string ReturnCode { get; set; } - - public ChannelIdT ChannelId { get; set; } - public string Path { get; set; } - public string Name { get; set; } - public long Size { get; set; } - public DateTime DateTime { get; set; } - public bool IsFile { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "cid": ChannelId = CommandDeserializer.DeserializeUInt64(value); break; - case "path": Path = CommandDeserializer.DeserializeString(value); break; - case "name": Name = CommandDeserializer.DeserializeString(value); break; - case "size": Size = CommandDeserializer.DeserializeInt64(value); break; - case "datetime": DateTime = CommandDeserializer.DeserializeDateTime(value); break; - case "type": IsFile = CommandDeserializer.DeserializeBool(value); break; - - } - - } - } - - public sealed class FileTransfer : INotification , IResponse - { - public NotificationType NotifyType { get; } = NotificationType.FileTransfer; - public string ReturnCode { get; set; } - - public ClientIdT ClientId { get; set; } - public string Path { get; set; } - public string Name { get; set; } - public long Size { get; set; } - public long SizeDone { get; set; } - public ushort ClientFileTransferId { get; set; } - public ushort ServerFileTransferId { get; set; } - public ulong Sender { get; set; } - public int Status { get; set; } - public float CurrentSpeed { get; set; } - public float AverageSpeed { get; set; } - public TimeSpanSecondsT Runtime { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "clid": ClientId = CommandDeserializer.DeserializeUInt16(value); break; - case "path": Path = CommandDeserializer.DeserializeString(value); break; - case "name": Name = CommandDeserializer.DeserializeString(value); break; - case "size": Size = CommandDeserializer.DeserializeInt64(value); break; - case "sizedone": SizeDone = CommandDeserializer.DeserializeInt64(value); break; - case "clientftfid": ClientFileTransferId = CommandDeserializer.DeserializeUInt16(value); break; - case "serverftfid": ServerFileTransferId = CommandDeserializer.DeserializeUInt16(value); break; - case "sender": Sender = CommandDeserializer.DeserializeUInt64(value); break; - case "status": Status = CommandDeserializer.DeserializeInt32(value); break; - case "current_speed": CurrentSpeed = CommandDeserializer.DeserializeSingle(value); break; - case "average_speed": AverageSpeed = CommandDeserializer.DeserializeSingle(value); break; - case "runtime": Runtime = CommandDeserializer.DeserializeTimeSpanSeconds(value); break; - - } - - } - } - - public sealed class FileUpload : INotification , IResponse - { - public NotificationType NotifyType { get; } = NotificationType.StartUpload; - public string ReturnCode { get; set; } - - public ushort ClientFileTransferId { get; set; } - public ushort ServerFileTransferId { get; set; } - public string FileTransferKey { get; set; } - public ushort Port { get; set; } - public long SeekPosistion { get; set; } - public string Message { get; set; } - - public void SetField(string name, string value) - { - - switch(name) - { - - case "clientftfid": ClientFileTransferId = CommandDeserializer.DeserializeUInt16(value); break; - case "serverftfid": ServerFileTransferId = CommandDeserializer.DeserializeUInt16(value); break; - case "ftkey": FileTransferKey = CommandDeserializer.DeserializeString(value); break; - case "port": Port = CommandDeserializer.DeserializeUInt16(value); break; - case "seekpos": SeekPosistion = CommandDeserializer.DeserializeInt64(value); break; - case "msg": Message = CommandDeserializer.DeserializeString(value); break; - - } - - } - } - - public static class MessageHelper - { - public static NotificationType GetNotificationType(string name) - { - switch(name) - { - - case "error": return NotificationType.Error; - case "notifychannelchanged": return NotificationType.ChannelChanged; - case "notifychannelcreated": return NotificationType.ChannelCreated; - case "notifychanneldeleted": return NotificationType.ChannelDeleted; - case "notifychanneledited": return NotificationType.ChannelEdited; - case "notifychannelmoved": return NotificationType.ChannelMoved; - case "notifychannelpasswordchanged": return NotificationType.ChannelPasswordChanged; - case "notifycliententerview": return NotificationType.ClientEnterView; - case "notifyclientleftview": return NotificationType.ClientLeftView; - case "notifyclientmoved": return NotificationType.ClientMoved; - case "notifyserveredited": return NotificationType.ServerEdited; - case "notifytextmessage": return NotificationType.TextMessage; - case "notifytokenused": return NotificationType.TokenUsed; - case "channellist": return NotificationType.ChannelList; - case "channellistfinished": return NotificationType.ChannelListFinished; - case "initivexpand": return NotificationType.InitIvExpand; - case "initserver": return NotificationType.InitServer; - case "notifychannelsubscribed": return NotificationType.ChannelSubscribed; - case "notifychannelunsubscribed": return NotificationType.ChannelUnsubscribed; - case "notifyclientchannelgroupchanged": return NotificationType.ClientChannelGroupChanged; - case "notifyclientchatcomposing": return NotificationType.ClientChatComposing; - case "notifyclientneededpermissions": return NotificationType.ClientNeededPermissions; - case "notifyconnectioninfo": return NotificationType.ConnectionInfo; - case "notifyconnectioninforequest": return NotificationType.ConnectionInfoRequest; - case "notifyfileinfo": return NotificationType.FileInfo; - case "notifyfilelist": return NotificationType.FileList; - case "notifyfilelistfinished": return NotificationType.FileListFinished; - case "notifyfiletransferlist": return NotificationType.FileTransfer; - case "notifyservergroupclientadded": return NotificationType.ClientServerGroupAdded; - case "notifyservergrouplist": return NotificationType.ServerGroupList; - case "notifyservergroupsbyclientid": return NotificationType.ServerGroupsByClientId; - case "notifystartdownload": return NotificationType.StartDownload; - case "notifystartupload": return NotificationType.StartUpload; - case "notifystatusfiletransfer": return NotificationType.FileTransferStatus; - default: return NotificationType.Unknown; - } - } - - public static INotification GenerateNotificationType(NotificationType name) - { - switch(name) - { - - case NotificationType.Error: return new CommandError(); - case NotificationType.ChannelChanged: return new ChannelChanged(); - case NotificationType.ChannelCreated: return new ChannelCreated(); - case NotificationType.ChannelDeleted: return new ChannelDeleted(); - case NotificationType.ChannelEdited: return new ChannelEdited(); - case NotificationType.ChannelMoved: return new ChannelMoved(); - case NotificationType.ChannelPasswordChanged: return new ChannelPasswordChanged(); - case NotificationType.ClientEnterView: return new ClientEnterView(); - case NotificationType.ClientLeftView: return new ClientLeftView(); - case NotificationType.ClientMoved: return new ClientMoved(); - case NotificationType.ServerEdited: return new ServerEdited(); - case NotificationType.TextMessage: return new TextMessage(); - case NotificationType.TokenUsed: return new TokenUsed(); - case NotificationType.ChannelList: return new ChannelList(); - case NotificationType.ChannelListFinished: return new ChannelListFinished(); - case NotificationType.InitIvExpand: return new InitIvExpand(); - case NotificationType.InitServer: return new InitServer(); - case NotificationType.ChannelSubscribed: return new ChannelSubscribed(); - case NotificationType.ChannelUnsubscribed: return new ChannelUnsubscribed(); - case NotificationType.ClientChannelGroupChanged: return new ClientChannelGroupChanged(); - case NotificationType.ClientChatComposing: return new ClientChatComposing(); - case NotificationType.ClientNeededPermissions: return new ClientNeededPermissions(); - case NotificationType.ConnectionInfo: return new ConnectionInfo(); - case NotificationType.ConnectionInfoRequest: return new ConnectionInfoRequest(); - case NotificationType.FileInfo: return new FileInfoTs(); - case NotificationType.FileList: return new FileList(); - case NotificationType.FileListFinished: return new FileListFinished(); - case NotificationType.FileTransfer: return new FileTransfer(); - case NotificationType.ClientServerGroupAdded: return new ClientServerGroupAdded(); - case NotificationType.ServerGroupList: return new ServerGroupList(); - case NotificationType.ServerGroupsByClientId: return new ClientServerGroup(); - case NotificationType.StartDownload: return new FileDownload(); - case NotificationType.StartUpload: return new FileUpload(); - case NotificationType.FileTransferStatus: return new FileTransferStatus(); - case NotificationType.Unknown: - default: throw Util.UnhandledDefault(name); - } - } - } -} - diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index fc0e33c6..d8556c85 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -96,14 +96,14 @@ - + - + True True - MessageTemplates.tt + Messages.tt @@ -131,10 +131,13 @@ - - + + + + + TextTemplatingFileGenerator - MessageTemplates.cs + Messages.cs From 7e2e3501f87ab77b2d16e3ce029830e2f01ad8c9 Mon Sep 17 00:00:00 2001 From: Splamy Date: Fri, 5 Jan 2018 00:40:39 +0100 Subject: [PATCH 26/48] Generation permissions + added transform property in csproj --- TS3Client/Generated/Messages.tt | 4 +- TS3Client/Generated/Permissions.tt | 48 +++ TS3Client/OwnEnums.cs | 513 ----------------------------- TS3Client/TS3Client.csproj | 11 +- 4 files changed, 60 insertions(+), 516 deletions(-) create mode 100644 TS3Client/Generated/Permissions.tt diff --git a/TS3Client/Generated/Messages.tt b/TS3Client/Generated/Messages.tt index bf505553..70defdec 100644 --- a/TS3Client/Generated/Messages.tt +++ b/TS3Client/Generated/Messages.tt @@ -8,8 +8,8 @@ // program. If not, see . <#@ output extension=".cs" #> -<#@ template debug="true" hostSpecific="true" #> -<#@ assembly name="System.Core" #> +<#@ template debug="false" hostSpecific="true" language="C#" #> +<#@ assembly name="System.Core" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> diff --git a/TS3Client/Generated/Permissions.tt b/TS3Client/Generated/Permissions.tt new file mode 100644 index 00000000..5f2a2bf5 --- /dev/null +++ b/TS3Client/Generated/Permissions.tt @@ -0,0 +1,48 @@ +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ assembly name="Microsoft.VisualBasic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Microsoft.VisualBasic.FileIO" #> +<#@ output extension=".cs" #> + +<# +string declFilePath = Host.ResolvePath("../Declarations/Permissions.csv"); +var data = new List(); +using (TextFieldParser parser = new TextFieldParser(declFilePath)) +{ + parser.TextFieldType = FieldType.Delimited; + parser.SetDelimiters(","); + while (!parser.EndOfData) + data.Add(parser.ReadFields()); +} +#> + +namespace TS3Client +{ + using Helper; + + // Source: https://www.tsviewer.com/index.php?page=faq&id=12&newlanguage=en + public enum PermissionId : int + { + // ReSharper disable InconsistentNaming, UnusedMember.Global + undefined = -1,<# foreach (var line in data.Skip(1)) { #> + <#= line[0] #> = <#= line[2] #>,<# } #> + // ReSharper enable InconsistentNaming, UnusedMember.Global + } + + public static class PerissionInfo + { + public static string Get(PermissionId permid) + { + switch (permid) + { + // ReSharper disable InconsistentNaming, UnusedMember.Global + case PermissionId.undefined: return "Undefined permission";<# foreach (var line in data.Skip(1)) { #> + case PermissionId.<#= line[0] #> : return "<#= line[1] #>";<# } #> + default: throw Util.UnhandledDefault(permid); + // ReSharper enable InconsistentNaming, UnusedMember.Global + } + } + } +} \ No newline at end of file diff --git a/TS3Client/OwnEnums.cs b/TS3Client/OwnEnums.cs index 8744ed07..23a07bf5 100644 --- a/TS3Client/OwnEnums.cs +++ b/TS3Client/OwnEnums.cs @@ -9,7 +9,6 @@ namespace TS3Client { - using Helper; using System; /* @@ -547,516 +546,4 @@ public enum Ts3ErrorCode : ushort custom_error = 0xFFFF, // ReSharper enable InconsistentNaming, UnusedMember.Global } - - // Source: https://www.tsviewer.com/index.php?page=faq&id=12&newlanguage=en - public enum PermissionId : int - { - undefined = -1, - unknown = 0, - b_serverinstance_help_view = 1, - b_serverinstance_version_view = 2, - b_serverinstance_info_view = 3, - b_serverinstance_virtualserver_list = 4, - b_serverinstance_binding_list = 5, - b_serverinstance_permission_list = 6, - b_serverinstance_permission_find = 7, - b_virtualserver_create = 8, - b_virtualserver_delete = 9, - b_virtualserver_start_any = 10, - b_virtualserver_stop_any = 11, - b_virtualserver_change_machine_id = 12, - b_virtualserver_change_template = 13, - b_serverquery_login = 14, - b_serverinstance_textmessage_send = 15, - b_serverinstance_log_view = 16, - b_serverinstance_log_add = 17, - b_serverinstance_stop = 18, - b_serverinstance_modify_settings = 19, - b_serverinstance_modify_querygroup = 20, - b_serverinstance_modify_templates = 21, - b_virtualserver_select = 22, - b_virtualserver_info_view = 23, - b_virtualserver_connectioninfo_view = 24, - b_virtualserver_channel_list = 25, - b_virtualserver_channel_search = 26, - b_virtualserver_client_list = 27, - b_virtualserver_client_search = 28, - b_virtualserver_client_dblist = 29, - b_virtualserver_client_dbsearch = 30, - b_virtualserver_client_dbinfo = 31, - b_virtualserver_permission_find = 32, - b_virtualserver_custom_search = 33, - b_virtualserver_start = 34, - b_virtualserver_stop = 35, - b_virtualserver_token_list = 36, - b_virtualserver_token_add = 37, - b_virtualserver_token_use = 38, - b_virtualserver_token_delete = 39, - b_virtualserver_log_view = 40, - b_virtualserver_log_add = 41, - b_virtualserver_join_ignore_password = 42, - b_virtualserver_notify_register = 43, - b_virtualserver_notify_unregister = 44, - b_virtualserver_snapshot_create = 45, - b_virtualserver_snapshot_deploy = 46, - b_virtualserver_permission_reset = 47, - b_virtualserver_modify_name = 48, - b_virtualserver_modify_welcomemessage = 49, - b_virtualserver_modify_maxclients = 50, - b_virtualserver_modify_reserved_slots = 51, - b_virtualserver_modify_password = 52, - b_virtualserver_modify_default_servergroup = 53, - b_virtualserver_modify_default_channelgroup = 54, - b_virtualserver_modify_default_channeladmingroup = 55, - b_virtualserver_modify_channel_forced_silence = 56, - b_virtualserver_modify_complain = 57, - b_virtualserver_modify_antiflood = 58, - b_virtualserver_modify_ft_settings = 59, - b_virtualserver_modify_ft_quotas = 60, - b_virtualserver_modify_hostmessage = 61, - b_virtualserver_modify_hostbanner = 62, - b_virtualserver_modify_hostbutton = 63, - b_virtualserver_modify_port = 64, - b_virtualserver_modify_autostart = 65, - b_virtualserver_modify_needed_identity_security_level = 66, - b_virtualserver_modify_priority_speaker_dimm_modificator = 67, - b_virtualserver_modify_log_settings = 68, - b_virtualserver_modify_min_client_version = 69, - b_virtualserver_modify_icon_id = 70, - b_virtualserver_modify_weblist = 71, - b_virtualserver_modify_codec_encryption_mode = 72, - b_virtualserver_modify_temporary_passwords = 73, - b_virtualserver_modify_temporary_passwords_own = 74, - b_virtualserver_modify_channel_temp_delete_delay_default = 75, - i_channel_min_depth = 76, - i_channel_max_depth = 77, - b_channel_group_inheritance_end = 78, - i_channel_permission_modify_power = 79, - i_channel_needed_permission_modify_power = 80, - b_channel_info_view = 81, - b_channel_create_child = 82, - b_channel_create_permanent = 83, - b_channel_create_semi_permanent = 84, - b_channel_create_temporary = 85, - b_channel_create_private = 86, - b_channel_create_with_topic = 87, - b_channel_create_with_description = 88, - b_channel_create_with_password = 89, - b_channel_create_modify_with_codec_speex8 = 90, - b_channel_create_modify_with_codec_speex16 = 91, - b_channel_create_modify_with_codec_speex32 = 92, - b_channel_create_modify_with_codec_celtmono48 = 93, - b_channel_create_modify_with_codec_opusvoice = 94, - b_channel_create_modify_with_codec_opusmusic = 95, - i_channel_create_modify_with_codec_maxquality = 96, - i_channel_create_modify_with_codec_latency_factor_min = 97, - b_channel_create_with_maxclients = 98, - b_channel_create_with_maxfamilyclients = 99, - b_channel_create_with_sortorder = 100, - b_channel_create_with_default = 101, - b_channel_create_with_needed_talk_power = 102, - b_channel_create_modify_with_force_password = 103, - i_channel_create_modify_with_temp_delete_delay = 104, - b_channel_modify_parent = 105, - b_channel_modify_make_default = 106, - b_channel_modify_make_permanent = 107, - b_channel_modify_make_semi_permanent = 108, - b_channel_modify_make_temporary = 109, - b_channel_modify_name = 110, - b_channel_modify_topic = 111, - b_channel_modify_description = 112, - b_channel_modify_password = 113, - b_channel_modify_codec = 114, - b_channel_modify_codec_quality = 115, - b_channel_modify_codec_latency_factor = 116, - b_channel_modify_maxclients = 117, - b_channel_modify_maxfamilyclients = 118, - b_channel_modify_sortorder = 119, - b_channel_modify_needed_talk_power = 120, - i_channel_modify_power = 121, - i_channel_needed_modify_power = 122, - b_channel_modify_make_codec_encrypted = 123, - b_channel_modify_temp_delete_delay = 124, - b_channel_delete_permanent = 125, - b_channel_delete_semi_permanent = 126, - b_channel_delete_temporary = 127, - b_channel_delete_flag_force = 128, - i_channel_delete_power = 129, - i_channel_needed_delete_power = 130, - b_channel_join_permanent = 131, - b_channel_join_semi_permanent = 132, - b_channel_join_temporary = 133, - b_channel_join_ignore_password = 134, - b_channel_join_ignore_maxclients = 135, - i_channel_join_power = 136, - i_channel_needed_join_power = 137, - i_channel_subscribe_power = 138, - i_channel_needed_subscribe_power = 139, - i_channel_description_view_power = 140, - i_channel_needed_description_view_power = 141, - i_icon_id = 142, - i_max_icon_filesize = 143, - b_icon_manage = 144, - b_group_is_permanent = 145, - i_group_auto_update_type = 146, - i_group_auto_update_max_value = 147, - i_group_sort_id = 148, - i_group_show_name_in_tree = 149, - b_virtualserver_servergroup_list = 150, - b_virtualserver_servergroup_permission_list = 151, - b_virtualserver_servergroup_client_list = 152, - b_virtualserver_channelgroup_list = 153, - b_virtualserver_channelgroup_permission_list = 154, - b_virtualserver_channelgroup_client_list = 155, - b_virtualserver_client_permission_list = 156, - b_virtualserver_channel_permission_list = 157, - b_virtualserver_channelclient_permission_list = 158, - b_virtualserver_servergroup_create = 159, - b_virtualserver_channelgroup_create = 160, - i_group_modify_power = 161, - i_group_needed_modify_power = 162, - i_group_member_add_power = 163, - i_group_needed_member_add_power = 164, - i_group_member_remove_power = 165, - i_group_needed_member_remove_power = 166, - i_permission_modify_power = 167, - b_permission_modify_power_ignore = 168, - b_virtualserver_servergroup_delete = 169, - b_virtualserver_channelgroup_delete = 170, - i_client_permission_modify_power = 171, - i_client_needed_permission_modify_power = 172, - i_client_max_clones_uid = 173, - i_client_max_idletime = 174, - i_client_max_avatar_filesize = 175, - i_client_max_channel_subscriptions = 176, - b_client_is_priority_speaker = 177, - b_client_skip_channelgroup_permissions = 178, - b_client_force_push_to_talk = 179, - b_client_ignore_bans = 180, - b_client_ignore_antiflood = 181, - b_client_issue_client_query_command = 182, - b_client_use_reserved_slot = 183, - b_client_use_channel_commander = 184, - b_client_request_talker = 185, - b_client_avatar_delete_other = 186, - b_client_is_sticky = 187, - b_client_ignore_sticky = 188, - b_client_info_view = 189, - b_client_permissionoverview_view = 190, - b_client_permissionoverview_own = 191, - b_client_remoteaddress_view = 192, - i_client_serverquery_view_power = 193, - i_client_needed_serverquery_view_power = 194, - b_client_custom_info_view = 195, - i_client_kick_from_server_power = 196, - i_client_needed_kick_from_server_power = 197, - i_client_kick_from_channel_power = 198, - i_client_needed_kick_from_channel_power = 199, - i_client_ban_power = 200, - i_client_needed_ban_power = 201, - i_client_move_power = 202, - i_client_needed_move_power = 203, - i_client_complain_power = 204, - i_client_needed_complain_power = 205, - b_client_complain_list = 206, - b_client_complain_delete_own = 207, - b_client_complain_delete = 208, - b_client_ban_list = 209, - b_client_ban_create = 210, - b_client_ban_delete_own = 211, - b_client_ban_delete = 212, - i_client_ban_max_bantime = 213, - i_client_private_textmessage_power = 214, - i_client_needed_private_textmessage_power = 215, - b_client_server_textmessage_send = 216, - b_client_channel_textmessage_send = 217, - b_client_offline_textmessage_send = 218, - i_client_talk_power = 219, - i_client_needed_talk_power = 220, - i_client_poke_power = 221, - i_client_needed_poke_power = 222, - b_client_set_flag_talker = 223, - i_client_whisper_power = 224, - i_client_needed_whisper_power = 225, - b_client_modify_description = 226, - b_client_modify_own_description = 227, - b_client_modify_dbproperties = 228, - b_client_delete_dbproperties = 229, - b_client_create_modify_serverquery_login = 230, - b_ft_ignore_password = 231, - b_ft_transfer_list = 232, - i_ft_file_upload_power = 233, - i_ft_needed_file_upload_power = 234, - i_ft_file_download_power = 235, - i_ft_needed_file_download_power = 236, - i_ft_file_delete_power = 237, - i_ft_needed_file_delete_power = 238, - i_ft_file_rename_power = 239, - i_ft_needed_file_rename_power = 240, - i_ft_file_browse_power = 241, - i_ft_needed_file_browse_power = 242, - i_ft_directory_create_power = 243, - i_ft_needed_directory_create_power = 244, - i_ft_quota_mb_download_per_client = 245, - i_ft_quota_mb_upload_per_client = 246, - } - - public static class PerissionInfo - { - public static string Get(PermissionId permid) - { - switch (permid) - { - case PermissionId.undefined: return "Undefined permission"; - case PermissionId.unknown: return "Unknown permission"; - case PermissionId.b_serverinstance_help_view: return "Retrieve information about ServerQuery commands"; - case PermissionId.b_serverinstance_version_view: return "Retrieve global server version (including platform and build number)"; - case PermissionId.b_serverinstance_info_view: return "Retrieve global server information"; - case PermissionId.b_serverinstance_virtualserver_list: return "List virtual servers stored in the database"; - case PermissionId.b_serverinstance_binding_list: return "List active IP bindings on multi-homed machines"; - case PermissionId.b_serverinstance_permission_list: return "List permissions available available on the server instance"; - case PermissionId.b_serverinstance_permission_find: return "Search permission assignments by name or ID"; - case PermissionId.b_virtualserver_create: return "Create virtual servers"; - case PermissionId.b_virtualserver_delete: return "Delete virtual servers"; - case PermissionId.b_virtualserver_start_any: return "Start any virtual server in the server instance"; - case PermissionId.b_virtualserver_stop_any: return "Stop any virtual server in the server instance"; - case PermissionId.b_virtualserver_change_machine_id: return "Change a virtual servers machine ID"; - case PermissionId.b_virtualserver_change_template: return "Edit virtual server default template values"; - case PermissionId.b_serverquery_login: return "Login to ServerQuery"; - case PermissionId.b_serverinstance_textmessage_send: return "Send text messages to all virtual servers at once"; - case PermissionId.b_serverinstance_log_view: return "Retrieve global server log"; - case PermissionId.b_serverinstance_log_add: return "Write to global server log"; - case PermissionId.b_serverinstance_stop: return "Shutdown the server process"; - case PermissionId.b_serverinstance_modify_settings: return "Edit global settings"; - case PermissionId.b_serverinstance_modify_querygroup: return "Edit global ServerQuery groups"; - case PermissionId.b_serverinstance_modify_templates: return "Edit global template groups"; - case PermissionId.b_virtualserver_select: return "Select a virtual server"; - case PermissionId.b_virtualserver_info_view: return "Retrieve virtual server information"; - case PermissionId.b_virtualserver_connectioninfo_view: return "Retrieve virtual server connection information"; - case PermissionId.b_virtualserver_channel_list: return "List channels on a virtual server"; - case PermissionId.b_virtualserver_channel_search: return "Search for channels on a virtual server"; - case PermissionId.b_virtualserver_client_list: return "List clients online on a virtual server"; - case PermissionId.b_virtualserver_client_search: return "Search for clients online on a virtual server"; - case PermissionId.b_virtualserver_client_dblist: return "List client identities known by the virtual server"; - case PermissionId.b_virtualserver_client_dbsearch: return "Search for client identities known by the virtual server"; - case PermissionId.b_virtualserver_client_dbinfo: return "Retrieve client information"; - case PermissionId.b_virtualserver_permission_find: return "Find permissions"; - case PermissionId.b_virtualserver_custom_search: return "Find custom fields"; - case PermissionId.b_virtualserver_start: return "Start own virtual server"; - case PermissionId.b_virtualserver_stop: return "Stop own virtual server"; - case PermissionId.b_virtualserver_token_list: return "List privilege keys available"; - case PermissionId.b_virtualserver_token_add: return "Create new privilege keys"; - case PermissionId.b_virtualserver_token_use: return "Use a privilege keys to gain access to groups"; - case PermissionId.b_virtualserver_token_delete: return "Delete a privilege key"; - case PermissionId.b_virtualserver_log_view: return "Retrieve virtual server log"; - case PermissionId.b_virtualserver_log_add: return "Write to virtual server log"; - case PermissionId.b_virtualserver_join_ignore_password: return "Join virtual server ignoring its password"; - case PermissionId.b_virtualserver_notify_register: return "Register for server notifications"; - case PermissionId.b_virtualserver_notify_unregister: return "Unregister from server notifications"; - case PermissionId.b_virtualserver_snapshot_create: return "Create server snapshots"; - case PermissionId.b_virtualserver_snapshot_deploy: return "Deploy server snapshots"; - case PermissionId.b_virtualserver_permission_reset: return "Reset the server permission settings to default values"; - case PermissionId.b_virtualserver_modify_name: return "Modify server name"; - case PermissionId.b_virtualserver_modify_welcomemessage: return "Modify welcome message"; - case PermissionId.b_virtualserver_modify_maxclients: return "Modify servers max clients"; - case PermissionId.b_virtualserver_modify_reserved_slots: return "Modify reserved slots"; - case PermissionId.b_virtualserver_modify_password: return "Modify server password"; - case PermissionId.b_virtualserver_modify_default_servergroup: return "Modify default Server Group"; - case PermissionId.b_virtualserver_modify_default_channelgroup: return "Modify default Channel Group"; - case PermissionId.b_virtualserver_modify_default_channeladmingroup: return "Modify default Channel Admin Group"; - case PermissionId.b_virtualserver_modify_channel_forced_silence: return "Modify channel force silence value"; - case PermissionId.b_virtualserver_modify_complain: return "Modify individual complain settings"; - case PermissionId.b_virtualserver_modify_antiflood: return "Modify individual antiflood settings"; - case PermissionId.b_virtualserver_modify_ft_settings: return "Modify file transfer settings"; - case PermissionId.b_virtualserver_modify_ft_quotas: return "Modify file transfer quotas"; - case PermissionId.b_virtualserver_modify_hostmessage: return "Modify individual hostmessage settings"; - case PermissionId.b_virtualserver_modify_hostbanner: return "Modify individual hostbanner settings"; - case PermissionId.b_virtualserver_modify_hostbutton: return "Modify individual hostbutton settings"; - case PermissionId.b_virtualserver_modify_port: return "Modify server port"; - case PermissionId.b_virtualserver_modify_autostart: return "Modify server autostart"; - case PermissionId.b_virtualserver_modify_needed_identity_security_level: return "Modify required identity security level"; - case PermissionId.b_virtualserver_modify_priority_speaker_dimm_modificator: return "Modify priority speaker dimm modificator"; - case PermissionId.b_virtualserver_modify_log_settings: return "Modify log settings"; - case PermissionId.b_virtualserver_modify_min_client_version: return "Modify min client version"; - case PermissionId.b_virtualserver_modify_icon_id: return "Modify server icon"; - case PermissionId.b_virtualserver_modify_weblist: return "Modify web server list reporting settings"; - case PermissionId.b_virtualserver_modify_codec_encryption_mode: return "Modify codec encryption mode"; - case PermissionId.b_virtualserver_modify_temporary_passwords: return "Modify temporary serverpasswords"; - case PermissionId.b_virtualserver_modify_temporary_passwords_own: return "Modify own temporary serverpasswords"; - case PermissionId.b_virtualserver_modify_channel_temp_delete_delay_default: return "Modify default temporary channel delete delay"; - case PermissionId.i_channel_min_depth: return "Min channel creation depth in hierarchy"; - case PermissionId.i_channel_max_depth: return "Max channel creation depth in hierarchy"; - case PermissionId.b_channel_group_inheritance_end: return "Stop inheritance of channel group permissions"; - case PermissionId.i_channel_permission_modify_power: return "Modify channel permission power"; - case PermissionId.i_channel_needed_permission_modify_power: return "Needed modify channel permission power"; - case PermissionId.b_channel_info_view: return "Retrieve channel information"; - case PermissionId.b_channel_create_child: return "Create sub-channels"; - case PermissionId.b_channel_create_permanent: return "Create permanent channels"; - case PermissionId.b_channel_create_semi_permanent: return "Create semi-permanent channels"; - case PermissionId.b_channel_create_temporary: return "Create temporary channels"; - case PermissionId.b_channel_create_private: return "Create private channel"; - case PermissionId.b_channel_create_with_topic: return "Create channels with a topic"; - case PermissionId.b_channel_create_with_description: return "Create channels with a description"; - case PermissionId.b_channel_create_with_password: return "Create password protected channels"; - case PermissionId.b_channel_create_modify_with_codec_speex8: return "Create channels using Speex Narrowband (8 kHz) codecs"; - case PermissionId.b_channel_create_modify_with_codec_speex16: return "Create channels using Speex Wideband (16 kHz) codecs"; - case PermissionId.b_channel_create_modify_with_codec_speex32: return "Create channels using Speex Ultra-Wideband (32 kHz) codecs"; - case PermissionId.b_channel_create_modify_with_codec_celtmono48: return "Create channels using the CELT Mono (48 kHz) codec "; - case PermissionId.b_channel_create_modify_with_codec_opusvoice: return "Create channels using OPUS (voice) codec"; - case PermissionId.b_channel_create_modify_with_codec_opusmusic: return "Create channels using OPUS (music) codec"; - case PermissionId.i_channel_create_modify_with_codec_maxquality: return "Create channels with custom codec quality"; - case PermissionId.i_channel_create_modify_with_codec_latency_factor_min: return "Create channels with minimal custom codec latency factor"; - case PermissionId.b_channel_create_with_maxclients: return "Create channels with custom max clients"; - case PermissionId.b_channel_create_with_maxfamilyclients: return "Create channels with custom max family clients"; - case PermissionId.b_channel_create_with_sortorder: return "Create channels with custom sort order"; - case PermissionId.b_channel_create_with_default: return "Create default channels"; - case PermissionId.b_channel_create_with_needed_talk_power: return "Create channels with needed talk power"; - case PermissionId.b_channel_create_modify_with_force_password: return "Create new channels only with password"; - case PermissionId.i_channel_create_modify_with_temp_delete_delay: return "Max delete delay for temporary channels"; - case PermissionId.b_channel_modify_parent: return "Move channels"; - case PermissionId.b_channel_modify_make_default: return "Make channel default"; - case PermissionId.b_channel_modify_make_permanent: return "Make channel permanent"; - case PermissionId.b_channel_modify_make_semi_permanent: return "Make channel semi-permanent"; - case PermissionId.b_channel_modify_make_temporary: return "Make channel temporary"; - case PermissionId.b_channel_modify_name: return "Modify channel name"; - case PermissionId.b_channel_modify_topic: return "Modify channel topic"; - case PermissionId.b_channel_modify_description: return "Modify channel description"; - case PermissionId.b_channel_modify_password: return "Modify channel password"; - case PermissionId.b_channel_modify_codec: return "Modify channel codec"; - case PermissionId.b_channel_modify_codec_quality: return "Modify channel codec quality"; - case PermissionId.b_channel_modify_codec_latency_factor: return "Modify channel codec latency factor"; - case PermissionId.b_channel_modify_maxclients: return "Modify channels max clients"; - case PermissionId.b_channel_modify_maxfamilyclients: return "Modify channels max family clients"; - case PermissionId.b_channel_modify_sortorder: return "Modify channel sort order"; - case PermissionId.b_channel_modify_needed_talk_power: return "Change needed channel talk power"; - case PermissionId.i_channel_modify_power: return "Channel modify power"; - case PermissionId.i_channel_needed_modify_power: return "Needed channel modify power"; - case PermissionId.b_channel_modify_make_codec_encrypted: return "Make channel codec encrypted"; - case PermissionId.b_channel_modify_temp_delete_delay: return "Modify temporary channel delete delay"; - case PermissionId.b_channel_delete_permanent: return "Delete permanent channels"; - case PermissionId.b_channel_delete_semi_permanent: return "Delete semi-permanent channels"; - case PermissionId.b_channel_delete_temporary: return "Delete temporary channels"; - case PermissionId.b_channel_delete_flag_force: return "Force channel delete"; - case PermissionId.i_channel_delete_power: return "Delete channel power"; - case PermissionId.i_channel_needed_delete_power: return "Needed delete channel power"; - case PermissionId.b_channel_join_permanent: return "Join permanent channels"; - case PermissionId.b_channel_join_semi_permanent: return "Join semi-permanent channels"; - case PermissionId.b_channel_join_temporary: return "Join temporary channels"; - case PermissionId.b_channel_join_ignore_password: return "Join channel ignoring its password"; - case PermissionId.b_channel_join_ignore_maxclients: return "Ignore channels max clients limit"; - case PermissionId.i_channel_join_power: return "Channel join power"; - case PermissionId.i_channel_needed_join_power: return "Needed channel join power"; - case PermissionId.i_channel_subscribe_power: return "Channel subscribe power"; - case PermissionId.i_channel_needed_subscribe_power: return "Needed channel subscribe power"; - case PermissionId.i_channel_description_view_power: return "Channel description view power"; - case PermissionId.i_channel_needed_description_view_power: return "Needed channel needed description view power"; - case PermissionId.i_icon_id: return "Group icon identifier"; - case PermissionId.i_max_icon_filesize: return "Max icon filesize in bytes"; - case PermissionId.b_icon_manage: return "Enables icon management"; - case PermissionId.b_group_is_permanent: return "Group is permanent"; - case PermissionId.i_group_auto_update_type: return "Group auto-update type"; - case PermissionId.i_group_auto_update_max_value: return "Group auto-update max value"; - case PermissionId.i_group_sort_id: return "Group sort id"; - case PermissionId.i_group_show_name_in_tree: return "Show group name in tree depending on selected mode"; - case PermissionId.b_virtualserver_servergroup_list: return "List server groups"; - case PermissionId.b_virtualserver_servergroup_permission_list: return "List server group permissions"; - case PermissionId.b_virtualserver_servergroup_client_list: return "List clients from a server group"; - case PermissionId.b_virtualserver_channelgroup_list: return "List channel groups "; - case PermissionId.b_virtualserver_channelgroup_permission_list: return "List channel group permissions"; - case PermissionId.b_virtualserver_channelgroup_client_list: return "List clients from a channel group"; - case PermissionId.b_virtualserver_client_permission_list: return "List client permissions"; - case PermissionId.b_virtualserver_channel_permission_list: return "List channel permissions"; - case PermissionId.b_virtualserver_channelclient_permission_list: return "List channel client permissions"; - case PermissionId.b_virtualserver_servergroup_create: return "Create server groups"; - case PermissionId.b_virtualserver_channelgroup_create: return "Create channel groups"; - case PermissionId.i_group_modify_power: return "Group modify power"; - case PermissionId.i_group_needed_modify_power: return "Needed group modify power"; - case PermissionId.i_group_member_add_power: return "Group member add power"; - case PermissionId.i_group_needed_member_add_power: return "Needed group member add power"; - case PermissionId.i_group_member_remove_power: return "Group member delete power"; - case PermissionId.i_group_needed_member_remove_power: return "Needed group member delete power"; - case PermissionId.i_permission_modify_power: return "Permission modify power"; - case PermissionId.b_permission_modify_power_ignore: return "Ignore needed permission modify power"; - case PermissionId.b_virtualserver_servergroup_delete: return "Delete server groups"; - case PermissionId.b_virtualserver_channelgroup_delete: return "Delete channel groups"; - case PermissionId.i_client_permission_modify_power: return "Client permission modify power"; - case PermissionId.i_client_needed_permission_modify_power: return "Needed client permission modify power"; - case PermissionId.i_client_max_clones_uid: return "Max additional connections per client identity"; - case PermissionId.i_client_max_idletime: return "Max idle time in seconds"; - case PermissionId.i_client_max_avatar_filesize: return "Max avatar filesize in bytes"; - case PermissionId.i_client_max_channel_subscriptions: return "Max channel subscriptions"; - case PermissionId.b_client_is_priority_speaker: return "Client is priority speaker"; - case PermissionId.b_client_skip_channelgroup_permissions: return "Ignore channel group permissions"; - case PermissionId.b_client_force_push_to_talk: return "Force Push-To-Talk capture mode"; - case PermissionId.b_client_ignore_bans: return "Ignore bans"; - case PermissionId.b_client_ignore_antiflood: return "Ignore antiflood measurements"; - case PermissionId.b_client_issue_client_query_command: return "Issue query commands from client"; - case PermissionId.b_client_use_reserved_slot: return "Use an reserved slot"; - case PermissionId.b_client_use_channel_commander: return "Use channel commander"; - case PermissionId.b_client_request_talker: return "Allow to request talk power"; - case PermissionId.b_client_avatar_delete_other: return "Allow deletion of avatars from other clients"; - case PermissionId.b_client_is_sticky: return "Client will be sticked to current channel"; - case PermissionId.b_client_ignore_sticky: return "Client ignores sticky flag"; - case PermissionId.b_client_info_view: return "Retrieve client information"; - case PermissionId.b_client_permissionoverview_view: return "Retrieve client permissions overview"; - case PermissionId.b_client_permissionoverview_own: return "Retrieve clients own permissions overview"; - case PermissionId.b_client_remoteaddress_view: return "View client IP address and port"; - case PermissionId.i_client_serverquery_view_power: return "ServerQuery view power"; - case PermissionId.i_client_needed_serverquery_view_power: return "Needed ServerQuery view power"; - case PermissionId.b_client_custom_info_view: return "View custom fields"; - case PermissionId.i_client_kick_from_server_power: return "Client kick power from server"; - case PermissionId.i_client_needed_kick_from_server_power: return "Needed client kick power from server"; - case PermissionId.i_client_kick_from_channel_power: return "Client kick power from channel"; - case PermissionId.i_client_needed_kick_from_channel_power: return "Needed client kick power from channel"; - case PermissionId.i_client_ban_power: return "Client ban power"; - case PermissionId.i_client_needed_ban_power: return "Needed client ban power"; - case PermissionId.i_client_move_power: return "Client move power"; - case PermissionId.i_client_needed_move_power: return "Needed client move power"; - case PermissionId.i_client_complain_power: return "Complain power"; - case PermissionId.i_client_needed_complain_power: return "Needed complain power"; - case PermissionId.b_client_complain_list: return "Show complain list"; - case PermissionId.b_client_complain_delete_own: return "Delete own complains"; - case PermissionId.b_client_complain_delete: return "Delete complains"; - case PermissionId.b_client_ban_list: return "Show banlist"; - case PermissionId.b_client_ban_create: return "Add a ban"; - case PermissionId.b_client_ban_delete_own: return "Delete own bans"; - case PermissionId.b_client_ban_delete: return "Delete bans"; - case PermissionId.i_client_ban_max_bantime: return "Max bantime"; - case PermissionId.i_client_private_textmessage_power: return "Client private message power"; - case PermissionId.i_client_needed_private_textmessage_power: return "Needed client private message power"; - case PermissionId.b_client_server_textmessage_send: return "Send text messages to virtual server"; - case PermissionId.b_client_channel_textmessage_send: return "Send text messages to channel"; - case PermissionId.b_client_offline_textmessage_send: return "Send offline messages to clients"; - case PermissionId.i_client_talk_power: return "Client talk power"; - case PermissionId.i_client_needed_talk_power: return "Needed client talk power"; - case PermissionId.i_client_poke_power: return "Client poke power"; - case PermissionId.i_client_needed_poke_power: return "Needed client poke power"; - case PermissionId.b_client_set_flag_talker: return "Set the talker flag for clients and allow them to speak"; - case PermissionId.i_client_whisper_power: return "Client whisper power"; - case PermissionId.i_client_needed_whisper_power: return "Client needed whisper power"; - case PermissionId.b_client_modify_description: return "Edit a clients description"; - case PermissionId.b_client_modify_own_description: return "Allow client to edit own description"; - case PermissionId.b_client_modify_dbproperties: return "Edit a clients properties in the database"; - case PermissionId.b_client_delete_dbproperties: return "Delete a clients properties in the database"; - case PermissionId.b_client_create_modify_serverquery_login: return "Create or modify own ServerQuery account"; - case PermissionId.b_ft_ignore_password: return "Browse files without channel password"; - case PermissionId.b_ft_transfer_list: return "Retrieve list of running filetransfers"; - case PermissionId.i_ft_file_upload_power: return "File upload power"; - case PermissionId.i_ft_needed_file_upload_power: return "Needed file upload power"; - case PermissionId.i_ft_file_download_power: return "File download power"; - case PermissionId.i_ft_needed_file_download_power: return "Needed file download power"; - case PermissionId.i_ft_file_delete_power: return "File delete power"; - case PermissionId.i_ft_needed_file_delete_power: return "Needed file delete power"; - case PermissionId.i_ft_file_rename_power: return "File rename power"; - case PermissionId.i_ft_needed_file_rename_power: return "Needed file rename power"; - case PermissionId.i_ft_file_browse_power: return "File browse power"; - case PermissionId.i_ft_needed_file_browse_power: return "Needed file browse power"; - case PermissionId.i_ft_directory_create_power: return "Create directory power"; - case PermissionId.i_ft_needed_directory_create_power: return "Needed create directory power"; - case PermissionId.i_ft_quota_mb_download_per_client: return "Download quota per client in MByte"; - case PermissionId.i_ft_quota_mb_upload_per_client: return "Upload quota per client in MByte"; - default: throw Util.UnhandledDefault(permid); - } - } - } } diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index d8556c85..33da26d9 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -12,6 +12,7 @@ v4.6 512 7 + true @@ -26,7 +27,6 @@ true Auto - 7 pdbonly @@ -74,6 +74,11 @@ + + True + True + Permissions.tt + @@ -139,6 +144,10 @@ TextTemplatingFileGenerator Messages.cs + + TextTemplatingFileGenerator + Permissions.cs + From 3e4bc0efd0c597482f5ee73e66db62bec94c7d20 Mon Sep 17 00:00:00 2001 From: Splamy Date: Fri, 5 Jan 2018 01:02:52 +0100 Subject: [PATCH 27/48] Added template out files back + Added errors generation + Added version generation --- .gitignore | 2 - TS3AudioBot/Ts3Full.cs | 2 +- TS3Client/Declarations | 2 +- TS3Client/Full/VersionSign.cs | 88 -- TS3Client/Generated/Errors.cs | 417 +++++++ TS3Client/Generated/Errors.tt | 40 + TS3Client/Generated/Messages.cs | 1749 ++++++++++++++++++++++++++++ TS3Client/Generated/Messages.tt | 4 +- TS3Client/Generated/Permissions.cs | 534 +++++++++ TS3Client/Generated/Permissions.tt | 11 +- TS3Client/Generated/Versions.cs | 81 ++ TS3Client/Generated/Versions.tt | 99 ++ TS3Client/OwnEnums.cs | 402 ------- TS3Client/TS3Client.csproj | 24 +- 14 files changed, 2955 insertions(+), 500 deletions(-) delete mode 100644 TS3Client/Full/VersionSign.cs create mode 100644 TS3Client/Generated/Errors.cs create mode 100644 TS3Client/Generated/Errors.tt create mode 100644 TS3Client/Generated/Messages.cs create mode 100644 TS3Client/Generated/Permissions.cs create mode 100644 TS3Client/Generated/Versions.cs create mode 100644 TS3Client/Generated/Versions.tt diff --git a/.gitignore b/.gitignore index a5b97763..8b7c6ad2 100644 --- a/.gitignore +++ b/.gitignore @@ -41,8 +41,6 @@ latex/ # Keep precompiled libs !TS3AudioBot/lib/**/* -# Ignore auto-generated files -TS3Client/Generated/*.cs # Roslyn cache directories *.ide/ diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index 296e600f..8063b239 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -162,7 +162,7 @@ private void ConnectClient() if (verionSign == null) { Log.Write(Log.Level.Warning, "Invalid version sign, falling back to unknown :P"); - verionSign = VersionSign.VER_WIN_3_UNKNOWN; + verionSign = VersionSign.VER_WIN_3_X_X; } } else if (Util.IsLinux) diff --git a/TS3Client/Declarations b/TS3Client/Declarations index bb104177..60423f77 160000 --- a/TS3Client/Declarations +++ b/TS3Client/Declarations @@ -1 +1 @@ -Subproject commit bb104177b9cb88fd9a9e1966dbfc5cfd02e918eb +Subproject commit 60423f776b13915f5b6036ee10c0d18653636d05 diff --git a/TS3Client/Full/VersionSign.cs b/TS3Client/Full/VersionSign.cs deleted file mode 100644 index f22bd3e3..00000000 --- a/TS3Client/Full/VersionSign.cs +++ /dev/null @@ -1,88 +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.Full -{ - using System; - - /// - /// Describes a triple of version, plattform and a crytographical signature (usually distributed by "TeamSpeak Systems"). - /// Each triple has to match and is not interchangeable with other triple parts. - /// - public class VersionSign - { - private static readonly string[] Plattforms = { null, "Windows", "Linux", "OS X", "Android", "iOS" }; - - public string Sign { get; } - public string Name { get; } - public ClientPlattform Plattform { get; } - public string PlattformName { get; } - - public VersionSign(string name, ClientPlattform plattform, string sign) - { - if (plattform == ClientPlattform.Other) - throw new ArgumentException(nameof(plattform)); - Name = name; - Sign = sign; - Plattform = plattform; - PlattformName = Plattforms[(int)plattform]; - } - - public VersionSign(string name, string plattform, string sign) - { - Name = name; - Sign = sign; - Plattform = ClientPlattform.Other; - PlattformName = plattform; - } - - // Many ids implemented from here: https://r4p3.net/threads/client-builds.499/ - - public static readonly VersionSign VER_WIN_3_0_11 - = new VersionSign("3.0.11 [Build: 1375083581]", ClientPlattform.Windows, "54wPDkfv0kT56UE0lv/LFkFJObH+Q4Irmo4Brfz1EcvjVhj8hJ+RCHcVTZsdKU2XvVvh+VLJpURulEHsAOsyBw=="); - public static readonly VersionSign VER_WIN_3_0_19_3 - = new VersionSign("3.0.19.3 [Build: 1466672534]", ClientPlattform.Windows, "a1OYzvM18mrmfUQBUgxYBxYz2DUU6y5k3/mEL6FurzU0y97Bd1FL7+PRpcHyPkg4R+kKAFZ1nhyzbgkGphDWDg=="); - public static readonly VersionSign VER_WIN_3_0_19_4 - = new VersionSign("3.0.19.4 [Build: 1468491418]", ClientPlattform.Windows, "ldWL49uDKC3N9uxdgWRMTOzUuiG1nBqUiOa+Nal5HvdxJiN4fsTnmmPo5tvglN7WqoVoFfuuKuYq1LzodtEtCg=="); - public static readonly VersionSign VER_LIN_3_0_19_4 - = new VersionSign("3.0.19.4 [Build: 1468491418]", ClientPlattform.Linux, "jvhhk75EV3nCGeewx4Y5zZmiZSN07q5ByKZ9Wlmg85aAbnw7c1jKq5/Iq0zY6dfGwCEwuKod0I5lQcVLf2NTCg=="); - public static readonly VersionSign VER_OSX_3_0_19_4 - = new VersionSign("3.0.19.4 [Build: 1468491418]", ClientPlattform.Osx, "Pvcizdk3HRQMzTLt7goUYBmmS5nbAS1g2E6HIypLU+9eXTqGTBLim0UUtKc0s867TFHbK91GroDrTtv0aMUGAw=="); - public static readonly VersionSign VER_WIN_3_0_20 - = new VersionSign("3.0.20 [Build: 1465542546]", ClientPlattform.Windows, "vDK31sOwOvDpTXgqAJzmR1NzeUeSDG9dLMgIz5LCX+KpDSVD/qU60mzScz9tuc9AsLyrL8DxHpDDO3eQD+hYCA=="); - public static readonly VersionSign VER_AND_3_0_23 - = new VersionSign("3.0.23 [Build: 1463662487]", ClientPlattform.Android, "RN+cwFI+jSHJEhggucIuUyEteWNVFy4iw0QDp3qn2UzfopypFVE9BPZqJjBUGeoCN7Q/SfYL4RNIRzJEQaZUCA=="); - public static readonly VersionSign VER_WIN_3_1 - = new VersionSign("3.1 [Build: 1471417187]", ClientPlattform.Windows, "Vr9F7kbVorcrkV5b/Iw+feH9qmDGvfsW8tpa737zhc1fDpK5uaEo6M5l2DzgaGqqOr3GKl5A7PF9Sj6eTM26Aw=="); - public static readonly VersionSign VER_WIN_3_1_6 - = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Windows, "73fB82Jt1lmIRHKBFaE8h1JKPGFbnt6/yrXOHwTS93Oo7Adx1usY5TzNg+8BKy9nmmA2FEBnRmz5cRfXDghnBA=="); - public static readonly VersionSign VER_LIN_3_1_6 - = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Linux, "o+l92HKfiUF+THx2rBsuNjj/S1QpxG1fd5o3Q7qtWxkviR3LI3JeWyc26eTmoQoMTgI3jjHV7dCwHsK1BVu6Aw=="); - public static readonly VersionSign VER_WIN_3_1_7_ALPHA - = new VersionSign("3.1.7 [Build: 1507896705]", ClientPlattform.Windows, "Iks42KIMcmFv5vzPLhziqahcPD2AHygkepr8xHNCbqx+li5n7Htbq5LE9e1YYhRhLoS4e2HqOpKkt+/+LC8EDA=="); - public static readonly VersionSign VER_OSX_3_1_7_ALPHA - = new VersionSign("3.1.7 [Build: 1507896705]", ClientPlattform.Osx, "iM0IyUpaH9ak0gTtrHlRT0VGZa4rC51iZwSFwifK6iFqciSba/WkIQDWk9GUJN0OCCfatoc/fmlq8TPBnE5XCA=="); - public static readonly VersionSign VER_WIN_3_UNKNOWN - = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Windows, "DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="); - public static readonly VersionSign VER_AND_3_UNKNOWN - = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Android, "AWb948BY32Z7bpIyoAlQguSmxOGcmjESPceQe1DpW5IZ4+AW1KfTk2VUIYNfUPsxReDJMCtlhVKslzhR2lf0AA=="); - public static readonly VersionSign VER_IOS_3_UNKNOWN - = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Ios, "XrAf+Buq6Eb0ehEW/niFp06YX+nGGOS0Ke4MoUBzn+cX9q6G5C0A/d5XtgcNMe8r9jJgV/adIYVpsGS3pVlSAA=="); - } - - public enum ClientPlattform - { - Other = 0, - Windows, - Linux, - Osx, - Android, - Ios, - } -} diff --git a/TS3Client/Generated/Errors.cs b/TS3Client/Generated/Errors.cs new file mode 100644 index 00000000..469fb473 --- /dev/null +++ b/TS3Client/Generated/Errors.cs @@ -0,0 +1,417 @@ +// 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 +{ + // Source: http://forum.teamspeak.com/threads/102276-Server-query-error-id-list + public enum Ts3ErrorCode : ushort + { + // ReSharper disable InconsistentNaming, UnusedMember.Global + /// unknown error code + ok = 0x0000, + /// undefined error + undefined = 0x0001, + /// not implemented + not_implemented = 0x0002, + /// + ok_no_update = 0x0003, + /// + dont_notify = 0x0004, + /// library time limit reached + lib_time_limit_reached = 0x0005, + /// command not found + command_not_found = 0x0100, + /// unable to bind network port + unable_to_bind_network_port = 0x0101, + /// no network port available + no_network_port_available = 0x0102, + /// invalid clientID + client_invalid_id = 0x0200, + /// nickname is already in use + client_nickname_inuse = 0x0201, + /// invalid error code + client_invalid_error_code = 0x0202, + /// max clients protocol limit reached + client_protocol_limit_reached = 0x0203, + /// invalid client type + client_invalid_type = 0x0204, + /// already subscribed + client_already_subscribed = 0x0205, + /// not logged in + client_not_logged_in = 0x0206, + /// could not validate client identity + client_could_not_validate_identity = 0x0207, + /// invalid loginname or password + client_invalid_password = 0x0208, + /// too many clones already connected + client_too_many_clones_connected = 0x0209, + /// client version outdated, please update + client_version_outdated = 0x020a, + /// client is online + client_is_online = 0x020b, + /// client is flooding + client_is_flooding = 0x020c, + /// client is modified + client_hacked = 0x020d, + /// can not verify client at this moment + client_cannot_verify_now = 0x020e, + /// client is not permitted to log in + client_login_not_permitted = 0x020f, + /// client is not subscribed to the channel + client_not_subscribed = 0x0210, + /// invalid channelID + channel_invalid_id = 0x0300, + /// max channels protocol limit reached + channel_protocol_limit_reached = 0x0301, + /// already member of channel + channel_already_in = 0x0302, + /// channel name is already in use + channel_name_inuse = 0x0303, + /// channel not empty + channel_not_empty = 0x0304, + /// can not delete default channel + channel_can_not_delete_default = 0x0305, + /// default channel requires permanent + channel_default_require_permanent = 0x0306, + /// invalid channel flags + channel_invalid_flags = 0x0307, + /// permanent channel can not be child of non permanent channel + channel_parent_not_permanent = 0x0308, + /// channel maxclient reached + channel_maxclients_reached = 0x0309, + /// channel maxfamily reached + channel_maxfamily_reached = 0x030a, + /// invalid channel order + channel_invalid_order = 0x030b, + /// channel does not support filetransfers + channel_no_filetransfer_supported = 0x030c, + /// invalid channel password + channel_invalid_password = 0x030d, + /// channel is private channel + channel_is_private_channel = 0x030e, + /// invalid security hash supplied by client + channel_invalid_security_hash = 0x030f, + /// invalid serverID + server_invalid_id = 0x0400, + /// server is running + server_running = 0x0401, + /// server is shutting down + server_is_shutting_down = 0x0402, + /// server maxclient reached + server_maxclients_reached = 0x0403, + /// invalid server password + server_invalid_password = 0x0404, + /// deployment active + server_deployment_active = 0x0405, + /// unable to stop own server in your connection class + server_unable_to_stop_own_server = 0x0406, + /// server is virtual + server_is_virtual = 0x0407, + /// server wrong machineID + server_wrong_machineid = 0x0408, + /// server is not running + server_is_not_running = 0x0409, + /// server is booting up + server_is_booting = 0x040a, + /// server got an invalid status for this operation + server_status_invalid = 0x040b, + /// server modal quit + server_modal_quit = 0x040c, + /// server version is too old for command + server_version_outdated = 0x040d, + /// database error + database = 0x0500, + /// database empty result set + database_empty_result = 0x0501, + /// database duplicate entry + database_duplicate_entry = 0x0502, + /// database no modifications + database_no_modifications = 0x0503, + /// database invalid constraint + database_constraint = 0x0504, + /// database reinvoke command + database_reinvoke = 0x0505, + /// invalid quote + parameter_quote = 0x0600, + /// invalid parameter count + parameter_invalid_count = 0x0601, + /// invalid parameter + parameter_invalid = 0x0602, + /// parameter not found + parameter_not_found = 0x0603, + /// convert error + parameter_convert = 0x0604, + /// invalid parameter size + parameter_invalid_size = 0x0605, + /// missing required parameter + parameter_missing = 0x0606, + /// invalid checksum + parameter_checksum = 0x0607, + /// virtual server got a critical error + vs_critical = 0x0700, + /// Connection lost + connection_lost = 0x0701, + /// not connected + not_connected = 0x0702, + /// no cached connection info + no_cached_connection_info = 0x0703, + /// currently not possible + currently_not_possible = 0x0704, + /// failed connection initialization + failed_connection_initialisation = 0x0705, + /// could not resolve hostname + could_not_resolve_hostname = 0x0706, + /// invalid server connection handler ID + invalid_server_connection_handler_id = 0x0707, + /// could not initialize Input Manager + could_not_initialise_input_manager = 0x0708, + /// client library not initialized + clientlibrary_not_initialised = 0x0709, + /// server library not initialized + serverlibrary_not_initialised = 0x070a, + /// too many whisper targets + whisper_too_many_targets = 0x070b, + /// no whisper targets found + whisper_no_targets = 0x070c, + /// invalid file name + file_invalid_name = 0x0800, + /// invalid file permissions + file_invalid_permissions = 0x0801, + /// file already exists + file_already_exists = 0x0802, + /// file not found + file_not_found = 0x0803, + /// file input/output error + file_io_error = 0x0804, + /// invalid file transfer ID + file_invalid_transfer_id = 0x0805, + /// invalid file path + file_invalid_path = 0x0806, + /// no files available + file_no_files_available = 0x0807, + /// overwrite excludes resume + file_overwrite_excludes_resume = 0x0808, + /// invalid file size + file_invalid_size = 0x0809, + /// file already in use + file_already_in_use = 0x080a, + /// could not open file transfer connection + file_could_not_open_connection = 0x080b, + /// no space left on device (disk full?) + file_no_space_left_on_device = 0x080c, + /// file exceeds file system's maximum file size + file_exceeds_file_system_maximum_size = 0x080d, + /// file transfer connection timeout + file_transfer_connection_timeout = 0x080e, + /// lost file transfer connection + file_connection_lost = 0x080f, + /// file exceeds supplied file size + file_exceeds_supplied_size = 0x0810, + /// file transfer complete + file_transfer_complete = 0x0811, + /// file transfer canceled + file_transfer_canceled = 0x0812, + /// file transfer interrupted + file_transfer_interrupted = 0x0813, + /// file transfer server quota exceeded + file_transfer_server_quota_exceeded = 0x0814, + /// file transfer client quota exceeded + file_transfer_client_quota_exceeded = 0x0815, + /// file transfer reset + file_transfer_reset = 0x0816, + /// file transfer limit reached + file_transfer_limit_reached = 0x0817, + /// preprocessor disabled + sound_preprocessor_disabled = 0x0900, + /// internal preprocessor + sound_internal_preprocessor = 0x0901, + /// internal encoder + sound_internal_encoder = 0x0902, + /// internal playback + sound_internal_playback = 0x0903, + /// no capture device available + sound_no_capture_device_available = 0x0904, + /// no playback device available + sound_no_playback_device_available = 0x0905, + /// could not open capture device + sound_could_not_open_capture_device = 0x0906, + /// could not open playback device + sound_could_not_open_playback_device = 0x0907, + /// ServerConnectionHandler has a device registered + sound_handler_has_device = 0x0908, + /// invalid capture device + sound_invalid_capture_device = 0x0909, + /// invalid clayback device + sound_invalid_playback_device = 0x090a, + /// invalid wave file + sound_invalid_wave = 0x090b, + /// wave file type not supported + sound_unsupported_wave = 0x090c, + /// could not open wave file + sound_open_wave = 0x090d, + /// internal capture + sound_internal_capture = 0x090e, + /// device still in use + sound_device_in_use = 0x090f, + /// device already registerred + sound_device_already_registerred = 0x0910, + /// device not registered/known + sound_unknown_device = 0x0911, + /// unsupported frequency + sound_unsupported_frequency = 0x0912, + /// invalid channel count + sound_invalid_channel_count = 0x0913, + /// read error in wave + sound_read_wave = 0x0914, + /// sound need more data + sound_need_more_data = 0x0915, + /// sound device was busy + sound_device_busy = 0x0916, + /// there is no sound data for this period + sound_no_data = 0x0917, + /// Channelmask set bits count (speakers) is not the same as (count) + sound_channel_mask_mismatch = 0x0918, + /// invalid group ID + permission_invalid_group_id = 0x0a00, + /// duplicate entry + permission_duplicate_entry = 0x0a01, + /// invalid permission ID + permission_invalid_perm_id = 0x0a02, + /// empty result set + permission_empty_result = 0x0a03, + /// access to default group is forbidden + permission_default_group_forbidden = 0x0a04, + /// invalid size + permission_invalid_size = 0x0a05, + /// invalid value + permission_invalid_value = 0x0a06, + /// group is not empty + permissions_group_not_empty = 0x0a07, + /// insufficient client permissions + permissions_client_insufficient = 0x0a08, + /// insufficient group modify power + permissions_insufficient_group_power = 0x0a09, + /// insufficient permission modify power + permissions_insufficient_permission_power = 0x0a0a, + /// template group is currently used + permission_template_group_is_used = 0x0a0b, + /// permission error + permissions = 0x0a0c, + /// virtualserver limit reached + accounting_virtualserver_limit_reached = 0x0b00, + /// max slot limit reached + accounting_slot_limit_reached = 0x0b01, + /// license file not found + accounting_license_file_not_found = 0x0b02, + /// license date not ok + accounting_license_date_not_ok = 0x0b03, + /// unable to connect to accounting server + accounting_unable_to_connect_to_server = 0x0b04, + /// unknown accounting error + accounting_unknown_error = 0x0b05, + /// accounting server error + accounting_server_error = 0x0b06, + /// instance limit reached + accounting_instance_limit_reached = 0x0b07, + /// instance check error + accounting_instance_check_error = 0x0b08, + /// license file invalid + accounting_license_file_invalid = 0x0b09, + /// virtualserver is running elsewhere + accounting_running_elsewhere = 0x0b0a, + /// virtualserver running in same instance already + accounting_instance_duplicated = 0x0b0b, + /// virtualserver already started + accounting_already_started = 0x0b0c, + /// virtualserver not started + accounting_not_started = 0x0b0d, + /// + accounting_to_many_starts = 0x0b0e, + /// invalid message id + message_invalid_id = 0x0c00, + /// invalid ban id + ban_invalid_id = 0x0d00, + /// connection failed, you are banned + connect_failed_banned = 0x0d01, + /// rename failed, new name is banned + rename_failed_banned = 0x0d02, + /// flood ban + ban_flooding = 0x0d03, + /// unable to initialize tts + tts_unable_to_initialize = 0x0e00, + /// invalid privilege key + privilege_key_invalid = 0x0f00, + /// + voip_pjsua = 0x1000, + /// + voip_already_initialized = 0x1001, + /// + voip_too_many_accounts = 0x1002, + /// + voip_invalid_account = 0x1003, + /// + voip_internal_error = 0x1004, + /// + voip_invalid_connectionId = 0x1005, + /// + voip_cannot_answer_initiated_call = 0x1006, + /// + voip_not_initialized = 0x1007, + /// invalid password + provisioning_invalid_password = 0x1100, + /// invalid request + provisioning_invalid_request = 0x1101, + /// no(more) slots available + provisioning_no_slots_available = 0x1102, + /// pool missing + provisioning_pool_missing = 0x1103, + /// pool unknown + provisioning_pool_unknown = 0x1104, + /// unknown ip location(perhaps LAN ip?) + provisioning_unknown_ip_location = 0x1105, + /// internal error(tried exceeded) + provisioning_internal_tries_exceeded = 0x1106, + /// too many slots requested + provisioning_too_many_slots_requested = 0x1107, + /// too many reserved + provisioning_too_many_reserved = 0x1108, + /// could not connect to provisioning server + provisioning_could_not_connect = 0x1109, + /// authentication server not connected + provisioning_auth_server_not_connected = 0x1110, + /// authentication data too large + provisioning_auth_data_too_large = 0x1111, + /// already initialized + provisioning_already_initialized = 0x1112, + /// not initialized + provisioning_not_initialized = 0x1113, + /// already connecting + provisioning_connecting = 0x1114, + /// already connected + provisioning_already_connected = 0x1115, + /// + provisioning_not_connected = 0x1116, + /// io_error + provisioning_io_error = 0x1117, + /// + provisioning_invalid_timeout = 0x1118, + /// + provisioning_ts3server_not_found = 0x1119, + /// unknown permissionID + provisioning_no_permission = 0x111A, + /// For own custom errors + custom_error = 0xFFFF, + // ReSharper enable InconsistentNaming, UnusedMember.Global + } +} \ No newline at end of file diff --git a/TS3Client/Generated/Errors.tt b/TS3Client/Generated/Errors.tt new file mode 100644 index 00000000..375e38e6 --- /dev/null +++ b/TS3Client/Generated/Errors.tt @@ -0,0 +1,40 @@ +// 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="false" hostspecific="true" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ assembly name="Microsoft.VisualBasic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Microsoft.VisualBasic.FileIO" #> +<#@ output extension=".cs" #> +<# +string declFilePath = Host.ResolvePath("../Declarations/Errors.csv"); +var data = new List(); +using (TextFieldParser parser = new TextFieldParser(declFilePath)) +{ + parser.TextFieldType = FieldType.Delimited; + parser.SetDelimiters(","); + while (!parser.EndOfData) + data.Add(parser.ReadFields()); +} +#> +namespace TS3Client +{ + // Source: http://forum.teamspeak.com/threads/102276-Server-query-error-id-list + public enum Ts3ErrorCode : ushort + { + // ReSharper disable InconsistentNaming, UnusedMember.Global<# foreach (var line in data.Skip(1)) { #> + /// <#= line[1] #> + <#= line[0] #> = <#= line[2] #>,<# } #> + /// For own custom errors + custom_error = 0xFFFF, + // ReSharper enable InconsistentNaming, UnusedMember.Global + } +} \ No newline at end of file diff --git a/TS3Client/Generated/Messages.cs b/TS3Client/Generated/Messages.cs new file mode 100644 index 00000000..acbffd3e --- /dev/null +++ b/TS3Client/Generated/Messages.cs @@ -0,0 +1,1749 @@ +// 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 . + + + + + + + +// *** DO NOT EDIT THIS FILE, IT HAS BEEN AUTO-GENERATED *** + +namespace TS3Client.Messages +{ + using Commands; + using Helper; + using System; + using System.Globalization; + + using UidT = System.String; + using ClientDbIdT = System.UInt64; + using ClientIdT = System.UInt16; + using ChannelIdT = System.UInt64; + using ServerGroupIdT = System.UInt64; + using ChannelGroupIdT = System.UInt64; + + + public sealed class ChannelChanged : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelChanged; + + + public ChannelIdT ChannelId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class ChannelCreated : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelCreated; + + + public ChannelIdT ChannelId { get; set; } + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + public int Order { get; set; } + public string Name { get; set; } + public string Topic { get; set; } + public bool IsDefaultChannel { get; set; } + public bool HasPassword { get; set; } + public bool IsPermanent { get; set; } + public bool IsSemiPermanent { get; set; } + public Codec Codec { get; set; } + public int CodecQuality { get; set; } + public int NeededTalkPower { get; set; } + public int IconId { get; set; } + public int MaxClients { get; set; } + public int MaxFamilyClients { get; set; } + public int CodecLatencyFactor { get; set; } + public bool IsUnencrypted { get; set; } + public TimeSpan DeleteDelay { get; set; } + public bool IsMaxClientsUnlimited { get; set; } + public bool IsMaxFamilyClientsUnlimited { get; set; } + public bool IsMaxFamilyClientsInherited { get; set; } + public string PhoneticName { get; set; } + public ChannelIdT ChannelParentId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "channel_order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_name": Name = Ts3String.Unescape(value); break; + case "channel_topic": Topic = Ts3String.Unescape(value); break; + case "channel_flag_default": IsDefaultChannel = value != "0"; break; + case "channel_flag_password": HasPassword = value != "0"; break; + case "channel_flag_permanent": IsPermanent = value != "0"; break; + case "channel_flag_semi_permanent": IsSemiPermanent = value != "0"; break; + case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; + case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_codec_is_unencrypted": IsUnencrypted = value != "0"; break; + case "channel_delete_delay": DeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value != "0"; break; + case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value != "0"; break; + case "channel_flag_maxfamilyclients_inherited": IsMaxFamilyClientsInherited = value != "0"; break; + case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "cpid": ChannelParentId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class ChannelDeleted : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelDeleted; + + + public ChannelIdT ChannelId { get; set; } + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class ChannelEdited : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelEdited; + + + public ChannelIdT ChannelId { get; set; } + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + public int Order { get; set; } + public string Name { get; set; } + public string Topic { get; set; } + public bool IsDefaultChannel { get; set; } + public bool HasPassword { get; set; } + public bool IsPermanent { get; set; } + public bool IsSemiPermanent { get; set; } + public Codec Codec { get; set; } + public int CodecQuality { get; set; } + public int NeededTalkPower { get; set; } + public int IconId { get; set; } + public int MaxClients { get; set; } + public int MaxFamilyClients { get; set; } + public int CodecLatencyFactor { get; set; } + public bool IsUnencrypted { get; set; } + public TimeSpan DeleteDelay { get; set; } + public bool IsMaxClientsUnlimited { get; set; } + public bool IsMaxFamilyClientsUnlimited { get; set; } + public bool IsMaxFamilyClientsInherited { get; set; } + public string PhoneticName { get; set; } + public MoveReason Reason { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "channel_order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_name": Name = Ts3String.Unescape(value); break; + case "channel_topic": Topic = Ts3String.Unescape(value); break; + case "channel_flag_default": IsDefaultChannel = value != "0"; break; + case "channel_flag_password": HasPassword = value != "0"; break; + case "channel_flag_permanent": IsPermanent = value != "0"; break; + case "channel_flag_semi_permanent": IsSemiPermanent = value != "0"; break; + case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; + case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_codec_is_unencrypted": IsUnencrypted = value != "0"; break; + case "channel_delete_delay": DeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value != "0"; break; + case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value != "0"; break; + case "channel_flag_maxfamilyclients_inherited": IsMaxFamilyClientsInherited = value != "0"; break; + case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; + + } + + } + } + + public sealed class ChannelList : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelList; + + + public ChannelIdT ChannelId { get; set; } + public ChannelIdT ChannelParentId { get; set; } + public string Name { get; set; } + public string Topic { get; set; } + public Codec Codec { get; set; } + public int CodecQuality { get; set; } + public int MaxClients { get; set; } + public int MaxFamilyClients { get; set; } + public int Order { get; set; } + public bool IsPermanent { get; set; } + public bool IsSemiPermanent { get; set; } + public bool IsDefaultChannel { get; set; } + public bool HasPassword { get; set; } + public int CodecLatencyFactor { get; set; } + public bool IsUnencrypted { get; set; } + public TimeSpan DeleteDelay { get; set; } + public bool IsMaxClientsUnlimited { get; set; } + public bool IsMaxFamilyClientsUnlimited { get; set; } + public bool IsMaxFamilyClientsInherited { get; set; } + public int NeededTalkPower { get; set; } + public bool ForcedSilence { get; set; } + public string PhoneticName { get; set; } + public int IconId { get; set; } + public bool IsPrivate { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cpid": ChannelParentId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_name": Name = Ts3String.Unescape(value); break; + case "channel_topic": Topic = Ts3String.Unescape(value); break; + case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; + case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_flag_permanent": IsPermanent = value != "0"; break; + case "channel_flag_semi_permanent": IsSemiPermanent = value != "0"; break; + case "channel_flag_default": IsDefaultChannel = value != "0"; break; + case "channel_flag_password": HasPassword = value != "0"; break; + case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_codec_is_unencrypted": IsUnencrypted = value != "0"; break; + case "channel_delete_delay": DeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value != "0"; break; + case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value != "0"; break; + case "channel_flag_maxfamilyclients_inherited": IsMaxFamilyClientsInherited = value != "0"; break; + case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_forced_silence": ForcedSilence = value != "0"; break; + case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "channel_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_flag_private": IsPrivate = value != "0"; break; + + } + + } + } + + public sealed class ChannelListFinished : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelListFinished; + + + + public void SetField(string name, string value) + { + + } + } + + public sealed class ChannelMoved : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelMoved; + + + public int Order { get; set; } + public ChannelIdT ChannelId { get; set; } + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + public MoveReason Reason { get; set; } + public ChannelIdT ChannelParentId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "cpid": ChannelParentId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class ChannelPasswordChanged : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelPasswordChanged; + + + public ChannelIdT ChannelId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class ChannelSubscribed : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelSubscribed; + + + public ChannelIdT ChannelId { get; set; } + public TimeSpan EmptySince { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "es": EmptySince = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + + } + + } + } + + public sealed class ChannelUnsubscribed : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ChannelUnsubscribed; + + + public ChannelIdT ChannelId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class ClientChannelGroupChanged : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ClientChannelGroupChanged; + + + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public ChannelGroupIdT ChannelGroupId { get; set; } + public ChannelGroupIdT ChannelGroupIndex { get; set; } + public ChannelIdT ChannelId { get; set; } + public ClientIdT ClientId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "cgid": ChannelGroupId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cgi": ChannelGroupIndex = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class ClientChatComposing : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ClientChatComposing; + + + public ClientIdT ClientId { get; set; } + public UidT ClientUid { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cluid": ClientUid = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class ClientEnterView : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ClientEnterView; + + + public MoveReason Reason { get; set; } + public ChannelIdT TargetChannelId { get; set; } + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + public ClientIdT ClientId { get; set; } + public ClientDbIdT DatabaseId { get; set; } + public string NickName { get; set; } + public ClientType ClientType { get; set; } + public ChannelIdT SourceChannelId { get; set; } + public UidT Uid { get; set; } + public string AvatarFlag { get; set; } + public string Description { get; set; } + public int IconId { get; set; } + public bool IsInputMuted { get; set; } + public bool IsOutputMuted { get; set; } + public bool IsOutputOnlyMuted { get; set; } + public bool IsInputHardware { get; set; } + public bool IsClientOutputHardware { get; set; } + public string Metadata { get; set; } + public bool IsRecording { get; set; } + public ChannelGroupIdT ChannelGroupId { get; set; } + public ChannelGroupIdT InheritedChannelGroupFromChannelId { get; set; } + public ServerGroupIdT[] ServerGroups { get; set; } + public bool IsAway { get; set; } + public string AwayMessage { get; set; } + public int TalkPower { get; set; } + public int RequestedTalkPower { get; set; } + public string TalkPowerRequestMessage { get; set; } + public bool IsTalker { get; set; } + public bool IsPrioritySpeaker { get; set; } + public int UnreadMessages { get; set; } + public string PhoneticName { get; set; } + public int NeededServerQueryViewPower { get; set; } + public bool IsChannelCommander { get; set; } + public string CountryCode { get; set; } + public string Badges { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "ctid": TargetChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_nickname": NickName = Ts3String.Unescape(value); break; + case "client_type": { if (!Enum.TryParse(value, out ClientType val)) throw new FormatException(); ClientType = val; } break; + case "cfid": SourceChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; + case "client_flag_avatar": AvatarFlag = Ts3String.Unescape(value); break; + case "client_description": Description = Ts3String.Unescape(value); break; + case "client_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_input_muted": IsInputMuted = value != "0"; break; + case "client_output_muted": IsOutputMuted = value != "0"; break; + case "client_outputonly_muted": IsOutputOnlyMuted = value != "0"; break; + case "client_input_hardware": IsInputHardware = value != "0"; break; + case "client_output_hardware": IsClientOutputHardware = value != "0"; break; + case "client_meta_data": Metadata = Ts3String.Unescape(value); break; + case "client_is_recording": IsRecording = value != "0"; break; + case "client_channel_group_id": ChannelGroupId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_channel_group_inherited_channel_id": InheritedChannelGroupFromChannelId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_servergroups": { var t = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); ServerGroups = new ServerGroupIdT[t.Length]; for(int i = 0; i < t.Length; i++) ServerGroups[i] = ServerGroupIdT.Parse(t[i], CultureInfo.InvariantCulture); } break; + case "client_away": IsAway = value != "0"; break; + case "client_away_message": AwayMessage = Ts3String.Unescape(value); break; + case "client_talk_power": TalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_talk_request": RequestedTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_talk_request_msg": TalkPowerRequestMessage = Ts3String.Unescape(value); break; + case "client_is_talker": IsTalker = value != "0"; break; + case "client_is_priority_speaker": IsPrioritySpeaker = value != "0"; break; + case "client_unread_messages": UnreadMessages = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_nickname_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_is_channel_commander": IsChannelCommander = value != "0"; break; + case "client_country": CountryCode = Ts3String.Unescape(value); break; + case "client_badges": Badges = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class ClientLeftView : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ClientLeftView; + + + public string ReasonMessage { get; set; } + public TimeSpan BanTime { get; set; } + public MoveReason Reason { get; set; } + public ChannelIdT TargetChannelId { get; set; } + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + public ClientIdT ClientId { get; set; } + public ChannelIdT SourceChannelId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "reasonmsg": ReasonMessage = Ts3String.Unescape(value); break; + case "bantime": BanTime = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "ctid": TargetChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cfid": SourceChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class ClientMoved : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ClientMoved; + + + public ClientIdT ClientId { get; set; } + public MoveReason Reason { get; set; } + public ChannelIdT TargetChannelId { get; set; } + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "ctid": TargetChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class ClientNeededPermissions : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ClientNeededPermissions; + + + public PermissionId PermissionId { get; set; } + public int PermissionValue { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "permid": PermissionId = (PermissionId)int.Parse(value, CultureInfo.InvariantCulture); break; + case "permvalue": PermissionValue = int.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class ClientServerGroupAdded : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ClientServerGroupAdded; + + + public string Name { get; set; } + public ServerGroupIdT ServerGroupId { get; set; } + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + public ClientIdT ClientId { get; set; } + public UidT ClientUid { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "name": Name = Ts3String.Unescape(value); break; + case "sgid": ServerGroupId = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cluid": ClientUid = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class CommandError : INotification + { + public NotificationType NotifyType { get; } = NotificationType.Error; + + + public Ts3ErrorCode Id { get; set; } + public string Message { get; set; } + public PermissionId MissingPermissionId { get; set; } + public string ReturnCode { get; set; } + public string ExtraMessage { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "id": Id = (Ts3ErrorCode)ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "msg": Message = Ts3String.Unescape(value); break; + case "failed_permid": MissingPermissionId = (PermissionId)int.Parse(value, CultureInfo.InvariantCulture); break; + case "return_code": ReturnCode = Ts3String.Unescape(value); break; + case "extra_msg": ExtraMessage = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class ConnectionInfo : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ConnectionInfo; + + + public ClientIdT ClientId { get; set; } + public float ConnectionPing { get; set; } + public float ConnectionPingDeviation { get; set; } + public TimeSpan ConnectionTime { get; set; } + public string Ip { get; set; } + public ushort ConnectionClientPort { get; set; } + public long ConnectionPacketsSentSpeech { get; set; } + public ulong ConnectionPacketsSentKeepalive { get; set; } + public ulong ConnectionPacketsSentControl { get; set; } + public ulong ConnectionBytesSentSpeech { get; set; } + public ulong ConnectionBytesSentKeepalive { get; set; } + public ulong ConnectionBytesSentControl { get; set; } + public ulong ConnectionPacketsReceivedSpeech { get; set; } + public ulong ConnectionPacketsReceivedKeepalive { get; set; } + public ulong ConnectionPacketsReceivedControl { get; set; } + public ulong ConnectionBytesReceivedSpeech { get; set; } + public ulong ConnectionBytesReceivedKeepalive { get; set; } + public ulong ConnectionBytesReceivedControl { get; set; } + public float ConnectionServerToClientPacketlossSpeech { get; set; } + public float ConnectionServerToClientPacketlossKeepalive { get; set; } + public float ConnectionServerToClientPacketlossControl { get; set; } + public float ConnectionServerToClientPacketlossTotal { get; set; } + public float ConnectionClientToServerPacketlossSpeech { get; set; } + public float ConnectionClientToServerPacketlossKeepalive { get; set; } + public float ConnectionClientToServerPacketlossControl { get; set; } + public float ConnectionClientToServerPacketlossTotal { get; set; } + public uint ConnectionBandwidthSentLastSecondSpeech { get; set; } + public uint ConnectionBandwidthSentLastSecondKeepalive { get; set; } + public uint ConnectionBandwidthSentLastSecondControl { get; set; } + public uint ConnectionBandwidthSentLastMinuteSpeech { get; set; } + public uint ConnectionBandwidthSentLastMinuteKeepalive { get; set; } + public uint ConnectionBandwidthSentLastMinuteControl { get; set; } + public uint ConnectionBandwidthReceivedLastSecondSpeech { get; set; } + public uint ConnectionBandwidthReceivedLastSecondKeepalive { get; set; } + public uint ConnectionBandwidthReceivedLastSecondControl { get; set; } + public uint ConnectionBandwidthReceivedLastMinuteSpeech { get; set; } + public uint ConnectionBandwidthReceivedLastMinuteKeepalive { get; set; } + public uint ConnectionBandwidthReceivedLastMinuteControl { get; set; } + public long ConnectionFiletransferSent { get; set; } + public long ConnectionFiletransferReceived { get; set; } + public TimeSpan ConnectionIdleTime { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_ping": ConnectionPing = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_ping_deviation": ConnectionPingDeviation = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_connected_time": ConnectionTime = TimeSpan.FromMilliseconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "connection_client_ip": Ip = Ts3String.Unescape(value); break; + case "connection_client_port": ConnectionClientPort = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_packets_sent_speech": ConnectionPacketsSentSpeech = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_packets_sent_keepalive": ConnectionPacketsSentKeepalive = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_packets_sent_control": ConnectionPacketsSentControl = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bytes_sent_speech": ConnectionBytesSentSpeech = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bytes_sent_keepalive": ConnectionBytesSentKeepalive = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bytes_sent_control": ConnectionBytesSentControl = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_packets_received_speech": ConnectionPacketsReceivedSpeech = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_packets_received_keepalive": ConnectionPacketsReceivedKeepalive = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_packets_received_control": ConnectionPacketsReceivedControl = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bytes_received_speech": ConnectionBytesReceivedSpeech = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bytes_received_keepalive": ConnectionBytesReceivedKeepalive = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bytes_received_control": ConnectionBytesReceivedControl = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_server2client_packetloss_speech": ConnectionServerToClientPacketlossSpeech = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_server2client_packetloss_keepalive": ConnectionServerToClientPacketlossKeepalive = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_server2client_packetloss_control": ConnectionServerToClientPacketlossControl = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_server2client_packetloss_total": ConnectionServerToClientPacketlossTotal = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_client2server_packetloss_speech": ConnectionClientToServerPacketlossSpeech = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_client2server_packetloss_keepalive": ConnectionClientToServerPacketlossKeepalive = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_client2server_packetloss_control": ConnectionClientToServerPacketlossControl = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_client2server_packetloss_total": ConnectionClientToServerPacketlossTotal = float.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_second_speech": ConnectionBandwidthSentLastSecondSpeech = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_second_keepalive": ConnectionBandwidthSentLastSecondKeepalive = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_second_control": ConnectionBandwidthSentLastSecondControl = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_minute_speech": ConnectionBandwidthSentLastMinuteSpeech = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_minute_keepalive": ConnectionBandwidthSentLastMinuteKeepalive = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_minute_control": ConnectionBandwidthSentLastMinuteControl = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_second_speech": ConnectionBandwidthReceivedLastSecondSpeech = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_second_keepalive": ConnectionBandwidthReceivedLastSecondKeepalive = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_second_control": ConnectionBandwidthReceivedLastSecondControl = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_minute_speech": ConnectionBandwidthReceivedLastMinuteSpeech = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_minute_keepalive": ConnectionBandwidthReceivedLastMinuteKeepalive = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_minute_control": ConnectionBandwidthReceivedLastMinuteControl = uint.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_filetransfer_bandwidth_sent": ConnectionFiletransferSent = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_filetransfer_bandwidth_received": ConnectionFiletransferReceived = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_idle_time": ConnectionIdleTime = TimeSpan.FromMilliseconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + + } + + } + } + + public sealed class ConnectionInfoRequest : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ConnectionInfoRequest; + + + + public void SetField(string name, string value) + { + + } + } + + public sealed class FileListFinished : INotification + { + public NotificationType NotifyType { get; } = NotificationType.FileListFinished; + + + public ChannelIdT ChannelId { get; set; } + public string Path { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "path": Path = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class FileTransferStatus : INotification + { + public NotificationType NotifyType { get; } = NotificationType.FileTransferStatus; + + + public ushort ClientFileTransferId { get; set; } + public Ts3ErrorCode Status { get; set; } + public string Message { get; set; } + public long Size { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "clientftfid": ClientFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "status": Status = (Ts3ErrorCode)ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "msg": Message = Ts3String.Unescape(value); break; + case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class InitIvExpand : INotification + { + public NotificationType NotifyType { get; } = NotificationType.InitIvExpand; + + + public string Alpha { get; set; } + public string Beta { get; set; } + public string Omega { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "alpha": Alpha = Ts3String.Unescape(value); break; + case "beta": Beta = Ts3String.Unescape(value); break; + case "omega": Omega = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class InitServer : INotification + { + public NotificationType NotifyType { get; } = NotificationType.InitServer; + + + public string WelcomeMessage { get; set; } + public string ServerPlatform { get; set; } + public string ServerVersion { get; set; } + public int MaxClients { get; set; } + public long ServerCreated { get; set; } + public string Hostmessage { get; set; } + public HostMessageMode HostmessageMode { get; set; } + public ulong VirtualServerId { get; set; } + public string ServerIp { get; set; } + public bool AskForPrivilege { get; set; } + public string ClientName { get; set; } + public ClientIdT ClientId { get; set; } + public ushort ProtocolVersion { get; set; } + public ushort LicenseType { get; set; } + public int TalkPower { get; set; } + public int NeededServerQueryViewPower { get; set; } + public string ServerName { get; set; } + public CodecEncryptionMode CodecEncryptionMode { get; set; } + public ServerGroupIdT DefaultServerGroup { get; set; } + public ChannelGroupIdT DefaultChannelGroup { get; set; } + public string HostbannerUrl { get; set; } + public string HostbannerGfxUrl { get; set; } + public TimeSpan HostbannerGfxInterval { get; set; } + public float PrioritySpeakerDimmModificator { get; set; } + public string HostbuttonTooltip { get; set; } + public string HostbuttonUrl { get; set; } + public string HostbuttonGfxUrl { get; set; } + public string PhoneticName { get; set; } + public int IconId { get; set; } + public HostBannerMode HostbannerMode { get; set; } + public TimeSpan DefaultTempChannelDeleteDelay { get; set; } + + public void SetField(string name, string value) + { + + 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": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_created": ServerCreated = long.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_hostmessage": Hostmessage = Ts3String.Unescape(value); break; + case "virtualserver_hostmessage_mode": { if (!Enum.TryParse(value, out HostMessageMode val)) throw new FormatException(); HostmessageMode = val; } break; + case "virtualserver_id": VirtualServerId = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_ip": ServerIp = Ts3String.Unescape(value); break; + case "virtualserver_ask_for_privilegekey": AskForPrivilege = value != "0"; break; + case "acn": ClientName = Ts3String.Unescape(value); break; + case "aclid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "pv": ProtocolVersion = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "lt": LicenseType = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "client_talk_power": TalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_name": ServerName = Ts3String.Unescape(value); break; + case "virtualserver_codec_encryption_mode": { if (!Enum.TryParse(value, out CodecEncryptionMode val)) throw new FormatException(); CodecEncryptionMode = val; } break; + case "virtualserver_default_server_group": DefaultServerGroup = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_default_channel_group": DefaultChannelGroup = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_hostbanner_url": HostbannerUrl = Ts3String.Unescape(value); break; + case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = Ts3String.Unescape(value); break; + case "virtualserver_hostbanner_gfx_interval": HostbannerGfxInterval = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "virtualserver_priority_speaker_dimm_modificator": PrioritySpeakerDimmModificator = float.Parse(value, CultureInfo.InvariantCulture); 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_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "virtualserver_hostbanner_mode": { if (!Enum.TryParse(value, out HostBannerMode val)) throw new FormatException(); HostbannerMode = val; } break; + case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + + } + + } + } + + public sealed class ServerEdited : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ServerEdited; + + + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + public MoveReason Reason { get; set; } + public string ServerName { get; set; } + public CodecEncryptionMode CodecEncryptionMode { get; set; } + public ServerGroupIdT DefaultServerGroup { get; set; } + public ChannelGroupIdT DefaultChannelGroup { get; set; } + public string HostbannerUrl { get; set; } + public string HostbannerGfxUrl { get; set; } + public TimeSpan HostbannerGfxInterval { get; set; } + public float PrioritySpeakerDimmModificator { get; set; } + public string HostbuttonTooltip { get; set; } + public string HostbuttonUrl { get; set; } + public string HostbuttonGfxUrl { get; set; } + public string PhoneticName { get; set; } + public int IconId { get; set; } + public HostBannerMode HostbannerMode { get; set; } + public TimeSpan DefaultTempChannelDeleteDelay { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "virtualserver_name": ServerName = Ts3String.Unescape(value); break; + case "virtualserver_codec_encryption_mode": { if (!Enum.TryParse(value, out CodecEncryptionMode val)) throw new FormatException(); CodecEncryptionMode = val; } break; + case "virtualserver_default_server_group": DefaultServerGroup = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_default_channel_group": DefaultChannelGroup = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_hostbanner_url": HostbannerUrl = Ts3String.Unescape(value); break; + case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = Ts3String.Unescape(value); break; + case "virtualserver_hostbanner_gfx_interval": HostbannerGfxInterval = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "virtualserver_priority_speaker_dimm_modificator": PrioritySpeakerDimmModificator = float.Parse(value, CultureInfo.InvariantCulture); 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_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "virtualserver_hostbanner_mode": { if (!Enum.TryParse(value, out HostBannerMode val)) throw new FormatException(); HostbannerMode = val; } break; + case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + + } + + } + } + + public sealed class ServerGroupList : INotification + { + public NotificationType NotifyType { get; } = NotificationType.ServerGroupList; + + + public ServerGroupIdT ServerGroupId { get; set; } + public string Name { get; set; } + public PermissionGroupDatabaseType GroupType { get; set; } + public int IconId { get; set; } + public bool GroupIsPermanent { get; set; } + public int SortId { get; set; } + public GroupNamingMode NamingMode { get; set; } + public int NeededModifyPower { get; set; } + public int NeededMemberAddPower { get; set; } + public int NeededMemberRemovePower { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "sgid": ServerGroupId = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "name": Name = Ts3String.Unescape(value); break; + case "type": { if (!Enum.TryParse(value, out PermissionGroupDatabaseType val)) throw new FormatException(); GroupType = val; } break; + case "iconid": IconId = int.Parse(value, CultureInfo.InvariantCulture); break; + case "savedb": GroupIsPermanent = value != "0"; break; + case "sortid": SortId = int.Parse(value, CultureInfo.InvariantCulture); break; + case "namemode": { if (!Enum.TryParse(value, out GroupNamingMode val)) throw new FormatException(); NamingMode = val; } break; + case "n_modifyp": NeededModifyPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "n_member_addp": NeededMemberAddPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "n_member_remove_p": NeededMemberRemovePower = int.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class TextMessage : INotification + { + public NotificationType NotifyType { get; } = NotificationType.TextMessage; + + + public TextMessageTargetMode Target { get; set; } + public string Message { get; set; } + public ClientIdT TargetClientId { get; set; } + public ClientIdT InvokerId { get; set; } + public string InvokerName { get; set; } + public UidT InvokerUid { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "targetmode": { if (!Enum.TryParse(value, out TextMessageTargetMode val)) throw new FormatException(); Target = val; } break; + case "msg": Message = Ts3String.Unescape(value); break; + case "target": TargetClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokername": InvokerName = Ts3String.Unescape(value); break; + case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class TokenUsed : INotification + { + public NotificationType NotifyType { get; } = NotificationType.TokenUsed; + + + public string UsedToken { get; set; } + public string TokenCustomSet { get; set; } + public string Token1 { get; set; } + public string Token2 { get; set; } + public ClientIdT ClientId { get; set; } + public ClientDbIdT ClientDbId { get; set; } + public UidT ClientUid { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "token": UsedToken = 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": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cldbid": ClientDbId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cluid": ClientUid = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class ChannelData : IResponse + { + + public string ReturnCode { get; set; } + + public ChannelIdT Id { get; set; } + public ChannelIdT ParentChannelId { get; set; } + public TimeSpan DurationEmpty { get; set; } + public int TotalFamilyClients { get; set; } + public int TotalClients { get; set; } + public int NeededSubscribePower { get; set; } + public int Order { get; set; } + public string Name { get; set; } + public string Topic { get; set; } + public bool IsDefaultChannel { get; set; } + public bool HasPassword { get; set; } + public bool IsPermanent { get; set; } + public bool IsSemiPermanent { get; set; } + public Codec Codec { get; set; } + public int CodecQuality { get; set; } + public int NeededTalkPower { get; set; } + public int IconId { get; set; } + public int MaxClients { get; set; } + public int MaxFamilyClients { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "id": Id = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "pid": ParentChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "seconds_empty": DurationEmpty = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "total_clients_family": TotalFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "total_clients": TotalClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_needed_subscribe_power": NeededSubscribePower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_name": Name = Ts3String.Unescape(value); break; + case "channel_topic": Topic = Ts3String.Unescape(value); break; + case "channel_flag_default": IsDefaultChannel = value != "0"; break; + case "channel_flag_password": HasPassword = value != "0"; break; + case "channel_flag_permanent": IsPermanent = value != "0"; break; + case "channel_flag_semi_permanent": IsSemiPermanent = value != "0"; break; + case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; + case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "return_code": ReturnCode = Ts3String.Unescape(value); break; + } + + } + } + + public sealed class ClientData : IResponse + { + + public string ReturnCode { get; set; } + + public ClientIdT ClientId { get; set; } + public UidT Uid { get; set; } + public ChannelIdT ChannelId { get; set; } + public ClientDbIdT DatabaseId { get; set; } + public string NickName { get; set; } + public ClientType ClientType { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_nickname": NickName = Ts3String.Unescape(value); break; + case "client_type": { if (!Enum.TryParse(value, out ClientType val)) throw new FormatException(); ClientType = val; } break; + case "return_code": ReturnCode = Ts3String.Unescape(value); break; + } + + } + } + + public sealed class ClientDbData : IResponse + { + + public string ReturnCode { get; set; } + + public string LastIp { get; set; } + public ClientIdT ClientId { get; set; } + public UidT Uid { get; set; } + public ChannelIdT ChannelId { get; set; } + public ClientDbIdT DatabaseId { get; set; } + public string NickName { get; set; } + public ClientType ClientType { get; set; } + public string AvatarFlag { get; set; } + public string Description { get; set; } + public int IconId { get; set; } + public DateTime CreationDate { get; set; } + public DateTime LastConnected { get; set; } + public int TotalConnections { get; set; } + public long MonthlyUploadQuota { get; set; } + public long MonthlyDownloadQuota { get; set; } + public long TotalUploadQuota { get; set; } + public long TotalDownloadQuota { get; set; } + public string Base64HashClientUid { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "client_lastip": LastIp = Ts3String.Unescape(value); break; + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_nickname": NickName = Ts3String.Unescape(value); break; + case "client_type": { if (!Enum.TryParse(value, out ClientType val)) throw new FormatException(); ClientType = val; } break; + case "client_flag_avatar": AvatarFlag = Ts3String.Unescape(value); break; + case "client_description": Description = Ts3String.Unescape(value); break; + case "client_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_created": CreationDate = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_lastconnected": LastConnected = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_totalconnections": TotalConnections = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_month_bytes_uploaded": MonthlyUploadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_month_bytes_downloaded": MonthlyDownloadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_total_bytes_uploaded": TotalUploadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_total_bytes_downloaded": TotalDownloadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_base64HashClientUID": Base64HashClientUid = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = Ts3String.Unescape(value); break; + } + + } + } + + public sealed class ClientInfo : IResponse + { + + public string ReturnCode { get; set; } + + public TimeSpan ClientIdleTime { get; set; } + public string ClientVersion { get; set; } + public string ClientVersionSign { get; set; } + public string ClientPlattform { get; set; } + public string DefaultChannel { get; set; } + public string SecurityHash { get; set; } + public string LoginName { get; set; } + public string DefaultToken { get; set; } + public long ConnectionFiletransferSent { get; set; } + public long ConnectionFiletransferReceived { get; set; } + public long ConnectionPacketsSent { get; set; } + public long ConnectionPacketsReceived { get; set; } + public long ConnectionBytesSent { get; set; } + public long ConnectionBytesReceived { get; set; } + public long ConnectionBandwidtSentLastSecond { get; set; } + public long ConnectionBandwidtReceivedLastSecond { get; set; } + public long ConnectionBandwidtSentLastMinute { get; set; } + public long ConnectionBandwidtReceivedLastMinute { get; set; } + public TimeSpan ConnectionTime { get; set; } + public string Ip { get; set; } + public ChannelIdT ChannelId { get; set; } + public UidT Uid { get; set; } + public ClientDbIdT DatabaseId { get; set; } + public string NickName { get; set; } + public ClientType ClientType { get; set; } + public bool IsInputMuted { get; set; } + public bool IsOutputMuted { get; set; } + public bool IsOutputOnlyMuted { get; set; } + public bool IsInputHardware { get; set; } + public bool IsClientOutputHardware { get; set; } + public string Metadata { get; set; } + public bool IsRecording { get; set; } + public ChannelGroupIdT ChannelGroupId { get; set; } + public ChannelGroupIdT InheritedChannelGroupFromChannelId { get; set; } + public ServerGroupIdT[] ServerGroups { get; set; } + public bool IsAway { get; set; } + public string AwayMessage { get; set; } + public int TalkPower { get; set; } + public int RequestedTalkPower { get; set; } + public string TalkPowerRequestMessage { get; set; } + public bool IsTalker { get; set; } + public bool IsPrioritySpeaker { get; set; } + public int UnreadMessages { get; set; } + public string PhoneticName { get; set; } + public int NeededServerQueryViewPower { get; set; } + public bool IsChannelCommander { get; set; } + public string CountryCode { get; set; } + public string Badges { get; set; } + public DateTime CreationDate { get; set; } + public DateTime LastConnected { get; set; } + public int TotalConnections { get; set; } + public long MonthlyUploadQuota { get; set; } + public long MonthlyDownloadQuota { get; set; } + public long TotalUploadQuota { get; set; } + public long TotalDownloadQuota { get; set; } + public string Base64HashClientUid { get; set; } + public string AvatarFlag { get; set; } + public string Description { get; set; } + public int IconId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "client_idle_time": ClientIdleTime = TimeSpan.FromMilliseconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_version": ClientVersion = Ts3String.Unescape(value); break; + case "client_version_sign": ClientVersionSign = Ts3String.Unescape(value); break; + case "client_platform": ClientPlattform = 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": ConnectionFiletransferSent = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_filetransfer_bandwidth_received": ConnectionFiletransferReceived = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_packets_sent_total": ConnectionPacketsSent = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_packets_received_total": ConnectionPacketsReceived = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bytes_sent_total": ConnectionBytesSent = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bytes_received_total": ConnectionBytesReceived = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_second_total": ConnectionBandwidtSentLastSecond = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_second_total": ConnectionBandwidtReceivedLastSecond = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_minute_total": ConnectionBandwidtSentLastMinute = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_minute_total": ConnectionBandwidtReceivedLastMinute = long.Parse(value, CultureInfo.InvariantCulture); break; + case "connection_connected_time": ConnectionTime = TimeSpan.FromMilliseconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "connection_client_ip": Ip = Ts3String.Unescape(value); break; + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_nickname": NickName = Ts3String.Unescape(value); break; + case "client_type": { if (!Enum.TryParse(value, out ClientType val)) throw new FormatException(); ClientType = val; } break; + case "client_input_muted": IsInputMuted = value != "0"; break; + case "client_output_muted": IsOutputMuted = value != "0"; break; + case "client_outputonly_muted": IsOutputOnlyMuted = value != "0"; break; + case "client_input_hardware": IsInputHardware = value != "0"; break; + case "client_output_hardware": IsClientOutputHardware = value != "0"; break; + case "client_meta_data": Metadata = Ts3String.Unescape(value); break; + case "client_is_recording": IsRecording = value != "0"; break; + case "client_channel_group_id": ChannelGroupId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_channel_group_inherited_channel_id": InheritedChannelGroupFromChannelId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_servergroups": { var t = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); ServerGroups = new ServerGroupIdT[t.Length]; for(int i = 0; i < t.Length; i++) ServerGroups[i] = ServerGroupIdT.Parse(t[i], CultureInfo.InvariantCulture); } break; + case "client_away": IsAway = value != "0"; break; + case "client_away_message": AwayMessage = Ts3String.Unescape(value); break; + case "client_talk_power": TalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_talk_request": RequestedTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_talk_request_msg": TalkPowerRequestMessage = Ts3String.Unescape(value); break; + case "client_is_talker": IsTalker = value != "0"; break; + case "client_is_priority_speaker": IsPrioritySpeaker = value != "0"; break; + case "client_unread_messages": UnreadMessages = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_nickname_phonetic": PhoneticName = Ts3String.Unescape(value); break; + case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_is_channel_commander": IsChannelCommander = value != "0"; break; + case "client_country": CountryCode = Ts3String.Unescape(value); break; + case "client_badges": Badges = Ts3String.Unescape(value); break; + case "client_created": CreationDate = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_lastconnected": LastConnected = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_totalconnections": TotalConnections = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_month_bytes_uploaded": MonthlyUploadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_month_bytes_downloaded": MonthlyDownloadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_total_bytes_uploaded": TotalUploadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_total_bytes_downloaded": TotalDownloadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_base64HashClientUID": Base64HashClientUid = Ts3String.Unescape(value); break; + case "client_flag_avatar": AvatarFlag = Ts3String.Unescape(value); break; + case "client_description": Description = Ts3String.Unescape(value); break; + case "client_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "return_code": ReturnCode = Ts3String.Unescape(value); break; + } + + } + } + + public sealed class ServerData : IResponse + { + + public string ReturnCode { get; set; } + + public int ClientsOnline { get; set; } + public int QueriesOnline { get; set; } + public int MaxClients { get; set; } + public TimeSpan Uptime { get; set; } + public bool Autostart { get; set; } + public string MachineId { get; set; } + public string ServerName { get; set; } + public ulong VirtualServerId { get; set; } + public UidT VirtualServerUid { get; set; } + public ushort VirtualServerPort { get; set; } + public string VirtualServerStatus { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "virtualserver_clientsonline": ClientsOnline = int.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_queryclientsonline": QueriesOnline = int.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_uptime": Uptime = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "virtualserver_autostart": Autostart = value != "0"; break; + case "virtualserver_machine_id": MachineId = Ts3String.Unescape(value); break; + case "virtualserver_name": ServerName = Ts3String.Unescape(value); break; + case "virtualserver_id": VirtualServerId = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_unique_identifier": VirtualServerUid = Ts3String.Unescape(value); break; + case "virtualserver_port": VirtualServerPort = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_status": VirtualServerStatus = Ts3String.Unescape(value); break; + case "return_code": ReturnCode = Ts3String.Unescape(value); break; + } + + } + } + + public sealed class ServerGroupAddResponse : IResponse + { + + public string ReturnCode { get; set; } + + public ServerGroupIdT ServerGroupId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "sgid": ServerGroupId = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "return_code": ReturnCode = Ts3String.Unescape(value); break; + } + + } + } + + public sealed class WhoAmI : IResponse + { + + public string ReturnCode { get; set; } + + public ClientIdT ClientId { get; set; } + public ChannelIdT ChannelId { get; set; } + public string NickName { get; set; } + public ClientDbIdT DatabaseId { get; set; } + public string LoginName { get; set; } + public ulong OriginServerId { get; set; } + public ulong VirtualServerId { get; set; } + public UidT VirtualServerUid { get; set; } + public ushort VirtualServerPort { get; set; } + public string VirtualServerStatus { get; set; } + public UidT Uid { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "client_id": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_channel_id": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_nickname": NickName = Ts3String.Unescape(value); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_login_name": LoginName = Ts3String.Unescape(value); break; + case "client_origin_server_id": OriginServerId = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_id": VirtualServerId = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_unique_identifier": VirtualServerUid = Ts3String.Unescape(value); break; + case "virtualserver_port": VirtualServerPort = ushort.Parse(value, CultureInfo.InvariantCulture); 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; + } + + } + } + + public sealed class ClientServerGroup : INotification , IResponse + { + public NotificationType NotifyType { get; } = NotificationType.ServerGroupsByClientId; + public string ReturnCode { get; set; } + + public string Name { get; set; } + public ServerGroupIdT ServerGroupId { get; set; } + public ClientDbIdT ClientDbId { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "name": Name = Ts3String.Unescape(value); break; + case "sgid": ServerGroupId = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cldbid": ClientDbId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + + } + + } + } + + public sealed class FileDownload : INotification , IResponse + { + public NotificationType NotifyType { get; } = NotificationType.StartDownload; + public string ReturnCode { get; set; } + + public ushort ClientFileTransferId { get; set; } + public ushort ServerFileTransferId { get; set; } + public string FileTransferKey { get; set; } + public ushort Port { get; set; } + public long Size { get; set; } + public string Message { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "clientftfid": ClientFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "serverftfid": ServerFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "ftkey": FileTransferKey = Ts3String.Unescape(value); break; + case "port": Port = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; + case "msg": Message = Ts3String.Unescape(value); break; + + } + + } + } + + public sealed class FileInfoTs : INotification , IResponse + { + public NotificationType NotifyType { get; } = NotificationType.FileInfo; + public string ReturnCode { get; set; } + + public ChannelIdT ChannelId { get; set; } + public string Path { get; set; } + public string Name { get; set; } + public long Size { get; set; } + public DateTime DateTime { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "path": Path = Ts3String.Unescape(value); break; + case "name": Name = Ts3String.Unescape(value); break; + case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; + case "datetime": DateTime = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + + } + + } + } + + public sealed class FileList : INotification , IResponse + { + public NotificationType NotifyType { get; } = NotificationType.FileList; + public string ReturnCode { get; set; } + + public ChannelIdT ChannelId { get; set; } + public string Path { get; set; } + public string Name { get; set; } + public long Size { get; set; } + public DateTime DateTime { get; set; } + public bool IsFile { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "path": Path = Ts3String.Unescape(value); break; + case "name": Name = Ts3String.Unescape(value); break; + case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; + case "datetime": DateTime = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "type": IsFile = value != "0"; break; + + } + + } + } + + public sealed class FileTransfer : INotification , IResponse + { + public NotificationType NotifyType { get; } = NotificationType.FileTransfer; + public string ReturnCode { get; set; } + + public ClientIdT ClientId { get; set; } + public string Path { get; set; } + public string Name { get; set; } + public long Size { get; set; } + public long SizeDone { get; set; } + public ushort ClientFileTransferId { get; set; } + public ushort ServerFileTransferId { get; set; } + public ulong Sender { get; set; } + public int Status { get; set; } + public float CurrentSpeed { get; set; } + public float AverageSpeed { get; set; } + public TimeSpan Runtime { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "path": Path = Ts3String.Unescape(value); break; + case "name": Name = Ts3String.Unescape(value); break; + case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; + case "sizedone": SizeDone = long.Parse(value, CultureInfo.InvariantCulture); break; + case "clientftfid": ClientFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "serverftfid": ServerFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "sender": Sender = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "status": Status = int.Parse(value, CultureInfo.InvariantCulture); break; + case "current_speed": CurrentSpeed = float.Parse(value, CultureInfo.InvariantCulture); break; + case "average_speed": AverageSpeed = float.Parse(value, CultureInfo.InvariantCulture); break; + case "runtime": Runtime = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + + } + + } + } + + public sealed class FileUpload : INotification , IResponse + { + public NotificationType NotifyType { get; } = NotificationType.StartUpload; + public string ReturnCode { get; set; } + + public ushort ClientFileTransferId { get; set; } + public ushort ServerFileTransferId { get; set; } + public string FileTransferKey { get; set; } + public ushort Port { get; set; } + public long SeekPosistion { get; set; } + public string Message { get; set; } + + public void SetField(string name, string value) + { + + switch(name) + { + + case "clientftfid": ClientFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "serverftfid": ServerFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "ftkey": FileTransferKey = Ts3String.Unescape(value); break; + case "port": Port = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "seekpos": SeekPosistion = long.Parse(value, CultureInfo.InvariantCulture); break; + case "msg": Message = Ts3String.Unescape(value); break; + + } + + } + } + + public static class MessageHelper + { + public static NotificationType GetNotificationType(string name) + { + switch(name) + { + + case "error": return NotificationType.Error; + case "notifychannelchanged": return NotificationType.ChannelChanged; + case "notifychannelcreated": return NotificationType.ChannelCreated; + case "notifychanneldeleted": return NotificationType.ChannelDeleted; + case "notifychanneledited": return NotificationType.ChannelEdited; + case "notifychannelmoved": return NotificationType.ChannelMoved; + case "notifychannelpasswordchanged": return NotificationType.ChannelPasswordChanged; + case "notifycliententerview": return NotificationType.ClientEnterView; + case "notifyclientleftview": return NotificationType.ClientLeftView; + case "notifyclientmoved": return NotificationType.ClientMoved; + case "notifyserveredited": return NotificationType.ServerEdited; + case "notifytextmessage": return NotificationType.TextMessage; + case "notifytokenused": return NotificationType.TokenUsed; + case "channellist": return NotificationType.ChannelList; + case "channellistfinished": return NotificationType.ChannelListFinished; + case "initivexpand": return NotificationType.InitIvExpand; + case "initserver": return NotificationType.InitServer; + case "notifychannelsubscribed": return NotificationType.ChannelSubscribed; + case "notifychannelunsubscribed": return NotificationType.ChannelUnsubscribed; + case "notifyclientchannelgroupchanged": return NotificationType.ClientChannelGroupChanged; + case "notifyclientchatcomposing": return NotificationType.ClientChatComposing; + case "notifyclientneededpermissions": return NotificationType.ClientNeededPermissions; + case "notifyconnectioninfo": return NotificationType.ConnectionInfo; + case "notifyconnectioninforequest": return NotificationType.ConnectionInfoRequest; + case "notifyfileinfo": return NotificationType.FileInfo; + case "notifyfilelist": return NotificationType.FileList; + case "notifyfilelistfinished": return NotificationType.FileListFinished; + case "notifyfiletransferlist": return NotificationType.FileTransfer; + case "notifyservergroupclientadded": return NotificationType.ClientServerGroupAdded; + case "notifyservergrouplist": return NotificationType.ServerGroupList; + case "notifyservergroupsbyclientid": return NotificationType.ServerGroupsByClientId; + case "notifystartdownload": return NotificationType.StartDownload; + case "notifystartupload": return NotificationType.StartUpload; + case "notifystatusfiletransfer": return NotificationType.FileTransferStatus; + default: return NotificationType.Unknown; + } + } + + public static INotification GenerateNotificationType(NotificationType name) + { + switch(name) + { + + case NotificationType.Error: return new CommandError(); + case NotificationType.ChannelChanged: return new ChannelChanged(); + case NotificationType.ChannelCreated: return new ChannelCreated(); + case NotificationType.ChannelDeleted: return new ChannelDeleted(); + case NotificationType.ChannelEdited: return new ChannelEdited(); + case NotificationType.ChannelMoved: return new ChannelMoved(); + case NotificationType.ChannelPasswordChanged: return new ChannelPasswordChanged(); + case NotificationType.ClientEnterView: return new ClientEnterView(); + case NotificationType.ClientLeftView: return new ClientLeftView(); + case NotificationType.ClientMoved: return new ClientMoved(); + case NotificationType.ServerEdited: return new ServerEdited(); + case NotificationType.TextMessage: return new TextMessage(); + case NotificationType.TokenUsed: return new TokenUsed(); + case NotificationType.ChannelList: return new ChannelList(); + case NotificationType.ChannelListFinished: return new ChannelListFinished(); + case NotificationType.InitIvExpand: return new InitIvExpand(); + case NotificationType.InitServer: return new InitServer(); + case NotificationType.ChannelSubscribed: return new ChannelSubscribed(); + case NotificationType.ChannelUnsubscribed: return new ChannelUnsubscribed(); + case NotificationType.ClientChannelGroupChanged: return new ClientChannelGroupChanged(); + case NotificationType.ClientChatComposing: return new ClientChatComposing(); + case NotificationType.ClientNeededPermissions: return new ClientNeededPermissions(); + case NotificationType.ConnectionInfo: return new ConnectionInfo(); + case NotificationType.ConnectionInfoRequest: return new ConnectionInfoRequest(); + case NotificationType.FileInfo: return new FileInfoTs(); + case NotificationType.FileList: return new FileList(); + case NotificationType.FileListFinished: return new FileListFinished(); + case NotificationType.FileTransfer: return new FileTransfer(); + case NotificationType.ClientServerGroupAdded: return new ClientServerGroupAdded(); + case NotificationType.ServerGroupList: return new ServerGroupList(); + case NotificationType.ServerGroupsByClientId: return new ClientServerGroup(); + case NotificationType.StartDownload: return new FileDownload(); + case NotificationType.StartUpload: return new FileUpload(); + case NotificationType.FileTransferStatus: return new FileTransferStatus(); + case NotificationType.Unknown: + default: throw Util.UnhandledDefault(name); + } + } + } +} + diff --git a/TS3Client/Generated/Messages.tt b/TS3Client/Generated/Messages.tt index 70defdec..3deb42b6 100644 --- a/TS3Client/Generated/Messages.tt +++ b/TS3Client/Generated/Messages.tt @@ -22,7 +22,7 @@ namespace TS3Client.Messages using System; using System.Globalization; - using ClientUidT = System.String; + using UidT = System.String; using ClientDbIdT = System.UInt64; using ClientIdT = System.UInt16; using ChannelIdT = System.UInt64; @@ -77,7 +77,7 @@ namespace TS3Client.Messages case "DateTime": return $"{output} = Util.UnixTimeStart.AddSeconds(double.Parse({input}, CultureInfo.InvariantCulture));"; case "string": - case "ClientUidT": + case "UidT": return $"{output} = Ts3String.Unescape({input});"; case "Codec": case "HostMessageMode": diff --git a/TS3Client/Generated/Permissions.cs b/TS3Client/Generated/Permissions.cs new file mode 100644 index 00000000..27f5772e --- /dev/null +++ b/TS3Client/Generated/Permissions.cs @@ -0,0 +1,534 @@ +// 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 PermissionId : int + { + // ReSharper disable InconsistentNaming, UnusedMember.Global + undefined = -1, + unknown = 0, + b_serverinstance_help_view = 1, + b_serverinstance_version_view = 2, + b_serverinstance_info_view = 3, + b_serverinstance_virtualserver_list = 4, + b_serverinstance_binding_list = 5, + b_serverinstance_permission_list = 6, + b_serverinstance_permission_find = 7, + b_virtualserver_create = 8, + b_virtualserver_delete = 9, + b_virtualserver_start_any = 10, + b_virtualserver_stop_any = 11, + b_virtualserver_change_machine_id = 12, + b_virtualserver_change_template = 13, + b_serverquery_login = 14, + b_serverinstance_textmessage_send = 15, + b_serverinstance_log_view = 16, + b_serverinstance_log_add = 17, + b_serverinstance_stop = 18, + b_serverinstance_modify_settings = 19, + b_serverinstance_modify_querygroup = 20, + b_serverinstance_modify_templates = 21, + b_virtualserver_select = 22, + b_virtualserver_info_view = 23, + b_virtualserver_connectioninfo_view = 24, + b_virtualserver_channel_list = 25, + b_virtualserver_channel_search = 26, + b_virtualserver_client_list = 27, + b_virtualserver_client_search = 28, + b_virtualserver_client_dblist = 29, + b_virtualserver_client_dbsearch = 30, + b_virtualserver_client_dbinfo = 31, + b_virtualserver_permission_find = 32, + b_virtualserver_custom_search = 33, + b_virtualserver_start = 34, + b_virtualserver_stop = 35, + b_virtualserver_token_list = 36, + b_virtualserver_token_add = 37, + b_virtualserver_token_use = 38, + b_virtualserver_token_delete = 39, + b_virtualserver_log_view = 40, + b_virtualserver_log_add = 41, + b_virtualserver_join_ignore_password = 42, + b_virtualserver_notify_register = 43, + b_virtualserver_notify_unregister = 44, + b_virtualserver_snapshot_create = 45, + b_virtualserver_snapshot_deploy = 46, + b_virtualserver_permission_reset = 47, + b_virtualserver_modify_name = 48, + b_virtualserver_modify_welcomemessage = 49, + b_virtualserver_modify_maxclients = 50, + b_virtualserver_modify_reserved_slots = 51, + b_virtualserver_modify_password = 52, + b_virtualserver_modify_default_servergroup = 53, + b_virtualserver_modify_default_channelgroup = 54, + b_virtualserver_modify_default_channeladmingroup = 55, + b_virtualserver_modify_channel_forced_silence = 56, + b_virtualserver_modify_complain = 57, + b_virtualserver_modify_antiflood = 58, + b_virtualserver_modify_ft_settings = 59, + b_virtualserver_modify_ft_quotas = 60, + b_virtualserver_modify_hostmessage = 61, + b_virtualserver_modify_hostbanner = 62, + b_virtualserver_modify_hostbutton = 63, + b_virtualserver_modify_port = 64, + b_virtualserver_modify_autostart = 65, + b_virtualserver_modify_needed_identity_security_level = 66, + b_virtualserver_modify_priority_speaker_dimm_modificator = 67, + b_virtualserver_modify_log_settings = 68, + b_virtualserver_modify_min_client_version = 69, + b_virtualserver_modify_icon_id = 70, + b_virtualserver_modify_weblist = 71, + b_virtualserver_modify_codec_encryption_mode = 72, + b_virtualserver_modify_temporary_passwords = 73, + b_virtualserver_modify_temporary_passwords_own = 74, + b_virtualserver_modify_channel_temp_delete_delay_default = 75, + i_channel_min_depth = 76, + i_channel_max_depth = 77, + b_channel_group_inheritance_end = 78, + i_channel_permission_modify_power = 79, + i_channel_needed_permission_modify_power = 80, + b_channel_info_view = 81, + b_channel_create_child = 82, + b_channel_create_permanent = 83, + b_channel_create_semi_permanent = 84, + b_channel_create_temporary = 85, + b_channel_create_private = 86, + b_channel_create_with_topic = 87, + b_channel_create_with_description = 88, + b_channel_create_with_password = 89, + b_channel_create_modify_with_codec_speex8 = 90, + b_channel_create_modify_with_codec_speex16 = 91, + b_channel_create_modify_with_codec_speex32 = 92, + b_channel_create_modify_with_codec_celtmono48 = 93, + b_channel_create_modify_with_codec_opusvoice = 94, + b_channel_create_modify_with_codec_opusmusic = 95, + i_channel_create_modify_with_codec_maxquality = 96, + i_channel_create_modify_with_codec_latency_factor_min = 97, + b_channel_create_with_maxclients = 98, + b_channel_create_with_maxfamilyclients = 99, + b_channel_create_with_sortorder = 100, + b_channel_create_with_default = 101, + b_channel_create_with_needed_talk_power = 102, + b_channel_create_modify_with_force_password = 103, + i_channel_create_modify_with_temp_delete_delay = 104, + b_channel_modify_parent = 105, + b_channel_modify_make_default = 106, + b_channel_modify_make_permanent = 107, + b_channel_modify_make_semi_permanent = 108, + b_channel_modify_make_temporary = 109, + b_channel_modify_name = 110, + b_channel_modify_topic = 111, + b_channel_modify_description = 112, + b_channel_modify_password = 113, + b_channel_modify_codec = 114, + b_channel_modify_codec_quality = 115, + b_channel_modify_codec_latency_factor = 116, + b_channel_modify_maxclients = 117, + b_channel_modify_maxfamilyclients = 118, + b_channel_modify_sortorder = 119, + b_channel_modify_needed_talk_power = 120, + i_channel_modify_power = 121, + i_channel_needed_modify_power = 122, + b_channel_modify_make_codec_encrypted = 123, + b_channel_modify_temp_delete_delay = 124, + b_channel_delete_permanent = 125, + b_channel_delete_semi_permanent = 126, + b_channel_delete_temporary = 127, + b_channel_delete_flag_force = 128, + i_channel_delete_power = 129, + i_channel_needed_delete_power = 130, + b_channel_join_permanent = 131, + b_channel_join_semi_permanent = 132, + b_channel_join_temporary = 133, + b_channel_join_ignore_password = 134, + b_channel_join_ignore_maxclients = 135, + i_channel_join_power = 136, + i_channel_needed_join_power = 137, + i_channel_subscribe_power = 138, + i_channel_needed_subscribe_power = 139, + i_channel_description_view_power = 140, + i_channel_needed_description_view_power = 141, + i_icon_id = 142, + i_max_icon_filesize = 143, + b_icon_manage = 144, + b_group_is_permanent = 145, + i_group_auto_update_type = 146, + i_group_auto_update_max_value = 147, + i_group_sort_id = 148, + i_group_show_name_in_tree = 149, + b_virtualserver_servergroup_list = 150, + b_virtualserver_servergroup_permission_list = 151, + b_virtualserver_servergroup_client_list = 152, + b_virtualserver_channelgroup_list = 153, + b_virtualserver_channelgroup_permission_list = 154, + b_virtualserver_channelgroup_client_list = 155, + b_virtualserver_client_permission_list = 156, + b_virtualserver_channel_permission_list = 157, + b_virtualserver_channelclient_permission_list = 158, + b_virtualserver_servergroup_create = 159, + b_virtualserver_channelgroup_create = 160, + i_group_modify_power = 161, + i_group_needed_modify_power = 162, + i_group_member_add_power = 163, + i_group_needed_member_add_power = 164, + i_group_member_remove_power = 165, + i_group_needed_member_remove_power = 166, + i_permission_modify_power = 167, + b_permission_modify_power_ignore = 168, + b_virtualserver_servergroup_delete = 169, + b_virtualserver_channelgroup_delete = 170, + i_client_permission_modify_power = 171, + i_client_needed_permission_modify_power = 172, + i_client_max_clones_uid = 173, + i_client_max_idletime = 174, + i_client_max_avatar_filesize = 175, + i_client_max_channel_subscriptions = 176, + b_client_is_priority_speaker = 177, + b_client_skip_channelgroup_permissions = 178, + b_client_force_push_to_talk = 179, + b_client_ignore_bans = 180, + b_client_ignore_antiflood = 181, + b_client_issue_client_query_command = 182, + b_client_use_reserved_slot = 183, + b_client_use_channel_commander = 184, + b_client_request_talker = 185, + b_client_avatar_delete_other = 186, + b_client_is_sticky = 187, + b_client_ignore_sticky = 188, + b_client_info_view = 189, + b_client_permissionoverview_view = 190, + b_client_permissionoverview_own = 191, + b_client_remoteaddress_view = 192, + i_client_serverquery_view_power = 193, + i_client_needed_serverquery_view_power = 194, + b_client_custom_info_view = 195, + i_client_kick_from_server_power = 196, + i_client_needed_kick_from_server_power = 197, + i_client_kick_from_channel_power = 198, + i_client_needed_kick_from_channel_power = 199, + i_client_ban_power = 200, + i_client_needed_ban_power = 201, + i_client_move_power = 202, + i_client_needed_move_power = 203, + i_client_complain_power = 204, + i_client_needed_complain_power = 205, + b_client_complain_list = 206, + b_client_complain_delete_own = 207, + b_client_complain_delete = 208, + b_client_ban_list = 209, + b_client_ban_create = 210, + b_client_ban_delete_own = 211, + b_client_ban_delete = 212, + i_client_ban_max_bantime = 213, + i_client_private_textmessage_power = 214, + i_client_needed_private_textmessage_power = 215, + b_client_server_textmessage_send = 216, + b_client_channel_textmessage_send = 217, + b_client_offline_textmessage_send = 218, + i_client_talk_power = 219, + i_client_needed_talk_power = 220, + i_client_poke_power = 221, + i_client_needed_poke_power = 222, + b_client_set_flag_talker = 223, + i_client_whisper_power = 224, + i_client_needed_whisper_power = 225, + b_client_modify_description = 226, + b_client_modify_own_description = 227, + b_client_modify_dbproperties = 228, + b_client_delete_dbproperties = 229, + b_client_create_modify_serverquery_login = 230, + b_ft_ignore_password = 231, + b_ft_transfer_list = 232, + i_ft_file_upload_power = 233, + i_ft_needed_file_upload_power = 234, + i_ft_file_download_power = 235, + i_ft_needed_file_download_power = 236, + i_ft_file_delete_power = 237, + i_ft_needed_file_delete_power = 238, + i_ft_file_rename_power = 239, + i_ft_needed_file_rename_power = 240, + i_ft_file_browse_power = 241, + i_ft_needed_file_browse_power = 242, + i_ft_directory_create_power = 243, + i_ft_needed_directory_create_power = 244, + i_ft_quota_mb_download_per_client = 245, + i_ft_quota_mb_upload_per_client = 246, + // ReSharper enable InconsistentNaming, UnusedMember.Global + } + + public static class PerissionInfo + { + public static string Get(PermissionId permid) + { + switch (permid) + { + // ReSharper disable InconsistentNaming, UnusedMember.Global + case PermissionId.undefined: return "Undefined permission"; + case PermissionId.unknown : return "May occour on error returns with no associated permission"; + case PermissionId.b_serverinstance_help_view : return "Retrieve information about ServerQuery commands"; + case PermissionId.b_serverinstance_version_view : return "Retrieve global server version (including platform and build number)"; + case PermissionId.b_serverinstance_info_view : return "Retrieve global server information"; + case PermissionId.b_serverinstance_virtualserver_list : return "List virtual servers stored in the database"; + case PermissionId.b_serverinstance_binding_list : return "List active IP bindings on multi-homed machines"; + case PermissionId.b_serverinstance_permission_list : return "List permissions available available on the server instance"; + case PermissionId.b_serverinstance_permission_find : return "Search permission assignments by name or ID"; + case PermissionId.b_virtualserver_create : return "Create virtual servers"; + case PermissionId.b_virtualserver_delete : return "Delete virtual servers"; + case PermissionId.b_virtualserver_start_any : return "Start any virtual server in the server instance"; + case PermissionId.b_virtualserver_stop_any : return "Stop any virtual server in the server instance"; + case PermissionId.b_virtualserver_change_machine_id : return "Change a virtual servers machine ID"; + case PermissionId.b_virtualserver_change_template : return "Edit virtual server default template values"; + case PermissionId.b_serverquery_login : return "Login to ServerQuery"; + case PermissionId.b_serverinstance_textmessage_send : return "Send text messages to all virtual servers at once"; + case PermissionId.b_serverinstance_log_view : return "Retrieve global server log"; + case PermissionId.b_serverinstance_log_add : return "Write to global server log"; + case PermissionId.b_serverinstance_stop : return "Shutdown the server process"; + case PermissionId.b_serverinstance_modify_settings : return "Edit global settings"; + case PermissionId.b_serverinstance_modify_querygroup : return "Edit global ServerQuery groups"; + case PermissionId.b_serverinstance_modify_templates : return "Edit global template groups"; + case PermissionId.b_virtualserver_select : return "Select a virtual server"; + case PermissionId.b_virtualserver_info_view : return "Retrieve virtual server information"; + case PermissionId.b_virtualserver_connectioninfo_view : return "Retrieve virtual server connection information"; + case PermissionId.b_virtualserver_channel_list : return "List channels on a virtual server"; + case PermissionId.b_virtualserver_channel_search : return "Search for channels on a virtual server"; + case PermissionId.b_virtualserver_client_list : return "List clients online on a virtual server"; + case PermissionId.b_virtualserver_client_search : return "Search for clients online on a virtual server"; + case PermissionId.b_virtualserver_client_dblist : return "List client identities known by the virtual server"; + case PermissionId.b_virtualserver_client_dbsearch : return "Search for client identities known by the virtual server"; + case PermissionId.b_virtualserver_client_dbinfo : return "Retrieve client information"; + case PermissionId.b_virtualserver_permission_find : return "Find permissions"; + case PermissionId.b_virtualserver_custom_search : return "Find custom fields"; + case PermissionId.b_virtualserver_start : return "Start own virtual server"; + case PermissionId.b_virtualserver_stop : return "Stop own virtual server"; + case PermissionId.b_virtualserver_token_list : return "List privilege keys available"; + case PermissionId.b_virtualserver_token_add : return "Create new privilege keys"; + case PermissionId.b_virtualserver_token_use : return "Use a privilege keys to gain access to groups"; + case PermissionId.b_virtualserver_token_delete : return "Delete a privilege key"; + case PermissionId.b_virtualserver_log_view : return "Retrieve virtual server log"; + case PermissionId.b_virtualserver_log_add : return "Write to virtual server log"; + case PermissionId.b_virtualserver_join_ignore_password : return "Join virtual server ignoring its password"; + case PermissionId.b_virtualserver_notify_register : return "Register for server notifications"; + case PermissionId.b_virtualserver_notify_unregister : return "Unregister from server notifications"; + case PermissionId.b_virtualserver_snapshot_create : return "Create server snapshots"; + case PermissionId.b_virtualserver_snapshot_deploy : return "Deploy server snapshots"; + case PermissionId.b_virtualserver_permission_reset : return "Reset the server permission settings to default values"; + case PermissionId.b_virtualserver_modify_name : return "Modify server name"; + case PermissionId.b_virtualserver_modify_welcomemessage : return "Modify welcome message"; + case PermissionId.b_virtualserver_modify_maxclients : return "Modify servers max clients"; + case PermissionId.b_virtualserver_modify_reserved_slots : return "Modify reserved slots"; + case PermissionId.b_virtualserver_modify_password : return "Modify server password"; + case PermissionId.b_virtualserver_modify_default_servergroup : return "Modify default Server Group"; + case PermissionId.b_virtualserver_modify_default_channelgroup : return "Modify default Channel Group"; + case PermissionId.b_virtualserver_modify_default_channeladmingroup : return "Modify default Channel Admin Group"; + case PermissionId.b_virtualserver_modify_channel_forced_silence : return "Modify channel force silence value"; + case PermissionId.b_virtualserver_modify_complain : return "Modify individual complain settings"; + case PermissionId.b_virtualserver_modify_antiflood : return "Modify individual antiflood settings"; + case PermissionId.b_virtualserver_modify_ft_settings : return "Modify file transfer settings"; + case PermissionId.b_virtualserver_modify_ft_quotas : return "Modify file transfer quotas"; + case PermissionId.b_virtualserver_modify_hostmessage : return "Modify individual hostmessage settings"; + case PermissionId.b_virtualserver_modify_hostbanner : return "Modify individual hostbanner settings"; + case PermissionId.b_virtualserver_modify_hostbutton : return "Modify individual hostbutton settings"; + case PermissionId.b_virtualserver_modify_port : return "Modify server port"; + case PermissionId.b_virtualserver_modify_autostart : return "Modify server autostart"; + case PermissionId.b_virtualserver_modify_needed_identity_security_level : return "Modify required identity security level"; + case PermissionId.b_virtualserver_modify_priority_speaker_dimm_modificator : return "Modify priority speaker dimm modificator"; + case PermissionId.b_virtualserver_modify_log_settings : return "Modify log settings"; + case PermissionId.b_virtualserver_modify_min_client_version : return "Modify min client version"; + case PermissionId.b_virtualserver_modify_icon_id : return "Modify server icon"; + case PermissionId.b_virtualserver_modify_weblist : return "Modify web server list reporting settings"; + case PermissionId.b_virtualserver_modify_codec_encryption_mode : return "Modify codec encryption mode"; + case PermissionId.b_virtualserver_modify_temporary_passwords : return "Modify temporary serverpasswords"; + case PermissionId.b_virtualserver_modify_temporary_passwords_own : return "Modify own temporary serverpasswords"; + case PermissionId.b_virtualserver_modify_channel_temp_delete_delay_default : return "Modify default temporary channel delete delay"; + case PermissionId.i_channel_min_depth : return "Min channel creation depth in hierarchy"; + case PermissionId.i_channel_max_depth : return "Max channel creation depth in hierarchy"; + case PermissionId.b_channel_group_inheritance_end : return "Stop inheritance of channel group permissions"; + case PermissionId.i_channel_permission_modify_power : return "Modify channel permission power"; + case PermissionId.i_channel_needed_permission_modify_power : return "Needed modify channel permission power"; + case PermissionId.b_channel_info_view : return "Retrieve channel information"; + case PermissionId.b_channel_create_child : return "Create sub-channels"; + case PermissionId.b_channel_create_permanent : return "Create permanent channels"; + case PermissionId.b_channel_create_semi_permanent : return "Create semi-permanent channels"; + case PermissionId.b_channel_create_temporary : return "Create temporary channels"; + case PermissionId.b_channel_create_private : return "Create private channel"; + case PermissionId.b_channel_create_with_topic : return "Create channels with a topic"; + case PermissionId.b_channel_create_with_description : return "Create channels with a description"; + case PermissionId.b_channel_create_with_password : return "Create password protected channels"; + case PermissionId.b_channel_create_modify_with_codec_speex8 : return "Create channels using Speex Narrowband (8 kHz) codecs"; + case PermissionId.b_channel_create_modify_with_codec_speex16 : return "Create channels using Speex Wideband (16 kHz) codecs"; + case PermissionId.b_channel_create_modify_with_codec_speex32 : return "Create channels using Speex Ultra-Wideband (32 kHz) codecs"; + case PermissionId.b_channel_create_modify_with_codec_celtmono48 : return "Create channels using the CELT Mono (48 kHz) codec"; + case PermissionId.b_channel_create_modify_with_codec_opusvoice : return "Create channels using OPUS (voice) codec"; + case PermissionId.b_channel_create_modify_with_codec_opusmusic : return "Create channels using OPUS (music) codec"; + case PermissionId.i_channel_create_modify_with_codec_maxquality : return "Create channels with custom codec quality"; + case PermissionId.i_channel_create_modify_with_codec_latency_factor_min : return "Create channels with minimal custom codec latency factor"; + case PermissionId.b_channel_create_with_maxclients : return "Create channels with custom max clients"; + case PermissionId.b_channel_create_with_maxfamilyclients : return "Create channels with custom max family clients"; + case PermissionId.b_channel_create_with_sortorder : return "Create channels with custom sort order"; + case PermissionId.b_channel_create_with_default : return "Create default channels"; + case PermissionId.b_channel_create_with_needed_talk_power : return "Create channels with needed talk power"; + case PermissionId.b_channel_create_modify_with_force_password : return "Create new channels only with password"; + case PermissionId.i_channel_create_modify_with_temp_delete_delay : return "Max delete delay for temporary channels"; + case PermissionId.b_channel_modify_parent : return "Move channels"; + case PermissionId.b_channel_modify_make_default : return "Make channel default"; + case PermissionId.b_channel_modify_make_permanent : return "Make channel permanent"; + case PermissionId.b_channel_modify_make_semi_permanent : return "Make channel semi-permanent"; + case PermissionId.b_channel_modify_make_temporary : return "Make channel temporary"; + case PermissionId.b_channel_modify_name : return "Modify channel name"; + case PermissionId.b_channel_modify_topic : return "Modify channel topic"; + case PermissionId.b_channel_modify_description : return "Modify channel description"; + case PermissionId.b_channel_modify_password : return "Modify channel password"; + case PermissionId.b_channel_modify_codec : return "Modify channel codec"; + case PermissionId.b_channel_modify_codec_quality : return "Modify channel codec quality"; + case PermissionId.b_channel_modify_codec_latency_factor : return "Modify channel codec latency factor"; + case PermissionId.b_channel_modify_maxclients : return "Modify channels max clients"; + case PermissionId.b_channel_modify_maxfamilyclients : return "Modify channels max family clients"; + case PermissionId.b_channel_modify_sortorder : return "Modify channel sort order"; + case PermissionId.b_channel_modify_needed_talk_power : return "Change needed channel talk power"; + case PermissionId.i_channel_modify_power : return "Channel modify power"; + case PermissionId.i_channel_needed_modify_power : return "Needed channel modify power"; + case PermissionId.b_channel_modify_make_codec_encrypted : return "Make channel codec encrypted"; + case PermissionId.b_channel_modify_temp_delete_delay : return "Modify temporary channel delete delay"; + case PermissionId.b_channel_delete_permanent : return "Delete permanent channels"; + case PermissionId.b_channel_delete_semi_permanent : return "Delete semi-permanent channels"; + case PermissionId.b_channel_delete_temporary : return "Delete temporary channels"; + case PermissionId.b_channel_delete_flag_force : return "Force channel delete"; + case PermissionId.i_channel_delete_power : return "Delete channel power"; + case PermissionId.i_channel_needed_delete_power : return "Needed delete channel power"; + case PermissionId.b_channel_join_permanent : return "Join permanent channels"; + case PermissionId.b_channel_join_semi_permanent : return "Join semi-permanent channels"; + case PermissionId.b_channel_join_temporary : return "Join temporary channels"; + case PermissionId.b_channel_join_ignore_password : return "Join channel ignoring its password"; + case PermissionId.b_channel_join_ignore_maxclients : return "Ignore channels max clients limit"; + case PermissionId.i_channel_join_power : return "Channel join power"; + case PermissionId.i_channel_needed_join_power : return "Needed channel join power"; + case PermissionId.i_channel_subscribe_power : return "Channel subscribe power"; + case PermissionId.i_channel_needed_subscribe_power : return "Needed channel subscribe power"; + case PermissionId.i_channel_description_view_power : return "Channel description view power"; + case PermissionId.i_channel_needed_description_view_power : return "Needed channel needed description view power"; + case PermissionId.i_icon_id : return "Group icon identifier"; + case PermissionId.i_max_icon_filesize : return "Max icon filesize in bytes"; + case PermissionId.b_icon_manage : return "Enables icon management"; + case PermissionId.b_group_is_permanent : return "Group is permanent"; + case PermissionId.i_group_auto_update_type : return "Group auto-update type"; + case PermissionId.i_group_auto_update_max_value : return "Group auto-update max value"; + case PermissionId.i_group_sort_id : return "Group sort id"; + case PermissionId.i_group_show_name_in_tree : return "Show group name in tree depending on selected mode"; + case PermissionId.b_virtualserver_servergroup_list : return "List server groups"; + case PermissionId.b_virtualserver_servergroup_permission_list : return "List server group permissions"; + case PermissionId.b_virtualserver_servergroup_client_list : return "List clients from a server group"; + case PermissionId.b_virtualserver_channelgroup_list : return "List channel groups"; + case PermissionId.b_virtualserver_channelgroup_permission_list : return "List channel group permissions"; + case PermissionId.b_virtualserver_channelgroup_client_list : return "List clients from a channel group"; + case PermissionId.b_virtualserver_client_permission_list : return "List client permissions"; + case PermissionId.b_virtualserver_channel_permission_list : return "List channel permissions"; + case PermissionId.b_virtualserver_channelclient_permission_list : return "List channel client permissions"; + case PermissionId.b_virtualserver_servergroup_create : return "Create server groups"; + case PermissionId.b_virtualserver_channelgroup_create : return "Create channel groups"; + case PermissionId.i_group_modify_power : return "Group modify power"; + case PermissionId.i_group_needed_modify_power : return "Needed group modify power"; + case PermissionId.i_group_member_add_power : return "Group member add power"; + case PermissionId.i_group_needed_member_add_power : return "Needed group member add power"; + case PermissionId.i_group_member_remove_power : return "Group member delete power"; + case PermissionId.i_group_needed_member_remove_power : return "Needed group member delete power"; + case PermissionId.i_permission_modify_power : return "Permission modify power"; + case PermissionId.b_permission_modify_power_ignore : return "Ignore needed permission modify power"; + case PermissionId.b_virtualserver_servergroup_delete : return "Delete server groups"; + case PermissionId.b_virtualserver_channelgroup_delete : return "Delete channel groups"; + case PermissionId.i_client_permission_modify_power : return "Client permission modify power"; + case PermissionId.i_client_needed_permission_modify_power : return "Needed client permission modify power"; + case PermissionId.i_client_max_clones_uid : return "Max additional connections per client identity"; + case PermissionId.i_client_max_idletime : return "Max idle time in seconds"; + case PermissionId.i_client_max_avatar_filesize : return "Max avatar filesize in bytes"; + case PermissionId.i_client_max_channel_subscriptions : return "Max channel subscriptions"; + case PermissionId.b_client_is_priority_speaker : return "Client is priority speaker"; + case PermissionId.b_client_skip_channelgroup_permissions : return "Ignore channel group permissions"; + case PermissionId.b_client_force_push_to_talk : return "Force Push-To-Talk capture mode"; + case PermissionId.b_client_ignore_bans : return "Ignore bans"; + case PermissionId.b_client_ignore_antiflood : return "Ignore antiflood measurements"; + case PermissionId.b_client_issue_client_query_command : return "Issue query commands from client"; + case PermissionId.b_client_use_reserved_slot : return "Use an reserved slot"; + case PermissionId.b_client_use_channel_commander : return "Use channel commander"; + case PermissionId.b_client_request_talker : return "Allow to request talk power"; + case PermissionId.b_client_avatar_delete_other : return "Allow deletion of avatars from other clients"; + case PermissionId.b_client_is_sticky : return "Client will be sticked to current channel"; + case PermissionId.b_client_ignore_sticky : return "Client ignores sticky flag"; + case PermissionId.b_client_info_view : return "Retrieve client information"; + case PermissionId.b_client_permissionoverview_view : return "Retrieve client permissions overview"; + case PermissionId.b_client_permissionoverview_own : return "Retrieve clients own permissions overview"; + case PermissionId.b_client_remoteaddress_view : return "View client IP address and port"; + case PermissionId.i_client_serverquery_view_power : return "ServerQuery view power"; + case PermissionId.i_client_needed_serverquery_view_power : return "Needed ServerQuery view power"; + case PermissionId.b_client_custom_info_view : return "View custom fields"; + case PermissionId.i_client_kick_from_server_power : return "Client kick power from server"; + case PermissionId.i_client_needed_kick_from_server_power : return "Needed client kick power from server"; + case PermissionId.i_client_kick_from_channel_power : return "Client kick power from channel"; + case PermissionId.i_client_needed_kick_from_channel_power : return "Needed client kick power from channel"; + case PermissionId.i_client_ban_power : return "Client ban power"; + case PermissionId.i_client_needed_ban_power : return "Needed client ban power"; + case PermissionId.i_client_move_power : return "Client move power"; + case PermissionId.i_client_needed_move_power : return "Needed client move power"; + case PermissionId.i_client_complain_power : return "Complain power"; + case PermissionId.i_client_needed_complain_power : return "Needed complain power"; + case PermissionId.b_client_complain_list : return "Show complain list"; + case PermissionId.b_client_complain_delete_own : return "Delete own complains"; + case PermissionId.b_client_complain_delete : return "Delete complains"; + case PermissionId.b_client_ban_list : return "Show banlist"; + case PermissionId.b_client_ban_create : return "Add a ban"; + case PermissionId.b_client_ban_delete_own : return "Delete own bans"; + case PermissionId.b_client_ban_delete : return "Delete bans"; + case PermissionId.i_client_ban_max_bantime : return "Max bantime"; + case PermissionId.i_client_private_textmessage_power : return "Client private message power"; + case PermissionId.i_client_needed_private_textmessage_power : return "Needed client private message power"; + case PermissionId.b_client_server_textmessage_send : return "Send text messages to virtual server"; + case PermissionId.b_client_channel_textmessage_send : return "Send text messages to channel"; + case PermissionId.b_client_offline_textmessage_send : return "Send offline messages to clients"; + case PermissionId.i_client_talk_power : return "Client talk power"; + case PermissionId.i_client_needed_talk_power : return "Needed client talk power"; + case PermissionId.i_client_poke_power : return "Client poke power"; + case PermissionId.i_client_needed_poke_power : return "Needed client poke power"; + case PermissionId.b_client_set_flag_talker : return "Set the talker flag for clients and allow them to speak"; + case PermissionId.i_client_whisper_power : return "Client whisper power"; + case PermissionId.i_client_needed_whisper_power : return "Client needed whisper power"; + case PermissionId.b_client_modify_description : return "Edit a clients description"; + case PermissionId.b_client_modify_own_description : return "Allow client to edit own description"; + case PermissionId.b_client_modify_dbproperties : return "Edit a clients properties in the database"; + case PermissionId.b_client_delete_dbproperties : return "Delete a clients properties in the database"; + case PermissionId.b_client_create_modify_serverquery_login : return "Create or modify own ServerQuery account"; + case PermissionId.b_ft_ignore_password : return "Browse files without channel password"; + case PermissionId.b_ft_transfer_list : return "Retrieve list of running filetransfers"; + case PermissionId.i_ft_file_upload_power : return "File upload power"; + case PermissionId.i_ft_needed_file_upload_power : return "Needed file upload power"; + case PermissionId.i_ft_file_download_power : return "File download power"; + case PermissionId.i_ft_needed_file_download_power : return "Needed file download power"; + case PermissionId.i_ft_file_delete_power : return "File delete power"; + case PermissionId.i_ft_needed_file_delete_power : return "Needed file delete power"; + case PermissionId.i_ft_file_rename_power : return "File rename power"; + case PermissionId.i_ft_needed_file_rename_power : return "Needed file rename power"; + case PermissionId.i_ft_file_browse_power : return "File browse power"; + case PermissionId.i_ft_needed_file_browse_power : return "Needed file browse power"; + case PermissionId.i_ft_directory_create_power : return "Create directory power"; + case PermissionId.i_ft_needed_directory_create_power : return "Needed create directory power"; + case PermissionId.i_ft_quota_mb_download_per_client : return "Download quota per client in MByte"; + case PermissionId.i_ft_quota_mb_upload_per_client : return "Upload quota per client in MByte"; + default: throw Util.UnhandledDefault(permid); + // ReSharper enable InconsistentNaming, UnusedMember.Global + } + } + } +} \ No newline at end of file diff --git a/TS3Client/Generated/Permissions.tt b/TS3Client/Generated/Permissions.tt index 5f2a2bf5..2d4d2b19 100644 --- a/TS3Client/Generated/Permissions.tt +++ b/TS3Client/Generated/Permissions.tt @@ -1,3 +1,12 @@ +// 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="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ assembly name="Microsoft.VisualBasic" #> @@ -5,7 +14,6 @@ <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="Microsoft.VisualBasic.FileIO" #> <#@ output extension=".cs" #> - <# string declFilePath = Host.ResolvePath("../Declarations/Permissions.csv"); var data = new List(); @@ -17,7 +25,6 @@ using (TextFieldParser parser = new TextFieldParser(declFilePath)) data.Add(parser.ReadFields()); } #> - namespace TS3Client { using Helper; diff --git a/TS3Client/Generated/Versions.cs b/TS3Client/Generated/Versions.cs new file mode 100644 index 00000000..e27361a9 --- /dev/null +++ b/TS3Client/Generated/Versions.cs @@ -0,0 +1,81 @@ +// 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.Full +{ + using System; + + /// + /// Describes a triple of version, plattform and a crytographical signature (usually distributed by "TeamSpeak Systems"). + /// Each triple has to match and is not interchangeable with other triple parts. + /// + public class VersionSign + { + private static readonly string[] Plattforms = { null, "Windows", "Linux", "OS X", "Android", "iOS" }; + + public string Sign { get; } + public string Name { get; } + public ClientPlattform Plattform { get; } + public string PlattformName { get; } + + public VersionSign(string name, ClientPlattform plattform, string sign) + { + if (plattform == ClientPlattform.Other) + throw new ArgumentException(nameof(plattform)); + Name = name; + Sign = sign; + Plattform = plattform; + PlattformName = Plattforms[(int)plattform]; + } + + public VersionSign(string name, string plattform, string sign) + { + Name = name; + Sign = sign; + Plattform = ClientPlattform.Other; + PlattformName = plattform; + } + + // Many ids implemented from here: https://r4p3.net/threads/client-builds.499/ + + + public static readonly VersionSign VER_WIN_3_0_11 = new VersionSign("3.0.11 [Build: 1375083581]", ClientPlattform.Windows, "54wPDkfv0kT56UE0lv/LFkFJObH+Q4Irmo4Brfz1EcvjVhj8hJ+RCHcVTZsdKU2XvVvh+VLJpURulEHsAOsyBw=="); + public static readonly VersionSign VER_WIN_3_0_19_3 = new VersionSign("3.0.19.3 [Build: 1466672534]", ClientPlattform.Windows, "a1OYzvM18mrmfUQBUgxYBxYz2DUU6y5k3/mEL6FurzU0y97Bd1FL7+PRpcHyPkg4R+kKAFZ1nhyzbgkGphDWDg=="); + public static readonly VersionSign VER_WIN_3_0_19_4 = new VersionSign("3.0.19.4 [Build: 1468491418]", ClientPlattform.Windows, "ldWL49uDKC3N9uxdgWRMTOzUuiG1nBqUiOa+Nal5HvdxJiN4fsTnmmPo5tvglN7WqoVoFfuuKuYq1LzodtEtCg=="); + public static readonly VersionSign VER_LIN_3_0_19_4 = new VersionSign("3.0.19.4 [Build: 1468491418]", ClientPlattform.Linux, "jvhhk75EV3nCGeewx4Y5zZmiZSN07q5ByKZ9Wlmg85aAbnw7c1jKq5/Iq0zY6dfGwCEwuKod0I5lQcVLf2NTCg=="); + public static readonly VersionSign VER_OSX_3_0_19_4 = new VersionSign("3.0.19.4 [Build: 1468491418]", ClientPlattform.Osx, "Pvcizdk3HRQMzTLt7goUYBmmS5nbAS1g2E6HIypLU+9eXTqGTBLim0UUtKc0s867TFHbK91GroDrTtv0aMUGAw=="); + public static readonly VersionSign VER_WIN_3_0_20 = new VersionSign("3.0.20 [Build: 1465542546]", ClientPlattform.Windows, "vDK31sOwOvDpTXgqAJzmR1NzeUeSDG9dLMgIz5LCX+KpDSVD/qU60mzScz9tuc9AsLyrL8DxHpDDO3eQD+hYCA=="); + public static readonly VersionSign VER_AND_3_0_23 = new VersionSign("3.0.23 [Build: 1463662487]", ClientPlattform.Android, "RN+cwFI+jSHJEhggucIuUyEteWNVFy4iw0QDp3qn2UzfopypFVE9BPZqJjBUGeoCN7Q/SfYL4RNIRzJEQaZUCA=="); + public static readonly VersionSign VER_WIN_3_1 = new VersionSign("3.1 [Build: 1471417187]", ClientPlattform.Windows, "Vr9F7kbVorcrkV5b/Iw+feH9qmDGvfsW8tpa737zhc1fDpK5uaEo6M5l2DzgaGqqOr3GKl5A7PF9Sj6eTM26Aw=="); + public static readonly VersionSign VER_WIN_3_1_6 = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Windows, "73fB82Jt1lmIRHKBFaE8h1JKPGFbnt6/yrXOHwTS93Oo7Adx1usY5TzNg+8BKy9nmmA2FEBnRmz5cRfXDghnBA=="); + public static readonly VersionSign VER_LIN_3_1_6 = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Linux, "o+l92HKfiUF+THx2rBsuNjj/S1QpxG1fd5o3Q7qtWxkviR3LI3JeWyc26eTmoQoMTgI3jjHV7dCwHsK1BVu6Aw=="); + public static readonly VersionSign VER_WIN_3_1_7 = new VersionSign("3.1.7 [Build: 1507896705]", ClientPlattform.Windows, "Iks42KIMcmFv5vzPLhziqahcPD2AHygkepr8xHNCbqx+li5n7Htbq5LE9e1YYhRhLoS4e2HqOpKkt+/+LC8EDA=="); + public static readonly VersionSign VER_OSX_3_1_7 = new VersionSign("3.1.7 [Build: 1507896705]", ClientPlattform.Osx, "iM0IyUpaH9ak0gTtrHlRT0VGZa4rC51iZwSFwifK6iFqciSba/WkIQDWk9GUJN0OCCfatoc/fmlq8TPBnE5XCA=="); + public static readonly VersionSign VER_WIN_3_X_X = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Windows, "DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="); + public static readonly VersionSign VER_AND_3_X_X = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Android, "AWb948BY32Z7bpIyoAlQguSmxOGcmjESPceQe1DpW5IZ4+AW1KfTk2VUIYNfUPsxReDJMCtlhVKslzhR2lf0AA=="); + public static readonly VersionSign VER_IOS_3_X_X = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Ios, "XrAf+Buq6Eb0ehEW/niFp06YX+nGGOS0Ke4MoUBzn+cX9q6G5C0A/d5XtgcNMe8r9jJgV/adIYVpsGS3pVlSAA=="); + } + + public enum ClientPlattform + { + Other = 0, + Windows, + Linux, + Osx, + Android, + Ios, + } +} diff --git a/TS3Client/Generated/Versions.tt b/TS3Client/Generated/Versions.tt new file mode 100644 index 00000000..9d69812b --- /dev/null +++ b/TS3Client/Generated/Versions.tt @@ -0,0 +1,99 @@ +// 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="false" hostspecific="true" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ assembly name="Microsoft.VisualBasic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Microsoft.VisualBasic.FileIO" #> +<#@ import namespace="System.Text.RegularExpressions" #> +<#@ output extension=".cs" #> +<# +string declFilePath = Host.ResolvePath("../Declarations/Versions.csv"); +var data = new List(); +using (TextFieldParser parser = new TextFieldParser(declFilePath)) +{ + parser.TextFieldType = FieldType.Delimited; + parser.SetDelimiters(","); + while (!parser.EndOfData) + data.Add(parser.ReadFields()); +} +var dict = new Dictionary { + { "Windows", new Ver { plat = "WIN", enu = "Windows" } }, + { "Linux", new Ver { plat = "LIN", enu = "Linux" } }, + { "OS X", new Ver { plat = "OSX", enu = "Osx" } }, + { "Android", new Ver { plat = "AND", enu = "Android" } }, + { "iOS", new Ver { plat = "IOS", enu = "Ios" } }, +}; +var reg = new Regex(@"^(\d(?:\.[\d?]+)+)"); +string BuildToFld(string build) +{ + var m = reg.Match(build); + return string.Join("_", m.Groups[1].Value.Split('.').Select(x => x.Replace("?", "X"))); +} +#> + +namespace TS3Client.Full +{ + using System; + + /// + /// Describes a triple of version, plattform and a crytographical signature (usually distributed by "TeamSpeak Systems"). + /// Each triple has to match and is not interchangeable with other triple parts. + /// + public class VersionSign + { + private static readonly string[] Plattforms = { null, "Windows", "Linux", "OS X", "Android", "iOS" }; + + public string Sign { get; } + public string Name { get; } + public ClientPlattform Plattform { get; } + public string PlattformName { get; } + + public VersionSign(string name, ClientPlattform plattform, string sign) + { + if (plattform == ClientPlattform.Other) + throw new ArgumentException(nameof(plattform)); + Name = name; + Sign = sign; + Plattform = plattform; + PlattformName = Plattforms[(int)plattform]; + } + + public VersionSign(string name, string plattform, string sign) + { + Name = name; + Sign = sign; + Plattform = ClientPlattform.Other; + PlattformName = plattform; + } + + // Many ids implemented from here: https://r4p3.net/threads/client-builds.499/ + + <# foreach (var line in data.Skip(1)) { var ver = dict[line[1]]; #> + public static readonly VersionSign VER_<#= ver.plat #>_<#= BuildToFld(line[0]) #> = new VersionSign("<#= line[0] #>", ClientPlattform.<#= ver.enu #>, "<#= line[2] #>");<# } #> + } + + public enum ClientPlattform + { + Other = 0, + Windows, + Linux, + Osx, + Android, + Ios, + } +} +<#+ class Ver +{ + public string plat; + public string enu; +} +#> \ No newline at end of file diff --git a/TS3Client/OwnEnums.cs b/TS3Client/OwnEnums.cs index 23a07bf5..c9f2da03 100644 --- a/TS3Client/OwnEnums.cs +++ b/TS3Client/OwnEnums.cs @@ -144,406 +144,4 @@ public enum GroupWhisperTarget : byte Subchannels, } // ReSharper enable UnusedMember.Global - - // Source: http://forum.teamspeak.com/threads/102276-Server-query-error-id-list - public enum Ts3ErrorCode : ushort - { - // ReSharper disable InconsistentNaming, UnusedMember.Global - /// (normal) unknown error code - ok = 0x0000, - /// (normal) undefined error - undefined = 0x0001, - /// (normal) not implemented - not_implemented = 0x0002, - /// (normal) - ok_no_update = 0x0003, - /// (normal) - dont_notify = 0x0004, - /// (normal) library time limit reached - lib_time_limit_reached = 0x0005, - /// (normal) command not found - command_not_found = 0x0100, - /// (normal) unable to bind network port - unable_to_bind_network_port = 0x0101, - /// (normal) no network port available - no_network_port_available = 0x0102, - /// (normal) invalid clientID - client_invalid_id = 0x0200, - /// (normal) nickname is already in use - client_nickname_inuse = 0x0201, - /// (normal) invalid error code - client_invalid_error_code = 0x0202, - /// (normal) max clients protocol limit reached - client_protocol_limit_reached = 0x0203, - /// (normal) invalid client type - client_invalid_type = 0x0204, - /// (normal) already subscribed - client_already_subscribed = 0x0205, - /// (normal) not logged in - client_not_logged_in = 0x0206, - /// (normal) could not validate client identity - client_could_not_validate_identity = 0x0207, - /// (rare) invalid loginname or password - client_invalid_password = 0x0208, - /// (rare) too many clones already connected - client_too_many_clones_connected = 0x0209, - /// (normal) client version outdated, please update - client_version_outdated = 0x020a, - /// (rare) client is online - client_is_online = 0x020b, - /// (normal) client is flooding - client_is_flooding = 0x020c, - /// (normal) client is modified - client_hacked = 0x020d, - /// (normal) can not verify client at this moment - client_cannot_verify_now = 0x020e, - /// (normal) client is not permitted to log in - client_login_not_permitted = 0x020f, - /// (normal) client is not subscribed to the channel - client_not_subscribed = 0x0210, - /// (normal) invalid channelID - channel_invalid_id = 0x0300, - /// (normal) max channels protocol limit reached - channel_protocol_limit_reached = 0x0301, - /// (normal) already member of channel - channel_already_in = 0x0302, - /// (normal) channel name is already in use - channel_name_inuse = 0x0303, - /// (normal) channel not empty - channel_not_empty = 0x0304, - /// (normal) can not delete default channel - channel_can_not_delete_default = 0x0305, - /// (normal) default channel requires permanent - channel_default_require_permanent = 0x0306, - /// (normal) invalid channel flags - channel_invalid_flags = 0x0307, - /// (normal) permanent channel can not be child of non permanent channel - channel_parent_not_permanent = 0x0308, - /// (normal) channel maxclient reached - channel_maxclients_reached = 0x0309, - /// (normal) channel maxfamily reached - channel_maxfamily_reached = 0x030a, - /// (normal) invalid channel order - channel_invalid_order = 0x030b, - /// (normal) channel does not support filetransfers - channel_no_filetransfer_supported = 0x030c, - /// (normal) invalid channel password - channel_invalid_password = 0x030d, - /// (rare) channel is private channel - channel_is_private_channel = 0x030e, - /// (normal) invalid security hash supplied by client - channel_invalid_security_hash = 0x030f, - /// (normal) invalid serverID - server_invalid_id = 0x0400, - /// (normal) server is running - server_running = 0x0401, - /// (normal) server is shutting down - server_is_shutting_down = 0x0402, - /// (normal) server maxclient reached - server_maxclients_reached = 0x0403, - /// (normal) invalid server password - server_invalid_password = 0x0404, - /// (rare) deployment active - server_deployment_active = 0x0405, - /// (rare) unable to stop own server in your connection class - server_unable_to_stop_own_server = 0x0406, - /// (normal) server is virtual - server_is_virtual = 0x0407, - /// (rare) server wrong machineID - server_wrong_machineid = 0x0408, - /// (normal) server is not running - server_is_not_running = 0x0409, - /// (normal) server is booting up - server_is_booting = 0x040a, - /// (normal) server got an invalid status for this operation - server_status_invalid = 0x040b, - /// (rare) server modal quit - server_modal_quit = 0x040c, - /// (normal) server version is too old for command - server_version_outdated = 0x040d, - /// (rare) database error - database = 0x0500, - /// (rare) database empty result set - database_empty_result = 0x0501, - /// (rare) database duplicate entry - database_duplicate_entry = 0x0502, - /// (rare) database no modifications - database_no_modifications = 0x0503, - /// (rare) database invalid constraint - database_constraint = 0x0504, - /// (rare) database reinvoke command - database_reinvoke = 0x0505, - /// (normal) invalid quote - parameter_quote = 0x0600, - /// (normal) invalid parameter count - parameter_invalid_count = 0x0601, - /// (normal) invalid parameter - parameter_invalid = 0x0602, - /// (normal) parameter not found - parameter_not_found = 0x0603, - /// (normal) convert error - parameter_convert = 0x0604, - /// (normal) invalid parameter size - parameter_invalid_size = 0x0605, - /// (normal) missing required parameter - parameter_missing = 0x0606, - /// (normal) invalid checksum - parameter_checksum = 0x0607, - /// (normal) virtual server got a critical error - vs_critical = 0x0700, - /// (normal) Connection lost - connection_lost = 0x0701, - /// (normal) not connected - not_connected = 0x0702, - /// (normal) no cached connection info - no_cached_connection_info = 0x0703, - /// (normal) currently not possible - currently_not_possible = 0x0704, - /// (normal) failed connection initialization - failed_connection_initialisation = 0x0705, - /// (normal) could not resolve hostname - could_not_resolve_hostname = 0x0706, - /// (normal) invalid server connection handler ID - invalid_server_connection_handler_id = 0x0707, - /// (normal) could not initialize Input Manager - could_not_initialise_input_manager = 0x0708, - /// (normal) client library not initialized - clientlibrary_not_initialised = 0x0709, - /// (normal) server library not initialized - serverlibrary_not_initialised = 0x070a, - /// (normal) too many whisper targets - whisper_too_many_targets = 0x070b, - /// (normal) no whisper targets found - whisper_no_targets = 0x070c, - /// (rare) invalid file name - file_invalid_name = 0x0800, - /// (rare) invalid file permissions - file_invalid_permissions = 0x0801, - /// (rare) file already exists - file_already_exists = 0x0802, - /// (rare) file not found - file_not_found = 0x0803, - /// (rare) file input/output error - file_io_error = 0x0804, - /// (rare) invalid file transfer ID - file_invalid_transfer_id = 0x0805, - /// (rare) invalid file path - file_invalid_path = 0x0806, - /// (rare) no files available - file_no_files_available = 0x0807, - /// (rare) overwrite excludes resume - file_overwrite_excludes_resume = 0x0808, - /// (rare) invalid file size - file_invalid_size = 0x0809, - /// (rare) file already in use - file_already_in_use = 0x080a, - /// (rare) could not open file transfer connection - file_could_not_open_connection = 0x080b, - /// (rare) no space left on device (disk full?) - file_no_space_left_on_device = 0x080c, - /// (rare) file exceeds file system's maximum file size - file_exceeds_file_system_maximum_size = 0x080d, - /// (rare) file transfer connection timeout - file_transfer_connection_timeout = 0x080e, - /// (rare) lost file transfer connection - file_connection_lost = 0x080f, - /// (rare) file exceeds supplied file size - file_exceeds_supplied_size = 0x0810, - /// (rare) file transfer complete - file_transfer_complete = 0x0811, - /// (rare) file transfer canceled - file_transfer_canceled = 0x0812, - /// (rare) file transfer interrupted - file_transfer_interrupted = 0x0813, - /// (rare) file transfer server quota exceeded - file_transfer_server_quota_exceeded = 0x0814, - /// (rare) file transfer client quota exceeded - file_transfer_client_quota_exceeded = 0x0815, - /// (rare) file transfer reset - file_transfer_reset = 0x0816, - /// (rare) file transfer limit reached - file_transfer_limit_reached = 0x0817, - /// (normal) preprocessor disabled - sound_preprocessor_disabled = 0x0900, - /// (normal) internal preprocessor - sound_internal_preprocessor = 0x0901, - /// (normal) internal encoder - sound_internal_encoder = 0x0902, - /// (normal) internal playback - sound_internal_playback = 0x0903, - /// (normal) no capture device available - sound_no_capture_device_available = 0x0904, - /// (normal) no playback device available - sound_no_playback_device_available = 0x0905, - /// (normal) could not open capture device - sound_could_not_open_capture_device = 0x0906, - /// (normal) could not open playback device - sound_could_not_open_playback_device = 0x0907, - /// (normal) ServerConnectionHandler has a device registered - sound_handler_has_device = 0x0908, - /// (normal) invalid capture device - sound_invalid_capture_device = 0x0909, - /// (normal) invalid clayback device - sound_invalid_playback_device = 0x090a, - /// (normal) invalid wave file - sound_invalid_wave = 0x090b, - /// (normal) wave file type not supported - sound_unsupported_wave = 0x090c, - /// (normal) could not open wave file - sound_open_wave = 0x090d, - /// (normal) internal capture - sound_internal_capture = 0x090e, - /// (normal) device still in use - sound_device_in_use = 0x090f, - /// (normal) device already registerred - sound_device_already_registerred = 0x0910, - /// (normal) device not registered/known - sound_unknown_device = 0x0911, - /// (normal) unsupported frequency - sound_unsupported_frequency = 0x0912, - /// (normal) invalid channel count - sound_invalid_channel_count = 0x0913, - /// (normal) read error in wave - sound_read_wave = 0x0914, - /// (normal) sound need more data - sound_need_more_data = 0x0915, - /// (normal) sound device was busy - sound_device_busy = 0x0916, - /// (normal) there is no sound data for this period - sound_no_data = 0x0917, - /// (normal) Channelmask set bits count (speakers) is not the same as (count) - sound_channel_mask_mismatch = 0x0918, - /// (rare) invalid group ID - permission_invalid_group_id = 0x0a00, - /// (rare) duplicate entry - permission_duplicate_entry = 0x0a01, - /// (rare) invalid permission ID - permission_invalid_perm_id = 0x0a02, - /// (rare) empty result set - permission_empty_result = 0x0a03, - /// (rare) access to default group is forbidden - permission_default_group_forbidden = 0x0a04, - /// (rare) invalid size - permission_invalid_size = 0x0a05, - /// (rare) invalid value - permission_invalid_value = 0x0a06, - /// (rare) group is not empty - permissions_group_not_empty = 0x0a07, - /// (normal) insufficient client permissions - permissions_client_insufficient = 0x0a08, - /// (rare) insufficient group modify power - permissions_insufficient_group_power = 0x0a09, - /// (rare) insufficient permission modify power - permissions_insufficient_permission_power = 0x0a0a, - /// (rare) template group is currently used - permission_template_group_is_used = 0x0a0b, - /// (normal) permission error - permissions = 0x0a0c, - /// (normal) virtualserver limit reached - accounting_virtualserver_limit_reached = 0x0b00, - /// (normal) max slot limit reached - accounting_slot_limit_reached = 0x0b01, - /// (normal) license file not found - accounting_license_file_not_found = 0x0b02, - /// (normal) license date not ok - accounting_license_date_not_ok = 0x0b03, - /// (normal) unable to connect to accounting server - accounting_unable_to_connect_to_server = 0x0b04, - /// (normal) unknown accounting error - accounting_unknown_error = 0x0b05, - /// (normal) accounting server error - accounting_server_error = 0x0b06, - /// (normal) instance limit reached - accounting_instance_limit_reached = 0x0b07, - /// (normal) instance check error - accounting_instance_check_error = 0x0b08, - /// (normal) license file invalid - accounting_license_file_invalid = 0x0b09, - /// (normal) virtualserver is running elsewhere - accounting_running_elsewhere = 0x0b0a, - /// (normal) virtualserver running in same instance already - accounting_instance_duplicated = 0x0b0b, - /// (normal) virtualserver already started - accounting_already_started = 0x0b0c, - /// (normal) virtualserver not started - accounting_not_started = 0x0b0d, - /// (normal) - accounting_to_many_starts = 0x0b0e, - /// (rare) invalid message id - message_invalid_id = 0x0c00, - /// (rare) invalid ban id - ban_invalid_id = 0x0d00, - /// (rare) connection failed, you are banned - connect_failed_banned = 0x0d01, - /// (rare) rename failed, new name is banned - rename_failed_banned = 0x0d02, - /// (rare) flood ban - ban_flooding = 0x0d03, - /// (rare) unable to initialize tts - tts_unable_to_initialize = 0x0e00, - /// (rare) invalid privilege key - privilege_key_invalid = 0x0f00, - /// (rare) - voip_pjsua = 0x1000, - /// (rare) - voip_already_initialized = 0x1001, - /// (rare) - voip_too_many_accounts = 0x1002, - /// (rare) - voip_invalid_account = 0x1003, - /// (rare) - voip_internal_error = 0x1004, - /// (rare) - voip_invalid_connectionId = 0x1005, - /// (rare) - voip_cannot_answer_initiated_call = 0x1006, - /// (rare) - voip_not_initialized = 0x1007, - /// (normal) invalid password - provisioning_invalid_password = 0x1100, - /// (normal) invalid request - provisioning_invalid_request = 0x1101, - /// (normal) no(more) slots available - provisioning_no_slots_available = 0x1102, - /// (normal) pool missing - provisioning_pool_missing = 0x1103, - /// (normal) pool unknown - provisioning_pool_unknown = 0x1104, - /// (normal) unknown ip location(perhaps LAN ip?) - provisioning_unknown_ip_location = 0x1105, - /// (normal) internal error(tried exceeded) - provisioning_internal_tries_exceeded = 0x1106, - /// (normal) too many slots requested - provisioning_too_many_slots_requested = 0x1107, - /// (normal) too many reserved - provisioning_too_many_reserved = 0x1108, - /// (normal) could not connect to provisioning server - provisioning_could_not_connect = 0x1109, - /// (normal) authentication server not connected - provisioning_auth_server_not_connected = 0x1110, - /// (normal) authentication data too large - provisioning_auth_data_too_large = 0x1111, - /// (normal) already initialized - provisioning_already_initialized = 0x1112, - /// (normal) not initialized - provisioning_not_initialized = 0x1113, - /// (normal) already connecting - provisioning_connecting = 0x1114, - /// (normal) already connected - provisioning_already_connected = 0x1115, - /// (normal) - provisioning_not_connected = 0x1116, - /// (normal) io_error - provisioning_io_error = 0x1117, - /// (normal) - provisioning_invalid_timeout = 0x1118, - /// (normal) - provisioning_ts3server_not_found = 0x1119, - /// (normal) unknown permissionID - provisioning_no_permission = 0x111A, - - /// For own custom errors - custom_error = 0xFFFF, - // ReSharper enable InconsistentNaming, UnusedMember.Global - } } diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index 33da26d9..5098d114 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -12,8 +12,9 @@ v4.6 512 7 - true + + true @@ -39,6 +40,7 @@ + true @@ -74,11 +76,21 @@ + + True + True + Errors.tt + True True Permissions.tt + + True + True + Versions.tt + @@ -100,7 +112,6 @@ - @@ -127,6 +138,7 @@ + @@ -140,6 +152,10 @@ + + TextTemplatingFileGenerator + Errors.cs + TextTemplatingFileGenerator Messages.cs @@ -148,6 +164,10 @@ TextTemplatingFileGenerator Permissions.cs + + TextTemplatingFileGenerator + Versions.cs + From dcbf4bd558a597e01bf9b2577129414da19afdf4 Mon Sep 17 00:00:00 2001 From: Splamy Date: Fri, 5 Jan 2018 13:58:33 +0100 Subject: [PATCH 28/48] Removed debug line Fixes #177 --- TS3AudioBot/Audio/FfmpegProducer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs index 1e5b4cbe..1b6b08ce 100644 --- a/TS3AudioBot/Audio/FfmpegProducer.cs +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -78,8 +78,7 @@ public int Read(byte[] buffer, int offset, int length, out Meta meta) { if (ffmpegProcess == null) return 0; - - ffmpegProcess.StandardOutput.BaseStream.ReadTimeout = 1000; + int read = ffmpegProcess.StandardOutput.BaseStream.Read(buffer, 0, length); if (read == 0) { From 3fabb8188c456e23f2e41811d46823671848d394 Mon Sep 17 00:00:00 2001 From: Splamy Date: Sat, 6 Jan 2018 03:16:35 +0100 Subject: [PATCH 29/48] Added parsing error handler Fixes #158 --- TS3AudioBot/Core.cs | 1 + TS3Client/ColorDbg.cs | 8 ++ TS3Client/Commands/Ts3String.cs | 8 +- TS3Client/Declarations | 2 +- TS3Client/Generated/Messages.cs | 4 +- TS3Client/Generated/Messages.tt | 10 ++- TS3Client/Helper/R.cs | 8 ++ TS3Client/MessageProcessor.cs | 6 +- TS3Client/Messages/Deserializer.cs | 89 +++++++++++++++++++++ TS3Client/Messages/MessageDeserializer.cs | 97 ----------------------- TS3Client/OwnEnums.cs | 14 ++++ TS3Client/TS3Client.csproj | 2 +- TS3Client/WaitBlock.cs | 3 +- 13 files changed, 141 insertions(+), 111 deletions(-) create mode 100644 TS3Client/Messages/Deserializer.cs delete mode 100644 TS3Client/Messages/MessageDeserializer.cs diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index 16ffd65c..e8a389db 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -221,6 +221,7 @@ void ColorLog(string msg, Log.Level lvl) Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); Log.Write(Log.Level.Info, "Using opus version: {0}", TS3Client.Full.Audio.Opus.NativeMethods.Info); + TS3Client.Messages.Deserializer.OnError += (s, e) => Log.Write(Log.Level.Error, e.ToString()); Injector = new Injector(); Injector.RegisterModule(this); diff --git a/TS3Client/ColorDbg.cs b/TS3Client/ColorDbg.cs index 53cb0f7e..6ad5b26f 100644 --- a/TS3Client/ColorDbg.cs +++ b/TS3Client/ColorDbg.cs @@ -142,5 +142,13 @@ public static void WriteDetail(string detail, string plus = "") } Console.WriteLine(detail); } + + [Conditional("DEBUG")] + [MethodImpl(MethodImplOptions.Synchronized)] + public static void WriteErrParse(string errMsg) + { + WriteType("ERR"); + Write(errMsg, ConsoleColor.Red); + } } } diff --git a/TS3Client/Commands/Ts3String.cs b/TS3Client/Commands/Ts3String.cs index 315134fd..7c6f4227 100644 --- a/TS3Client/Commands/Ts3String.cs +++ b/TS3Client/Commands/Ts3String.cs @@ -15,7 +15,9 @@ namespace TS3Client.Commands public static class Ts3String { - public static string Escape(string stringToEscape) + public static string Escape(string stringToEscape) => Escape(stringToEscape.AsSpan()); + + public static string Escape(ReadOnlySpan stringToEscape) { var strb = new StringBuilder(stringToEscape.Length); for (int i = 0; i < stringToEscape.Length; i++) @@ -37,7 +39,9 @@ public static string Escape(string stringToEscape) return strb.ToString(); } - public static string Unescape(string stringToUnescape) + public static string Unescape(string stringToUnescape) => Unescape(stringToUnescape.AsSpan()); + + public static string Unescape(ReadOnlySpan stringToUnescape) { var strb = new StringBuilder(stringToUnescape.Length); for (int i = 0; i < stringToUnescape.Length; i++) diff --git a/TS3Client/Declarations b/TS3Client/Declarations index 60423f77..6a0f5123 160000 --- a/TS3Client/Declarations +++ b/TS3Client/Declarations @@ -1 +1 @@ -Subproject commit 60423f776b13915f5b6036ee10c0d18653636d05 +Subproject commit 6a0f5123641bae5d7e4152616034672700cb349c diff --git a/TS3Client/Generated/Messages.cs b/TS3Client/Generated/Messages.cs index acbffd3e..f8e33574 100644 --- a/TS3Client/Generated/Messages.cs +++ b/TS3Client/Generated/Messages.cs @@ -878,7 +878,7 @@ public sealed class InitServer : INotification public string ClientName { get; set; } public ClientIdT ClientId { get; set; } public ushort ProtocolVersion { get; set; } - public ushort LicenseType { get; set; } + public LicenseType LicenseType { get; set; } public int TalkPower { get; set; } public int NeededServerQueryViewPower { get; set; } public string ServerName { get; set; } @@ -916,7 +916,7 @@ public void SetField(string name, string value) case "acn": ClientName = Ts3String.Unescape(value); break; case "aclid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; case "pv": ProtocolVersion = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "lt": LicenseType = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "lt": LicenseType = (LicenseType)ushort.Parse(value, CultureInfo.InvariantCulture); break; case "client_talk_power": TalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value, CultureInfo.InvariantCulture); break; case "virtualserver_name": ServerName = Ts3String.Unescape(value); break; diff --git a/TS3Client/Generated/Messages.tt b/TS3Client/Generated/Messages.tt index 3deb42b6..dc62c96d 100644 --- a/TS3Client/Generated/Messages.tt +++ b/TS3Client/Generated/Messages.tt @@ -90,9 +90,10 @@ namespace TS3Client.Messages case "GroupNamingMode": return $"{{ if (!Enum.TryParse({input}, out {fld.fldType} val)) throw new FormatException(); {output} = val; }}"; case "Ts3ErrorCode": - return $"{output} = (Ts3ErrorCode)ushort.Parse({input}, CultureInfo.InvariantCulture);"; + case "LicenseType": + return $"{output} = ({fld.fldType})ushort.Parse({input}, CultureInfo.InvariantCulture);"; case "PermissionId": - return $"{output} = (PermissionId)int.Parse({input}, CultureInfo.InvariantCulture);"; + return $"{output} = ({fld.fldType})int.Parse({input}, CultureInfo.InvariantCulture);"; case "IconHash": return $"{output} = unchecked((int)long.Parse({input}, CultureInfo.InvariantCulture));"; default: @@ -202,8 +203,9 @@ namespace TS3Client.Messages } else if(parts[0] == "TYPE") { - if(parts[1].Contains(",")) { this.Write("#warning Invalid TYPE: " + line); continue; } - convSet.Add(parts[1].Trim()); + var param = parts[1].Replace(" ", "").Split(','); + if(param.Length != 1 && param.Length != 2) { this.Write("#warning Invalid TYPE: " + line); continue; } + convSet.Add(param[0]); } else if(parts[0] == "BREAK") { diff --git a/TS3Client/Helper/R.cs b/TS3Client/Helper/R.cs index e45f9870..e51ceafd 100644 --- a/TS3Client/Helper/R.cs +++ b/TS3Client/Helper/R.cs @@ -84,6 +84,7 @@ public struct R private R(TSuccess value) { isError = false; Error = default(TError); if (value == null) throw new ArgumentNullException(nameof(value), "Return of ok must not be null."); Value = value; } private R(TError error) { isError = true; Value = default(TSuccess); if (error == null) throw new ArgumentNullException(nameof(error), "Error must not be null."); Error = error; } + internal R(bool isError, TSuccess value, TError error) { this.isError = isError; Value = value; Error = error; } /// Creates a new failed result with an error object /// The error @@ -128,6 +129,13 @@ public struct E public static implicit operator TError(E result) => result.Error; public static implicit operator E(TError result) => new E(result); + + // Upwrapping + public R WithValue(TSuccess value) + { + if (!isError && value == null) throw new ArgumentNullException(nameof(value), "Value must not be null."); + return new R(isError, value, Error); + } } #pragma warning restore IDE0016 diff --git a/TS3Client/MessageProcessor.cs b/TS3Client/MessageProcessor.cs index 5917ba95..67db9f90 100644 --- a/TS3Client/MessageProcessor.cs +++ b/TS3Client/MessageProcessor.cs @@ -61,7 +61,7 @@ public MessageProcessor(bool synchronQueue) // if it's not an error it is a notification if (ntfyType != NotificationType.Error) { - var notification = CommandDeserializer.GenerateNotification(lineDataPart, ntfyType); + var notification = Deserializer.GenerateNotification(lineDataPart, ntfyType); var lazyNotification = new LazyNotification(notification, ntfyType); lock (waitBlockLock) { @@ -88,7 +88,9 @@ public MessageProcessor(bool synchronQueue) return lazyNotification; } - var errorStatus = (CommandError)CommandDeserializer.GenerateSingleNotification(lineDataPart, NotificationType.Error); + CommandError errorStatus; + var result = Deserializer.GenerateSingleNotification(lineDataPart, NotificationType.Error); + errorStatus = result.Ok ? (CommandError)result.Value : Util.CustomError("Invalid Error code"); if (synchronQueue) { diff --git a/TS3Client/Messages/Deserializer.cs b/TS3Client/Messages/Deserializer.cs new file mode 100644 index 00000000..bb566ba7 --- /dev/null +++ b/TS3Client/Messages/Deserializer.cs @@ -0,0 +1,89 @@ +// 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.Messages +{ + using System; + using System.Collections.Generic; + using System.Linq; + using KVEnu = System.Collections.Generic.IEnumerable>; + + public static class Deserializer + { + public static event EventHandler OnError; + + // data to notification + internal static IEnumerable GenerateNotification(string lineDataPart, NotificationType ntfyType) + { + if (ntfyType == NotificationType.Unknown) + throw new ArgumentException("The NotificationType must not be unknown", nameof(lineDataPart)); + + if (lineDataPart == null) + throw new ArgumentNullException(nameof(lineDataPart)); + + return lineDataPart.TrimStart().Split('|').Select(msg => GenerateSingleNotification(msg, ntfyType)).Where(x => x.Ok).Select(x => x.Value); + } + + internal static R GenerateSingleNotification(string lineDataPart, NotificationType ntfyType) + { + if (ntfyType == NotificationType.Unknown) + throw new ArgumentException("The NotificationType must not be unknown", nameof(lineDataPart)); + + if (lineDataPart == null) + throw new ArgumentNullException(nameof(lineDataPart)); + + var notification = MessageHelper.GenerateNotificationType(ntfyType); + return ParseKeyValueLine(notification, lineDataPart, false); + } + + // data to response + internal static IEnumerable GenerateResponse(string line) where T : IResponse, new() + { + if (string.IsNullOrWhiteSpace(line)) + return Enumerable.Empty(); + var messageList = line.Split('|'); + return messageList.Select(msg => ParseKeyValueLine(new T(), msg, false)).Where(x => x.Ok).Select(x => x.Value); + } + + private static R ParseKeyValueLine(T qm, string line, bool ignoreFirst) where T : IQueryMessage + { + if (string.IsNullOrWhiteSpace(line)) + return R.Err("Empty"); + + // can be optimized by using Span (wating for more features) + var splitValues = line.Split(' '); + string key = null, value = null; + try + { + for (int i = ignoreFirst ? 1 : 0; i < splitValues.Length; i++) + { + var keyValuePair = splitValues[i].Split(new[] { '=' }, 2); + key = keyValuePair[0]; + value = keyValuePair.Length > 1 ? keyValuePair[1] : string.Empty; + qm.SetField(key, value); + } + return R.OkR(qm); + } + catch (Exception) { OnError?.Invoke(null, new Error(qm.GetType().Name, line, key, value)); } + return R.Err("Error"); + } + + public class Error : EventArgs + { + public string Class { get; set; } + public string Message { get; set; } + public string Field { get; } + public string Value { get; } + + public Error(string classname, string message, string field, string value) { Class = classname; Message = message; Field = field; Value = value; } + + public override string ToString() => $"Deserealization format error. Data: class:{Class} field:{Field} value:{Value} msg:{Message}"; + } + } +} diff --git a/TS3Client/Messages/MessageDeserializer.cs b/TS3Client/Messages/MessageDeserializer.cs deleted file mode 100644 index b15d3dcb..00000000 --- a/TS3Client/Messages/MessageDeserializer.cs +++ /dev/null @@ -1,97 +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.Messages -{ - using System; - using System.Collections.Generic; - using System.Linq; - using KVEnu = System.Collections.Generic.IEnumerable>; - - internal static class CommandDeserializer - { - // data to notification - public static IEnumerable GenerateNotification(string lineDataPart, NotificationType ntfyType) - { - if (ntfyType == NotificationType.Unknown) - throw new ArgumentException("The NotificationType must not be unknown", nameof(lineDataPart)); - - if (lineDataPart == null) - throw new ArgumentNullException(nameof(lineDataPart)); - - return lineDataPart.TrimStart().Split('|').Select(msg => GenerateSingleNotification(msg, ntfyType)); - } - - public static INotification GenerateSingleNotification(string lineDataPart, NotificationType ntfyType) - { - if (ntfyType == NotificationType.Unknown) - throw new ArgumentException("The NotificationType must not be unknown", nameof(lineDataPart)); - - if (lineDataPart == null) - return null; - - var incomingData = ParseKeyValueLine(lineDataPart, false); - var notification = MessageHelper.GenerateNotificationType(ntfyType); - FillQueryMessage(notification, incomingData); - return notification; - } - - // data to response - public static IEnumerable GenerateResponse(string line) where T : IResponse, new() - { - if (typeof(T) == typeof(ResponseDictionary)) - { - if (string.IsNullOrWhiteSpace(line)) - return Enumerable.Empty(); - var messageList = line.Split('|'); - return (IEnumerable)messageList.Select(msg => new ResponseDictionary(ParseKeyValueLineDict(msg, false))); - } - else - { - if (string.IsNullOrWhiteSpace(line)) - return Enumerable.Empty(); - var messageList = line.Split('|'); - return messageList.Select(msg => - { - var incomingData = ParseKeyValueLine(msg, false); - var response = new T(); - FillQueryMessage(response, incomingData); - return response; - }); - } - } - - // HELPER - - private static void FillQueryMessage(IQueryMessage qm, KVEnu kvpData) - { - foreach (var kvp in kvpData) - { - qm.SetField(kvp.Key, kvp.Value); - } - } - - private static KVEnu ParseKeyValueLine(string line, bool ignoreFirst) - { - if (string.IsNullOrWhiteSpace(line)) - return Enumerable.Empty>(); - IEnumerable splitValues = line.Split(' '); - if (ignoreFirst) splitValues = splitValues.Skip(1); - return from part in splitValues - select part.Split(new[] { '=' }, 2) into keyValuePair - select new KeyValuePair(keyValuePair[0], keyValuePair.Length > 1 ? keyValuePair[1] : string.Empty); - } - - private static Dictionary ParseKeyValueLineDict(string line, bool ignoreFirst) - => ParseKeyValueLineDict(ParseKeyValueLine(line, ignoreFirst)); - - private static Dictionary ParseKeyValueLineDict(KVEnu data) - => data.ToDictionary(pair => pair.Key, pair => pair.Value); - } -} diff --git a/TS3Client/OwnEnums.cs b/TS3Client/OwnEnums.cs index c9f2da03..68d36ee8 100644 --- a/TS3Client/OwnEnums.cs +++ b/TS3Client/OwnEnums.cs @@ -144,4 +144,18 @@ public enum GroupWhisperTarget : byte Subchannels, } // ReSharper enable UnusedMember.Global + + public enum LicenseType : ushort + { + /// No licence + NoLicense = 0, + ///Authorised TeamSpeak Host Provider License (ATHP) + Athp = 1, + ///Offline/LAN License + Lan = 2, + ///Non-Profit License (NPL) + Npl = 3, + ///Unknown License + Unknown = 4, + } } diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index 5098d114..ddf0e40f 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -112,7 +112,7 @@ - + diff --git a/TS3Client/WaitBlock.cs b/TS3Client/WaitBlock.cs index 15dd25de..4e2d6513 100644 --- a/TS3Client/WaitBlock.cs +++ b/TS3Client/WaitBlock.cs @@ -9,7 +9,6 @@ namespace TS3Client { - using Commands; using Helper; using Messages; using System; @@ -49,7 +48,7 @@ public WaitBlock(NotificationType[] dependsOn = null) if (commandError.Id != Ts3ErrorCode.ok) return commandError; - return R, CommandError>.OkR(CommandDeserializer.GenerateResponse(commandLine)); + return R, CommandError>.OkR(Deserializer.GenerateResponse(commandLine)); } public R WaitForNotification() From 569b370b7e6e9d217a2097e30429f4fa52b0ee29 Mon Sep 17 00:00:00 2001 From: Splamy Date: Sun, 7 Jan 2018 14:13:06 +0100 Subject: [PATCH 30/48] Fixed a deadlock + fixed some minor bugs --- TS3AudioBot.sln | 5 ++- TS3AudioBot/Bot.cs | 12 ++--- TS3AudioBot/BotManager.cs | 77 ++++++++++++++++++--------------- TS3AudioBot/TeamspeakControl.cs | 2 + TS3Client/Full/Ts3Crypt.cs | 4 +- TS3Client/Generated/Messages.cs | 18 ++++---- TS3Client/Generated/Messages.tt | 2 +- 7 files changed, 65 insertions(+), 55 deletions(-) diff --git a/TS3AudioBot.sln b/TS3AudioBot.sln index 9c79c42e..8545bdd4 100644 --- a/TS3AudioBot.sln +++ b/TS3AudioBot.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2009 +VisualStudioVersion = 15.0.27130.2010 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TS3AudioBot", "TS3AudioBot\TS3AudioBot.csproj", "{0ECC38F3-DE6E-4D7F-81EB-58B15F584635}" EndProject @@ -18,6 +18,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ts3ClientTests", "Ts3ClientTests\Ts3ClientTests.csproj", "{3F6F11F0-C0DE-4C24-B39F-4A5B5B150376}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index fb68ad32..8e24ac00 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -73,7 +73,7 @@ public bool InitializeBot() PlayManager = new PlayManager(core, this); TargetManager = teamspeakClient.TargetPipe; TargetScript = new TargetScript(core, this); - + PlayerConnection.OnSongEnd += PlayManager.SongStoppedHook; PlayManager.BeforeResourceStarted += TargetScript.BeforeResourceStarted; // In own favor update the own status text to the current song title @@ -90,7 +90,7 @@ public bool InitializeBot() // Register callback to remove open private sessions, when user disconnects QueryConnection.OnClientDisconnect += OnClientDisconnect; QueryConnection.OnBotDisconnect += (s, e) => Dispose(); - QueryConnection.OnClientConnect += OnClientConnect; + QueryConnection.OnBotConnected += OnBotConnected; BadgesString = tfcd.ClientBadges; // Connect the query after everyting is set up @@ -103,13 +103,9 @@ public bool InitializeBot() return true; } - private void OnClientConnect(object sender, ClientEnterView e) + private void OnBotConnected(object sender, EventArgs e) { - var me = QueryConnection.GetSelf(); - if (e.ClientId == me.Value.ClientId) - { - QueryConnection.ChangeBadges(BadgesString); - } + QueryConnection.ChangeBadges(BadgesString); } private void TextCallback(object sender, TextMessage textMessage) diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index 387456c1..6669159c 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -18,7 +18,8 @@ public class BotManager : Dependency.ICoreModule, IDisposable { private bool isRunning; public Core Core { get; set; } - private readonly List activeBots; + private List activeBots; + private readonly object lockObj = new object(); public BotManager() { @@ -32,7 +33,7 @@ public void WatchBots() { while (isRunning) { - lock (activeBots) + lock (lockObj) { if (activeBots.Count == 0) { @@ -51,14 +52,17 @@ public void WatchBots() private void CleanStrayBots() { List strayList = null; - foreach (var bot in activeBots) + lock (lockObj) { - var client = bot.QueryConnection.GetLowLibrary(); - if (!client.Connected && !client.Connecting) + foreach (var bot in activeBots) { - Log.Write(Log.Level.Warning, "Cleaning up stray bot."); - strayList = strayList ?? new List(); - strayList.Add(bot); + var client = bot.QueryConnection.GetLowLibrary(); + if (!client.Connected && !client.Connecting) + { + Log.Write(Log.Level.Warning, "Cleaning up stray bot."); + strayList = strayList ?? new List(); + strayList.Add(bot); + } } } @@ -69,30 +73,30 @@ private void CleanStrayBots() public bool CreateBot(/*Ts3FullClientData bot*/) { - string error = string.Empty; + bool removeBot = false; var bot = new Bot(Core); - try + if (bot.InitializeBot()) { - if (bot.InitializeBot()) + lock (lockObj) { - lock (activeBots) - { - activeBots.Add(bot); - } - return true; + activeBots.Add(bot); + removeBot = !isRunning; } } - catch (Exception ex) { error = ex.ToString(); } - - bot.Dispose(); - Log.Write(Log.Level.Warning, "Could not create new Bot ({0})", error); - return false; + if (removeBot) + { + StopBot(bot); + return false; + } + return true; } public Bot GetBot(int id) { - lock (activeBots) + lock (lockObj) { + if (!isRunning) + return null; return id < activeBots.Count ? activeBots[id] : null; @@ -101,25 +105,30 @@ public Bot GetBot(int id) public void StopBot(Bot bot) { - lock (activeBots) + bool tookBot = false; + lock (lockObj) { - if (activeBots.Remove(bot)) - { - bot.Dispose(); - } + tookBot = activeBots.Remove(bot); + } + if (tookBot) + { + bot.Dispose(); } } public void Dispose() { - isRunning = false; - lock (activeBots) + List disposeBots; + lock (lockObj) { - var bots = activeBots.ToArray(); - foreach (var bot in bots) - { - StopBot(bot); - } + isRunning = false; + disposeBots = activeBots; + activeBots = new List(); + } + + foreach (var bot in disposeBots) + { + StopBot(bot); } } } diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index dc4a1c3f..e054eb35 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -60,6 +60,7 @@ private void ExtendedClientLeftView(object sender, IEnumerable e } } + public event EventHandler OnBotConnected; public abstract event EventHandler OnBotDisconnect; private List clientbuffer; @@ -81,6 +82,7 @@ protected TeamspeakControl(ClientType connectionType) tsBaseClient.OnClientLeftView += ExtendedClientLeftView; tsBaseClient.OnClientEnterView += ExtendedClientEnterView; tsBaseClient.OnTextMessageReceived += ExtendedTextMessage; + tsBaseClient.OnConnected += OnBotConnected; } public virtual T GetLowLibrary() where T : class diff --git a/TS3Client/Full/Ts3Crypt.cs b/TS3Client/Full/Ts3Crypt.cs index 55989d07..ad861be9 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TS3Client/Full/Ts3Crypt.cs @@ -467,8 +467,8 @@ private static bool FakeDecrypt(IncomingPacket packet, byte[] mac) Array.Copy(cachedKeyNonces[cacheIndex].Item2, 0, nonce, 0, 16); // finally the first two bytes get xor'd with the packet id - key[0] ^= (byte)(packetId >> 8); - key[1] ^= (byte)(packetId >> 0); + key[0] ^= unchecked((byte)(packetId >> 8)); + key[1] ^= unchecked((byte)(packetId >> 0)); return (key, nonce); } diff --git a/TS3Client/Generated/Messages.cs b/TS3Client/Generated/Messages.cs index f8e33574..b93f6624 100644 --- a/TS3Client/Generated/Messages.cs +++ b/TS3Client/Generated/Messages.cs @@ -101,7 +101,7 @@ public void SetField(string name, string value) case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value, CultureInfo.InvariantCulture); break; @@ -195,7 +195,7 @@ public void SetField(string name, string value) case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value, CultureInfo.InvariantCulture); break; @@ -270,7 +270,7 @@ public void SetField(string name, string value) case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; case "channel_forced_silence": ForcedSilence = value != "0"; break; case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "channel_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; case "channel_flag_private": IsPrivate = value != "0"; break; } @@ -498,7 +498,7 @@ public void SetField(string name, string value) case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; case "client_flag_avatar": AvatarFlag = Ts3String.Unescape(value); break; case "client_description": Description = Ts3String.Unescape(value); break; - case "client_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; case "client_input_muted": IsInputMuted = value != "0"; break; case "client_output_muted": IsOutputMuted = value != "0"; break; case "client_outputonly_muted": IsOutputOnlyMuted = value != "0"; break; @@ -931,7 +931,7 @@ public void SetField(string name, string value) 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_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "virtualserver_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; case "virtualserver_hostbanner_mode": { if (!Enum.TryParse(value, out HostBannerMode val)) throw new FormatException(); HostbannerMode = val; } break; case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; @@ -987,7 +987,7 @@ public void SetField(string name, string value) 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_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "virtualserver_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; case "virtualserver_hostbanner_mode": { if (!Enum.TryParse(value, out HostBannerMode val)) throw new FormatException(); HostbannerMode = val; } break; case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; @@ -1143,7 +1143,7 @@ public void SetField(string name, string value) case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; case "return_code": ReturnCode = Ts3String.Unescape(value); break; @@ -1221,7 +1221,7 @@ public void SetField(string name, string value) case "client_type": { if (!Enum.TryParse(value, out ClientType val)) throw new FormatException(); ClientType = val; } break; case "client_flag_avatar": AvatarFlag = Ts3String.Unescape(value); break; case "client_description": Description = Ts3String.Unescape(value); break; - case "client_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; case "client_created": CreationDate = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; case "client_lastconnected": LastConnected = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; case "client_totalconnections": TotalConnections = int.Parse(value, CultureInfo.InvariantCulture); break; @@ -1365,7 +1365,7 @@ public void SetField(string name, string value) case "client_base64HashClientUID": Base64HashClientUid = Ts3String.Unescape(value); break; case "client_flag_avatar": AvatarFlag = Ts3String.Unescape(value); break; case "client_description": Description = Ts3String.Unescape(value); break; - case "client_icon_id": IconId = unchecked((int)long.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; case "return_code": ReturnCode = Ts3String.Unescape(value); break; } diff --git a/TS3Client/Generated/Messages.tt b/TS3Client/Generated/Messages.tt index dc62c96d..18dc5074 100644 --- a/TS3Client/Generated/Messages.tt +++ b/TS3Client/Generated/Messages.tt @@ -95,7 +95,7 @@ namespace TS3Client.Messages case "PermissionId": return $"{output} = ({fld.fldType})int.Parse({input}, CultureInfo.InvariantCulture);"; case "IconHash": - return $"{output} = unchecked((int)long.Parse({input}, CultureInfo.InvariantCulture));"; + return $"{output} = unchecked((int)ulong.Parse({input}, CultureInfo.InvariantCulture));"; default: return "#error missing deserializer"; } From b32eac5189cd10b517fe89841f219e27e70a7adb Mon Sep 17 00:00:00 2001 From: Splamy Date: Mon, 8 Jan 2018 16:20:11 +0100 Subject: [PATCH 31/48] Some async tests --- TS3Client/Full/Audio/AudioPipeExtensions.cs | 2 +- TS3Client/Full/Audio/Opus/OpusEncoder.cs | 1 - TS3Client/Full/Audio/PreciseTimedPipe.cs | 4 +-- TS3Client/Full/Audio/SplitterPipe.cs | 2 +- TS3Client/Full/Audio/StaticMetaPipe.cs | 2 +- TS3Client/Full/QuickerLz.cs | 4 --- TS3Client/Full/Ts3FullClient.cs | 23 +++++++++++-- TS3Client/MessageProcessor.cs | 4 +-- TS3Client/Query/Ts3QueryClient.cs | 2 +- TS3Client/WaitBlock.cs | 37 ++++++++++++++++++--- 10 files changed, 59 insertions(+), 22 deletions(-) diff --git a/TS3Client/Full/Audio/AudioPipeExtensions.cs b/TS3Client/Full/Audio/AudioPipeExtensions.cs index 70b031da..0bdd4d14 100644 --- a/TS3Client/Full/Audio/AudioPipeExtensions.cs +++ b/TS3Client/Full/Audio/AudioPipeExtensions.cs @@ -19,7 +19,7 @@ public static T Chain(this IAudioActiveProducer producer, T addConsumer) wher { producer.OutStream = addConsumer; } - else if (producer is SplitterPipe splitter) + else if (producer.OutStream is SplitterPipe splitter) { splitter.Add(addConsumer); } diff --git a/TS3Client/Full/Audio/Opus/OpusEncoder.cs b/TS3Client/Full/Audio/Opus/OpusEncoder.cs index bf872d64..f8d944d3 100644 --- a/TS3Client/Full/Audio/Opus/OpusEncoder.cs +++ b/TS3Client/Full/Audio/Opus/OpusEncoder.cs @@ -71,7 +71,6 @@ private OpusEncoder(IntPtr encoder, int inputSamplingRate, int inputChannels, Ap /// PCM samples to encode. /// How many bytes to encode. /// The encoded data is written to this buffer. - /// Set to length of encoded audio. /// Opus encoded audio buffer. public Span Encode(byte[] inputPcmSamples, int sampleLength, byte[] outputEncodedBuffer) { diff --git a/TS3Client/Full/Audio/PreciseTimedPipe.cs b/TS3Client/Full/Audio/PreciseTimedPipe.cs index 90c496a3..b11400a9 100644 --- a/TS3Client/Full/Audio/PreciseTimedPipe.cs +++ b/TS3Client/Full/Audio/PreciseTimedPipe.cs @@ -24,8 +24,8 @@ public class PreciseTimedPipe : IAudioActiveConsumer, IAudioActiveProducer, IDis public int ReadBufferSize { get; set; } = 2048; private byte[] readBuffer = new byte[0]; private readonly object lockObject = new object(); - private Thread tickThread = null; - private bool running = false; + private Thread tickThread; + private bool running; private bool paused; public bool Paused diff --git a/TS3Client/Full/Audio/SplitterPipe.cs b/TS3Client/Full/Audio/SplitterPipe.cs index 42bf2f4d..8a5499bd 100644 --- a/TS3Client/Full/Audio/SplitterPipe.cs +++ b/TS3Client/Full/Audio/SplitterPipe.cs @@ -16,7 +16,7 @@ public class SplitterPipe : IAudioPassiveConsumer { private readonly List safeConsumerList = new List(); private readonly List consumerList = new List(); - private bool changed = false; + private bool changed; private readonly object listLock = new object(); private byte[] buffer = new byte[0]; diff --git a/TS3Client/Full/Audio/StaticMetaPipe.cs b/TS3Client/Full/Audio/StaticMetaPipe.cs index 8bcfafe5..b063bc36 100644 --- a/TS3Client/Full/Audio/StaticMetaPipe.cs +++ b/TS3Client/Full/Audio/StaticMetaPipe.cs @@ -76,7 +76,7 @@ public void Write(Span data, Meta meta) meta.Out.GroupWhisperType = setMeta.GroupWhisperType; meta.Out.TargetId = setMeta.TargetId; break; - default: break; + default: throw new ArgumentOutOfRangeException(nameof(SendMode), SendMode, "SendMode not handled"); } OutStream?.Write(data, meta); } diff --git a/TS3Client/Full/QuickerLz.cs b/TS3Client/Full/QuickerLz.cs index d61e420b..8ac9f34e 100644 --- a/TS3Client/Full/QuickerLz.cs +++ b/TS3Client/Full/QuickerLz.cs @@ -291,10 +291,6 @@ private static void WriteU32(byte[] outArr, int outOff, uint value) outArr[outOff + 3] = unchecked((byte)(value >> 24)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort ReadU16(byte[] intArr, int inOff) - => unchecked((ushort)(intArr[inOff] | (intArr[inOff + 1] << 8))); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Read24(ReadOnlySpan intArr, int inOff) => unchecked(intArr[inOff] | (intArr[inOff + 1] << 8) | (intArr[inOff + 2] << 16)); diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index e50dc670..4f07c7ce 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -17,6 +17,7 @@ namespace TS3Client.Full using System.Collections.Generic; using System.Linq; using System.Threading; + using System.Threading.Tasks; using CmdR = E; @@ -343,7 +344,7 @@ private void ProcessConnectionInfoRequest() /// Or R(ERR) with the returned error if no reponse is expected. public override R, CommandError> SendCommand(Ts3Command com) { - using (var wb = new WaitBlock()) + using (var wb = new WaitBlock(false)) { var result = SendCommandBase(wb, com); if (!result.Ok) @@ -362,7 +363,7 @@ public R SendNotifyCommand(Ts3Command com, param if (!com.ExpectResponse) throw new ArgumentException("A special command must take a response"); - using (var wb = new WaitBlock(dependsOn)) + using (var wb = new WaitBlock(false, dependsOn)) { var result = SendCommandBase(wb, com); if (!result.Ok) @@ -394,6 +395,22 @@ private E SendCommandBase(WaitBlock wb, Ts3Command com) return E.OkR; } + public async Task, CommandError>> SendCommandAsync(Ts3Command com) where T : IResponse, new() + { + using (var wb = new WaitBlock(true)) + { + var result = SendCommandBase(wb, com); + if (!result.Ok) + return result.Error; + if (com.ExpectResponse) + return await wb.WaitForMessageAsync(); + 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; + } + } + /// Release all resources. Will try to disconnect before disposing. public override void Dispose() { @@ -428,7 +445,7 @@ public void Write(Span data, Meta meta) case TargetSendMode.WhisperGroup: SendAudioGroupWhisper(data, meta.Codec.Value, meta.Out.GroupWhisperType, meta.Out.GroupWhisperTarget, meta.Out.TargetId); break; - default: break; + default: throw new ArgumentOutOfRangeException(nameof(meta.Out.SendMode), meta.Out.SendMode, "SendMode not handled"); } } #endregion diff --git a/TS3Client/MessageProcessor.cs b/TS3Client/MessageProcessor.cs index 67db9f90..9efdb862 100644 --- a/TS3Client/MessageProcessor.cs +++ b/TS3Client/MessageProcessor.cs @@ -9,7 +9,6 @@ namespace TS3Client { - using Commands; using Helper; using Messages; using System; @@ -88,9 +87,8 @@ public MessageProcessor(bool synchronQueue) return lazyNotification; } - CommandError errorStatus; var result = Deserializer.GenerateSingleNotification(lineDataPart, NotificationType.Error); - errorStatus = result.Ok ? (CommandError)result.Value : Util.CustomError("Invalid Error code"); + var errorStatus = result.Ok ? (CommandError)result.Value : Util.CustomError("Invalid Error code"); if (synchronQueue) { diff --git a/TS3Client/Query/Ts3QueryClient.cs b/TS3Client/Query/Ts3QueryClient.cs index 75d8ec1a..c1ee5252 100644 --- a/TS3Client/Query/Ts3QueryClient.cs +++ b/TS3Client/Query/Ts3QueryClient.cs @@ -134,7 +134,7 @@ private void InvokeEvent(LazyNotification lazyNotification) public override R, CommandError> SendCommand(Ts3Command com) // Synchronous { - using (var wb = new WaitBlock()) + using (var wb = new WaitBlock(false)) { lock (sendQueueLock) { diff --git a/TS3Client/WaitBlock.cs b/TS3Client/WaitBlock.cs index 4e2d6513..b7ac5d7a 100644 --- a/TS3Client/WaitBlock.cs +++ b/TS3Client/WaitBlock.cs @@ -14,9 +14,11 @@ namespace TS3Client using System; using System.Collections.Generic; using System.Threading; + using System.Threading.Tasks; internal sealed class WaitBlock : IDisposable { + private readonly TaskCompletionSource answerWaiterAsync; private readonly ManualResetEvent answerWaiter; private readonly ManualResetEvent notificationWaiter; private CommandError commandError; @@ -25,11 +27,16 @@ internal sealed class WaitBlock : IDisposable private LazyNotification notification; private bool isDisposed; private static readonly TimeSpan CommandTimeout = TimeSpan.FromSeconds(15); + private readonly bool async; - public WaitBlock(NotificationType[] dependsOn = null) + public WaitBlock(bool async, NotificationType[] dependsOn = null) { + this.async = async; isDisposed = false; - answerWaiter = new ManualResetEvent(false); + if (async) + answerWaiterAsync = new TaskCompletionSource(); + else + answerWaiter = new ManualResetEvent(false); DependsOn = dependsOn; if (DependsOn != null) { @@ -51,6 +58,20 @@ public WaitBlock(NotificationType[] dependsOn = null) return R, CommandError>.OkR(Deserializer.GenerateResponse(commandLine)); } + public async Task, CommandError>> WaitForMessageAsync() where T : IResponse, new() + { + if (isDisposed) + throw new ObjectDisposedException(nameof(WaitBlock)); + var timeOut = Task.Delay(CommandTimeout); + var res = await Task.WhenAny(answerWaiterAsync.Task, timeOut); + if (res == timeOut) + return Util.TimeOutCommandError; + if (commandError.Id != Ts3ErrorCode.ok) + return commandError; + + return R, CommandError>.OkR(Deserializer.GenerateResponse(commandLine)); + } + public R WaitForNotification() { if (isDisposed) @@ -73,7 +94,10 @@ public void SetAnswer(CommandError commandError, string commandLine = null) return; this.commandError = commandError ?? throw new ArgumentNullException(nameof(commandError)); this.commandLine = commandLine; - answerWaiter.Set(); + if (async) + answerWaiterAsync.SetResult(true); + else + answerWaiter.Set(); } public void SetNotification(LazyNotification notification) @@ -92,8 +116,11 @@ public void Dispose() return; isDisposed = true; - answerWaiter.Set(); - answerWaiter.Dispose(); + if (!async) + { + answerWaiter.Set(); + answerWaiter.Dispose(); + } if (notificationWaiter != null) { From 178bb0ecd44d2fd8e2ec019ef76006074db64376 Mon Sep 17 00:00:00 2001 From: Splamy Date: Mon, 8 Jan 2018 22:18:54 +0100 Subject: [PATCH 32/48] Fixed RingQueue buffer resize --- TS3ABotUnitTests/UnitTests.cs | 41 ++++++++++++++++++++++++++++++--- TS3AudioBot/BotManager.cs | 2 +- TS3Client/Full/PacketHandler.cs | 28 ++++++++++++---------- TS3Client/Full/RingQueue.cs | 4 ++-- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/TS3ABotUnitTests/UnitTests.cs b/TS3ABotUnitTests/UnitTests.cs index e3bf554e..adc5fcbb 100644 --- a/TS3ABotUnitTests/UnitTests.cs +++ b/TS3ABotUnitTests/UnitTests.cs @@ -396,11 +396,12 @@ public void Ts3Client_RingQueueTest() public void Ts3Client_RingQueueTest2() { var q = new RingQueue(50, ushort.MaxValue + 1); - + for (int i = 0; i < ushort.MaxValue - 10; i++) { - q.Set(i, 42); - q.TryDequeue(out var _); + q.Set(i, i); + Assert.True(q.TryDequeue(out var iCheck)); + Assert.AreEqual(iCheck, i); } var setStatus = q.IsSet(ushort.MaxValue - 20); @@ -411,6 +412,40 @@ public void Ts3Client_RingQueueTest2() q.Set(i % (ushort.MaxValue + 1), 42); } } + + [Test] + public void Ts3Client_RingQueueTest3() + { + var q = new RingQueue(100, ushort.MaxValue + 1); + + int iSet = 0; + for (int blockSize = 1; blockSize < 100; blockSize++) + { + for (int i = 0; i < blockSize; i++) + { + q.Set(iSet++, i); + } + for (int i = 0; i < blockSize; i++) + { + Assert.True(q.TryDequeue(out var iCheck)); + Assert.AreEqual(i, iCheck); + } + } + + for (int blockSize = 1; blockSize < 100; blockSize++) + { + q = new RingQueue(100, ushort.MaxValue + 1); + for (int i = 0; i < blockSize; i++) + { + q.Set(i, i); + } + for (int i = 0; i < blockSize; i++) + { + Assert.True(q.TryDequeue(out var iCheck)); + Assert.AreEqual(i, iCheck); + } + } + } } static class Extensions diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index 6669159c..b24080cc 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -105,7 +105,7 @@ public Bot GetBot(int id) public void StopBot(Bot bot) { - bool tookBot = false; + bool tookBot; lock (lockObj) { tookBot = activeBots.Remove(bot); diff --git a/TS3Client/Full/PacketHandler.cs b/TS3Client/Full/PacketHandler.cs index 0e92cc19..37986874 100644 --- a/TS3Client/Full/PacketHandler.cs +++ b/TS3Client/Full/PacketHandler.cs @@ -159,17 +159,18 @@ public void AddOutgoingPacket(ReadOnlySpan packet, PacketType packetType, if (NeedsSplitting(packet.Length)) { - foreach (var splitPacket in BuildSplitList(packet, packetType)) - AddOutgoingPacket(splitPacket, addFlags); + AddOutgoingSplitData(packet, packetType, addFlags); return; } } - AddOutgoingPacket(new OutgoingPacket(packet.ToArray(), packetType), addFlags); // TODO optimize to array here + SendOutgoingData(packet, packetType, addFlags); } } - private void AddOutgoingPacket(OutgoingPacket packet, PacketFlags flags = PacketFlags.None) + private void SendOutgoingData(ReadOnlySpan data, PacketType packetType, PacketFlags flags = PacketFlags.None, bool skipCommandFlagging = false) { + var packet = new OutgoingPacket(data.ToArray(), packetType); + lock (sendLoopLock) { var ids = GetPacketCounter(packet.PacketType); @@ -191,7 +192,8 @@ private void AddOutgoingPacket(OutgoingPacket packet, PacketFlags flags = Packet case PacketType.Command: case PacketType.CommandLow: - packet.PacketFlags |= PacketFlags.Newprotocol; + if (!skipCommandFlagging) + packet.PacketFlags |= PacketFlags.Newprotocol; packetAckManager.Add(packet.PacketId, packet); break; @@ -244,7 +246,7 @@ public void CryptoInitDone() IncPacketCounter(PacketType.Command); } - private static IEnumerable BuildSplitList(ReadOnlySpan rawData, PacketType packetType) + private void AddOutgoingSplitData(ReadOnlySpan rawData, PacketType packetType, PacketFlags addFlags = PacketFlags.None) { int pos = 0; bool first = true; @@ -255,18 +257,20 @@ private static IEnumerable BuildSplitList(ReadOnlySpan raw { int blockSize = Math.Min(maxContent, rawData.Length - pos); if (blockSize <= 0) break; - - var packet = new OutgoingPacket(rawData.Slice(pos, blockSize).ToArray(), packetType); // TODO optimize toarray call + var flags = PacketFlags.None; + var skipFlagging = !first; last = pos + blockSize == rawData.Length; if (first ^ last) - packet.FragmentedFlag = true; + flags |= PacketFlags.Fragmented; if (first) + { + flags |= addFlags; first = false; + } - yield return packet; + SendOutgoingData(rawData.Slice(pos, blockSize), packetType, flags, skipFlagging); pos += blockSize; - } while (!last); } @@ -557,7 +561,7 @@ private void ResendLoop() pingCheck += PingInterval; SendPing(); } - + // TODO implement ping-timeout here sendLoopPulse.WaitOne(ClockResolution); } } diff --git a/TS3Client/Full/RingQueue.cs b/TS3Client/Full/RingQueue.cs index 89232127..0e7eaa77 100644 --- a/TS3Client/Full/RingQueue.cs +++ b/TS3Client/Full/RingQueue.cs @@ -99,8 +99,8 @@ private void BufferExtend(int index) var extRingBufferSet = new bool[extendTo]; Array.Copy(ringBuffer, currentStart, extRingBuffer, 0, ringBuffer.Length - currentStart); Array.Copy(ringBufferSet, currentStart, extRingBufferSet, 0, ringBufferSet.Length - currentStart); - Array.Copy(ringBuffer, 0, extRingBuffer, currentStart, currentStart); - Array.Copy(ringBufferSet, 0, extRingBufferSet, currentStart, currentStart); + Array.Copy(ringBuffer, 0, extRingBuffer, ringBuffer.Length - currentStart, currentStart); + Array.Copy(ringBufferSet, 0, extRingBufferSet, ringBufferSet.Length - currentStart, currentStart); currentStart = 0; ringBuffer = extRingBuffer; ringBufferSet = extRingBufferSet; From 4c4511cb8db47fa5168063825b8bc1794f6d2890 Mon Sep 17 00:00:00 2001 From: Splamy Date: Tue, 9 Jan 2018 07:01:28 +0100 Subject: [PATCH 33/48] Moved to proper logging system Closes #149 --- TS3AudioBot/Audio/FfmpegProducer.cs | 7 +- TS3AudioBot/Bot.cs | 25 +- TS3AudioBot/BotManager.cs | 3 +- TS3AudioBot/CommandSystem/CommandManager.cs | 3 +- TS3AudioBot/Commands.cs | 8 +- TS3AudioBot/Core.cs | 109 ++---- .../Helper/AudioTags/AudioTagReader.cs | 9 +- TS3AudioBot/Helper/TickPool.cs | 3 +- TS3AudioBot/Helper/Util.cs | 3 +- TS3AudioBot/Helper/WebWrapper.cs | 10 +- TS3AudioBot/History/HistoryManager.cs | 5 +- TS3AudioBot/Log.cs | 313 ------------------ TS3AudioBot/NLog.config | 25 ++ TS3AudioBot/PlayManager.cs | 9 +- TS3AudioBot/PlaylistManager.cs | 7 +- TS3AudioBot/Plugins/Plugin.cs | 18 +- TS3AudioBot/Plugins/PluginManager.cs | 3 +- .../ResourceFactories/SoundcloudFactory.cs | 13 +- .../ResourceFactories/YoutubeDlHelper.cs | 7 +- .../ResourceFactories/YoutubeFactory.cs | 13 +- TS3AudioBot/Rights/RightsManager.cs | 11 +- TS3AudioBot/Sessions/SessionManager.cs | 3 +- TS3AudioBot/Sessions/UserSession.cs | 3 +- TS3AudioBot/TS3AudioBot.csproj | 7 +- TS3AudioBot/TargetScript.cs | 3 +- TS3AudioBot/TeamspeakControl.cs | 6 +- TS3AudioBot/Ts3Full.cs | 19 +- TS3AudioBot/Web/Api/WebApi.cs | 5 +- TS3AudioBot/Web/WebManager.cs | 7 +- TS3AudioBot/packages.config | 1 + TS3Client/ColorDbg.cs | 154 --------- TS3Client/Full/PacketHandler.cs | 66 ++-- TS3Client/Full/Ts3FullClient.cs | 4 +- TS3Client/TS3Client.csproj | 4 +- TS3Client/Ts3BaseClient.cs | 1 + TS3Client/packages.config | 1 + 36 files changed, 226 insertions(+), 662 deletions(-) delete mode 100644 TS3AudioBot/Log.cs create mode 100644 TS3AudioBot/NLog.config delete mode 100644 TS3Client/ColorDbg.cs diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs index 1b6b08ce..be86e44d 100644 --- a/TS3AudioBot/Audio/FfmpegProducer.cs +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -18,8 +18,7 @@ namespace TS3AudioBot.Audio class FfmpegProducer : IAudioPassiveProducer, ISampleInfo, IDisposable { - public event EventHandler OnSongEnd; - + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex FindDurationMatch = new Regex(@"^\s*Duration: (\d+):(\d\d):(\d\d).(\d\d)", Util.DefaultRegexConfig); private const string PreLinkConf = "-hide_banner -nostats -i \""; private const string PostLinkConf = "\" -ac 2 -ar 48000 -f s16le -acodec pcm_s16le pipe:1"; @@ -28,6 +27,8 @@ class FfmpegProducer : IAudioPassiveProducer, ISampleInfo, IDisposable private readonly Ts3FullClientData ts3FullClientData; + public event EventHandler OnSongEnd; + private PreciseAudioTimer audioTimer; private string lastLink; private Process ffmpegProcess; @@ -91,7 +92,7 @@ public int Read(byte[] buffer, int offset, int length, out Meta meta) var actualStopPosition = audioTimer.SongPosition; if (actualStopPosition + retryOnDropBeforeEnd < expectedStopLength) { - Log.Write(Log.Level.Debug, "Connection to song lost, retrying at {0}", actualStopPosition); + Log.Debug("Connection to song lost, retrying at {0}", actualStopPosition); hasTriedToReconnectAudio = true; Position = actualStopPosition; return 0; diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index 8e24ac00..5870c394 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -21,6 +21,8 @@ namespace TS3AudioBot /// Core class managing all bots and utility modules. public sealed class Bot : IDisposable { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + private bool isDisposed; private readonly Core core; private MainBotData mainBotData; @@ -52,7 +54,7 @@ public Bot(Core core) public bool InitializeBot() { - Log.Write(Log.Level.Info, "Bot connecting..."); + Log.Info("Bot connecting..."); // Read Config File var conf = core.ConfigManager; @@ -97,7 +99,7 @@ public bool InitializeBot() try { QueryConnection.Connect(); } catch (Ts3Exception qcex) { - Log.Write(Log.Level.Error, "There is either a problem with your connection configuration, or the query has not all permissions it needs. ({0})", qcex); + Log.Info(qcex, "There is either a problem with your connection configuration, or the query has not all permissions it needs."); return false; } return true; @@ -110,7 +112,7 @@ private void OnBotConnected(object sender, EventArgs e) private void TextCallback(object sender, TextMessage textMessage) { - Log.Write(Log.Level.Debug, "MB Got message from {0}: {1}", textMessage.InvokerName, textMessage.Message); + Log.Debug("Got message from {0}: {1}", textMessage.InvokerName, textMessage.Message); textMessage.Message = textMessage.Message.TrimStart(' '); if (!textMessage.Message.StartsWith("!", StringComparison.Ordinal)) @@ -118,7 +120,7 @@ private void TextCallback(object sender, TextMessage textMessage) var refreshResult = QueryConnection.RefreshClientBuffer(true); if (!refreshResult.Ok) - Log.Write(Log.Level.Warning, "Bot is not correctly set up. Some requests might fail or are slower. ({0})", refreshResult.Error); + Log.Warn("Bot is not correctly set up. Some requests might fail or are slower. ({0})", refreshResult.Error); var clientResult = QueryConnection.GetClientById(textMessage.InvokerId); @@ -133,7 +135,7 @@ private void TextCallback(object sender, TextMessage textMessage) { if (!clientResult.Ok) { - Log.Write(Log.Level.Error, clientResult.Error); + Log.Error(clientResult.Error); return; } session = SessionManager.CreateSession(this, clientResult.Value); @@ -185,11 +187,12 @@ private void TextCallback(object sender, TextMessage textMessage) } catch (CommandException ex) { + Log.Debug(ex, "Command Error"); execInfo.Write("Error: " + ex.Message); } catch (Exception ex) { - Log.Write(Log.Level.Error, "MB Unexpected command error: {0}", ex.UnrollException()); + Log.Error(ex, "Unexpected command error: {0}", ex.UnrollException()); execInfo.Write("An unexpected error occured: " + ex.Message); } } @@ -205,7 +208,7 @@ private void LoggedUpdateBotStatus(object sender, EventArgs e) { var result = UpdateBotStatus(); if (!result) - Log.Write(Log.Level.Warning, result.Error); + Log.Warn(result.Error); } public R UpdateBotStatus(string overrideStr = null) @@ -247,7 +250,7 @@ private void GenerateStatusImage(object sender, EventArgs e) bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg); var result = QueryConnection.UploadAvatar(mem); if (!result.Ok) - Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Error); + Log.Warn("Could not save avatar: {0}", result.Error); } } } @@ -257,7 +260,7 @@ private void GenerateStatusImage(object sender, EventArgs e) { var result = QueryConnection.UploadAvatar(sleepPic); if (!result.Ok) - Log.Write(Log.Level.Warning, "Could not save avatar: {0}", result.Error); + Log.Warn("Could not save avatar: {0}", result.Error); } } } @@ -266,7 +269,7 @@ public void Dispose() { if (!isDisposed) isDisposed = true; else return; - Log.Write(Log.Level.Info, "Bot disconnecting."); + Log.Info("Bot disconnecting."); core.Bots.StopBot(this); @@ -283,8 +286,6 @@ public void Dispose() #pragma warning disable CS0649 internal class MainBotData : ConfigData { - [Info("Path to the logfile", "ts3audiobot.log")] - public string LogFile { get; set; } [Info("Teamspeak group id giving the Bot enough power to do his job", "0")] public ulong BotGroupId { get; set; } [Info("Generate fancy status images as avatar", "true")] diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index b24080cc..71cea09e 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -16,6 +16,7 @@ namespace TS3AudioBot public class BotManager : Dependency.ICoreModule, IDisposable { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private bool isRunning; public Core Core { get; set; } private List activeBots; @@ -59,7 +60,7 @@ private void CleanStrayBots() var client = bot.QueryConnection.GetLowLibrary(); if (!client.Connected && !client.Connecting) { - Log.Write(Log.Level.Warning, "Cleaning up stray bot."); + Log.Warn("Cleaning up stray bot."); strayList = strayList ?? new List(); strayList.Add(bot); } diff --git a/TS3AudioBot/CommandSystem/CommandManager.cs b/TS3AudioBot/CommandSystem/CommandManager.cs index 24757f5b..995cafda 100644 --- a/TS3AudioBot/CommandSystem/CommandManager.cs +++ b/TS3AudioBot/CommandSystem/CommandManager.cs @@ -18,6 +18,7 @@ namespace TS3AudioBot.CommandSystem public class CommandManager : Dependency.ICoreModule { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex CommandNamespaceValidator = new Regex(@"^[a-z]+( [a-z]+)*$", Util.DefaultRegexConfig & ~RegexOptions.IgnoreCase); @@ -235,7 +236,7 @@ private static R InsertInto(CommandGroup group, ICommand com, string name) { insertCommand.AddCommand(string.Empty, com); if (com is BotCommand botCom && botCom.NormalParameters > 0) - Log.Write(Log.Level.Warning, $"\"{botCom.FullQualifiedName}\" has at least one parameter and won't be reachable due to an overloading function."); + Log.Warn("\"{0}\" has at least one parameter and won't be reachable due to an overloading function.", botCom.FullQualifiedName); return R.OkR; } else diff --git a/TS3AudioBot/Commands.cs b/TS3AudioBot/Commands.cs index 4a22a53d..dd927f25 100644 --- a/TS3AudioBot/Commands.cs +++ b/TS3AudioBot/Commands.cs @@ -25,6 +25,8 @@ namespace TS3AudioBot public static class Commands { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + #region COMMANDS public const string RightHighVolume = "ts3ab.admin.volume"; @@ -706,7 +708,7 @@ public static void CommandListPlay(ExecutionInformation info, int? index) public static void CommandListQueue(ExecutionInformation info) { var plist = AutoGetPlaylist(info); - info.Bot.PlayManager.Enqueue(plist.AsEnumerable()); + info.Bot.PlayManager.Enqueue(plist.AsEnumerable()).UnwrapThrow(); } [Command("list save", "Stores your current workinglist to disk.")] @@ -1269,7 +1271,7 @@ private static string ResponseVolume(ExecutionInformation info) } else { - Log.Write(Log.Level.Error, "responseData is not an float."); + Log.Error("responseData is not an float."); return "Internal error"; } } @@ -1307,7 +1309,7 @@ private static string ResponseHistoryDelete(ExecutionInformation info) } else { - Log.Write(Log.Level.Error, "No entry provided."); + Log.Error("No entry provided."); return "Internal error"; } } diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index e8a389db..5b9fe2e8 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -7,38 +7,43 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . + +using NLog.Targets; + namespace TS3AudioBot { using CommandSystem; using Dependency; using Helper; using History; + using NLog; using Plugins; using ResourceFactories; using Rights; using System; - using System.IO; using System.Threading; using Web; public sealed class Core : IDisposable, ICoreModule { - private bool consoleOutput; - private bool writeLog; - private bool writeLogStack; - internal string configFilePath; - private StreamWriter logStream; - private MainBotData mainBotData; + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + private string configFilePath; internal static void Main(string[] args) { Thread.CurrentThread.Name = "TAB Main"; + if (LogManager.Configuration.AllTargets.Count == 0) + { + Console.WriteLine("No or empty NLog config found. Please refer to https://github.com/NLog/NLog/wiki/Configuration-file" + + "to learn more how to set up the logging configuration."); + } + var core = new Core(); AppDomain.CurrentDomain.UnhandledException += (s, e) => { - Log.Write(Log.Level.Error, "Critical program failure!. Exception:\n{0}", (e.ExceptionObject as Exception).UnrollException()); + Log.Fatal(e.ExceptionObject as Exception, "Critical program failure!."); core.Dispose(); }; @@ -66,7 +71,7 @@ internal static void Main(string[] args) var initResult = core.InitializeCore(); if (!initResult) { - Log.Write(Log.Level.Error, "Core initialization failed: {0}", initResult.Error); + Log.Error("Core initialization failed: {0}", initResult.Error); core.Dispose(); return; } @@ -96,9 +101,6 @@ internal static void Main(string[] args) public Core() { // setting defaults - consoleOutput = true; - writeLog = true; - writeLogStack = false; configFilePath = "configTS3AudioBot.cfg"; } @@ -111,28 +113,11 @@ private bool ReadParameter(string[] args) case "-h": case "--help": Console.WriteLine(" --quiet -q Deactivates all output to stdout."); - Console.WriteLine(" --no-log -L Deactivates writing to the logfile."); - Console.WriteLine(" --stack -s Adds the stacktrace to all log writes."); Console.WriteLine(" --config -c Specifies the path to the config file."); Console.WriteLine(" --version -V Gets the bot version."); Console.WriteLine(" --help -h Prints this help...."); return false; - case "-q": - case "--quiet": - consoleOutput = false; - break; - - case "-L": - case "--no-log": - writeLog = false; - break; - - case "-s": - case "--stack": - writeLogStack = true; - break; - case "-c": case "--config": if (i >= args.Length - 1) @@ -163,6 +148,7 @@ private R InitializeCore() ConfigManager = ConfigFile.OpenOrCreate(configFilePath) ?? ConfigFile.CreateDummy(); var webd = ConfigManager.GetDataStruct("WebData", true); var rmd = ConfigManager.GetDataStruct("RightsManager", true); + var mbd = ConfigManager.GetDataStruct("MainBot", true); // TODO: DUMMY REQUESTS YoutubeDlHelper.DataObj = ConfigManager.GetDataStruct("YoutubeFactory", true); @@ -172,56 +158,18 @@ private R InitializeCore() ConfigManager.GetDataStruct("AudioFramework", true); ConfigManager.GetDataStruct("QueryConnection", true); ConfigManager.GetDataStruct("PlaylistManager", true); - mainBotData = ConfigManager.GetDataStruct("MainBot", true); // END TODO - - mainBotData = ConfigManager.GetDataStruct("MainBot", true); ConfigManager.Close(); - if (consoleOutput) - { - void ColorLog(string msg, Log.Level lvl) - { - switch (lvl) - { - case Log.Level.Debug: break; - case Log.Level.Info: Console.ForegroundColor = ConsoleColor.Cyan; break; - case Log.Level.Warning: Console.ForegroundColor = ConsoleColor.Yellow; break; - case Log.Level.Error: Console.ForegroundColor = ConsoleColor.Red; break; - default: throw new ArgumentOutOfRangeException(nameof(lvl), lvl, null); - } - Console.WriteLine(msg); - Console.ResetColor(); - } + Log.Info("[============ TS3AudioBot started =============]"); + Log.Info("[=== Date/Time: {0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString()); + Log.Info("[=== Version: {0}", Util.GetAssemblyData().ToString()); + Log.Info("[=== Plattform: {0}", Util.GetPlattformData()); + Log.Info("[==============================================]"); - Log.RegisterLogger("[%T]%L: %M", 19, ColorLog); - Log.RegisterLogger("Error call Stack:\n%S", 19, ColorLog, Log.Level.Error); - } - - if (writeLog && !string.IsNullOrEmpty(mainBotData.LogFile)) - { - logStream = new StreamWriter(File.Open(mainBotData.LogFile, FileMode.Append, FileAccess.Write, FileShare.Read), Util.Utf8Encoder); - Log.RegisterLogger("[%T]%L: %M\n" + (writeLogStack ? "%S\n" : ""), 19, (msg, lvl) => - { - if (logStream == null) return; - try - { - logStream.Write(msg); - logStream.Flush(); - } - catch (IOException) { } - }); - } - - Log.Write(Log.Level.Info, "[============ TS3AudioBot started =============]"); - Log.Write(Log.Level.Info, "[=== Date/Time: {0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString()); - Log.Write(Log.Level.Info, "[=== Version: {0}", Util.GetAssemblyData().ToString()); - Log.Write(Log.Level.Info, "[=== Plattform: {0}", Util.GetPlattformData()); - Log.Write(Log.Level.Info, "[==============================================]"); - - Log.Write(Log.Level.Info, "[============ Initializing Modules ============]"); - Log.Write(Log.Level.Info, "Using opus version: {0}", TS3Client.Full.Audio.Opus.NativeMethods.Info); - TS3Client.Messages.Deserializer.OnError += (s, e) => Log.Write(Log.Level.Error, e.ToString()); + Log.Info("[============ Initializing Modules ============]"); + Log.Info("Using opus version: {0}", TS3Client.Full.Audio.Opus.NativeMethods.Info); + TS3Client.Messages.Deserializer.OnError += (s, e) => Log.Error(e.ToString()); Injector = new Injector(); Injector.RegisterModule(this); @@ -240,16 +188,16 @@ void ColorLog(string msg, Log.Level lvl) if (!Injector.AllResolved()) { // TODO detailed log + for inner if - Log.Write(Log.Level.Warning, "Cyclic module dependency"); + Log.Warn("Cyclic module dependency"); Injector.ForceCyclicResolve(); if (!Injector.AllResolved()) { - Log.Write(Log.Level.Error, "Missing module dependency"); + Log.Error("Missing module dependency"); return "Could not load all modules"; } } - Log.Write(Log.Level.Info, "[==================== Done ====================]"); + Log.Info("[==================== Done ====================]"); return R.OkR; } @@ -260,7 +208,7 @@ private void Run() public void Dispose() { - Log.Write(Log.Level.Info, "TS3AudioBot shutting down."); + Log.Info("TS3AudioBot shutting down."); Bots?.Dispose(); Bots = null; @@ -278,9 +226,6 @@ public void Dispose() FactoryManager = null; TickPool.Close(); - - logStream?.Dispose(); - logStream = null; } } } diff --git a/TS3AudioBot/Helper/AudioTags/AudioTagReader.cs b/TS3AudioBot/Helper/AudioTags/AudioTagReader.cs index 7f779956..3f484d86 100644 --- a/TS3AudioBot/Helper/AudioTags/AudioTagReader.cs +++ b/TS3AudioBot/Helper/AudioTags/AudioTagReader.cs @@ -16,6 +16,7 @@ namespace TS3AudioBot.Helper.AudioTags internal static class AudioTagReader { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Dictionary TagDict; static AudioTagReader() @@ -38,8 +39,8 @@ public static string GetTitle(Stream fileStream) { try { return tagHeader.GetTitle(sr)?.TrimEnd('\0'); } catch (IOException) { } - catch (FormatException fex) { Log.Write(Log.Level.Debug, "ATR FEX: " + fex.Message); } - catch (NullReferenceException) { Log.Write(Log.Level.Debug, "ATR Unparsed Link!"); } + catch (FormatException fex) { Log.Debug(fex, "Audiotag format exception"); } + catch (NullReferenceException) { Log.Debug("Unparsed link!"); } } return null; } @@ -54,8 +55,8 @@ public static byte[] GetImage(Stream fileStream) { try { return tagHeader.GetImage(sr); } catch (IOException) { } - catch (FormatException fex) { Log.Write(Log.Level.Debug, "ATR FEX: " + fex.Message); } - catch (NullReferenceException) { Log.Write(Log.Level.Debug, "ATR Unparsed Link!"); } + catch (FormatException fex) { Log.Debug(fex, "Audiotag format exception"); } + catch (NullReferenceException) { Log.Debug("Unparsed link!"); } } return null; } diff --git a/TS3AudioBot/Helper/TickPool.cs b/TS3AudioBot/Helper/TickPool.cs index 429de5cb..884007cc 100644 --- a/TS3AudioBot/Helper/TickPool.cs +++ b/TS3AudioBot/Helper/TickPool.cs @@ -17,6 +17,7 @@ namespace TS3AudioBot.Helper [Serializable] public static class TickPool { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static bool run; private static readonly Thread tickThread; private static readonly object tickLock = new object(); @@ -120,7 +121,7 @@ public static void Close() } else { - Log.Write(Log.Level.Warning, "TickPool could not close correctly."); + Log.Warn("TickPool could not close correctly."); } tickLoopPulse.Set(); Util.WaitForThreadEnd(tickThread, MinTick); diff --git a/TS3AudioBot/Helper/Util.cs b/TS3AudioBot/Helper/Util.cs index 2150679f..139e8617 100644 --- a/TS3AudioBot/Helper/Util.cs +++ b/TS3AudioBot/Helper/Util.cs @@ -23,6 +23,7 @@ namespace TS3AudioBot.Helper [Serializable] public static class Util { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); public const RegexOptions DefaultRegexConfig = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ECMAScript; public static bool IsLinux @@ -81,7 +82,7 @@ public static bool IsAdmin catch (UnauthorizedAccessException) { return false; } catch (Exception) { - Log.Write(Log.Level.Warning, "Uncatched admin check."); + Log.Warn("Uncatched admin check."); return false; } } diff --git a/TS3AudioBot/Helper/WebWrapper.cs b/TS3AudioBot/Helper/WebWrapper.cs index 32c8d064..8a1e74fe 100644 --- a/TS3AudioBot/Helper/WebWrapper.cs +++ b/TS3AudioBot/Helper/WebWrapper.cs @@ -15,6 +15,7 @@ namespace TS3AudioBot.Helper internal static class WebWrapper { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(3); public static bool DownloadString(out string site, Uri link, params Tuple[] optionalHeaders) @@ -68,17 +69,17 @@ public static ValidateCode GetResponse(Uri link, Action body, TimeS HttpWebResponse errorResponse; if (webEx.Status == WebExceptionStatus.Timeout) { - Log.Write(Log.Level.Warning, "TH Request timed out"); + Log.Warn("Request timed out"); return ValidateCode.Timeout; } else if ((errorResponse = webEx.Response as HttpWebResponse) != null) { - Log.Write(Log.Level.Warning, $"TH Web error: [{(int)errorResponse.StatusCode}] {errorResponse.StatusCode}"); + Log.Warn("Web error: [{0}] {1}", (int)errorResponse.StatusCode, errorResponse.StatusCode); return ValidateCode.Restricted; } else { - Log.Write(Log.Level.Warning, $"TH Unknown request error: {webEx}"); + Log.Warn("Unknown request error: {0}", webEx); return ValidateCode.UnknownError; } } @@ -98,10 +99,9 @@ internal static R GetResponseUnsafe(Uri link, TimeSpan timeout) } catch (WebException webEx) { - HttpWebResponse errorResponse; if (webEx.Status == WebExceptionStatus.Timeout) return "WEB Request timed out"; - if ((errorResponse = webEx.Response as HttpWebResponse) != null) + if (webEx.Response is HttpWebResponse errorResponse) return $"WEB error: [{(int)errorResponse.StatusCode}] {errorResponse.StatusCode}"; return $"WEB Unknown request error: {webEx}"; } diff --git a/TS3AudioBot/History/HistoryManager.cs b/TS3AudioBot/History/HistoryManager.cs index 09409827..690684bb 100644 --- a/TS3AudioBot/History/HistoryManager.cs +++ b/TS3AudioBot/History/HistoryManager.cs @@ -18,6 +18,7 @@ namespace TS3AudioBot.History public sealed class HistoryManager { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private const int CurrentHistoryVersion = 1; private const string AudioLogEntriesTable = "audioLogEntries"; private const string ResourceTitleQueryColumn = "lowTitle"; @@ -77,7 +78,7 @@ public HistoryManager(HistoryManagerData hmd, DbStore database) goto default; default: - Log.Write(Log.Level.Info, "Database table \"{0}\" upgraded to {1}", AudioLogEntriesTable, meta.Version); + Log.Info("Database table \"{0}\" upgraded to {1}", AudioLogEntriesTable, meta.Version); break; } } @@ -106,7 +107,7 @@ private AudioLogEntry Store(HistorySaveData saveData) { ale = CreateLogEntry(saveData); if (ale == null) - Log.Write(Log.Level.Error, "AudioLogEntry could not be created!"); + Log.Error("AudioLogEntry could not be created!"); } else { diff --git a/TS3AudioBot/Log.cs b/TS3AudioBot/Log.cs deleted file mode 100644 index c15c1240..00000000 --- a/TS3AudioBot/Log.cs +++ /dev/null @@ -1,313 +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 . - -using TS3AudioBot.Helper; - -namespace TS3AudioBot -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Reflection; - using System.Text; - using System.Text.RegularExpressions; - using System.Threading; - - [Serializable] - public static class Log - { - private static readonly Regex LinebreakRegex = new Regex(@"(\r\n?|\n)", Util.DefaultRegexConfig); - private static readonly object WriteLock; - private static readonly List LoggerFormats; - - private static string[] spaceup; - private static string[] formatCache; - - private const string PlaceLogLevelSpaced = "{0}"; - private const string PlaceErrorTextFormatted = "{1}"; - private const string PlaceDateFormatted = "{2}"; - private const string PlaceStackTraceFormatted = "{3}"; - - static Log() - { - WriteLock = new object(); - LoggerFormats = new List(); - - Active = true; - formatCache = new string[0]; - - CalcSpaceLength(); - } - - public static bool Active { get; set; } - - private static void CalcSpaceLength() - { - string[] earr = Enum.GetNames(typeof(Level)); - - int longestelem = 0; - for (int i = 0; i < earr.Length; i++) - if (earr[i].Length > longestelem) - longestelem = earr[i].Length; - spaceup = new string[earr.Length]; - var strb = new StringBuilder(longestelem + 1); - for (int i = 0; i < earr.Length; i++) - { - strb.Append(' ', longestelem - earr[i].Length); - strb.Append(earr[i]); - spaceup[i] = strb.ToString(); - strb.Clear(); - } - } - - public static void RegisterLogger(string format, int linebreakIndent, CallbackActionDelegate callback) - => RegisterLogger(format, linebreakIndent, callback, 0); - - public static void RegisterLogger(string format, int linebreakIndent, CallbackActionDelegate callback, Level logLevel) - { - var tokenList = ParseAndValidate(format); - RegisterLogger(tokenList, linebreakIndent, callback, logLevel); - } - - private static List ParseAndValidate(string format) - { - var validator = new List(); - - int starttext = 0; - for (int c = 0; c < format.Length; c++) - { - bool specSymbol = format[c] == '%'; - bool endoftext = c >= format.Length - 1; - - if ((specSymbol && c - starttext > 0) || endoftext) // static format text - { - if (endoftext) c++; - validator.Add(new ParseToken(MethodBuildToken.Text, format.Substring(starttext, c - starttext))); - } - - if (specSymbol) - { - if (c + 1 < format.Length) - { - switch (format[c + 1]) - { - case 'L': // Level - validator.Add(new ParseToken(MethodBuildToken.LogLevelSpaced, null)); - break; - case 'M': // Message - validator.Add(new ParseToken(MethodBuildToken.ErrorTextFormatted, null)); - break; - case 'T': // Time - validator.Add(new ParseToken(MethodBuildToken.DateFormatted, null)); - break; - case 'S': // Stack - validator.Add(new ParseToken(MethodBuildToken.StackFormatted, null)); - break; - case '%': // %-Escape - validator.Add(new ParseToken(MethodBuildToken.Text, "%")); - break; - default: - throw new ArgumentException("Unrecognized variable name after '%' at char: " + c); - } - c++; - starttext = c + 1; - } - else - { - throw new ArgumentException("Missing variable name after '%' at char: " + c); - } - } - } - - return validator; - } - - private static void RegisterLogger(List tokenList, int linebreakIndent, CallbackActionDelegate callback, Level logLevel) - { - bool usesLls = false, usesEtf = false, usesDf = false, usesStf = false; - var formatString = new StringBuilder(); - foreach (var part in tokenList) - { - switch (part.TokenType) - { - case MethodBuildToken.Text: - formatString.Append(part.Value); - break; - case MethodBuildToken.LogLevelSpaced: - formatString.Append(PlaceLogLevelSpaced); - usesLls = true; - break; - case MethodBuildToken.ErrorTextFormatted: - formatString.Append(PlaceErrorTextFormatted); - usesEtf = true; - break; - case MethodBuildToken.DateFormatted: - formatString.Append(PlaceDateFormatted); - usesDf = true; - break; - case MethodBuildToken.StackFormatted: - formatString.Append(PlaceStackTraceFormatted); - usesStf = true; - break; - default: - throw Util.UnhandledDefault(part.TokenType); - } - } - - lock (WriteLock) - { - LoggerFormats.Add(new LogAction( - formatString.ToString(), - linebreakIndent, - callback, - logLevel, - usesLls, usesEtf, usesDf, usesStf)); - - if (formatCache.Length <= linebreakIndent) - formatCache = new string[linebreakIndent + 1]; - } - } - - public static void Write(Level lvl, string errText, params object[] infos) - { - if (!Active) - return; - - var stack = new StackTrace(1); - lock (WriteLock) - { - string logLevelSpaced = null; - string dateFormatted = null; - string stackTraceFormatted = null; - - foreach (var callbackProc in LoggerFormats) - { - if (lvl < callbackProc.LogLevel) - continue; - - if (callbackProc.UsesLogLevelSpaced && logLevelSpaced == null) - logLevelSpaced = GenLogLevelRaw(lvl); - if (callbackProc.UsesErrorTextFormatted && formatCache[callbackProc.FormatIndent] == null) - formatCache[callbackProc.FormatIndent] = GenErrorTextFormatted(callbackProc.FormatIndent, errText, infos); - if (callbackProc.UsesDateFormatted && dateFormatted == null) - dateFormatted = GenDateFormatted(); - if (callbackProc.UsesStackTraceFormatted && stackTraceFormatted == null) - stackTraceFormatted = GenStackTraceFormatted(stack); - - callbackProc.Callback.Invoke( - string.Format(callbackProc.FormatString, - logLevelSpaced, - formatCache[callbackProc.FormatIndent], - dateFormatted, - stackTraceFormatted), - lvl); - } - - Array.Clear(formatCache, 0, formatCache.Length); - } - } - - private static string GenLogLevelRaw(Level lvl) => spaceup[(int)lvl]; - - private static string GenErrorTextFormatted(int linebreakIndent, string errorTextRaw, object[] infos) - { - var text = string.Format(CultureInfo.InvariantCulture, errorTextRaw, infos); - if (linebreakIndent > 0) - { - string indentedSpace = Environment.NewLine + new string(' ', linebreakIndent); - text = LinebreakRegex.Replace(text, x => indentedSpace); - } - return text; - } - - private static string GenDateFormatted() => DateTime.Now.ToString("HH:mm:ss", CultureInfo.InvariantCulture); - - private static string GenStackTraceFormatted(StackTrace stackTrace) - { - var strb = new StringBuilder(); - foreach (var frames in stackTrace.GetFrames()) - { - var method = frames.GetMethod(); - bool endOfIl = method.MethodImplementationFlags == MethodImplAttributes.InternalCall; - if (endOfIl) - strb.Append("$internal"); - else - strb.Append(method).Append('@').Append(frames.GetFileLineNumber()); // TODO Add classname - strb.Append('\n'); - if (endOfIl) break; - } - - if (strb.Length > 0) - strb.Length--; // remove last char - strb.Append(" T:").Append(Thread.CurrentThread.Name); - return strb.ToString(); - } - - private enum MethodBuildToken - { - Text, - LogLevelSpaced, - ErrorTextFormatted, - DateFormatted, - StackFormatted, - } - - public enum Level - { - Debug, - Info, - Warning, - Error, - } - - private class ParseToken - { - public readonly MethodBuildToken TokenType; - public readonly object Value; - - public ParseToken(MethodBuildToken tokenType, object value) - { - TokenType = tokenType; - Value = value; - } - } - - private class LogAction - { - public string FormatString { get; } - public int FormatIndent { get; } - public CallbackActionDelegate Callback { get; } - public Level LogLevel { get; } - - public bool UsesLogLevelSpaced { get; } - public bool UsesErrorTextFormatted { get; } - public bool UsesDateFormatted { get; } - public bool UsesStackTraceFormatted { get; } - - public LogAction(string formatString, int formatIndent, - CallbackActionDelegate callback, Level logLevel, - bool usesLls, bool usesEtf, bool usesDf, bool usesStf) - { - FormatString = formatString; - FormatIndent = formatIndent; - Callback = callback; - LogLevel = logLevel; - - UsesLogLevelSpaced = usesLls; - UsesErrorTextFormatted = usesEtf; - UsesDateFormatted = usesDf; - UsesStackTraceFormatted = usesStf; - } - } - } - - [Serializable] - public delegate void CallbackActionDelegate(string result, Log.Level level); -} diff --git a/TS3AudioBot/NLog.config b/TS3AudioBot/NLog.config new file mode 100644 index 00000000..4489227c --- /dev/null +++ b/TS3AudioBot/NLog.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/TS3AudioBot/PlayManager.cs b/TS3AudioBot/PlayManager.cs index 05629c7a..3d62cd75 100644 --- a/TS3AudioBot/PlayManager.cs +++ b/TS3AudioBot/PlayManager.cs @@ -17,6 +17,7 @@ namespace TS3AudioBot public class PlayManager { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private readonly Core core; private readonly Bot botParent; private IPlayerConnection PlayerConnection => botParent.PlayerConnection; @@ -163,11 +164,11 @@ private R StartResource(PlayResource playResource, MetaData config) if (string.IsNullOrWhiteSpace(playResource.PlayUri)) return "Internal resource error: link is empty"; - Log.Write(Log.Level.Debug, "PM ar start: {0}", playResource); + Log.Debug("AudioResource start: {0}", playResource); var result = PlayerConnection.AudioStart(playResource.PlayUri); if (!result) { - Log.Write(Log.Level.Error, "Error return from player: {0}", result.Error); + Log.Error("Error return from player: {0}", result.Error); return $"Internal player error ({result.Error})"; } @@ -217,10 +218,10 @@ private void StopInternal(bool songEndedByCallback) if (songEndedByCallback && CurrentPlayData != null) { - R result = Next(CurrentPlayData.Invoker); + var result = Next(CurrentPlayData.Invoker); if (result) return; - Log.Write(Log.Level.Warning, nameof(SongStoppedHook) + " could not play Next: " + result.Error); + Log.Warn(nameof(SongStoppedHook) + " could not play Next: {0}", result.Error); } else { diff --git a/TS3AudioBot/PlaylistManager.cs b/TS3AudioBot/PlaylistManager.cs index da4ac13d..d11479bd 100644 --- a/TS3AudioBot/PlaylistManager.cs +++ b/TS3AudioBot/PlaylistManager.cs @@ -21,6 +21,7 @@ namespace TS3AudioBot public sealed class PlaylistManager { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex ValidPlistName = new Regex(@"^[\w-]+$", Util.DefaultRegexConfig); private static readonly Regex CleansePlaylistName = new Regex(@"[^\w-]", Util.DefaultRegexConfig); @@ -205,7 +206,7 @@ public R LoadPlaylist(string name, bool headOnly = false) var kvp = line.Split(new[] { ':' }, 3); if (kvp.Length < 3) { - Log.Write(Log.Level.Warning, "Erroneus playlist split count: {0}", line); + Log.Warn("Erroneus playlist split count: {0}", line); continue; } string kind = kvp[0]; @@ -218,7 +219,7 @@ public R LoadPlaylist(string name, bool headOnly = false) else if (ulong.TryParse(optOwner, out var userid)) meta.ResourceOwnerDbId = userid; else - Log.Write(Log.Level.Warning, "Erroneus playlist meta data: {0}", line); + Log.Warn("Erroneus playlist meta data: {0}", line); switch (kind) { @@ -251,7 +252,7 @@ public R LoadPlaylist(string name, bool headOnly = false) break; default: - Log.Write(Log.Level.Warning, "Erroneus playlist data block: {0}", line); + Log.Warn("Erroneus playlist data block: {0}", line); break; } } diff --git a/TS3AudioBot/Plugins/Plugin.cs b/TS3AudioBot/Plugins/Plugin.cs index 5afcdbc0..6b1c8835 100644 --- a/TS3AudioBot/Plugins/Plugin.cs +++ b/TS3AudioBot/Plugins/Plugin.cs @@ -23,6 +23,7 @@ namespace TS3AudioBot.Plugins internal class Plugin : ICommandBag { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private readonly Core core; private Assembly assembly; @@ -108,7 +109,7 @@ public PluginResponse Load() } catch (BadImageFormatException bifex) { - Log.Write(Log.Level.Warning, "Plugin \"{0}\" has an invalid format: {1} (Add a \"{0}.ignore\" file to ignore this file)", + Log.Warn("Plugin \"{0}\" has an invalid format: {1} (Add a \"{0}.ignore\" file to ignore this file)", PluginFile.Name, bifex.InnerException?.Message ?? bifex.Message); Status = PluginStatus.Error; @@ -116,7 +117,7 @@ public PluginResponse Load() } catch (Exception ex) { - Log.Write(Log.Level.Warning, "Plugin \"{0}\" failed to prepare: {1}", + Log.Warn("Plugin \"{0}\" failed to prepare: {1}", PluginFile.Name, ex.Message); Status = PluginStatus.Error; @@ -189,7 +190,7 @@ private PluginResponse PrepareSource() error.ErrorText); } strb.Length -= 1; // remove last linebreak - Log.Write(Log.Level.Warning, strb.ToString()); + Log.Warn(strb.ToString()); if (containsErrors) { @@ -211,12 +212,12 @@ private PluginResponse InitlializeAssembly(Assembly assembly) if (pluginTypes.Length + factoryTypes.Length > 1) { - Log.Write(Log.Level.Warning, "Any source or binary plugin file may contain one plugin or factory at most."); + Log.Warn("Any source or binary plugin file may contain one plugin or factory at most."); return PluginResponse.TooManyPlugins; } if (pluginTypes.Length + factoryTypes.Length == 0) { - Log.Write(Log.Level.Warning, "Any source or binary plugin file must contain at least one plugin or factory."); + Log.Warn("Any source or binary plugin file must contain at least one plugin or factory."); return PluginResponse.NoTypeMatch; } @@ -240,13 +241,12 @@ private PluginResponse InitlializeAssembly(Assembly assembly) } catch (TypeLoadException tlex) { - Log.Write(Log.Level.Warning, - $"{nameof(InitlializeAssembly)} failed, The file \"{PluginFile.Name}\" seems to be missing some dependecies ({tlex.Message})"); + Log.Warn(nameof(InitlializeAssembly) + " failed, The file \"{0}\" seems to be missing some dependecies ({1})", PluginFile.Name, tlex.Message); return PluginResponse.MissingDependency; } catch (Exception ex) { - Log.Write(Log.Level.Error, $"{nameof(InitlializeAssembly)} failed ({ex})"); + Log.Error(ex, nameof(InitlializeAssembly) + " failed"); return PluginResponse.Crash; } } @@ -288,7 +288,7 @@ public void Start() } catch (MissingMethodException mmex) { - Log.Write(Log.Level.Error, "Plugins and Factories needs a parameterless constructor ({0}).", mmex.Message); + Log.Error(mmex, "Plugins and Factories needs a parameterless constructor."); Status = PluginStatus.Error; return; } diff --git a/TS3AudioBot/Plugins/PluginManager.cs b/TS3AudioBot/Plugins/PluginManager.cs index 7f952915..7697eeb6 100644 --- a/TS3AudioBot/Plugins/PluginManager.cs +++ b/TS3AudioBot/Plugins/PluginManager.cs @@ -32,6 +32,7 @@ namespace TS3AudioBot.Plugins internal class PluginManager : IDisposable, Dependency.ICoreModule { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); public Core Core { get; set; } public ConfigFile Config { get; set; } @@ -184,7 +185,7 @@ private PluginResponse StartPlugin(Plugin plugin) catch (Exception ex) { StopPlugin(plugin); - Log.Write(Log.Level.Warning, "Plugin \"{0}\" failed to load: {1}", + Log.Warn("Plugin \"{0}\" failed to load: {1}", plugin.PluginFile.Name, ex); return PluginResponse.UnknownError; diff --git a/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs b/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs index 77bdc6e0..b25c5e09 100644 --- a/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs +++ b/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs @@ -19,6 +19,7 @@ namespace TS3AudioBot.ResourceFactories public sealed class SoundcloudFactory : IResourceFactory, IPlaylistFactory, IThumbnailFactory { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex SoundcloudLink = new Regex(@"^https?\:\/\/(www\.)?soundcloud\.", Util.DefaultRegexConfig); private const string SoundcloudClientId = "a9dd3403f858e105d7e266edc162a0c5"; @@ -88,21 +89,19 @@ private AudioResource ParseDictToResource(Dictionary dict) private R YoutubeDlWrapped(string link) { - Log.Write(Log.Level.Debug, "SC Ruined!"); + Log.Debug("Falling back to youtube-dl!"); var result = YoutubeDlHelper.FindAndRunYoutubeDl(link); if (!result.Ok) return result.Error; - var response = result.Value; - string title = response.title; - string url = response.links.FirstOrDefault(); - if (response.links.Count == 0 || string.IsNullOrEmpty(title) || string.IsNullOrEmpty(url)) + var (title, urls) = result.Value; + if (urls.Count == 0 || string.IsNullOrEmpty(title) || string.IsNullOrEmpty(urls[0])) return "No youtube-dl response"; - Log.Write(Log.Level.Debug, "SC Saved!"); + Log.Debug("youtube-dl succeeded!"); - return new PlayResource(url, new AudioResource(link, title, FactoryFor)); + return new PlayResource(urls[0], new AudioResource(link, title, FactoryFor)); } public R GetPlaylist(string url) diff --git a/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs b/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs index 0c3ba39a..c4adb783 100644 --- a/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs +++ b/TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs @@ -15,8 +15,9 @@ namespace TS3AudioBot.ResourceFactories using System.Diagnostics; using System.IO; - static class YoutubeDlHelper + internal static class YoutubeDlHelper { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); public static YoutubeFactoryData DataObj { private get; set; } private static string YoutubeDlPath => DataObj?.YoutubedlPath; @@ -45,7 +46,7 @@ public static (string ytdlpath, string param)? FindYoutubeDl(string id) try { fullCustomPath = Path.GetFullPath(YoutubeDlPath); } catch (ArgumentException) { - Log.Write(Log.Level.Warning, "Your youtube-dl path contains invalid characters"); + Log.Warn("Your youtube-dl path contains invalid characters"); return null; } @@ -86,7 +87,7 @@ public static (string ytdlpath, string param)? FindYoutubeDl(string id) string result = reader.ReadToEnd(); if (!string.IsNullOrEmpty(result)) { - Log.Write(Log.Level.Error, "youtube-dl failed to load the resource:\n{0}", result); + Log.Error("youtube-dl failed to load the resource:\n{0}", result); return "youtube-dl failed to load the resource"; } } diff --git a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs index 1201048d..6ae68a9c 100644 --- a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs +++ b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs @@ -22,6 +22,7 @@ namespace TS3AudioBot.ResourceFactories public sealed class YoutubeFactory : IResourceFactory, IPlaylistFactory, IThumbnailFactory { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex IdMatch = new Regex(@"((&|\?)v=|youtu\.be\/)([\w\-_]+)", Util.DefaultRegexConfig); private static readonly Regex LinkMatch = new Regex(@"^(https?\:\/\/)?(www\.|m\.)?(youtube\.|youtu\.be)", Util.DefaultRegexConfig); private static readonly Regex ListMatch = new Regex(@"(&|\?)list=([\w\-_]+)", Util.DefaultRegexConfig); @@ -57,8 +58,7 @@ public R GetResourceById(AudioResource resource) var result = ResolveResourceInternal(resource); if (result.Ok) return result; - - Log.Write(Log.Level.Debug, "YT Falling back to youtube-dl!"); + return YoutubeDlWrapped(resource); } @@ -162,7 +162,7 @@ private static int SelectStream(List list) var dbg = new StringBuilder("YT avail codecs: "); foreach (var yd in list) dbg.Append(yd.Qualitydesciption).Append(" @ ").Append(yd.Codec).Append(", "); - Log.Write(Log.Level.Debug, dbg.ToString()); + Log.Trace(dbg.ToString()); #endif int autoselectIndex = list.FindIndex(t => t.Codec == VideoCodec.M4A); @@ -202,9 +202,8 @@ private static VideoCodec GetCodec(string type) } else return VideoCodec.Unknown; - string extractedCodec; int codecEnd; - extractedCodec = (codecEnd = codecSubStr.IndexOf(';')) >= 0 ? codecSubStr.Substring(0, codecEnd) : codecSubStr; + var extractedCodec = (codecEnd = codecSubStr.IndexOf(';')) >= 0 ? codecSubStr.Substring(0, codecEnd) : codecSubStr; switch (extractedCodec) { @@ -316,6 +315,8 @@ public static string LoadAlternative(string id) private static R YoutubeDlWrapped(AudioResource resource) { + Log.Debug("Falling back to youtube-dl!"); + var result = YoutubeDlHelper.FindAndRunYoutubeDl(resource.ResourceId); if (!result.Ok) return result.Error; @@ -342,7 +343,7 @@ private static R YoutubeDlWrapped(AudioResource resource) if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(url)) return "No youtube-dl response"; - Log.Write(Log.Level.Debug, "YT youtube-dl succeeded!"); + Log.Debug("youtube-dl succeeded!"); return new PlayResource(url, resource.WithName(title)); } diff --git a/TS3AudioBot/Rights/RightsManager.cs b/TS3AudioBot/Rights/RightsManager.cs index eaed694f..83d8431b 100644 --- a/TS3AudioBot/Rights/RightsManager.cs +++ b/TS3AudioBot/Rights/RightsManager.cs @@ -20,6 +20,7 @@ namespace TS3AudioBot.Rights public class RightsManager : Dependency.ICoreModule { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private const int RuleLevelSize = 2; public ConfigFile Config { get; set; } @@ -53,7 +54,7 @@ public void Initialize() RegisterRights(CommandManager.AllRights); RegisterRights(Commands.RightHighVolume, Commands.RightDeleteAllPlaylists); if (!ReadFile()) - Log.Write(Log.Level.Error, "Could not read Permission file."); + Log.Error("Could not read Permission file."); } public void RegisterRights(params string[] rights) => RegisterRights((IEnumerable)rights); @@ -182,7 +183,7 @@ public bool ReadFile() { if (!File.Exists(rightsManagerData.RightsFile)) { - Log.Write(Log.Level.Info, "No rights file found. Creating default."); + Log.Info("No rights file found. Creating default."); using (var fs = File.OpenWrite(rightsManagerData.RightsFile)) using (var data = Util.GetEmbeddedFile("TS3AudioBot.Rights.DefaultRights.toml")) data.CopyTo(fs); @@ -192,14 +193,14 @@ public bool ReadFile() var ctx = new ParseContext(); RecalculateRights(table, ctx); foreach (var err in ctx.Errors) - Log.Write(Log.Level.Error, err); + Log.Error(err); foreach (var warn in ctx.Warnings) - Log.Write(Log.Level.Warning, warn); + Log.Warn(warn); return ctx.Errors.Count == 0; } catch (Exception ex) { - Log.Write(Log.Level.Error, "The rights file could not be parsed: {0}", ex); + Log.Error(ex, "The rights file could not be parsed"); return false; } } diff --git a/TS3AudioBot/Sessions/SessionManager.cs b/TS3AudioBot/Sessions/SessionManager.cs index e2758a2f..a29f8832 100644 --- a/TS3AudioBot/Sessions/SessionManager.cs +++ b/TS3AudioBot/Sessions/SessionManager.cs @@ -17,6 +17,7 @@ namespace TS3AudioBot.Sessions public class SessionManager { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private const string TokenFormat = "{0}:" + Web.WebManager.WebRealm + ":{1}"; // Map: Id => UserSession @@ -49,7 +50,7 @@ public UserSession CreateSession(Bot bot, ClientData client) if (openSessions.TryGetValue(client.ClientId, out var session)) return session; - Log.Write(Log.Level.Debug, "SM User {0} created session with the bot", client.NickName); + Log.Debug("User {0} created session with the bot", client.NickName); session = new UserSession(bot, client); openSessions.Add(client.ClientId, session); return session; diff --git a/TS3AudioBot/Sessions/UserSession.cs b/TS3AudioBot/Sessions/UserSession.cs index 8f04db10..27ec97f7 100644 --- a/TS3AudioBot/Sessions/UserSession.cs +++ b/TS3AudioBot/Sessions/UserSession.cs @@ -19,6 +19,7 @@ namespace TS3AudioBot.Sessions public sealed class UserSession { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private Dictionary assocMap; private bool lockToken; private readonly ClientData client; @@ -57,7 +58,7 @@ public void Write(string message, TextMessageTargetMode targetMode) } if (!result) - Log.Write(Log.Level.Error, "Could not write message (Err:{0}) (Msg:{1})", result.Error, message); + Log.Error("Could not write message (Err:{0}) (Msg:{1})", result.Error, message); } public void SetResponse(Response responseProcessor, object responseData) diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index 1d6fd0b4..31497eb8 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -64,6 +64,9 @@ ..\packages\Nett.0.8.0\lib\Net40\Nett.dll + + ..\packages\NLog.4.4.12\lib\net45\NLog.dll + ..\packages\PropertyChanged.Fody.2.1.4\lib\netstandard1.0\PropertyChanged.dll False @@ -138,7 +141,6 @@ - @@ -206,6 +208,9 @@ + + PreserveNewest + diff --git a/TS3AudioBot/TargetScript.cs b/TS3AudioBot/TargetScript.cs index a8b23126..aa42ee50 100644 --- a/TS3AudioBot/TargetScript.cs +++ b/TS3AudioBot/TargetScript.cs @@ -14,6 +14,7 @@ namespace TS3AudioBot internal class TargetScript { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private const string DefaultVoiceScript = "!whisper off"; private const string DefaultWhisperScript = "!xecute (!whisper subscription) (!unsubscribe temporary) (!subscribe channeltemp (!getuser channel))"; @@ -39,7 +40,7 @@ public void BeforeResourceStarted(object sender, PlayInfoEventArgs e) script = DefaultWhisperScript; else { - Log.Write(Log.Level.Error, "Invalid voice mode"); + Log.Error("Invalid voice mode"); return; } CallScript(script, e.Invoker); diff --git a/TS3AudioBot/TeamspeakControl.cs b/TS3AudioBot/TeamspeakControl.cs index e054eb35..f79b65cc 100644 --- a/TS3AudioBot/TeamspeakControl.cs +++ b/TS3AudioBot/TeamspeakControl.cs @@ -23,6 +23,8 @@ namespace TS3AudioBot public abstract class TeamspeakControl : IDisposable { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + public event EventHandler OnMessageReceived; private void ExtendedTextMessage(object sender, IEnumerable eventArgs) { @@ -145,7 +147,7 @@ public R GetClientById(ushort id) { var result = ClientBufferRequest(client => client.ClientId == id); if (result.Ok) return result; - Log.Write(Log.Level.Debug, "Slow double request, due to missing or wrong permission confinguration!"); + Log.Debug("Slow double request due to missing or wrong permission confinguration!"); var result2 = tsBaseClient.Send("clientinfo", new CommandParameter("clid", id)).WrapSingle(); if (!result2.Ok) return "No client found"; @@ -325,7 +327,7 @@ internal R SetupRights(string key, MainBotData mainBotData) if (!result) { - Log.Write(Log.Level.Warning, permresult.Error.ErrorFormat()); + Log.Warn(permresult.Error.ErrorFormat()); return "Auto setup failed! (See logs for more details)"; } diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index 8063b239..e3722045 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -22,6 +22,7 @@ namespace TS3AudioBot internal sealed class Ts3Full : TeamspeakControl, IPlayerConnection { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private readonly Ts3FullClient tsFullClient; private ClientData self; @@ -115,14 +116,14 @@ public override void Connect() { if (Ts3Crypt.GetSecurityLevel(identity) < targetLevel) { - Log.Write(Log.Level.Info, "Calculating up to required security level: {0}", targetLevel); + Log.Info("Calculating up to required security level: {0}", targetLevel); Ts3Crypt.ImproveSecurity(identity, targetLevel); ts3FullClientData.IdentityOffset = identity.ValidKeyOffset; } } else { - Log.Write(Log.Level.Warning, "Invalid value for QueryConnection::IdentityLevel, enter a number or \"auto\"."); + Log.Warn("Invalid value for QueryConnection::IdentityLevel, enter a number or \"auto\"."); } @@ -161,7 +162,7 @@ private void ConnectClient() if (verionSign == null) { - Log.Write(Log.Level.Warning, "Invalid version sign, falling back to unknown :P"); + Log.Warn("Invalid version sign, falling back to unknown :P"); verionSign = VersionSign.VER_WIN_3_X_X; } } @@ -192,7 +193,7 @@ private void TsFullClient_OnErrorEvent(object sender, CommandError error) break; default: - Log.Write(Log.Level.Debug, "Got ts3 error event: {0}", error.ErrorFormat()); + Log.Debug("Got ts3 error event: {0}", error.ErrorFormat()); break; } } @@ -208,7 +209,7 @@ private void TsFullClient_OnDisconnected(object sender, DisconnectEventArgs e) if (ts3FullClientData.IdentityLevel == "auto") { int targetSecLevel = int.Parse(error.ExtraMessage); - Log.Write(Log.Level.Info, "Calculating up to required security level: {0}", targetSecLevel); + Log.Info("Calculating up to required security level: {0}", targetSecLevel); Ts3Crypt.ImproveSecurity(identity, targetSecLevel); ts3FullClientData.IdentityOffset = identity.ValidKeyOffset; @@ -217,19 +218,19 @@ private void TsFullClient_OnDisconnected(object sender, DisconnectEventArgs e) } else { - Log.Write(Log.Level.Warning, "The server reported that the security level you set is not high enough." + - "Increase the value to \"{0}\" or set it to \"auto\" to generate it on demand when connecting.", error.ExtraMessage); + Log.Warn("The server reported that the security level you set is not high enough." + + "Increase the value to \"{0}\" or set it to \"auto\" to generate it on demand when connecting.", error.ExtraMessage); } break; default: - Log.Write(Log.Level.Warning, "Could not connect: {0}", error.ErrorFormat()); + Log.Warn("Could not connect: {0}", error.ErrorFormat()); break; } } else { - Log.Write(Log.Level.Debug, "Bot disconnected. Reason: {0}", e.ExitReason); + Log.Debug("Bot disconnected. Reason: {0}", e.ExitReason); } OnBotDisconnect?.Invoke(this, new EventArgs()); diff --git a/TS3AudioBot/Web/Api/WebApi.cs b/TS3AudioBot/Web/Api/WebApi.cs index 16d6f6d8..0d5bb964 100644 --- a/TS3AudioBot/Web/Api/WebApi.cs +++ b/TS3AudioBot/Web/Api/WebApi.cs @@ -22,6 +22,7 @@ namespace TS3AudioBot.Web.Api public sealed class WebApi : WebComponent { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex DigestMatch = new Regex(@"\s*(\w+)\s*=\s*""([^""]*)""\s*,?", Util.DefaultRegexConfig); private static readonly MD5 Md5Hash = MD5.Create(); @@ -36,7 +37,7 @@ public override void DispatchCall(HttpListenerContext context) var invoker = Authenticate(context); if (invoker == null) { - Log.Write(Log.Level.Debug, "Not authorized!"); + Log.Debug("Unauthorized request!"); ReturnError(CommandExceptionReason.Unauthorized, "", context.Response); return; } @@ -83,7 +84,7 @@ private void ProcessApiV1Call(Uri uri, HttpListenerResponse response, InvokerDat response.StatusCode = (int)HttpStatusCode.NotImplemented; else response.StatusCode = (int)HttpStatusCode.InternalServerError; - Log.Write(Log.Level.Error, "WA Unexpected command error: {0}", ex); + Log.Error(ex, "Unexpected command error"); using (var responseStream = new StreamWriter(response.OutputStream)) responseStream.Write(new JsonError(ex.Message, CommandExceptionReason.Unknown).Serialize()); } diff --git a/TS3AudioBot/Web/WebManager.cs b/TS3AudioBot/Web/WebManager.cs index 3f876c11..2a78490c 100644 --- a/TS3AudioBot/Web/WebManager.cs +++ b/TS3AudioBot/Web/WebManager.cs @@ -17,6 +17,7 @@ namespace TS3AudioBot.Web public sealed class WebManager : Dependency.ICoreModule, IDisposable { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); public const string WebRealm = "ts3ab"; private Uri localhost; @@ -87,7 +88,7 @@ private void ReloadHostPaths() } else { - Log.Write(Log.Level.Warning, "App launched without elevated rights. Only localhost will be availbale as api server."); + Log.Warn("App launched without elevated rights. Only localhost will be availbale as api server."); hostPaths = new[] { localhost }; } @@ -132,7 +133,7 @@ public void EnterWebLoop() try { webListener.Start(); } catch (HttpListenerException ex) { - Log.Write(Log.Level.Error, "The webserver could not be started ({0})", ex.Message); + Log.Error(ex, "The webserver could not be started"); return; } // TODO @@ -150,7 +151,7 @@ public void EnterWebLoop() } catch (NullReferenceException) { return; } - Log.Write(Log.Level.Info, "{0} Requested: {1}", remoteAddress, context.Request.Url.PathAndQuery); + Log.Info("{0} Requested: {1}", remoteAddress, context.Request.Url.PathAndQuery); if (context.Request.Url.AbsolutePath.StartsWith("/api/", true, CultureInfo.InvariantCulture)) Api?.DispatchCall(context); else diff --git a/TS3AudioBot/packages.config b/TS3AudioBot/packages.config index a8d94a4c..c7b243ce 100644 --- a/TS3AudioBot/packages.config +++ b/TS3AudioBot/packages.config @@ -4,6 +4,7 @@ + diff --git a/TS3Client/ColorDbg.cs b/TS3Client/ColorDbg.cs deleted file mode 100644 index 6ad5b26f..00000000 --- a/TS3Client/ColorDbg.cs +++ /dev/null @@ -1,154 +0,0 @@ -namespace TS3Client -{ - using Full; - using Helper; - using System; - using System.Diagnostics; - using System.Runtime.CompilerServices; - - internal static class ColorDbg - { - private static void WriteType(string type, ConsoleColor typeColor = ConsoleColor.Cyan) - { - Console.ForegroundColor = typeColor; - Console.Write("{0} ", type); - Console.ResetColor(); - } - - private static void Write(string text, ConsoleColor color) - { - Console.ForegroundColor = color; - Console.Write(text); - Console.ResetColor(); - } - - [Conditional("COLOG")] - [MethodImpl(MethodImplOptions.Synchronized)] - public static void WriteLine(string type, string text) - { - WriteType(type); - Write(text, ConsoleColor.Gray); - Console.WriteLine(); - } - - [Conditional("COLOG_RTT")] - [MethodImpl(MethodImplOptions.Synchronized)] - public static void WriteRtt(TimeSpan smoothedRtt, TimeSpan smoothedRttVar, TimeSpan currentRto) - { - WriteType("RTT"); - Console.Write("SRTT:"); - Write(smoothedRtt.ToString(), ConsoleColor.Cyan); - Console.Write("RTTVAR:"); - Write(smoothedRttVar.ToString(), ConsoleColor.Cyan); - Console.Write("RTO:"); - Write(currentRto.ToString(), ConsoleColor.Cyan); - Console.WriteLine(); - } - - [Conditional("COLOG_CMD")] - [MethodImpl(MethodImplOptions.Synchronized)] - public static void WriteCmd(string cmd, bool send) - { - WriteType(send ? "[O]" : "[I]"); - Console.WriteLine(cmd); - } - - [Conditional("COLOG_RAWPKG")] - [MethodImpl(MethodImplOptions.Synchronized)] - public static void WritePkgOut(OutgoingPacket packet) - { - if (packet.PacketType == PacketType.Ping || packet.PacketType == PacketType.Pong) - return; - WriteType("[O]"); - switch (packet.PacketType) - { - case PacketType.Init1: - Console.Write("InitID: "); - Write(packet.Data[4].ToString(), ConsoleColor.Magenta); - break; - case PacketType.Ack: - case PacketType.AckLow: - Console.Write("Acking: "); - Write(NetUtil.N2Hushort(packet.Data, 0).ToString(), ConsoleColor.Magenta); - break; - default: - Console.Write(packet); - break; - } - Console.WriteLine(); - } - - [Conditional("COLOG_RAWPKG")] - [MethodImpl(MethodImplOptions.Synchronized)] - public static void WritePkgIn(IncomingPacket packet) - { - if (packet.PacketType == PacketType.Ping || packet.PacketType == PacketType.Pong) - return; - WriteType("[I]"); - switch (packet.PacketType) - { - case PacketType.Init1: - Console.Write("InitID: "); - Write(packet.Data[0].ToString(), ConsoleColor.Magenta); - break; - case PacketType.Ack: - case PacketType.AckLow: - Console.Write("Acking: "); - Write(NetUtil.N2Hushort(packet.Data, 0).ToString(), ConsoleColor.Magenta); - break; - default: - Console.Write(packet); - break; - } - Console.WriteLine(); - } - - [Conditional("COLOG_RAWPKG")] - [MethodImpl(MethodImplOptions.Synchronized)] - public static void WritePkgRaw(byte[] data, string op) - { - WriteType("[I]"); - switch (op) - { - case "DROPPING": Write("DROPPING ", ConsoleColor.DarkRed); break; - case "RAW": Write("RAW ", ConsoleColor.Cyan); break; - } - //Console.WriteLine(Encoding.ASCII.GetString(data)); - Console.WriteLine(DebugUtil.DebugToHex(data)); - } - - [Conditional("COLOG_TIMEOUT")] - [MethodImpl(MethodImplOptions.Synchronized)] - public static void WriteResend(BasePacket packet, string op = "") - { - WriteType("PKG"); - switch (op) - { - case "RESEND": Write("RESEND ", ConsoleColor.Yellow); break; - case "TIMEOUT": Write("TIMEOUT ", ConsoleColor.Red); break; - case "QUEUE": Write("IN QUEUE ", ConsoleColor.Green); break; - } - Console.WriteLine(packet); - } - - [Conditional("COLOG_DETAILED")] - [MethodImpl(MethodImplOptions.Synchronized)] - public static void WriteDetail(string detail, string plus = "") - { - WriteType("+++"); - switch (plus) - { - case "INIT": Write("INIT ", ConsoleColor.Magenta); break; - } - Console.WriteLine(detail); - } - - [Conditional("DEBUG")] - [MethodImpl(MethodImplOptions.Synchronized)] - public static void WriteErrParse(string errMsg) - { - WriteType("ERR"); - Write(errMsg, ConsoleColor.Red); - } - } -} diff --git a/TS3Client/Full/PacketHandler.cs b/TS3Client/Full/PacketHandler.cs index 37986874..df095631 100644 --- a/TS3Client/Full/PacketHandler.cs +++ b/TS3Client/Full/PacketHandler.cs @@ -10,6 +10,7 @@ namespace TS3Client.Full { using Helper; + using NLog; using System; using System.Collections.Generic; using System.Diagnostics; @@ -27,6 +28,9 @@ internal sealed class PacketHandler private const int MaxDecompressedSize = 1024 * 1024; // ServerDefault: 40000 (check original code again) private const int ReceivePacketWindowSize = 100; + private static readonly Logger LoggerRtt = LogManager.GetLogger("TS3Client.PacketHandler.Rtt"); + private static readonly Logger LoggerRaw = LogManager.GetLogger("TS3Client.PacketHandler.Raw"); + private static readonly Logger LoggerTimeout = LogManager.GetLogger("TS3Client.PacketHandler.Timeout"); // Timout calculations private static readonly TimeSpan PacketTimeout = TimeSpan.FromSeconds(30); /// The SmoothedRoundTripTime holds the smoothed average time @@ -188,6 +192,7 @@ private void SendOutgoingData(ReadOnlySpan data, PacketType packetType, Pa case PacketType.VoiceWhisper: packet.PacketFlags |= PacketFlags.Unencrypted; NetUtil.H2N(packet.PacketId, packet.Data, 0); + LoggerRaw.ConditionalTrace("[O] {0}", packet); break; case PacketType.Command: @@ -195,31 +200,35 @@ private void SendOutgoingData(ReadOnlySpan data, PacketType packetType, Pa if (!skipCommandFlagging) packet.PacketFlags |= PacketFlags.Newprotocol; packetAckManager.Add(packet.PacketId, packet); + LoggerRaw.Debug("[O] {0}", packet); break; case PacketType.Ping: lastSentPingId = packet.PacketId; packet.PacketFlags |= PacketFlags.Unencrypted; - + LoggerRaw.ConditionalTrace("[O] Ping {0}", packet.PacketId); break; + case PacketType.Pong: packet.PacketFlags |= PacketFlags.Unencrypted; + LoggerRaw.ConditionalTrace("[O] Pong {0}", NetUtil.N2Hushort(packet.Data, 0)); break; case PacketType.Ack: case PacketType.AckLow: - break; // Nothing to do + LoggerRaw.ConditionalDebug("[O] Acking {1}: {0}", NetUtil.N2Hushort(packet.Data, 0), packet.PacketType); + break; case PacketType.Init1: packet.PacketFlags |= PacketFlags.Unencrypted; packetAckManager.Add(packet.PacketId, packet); + LoggerRaw.Debug("[O] InitID: {0}", packet.Data[4]); + LoggerRaw.Trace("[O] {0}", packet); break; default: throw Util.UnhandledDefault(packet.PacketType); } - ColorDbg.WritePkgOut(packet); - ts3Crypt.Encrypt(packet); packet.FirstSendTime = Util.Now; @@ -302,7 +311,7 @@ public IncomingPacket FetchPacket() // Invalid packet, ignore if (packet == null) { - ColorDbg.WritePkgRaw(buffer, "DROPPING"); + LoggerRaw.Debug("Dropping invalid packet: {0}", DebugUtil.DebugToHex(buffer)); continue; } @@ -312,19 +321,38 @@ public IncomingPacket FetchPacket() NetworkStats.LogInPacket(packet); - ColorDbg.WritePkgIn(packet); - switch (packet.PacketType) { - case PacketType.Voice: break; - case PacketType.VoiceWhisper: break; - case PacketType.Command: packet = ReceiveCommand(packet, receiveQueue, PacketType.Ack); break; - case PacketType.CommandLow: packet = ReceiveCommand(packet, receiveQueueLow, PacketType.AckLow); break; - case PacketType.Ping: ReceivePing(packet); break; - case PacketType.Pong: ReceivePong(packet); break; - case PacketType.Ack: packet = ReceiveAck(packet); break; + case PacketType.Voice: + case PacketType.VoiceWhisper: + LoggerRaw.ConditionalTrace("[I] {0}", packet); + break; + case PacketType.Command: + LoggerRaw.Debug("[I] {0}", packet); + packet = ReceiveCommand(packet, receiveQueue, PacketType.Ack); + break; + case PacketType.CommandLow: + LoggerRaw.Debug("[I] {0}", packet); + packet = ReceiveCommand(packet, receiveQueueLow, PacketType.AckLow); + break; + case PacketType.Ping: + LoggerRaw.ConditionalTrace("[I] Ping {0}", packet.PacketId); + ReceivePing(packet); + break; + case PacketType.Pong: + LoggerRaw.ConditionalTrace("[I] Pong {0}", NetUtil.N2Hushort(packet.Data, 0)); + ReceivePong(packet); + break; + case PacketType.Ack: + LoggerRaw.ConditionalDebug("[I] Acking: {0}", NetUtil.N2Hushort(packet.Data, 0)); + packet = ReceiveAck(packet); + break; case PacketType.AckLow: break; - case PacketType.Init1: ReceiveInitAck(); break; + case PacketType.Init1: + LoggerRaw.Debug("[I] InitID: {0}", packet.Data[0]); + LoggerRaw.Trace("[I] {0}", packet); + ReceiveInitAck(); + break; default: throw Util.UnhandledDefault(packet.PacketType); } @@ -507,7 +535,7 @@ public void ReceiveInitAck() // But usually this should be no problem since the init order is linear lock (sendLoopLock) { - ColorDbg.WriteDetail("Cleaned Inits", "INIT"); + LoggerRaw.Debug("Cleaned Inits"); var remPacket = packetAckManager.Values.Where(x => x.PacketType == PacketType.Init1).ToArray(); foreach (var packet in remPacket) packetAckManager.Remove(packet.PacketId); @@ -528,7 +556,7 @@ private void UpdateRto(TimeSpan sampleRtt) 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)); - ColorDbg.WriteRtt(smoothedRtt, smoothedRttVar, currentRto); + LoggerRtt.Debug("RTT SRTT:{0} RTTVAR:{1} RTO:{2}", smoothedRtt, smoothedRttVar, currentRto); } /// @@ -574,14 +602,14 @@ private bool ResendPackages(IEnumerable packetList) // Check if the packet timed out completely if (outgoingPacket.FirstSendTime < now - PacketTimeout) { - ColorDbg.WriteResend(outgoingPacket, "TIMEOUT"); + LoggerTimeout.Debug("TIMEOUT: {0}", outgoingPacket); return true; } // Check if we should retransmit a packet because it probably got lost if (outgoingPacket.LastSendTime < now - currentRto) { - ColorDbg.WriteResend(outgoingPacket, "RESEND"); + LoggerTimeout.Debug("RESEND: {0}", outgoingPacket); currentRto = currentRto + currentRto; if (currentRto > MaxRetryInterval) currentRto = MaxRetryInterval; diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index 4f07c7ce..bf33b2be 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -266,7 +266,7 @@ private void NetworkLoop(object ctxObject) case PacketType.Command: case PacketType.CommandLow: string message = Util.Encoder.GetString(packet.Data, 0, packet.Data.Length); - ColorDbg.WriteCmd(message, false); + LogCmd.Debug("[I] {0}", message); var result = msgProc.PushMessage(message); if (result.HasValue) dispatcher.Invoke(result.Value); @@ -388,7 +388,7 @@ private E SendCommandBase(WaitBlock wb, Ts3Command com) } var message = com.ToString(); - ColorDbg.WriteCmd(message, true); + LogCmd.Debug("[O] {0}", message); byte[] data = Util.Encoder.GetBytes(message); packetHandler.AddOutgoingPacket(data, PacketType.Command); } diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index ddf0e40f..e146de50 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -50,6 +50,9 @@ ..\packages\Heijden.Dns.2.0.0\lib\net35\Heijden.Dns.dll + + ..\packages\NLog.4.4.12\lib\net45\NLog.dll + ..\packages\System.Memory.4.4.0-preview1-25305-02\lib\netstandard1.0\System.Memory.dll @@ -62,7 +65,6 @@ - diff --git a/TS3Client/Ts3BaseClient.cs b/TS3Client/Ts3BaseClient.cs index f846e668..5bcb22f1 100644 --- a/TS3Client/Ts3BaseClient.cs +++ b/TS3Client/Ts3BaseClient.cs @@ -32,6 +32,7 @@ namespace TS3Client /// A shared function base between the query and full client. public abstract class Ts3BaseFunctions : IDisposable { + protected static readonly NLog.Logger LogCmd = NLog.LogManager.GetLogger("TS3Client.Cmd"); /// When this client receives any visible message. public abstract event NotifyEventHandler OnTextMessageReceived; /// When another client enters visiblility. diff --git a/TS3Client/packages.config b/TS3Client/packages.config index 1b2310c8..2207f0ac 100644 --- a/TS3Client/packages.config +++ b/TS3Client/packages.config @@ -2,6 +2,7 @@ + From e03237239407330353a841a2c09afa5686e8cf29 Mon Sep 17 00:00:00 2001 From: Splamy Date: Tue, 9 Jan 2018 18:33:02 +0100 Subject: [PATCH 34/48] Fixed crash with no config. Fixes #179 --- .travis.yml | 2 +- README.md | 2 +- TS3AudioBot/Core.cs | 22 +++++++++++++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 269619fd..a29f2ecf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ after_success: - export MAIN_DIR=`pwd` - cd ./TS3AudioBot/bin/Release - ls - - zip TS3AudioBot.zip *.exe *.dll x64/* x86/* + - zip TS3AudioBot.zip NLog.config *.exe *.dll x64/* x86/* - 'export version=`mono TS3AudioBot.exe --version | grep "Version: "`' - "curl -I -H \"Content-Type: application/zip\" -X PUT \"https://splamy.de/api/nightly/ts3ab/${TRAVIS_BRANCH}?token=${uploadkey}&filename=TS3AudioBot.zip&commit=${TRAVIS_COMMIT}&version=${version:9}\" --upload-file ./TS3AudioBot.zip" - cd "$MAIN_DIR" diff --git a/README.md b/README.md index 3b724ec7..0cd23d65 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ or dive into the Rights syntax [here](https://github.com/Splamy/TS3AudioBot/wiki 1. Start the bot again. 1. Send the bot in a private message `!bot setup ` where `` is the privilege key from a previous step. 1. Now you can move the process to the background or close the bot with `!quit` in teamspeak and run it in the background. -The recommended start from now on is `mono TS3AudioBot.exe -q` to disable writing to stdout since the bot logs everything to a log file anyway. +1. (optional) You can configure the logging levels and outputs in the `NLog.config` file, read [here](https://github.com/NLog/NLog/wiki/Configuration-file) to learn more. 1. Congratz, you're done! Enjoy listening to your favourite music, experimenting with the crazy command system or do whatever you whish to do ;). For further reading check out the [CommandSystem](https://github.com/Splamy/TS3AudioBot/wiki/CommandSystem) diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index 5b9fe2e8..33b86e7d 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -33,10 +33,26 @@ internal static void Main(string[] args) { Thread.CurrentThread.Name = "TAB Main"; - if (LogManager.Configuration.AllTargets.Count == 0) + if (LogManager.Configuration == null || LogManager.Configuration.AllTargets.Count == 0) { - Console.WriteLine("No or empty NLog config found. Please refer to https://github.com/NLog/NLog/wiki/Configuration-file" + - "to learn more how to set up the logging configuration."); + Console.WriteLine("No or empty NLog config found.\n" + + "You can copy the default config from TS3AudioBot/NLog.config.\n" + + "Please refer to https://github.com/NLog/NLog/wiki/Configuration-file " + + "to learn more how to set up your own logging configuration."); + + if (LogManager.Configuration == null) + { + Console.WriteLine("Create a default config to prevent this step."); + Console.WriteLine("Do you want to continue? [Y/N]"); + while (true) + { + var key = Console.ReadKey().Key; + if (key == ConsoleKey.N) + return; + if (key == ConsoleKey.Y) + break; + } + } } var core = new Core(); From 6d94ffc62947971349480027c2256978eb3a359f Mon Sep 17 00:00:00 2001 From: Splamy Date: Tue, 9 Jan 2018 18:48:33 +0100 Subject: [PATCH 35/48] Fixed looping songs Closes #180 --- TS3AudioBot/Audio/FfmpegProducer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs index be86e44d..b78d4118 100644 --- a/TS3AudioBot/Audio/FfmpegProducer.cs +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -106,9 +106,9 @@ public int Read(byte[] buffer, int offset, int length, out Meta meta) OnSongEnd?.Invoke(this, new EventArgs()); } } - + hasTriedToReconnectAudio = false; - // TODO push bytes //audioTimer.PushBytes(read); + audioTimer.PushBytes(read); return read; } } From cae524c65d345e25e9bd83c66cdf76ddb0948ca4 Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 10 Jan 2018 18:37:33 +0100 Subject: [PATCH 36/48] Fixed some deadlocks + Fixed song end callback (Related #180) --- TS3AudioBot/Audio/FfmpegProducer.cs | 31 ++++++--- TS3AudioBot/Bot.cs | 65 ++++++++++++------- TS3AudioBot/BotManager.cs | 60 ++++++++++++----- .../CommandSystem/ExecutionInformation.cs | 7 +- TS3AudioBot/Commands.cs | 2 +- TS3AudioBot/Core.cs | 3 - TS3AudioBot/Rights/RightsManager.cs | 20 +++--- TS3AudioBot/Sessions/UserSession.cs | 3 +- TS3AudioBot/Ts3Full.cs | 15 +++-- TS3Client/Full/Audio/PreciseTimedPipe.cs | 1 - TS3Client/Full/Ts3FullClient.cs | 23 ++++--- 11 files changed, 141 insertions(+), 89 deletions(-) diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs index b78d4118..4563cf4c 100644 --- a/TS3AudioBot/Audio/FfmpegProducer.cs +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -16,7 +16,7 @@ namespace TS3AudioBot.Audio using System.Text.RegularExpressions; using TS3Client.Full.Audio; - class FfmpegProducer : IAudioPassiveProducer, ISampleInfo, IDisposable + public class FfmpegProducer : IAudioPassiveProducer, ISampleInfo, IDisposable { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex FindDurationMatch = new Regex(@"^\s*Duration: (\d+):(\d\d):(\d\d).(\d\d)", Util.DefaultRegexConfig); @@ -29,7 +29,7 @@ class FfmpegProducer : IAudioPassiveProducer, ISampleInfo, IDisposable public event EventHandler OnSongEnd; - private PreciseAudioTimer audioTimer; + private readonly PreciseAudioTimer audioTimer; private string lastLink; private Process ffmpegProcess; private TimeSpan? parsedSongLength; @@ -74,22 +74,26 @@ public TimeSpan Position public int Read(byte[] buffer, int offset, int length, out Meta meta) { meta = null; + bool triggerEndSafe = false; + int read; lock (ffmpegLock) { if (ffmpegProcess == null) return 0; - - int read = ffmpegProcess.StandardOutput.BaseStream.Read(buffer, 0, length); + + read = ffmpegProcess.StandardOutput.BaseStream.Read(buffer, 0, length); if (read == 0) { // check for premature connection drop if (ffmpegProcess.HasExited && !hasTriedToReconnectAudio) { var expectedStopLength = GetCurrentSongLength(); + Log.Trace("Expected song length {0}", expectedStopLength); if (expectedStopLength != TimeSpan.Zero) { var actualStopPosition = audioTimer.SongPosition; + Log.Trace("Actual song position {0}", actualStopPosition); if (actualStopPosition + retryOnDropBeforeEnd < expectedStopLength) { Log.Debug("Connection to song lost, retrying at {0}", actualStopPosition); @@ -102,19 +106,27 @@ public int Read(byte[] buffer, int offset, int length, out Meta meta) if (ffmpegProcess.HasExited) { + Log.Trace("Ffmpeg has exited with {0}", ffmpegProcess.ExitCode); AudioStop(); - OnSongEnd?.Invoke(this, new EventArgs()); + triggerEndSafe = true; } } - - hasTriedToReconnectAudio = false; - audioTimer.PushBytes(read); - return read; } + + if (triggerEndSafe) + { + OnSongEnd?.Invoke(this, new EventArgs()); + return 0; + } + + hasTriedToReconnectAudio = false; + audioTimer.PushBytes(read); + return read; } public R StartFfmpegProcess(string url, string extraPreParam = null, string extraPostParam = null) { + Log.Trace("Start request {0}", url); try { lock (ffmpegLock) @@ -134,6 +146,7 @@ public R StartFfmpegProcess(string url, string extraPreParam = null, string extr CreateNoWindow = true, } }; + Log.Trace("Starting with {0}", ffmpegProcess.StartInfo.Arguments); ffmpegProcess.Start(); lastLink = url; diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index 5870c394..cf7f883c 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -15,6 +15,7 @@ namespace TS3AudioBot using Sessions; using System; using System.IO; + using System.Threading; using TS3Client; using TS3Client.Messages; @@ -23,10 +24,12 @@ public sealed class Bot : IDisposable { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); - private bool isDisposed; private readonly Core core; private MainBotData mainBotData; + internal object SyncRoot { get; } = new object(); + internal bool IsDisposed { get; private set; } + internal TargetScript TargetScript { get; set; } /// Mangement for playlists. public PlaylistManager PlaylistManager { get; private set; } @@ -213,23 +216,26 @@ private void LoggedUpdateBotStatus(object sender, EventArgs e) public R UpdateBotStatus(string overrideStr = null) { - string setString; - if (overrideStr != null) - { - setString = overrideStr; - } - else if (PlayManager.IsPlaying) - { - setString = QuizMode - ? "" - : PlayManager.CurrentPlayData.ResourceData.ResourceTitle; - } - else + lock (SyncRoot) { - setString = ""; - } + string setString; + if (overrideStr != null) + { + setString = overrideStr; + } + else if (PlayManager.IsPlaying) + { + setString = QuizMode + ? "" + : PlayManager.CurrentPlayData.ResourceData.ResourceTitle; + } + else + { + setString = ""; + } - return QueryConnection.ChangeDescription(setString); + return QueryConnection.ChangeDescription(setString); + } } private void GenerateStatusImage(object sender, EventArgs e) @@ -265,21 +271,30 @@ private void GenerateStatusImage(object sender, EventArgs e) } } + public BotLock GetBotLock() + { + Monitor.Enter(SyncRoot); + return new BotLock(!IsDisposed, this); + } + public void Dispose() { - if (!isDisposed) isDisposed = true; - else return; - Log.Info("Bot disconnecting."); + lock (SyncRoot) + { + if (!IsDisposed) IsDisposed = true; + else return; + Log.Info("Bot disconnecting."); - core.Bots.StopBot(this); + core.Bots.StopBot(this); - PlayManager?.Stop(); + PlayManager?.Stop(); - PlayerConnection?.Dispose(); // before: logStream, - PlayerConnection = null; + PlayerConnection?.Dispose(); // before: logStream, + PlayerConnection = null; - QueryConnection?.Dispose(); // before: logStream, - QueryConnection = null; + QueryConnection?.Dispose(); // before: logStream, + QueryConnection = null; + } } } diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index 71cea09e..467ba5f7 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -76,45 +76,49 @@ public bool CreateBot(/*Ts3FullClientData bot*/) { bool removeBot = false; var bot = new Bot(Core); - if (bot.InitializeBot()) + lock (bot.SyncRoot) { - lock (lockObj) + if (bot.InitializeBot()) { - activeBots.Add(bot); - removeBot = !isRunning; + lock (lockObj) + { + activeBots.Add(bot); + removeBot = !isRunning; + } + } + + if (removeBot) + { + StopBot(bot); + return false; } - } - if (removeBot) - { - StopBot(bot); - return false; } return true; } - public Bot GetBot(int id) + public BotLock GetBotLock(int id) { + Bot bot; lock (lockObj) { if (!isRunning) return null; - return id < activeBots.Count + bot = id < activeBots.Count ? activeBots[id] : null; + if (bot == null) + return new BotLock(false, null); } + return bot.GetBotLock(); } public void StopBot(Bot bot) { - bool tookBot; lock (lockObj) { - tookBot = activeBots.Remove(bot); - } - if (tookBot) - { - bot.Dispose(); + activeBots.Remove(bot); } + bot.Dispose(); } public void Dispose() @@ -133,4 +137,26 @@ public void Dispose() } } } + + public class BotLock : IDisposable + { + private Bot bot; + public bool IsValid { get; private set; } + public Bot Bot => IsValid ? bot : throw new InvalidOperationException("The bot lock is not valid."); + + internal BotLock(bool isValid, Bot bot) + { + IsValid = isValid; + this.bot = bot; + } + + public void Dispose() + { + if (IsValid) + { + IsValid = false; + Monitor.Exit(bot.SyncRoot); + } + } + } } diff --git a/TS3AudioBot/CommandSystem/ExecutionInformation.cs b/TS3AudioBot/CommandSystem/ExecutionInformation.cs index b04bae2d..5ab60c7f 100644 --- a/TS3AudioBot/CommandSystem/ExecutionInformation.cs +++ b/TS3AudioBot/CommandSystem/ExecutionInformation.cs @@ -36,16 +36,13 @@ public bool HasRights(params string[] rights) { if (SkipRightsChecks) return true; - return Core.RightsManager.HasAllRights(InvokerData, rights); + return Core.RightsManager.HasAllRights(InvokerData, Bot, rights); } public R Write(string message) { if (InvokerData.Visibiliy.HasValue) - { - Session.Write(message, InvokerData.Visibiliy.Value); - return R.OkR; - } + return Session.Write(message, InvokerData.Visibiliy.Value); else return "User has no visibility"; } diff --git a/TS3AudioBot/Commands.cs b/TS3AudioBot/Commands.cs index dd927f25..c02cb7ff 100644 --- a/TS3AudioBot/Commands.cs +++ b/TS3AudioBot/Commands.cs @@ -919,7 +919,7 @@ public static void CommandRandomSeed(ExecutionInformation info, string newSeed) [Command("rights can", "Returns the subset of allowed commands the caller (you) can execute.")] public static JsonObject CommandRightsCan(ExecutionInformation info, params string[] rights) { - var result = info.Core.RightsManager.GetRightsSubset(info.InvokerData, rights); + var result = info.Core.RightsManager.GetRightsSubset(info.InvokerData, info.Bot, rights); if (result.Length > 0) return new JsonArray(string.Join(", ", result), result); else diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index 33b86e7d..ecde79bb 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -7,9 +7,6 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . - -using NLog.Targets; - namespace TS3AudioBot { using CommandSystem; diff --git a/TS3AudioBot/Rights/RightsManager.cs b/TS3AudioBot/Rights/RightsManager.cs index 83d8431b..90823d60 100644 --- a/TS3AudioBot/Rights/RightsManager.cs +++ b/TS3AudioBot/Rights/RightsManager.cs @@ -9,6 +9,7 @@ namespace TS3AudioBot.Rights { + using CommandSystem; using Helper; using Nett; using System; @@ -24,8 +25,7 @@ public class RightsManager : Dependency.ICoreModule private const int RuleLevelSize = 2; public ConfigFile Config { get; set; } - public BotManager Bots { get; set; } - public CommandSystem.CommandManager CommandManager { get; set; } + public CommandManager CommandManager { get; set; } private bool needsRecalculation; private readonly Cache cachedRights; @@ -75,21 +75,21 @@ public void UnregisterRights(IEnumerable rights) } // TODO: b_client_permissionoverview_view - public bool HasAllRights(InvokerData inv, params string[] requestedRights) + public bool HasAllRights(InvokerData inv, Bot bot, params string[] requestedRights) { - var ctx = GetRightsContext(inv); + var ctx = GetRightsContext(inv, bot); var normalizedRequest = ExpandRights(requestedRights); return ctx.DeclAdd.IsSupersetOf(normalizedRequest); } - public string[] GetRightsSubset(InvokerData inv, params string[] requestedRights) + public string[] GetRightsSubset(InvokerData inv, Bot bot, params string[] requestedRights) { - var ctx = GetRightsContext(inv); + var ctx = GetRightsContext(inv, bot); var normalizedRequest = ExpandRights(requestedRights); return ctx.DeclAdd.Intersect(normalizedRequest).ToArray(); } - private ExecuteContext GetRightsContext(InvokerData inv) + private ExecuteContext GetRightsContext(InvokerData inv, Bot bot) { if (needsRecalculation) { @@ -112,8 +112,7 @@ private ExecuteContext GetRightsContext(InvokerData inv) ((needsAvailableGroups && execCtx.AvailableGroups == null) || (needsAvailableChanGroups && !execCtx.ChannelGroupId.HasValue))) { - // TODO fixme !!!!!!!! - var result = Bots.GetBot(0)?.QueryConnection.GetClientInfoById(inv.ClientId.Value) ?? R.Err("No bot"); + var result = bot?.QueryConnection.GetClientInfoById(inv.ClientId.Value) ?? R.Err(""); if (result.Ok) { if (execCtx.AvailableGroups == null) @@ -125,8 +124,7 @@ private ExecuteContext GetRightsContext(InvokerData inv) if (needsAvailableGroups && inv.DatabaseId.HasValue && execCtx.AvailableGroups == null) { - // TODO fixme !!!!!!!! - var result = Bots.GetBot(0)?.QueryConnection.GetClientServerGroups(inv.DatabaseId.Value) ?? R.Err(""); + var result = bot?.QueryConnection.GetClientServerGroups(inv.DatabaseId.Value) ?? R.Err(""); if (result.Ok) execCtx.AvailableGroups = result.Value; } diff --git a/TS3AudioBot/Sessions/UserSession.cs b/TS3AudioBot/Sessions/UserSession.cs index 27ec97f7..6ccdd2c1 100644 --- a/TS3AudioBot/Sessions/UserSession.cs +++ b/TS3AudioBot/Sessions/UserSession.cs @@ -37,7 +37,7 @@ public UserSession(Bot bot, ClientData client) ResponseData = null; } - public void Write(string message, TextMessageTargetMode targetMode) + public R Write(string message, TextMessageTargetMode targetMode) { VerifyLock(); @@ -59,6 +59,7 @@ public void Write(string message, TextMessageTargetMode targetMode) if (!result) Log.Error("Could not write message (Err:{0}) (Msg:{1})", result.Error, message); + return result; } public void SetResponse(Response responseProcessor, object responseData) diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index e3722045..bd4356a3 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -49,10 +49,10 @@ internal sealed class Ts3Full : TeamspeakControl, IPlayerConnection private uint stallNoErrorCount; private IdentityData identity; - private VolumePipe volumePipe; - private FfmpegProducer ffmpegProducer; - private PreciseTimedPipe timePipe; - private EncoderPipe encoderPipe; + private readonly VolumePipe volumePipe; + private readonly FfmpegProducer ffmpegProducer; + private readonly PreciseTimedPipe timePipe; + private readonly EncoderPipe encoderPipe; internal CustomTargetPipe TargetPipe { get; private set; } public Ts3Full(Ts3FullClientData tfcd) : base(ClientType.Full) @@ -63,7 +63,6 @@ public Ts3Full(Ts3FullClientData tfcd) : base(ClientType.Full) tfcd.PropertyChanged += Tfcd_PropertyChanged; ffmpegProducer = new FfmpegProducer(tfcd); - ffmpegProducer.OnSongEnd += OnSongEnd; volumePipe = new VolumePipe(); encoderPipe = new EncoderPipe(SendCodec) { Bitrate = ts3FullClientData.AudioBitrate * 1000 }; timePipe = new PreciseTimedPipe { ReadBufferSize = encoderPipe.OptimalPacketSize }; @@ -304,7 +303,11 @@ private void AudioSend() #region IPlayerConnection - public event EventHandler OnSongEnd; + public event EventHandler OnSongEnd + { + add => ffmpegProducer.OnSongEnd += value; + remove => ffmpegProducer.OnSongEnd -= value; + } public R AudioStart(string url) { diff --git a/TS3Client/Full/Audio/PreciseTimedPipe.cs b/TS3Client/Full/Audio/PreciseTimedPipe.cs index b11400a9..2f7a1b49 100644 --- a/TS3Client/Full/Audio/PreciseTimedPipe.cs +++ b/TS3Client/Full/Audio/PreciseTimedPipe.cs @@ -89,7 +89,6 @@ private void ReadTick() int read = inStream.Read(readBuffer, 0, readBuffer.Length, out var meta); if (read == 0) { - // TODO do stuff AudioTimer.Start(); return; } diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index bf33b2be..bae309c4 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -31,6 +31,7 @@ namespace TS3Client.Full /// Creates a full TeamSpeak3 client with voice capabilities. public sealed class Ts3FullClient : Ts3BaseFunctions, IAudioActiveProducer, IAudioPassiveConsumer { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private readonly Ts3Crypt ts3Crypt; private readonly PacketHandler packetHandler; private readonly MessageProcessor msgProc; @@ -125,12 +126,17 @@ public override void Disconnect() } } - private void DisconnectInternal(ConnectionContext ctx, CommandError error = null) + private void DisconnectInternal(ConnectionContext ctx, CommandError error = null, Ts3ClientStatus? setStatus = null) { bool triggerEventSafe = false; lock (statusLock) { + Log.Debug("DisconnectInternal wasExit:{0} error:{1} oldStatus:{2} newStatus:{3}", ctx.WasExit, error?.ErrorFormat(), status, setStatus); + + if (setStatus.HasValue) + status = setStatus.Value; + if (ctx.WasExit) return; @@ -178,11 +184,7 @@ private void InvokeEvent(LazyNotification lazyNotification) if (leftViewEvent != null) { packetHandler.ExitReason = leftViewEvent.Reason; - lock (statusLock) - { - status = Ts3ClientStatus.Disconnected; - DisconnectInternal(context); - } + DisconnectInternal(context, setStatus: Ts3ClientStatus.Disconnected); break; } OnClientLeftView?.Invoke(this, clientLeftArr); @@ -221,16 +223,18 @@ private void InvokeEvent(LazyNotification lazyNotification) var error = result.Ok ? result.Value : Util.CustomError("Got empty error while connecting."); bool skipError = false; + bool disconnect = false; lock (statusLock) { if (status == Ts3ClientStatus.Connecting) { + disconnect = true; skipError = true; - status = Ts3ClientStatus.Disconnected; - DisconnectInternal(context, error); } } + if (disconnect) + DisconnectInternal(context, error, Ts3ClientStatus.Disconnected); if (!skipError) OnErrorEvent?.Invoke(this, error); } @@ -295,8 +299,7 @@ private void NetworkLoop(object ctxObject) lock (statusLock) { - status = Ts3ClientStatus.Disconnected; - DisconnectInternal(ctx); + DisconnectInternal(ctx, setStatus: Ts3ClientStatus.Disconnected); } } From 5d44190dd4d9270961772eaf0d680496e8a72947 Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 10 Jan 2018 22:00:56 +0100 Subject: [PATCH 37/48] Fixed another deadlock + apparently fixed split packet sending --- TS3AudioBot/Bot.cs | 6 +++--- TS3AudioBot/BotManager.cs | 23 ++++++++++++++--------- TS3Client/Full/BasePacket.cs | 1 + TS3Client/Full/NetUtil.cs | 7 +++++++ TS3Client/Full/OutgoingPacket.cs | 16 ++++++++++++++++ TS3Client/Full/PacketHandler.cs | 9 ++++----- TS3Client/Full/Ts3Crypt.cs | 3 +-- 7 files changed, 46 insertions(+), 19 deletions(-) diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index cf7f883c..5ff6e6a1 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -279,14 +279,14 @@ public BotLock GetBotLock() public void Dispose() { + core.Bots?.RemoveBot(this); + lock (SyncRoot) { if (!IsDisposed) IsDisposed = true; else return; Log.Info("Bot disconnecting."); - - core.Bots.StopBot(this); - + PlayManager?.Stop(); PlayerConnection?.Dispose(); // before: logStream, diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index 467ba5f7..f110d5e8 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -34,18 +34,18 @@ public void WatchBots() { while (isRunning) { + bool createBot; lock (lockObj) { - if (activeBots.Count == 0) - { - if (!CreateBot()) - { - Thread.Sleep(1000); - } - } + createBot = activeBots.Count == 0; + } - CleanStrayBots(); + if (createBot && !CreateBot()) + { + Thread.Sleep(1000); } + + CleanStrayBots(); Thread.Sleep(200); } } @@ -113,12 +113,17 @@ public BotLock GetBotLock(int id) } public void StopBot(Bot bot) + { + RemoveBot(bot); + bot.Dispose(); + } + + internal void RemoveBot(Bot bot) { lock (lockObj) { activeBots.Remove(bot); } - bot.Dispose(); } public void Dispose() diff --git a/TS3Client/Full/BasePacket.cs b/TS3Client/Full/BasePacket.cs index a9701628..42b0b646 100644 --- a/TS3Client/Full/BasePacket.cs +++ b/TS3Client/Full/BasePacket.cs @@ -75,6 +75,7 @@ public override string ToString() $"{(FragmentedFlag ? "X" : "_")} {(NewProtocolFlag ? "X" : "_")} " + $"{(CompressedFlag ? "X" : "_")} {(UnencryptedFlag ? "X" : "_")} ]\t" + $"Id: {PacketId}\n" + + $" Header: { DebugUtil.DebugToHex(Header) }\n" + $" Data: { DebugUtil.DebugToHex(Data) }\n" + $" ASCI: { Util.Encoder.GetString(Data) }"; } diff --git a/TS3Client/Full/NetUtil.cs b/TS3Client/Full/NetUtil.cs index 110d17d8..7561dae9 100644 --- a/TS3Client/Full/NetUtil.cs +++ b/TS3Client/Full/NetUtil.cs @@ -43,6 +43,13 @@ public static void H2N(ushort value, byte[] outArr, int outOff) outArr[outOff + 1] = unchecked((byte)(value >> 0)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void H2N(ushort value, Span buf) + { + buf[0] = unchecked((byte)(value >> 8)); + buf[1] = unchecked((byte)(value >> 0)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void H2N(uint value, byte[] outArr, int outOff) { diff --git a/TS3Client/Full/OutgoingPacket.cs b/TS3Client/Full/OutgoingPacket.cs index 9dde3bd0..6c3d6ff5 100644 --- a/TS3Client/Full/OutgoingPacket.cs +++ b/TS3Client/Full/OutgoingPacket.cs @@ -31,5 +31,21 @@ public void BuildHeader() NetUtil.H2N(ClientId, Header, 2); Header[4] = PacketTypeFlagged; } + + public void BuildHeader(Span buffer) + { + NetUtil.H2N(PacketId, buffer.Slice(0, 2)); + NetUtil.H2N(ClientId, buffer.Slice(2, 2)); + buffer[4] = PacketTypeFlagged; +#if DEBUG + buffer.CopyTo(Header.AsSpan()); +#endif + } + + public override string ToString() + { + BuildHeader(); + return base.ToString(); + } } } diff --git a/TS3Client/Full/PacketHandler.cs b/TS3Client/Full/PacketHandler.cs index df095631..f72447b7 100644 --- a/TS3Client/Full/PacketHandler.cs +++ b/TS3Client/Full/PacketHandler.cs @@ -171,7 +171,7 @@ public void AddOutgoingPacket(ReadOnlySpan packet, PacketType packetType, } } - private void SendOutgoingData(ReadOnlySpan data, PacketType packetType, PacketFlags flags = PacketFlags.None, bool skipCommandFlagging = false) + private void SendOutgoingData(ReadOnlySpan data, PacketType packetType, PacketFlags flags = PacketFlags.None) { var packet = new OutgoingPacket(data.ToArray(), packetType); @@ -197,8 +197,7 @@ private void SendOutgoingData(ReadOnlySpan data, PacketType packetType, Pa case PacketType.Command: case PacketType.CommandLow: - if (!skipCommandFlagging) - packet.PacketFlags |= PacketFlags.Newprotocol; + packet.PacketFlags |= PacketFlags.Newprotocol; packetAckManager.Add(packet.PacketId, packet); LoggerRaw.Debug("[O] {0}", packet); break; @@ -268,7 +267,6 @@ private void AddOutgoingSplitData(ReadOnlySpan rawData, PacketType packetT if (blockSize <= 0) break; var flags = PacketFlags.None; - var skipFlagging = !first; last = pos + blockSize == rawData.Length; if (first ^ last) flags |= PacketFlags.Fragmented; @@ -278,7 +276,7 @@ private void AddOutgoingSplitData(ReadOnlySpan rawData, PacketType packetT first = false; } - SendOutgoingData(rawData.Slice(pos, blockSize), packetType, flags, skipFlagging); + SendOutgoingData(rawData.Slice(pos, blockSize), packetType, flags); pos += blockSize; } while (!last); } @@ -623,6 +621,7 @@ private void SendRaw(OutgoingPacket packet) { packet.LastSendTime = Util.Now; NetworkStats.LogOutPacket(packet); + LoggerRaw.ConditionalTrace("Sending Raw: {0}", DebugUtil.DebugToHex(packet.Raw)); udpClient.Send(packet.Raw, packet.Raw.Length); } } diff --git a/TS3Client/Full/Ts3Crypt.cs b/TS3Client/Full/Ts3Crypt.cs index ad861be9..edef73c5 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TS3Client/Full/Ts3Crypt.cs @@ -355,12 +355,11 @@ internal void Encrypt(OutgoingPacket packet) private static void FakeEncrypt(OutgoingPacket packet, byte[] mac) { - packet.BuildHeader(); packet.Raw = new byte[packet.Data.Length + MacLen + OutHeaderLen]; // Copy the Mac from [Mac...] to [Mac..., Header..., Data...] Array.Copy(mac, 0, packet.Raw, 0, MacLen); // Copy the Header from packet.Header to [Mac..., Header..., Data...] - Array.Copy(packet.Header, 0, packet.Raw, MacLen, OutHeaderLen); + packet.BuildHeader(packet.Raw.AsSpan().Slice(MacLen, OutHeaderLen)); // Copy the Data from packet.Data to [Mac..., Header..., Data...] Array.Copy(packet.Data, 0, packet.Raw, MacLen + OutHeaderLen, packet.Data.Length); // Raw is now [Mac..., Header..., Data...] From a62c01c129a5ada0d243ae634af72560bf8c233a Mon Sep 17 00:00:00 2001 From: Splamy Date: Thu, 11 Jan 2018 01:06:51 +0100 Subject: [PATCH 38/48] Trying to fix file upload addressfamily --- TS3Client/FileTransferManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TS3Client/FileTransferManager.cs b/TS3Client/FileTransferManager.cs index 725906cf..179139c7 100644 --- a/TS3Client/FileTransferManager.cs +++ b/TS3Client/FileTransferManager.cs @@ -29,6 +29,7 @@ namespace TS3Client /// 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 Queue transferQueue; private Thread workerThread; @@ -216,9 +217,9 @@ private void TransferLoop() token.Status = TransferStatus.Trasfering; } - using (var client = new TcpClient(AddressFamily.InterNetworkV6)) + Log.Trace("Creating new file transfer connection to {0}", parent.remoteAddress); + using (var client = new TcpClient(parent.remoteAddress.AddressFamily)) { - client.Client.DualMode = true; try { client.Connect(parent.remoteAddress.Address, token.Port); } catch (SocketException) { From 2b7fcacefb83a063e3be75a01a03cb98f66dc811 Mon Sep 17 00:00:00 2001 From: Splamy Date: Fri, 12 Jan 2018 22:05:25 +0100 Subject: [PATCH 39/48] Fixed init hadling for some servers + moved to Newtonsoft.Json --- TS3ABotUnitTests/UnitTests.cs | 28 +++--- TS3AudioBot/Audio/CustomTargetPipe.cs | 13 ++- TS3AudioBot/Bot.cs | 3 +- TS3AudioBot/Helper/Util.cs | 17 +++- TS3AudioBot/PlayManager.cs | 9 +- .../ResourceFactories/SoundcloudFactory.cs | 40 +++++---- .../ResourceFactories/TwitchFactory.cs | 11 ++- .../ResourceFactories/YoutubeFactory.cs | 48 ++-------- TS3AudioBot/TS3AudioBot.csproj | 4 +- TS3AudioBot/Web/Api/JsonObject.cs | 7 +- TS3AudioBot/Web/Api/WebApi.cs | 3 +- TS3AudioBot/packages.config | 1 + TS3Client/Full/Audio/EncoderPipe.cs | 14 ++- TS3Client/Full/Audio/Opus/NativeMethods.cs | 2 +- TS3Client/Full/Audio/Opus/OpusEncoder.cs | 6 +- TS3Client/Full/Audio/VolumePipe.cs | 4 +- TS3Client/Full/BasePacket.cs | 5 +- TS3Client/Full/PacketHandler.cs | 87 +++++++++++-------- TS3Client/Full/Ts3Crypt.cs | 47 ++++++---- TS3Client/Full/Ts3FullClient.cs | 9 +- TS3Client/Helper/R.cs | 2 + TS3Client/Helper/Util.cs | 17 +++- 22 files changed, 197 insertions(+), 180 deletions(-) diff --git a/TS3ABotUnitTests/UnitTests.cs b/TS3ABotUnitTests/UnitTests.cs index adc5fcbb..e7f86a81 100644 --- a/TS3ABotUnitTests/UnitTests.cs +++ b/TS3ABotUnitTests/UnitTests.cs @@ -63,16 +63,17 @@ public void HistoryFileIntergrityTest() hmf.HistoryFile = testFile; hmf.FillDeletedIds = false; - DbStore db = null; - HistoryManager hf = null; - Action createDbStore = () => + DbStore db; + HistoryManager hf; + + void CreateDbStore() { - db = new DbStore() { Config = memcfg }; + db = new DbStore() {Config = memcfg}; db.Initialize(); hf = new HistoryManager(hmf, db); - }; + } - createDbStore(); + CreateDbStore(); hf.LogAudioResource(data1); @@ -83,7 +84,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); - createDbStore(); + CreateDbStore(); lastXEntries = hf.GetLastXEntrys(1); Assert.True(lastXEntries.Any()); lastEntry = lastXEntries.First(); @@ -100,7 +101,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // store and order check - createDbStore(); + CreateDbStore(); var lastXEntriesArray = hf.GetLastXEntrys(2).ToArray(); Assert.AreEqual(2, lastXEntriesArray.Length); Assert.AreEqual(ar2, lastXEntriesArray[0].AudioResource); @@ -114,7 +115,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // check entry renaming - createDbStore(); + CreateDbStore(); lastXEntriesArray = hf.GetLastXEntrys(2).ToArray(); Assert.AreEqual(2, lastXEntriesArray.Length); Assert.AreEqual(ar1, lastXEntriesArray[0].AudioResource); @@ -134,7 +135,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // recheck order - createDbStore(); + CreateDbStore(); lastXEntriesArray = hf.GetLastXEntrys(2).ToArray(); Assert.AreEqual(2, lastXEntriesArray.Length); Assert.AreEqual(ar2, lastXEntriesArray[0].AudioResource); @@ -142,7 +143,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // delete entry 1 - createDbStore(); + CreateDbStore(); hf.RemoveEntry(hf.FindEntryByResource(ar1)); lastXEntriesArray = hf.GetLastXEntrys(3).ToArray(); @@ -156,7 +157,7 @@ public void HistoryFileIntergrityTest() db.Dispose(); // delete entry 2 - createDbStore(); + CreateDbStore(); // .. check integrity from previous store lastXEntriesArray = hf.GetLastXEntrys(3).ToArray(); Assert.AreEqual(2, lastXEntriesArray.Length); @@ -330,12 +331,11 @@ public void Factory_YoutubeFactoryTest() [Test] public void Ts3Client_RingQueueTest() { - int ov; var q = new RingQueue(3, 5); q.Set(0, 42); - Assert.True(q.TryPeekStart(0, out ov)); + Assert.True(q.TryPeekStart(0, out int ov)); Assert.AreEqual(ov, 42); q.Set(1, 43); diff --git a/TS3AudioBot/Audio/CustomTargetPipe.cs b/TS3AudioBot/Audio/CustomTargetPipe.cs index e257c2a2..a827222d 100644 --- a/TS3AudioBot/Audio/CustomTargetPipe.cs +++ b/TS3AudioBot/Audio/CustomTargetPipe.cs @@ -10,16 +10,12 @@ namespace TS3AudioBot.Audio { using Helper; - using TS3Client; - using TS3Client.Helper; - using TS3Client.Full; - using TS3Client.Full.Audio; - using TS3Client.Messages; using System; using System.Collections.Generic; using System.Linq; - using System.Text; - using System.Threading.Tasks; + using TS3Client; + using TS3Client.Full; + using TS3Client.Full.Audio; internal class CustomTargetPipe : ITargetManager, IAudioPassiveConsumer { @@ -63,7 +59,8 @@ public void Write(Span data, Meta meta) case TargetSendMode.WhisperGroup: client.SendAudioGroupWhisper(data, codec, GroupWhisperType, GroupWhisperTarget, GroupWhisperTargetId); break; - default: break; + default: + throw new ArgumentOutOfRangeException(nameof(SendMode), "Unknown send target"); } } diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index 5ff6e6a1..b5fc7239 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -12,6 +12,7 @@ namespace TS3AudioBot using CommandSystem; using Helper; using History; + using Newtonsoft.Json; using Sessions; using System; using System.IO; @@ -185,7 +186,7 @@ private void TextCallback(object sender, TextMessage textMessage) { var sRes = (JsonCommandResult)res; execInfo.Write("\nJson str: \n" + sRes.JsonObject.AsStringResult); - execInfo.Write("\nJson val: \n" + Util.Serializer.Serialize(sRes.JsonObject)); + execInfo.Write("\nJson val: \n" + JsonConvert.SerializeObject(sRes.JsonObject)); } } catch (CommandException ex) diff --git a/TS3AudioBot/Helper/Util.cs b/TS3AudioBot/Helper/Util.cs index 139e8617..105ae76a 100644 --- a/TS3AudioBot/Helper/Util.cs +++ b/TS3AudioBot/Helper/Util.cs @@ -10,6 +10,7 @@ namespace TS3AudioBot.Helper { using CommandSystem; + using Newtonsoft.Json.Linq; using System; using System.Diagnostics; using System.IO; @@ -18,7 +19,6 @@ namespace TS3AudioBot.Helper using System.Text; using System.Text.RegularExpressions; using System.Threading; - using System.Web.Script.Serialization; [Serializable] public static class Util @@ -63,7 +63,7 @@ public static void WaitForThreadEnd(Thread thread, TimeSpan timeout) public static Random Random { get; } = new Random(); - public static JavaScriptSerializer Serializer { get; } = new JavaScriptSerializer(); + //public static JavaScriptSerializer Serializer { get; } = new JavaScriptSerializer(); public static Encoding Utf8Encoder { get; } = new UTF8Encoding(false, false); @@ -150,7 +150,7 @@ public static T UnwrapThrow(this R r) else throw new CommandException(r.Error, CommandExceptionReason.CommandError); } - + public static string UnrollException(this Exception ex) { var strb = new StringBuilder(); @@ -228,7 +228,7 @@ public static string GetPlattformData() }; p.Start(); p.WaitForExit(100); - + while (p.StandardOutput.Peek() > -1) { var infoLine = p.StandardOutput.ReadLine(); @@ -257,6 +257,15 @@ public static string GetPlattformData() return $"{plattform} {version} ({bitness})"; } + + public static R TryCast(this JToken token, string key) + { + var value = token.SelectToken(key); + if (value == null) + return "Key not found"; + try { return value.ToObject(); } + catch (FormatException) { return "Invalid type"; } + } } public class MissingEnumCaseException : Exception diff --git a/TS3AudioBot/PlayManager.cs b/TS3AudioBot/PlayManager.cs index 3d62cd75..bc577827 100644 --- a/TS3AudioBot/PlayManager.cs +++ b/TS3AudioBot/PlayManager.cs @@ -183,9 +183,10 @@ public R Next(InvokerData invoker) for (int i = 0; i < 10; i++) { if ((pli = PlaylistManager.Next()) == null) break; - if (Play(invoker, pli)) - return R.OkR; - // optional message here that playlist entry has been skipped + var result = Play(invoker, pli); + if (result.Ok) + return result; + Log.Warn("Skipping: {0} because {1}", pli.DisplayString, result.Error); } if (pli == null) return "No next song could be played"; @@ -221,7 +222,7 @@ private void StopInternal(bool songEndedByCallback) var result = Next(CurrentPlayData.Invoker); if (result) return; - Log.Warn(nameof(SongStoppedHook) + " could not play Next: {0}", result.Error); + Log.Info("Song queue ended: {0}", result.Error); } else { diff --git a/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs b/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs index b25c5e09..6f77ddaf 100644 --- a/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs +++ b/TS3AudioBot/ResourceFactories/SoundcloudFactory.cs @@ -10,11 +10,10 @@ namespace TS3AudioBot.ResourceFactories { using Helper; + using Newtonsoft.Json.Linq; using System; - using System.Collections.Generic; using System.Drawing; using System.Globalization; - using System.Linq; using System.Text.RegularExpressions; public sealed class SoundcloudFactory : IResourceFactory, IPlaylistFactory, IThumbnailFactory @@ -39,7 +38,7 @@ public R GetResource(string link) return YoutubeDlWrapped(link); } var parsedDict = ParseJson(jsonResponse); - var resource = ParseDictToResource(parsedDict); + var resource = ParseJObjectToResource(parsedDict); if (resource == null) return "Empty or missing response parts (parsedDict)"; return GetResourceById(resource, false); @@ -72,19 +71,24 @@ public string RestoreLink(string id) var uri = new Uri($"https://api.soundcloud.com/tracks/{id}?client_id={SoundcloudClientId}"); if (!WebWrapper.DownloadString(out string jsonResponse, uri)) return null; - var parsedDict = ParseJson(jsonResponse); - return parsedDict?["permalink_url"] as string; + var jobj = ParseJson(jsonResponse); + return jobj.TryCast("permalink_url").OkOr(null); } - private static Dictionary ParseJson(string jsonResponse) - => (Dictionary)Util.Serializer.DeserializeObject(jsonResponse); + private static JObject ParseJson(string jsonResponse) + { + try { return JObject.Parse(jsonResponse); } + catch (FormatException) { return null; } + } - private AudioResource ParseDictToResource(Dictionary dict) + private AudioResource ParseJObjectToResource(JToken jobj) { - if (dict == null) return null; - if (!(dict["id"] is int id)) return null; - if (!(dict["title"] is string title)) return null; - return new AudioResource(id.ToString(CultureInfo.InvariantCulture), title, FactoryFor); + if (jobj == null) return null; + var id = jobj.TryCast("id"); + if (!id.Ok) return null; + var title = jobj.TryCast("title"); + if (!title.Ok) return null; + return new AudioResource(id.Value.ToString(CultureInfo.InvariantCulture), title.Value, FactoryFor); } private R YoutubeDlWrapped(string link) @@ -114,15 +118,16 @@ public R GetPlaylist(string url) if (parsedDict == null) return "Empty or missing response parts (parsedDict)"; - string name = PlaylistManager.CleanseName(parsedDict["title"] as string); + string name = PlaylistManager.CleanseName(parsedDict.TryCast("title").OkOr(null)); var plist = new Playlist(name); - if (!(parsedDict["tracks"] is object[] tracks)) + var tracksJobj = parsedDict["tracks"]; + if (tracksJobj == null) return "Empty or missing response parts (tracks)"; - foreach (var track in tracks.OfType>()) + foreach (var track in tracksJobj) { - var resource = ParseDictToResource(track); + var resource = ParseJObjectToResource(track); if (resource == null) continue; @@ -142,7 +147,8 @@ public R GetThumbnail(PlayResource playResource) if (parsedDict == null) return "Empty or missing response parts (parsedDict)"; - if (!(parsedDict["artwork_url"] is string imgUrl)) + var imgUrl = parsedDict.TryCast("artwork_url").OkOr(null); + if (imgUrl == null) return "Empty or missing response parts (artwork_url)"; // t500x500: 500px×500px diff --git a/TS3AudioBot/ResourceFactories/TwitchFactory.cs b/TS3AudioBot/ResourceFactories/TwitchFactory.cs index b3fd9a66..272ca097 100644 --- a/TS3AudioBot/ResourceFactories/TwitchFactory.cs +++ b/TS3AudioBot/ResourceFactories/TwitchFactory.cs @@ -10,6 +10,7 @@ namespace TS3AudioBot.ResourceFactories { using Helper; + using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; @@ -41,11 +42,15 @@ public R GetResourceById(AudioResource resource) if (!WebWrapper.DownloadString(out string jsonResponse, new Uri($"http://api.twitch.tv/api/channels/{channel}/access_token"), new Tuple("Client-ID", TwitchClientId))) return RResultCode.NoConnection.ToString(); - var jsonDict = (Dictionary)Util.Serializer.DeserializeObject(jsonResponse); + var jObj = JObject.Parse(jsonResponse); // request m3u8 file - var token = Uri.EscapeUriString(jsonDict["token"].ToString()); - var sig = jsonDict["sig"]; + var tokenResult = jObj.TryCast("token"); + var sigResult = jObj.TryCast("sig"); + if (!tokenResult.Ok || !sigResult.Ok) + return "Invalid api response"; + var token = Uri.EscapeUriString(tokenResult.Value); + var sig = sigResult.Value; // 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}"))) diff --git a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs index 6ae68a9c..c5990455 100644 --- a/TS3AudioBot/ResourceFactories/YoutubeFactory.cs +++ b/TS3AudioBot/ResourceFactories/YoutubeFactory.cs @@ -19,6 +19,7 @@ namespace TS3AudioBot.ResourceFactories using System.Text; using System.Text.RegularExpressions; using System.Web; + using Newtonsoft.Json; public sealed class YoutubeFactory : IResourceFactory, IPlaylistFactory, IThumbnailFactory { @@ -245,7 +246,7 @@ public R GetPlaylist(string url) if (!WebWrapper.DownloadString(out string response, queryString)) return "Web response error"; - var parsed = Util.Serializer.Deserialize(response); + var parsed = JsonConvert.DeserializeObject(response); var videoItems = parsed.items; YoutubePlaylistItem[] itemBuffer = new YoutubePlaylistItem[videoItems.Length]; for (int i = 0; i < videoItems.Length; i++) @@ -260,7 +261,7 @@ public R GetPlaylist(string url) queryString = new Uri($"https://www.googleapis.com/youtube/v3/videos?id={string.Join(",", itemBuffer.Select(item => item.Resource.ResourceId))}&part=contentDetails&key={data.apiKey}"); if (!WebWrapper.DownloadString(out response, queryString)) return "Web response error"; - var parsedTime = (Dictionary)Util.Serializer.DeserializeObject(response); + var parsedTime = (Dictionary)Util.Serializer.DeserializeObject(response); // TODO dictionary-object does not work with newtonsoft var videoDicts = ((object[])parsedTime["items"]).Cast>().ToArray(); for (int i = 0; i < videoDicts.Length; i++) itemBuffer[i].Length = XmlConvert.ToTimeSpan((string)(((Dictionary)videoDicts[i]["contentDetails"])["duration"])); @@ -273,46 +274,7 @@ public R GetPlaylist(string url) return plist; } - - public static string LoadAlternative(string id) - { - if (!WebWrapper.DownloadString(out string resulthtml, new Uri($"https://www.youtube.com/watch?v={id}&gl=US&hl=en&has_verified=1&bpctr=9999999999"))) - return "No con"; - - int indexof = resulthtml.IndexOf("ytplayer.config =", StringComparison.OrdinalIgnoreCase); - int ptr = indexof; - while (resulthtml[ptr] != '{') ptr++; - int start = ptr; - int stackcnt = 1; - while (stackcnt > 0) - { - ptr++; - if (resulthtml[ptr] == '{') stackcnt++; - else if (resulthtml[ptr] == '}') stackcnt--; - } - - var jsonobj = Util.Serializer.DeserializeObject(resulthtml.Substring(start, ptr - start + 1)); - var args = GetDictVal(jsonobj, "args"); - var urlEncodedFmtStreamMap = GetDictVal(args, "url_encoded_fmt_stream_map"); - if (urlEncodedFmtStreamMap == null) - return "No Data"; - - string[] encoSplit = ((string)urlEncodedFmtStreamMap).Split(','); - foreach (var singleEnco in encoSplit) - { - var lis = HttpUtility.ParseQueryString(singleEnco); - - var signature = lis["s"]; - var url = lis["url"]; - if (!url.Contains("signature")) - url += "&signature=" + signature; - return url; - } - return "No match"; - } - - private static object GetDictVal(object dict, string field) => (dict as Dictionary)?[field]; - + private static R YoutubeDlWrapped(AudioResource resource) { Log.Debug("Falling back to youtube-dl!"); @@ -352,7 +314,7 @@ public R GetThumbnail(PlayResource playResource) if (!WebWrapper.DownloadString(out string response, new Uri($"https://www.googleapis.com/youtube/v3/videos?part=snippet&id={playResource.BaseData.ResourceId}&key={data.ApiKey}"))) return "No connection"; - var parsed = Util.Serializer.Deserialize(response); + var parsed = JsonConvert.DeserializeObject(response); // default: 120px/ 90px // medium : 320px/180px diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index 31497eb8..8ff7e81a 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -64,6 +64,9 @@ ..\packages\Nett.0.8.0\lib\Net40\Nett.dll + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + ..\packages\NLog.4.4.12\lib\net45\NLog.dll @@ -83,7 +86,6 @@ ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll - diff --git a/TS3AudioBot/Web/Api/JsonObject.cs b/TS3AudioBot/Web/Api/JsonObject.cs index 92e8e771..174b619d 100644 --- a/TS3AudioBot/Web/Api/JsonObject.cs +++ b/TS3AudioBot/Web/Api/JsonObject.cs @@ -9,12 +9,11 @@ namespace TS3AudioBot.Web.Api { - using Helper; - using System.Web.Script.Serialization; + using Newtonsoft.Json; public abstract class JsonObject { - [ScriptIgnore] + [JsonIgnore] public string AsStringResult { get; } protected JsonObject(string stringResult) @@ -25,6 +24,6 @@ protected JsonObject(string stringResult) public override string ToString() => AsStringResult; public virtual object GetSerializeObject() => this; - public virtual string Serialize() => Util.Serializer.Serialize(GetSerializeObject()); + public virtual string Serialize() => JsonConvert.SerializeObject(GetSerializeObject()); } } diff --git a/TS3AudioBot/Web/Api/WebApi.cs b/TS3AudioBot/Web/Api/WebApi.cs index 0d5bb964..bcf34b38 100644 --- a/TS3AudioBot/Web/Api/WebApi.cs +++ b/TS3AudioBot/Web/Api/WebApi.cs @@ -11,6 +11,7 @@ namespace TS3AudioBot.Web.Api { using CommandSystem; using Helper; + using Newtonsoft.Json; using Sessions; using System; using System.IO; @@ -126,7 +127,7 @@ private static void ReturnError(CommandExceptionReason reason, string message, H } using (var responseStream = new StreamWriter(response.OutputStream)) - responseStream.Write(Util.Serializer.Serialize(new JsonError(message, reason))); + responseStream.Write(JsonConvert.SerializeObject(new JsonError(message, reason))); } private static void UnescapeAstTree(AstNode node) diff --git a/TS3AudioBot/packages.config b/TS3AudioBot/packages.config index c7b243ce..3645ea60 100644 --- a/TS3AudioBot/packages.config +++ b/TS3AudioBot/packages.config @@ -4,6 +4,7 @@ + diff --git a/TS3Client/Full/Audio/EncoderPipe.cs b/TS3Client/Full/Audio/EncoderPipe.cs index 67eefe2a..1333db2f 100644 --- a/TS3Client/Full/Audio/EncoderPipe.cs +++ b/TS3Client/Full/Audio/EncoderPipe.cs @@ -32,8 +32,7 @@ public class EncoderPipe : IAudioPipe, IDisposable, ISampleInfo private int soundBufferLength; private byte[] notEncodedBuffer = new byte[0]; private int notEncodedBufferLength; - private readonly byte[] segment; - private byte[] encodedBuffer; + private readonly byte[] encodedBuffer; public EncoderPipe(Codec codec) { @@ -72,7 +71,6 @@ public EncoderPipe(Codec codec) BitsPerSample = 16; OptimalPacketSize = opusEncoder.FrameByteCount(SegmentFrames); - segment = new byte[OptimalPacketSize]; encodedBuffer = new byte[opusEncoder.MaxDataBytes]; } @@ -89,9 +87,9 @@ public void Write(Span data, Meta meta) Array.Copy(notEncodedBuffer, 0, soundBuffer, 0, notEncodedBufferLength); data.CopyTo(new Span(soundBuffer, notEncodedBufferLength)); - int byteCap = OptimalPacketSize; - int segmentCount = (int)Math.Floor((float)soundBufferLength / byteCap); - int segmentsEnd = segmentCount * byteCap; + int packetSize = OptimalPacketSize; + int segmentCount = soundBufferLength / packetSize; + int segmentsEnd = segmentCount * packetSize; int newNotEncodedBufferLength = soundBufferLength - segmentsEnd; if (newNotEncodedBufferLength > notEncodedBuffer.Length) notEncodedBuffer = new byte[newNotEncodedBufferLength]; @@ -100,9 +98,7 @@ public void Write(Span data, Meta meta) for (int i = 0; i < segmentCount; i++) { - for (int j = 0; j < segment.Length; j++) - segment[j] = soundBuffer[(i * byteCap) + j]; - var encodedData = opusEncoder.Encode(segment, segment.Length, encodedBuffer); + var encodedData = opusEncoder.Encode(soundBuffer.AsSpan().Slice(i * packetSize, packetSize), packetSize, encodedBuffer); if (meta != null) meta.Codec = Codec; // TODO copy ? OutStream?.Write(encodedData, meta); diff --git a/TS3Client/Full/Audio/Opus/NativeMethods.cs b/TS3Client/Full/Audio/Opus/NativeMethods.cs index fef9b27d..4e4dff74 100644 --- a/TS3Client/Full/Audio/Opus/NativeMethods.cs +++ b/TS3Client/Full/Audio/Opus/NativeMethods.cs @@ -54,7 +54,7 @@ public static string Info internal static extern void opus_encoder_destroy(IntPtr encoder); [DllImport("libopus", CallingConvention = CallingConvention.Cdecl)] - internal static extern int opus_encode(IntPtr st, byte[] pcm, int frameSize, byte[] data, int maxDataBytes); + internal static extern int opus_encode(IntPtr st, ref byte pcm, int frameSize, byte[] data, int maxDataBytes); [DllImport("libopus", CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr opus_decoder_create(int sampleRate, int channels, out IntPtr error); diff --git a/TS3Client/Full/Audio/Opus/OpusEncoder.cs b/TS3Client/Full/Audio/Opus/OpusEncoder.cs index f8d944d3..aa6a8f60 100644 --- a/TS3Client/Full/Audio/Opus/OpusEncoder.cs +++ b/TS3Client/Full/Audio/Opus/OpusEncoder.cs @@ -72,7 +72,7 @@ private OpusEncoder(IntPtr encoder, int inputSamplingRate, int inputChannels, Ap /// How many bytes to encode. /// The encoded data is written to this buffer. /// Opus encoded audio buffer. - public Span Encode(byte[] inputPcmSamples, int sampleLength, byte[] outputEncodedBuffer) + public Span Encode(ReadOnlySpan inputPcmSamples, int sampleLength, byte[] outputEncodedBuffer) { if (disposed) throw new ObjectDisposedException("OpusEncoder"); @@ -80,7 +80,7 @@ public Span Encode(byte[] inputPcmSamples, int sampleLength, byte[] output throw new ArgumentException("Array must be at least MaxDataBytes long", nameof(outputEncodedBuffer)); int frames = FrameCount(inputPcmSamples); - int encodedLength = NativeMethods.opus_encode(encoder, inputPcmSamples, frames, outputEncodedBuffer, sampleLength); + int encodedLength = NativeMethods.opus_encode(encoder, ref inputPcmSamples.DangerousGetPinnableReference(), frames, outputEncodedBuffer, sampleLength); if (encodedLength < 0) throw new Exception("Encoding failed - " + (Errors)encodedLength); @@ -93,7 +93,7 @@ public Span Encode(byte[] inputPcmSamples, int sampleLength, byte[] output /// /// /// - public int FrameCount(byte[] pcmSamples) + public int FrameCount(ReadOnlySpan pcmSamples) { // seems like bitrate should be required const int bitrate = 16; diff --git a/TS3Client/Full/Audio/VolumePipe.cs b/TS3Client/Full/Audio/VolumePipe.cs index 5c2b13e7..abacea2c 100644 --- a/TS3Client/Full/Audio/VolumePipe.cs +++ b/TS3Client/Full/Audio/VolumePipe.cs @@ -28,7 +28,7 @@ public static void AdjustVolume(Span audioSamples, float volume) // fast calculation for *0.5 volume for (int i = 0; i < audioSamples.Length; i += 2) { - short value = unchecked((short)((audioSamples[i + 1]) << 8 | audioSamples[i])); + short value = unchecked((short)((audioSamples[i + 1] << 8) | audioSamples[i])); var tmpshort = value >> 1; audioSamples[i + 0] = unchecked((byte)(tmpshort >> 0)); audioSamples[i + 1] = unchecked((byte)(tmpshort >> 8)); @@ -38,7 +38,7 @@ public static void AdjustVolume(Span audioSamples, float volume) { for (int i = 0; i < audioSamples.Length; i += 2) { - short value = unchecked((short)((audioSamples[i + 1]) << 8 | audioSamples[i])); + short value = unchecked((short)((audioSamples[i + 1] << 8) | audioSamples[i])); var tmpshort = (short)Math.Max(Math.Min(value * volume, short.MaxValue), short.MinValue); audioSamples[i + 0] = unchecked((byte)(tmpshort >> 0)); audioSamples[i + 1] = unchecked((byte)(tmpshort >> 8)); diff --git a/TS3Client/Full/BasePacket.cs b/TS3Client/Full/BasePacket.cs index 42b0b646..36fee155 100644 --- a/TS3Client/Full/BasePacket.cs +++ b/TS3Client/Full/BasePacket.cs @@ -10,6 +10,7 @@ namespace TS3Client.Full { using Helper; + using System; internal class BasePacket { @@ -75,9 +76,9 @@ public override string ToString() $"{(FragmentedFlag ? "X" : "_")} {(NewProtocolFlag ? "X" : "_")} " + $"{(CompressedFlag ? "X" : "_")} {(UnencryptedFlag ? "X" : "_")} ]\t" + $"Id: {PacketId}\n" + + $" MAC: { DebugUtil.DebugToHex(Raw?.AsSpan().Slice(0, 8) ?? new Span()) }\t" + $" Header: { DebugUtil.DebugToHex(Header) }\n" + - $" Data: { DebugUtil.DebugToHex(Data) }\n" + - $" ASCI: { Util.Encoder.GetString(Data) }"; + $" Data: { DebugUtil.DebugToHex(Data) }"; } } } diff --git a/TS3Client/Full/PacketHandler.cs b/TS3Client/Full/PacketHandler.cs index f72447b7..0e4f4f77 100644 --- a/TS3Client/Full/PacketHandler.cs +++ b/TS3Client/Full/PacketHandler.cs @@ -15,7 +15,6 @@ namespace TS3Client.Full using System.Collections.Generic; using System.Diagnostics; using System.IO; - using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; @@ -30,6 +29,7 @@ internal sealed class PacketHandler private static readonly Logger LoggerRtt = LogManager.GetLogger("TS3Client.PacketHandler.Rtt"); private static readonly Logger LoggerRaw = LogManager.GetLogger("TS3Client.PacketHandler.Raw"); + private static readonly Logger LoggerRawVoice = LogManager.GetLogger("TS3Client.PacketHandler.Raw.Voice"); private static readonly Logger LoggerTimeout = LogManager.GetLogger("TS3Client.PacketHandler.Timeout"); // Timout calculations private static readonly TimeSpan PacketTimeout = TimeSpan.FromSeconds(30); @@ -56,6 +56,7 @@ internal sealed class PacketHandler private readonly ushort[] packetCounter; private readonly uint[] generationCounter; + private OutgoingPacket initPacketCheck; private readonly Dictionary packetAckManager; private readonly RingQueue receiveQueue; private readonly RingQueue receiveQueueLow; @@ -101,6 +102,7 @@ public void Connect(IPEndPoint address) lastSentPingId = 0; lastReceivedPingId = 0; + initPacketCheck = null; packetAckManager.Clear(); receiveQueue.Clear(); receiveQueueLow.Clear(); @@ -192,7 +194,7 @@ private void SendOutgoingData(ReadOnlySpan data, PacketType packetType, Pa case PacketType.VoiceWhisper: packet.PacketFlags |= PacketFlags.Unencrypted; NetUtil.H2N(packet.PacketId, packet.Data, 0); - LoggerRaw.ConditionalTrace("[O] {0}", packet); + LoggerRawVoice.ConditionalTrace("[O] {0}", packet); break; case PacketType.Command: @@ -220,7 +222,7 @@ private void SendOutgoingData(ReadOnlySpan data, PacketType packetType, Pa case PacketType.Init1: packet.PacketFlags |= PacketFlags.Unencrypted; - packetAckManager.Add(packet.PacketId, packet); + initPacketCheck = packet; LoggerRaw.Debug("[O] InitID: {0}", packet.Data[4]); LoggerRaw.Trace("[O] {0}", packet); break; @@ -323,7 +325,7 @@ public IncomingPacket FetchPacket() { case PacketType.Voice: case PacketType.VoiceWhisper: - LoggerRaw.ConditionalTrace("[I] {0}", packet); + LoggerRawVoice.ConditionalTrace("[I] {0}", packet); break; case PacketType.Command: LoggerRaw.Debug("[I] {0}", packet); @@ -349,7 +351,7 @@ public IncomingPacket FetchPacket() case PacketType.Init1: LoggerRaw.Debug("[I] InitID: {0}", packet.Data[0]); LoggerRaw.Trace("[I] {0}", packet); - ReceiveInitAck(); + ReceiveInitAck(packet); break; default: throw Util.UnhandledDefault(packet.PacketType); } @@ -525,18 +527,27 @@ private void ReceivePong(IncomingPacket packet) } } - public void ReceiveInitAck() + public void ReceivedFinalInitAck() => ReceiveInitAck(null, true); + + private void ReceiveInitAck(IncomingPacket packet, bool done = false) { - // this method is a bit hacky since it removes ALL Init1 packets - // from the sendQueue instead of the one with the preceding - // init step id (see Ts3Crypt.ProcessInit1). - // But usually this should be no problem since the init order is linear lock (sendLoopLock) { - LoggerRaw.Debug("Cleaned Inits"); - var remPacket = packetAckManager.Values.Where(x => x.PacketType == PacketType.Init1).ToArray(); - foreach (var packet in remPacket) - packetAckManager.Remove(packet.PacketId); + if (initPacketCheck == null || packet == null) + { + if (done) + initPacketCheck = null; + return; + } + // optional: add random number check from init data + var forwardData = ts3Crypt.ProcessInit1(packet.Data); + if (!forwardData.Ok) + { + LoggerRaw.Debug("Wrong init: {0}", forwardData.Error); + return; + } + initPacketCheck = null; + AddOutgoingPacket(forwardData.Value, PacketType.Init1); } } @@ -563,23 +574,24 @@ private void UpdateRto(TimeSpan sampleRtt) /// private void ResendLoop() { - DateTime pingCheck = Util.Now; + var pingCheck = Util.Now; while (Thread.CurrentThread.ManagedThreadId == resendThreadId) { + var now = Util.Now; lock (sendLoopLock) { if (Closed) break; - if (packetAckManager.Count > 0 && ResendPackages(packetAckManager.Values)) + if ((packetAckManager.Count > 0 && ResendPackets(packetAckManager.Values, now)) || + (initPacketCheck != null && ResendPacket(initPacketCheck, now))) { Stop(MoveReason.Timeout); return; } } - var now = Util.Now; var nextTest = pingCheck - now + PingInterval; // we need to check if CryptoInitComplete because while false packet ids won't be incremented if (nextTest < TimeSpan.Zero && ts3Crypt.CryptoInitComplete) @@ -592,28 +604,33 @@ private void ResendLoop() } } - private bool ResendPackages(IEnumerable packetList) + private bool ResendPackets(IEnumerable packetList, DateTime now) { - var now = Util.Now; foreach (var outgoingPacket in packetList) - { - // Check if the packet timed out completely - if (outgoingPacket.FirstSendTime < now - PacketTimeout) - { - LoggerTimeout.Debug("TIMEOUT: {0}", outgoingPacket); + if (ResendPacket(outgoingPacket, now)) return true; - } + return false; + } - // Check if we should retransmit a packet because it probably got lost - if (outgoingPacket.LastSendTime < now - currentRto) - { - LoggerTimeout.Debug("RESEND: {0}", outgoingPacket); - currentRto = currentRto + currentRto; - if (currentRto > MaxRetryInterval) - currentRto = MaxRetryInterval; - SendRaw(outgoingPacket); - } + private bool ResendPacket(OutgoingPacket packet, DateTime now) + { + // Check if the packet timed out completely + if (packet.FirstSendTime < now - PacketTimeout) + { + LoggerTimeout.Debug("TIMEOUT: {0}", packet); + return true; + } + + // Check if we should retransmit a packet because it probably got lost + if (packet.LastSendTime < now - currentRto) + { + LoggerTimeout.Debug("RESEND: {0}", packet); + currentRto = currentRto + currentRto; + if (currentRto > MaxRetryInterval) + currentRto = MaxRetryInterval; + SendRaw(packet); } + return false; } @@ -621,7 +638,7 @@ private void SendRaw(OutgoingPacket packet) { packet.LastSendTime = Util.Now; NetworkStats.LogOutPacket(packet); - LoggerRaw.ConditionalTrace("Sending Raw: {0}", DebugUtil.DebugToHex(packet.Raw)); + LoggerRaw.ConditionalTrace("[O] Raw: {0}", DebugUtil.DebugToHex(packet.Raw)); udpClient.Send(packet.Raw, packet.Raw.Length); } } diff --git a/TS3Client/Full/Ts3Crypt.cs b/TS3Client/Full/Ts3Crypt.cs index edef73c5..b33f3b85 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TS3Client/Full/Ts3Crypt.cs @@ -231,36 +231,45 @@ internal R ProcessInit1(byte[] data) const int versionLen = 4; const int initTypeLen = 1; - if (data == null) + int? type = null; + if (data != null) { - var sendData = new byte[versionLen + initTypeLen + 4 + 4 + 8]; + type = data[0]; + if (data.Length < initTypeLen) + return "Invalid init packet (too short)"; + } + byte[] sendData; + + switch (type) + { + case 0x7F: + // Some strange servers do this + // the normal client responds by starting agian + case null: + sendData = new byte[versionLen + initTypeLen + 4 + 4 + 8]; Array.Copy(Initversion, 0, sendData, 0, versionLen); // initVersion sendData[versionLen] = 0x00; // initType NetUtil.H2N(Util.UnixNow, sendData, versionLen + initTypeLen); // 4byte timestamp - for (int i = 0; i < 4; i++) sendData[i + versionLen + initTypeLen + 4] = (byte)Util.Random.Next(0, 256); // 4byte random + for (int i = 0; i < 4; i++) + sendData[i + versionLen + initTypeLen + 4] = (byte)Util.Random.Next(0, 256); // 4byte random return sendData; - } - if (data.Length < initTypeLen) return "Invalid init packet (too short)"; - int type = data[0]; - if (type == 1) - { - var sendData = new byte[versionLen + initTypeLen + 16 + 4]; + case 1: + sendData = new byte[versionLen + initTypeLen + 16 + 4]; Array.Copy(Initversion, 0, sendData, 0, versionLen); // initVersion sendData[versionLen] = 0x02; // initType Array.Copy(data, 1, sendData, versionLen + initTypeLen, 20); return sendData; - } - else if (type == 3) - { + + case 3: byte[] alphaBytes = new byte[10]; Util.Random.NextBytes(alphaBytes); var alpha = Convert.ToBase64String(alphaBytes); string initAdd = Ts3Command.BuildToString("clientinitiv", new ICommandPart[] { - new CommandParameter("alpha", alpha), - new CommandParameter("omega", Identity.PublicKeyString), - new CommandParameter("ip", string.Empty) }); + new CommandParameter("alpha", alpha), + new CommandParameter("omega", Identity.PublicKeyString), + new CommandParameter("ip", string.Empty) }); var textBytes = Util.Encoder.GetBytes(initAdd); // Prepare solution @@ -270,7 +279,7 @@ internal R ProcessInit1(byte[] data) return y; // Copy bytes for this result: [Version..., InitType..., data..., y..., text...] - var sendData = new byte[versionLen + initTypeLen + 232 + 64 + textBytes.Length]; + sendData = new byte[versionLen + initTypeLen + 232 + 64 + textBytes.Length]; // Copy this.Version Array.Copy(Initversion, 0, sendData, 0, versionLen); // Write InitType @@ -281,11 +290,11 @@ internal R ProcessInit1(byte[] data) Array.Copy(y.Value, 0, sendData, versionLen + initTypeLen + 232 + (64 - y.Value.Length), y.Value.Length); // Copy text Array.Copy(textBytes, 0, sendData, versionLen + initTypeLen + 232 + 64, textBytes.Length); - return sendData; - } - return "Got invalid init packet id"; + default: + return "Got invalid init packet id"; + } } /// This method calculates x ^ (2^level) % n = y which is the solution to the server RSA puzzle. diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index bae309c4..e152f6f0 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -286,13 +286,6 @@ private void NetworkLoop(object ctxObject) } }); break; - - case PacketType.Init1: - var forwardData = ts3Crypt.ProcessInit1(packet.Data); - if (!forwardData.Ok) - break; - packetHandler.AddOutgoingPacket(forwardData.Value, PacketType.Init1); - break; } } } @@ -323,7 +316,7 @@ private void ProcessInitIvExpand(InitIvExpand initIvExpand) private void ProcessInitServer(InitServer initServer) { packetHandler.ClientId = initServer.ClientId; - packetHandler.ReceiveInitAck(); + packetHandler.ReceivedFinalInitAck(); lock (statusLock) status = Ts3ClientStatus.Connected; diff --git a/TS3Client/Helper/R.cs b/TS3Client/Helper/R.cs index e51ceafd..3c30a4a0 100644 --- a/TS3Client/Helper/R.cs +++ b/TS3Client/Helper/R.cs @@ -65,6 +65,8 @@ public struct R public static implicit operator R(TSuccess result) => new R(result); public static implicit operator R(string error) => new R(error); + public TSuccess OkOr(TSuccess alt) => Ok ? Value : alt; + public override string ToString() => Error; } diff --git a/TS3Client/Helper/Util.cs b/TS3Client/Helper/Util.cs index ac86a995..f165c2bf 100644 --- a/TS3Client/Helper/Util.cs +++ b/TS3Client/Helper/Util.cs @@ -94,6 +94,21 @@ internal static R, CommandError> UnwrapNotification(this R string.Join(" ", data.Select(x => x.ToString("X2"))); + public static string DebugToHex(byte[] bytes) => bytes == 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); + } } } From c88dad0605794e8eb715271f1dcd008a57a51f01 Mon Sep 17 00:00:00 2001 From: Splamy Date: Sat, 13 Jan 2018 03:54:02 +0100 Subject: [PATCH 40/48] Fixed ffmpeg pipe deadlock. Fixed #130 --- TS3AudioBot/Audio/FfmpegProducer.cs | 47 +++++++++++++++++------------ 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs index 4563cf4c..ba21914e 100644 --- a/TS3AudioBot/Audio/FfmpegProducer.cs +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -147,7 +147,9 @@ public R StartFfmpegProcess(string url, string extraPreParam = null, string extr } }; Log.Trace("Starting with {0}", ffmpegProcess.StartInfo.Arguments); + ffmpegProcess.ErrorDataReceived += FfmpegProcess_ErrorDataReceived; ffmpegProcess.Start(); + ffmpegProcess.BeginErrorReadLine(); lastLink = url; parsedSongLength = null; @@ -160,6 +162,31 @@ public R StartFfmpegProcess(string url, string extraPreParam = null, string extr catch (Exception ex) { return $"Unable to create stream ({ex.Message})"; } } + private void FfmpegProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e) + { + if (e.Data == null) + return; + + lock (ffmpegLock) + { + if (parsedSongLength.HasValue) + return; + + var match = FindDurationMatch.Match(e.Data); + if (!match.Success) + return; + + if (sender != ffmpegProcess) + return; + + int hours = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); + int minutes = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); + int seconds = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture); + int millisec = int.Parse(match.Groups[4].Value, CultureInfo.InvariantCulture) * 10; + parsedSongLength = new TimeSpan(0, hours, minutes, seconds, millisec); + } + } + private void StopFfmpegProcess() { // TODO somehow bypass lock @@ -190,25 +217,7 @@ private TimeSpan GetCurrentSongLength() if (parsedSongLength.HasValue) return parsedSongLength.Value; - Match match = null; - while (ffmpegProcess.StandardError.Peek() > -1) - { - var infoLine = ffmpegProcess.StandardError.ReadLine(); - if (string.IsNullOrEmpty(infoLine)) - continue; - match = FindDurationMatch.Match(infoLine); - if (match.Success) - break; - } - if (match == null || !match.Success) - return TimeSpan.Zero; - - int hours = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); - int minutes = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); - int seconds = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture); - int millisec = int.Parse(match.Groups[4].Value, CultureInfo.InvariantCulture) * 10; - parsedSongLength = new TimeSpan(0, hours, minutes, seconds, millisec); - return parsedSongLength.Value; + return TimeSpan.Zero; } } From 7723cfcc4743a32b25a1feaedbc334b34eb99aea Mon Sep 17 00:00:00 2001 From: Splamy Date: Mon, 22 Jan 2018 03:40:42 +0100 Subject: [PATCH 41/48] dependency wip --- TS3AudioBot.sln | 6 +- TS3AudioBot/Bot.cs | 5 +- TS3AudioBot/Core.cs | 8 +- TS3AudioBot/Dependency/DependencyInjector.cs | 60 +- TS3Client/Full/Audio/VolumePipe.cs | 20 +- TS3Client/Full/BasePacket.cs | 7 +- .../Full/{OutgoingPacket.cs => C2SPacket.cs} | 22 +- TS3Client/Full/IncomingPacket.cs | 20 - TS3Client/Full/NetworkStats.cs | 4 +- TS3Client/Full/PacketHandler.cs | 44 +- TS3Client/Full/S2CPacket.cs | 42 + TS3Client/Full/Ts3Crypt.cs | 72 +- TS3Client/Full/Ts3FullClient.cs | 46 +- TS3Client/Generated/Errors.cs | 3 + TS3Client/Generated/Messages.cs | 797 ++++++++++-------- TS3Client/Generated/Messages.tt | 40 +- TS3Client/Generated/Permissions.cs | 3 + TS3Client/Generated/Versions.cs | 4 + TS3Client/Helper/Extensions.cs | 52 ++ TS3Client/Helper/SpanSplitter.cs | 50 ++ TS3Client/Helper/Util.cs | 36 +- TS3Client/Messages/BaseTypes.cs | 4 +- TS3Client/Messages/Deserializer.cs | 42 +- TS3Client/Messages/ResponseDictionary.cs | 16 +- TS3Client/OwnEnums.cs | 48 -- TS3Client/TS3Client.csproj | 7 +- TS3Client/ts3protocol.md | 644 -------------- Ts3ClientTests/Program.cs | 9 +- 28 files changed, 836 insertions(+), 1275 deletions(-) rename TS3Client/Full/{OutgoingPacket.cs => C2SPacket.cs} (62%) delete mode 100644 TS3Client/Full/IncomingPacket.cs create mode 100644 TS3Client/Full/S2CPacket.cs create mode 100644 TS3Client/Helper/Extensions.cs create mode 100644 TS3Client/Helper/SpanSplitter.cs delete mode 100644 TS3Client/ts3protocol.md diff --git a/TS3AudioBot.sln b/TS3AudioBot.sln index 8545bdd4..5bfa0af1 100644 --- a/TS3AudioBot.sln +++ b/TS3AudioBot.sln @@ -18,9 +18,6 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ts3ClientTests", "Ts3ClientTests\Ts3ClientTests.csproj", "{3F6F11F0-C0DE-4C24-B39F-4A5B5B150376}" EndProject Global - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -49,4 +46,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {15C2EA96-9126-41B1-A7A1-B02663F50EE3} EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index b5fc7239..63dd3ee1 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -17,6 +17,7 @@ namespace TS3AudioBot using System; using System.IO; using System.Threading; + using Dependency; using TS3Client; using TS3Client.Messages; @@ -31,6 +32,8 @@ public sealed class Bot : IDisposable internal object SyncRoot { get; } = new object(); internal bool IsDisposed { get; private set; } + internal DependencyRealm Injector { get; set; } + internal TargetScript TargetScript { get; set; } /// Mangement for playlists. public PlaylistManager PlaylistManager { get; private set; } @@ -287,7 +290,7 @@ public void Dispose() if (!IsDisposed) IsDisposed = true; else return; Log.Info("Bot disconnecting."); - + PlayManager?.Stop(); PlayerConnection?.Dispose(); // before: logStream, diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index ecde79bb..a18e2f7a 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -97,7 +97,7 @@ internal static void Main(string[] args) /// Manages plugins, provides various loading and unloading mechanisms. internal PluginManager PluginManager { get; set; } /// Manages a dependency hierachy and injects required modules at runtime. - internal Injector Injector { get; set; } + internal CoreInjector Injector { get; set; } /// Mangement for the bot command system. public CommandManager CommandManager { get; set; } /// Manages factories which can load resources. @@ -176,15 +176,15 @@ private R InitializeCore() Log.Info("[============ TS3AudioBot started =============]"); Log.Info("[=== Date/Time: {0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString()); - Log.Info("[=== Version: {0}", Util.GetAssemblyData().ToString()); - Log.Info("[=== Plattform: {0}", Util.GetPlattformData()); + Log.Info("[=== Version: {0}", Util.GetAssemblyData()); + Log.Info("[=== Platform: {0}", Util.GetPlattformData()); Log.Info("[==============================================]"); Log.Info("[============ Initializing Modules ============]"); Log.Info("Using opus version: {0}", TS3Client.Full.Audio.Opus.NativeMethods.Info); TS3Client.Messages.Deserializer.OnError += (s, e) => Log.Error(e.ToString()); - Injector = new Injector(); + Injector = new CoreInjector(); Injector.RegisterModule(this); Injector.RegisterModule(ConfigManager); Injector.RegisterModule(Injector); diff --git a/TS3AudioBot/Dependency/DependencyInjector.cs b/TS3AudioBot/Dependency/DependencyInjector.cs index fc9b6ec8..143160b9 100644 --- a/TS3AudioBot/Dependency/DependencyInjector.cs +++ b/TS3AudioBot/Dependency/DependencyInjector.cs @@ -16,36 +16,17 @@ namespace TS3AudioBot.Dependency using System.Linq; using System.Reflection; - public class Injector : DependencyRealm, ICoreModule - { - public Injector() - { - } - - public void Initialize() { } - - public R GetCoreModule() where T : ICoreModule - { - if(loaded.TryGetValue(typeof(T), out var mod)) - return (T)mod.Obj; - return "Module not found"; - } - } + public sealed class CoreInjector : DependencyRealm { } public class Module { - private static readonly ConcurrentDictionary typeData; + private static readonly ConcurrentDictionary TypeData = new ConcurrentDictionary(); public bool IsInitialized { get; set; } public object Obj { get; } public Type BaseType { get; } // object SyncContext; - static Module() - { - Util.Init(out typeData); - } - public Module(object obj, Type baseType) { IsInitialized = false; @@ -55,26 +36,26 @@ public Module(object obj, Type baseType) public Type[] GetDependants() => GetDependants(Obj.GetType()); - public IEnumerable GetModuleProperties() => GetModuleProperties(Obj.GetType()); - private static Type[] GetDependants(Type type) { - if (!typeData.TryGetValue(type, out var depArr)) + if (!TypeData.TryGetValue(type, out var depArr)) { depArr = GetModuleProperties(type).Select(p => p.PropertyType).ToArray(); - typeData[type] = depArr; + TypeData[type] = depArr; } return depArr; } - private static IEnumerable GetModuleProperties(Type type) => type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + public IEnumerable GetModuleProperties() => GetModuleProperties(Obj.GetType()); + + private static IEnumerable GetModuleProperties(IReflect type) => type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(p => p.CanRead && p.CanWrite && typeof(ITabModule).IsAssignableFrom(p.PropertyType)); } - public class DependencyRealm + public class DependencyRealm where TRealm : ITabModule { - protected ConcurrentDictionary loaded; - protected List waiting; + private readonly ConcurrentDictionary loaded; + private readonly List waiting; public DependencyRealm() { @@ -82,14 +63,14 @@ public DependencyRealm() Util.Init(out waiting); } - public T Create() where T : ICoreModule => (T)CreateFromType(typeof(T)); + public TModule Create() where TModule : TRealm => (TModule)CreateFromType(typeof(TModule)); public object CreateFromType(Type type) { var obj = Activator.CreateInstance(type); RegisterInjectable(obj, false); return obj; } - public void RegisterModule(T obj, bool initialized = false) where T : ICoreModule => RegisterInjectable(obj, initialized, typeof(T)); + public void RegisterModule(TModule obj, bool initialized = false) => RegisterInjectable(obj, initialized, typeof(TModule)); public void RegisterInjectable(object obj, bool initialized = false, Type baseType = null) { var modType = baseType ?? obj.GetType(); @@ -115,10 +96,10 @@ public void SkipInitialized(object obj) public void ForceCyclicResolve() { - + // TODO } - protected bool SetInitalized(Module module) + private bool SetInitalized(Module module) { if (!module.IsInitialized && module.Obj is ITabModule tabModule) { @@ -129,7 +110,7 @@ protected bool SetInitalized(Module module) return true; } - protected void DoQueueInitialize() + private void DoQueueInitialize() { bool changed; do @@ -153,7 +134,7 @@ protected void DoQueueInitialize() } while (changed); } - protected bool IsResolvable(Module module) + private bool IsResolvable(Module module) { var deps = module.GetDependants(); foreach (var depeningType in deps) @@ -164,7 +145,7 @@ protected bool IsResolvable(Module module) return true; } - protected bool DoResolve(Module module) + private bool DoResolve(Module module) { var props = module.GetModuleProperties(); foreach (var prop in props) @@ -181,6 +162,13 @@ protected bool DoResolve(Module module) return SetInitalized(module); } + public R GetModule() where TModule : TRealm + { + if (loaded.TryGetValue(typeof(TModule), out var mod)) + return (TModule)mod.Obj; + return "Module not found"; + } + public bool AllResolved() => waiting.Count == 0; public void Unregister(Type type) diff --git a/TS3Client/Full/Audio/VolumePipe.cs b/TS3Client/Full/Audio/VolumePipe.cs index abacea2c..f007076c 100644 --- a/TS3Client/Full/Audio/VolumePipe.cs +++ b/TS3Client/Full/Audio/VolumePipe.cs @@ -26,23 +26,15 @@ public static void AdjustVolume(Span audioSamples, float volume) else if (IsAbout(volume, 0.5f)) { // fast calculation for *0.5 volume - for (int i = 0; i < audioSamples.Length; i += 2) - { - short value = unchecked((short)((audioSamples[i + 1] << 8) | audioSamples[i])); - var tmpshort = value >> 1; - audioSamples[i + 0] = unchecked((byte)(tmpshort >> 0)); - audioSamples[i + 1] = unchecked((byte)(tmpshort >> 8)); - } + var shortArr = audioSamples.NonPortableCast(); + for (int i = 0; i < shortArr.Length; i++) + shortArr[i] = (short)(shortArr[i] >> 1); } else { - for (int i = 0; i < audioSamples.Length; i += 2) - { - short value = unchecked((short)((audioSamples[i + 1] << 8) | audioSamples[i])); - var tmpshort = (short)Math.Max(Math.Min(value * volume, short.MaxValue), short.MinValue); - audioSamples[i + 0] = unchecked((byte)(tmpshort >> 0)); - audioSamples[i + 1] = unchecked((byte)(tmpshort >> 8)); - } + var shortArr = audioSamples.NonPortableCast(); + for (int i = 0; i < shortArr.Length; i++) + shortArr[i] = (short)Math.Max(Math.Min(shortArr[i] * volume, short.MaxValue), short.MinValue); } } diff --git a/TS3Client/Full/BasePacket.cs b/TS3Client/Full/BasePacket.cs index 36fee155..2ce2e79b 100644 --- a/TS3Client/Full/BasePacket.cs +++ b/TS3Client/Full/BasePacket.cs @@ -12,7 +12,7 @@ namespace TS3Client.Full using Helper; using System; - internal class BasePacket + internal abstract class BasePacket { public PacketType PacketType { @@ -28,6 +28,8 @@ public PacketFlags PacketFlags public ushort PacketId { get; set; } public uint GenerationId { get; set; } public int Size => Data.Length; + public abstract bool FromServer { get; } + public abstract int HeaderLength { get; } public byte[] Raw { get; set; } public byte[] Header { get; protected set; } @@ -80,5 +82,8 @@ public override string ToString() $" Header: { DebugUtil.DebugToHex(Header) }\n" + $" Data: { DebugUtil.DebugToHex(Data) }"; } + + public abstract void BuildHeader(); + public abstract void BuildHeader(Span into); } } diff --git a/TS3Client/Full/OutgoingPacket.cs b/TS3Client/Full/C2SPacket.cs similarity index 62% rename from TS3Client/Full/OutgoingPacket.cs rename to TS3Client/Full/C2SPacket.cs index 6c3d6ff5..156e73ad 100644 --- a/TS3Client/Full/OutgoingPacket.cs +++ b/TS3Client/Full/C2SPacket.cs @@ -11,34 +11,38 @@ namespace TS3Client.Full { using System; - internal sealed class OutgoingPacket : BasePacket + internal sealed class C2SPacket : BasePacket { + public const int HeaderLen = 5; + public ushort ClientId { get; set; } + public override bool FromServer { get; } = false; + public override int HeaderLength { get; } = HeaderLen; public DateTime FirstSendTime { get; set; } public DateTime LastSendTime { get; set; } - public OutgoingPacket(byte[] data, PacketType type) + public C2SPacket(byte[] data, PacketType type) { Data = data; PacketType = type; - Header = new byte[5]; + Header = new byte[HeaderLen]; } - public void BuildHeader() + public override void BuildHeader() { NetUtil.H2N(PacketId, Header, 0); NetUtil.H2N(ClientId, Header, 2); Header[4] = PacketTypeFlagged; } - public void BuildHeader(Span buffer) + public override void BuildHeader(Span into) { - NetUtil.H2N(PacketId, buffer.Slice(0, 2)); - NetUtil.H2N(ClientId, buffer.Slice(2, 2)); - buffer[4] = PacketTypeFlagged; + NetUtil.H2N(PacketId, into.Slice(0, 2)); + NetUtil.H2N(ClientId, into.Slice(2, 2)); + into[4] = PacketTypeFlagged; #if DEBUG - buffer.CopyTo(Header.AsSpan()); + into.CopyTo(Header.AsSpan()); #endif } diff --git a/TS3Client/Full/IncomingPacket.cs b/TS3Client/Full/IncomingPacket.cs deleted file mode 100644 index 3d29880b..00000000 --- a/TS3Client/Full/IncomingPacket.cs +++ /dev/null @@ -1,20 +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.Full -{ - internal sealed class IncomingPacket : BasePacket - { - public IncomingPacket(byte[] raw) - { - Raw = raw; - Header = new byte[3]; - } - } -} diff --git a/TS3Client/Full/NetworkStats.cs b/TS3Client/Full/NetworkStats.cs index 86608f6d..029de432 100644 --- a/TS3Client/Full/NetworkStats.cs +++ b/TS3Client/Full/NetworkStats.cs @@ -30,7 +30,7 @@ public sealed class NetworkStats private static readonly TimeSpan TimeMinute = TimeSpan.FromMinutes(1); private readonly object queueLock = new object(); - internal void LogOutPacket(OutgoingPacket packet) + internal void LogOutPacket(C2SPacket packet) { var kind = TypeToKind(packet.PacketType); outPackets[(int)kind]++; @@ -42,7 +42,7 @@ internal void LogOutPacket(OutgoingPacket packet) } } - internal void LogInPacket(IncomingPacket packet) + internal void LogInPacket(S2CPacket packet) { var kind = TypeToKind(packet.PacketType); inPackets[(int)kind]++; diff --git a/TS3Client/Full/PacketHandler.cs b/TS3Client/Full/PacketHandler.cs index 0e4f4f77..94e6fcb2 100644 --- a/TS3Client/Full/PacketHandler.cs +++ b/TS3Client/Full/PacketHandler.cs @@ -56,10 +56,10 @@ internal sealed class PacketHandler private readonly ushort[] packetCounter; private readonly uint[] generationCounter; - private OutgoingPacket initPacketCheck; - private readonly Dictionary packetAckManager; - private readonly RingQueue receiveQueue; - private readonly RingQueue receiveQueueLow; + private C2SPacket initPacketCheck; + private readonly Dictionary packetAckManager; + private readonly RingQueue receiveQueue; + private readonly RingQueue receiveQueueLow; private readonly object sendLoopLock = new object(); private readonly AutoResetEvent sendLoopPulse = new AutoResetEvent(false); private readonly Ts3Crypt ts3Crypt; @@ -76,9 +76,9 @@ internal sealed class PacketHandler public PacketHandler(Ts3Crypt ts3Crypt) { - packetAckManager = new Dictionary(); - receiveQueue = new RingQueue(ReceivePacketWindowSize, ushort.MaxValue + 1); - receiveQueueLow = new RingQueue(ReceivePacketWindowSize, ushort.MaxValue + 1); + packetAckManager = new Dictionary(); + receiveQueue = new RingQueue(ReceivePacketWindowSize, ushort.MaxValue + 1); + receiveQueueLow = new RingQueue(ReceivePacketWindowSize, ushort.MaxValue + 1); NetworkStats = new NetworkStats(); packetCounter = new ushort[9]; @@ -175,7 +175,7 @@ public void AddOutgoingPacket(ReadOnlySpan packet, PacketType packetType, private void SendOutgoingData(ReadOnlySpan data, PacketType packetType, PacketFlags flags = PacketFlags.None) { - var packet = new OutgoingPacket(data.ToArray(), packetType); + var packet = new C2SPacket(data.ToArray(), packetType); lock (sendLoopLock) { @@ -242,7 +242,7 @@ private IdTuple GetPacketCounter(PacketType packetType) ? new IdTuple(packetCounter[(int)packetType], generationCounter[(int)packetType]) : new IdTuple(101, 0); - private void IncPacketCounter(PacketType packetType) + public void IncPacketCounter(PacketType packetType) { unchecked { packetCounter[(int)packetType]++; } if (packetCounter[(int)packetType] == 0) @@ -285,7 +285,7 @@ private void AddOutgoingSplitData(ReadOnlySpan rawData, PacketType packetT private static bool NeedsSplitting(int dataSize) => dataSize + HeaderSize > MaxPacketSize; - public IncomingPacket FetchPacket() + public S2CPacket FetchPacket() { while (true) { @@ -365,10 +365,10 @@ public IncomingPacket FetchPacket() // These methods are for low level packet processing which the // rather high level TS3FullClient should not worry about. - private void GenerateGenerationId(IncomingPacket packet) + private void GenerateGenerationId(S2CPacket packet) { // TODO rework this for all packet types - RingQueue packetQueue; + RingQueue packetQueue; switch (packet.PacketType) { case PacketType.Command: packetQueue = receiveQueue; break; @@ -379,7 +379,7 @@ private void GenerateGenerationId(IncomingPacket packet) packet.GenerationId = packetQueue.GetGeneration(packet.PacketId); } - private IncomingPacket ReceiveCommand(IncomingPacket packet, RingQueue packetQueue, PacketType ackType) + private S2CPacket ReceiveCommand(S2CPacket packet, RingQueue packetQueue, PacketType ackType) { var setStatus = packetQueue.IsSet(packet.PacketId); @@ -398,7 +398,7 @@ private IncomingPacket ReceiveCommand(IncomingPacket packet, RingQueue packetQueue, out IncomingPacket packet) + private static bool TryFetchPacket(RingQueue packetQueue, out S2CPacket packet) { if (packetQueue.Count <= 0) { packet = null; return false; } @@ -445,7 +445,7 @@ private static bool TryFetchPacket(RingQueue packetQueue, out In for (int i = 1; i < take; i++) { - if (!packetQueue.TryDequeue(out IncomingPacket nextPacket)) + if (!packetQueue.TryDequeue(out S2CPacket nextPacket)) throw new InvalidOperationException("Packet in queue got missing (?)"); Array.Copy(nextPacket.Data, 0, preFinalArray, curCopyPos, nextPacket.Size); @@ -480,7 +480,7 @@ private void SendAck(ushort ackId, PacketType ackType) throw new InvalidOperationException("Packet type is not an Ack-type"); } - private IncomingPacket ReceiveAck(IncomingPacket packet) + private S2CPacket ReceiveAck(S2CPacket packet) { if (packet.Data.Length < 2) return null; @@ -503,7 +503,7 @@ private void SendPing() pingTimer.Restart(); } - private void ReceivePing(IncomingPacket packet) + private void ReceivePing(S2CPacket packet) { var idDiff = packet.PacketId - lastReceivedPingId; if (idDiff > 1 && idDiff < ReceivePacketWindowSize) @@ -515,7 +515,7 @@ private void ReceivePing(IncomingPacket packet) AddOutgoingPacket(pongData, PacketType.Pong); } - private void ReceivePong(IncomingPacket packet) + private void ReceivePong(S2CPacket packet) { ushort answerId = NetUtil.N2Hushort(packet.Data, 0); @@ -529,7 +529,7 @@ private void ReceivePong(IncomingPacket packet) public void ReceivedFinalInitAck() => ReceiveInitAck(null, true); - private void ReceiveInitAck(IncomingPacket packet, bool done = false) + private void ReceiveInitAck(S2CPacket packet, bool done = false) { lock (sendLoopLock) { @@ -604,7 +604,7 @@ private void ResendLoop() } } - private bool ResendPackets(IEnumerable packetList, DateTime now) + private bool ResendPackets(IEnumerable packetList, DateTime now) { foreach (var outgoingPacket in packetList) if (ResendPacket(outgoingPacket, now)) @@ -612,7 +612,7 @@ private bool ResendPackets(IEnumerable packetList, DateTime now) return false; } - private bool ResendPacket(OutgoingPacket packet, DateTime now) + private bool ResendPacket(C2SPacket packet, DateTime now) { // Check if the packet timed out completely if (packet.FirstSendTime < now - PacketTimeout) @@ -634,7 +634,7 @@ private bool ResendPacket(OutgoingPacket packet, DateTime now) return false; } - private void SendRaw(OutgoingPacket packet) + private void SendRaw(C2SPacket packet) { packet.LastSendTime = Util.Now; NetworkStats.LogOutPacket(packet); diff --git a/TS3Client/Full/S2CPacket.cs b/TS3Client/Full/S2CPacket.cs new file mode 100644 index 00000000..6a08b75f --- /dev/null +++ b/TS3Client/Full/S2CPacket.cs @@ -0,0 +1,42 @@ +// 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.Full +{ + using System; + + internal sealed class S2CPacket : BasePacket + { + public const int HeaderLen = 3; + + public override bool FromServer { get; } = true; + public override int HeaderLength { get; } = HeaderLen; + + public S2CPacket(byte[] raw) + { + Raw = raw; + Header = new byte[HeaderLen]; + } + + public override void BuildHeader() + { + NetUtil.H2N(PacketId, Header, 0); + Header[2] = PacketTypeFlagged; + } + + public override void BuildHeader(Span into) + { + NetUtil.H2N(PacketId, into.Slice(0, 2)); + into[2] = PacketTypeFlagged; +#if DEBUG + into.CopyTo(Header.AsSpan()); +#endif + } + } +} diff --git a/TS3Client/Full/Ts3Crypt.cs b/TS3Client/Full/Ts3Crypt.cs index b33f3b85..943a9250 100644 --- a/TS3Client/Full/Ts3Crypt.cs +++ b/TS3Client/Full/Ts3Crypt.cs @@ -38,14 +38,12 @@ public sealed class Ts3Crypt private readonly EaxBlockCipher eaxCipher = new EaxBlockCipher(new AesEngine()); private const int MacLen = 8; - private const int OutHeaderLen = 5; - private const int InHeaderLen = 3; private const int PacketTypeKinds = 9; public IdentityData Identity { get; set; } internal bool CryptoInitComplete { get; private set; } - private readonly byte[] ivStruct = new byte[20]; + private byte[] ivStruct; private readonly byte[] fakeSignature = new byte[MacLen]; private readonly Tuple[] cachedKeyNonces = new Tuple[PacketTypeKinds * 2]; @@ -57,7 +55,7 @@ public Ts3Crypt() internal void Reset() { CryptoInitComplete = false; - Array.Clear(ivStruct, 0, ivStruct.Length); + ivStruct = null; // TODO recheck Array.Clear(fakeSignature, 0, fakeSignature.Length); Array.Clear(cachedKeyNonces, 0, cachedKeyNonces.Length); Identity = null; @@ -214,6 +212,7 @@ private byte[] GetSharedSecret(ECPoint publicKeyPoint) private void SetSharedSecret(byte[] alpha, byte[] beta, byte[] sharedKey) { // prepares the ivstruct consisting of 2 random byte chains of 10 bytes which each both clients agreed on + ivStruct = new byte[20]; Array.Copy(alpha, 0, ivStruct, 0, 10); Array.Copy(beta, 0, ivStruct, 10, 10); @@ -226,6 +225,10 @@ private void SetSharedSecret(byte[] alpha, byte[] beta, byte[] sharedKey) Array.Copy(buffer, 0, fakeSignature, 0, 8); } + internal void CryptoInit2(string alpha, string beta, string omega) { throw new NotImplementedException(); } + private byte[] GetSharedSecret2(ECPoint publicKeyPoint) { throw new NotImplementedException(); } + private void SetSharedSecret2(byte[] alpha, byte[] beta, byte[] sharedKey) { throw new NotImplementedException(); } + internal R ProcessInit1(byte[] data) { const int versionLen = 4; @@ -243,8 +246,8 @@ internal R ProcessInit1(byte[] data) switch (type) { case 0x7F: - // Some strange servers do this - // the normal client responds by starting agian + // Some strange servers do this + // the normal client responds by starting agian case null: sendData = new byte[versionLen + initTypeLen + 4 + 4 + 8]; Array.Copy(Initversion, 0, sendData, 0, versionLen); // initVersion @@ -269,6 +272,7 @@ internal R ProcessInit1(byte[] data) 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); @@ -317,7 +321,7 @@ private static R SolveRsaChallange(byte[] data, int offset, int level) #region ENCRYPTION/DECRYPTION - internal void Encrypt(OutgoingPacket packet) + internal void Encrypt(BasePacket packet) { if (packet.PacketType == PacketType.Init1) { @@ -330,7 +334,7 @@ internal void Encrypt(OutgoingPacket packet) return; } - var (key, nonce) = GetKeyNonce(false, packet.PacketId, packet.GenerationId, packet.PacketType); + var (key, nonce) = GetKeyNonce(packet.FromServer, packet.PacketId, packet.GenerationId, packet.PacketType); packet.BuildHeader(); ICipherParameters ivAndKey = new AeadParameters(new KeyParameter(key), 8 * MacLen, nonce, packet.Header); @@ -352,41 +356,41 @@ internal void Encrypt(OutgoingPacket packet) // to build the final TS3/libtomcrypt we need to copy it into another order // len is Data.Length + Mac.Length - packet.Raw = new byte[OutHeaderLen + len]; + packet.Raw = new byte[packet.HeaderLength + len]; // Copy the Mac from [Data..., Mac...] to [Mac..., Header..., Data...] Array.Copy(result, len - MacLen, packet.Raw, 0, MacLen); // Copy the Header from packet.Header to [Mac..., Header..., Data...] - Array.Copy(packet.Header, 0, packet.Raw, MacLen, OutHeaderLen); + Array.Copy(packet.Header, 0, packet.Raw, MacLen, packet.HeaderLength); // Copy the Data from [Data..., Mac...] to [Mac..., Header..., Data...] - Array.Copy(result, 0, packet.Raw, MacLen + OutHeaderLen, len - MacLen); + Array.Copy(result, 0, packet.Raw, MacLen + packet.HeaderLength, len - MacLen); // Raw is now [Mac..., Header..., Data...] } - private static void FakeEncrypt(OutgoingPacket packet, byte[] mac) + private static void FakeEncrypt(BasePacket packet, byte[] mac) { - packet.Raw = new byte[packet.Data.Length + MacLen + OutHeaderLen]; + packet.Raw = new byte[packet.Data.Length + MacLen + packet.HeaderLength]; // Copy the Mac from [Mac...] to [Mac..., Header..., Data...] Array.Copy(mac, 0, packet.Raw, 0, MacLen); // Copy the Header from packet.Header to [Mac..., Header..., Data...] - packet.BuildHeader(packet.Raw.AsSpan().Slice(MacLen, OutHeaderLen)); + packet.BuildHeader(packet.Raw.AsSpan().Slice(MacLen, packet.HeaderLength)); // Copy the Data from packet.Data to [Mac..., Header..., Data...] - Array.Copy(packet.Data, 0, packet.Raw, MacLen + OutHeaderLen, packet.Data.Length); + Array.Copy(packet.Data, 0, packet.Raw, MacLen + packet.HeaderLength, packet.Data.Length); // Raw is now [Mac..., Header..., Data...] } - internal static IncomingPacket GetIncommingPacket(byte[] data) + internal static S2CPacket GetIncommingPacket(byte[] data) { - if (data.Length < InHeaderLen + MacLen) + if (data.Length < S2CPacket.HeaderLen + MacLen) return null; - return new IncomingPacket(data) + return new S2CPacket(data) { PacketTypeFlagged = data[MacLen + 2], PacketId = NetUtil.N2Hushort(data, MacLen), }; } - internal bool Decrypt(IncomingPacket packet) + internal bool Decrypt(BasePacket packet) { if (packet.PacketType == PacketType.Init1) return FakeDecrypt(packet, Ts3InitMac); @@ -397,11 +401,11 @@ internal bool Decrypt(IncomingPacket packet) return DecryptData(packet); } - private bool DecryptData(IncomingPacket packet) + private bool DecryptData(BasePacket packet) { - Array.Copy(packet.Raw, MacLen, packet.Header, 0, InHeaderLen); + Array.Copy(packet.Raw, MacLen, packet.Header, 0, packet.HeaderLength); var (key, nonce) = GetKeyNonce(true, packet.PacketId, packet.GenerationId, packet.PacketType); - int dataLen = packet.Raw.Length - (MacLen + InHeaderLen); + int dataLen = packet.Raw.Length - (MacLen + packet.HeaderLength); ICipherParameters ivAndKey = new AeadParameters(new KeyParameter(key), 8 * MacLen, nonce, packet.Header); try @@ -412,9 +416,12 @@ private bool DecryptData(IncomingPacket packet) eaxCipher.Init(false, ivAndKey); result = new byte[eaxCipher.GetOutputSize(dataLen + MacLen)]; - int len = eaxCipher.ProcessBytes(packet.Raw, MacLen + InHeaderLen, dataLen, result, 0); + int len = eaxCipher.ProcessBytes(packet.Raw, MacLen + packet.HeaderLength, dataLen, result, 0); len += eaxCipher.ProcessBytes(packet.Raw, 0, MacLen, result, len); len += eaxCipher.DoFinal(result, len); + + if (len != dataLen) + return false; } packet.Data = result; @@ -423,13 +430,13 @@ private bool DecryptData(IncomingPacket packet) return true; } - private static bool FakeDecrypt(IncomingPacket packet, byte[] mac) + private static bool FakeDecrypt(BasePacket packet, byte[] mac) { if (!CheckEqual(packet.Raw, 0, mac, 0, MacLen)) return false; - int dataLen = packet.Raw.Length - (MacLen + InHeaderLen); + int dataLen = packet.Raw.Length - (MacLen + packet.HeaderLength); packet.Data = new byte[dataLen]; - Array.Copy(packet.Raw, MacLen + InHeaderLen, packet.Data, 0, dataLen); + Array.Copy(packet.Raw, MacLen + packet.HeaderLength, packet.Data, 0, dataLen); return true; } @@ -501,8 +508,10 @@ private static void XorBinary(byte[] a, byte[] b, int len, byte[] outBuf) private static readonly SHA1Managed Sha1HashInternal = new SHA1Managed(); private static readonly Sha256Digest Sha256Hash = new Sha256Digest(); + private static readonly Sha512Digest Sha512Hash = new Sha512Digest(); private static byte[] Hash1It(byte[] data, int offset = 0, int len = 0) => HashItInternal(Sha1HashInternal, data, offset, len); private static byte[] Hash256It(byte[] data, int offset = 0, int len = 0) => HashIt(Sha256Hash, data, offset, len); + private static byte[] Hash512It(byte[] data, int offset = 0, int len = 0) => HashIt(Sha512Hash, data, offset, len); private static byte[] HashItInternal(HashAlgorithm hashAlgo, byte[] data, int offset = 0, int len = 0) { lock (hashAlgo) @@ -510,7 +519,7 @@ private static byte[] HashItInternal(HashAlgorithm hashAlgo, byte[] data, int of return hashAlgo.ComputeHash(data, offset, len == 0 ? data.Length - offset : len); } } - private static byte[] HashIt(GeneralDigest hashAlgo, byte[] data, int offset = 0, int len = 0) + private static byte[] HashIt(IDigest hashAlgo, byte[] data, int offset = 0, int len = 0) { byte[] result; lock (hashAlgo) @@ -532,6 +541,15 @@ public static string HashPassword(string password) return Convert.ToBase64String(hashed); } + public static byte[] Sign(BigInteger privateKey, byte[] data) + { + var signer = SignerUtilities.GetSigner(X9ObjectIdentifiers.ECDsaWithSha256); + var signKey = new ECPrivateKeyParameters(privateKey, KeyGenParams.DomainParameters); + signer.Init(true, signKey); + signer.BlockUpdate(data, 0, data.Length); + return signer.GenerateSignature(); + } + #endregion #region IDENTITY & SECURITY LEVEL diff --git a/TS3Client/Full/Ts3FullClient.cs b/TS3Client/Full/Ts3FullClient.cs index e152f6f0..c9152747 100644 --- a/TS3Client/Full/Ts3FullClient.cs +++ b/TS3Client/Full/Ts3FullClient.cs @@ -196,6 +196,7 @@ private void InvokeEvent(LazyNotification lazyNotification) case NotificationType.TokenUsed: break; // full client events case NotificationType.InitIvExpand: { var result = lazyNotification.WrapSingle(); if (result.Ok) ProcessInitIvExpand(result.Value); } break; + case NotificationType.InitIvExpand2: { var result = lazyNotification.WrapSingle(); if (result.Ok) ProcessInitIvExpand2(result.Value); } break; case NotificationType.InitServer: { var result = lazyNotification.WrapSingle(); if (result.Ok) ProcessInitServer(result.Value); } break; case NotificationType.ChannelList: break; case NotificationType.ChannelListFinished: ChannelSubscribeAll(); break; @@ -256,7 +257,7 @@ private void NetworkLoop(object ctxObject) break; } - IncomingPacket packet = packetHandler.FetchPacket(); + var packet = packetHandler.FetchPacket(); if (packet == null) break; @@ -313,6 +314,42 @@ private void ProcessInitIvExpand(InitIvExpand initIvExpand) "123,456", VersionSign); } + private void ProcessInitIvExpand2(InitIvExpand2 initIvExpand2) + { + //DisconnectInternal(context, Util.CustomError("Cannot connect to server 3.1 yet.")); + //return; + + var password = connectionDataFull.IsPasswordHashed + ? connectionDataFull.Password + : Ts3Crypt.HashPassword(connectionDataFull.Password); + + packetHandler.IncPacketCounter(PacketType.Command); + + // EK SIGN + var buffer = new byte[32]; + Util.Random.NextBytes(buffer); + var ek = Convert.ToBase64String(buffer); + var toSign = new byte[86]; + Array.Copy(buffer, 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 proof = Convert.ToBase64String(sign); + ClientEk(ek, proof); + // END EK SIGN + + //ts3Crypt.CryptoInit2("", initIvExpand2.Beta, initIvExpand2.Omega); // TODO ??? + //packetHandler.CryptoInitDone(); + + ClientInit( + connectionDataFull.Username, + true, true, + connectionDataFull.DefaultChannel, + Ts3Crypt.HashPassword(connectionDataFull.DefaultChannelPassword), + password, string.Empty, string.Empty, string.Empty, + "123,456", VersionSign); + } + private void ProcessInitServer(InitServer initServer) { packetHandler.ClientId = initServer.ClientId; @@ -452,11 +489,16 @@ public CmdR ChangeIsChannelCommander(bool isChannelCommander) => Send("clientupdate", new CommandParameter("client_is_channel_commander", isChannelCommander)); + public CmdR ClientEk(string ek, string proof) + => SendNoResponsed(new Ts3Command("clientek", new List { + new CommandParameter("ek", ek), + new CommandParameter("proof", 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", new List() { + new Ts3Command("clientinit", new List { new CommandParameter("client_nickname", nickname), new CommandParameter("client_version", versionSign.Name), new CommandParameter("client_platform", versionSign.PlattformName), diff --git a/TS3Client/Generated/Errors.cs b/TS3Client/Generated/Errors.cs index 469fb473..4e27e9af 100644 --- a/TS3Client/Generated/Errors.cs +++ b/TS3Client/Generated/Errors.cs @@ -12,6 +12,9 @@ + + + namespace TS3Client { // Source: http://forum.teamspeak.com/threads/102276-Server-query-error-id-list diff --git a/TS3Client/Generated/Messages.cs b/TS3Client/Generated/Messages.cs index b93f6624..b88bedf1 100644 --- a/TS3Client/Generated/Messages.cs +++ b/TS3Client/Generated/Messages.cs @@ -37,13 +37,13 @@ public sealed class ChannelChanged : INotification public ChannelIdT ChannelId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -81,37 +81,37 @@ public sealed class ChannelCreated : INotification public string PhoneticName { get; set; } public ChannelIdT ChannelParentId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "channel_order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_order": Order = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "channel_name": Name = Ts3String.Unescape(value); break; case "channel_topic": Topic = Ts3String.Unescape(value); break; - case "channel_flag_default": IsDefaultChannel = value != "0"; break; - case "channel_flag_password": HasPassword = value != "0"; break; - case "channel_flag_permanent": IsPermanent = value != "0"; break; - case "channel_flag_semi_permanent": IsSemiPermanent = value != "0"; break; - case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; - case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; - case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_codec_is_unencrypted": IsUnencrypted = value != "0"; break; - case "channel_delete_delay": DeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value != "0"; break; - case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value != "0"; break; - case "channel_flag_maxfamilyclients_inherited": IsMaxFamilyClientsInherited = value != "0"; break; + case "channel_flag_default": IsDefaultChannel = 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 (!Enum.TryParse(value.NewString(), out Codec val)) throw new FormatException(); Codec = val; } break; + case "channel_codec_quality": CodecQuality = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_needed_talk_power": NeededTalkPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "channel_maxclients": MaxClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_codec_is_unencrypted": IsUnencrypted = value.Length > 0 && value[0] != '0'; break; + case "channel_delete_delay": DeleteDelay = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); 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": IsMaxFamilyClientsInherited = value.Length > 0 && value[0] != '0'; break; case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "cpid": ChannelParentId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cpid": ChannelParentId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -128,14 +128,14 @@ public sealed class ChannelDeleted : INotification public string InvokerName { get; set; } public UidT InvokerUid { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; @@ -175,37 +175,37 @@ public sealed class ChannelEdited : INotification public string PhoneticName { get; set; } public MoveReason Reason { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "channel_order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_order": Order = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "channel_name": Name = Ts3String.Unescape(value); break; case "channel_topic": Topic = Ts3String.Unescape(value); break; - case "channel_flag_default": IsDefaultChannel = value != "0"; break; - case "channel_flag_password": HasPassword = value != "0"; break; - case "channel_flag_permanent": IsPermanent = value != "0"; break; - case "channel_flag_semi_permanent": IsSemiPermanent = value != "0"; break; - case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; - case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; - case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_codec_is_unencrypted": IsUnencrypted = value != "0"; break; - case "channel_delete_delay": DeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value != "0"; break; - case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value != "0"; break; - case "channel_flag_maxfamilyclients_inherited": IsMaxFamilyClientsInherited = value != "0"; break; + case "channel_flag_default": IsDefaultChannel = 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 (!Enum.TryParse(value.NewString(), out Codec val)) throw new FormatException(); Codec = val; } break; + case "channel_codec_quality": CodecQuality = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_needed_talk_power": NeededTalkPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "channel_maxclients": MaxClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_codec_is_unencrypted": IsUnencrypted = value.Length > 0 && value[0] != '0'; break; + case "channel_delete_delay": DeleteDelay = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); 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": IsMaxFamilyClientsInherited = value.Length > 0 && value[0] != '0'; break; case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "reasonid": { if (!Enum.TryParse(value.NewString(), out MoveReason val)) throw new FormatException(); Reason = val; } break; } @@ -242,36 +242,36 @@ public sealed class ChannelList : INotification public int IconId { get; set; } public bool IsPrivate { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "cpid": ChannelParentId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "cpid": ChannelParentId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "channel_name": Name = Ts3String.Unescape(value); break; case "channel_topic": Topic = Ts3String.Unescape(value); break; - case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; - case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_flag_permanent": IsPermanent = value != "0"; break; - case "channel_flag_semi_permanent": IsSemiPermanent = value != "0"; break; - case "channel_flag_default": IsDefaultChannel = value != "0"; break; - case "channel_flag_password": HasPassword = value != "0"; break; - case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_codec_is_unencrypted": IsUnencrypted = value != "0"; break; - case "channel_delete_delay": DeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "channel_flag_maxclients_unlimited": IsMaxClientsUnlimited = value != "0"; break; - case "channel_flag_maxfamilyclients_unlimited": IsMaxFamilyClientsUnlimited = value != "0"; break; - case "channel_flag_maxfamilyclients_inherited": IsMaxFamilyClientsInherited = value != "0"; break; - case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_forced_silence": ForcedSilence = value != "0"; break; + case "channel_codec": { if (!Enum.TryParse(value.NewString(), out Codec val)) throw new FormatException(); Codec = val; } break; + case "channel_codec_quality": CodecQuality = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_maxclients": MaxClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_order": Order = int.Parse(value.NewString(), CultureInfo.InvariantCulture); 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": IsDefaultChannel = value.Length > 0 && value[0] != '0'; break; + case "channel_flag_password": HasPassword = value.Length > 0 && value[0] != '0'; break; + case "channel_codec_latency_factor": CodecLatencyFactor = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_codec_is_unencrypted": IsUnencrypted = value.Length > 0 && value[0] != '0'; break; + case "channel_delete_delay": DeleteDelay = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); 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": IsMaxFamilyClientsInherited = value.Length > 0 && value[0] != '0'; break; + case "channel_needed_talk_power": NeededTalkPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_forced_silence": ForcedSilence = value.Length > 0 && value[0] != '0'; break; case "channel_name_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; - case "channel_flag_private": IsPrivate = value != "0"; break; + case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "channel_flag_private": IsPrivate = value.Length > 0 && value[0] != '0'; break; } @@ -284,7 +284,7 @@ public sealed class ChannelListFinished : INotification - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { } @@ -303,19 +303,19 @@ public sealed class ChannelMoved : INotification public MoveReason Reason { get; set; } public ChannelIdT ChannelParentId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "order": Order = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; - case "cpid": ChannelParentId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "reasonid": { if (!Enum.TryParse(value.NewString(), out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "cpid": ChannelParentId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -329,13 +329,13 @@ public sealed class ChannelPasswordChanged : INotification public ChannelIdT ChannelId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -350,14 +350,14 @@ public sealed class ChannelSubscribed : INotification public ChannelIdT ChannelId { get; set; } public TimeSpan EmptySince { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "es": EmptySince = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "es": EmptySince = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; } @@ -371,13 +371,13 @@ public sealed class ChannelUnsubscribed : INotification public ChannelIdT ChannelId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -396,18 +396,18 @@ public sealed class ClientChannelGroupChanged : INotification public ChannelIdT ChannelId { get; set; } public ClientIdT ClientId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; - case "cgid": ChannelGroupId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "cgi": ChannelGroupIndex = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cgid": ChannelGroupId = ChannelGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "cgi": ChannelGroupIndex = ChannelGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -422,13 +422,13 @@ public sealed class ClientChatComposing : INotification public ClientIdT ClientId { get; set; } public UidT ClientUid { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "cluid": ClientUid = Ts3String.Unescape(value); break; } @@ -479,47 +479,47 @@ public sealed class ClientEnterView : INotification public string CountryCode { get; set; } public string Badges { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; - case "ctid": TargetChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "reasonid": { if (!Enum.TryParse(value.NewString(), out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "ctid": TargetChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_nickname": NickName = Ts3String.Unescape(value); break; - case "client_type": { if (!Enum.TryParse(value, out ClientType val)) throw new FormatException(); ClientType = val; } break; - case "cfid": SourceChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_type": { if (!Enum.TryParse(value.NewString(), out ClientType val)) throw new FormatException(); ClientType = val; } break; + case "cfid": SourceChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; case "client_flag_avatar": AvatarFlag = Ts3String.Unescape(value); break; case "client_description": Description = Ts3String.Unescape(value); break; - case "client_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; - case "client_input_muted": IsInputMuted = value != "0"; break; - case "client_output_muted": IsOutputMuted = value != "0"; break; - case "client_outputonly_muted": IsOutputOnlyMuted = value != "0"; break; - case "client_input_hardware": IsInputHardware = value != "0"; break; - case "client_output_hardware": IsClientOutputHardware = value != "0"; break; + case "client_icon_id": IconId = unchecked((int)ulong.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "client_input_muted": IsInputMuted = value.Length > 0 && value[0] != '0'; break; + case "client_output_muted": IsOutputMuted = value.Length > 0 && value[0] != '0'; break; + case "client_outputonly_muted": IsOutputOnlyMuted = value.Length > 0 && value[0] != '0'; break; + case "client_input_hardware": IsInputHardware = value.Length > 0 && value[0] != '0'; break; + case "client_output_hardware": IsClientOutputHardware = value.Length > 0 && value[0] != '0'; break; case "client_meta_data": Metadata = Ts3String.Unescape(value); break; - case "client_is_recording": IsRecording = value != "0"; break; - case "client_channel_group_id": ChannelGroupId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "client_channel_group_inherited_channel_id": InheritedChannelGroupFromChannelId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "client_servergroups": { var t = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); ServerGroups = new ServerGroupIdT[t.Length]; for(int i = 0; i < t.Length; i++) ServerGroups[i] = ServerGroupIdT.Parse(t[i], CultureInfo.InvariantCulture); } break; - case "client_away": IsAway = value != "0"; break; + case "client_is_recording": IsRecording = value.Length > 0 && value[0] != '0'; break; + case "client_channel_group_id": ChannelGroupId = ChannelGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_channel_group_inherited_channel_id": InheritedChannelGroupFromChannelId = ChannelGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_servergroups": { if(value.Length == 0) ServerGroups = new ServerGroupIdT[0]; else { var ss = new SpanSplitter(); ss.First(value, ','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupIdT[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { ServerGroups[i] = ServerGroupIdT.Parse(ss.Trim(value).NewString(), CultureInfo.InvariantCulture); 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": TalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "client_talk_request": RequestedTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_talk_power": TalkPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_talk_request": RequestedTalkPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_talk_request_msg": TalkPowerRequestMessage = Ts3String.Unescape(value); break; - case "client_is_talker": IsTalker = value != "0"; break; - case "client_is_priority_speaker": IsPrioritySpeaker = value != "0"; break; - case "client_unread_messages": UnreadMessages = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_is_talker": IsTalker = value.Length > 0 && value[0] != '0'; break; + case "client_is_priority_speaker": IsPrioritySpeaker = value.Length > 0 && value[0] != '0'; break; + case "client_unread_messages": UnreadMessages = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_nickname_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "client_is_channel_commander": IsChannelCommander = value != "0"; break; + case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); 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; @@ -543,21 +543,21 @@ public sealed class ClientLeftView : INotification public ClientIdT ClientId { get; set; } public ChannelIdT SourceChannelId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { case "reasonmsg": ReasonMessage = Ts3String.Unescape(value); break; - case "bantime": BanTime = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; - case "ctid": TargetChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "bantime": BanTime = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "reasonid": { if (!Enum.TryParse(value.NewString(), out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "ctid": TargetChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "cfid": SourceChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "cfid": SourceChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -576,16 +576,16 @@ public sealed class ClientMoved : INotification public string InvokerName { get; set; } public UidT InvokerUid { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; - case "ctid": TargetChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "reasonid": { if (!Enum.TryParse(value.NewString(), out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "ctid": TargetChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; @@ -602,14 +602,14 @@ public sealed class ClientNeededPermissions : INotification public PermissionId PermissionId { get; set; } public int PermissionValue { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "permid": PermissionId = (PermissionId)int.Parse(value, CultureInfo.InvariantCulture); break; - case "permvalue": PermissionValue = int.Parse(value, CultureInfo.InvariantCulture); break; + case "permid": PermissionId = (PermissionId)int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "permvalue": PermissionValue = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -629,18 +629,18 @@ public sealed class ClientServerGroupAdded : INotification public ClientIdT ClientId { get; set; } public UidT ClientUid { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { case "name": Name = Ts3String.Unescape(value); break; - case "sgid": ServerGroupId = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "sgid": ServerGroupId = ServerGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "cluid": ClientUid = Ts3String.Unescape(value); break; } @@ -659,15 +659,15 @@ public sealed class CommandError : INotification public string ReturnCode { get; set; } public string ExtraMessage { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "id": Id = (Ts3ErrorCode)ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "id": Id = (Ts3ErrorCode)ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "msg": Message = Ts3String.Unescape(value); break; - case "failed_permid": MissingPermissionId = (PermissionId)int.Parse(value, CultureInfo.InvariantCulture); break; + case "failed_permid": MissingPermissionId = (PermissionId)int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "return_code": ReturnCode = Ts3String.Unescape(value); break; case "extra_msg": ExtraMessage = Ts3String.Unescape(value); break; @@ -723,53 +723,53 @@ public sealed class ConnectionInfo : INotification public long ConnectionFiletransferReceived { get; set; } public TimeSpan ConnectionIdleTime { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_ping": ConnectionPing = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_ping_deviation": ConnectionPingDeviation = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_connected_time": ConnectionTime = TimeSpan.FromMilliseconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_ping": ConnectionPing = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_ping_deviation": ConnectionPingDeviation = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_connected_time": ConnectionTime = TimeSpan.FromMilliseconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; case "connection_client_ip": Ip = Ts3String.Unescape(value); break; - case "connection_client_port": ConnectionClientPort = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_packets_sent_speech": ConnectionPacketsSentSpeech = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_packets_sent_keepalive": ConnectionPacketsSentKeepalive = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_packets_sent_control": ConnectionPacketsSentControl = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bytes_sent_speech": ConnectionBytesSentSpeech = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bytes_sent_keepalive": ConnectionBytesSentKeepalive = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bytes_sent_control": ConnectionBytesSentControl = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_packets_received_speech": ConnectionPacketsReceivedSpeech = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_packets_received_keepalive": ConnectionPacketsReceivedKeepalive = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_packets_received_control": ConnectionPacketsReceivedControl = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bytes_received_speech": ConnectionBytesReceivedSpeech = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bytes_received_keepalive": ConnectionBytesReceivedKeepalive = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bytes_received_control": ConnectionBytesReceivedControl = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_server2client_packetloss_speech": ConnectionServerToClientPacketlossSpeech = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_server2client_packetloss_keepalive": ConnectionServerToClientPacketlossKeepalive = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_server2client_packetloss_control": ConnectionServerToClientPacketlossControl = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_server2client_packetloss_total": ConnectionServerToClientPacketlossTotal = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_client2server_packetloss_speech": ConnectionClientToServerPacketlossSpeech = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_client2server_packetloss_keepalive": ConnectionClientToServerPacketlossKeepalive = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_client2server_packetloss_control": ConnectionClientToServerPacketlossControl = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_client2server_packetloss_total": ConnectionClientToServerPacketlossTotal = float.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_sent_last_second_speech": ConnectionBandwidthSentLastSecondSpeech = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_sent_last_second_keepalive": ConnectionBandwidthSentLastSecondKeepalive = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_sent_last_second_control": ConnectionBandwidthSentLastSecondControl = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_sent_last_minute_speech": ConnectionBandwidthSentLastMinuteSpeech = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_sent_last_minute_keepalive": ConnectionBandwidthSentLastMinuteKeepalive = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_sent_last_minute_control": ConnectionBandwidthSentLastMinuteControl = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_received_last_second_speech": ConnectionBandwidthReceivedLastSecondSpeech = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_received_last_second_keepalive": ConnectionBandwidthReceivedLastSecondKeepalive = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_received_last_second_control": ConnectionBandwidthReceivedLastSecondControl = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_received_last_minute_speech": ConnectionBandwidthReceivedLastMinuteSpeech = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_received_last_minute_keepalive": ConnectionBandwidthReceivedLastMinuteKeepalive = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_received_last_minute_control": ConnectionBandwidthReceivedLastMinuteControl = uint.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_filetransfer_bandwidth_sent": ConnectionFiletransferSent = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_filetransfer_bandwidth_received": ConnectionFiletransferReceived = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_idle_time": ConnectionIdleTime = TimeSpan.FromMilliseconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "connection_client_port": ConnectionClientPort = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_packets_sent_speech": ConnectionPacketsSentSpeech = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_packets_sent_keepalive": ConnectionPacketsSentKeepalive = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_packets_sent_control": ConnectionPacketsSentControl = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bytes_sent_speech": ConnectionBytesSentSpeech = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bytes_sent_keepalive": ConnectionBytesSentKeepalive = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bytes_sent_control": ConnectionBytesSentControl = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_packets_received_speech": ConnectionPacketsReceivedSpeech = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_packets_received_keepalive": ConnectionPacketsReceivedKeepalive = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_packets_received_control": ConnectionPacketsReceivedControl = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bytes_received_speech": ConnectionBytesReceivedSpeech = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bytes_received_keepalive": ConnectionBytesReceivedKeepalive = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bytes_received_control": ConnectionBytesReceivedControl = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_server2client_packetloss_speech": ConnectionServerToClientPacketlossSpeech = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_server2client_packetloss_keepalive": ConnectionServerToClientPacketlossKeepalive = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_server2client_packetloss_control": ConnectionServerToClientPacketlossControl = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_server2client_packetloss_total": ConnectionServerToClientPacketlossTotal = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_client2server_packetloss_speech": ConnectionClientToServerPacketlossSpeech = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_client2server_packetloss_keepalive": ConnectionClientToServerPacketlossKeepalive = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_client2server_packetloss_control": ConnectionClientToServerPacketlossControl = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_client2server_packetloss_total": ConnectionClientToServerPacketlossTotal = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_second_speech": ConnectionBandwidthSentLastSecondSpeech = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_second_keepalive": ConnectionBandwidthSentLastSecondKeepalive = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_second_control": ConnectionBandwidthSentLastSecondControl = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_minute_speech": ConnectionBandwidthSentLastMinuteSpeech = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_minute_keepalive": ConnectionBandwidthSentLastMinuteKeepalive = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_minute_control": ConnectionBandwidthSentLastMinuteControl = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_second_speech": ConnectionBandwidthReceivedLastSecondSpeech = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_second_keepalive": ConnectionBandwidthReceivedLastSecondKeepalive = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_second_control": ConnectionBandwidthReceivedLastSecondControl = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_minute_speech": ConnectionBandwidthReceivedLastMinuteSpeech = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_minute_keepalive": ConnectionBandwidthReceivedLastMinuteKeepalive = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_minute_control": ConnectionBandwidthReceivedLastMinuteControl = uint.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_filetransfer_bandwidth_sent": ConnectionFiletransferSent = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_filetransfer_bandwidth_received": ConnectionFiletransferReceived = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_idle_time": ConnectionIdleTime = TimeSpan.FromMilliseconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; } @@ -782,7 +782,7 @@ public sealed class ConnectionInfoRequest : INotification - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { } @@ -796,13 +796,13 @@ public sealed class FileListFinished : INotification public ChannelIdT ChannelId { get; set; } public string Path { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "path": Path = Ts3String.Unescape(value); break; } @@ -820,16 +820,16 @@ public sealed class FileTransferStatus : INotification public string Message { get; set; } public long Size { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "clientftfid": ClientFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "status": Status = (Ts3ErrorCode)ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "clientftfid": ClientFileTransferId = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "status": Status = (Ts3ErrorCode)ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "msg": Message = Ts3String.Unescape(value); break; - case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; + case "size": Size = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -845,7 +845,7 @@ public sealed class InitIvExpand : INotification public string Beta { get; set; } public string Omega { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) @@ -860,6 +860,36 @@ public void SetField(string name, string value) } } + public sealed class InitIvExpand2 : INotification + { + public NotificationType NotifyType { get; } = NotificationType.InitIvExpand2; + + + public string License { get; set; } + public string Beta { get; set; } + public string Omega { get; set; } + public bool Ot { get; set; } + public string Proof { get; set; } + public string Tvd { get; set; } + + public void SetField(string name, ReadOnlySpan value) + { + + switch(name) + { + + case "l": License = Ts3String.Unescape(value); break; + case "beta": Beta = Ts3String.Unescape(value); break; + case "omega": Omega = Ts3String.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; + + } + + } + } + public sealed class InitServer : INotification { public NotificationType NotifyType { get; } = NotificationType.InitServer; @@ -897,7 +927,7 @@ public sealed class InitServer : INotification public HostBannerMode HostbannerMode { get; set; } public TimeSpan DefaultTempChannelDeleteDelay { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) @@ -906,34 +936,34 @@ public void SetField(string name, string value) 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": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "virtualserver_created": ServerCreated = long.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_maxclients": MaxClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "virtualserver_created": ServerCreated = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "virtualserver_hostmessage": Hostmessage = Ts3String.Unescape(value); break; - case "virtualserver_hostmessage_mode": { if (!Enum.TryParse(value, out HostMessageMode val)) throw new FormatException(); HostmessageMode = val; } break; - case "virtualserver_id": VirtualServerId = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_hostmessage_mode": { if (!Enum.TryParse(value.NewString(), out HostMessageMode val)) throw new FormatException(); HostmessageMode = val; } break; + case "virtualserver_id": VirtualServerId = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "virtualserver_ip": ServerIp = Ts3String.Unescape(value); break; - case "virtualserver_ask_for_privilegekey": AskForPrivilege = value != "0"; break; + case "virtualserver_ask_for_privilegekey": AskForPrivilege = value.Length > 0 && value[0] != '0'; break; case "acn": ClientName = Ts3String.Unescape(value); break; - case "aclid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "pv": ProtocolVersion = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "lt": LicenseType = (LicenseType)ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "client_talk_power": TalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "aclid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "pv": ProtocolVersion = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "lt": LicenseType = (LicenseType)ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_talk_power": TalkPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "virtualserver_name": ServerName = Ts3String.Unescape(value); break; - case "virtualserver_codec_encryption_mode": { if (!Enum.TryParse(value, out CodecEncryptionMode val)) throw new FormatException(); CodecEncryptionMode = val; } break; - case "virtualserver_default_server_group": DefaultServerGroup = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "virtualserver_default_channel_group": DefaultChannelGroup = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_codec_encryption_mode": { if (!Enum.TryParse(value.NewString(), out CodecEncryptionMode val)) throw new FormatException(); CodecEncryptionMode = val; } break; + case "virtualserver_default_server_group": DefaultServerGroup = ServerGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "virtualserver_default_channel_group": DefaultChannelGroup = ChannelGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "virtualserver_hostbanner_url": HostbannerUrl = Ts3String.Unescape(value); break; case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = Ts3String.Unescape(value); break; - case "virtualserver_hostbanner_gfx_interval": HostbannerGfxInterval = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "virtualserver_priority_speaker_dimm_modificator": PrioritySpeakerDimmModificator = float.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_hostbanner_gfx_interval": HostbannerGfxInterval = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "virtualserver_priority_speaker_dimm_modificator": PrioritySpeakerDimmModificator = float.Parse(value.NewString(), CultureInfo.InvariantCulture); 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_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; - case "virtualserver_hostbanner_mode": { if (!Enum.TryParse(value, out HostBannerMode val)) throw new FormatException(); HostbannerMode = val; } break; - case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "virtualserver_icon_id": IconId = unchecked((int)ulong.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "virtualserver_hostbanner_mode": { if (!Enum.TryParse(value.NewString(), out HostBannerMode val)) throw new FormatException(); HostbannerMode = val; } break; + case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; } @@ -965,31 +995,31 @@ public sealed class ServerEdited : INotification public HostBannerMode HostbannerMode { get; set; } public TimeSpan DefaultTempChannelDeleteDelay { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; - case "reasonid": { if (!Enum.TryParse(value, out MoveReason val)) throw new FormatException(); Reason = val; } break; + case "reasonid": { if (!Enum.TryParse(value.NewString(), out MoveReason val)) throw new FormatException(); Reason = val; } break; case "virtualserver_name": ServerName = Ts3String.Unescape(value); break; - case "virtualserver_codec_encryption_mode": { if (!Enum.TryParse(value, out CodecEncryptionMode val)) throw new FormatException(); CodecEncryptionMode = val; } break; - case "virtualserver_default_server_group": DefaultServerGroup = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "virtualserver_default_channel_group": DefaultChannelGroup = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_codec_encryption_mode": { if (!Enum.TryParse(value.NewString(), out CodecEncryptionMode val)) throw new FormatException(); CodecEncryptionMode = val; } break; + case "virtualserver_default_server_group": DefaultServerGroup = ServerGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "virtualserver_default_channel_group": DefaultChannelGroup = ChannelGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "virtualserver_hostbanner_url": HostbannerUrl = Ts3String.Unescape(value); break; case "virtualserver_hostbanner_gfx_url": HostbannerGfxUrl = Ts3String.Unescape(value); break; - case "virtualserver_hostbanner_gfx_interval": HostbannerGfxInterval = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "virtualserver_priority_speaker_dimm_modificator": PrioritySpeakerDimmModificator = float.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_hostbanner_gfx_interval": HostbannerGfxInterval = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "virtualserver_priority_speaker_dimm_modificator": PrioritySpeakerDimmModificator = float.Parse(value.NewString(), CultureInfo.InvariantCulture); 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_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; - case "virtualserver_hostbanner_mode": { if (!Enum.TryParse(value, out HostBannerMode val)) throw new FormatException(); HostbannerMode = val; } break; - case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "virtualserver_icon_id": IconId = unchecked((int)ulong.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "virtualserver_hostbanner_mode": { if (!Enum.TryParse(value.NewString(), out HostBannerMode val)) throw new FormatException(); HostbannerMode = val; } break; + case "virtualserver_channel_temp_delete_delay_default": DefaultTempChannelDeleteDelay = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; } @@ -1012,22 +1042,22 @@ public sealed class ServerGroupList : INotification public int NeededMemberAddPower { get; set; } public int NeededMemberRemovePower { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "sgid": ServerGroupId = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "sgid": ServerGroupId = ServerGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "name": Name = Ts3String.Unescape(value); break; - case "type": { if (!Enum.TryParse(value, out PermissionGroupDatabaseType val)) throw new FormatException(); GroupType = val; } break; - case "iconid": IconId = int.Parse(value, CultureInfo.InvariantCulture); break; - case "savedb": GroupIsPermanent = value != "0"; break; - case "sortid": SortId = int.Parse(value, CultureInfo.InvariantCulture); break; - case "namemode": { if (!Enum.TryParse(value, out GroupNamingMode val)) throw new FormatException(); NamingMode = val; } break; - case "n_modifyp": NeededModifyPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "n_member_addp": NeededMemberAddPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "n_member_remove_p": NeededMemberRemovePower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "type": { if (!Enum.TryParse(value.NewString(), out PermissionGroupDatabaseType val)) throw new FormatException(); GroupType = val; } break; + case "iconid": IconId = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "savedb": GroupIsPermanent = value.Length > 0 && value[0] != '0'; break; + case "sortid": SortId = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "namemode": { if (!Enum.TryParse(value.NewString(), out GroupNamingMode val)) throw new FormatException(); NamingMode = val; } break; + case "n_modifyp": NeededModifyPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "n_member_addp": NeededMemberAddPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "n_member_remove_p": NeededMemberRemovePower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -1046,16 +1076,16 @@ public sealed class TextMessage : INotification public string InvokerName { get; set; } public UidT InvokerUid { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "targetmode": { if (!Enum.TryParse(value, out TextMessageTargetMode val)) throw new FormatException(); Target = val; } break; + case "targetmode": { if (!Enum.TryParse(value.NewString(), out TextMessageTargetMode val)) throw new FormatException(); Target = val; } break; case "msg": Message = Ts3String.Unescape(value); break; - case "target": TargetClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "invokerid": InvokerId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "target": TargetClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "invokerid": InvokerId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "invokername": InvokerName = Ts3String.Unescape(value); break; case "invokeruid": InvokerUid = Ts3String.Unescape(value); break; @@ -1077,7 +1107,7 @@ public sealed class TokenUsed : INotification public ClientDbIdT ClientDbId { get; set; } public UidT ClientUid { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) @@ -1087,8 +1117,8 @@ public void SetField(string name, string value) case "tokencustomset": TokenCustomSet = Ts3String.Unescape(value); break; case "token1": Token1 = Ts3String.Unescape(value); break; case "token2": Token2 = Ts3String.Unescape(value); break; - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "cldbid": ClientDbId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "cldbid": ClientDbId = ClientDbIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "cluid": ClientUid = Ts3String.Unescape(value); break; } @@ -1121,31 +1151,31 @@ public sealed class ChannelData : IResponse public int MaxClients { get; set; } public int MaxFamilyClients { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "id": Id = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "pid": ParentChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "seconds_empty": DurationEmpty = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "total_clients_family": TotalFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "total_clients": TotalClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_needed_subscribe_power": NeededSubscribePower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_order": Order = int.Parse(value, CultureInfo.InvariantCulture); break; + case "id": Id = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "pid": ParentChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "seconds_empty": DurationEmpty = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "total_clients_family": TotalFamilyClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "total_clients": TotalClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_needed_subscribe_power": NeededSubscribePower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_order": Order = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "channel_name": Name = Ts3String.Unescape(value); break; case "channel_topic": Topic = Ts3String.Unescape(value); break; - case "channel_flag_default": IsDefaultChannel = value != "0"; break; - case "channel_flag_password": HasPassword = value != "0"; break; - case "channel_flag_permanent": IsPermanent = value != "0"; break; - case "channel_flag_semi_permanent": IsSemiPermanent = value != "0"; break; - case "channel_codec": { if (!Enum.TryParse(value, out Codec val)) throw new FormatException(); Codec = val; } break; - case "channel_codec_quality": CodecQuality = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_needed_talk_power": NeededTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; - case "channel_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value, CultureInfo.InvariantCulture); break; + case "channel_flag_default": IsDefaultChannel = 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 (!Enum.TryParse(value.NewString(), out Codec val)) throw new FormatException(); Codec = val; } break; + case "channel_codec_quality": CodecQuality = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_needed_talk_power": NeededTalkPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_icon_id": IconId = unchecked((int)ulong.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "channel_maxclients": MaxClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "channel_maxfamilyclients": MaxFamilyClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "return_code": ReturnCode = Ts3String.Unescape(value); break; } @@ -1164,18 +1194,18 @@ public sealed class ClientData : IResponse public string NickName { get; set; } public ClientType ClientType { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_nickname": NickName = Ts3String.Unescape(value); break; - case "client_type": { if (!Enum.TryParse(value, out ClientType val)) throw new FormatException(); ClientType = val; } break; + case "client_type": { if (!Enum.TryParse(value.NewString(), out ClientType val)) throw new FormatException(); ClientType = val; } break; case "return_code": ReturnCode = Ts3String.Unescape(value); break; } @@ -1206,29 +1236,29 @@ public sealed class ClientDbData : IResponse public long TotalDownloadQuota { get; set; } public string Base64HashClientUid { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { case "client_lastip": LastIp = Ts3String.Unescape(value); break; - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_nickname": NickName = Ts3String.Unescape(value); break; - case "client_type": { if (!Enum.TryParse(value, out ClientType val)) throw new FormatException(); ClientType = val; } break; + case "client_type": { if (!Enum.TryParse(value.NewString(), out ClientType val)) throw new FormatException(); ClientType = val; } break; case "client_flag_avatar": AvatarFlag = Ts3String.Unescape(value); break; case "client_description": Description = Ts3String.Unescape(value); break; - case "client_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; - case "client_created": CreationDate = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "client_lastconnected": LastConnected = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "client_totalconnections": TotalConnections = int.Parse(value, CultureInfo.InvariantCulture); break; - case "client_month_bytes_uploaded": MonthlyUploadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; - case "client_month_bytes_downloaded": MonthlyDownloadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; - case "client_total_bytes_uploaded": TotalUploadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; - case "client_total_bytes_downloaded": TotalDownloadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_icon_id": IconId = unchecked((int)ulong.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "client_created": CreationDate = Util.UnixTimeStart.AddSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "client_lastconnected": LastConnected = Util.UnixTimeStart.AddSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "client_totalconnections": TotalConnections = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_month_bytes_uploaded": MonthlyUploadQuota = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_month_bytes_downloaded": MonthlyDownloadQuota = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_total_bytes_uploaded": TotalUploadQuota = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_total_bytes_downloaded": TotalDownloadQuota = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_base64HashClientUID": Base64HashClientUid = Ts3String.Unescape(value); break; case "return_code": ReturnCode = Ts3String.Unescape(value); break; } @@ -1301,13 +1331,13 @@ public sealed class ClientInfo : IResponse public string Description { get; set; } public int IconId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "client_idle_time": ClientIdleTime = TimeSpan.FromMilliseconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_idle_time": ClientIdleTime = TimeSpan.FromMilliseconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; case "client_version": ClientVersion = Ts3String.Unescape(value); break; case "client_version_sign": ClientVersionSign = Ts3String.Unescape(value); break; case "client_platform": ClientPlattform = Ts3String.Unescape(value); break; @@ -1315,57 +1345,57 @@ public void SetField(string name, string value) 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": ConnectionFiletransferSent = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_filetransfer_bandwidth_received": ConnectionFiletransferReceived = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_packets_sent_total": ConnectionPacketsSent = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_packets_received_total": ConnectionPacketsReceived = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bytes_sent_total": ConnectionBytesSent = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bytes_received_total": ConnectionBytesReceived = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_sent_last_second_total": ConnectionBandwidtSentLastSecond = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_received_last_second_total": ConnectionBandwidtReceivedLastSecond = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_sent_last_minute_total": ConnectionBandwidtSentLastMinute = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_bandwidth_received_last_minute_total": ConnectionBandwidtReceivedLastMinute = long.Parse(value, CultureInfo.InvariantCulture); break; - case "connection_connected_time": ConnectionTime = TimeSpan.FromMilliseconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "connection_filetransfer_bandwidth_sent": ConnectionFiletransferSent = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_filetransfer_bandwidth_received": ConnectionFiletransferReceived = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_packets_sent_total": ConnectionPacketsSent = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_packets_received_total": ConnectionPacketsReceived = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bytes_sent_total": ConnectionBytesSent = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bytes_received_total": ConnectionBytesReceived = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_second_total": ConnectionBandwidtSentLastSecond = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_second_total": ConnectionBandwidtReceivedLastSecond = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_sent_last_minute_total": ConnectionBandwidtSentLastMinute = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_bandwidth_received_last_minute_total": ConnectionBandwidtReceivedLastMinute = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "connection_connected_time": ConnectionTime = TimeSpan.FromMilliseconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; case "connection_client_ip": Ip = Ts3String.Unescape(value); break; - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_unique_identifier": Uid = Ts3String.Unescape(value); break; - case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_nickname": NickName = Ts3String.Unescape(value); break; - case "client_type": { if (!Enum.TryParse(value, out ClientType val)) throw new FormatException(); ClientType = val; } break; - case "client_input_muted": IsInputMuted = value != "0"; break; - case "client_output_muted": IsOutputMuted = value != "0"; break; - case "client_outputonly_muted": IsOutputOnlyMuted = value != "0"; break; - case "client_input_hardware": IsInputHardware = value != "0"; break; - case "client_output_hardware": IsClientOutputHardware = value != "0"; break; + case "client_type": { if (!Enum.TryParse(value.NewString(), out ClientType val)) throw new FormatException(); ClientType = val; } break; + case "client_input_muted": IsInputMuted = value.Length > 0 && value[0] != '0'; break; + case "client_output_muted": IsOutputMuted = value.Length > 0 && value[0] != '0'; break; + case "client_outputonly_muted": IsOutputOnlyMuted = value.Length > 0 && value[0] != '0'; break; + case "client_input_hardware": IsInputHardware = value.Length > 0 && value[0] != '0'; break; + case "client_output_hardware": IsClientOutputHardware = value.Length > 0 && value[0] != '0'; break; case "client_meta_data": Metadata = Ts3String.Unescape(value); break; - case "client_is_recording": IsRecording = value != "0"; break; - case "client_channel_group_id": ChannelGroupId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "client_channel_group_inherited_channel_id": InheritedChannelGroupFromChannelId = ChannelGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "client_servergroups": { var t = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); ServerGroups = new ServerGroupIdT[t.Length]; for(int i = 0; i < t.Length; i++) ServerGroups[i] = ServerGroupIdT.Parse(t[i], CultureInfo.InvariantCulture); } break; - case "client_away": IsAway = value != "0"; break; + case "client_is_recording": IsRecording = value.Length > 0 && value[0] != '0'; break; + case "client_channel_group_id": ChannelGroupId = ChannelGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_channel_group_inherited_channel_id": InheritedChannelGroupFromChannelId = ChannelGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_servergroups": { if(value.Length == 0) ServerGroups = new ServerGroupIdT[0]; else { var ss = new SpanSplitter(); ss.First(value, ','); int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++; ServerGroups = new ServerGroupIdT[cnt + 1]; for(int i = 0; i < cnt + 1; i++) { ServerGroups[i] = ServerGroupIdT.Parse(ss.Trim(value).NewString(), CultureInfo.InvariantCulture); 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": TalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "client_talk_request": RequestedTalkPower = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_talk_power": TalkPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_talk_request": RequestedTalkPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_talk_request_msg": TalkPowerRequestMessage = Ts3String.Unescape(value); break; - case "client_is_talker": IsTalker = value != "0"; break; - case "client_is_priority_speaker": IsPrioritySpeaker = value != "0"; break; - case "client_unread_messages": UnreadMessages = int.Parse(value, CultureInfo.InvariantCulture); break; + case "client_is_talker": IsTalker = value.Length > 0 && value[0] != '0'; break; + case "client_is_priority_speaker": IsPrioritySpeaker = value.Length > 0 && value[0] != '0'; break; + case "client_unread_messages": UnreadMessages = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_nickname_phonetic": PhoneticName = Ts3String.Unescape(value); break; - case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value, CultureInfo.InvariantCulture); break; - case "client_is_channel_commander": IsChannelCommander = value != "0"; break; + case "client_needed_serverquery_view_power": NeededServerQueryViewPower = int.Parse(value.NewString(), CultureInfo.InvariantCulture); 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": CreationDate = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "client_lastconnected": LastConnected = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "client_totalconnections": TotalConnections = int.Parse(value, CultureInfo.InvariantCulture); break; - case "client_month_bytes_uploaded": MonthlyUploadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; - case "client_month_bytes_downloaded": MonthlyDownloadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; - case "client_total_bytes_uploaded": TotalUploadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; - case "client_total_bytes_downloaded": TotalDownloadQuota = long.Parse(value, CultureInfo.InvariantCulture); break; + case "client_created": CreationDate = Util.UnixTimeStart.AddSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "client_lastconnected": LastConnected = Util.UnixTimeStart.AddSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "client_totalconnections": TotalConnections = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_month_bytes_uploaded": MonthlyUploadQuota = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_month_bytes_downloaded": MonthlyDownloadQuota = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_total_bytes_uploaded": TotalUploadQuota = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_total_bytes_downloaded": TotalDownloadQuota = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_base64HashClientUID": Base64HashClientUid = Ts3String.Unescape(value); break; case "client_flag_avatar": AvatarFlag = Ts3String.Unescape(value); break; case "client_description": Description = Ts3String.Unescape(value); break; - case "client_icon_id": IconId = unchecked((int)ulong.Parse(value, CultureInfo.InvariantCulture)); break; + case "client_icon_id": IconId = unchecked((int)ulong.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; case "return_code": ReturnCode = Ts3String.Unescape(value); break; } @@ -1389,22 +1419,22 @@ public sealed class ServerData : IResponse public ushort VirtualServerPort { get; set; } public string VirtualServerStatus { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "virtualserver_clientsonline": ClientsOnline = int.Parse(value, CultureInfo.InvariantCulture); break; - case "virtualserver_queryclientsonline": QueriesOnline = int.Parse(value, CultureInfo.InvariantCulture); break; - case "virtualserver_maxclients": MaxClients = int.Parse(value, CultureInfo.InvariantCulture); break; - case "virtualserver_uptime": Uptime = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "virtualserver_autostart": Autostart = value != "0"; break; + case "virtualserver_clientsonline": ClientsOnline = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "virtualserver_queryclientsonline": QueriesOnline = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "virtualserver_maxclients": MaxClients = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "virtualserver_uptime": Uptime = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "virtualserver_autostart": Autostart = value.Length > 0 && value[0] != '0'; break; case "virtualserver_machine_id": MachineId = Ts3String.Unescape(value); break; case "virtualserver_name": ServerName = Ts3String.Unescape(value); break; - case "virtualserver_id": VirtualServerId = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_id": VirtualServerId = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "virtualserver_unique_identifier": VirtualServerUid = Ts3String.Unescape(value); break; - case "virtualserver_port": VirtualServerPort = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_port": VirtualServerPort = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "virtualserver_status": VirtualServerStatus = Ts3String.Unescape(value); break; case "return_code": ReturnCode = Ts3String.Unescape(value); break; } @@ -1419,13 +1449,13 @@ public sealed class ServerGroupAddResponse : IResponse public ServerGroupIdT ServerGroupId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "sgid": ServerGroupId = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "sgid": ServerGroupId = ServerGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "return_code": ReturnCode = Ts3String.Unescape(value); break; } @@ -1449,21 +1479,21 @@ public sealed class WhoAmI : IResponse public string VirtualServerStatus { get; set; } public UidT Uid { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "client_id": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "client_channel_id": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_id": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "client_channel_id": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_nickname": NickName = Ts3String.Unescape(value); break; - case "client_database_id": DatabaseId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "client_database_id": DatabaseId = ClientDbIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "client_login_name": LoginName = Ts3String.Unescape(value); break; - case "client_origin_server_id": OriginServerId = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "virtualserver_id": VirtualServerId = ulong.Parse(value, CultureInfo.InvariantCulture); break; + case "client_origin_server_id": OriginServerId = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "virtualserver_id": VirtualServerId = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "virtualserver_unique_identifier": VirtualServerUid = Ts3String.Unescape(value); break; - case "virtualserver_port": VirtualServerPort = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "virtualserver_port": VirtualServerPort = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); 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; @@ -1481,15 +1511,15 @@ public sealed class ClientServerGroup : INotification , IResponse public ServerGroupIdT ServerGroupId { get; set; } public ClientDbIdT ClientDbId { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { case "name": Name = Ts3String.Unescape(value); break; - case "sgid": ServerGroupId = ServerGroupIdT.Parse(value, CultureInfo.InvariantCulture); break; - case "cldbid": ClientDbId = ClientDbIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "sgid": ServerGroupId = ServerGroupIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "cldbid": ClientDbId = ClientDbIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; } @@ -1508,17 +1538,17 @@ public sealed class FileDownload : INotification , IResponse public long Size { get; set; } public string Message { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "clientftfid": ClientFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "serverftfid": ServerFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "clientftfid": ClientFileTransferId = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "serverftfid": ServerFileTransferId = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "ftkey": FileTransferKey = Ts3String.Unescape(value); break; - case "port": Port = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; + case "port": Port = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "size": Size = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "msg": Message = Ts3String.Unescape(value); break; } @@ -1537,17 +1567,17 @@ public sealed class FileInfoTs : INotification , IResponse public long Size { get; set; } public DateTime DateTime { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "path": Path = Ts3String.Unescape(value); break; case "name": Name = Ts3String.Unescape(value); break; - case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; - case "datetime": DateTime = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "size": Size = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "datetime": DateTime = Util.UnixTimeStart.AddSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; } @@ -1566,18 +1596,18 @@ public sealed class FileList : INotification , IResponse public DateTime DateTime { get; set; } public bool IsFile { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "cid": ChannelId = ChannelIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "cid": ChannelId = ChannelIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "path": Path = Ts3String.Unescape(value); break; case "name": Name = Ts3String.Unescape(value); break; - case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; - case "datetime": DateTime = Util.UnixTimeStart.AddSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; - case "type": IsFile = value != "0"; break; + case "size": Size = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "datetime": DateTime = Util.UnixTimeStart.AddSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; + case "type": IsFile = value.Length > 0 && value[0] != '0'; break; } @@ -1602,24 +1632,24 @@ public sealed class FileTransfer : INotification , IResponse public float AverageSpeed { get; set; } public TimeSpan Runtime { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "clid": ClientId = ClientIdT.Parse(value, CultureInfo.InvariantCulture); break; + case "clid": ClientId = ClientIdT.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "path": Path = Ts3String.Unescape(value); break; case "name": Name = Ts3String.Unescape(value); break; - case "size": Size = long.Parse(value, CultureInfo.InvariantCulture); break; - case "sizedone": SizeDone = long.Parse(value, CultureInfo.InvariantCulture); break; - case "clientftfid": ClientFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "serverftfid": ServerFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "sender": Sender = ulong.Parse(value, CultureInfo.InvariantCulture); break; - case "status": Status = int.Parse(value, CultureInfo.InvariantCulture); break; - case "current_speed": CurrentSpeed = float.Parse(value, CultureInfo.InvariantCulture); break; - case "average_speed": AverageSpeed = float.Parse(value, CultureInfo.InvariantCulture); break; - case "runtime": Runtime = TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture)); break; + case "size": Size = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "sizedone": SizeDone = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "clientftfid": ClientFileTransferId = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "serverftfid": ServerFileTransferId = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "sender": Sender = ulong.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "status": Status = int.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "current_speed": CurrentSpeed = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "average_speed": AverageSpeed = float.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "runtime": Runtime = TimeSpan.FromSeconds(double.Parse(value.NewString(), CultureInfo.InvariantCulture)); break; } @@ -1638,17 +1668,17 @@ public sealed class FileUpload : INotification , IResponse public long SeekPosistion { get; set; } public string Message { get; set; } - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { switch(name) { - case "clientftfid": ClientFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "serverftfid": ServerFileTransferId = ushort.Parse(value, CultureInfo.InvariantCulture); break; + case "clientftfid": ClientFileTransferId = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "serverftfid": ServerFileTransferId = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "ftkey": FileTransferKey = Ts3String.Unescape(value); break; - case "port": Port = ushort.Parse(value, CultureInfo.InvariantCulture); break; - case "seekpos": SeekPosistion = long.Parse(value, CultureInfo.InvariantCulture); break; + case "port": Port = ushort.Parse(value.NewString(), CultureInfo.InvariantCulture); break; + case "seekpos": SeekPosistion = long.Parse(value.NewString(), CultureInfo.InvariantCulture); break; case "msg": Message = Ts3String.Unescape(value); break; } @@ -1656,6 +1686,47 @@ public void SetField(string name, string value) } } + public enum NotificationType + { + Unknown, + + Error, + ChannelChanged, + ChannelCreated, + ChannelDeleted, + ChannelEdited, + ChannelMoved, + ChannelPasswordChanged, + ClientEnterView, + ClientLeftView, + ClientMoved, + ServerEdited, + TextMessage, + TokenUsed, + ChannelList, + ChannelListFinished, + InitIvExpand, + InitIvExpand2, + InitServer, + ChannelSubscribed, + ChannelUnsubscribed, + ClientChannelGroupChanged, + ClientChatComposing, + ClientNeededPermissions, + ConnectionInfo, + ConnectionInfoRequest, + FileInfo, + FileList, + FileListFinished, + FileTransfer, + ClientServerGroupAdded, + ServerGroupList, + ServerGroupsByClientId, + StartDownload, + StartUpload, + FileTransferStatus, + } + public static class MessageHelper { public static NotificationType GetNotificationType(string name) @@ -1679,6 +1750,7 @@ public static NotificationType GetNotificationType(string name) case "channellist": return NotificationType.ChannelList; case "channellistfinished": return NotificationType.ChannelListFinished; case "initivexpand": return NotificationType.InitIvExpand; + case "initivexpand2": return NotificationType.InitIvExpand2; case "initserver": return NotificationType.InitServer; case "notifychannelsubscribed": return NotificationType.ChannelSubscribed; case "notifychannelunsubscribed": return NotificationType.ChannelUnsubscribed; @@ -1722,6 +1794,7 @@ public static INotification GenerateNotificationType(NotificationType name) case NotificationType.ChannelList: return new ChannelList(); case NotificationType.ChannelListFinished: return new ChannelListFinished(); case NotificationType.InitIvExpand: return new InitIvExpand(); + case NotificationType.InitIvExpand2: return new InitIvExpand2(); case NotificationType.InitServer: return new InitServer(); case NotificationType.ChannelSubscribed: return new ChannelSubscribed(); case NotificationType.ChannelUnsubscribed: return new ChannelUnsubscribed(); diff --git a/TS3Client/Generated/Messages.tt b/TS3Client/Generated/Messages.tt index 18dc5074..380653bd 100644 --- a/TS3Client/Generated/Messages.tt +++ b/TS3Client/Generated/Messages.tt @@ -41,7 +41,11 @@ namespace TS3Client.Messages string GenerateDeserializer(GenField fld) { if(fld.isArray) - return $"{{ var t = value.Split(new[] {{ ',' }}, StringSplitOptions.RemoveEmptyEntries); {fld.fldName} = new {fld.fldType}[t.Length]; for(int i = 0; i < t.Length; i++) {GenerateSingleDeserializer(fld, "t[i]", fld.fldName + "[i]")} }}"; + return $"{{ if(value.Length == 0) {fld.fldName} = new {fld.fldType}[0]; else {{" + + $" var ss = new SpanSplitter(); ss.First(value, ',');" + + $" int cnt = 0; for (int i = 0; i < value.Length; i++) if (value[i] == ',') cnt++;" + + $" {fld.fldName} = new {fld.fldType}[cnt + 1];" + + $" for(int i = 0; i < cnt + 1; i++) {{ {GenerateSingleDeserializer(fld, "ss.Trim(value)", fld.fldName + "[i]")} if (i < cnt) value = ss.Next(value); }} }} }}"; else return GenerateSingleDeserializer(fld, "value", fld.fldName); } @@ -53,7 +57,7 @@ namespace TS3Client.Messages switch (fld.fldType) { case "bool": - return $"{output} = {input} != \"0\";"; + return $"{output} = {input}.Length > 0 && {input}[0] != '0';"; case "sbyte": case "byte": case "short": @@ -69,13 +73,13 @@ namespace TS3Client.Messages case "ChannelIdT": case "ServerGroupIdT": case "ChannelGroupIdT": - return $"{output} = {fld.fldType}.Parse({input}, CultureInfo.InvariantCulture);"; + return $"{output} = {fld.fldType}.Parse({input}.NewString(), CultureInfo.InvariantCulture);"; case "TimeSpanSecondsT": - return $"{output} = TimeSpan.FromSeconds(double.Parse({input}, CultureInfo.InvariantCulture));"; + return $"{output} = TimeSpan.FromSeconds(double.Parse({input}.NewString(), CultureInfo.InvariantCulture));"; case "TimeSpanMillisecT": - return $"{output} = TimeSpan.FromMilliseconds(double.Parse({input}, CultureInfo.InvariantCulture));"; + return $"{output} = TimeSpan.FromMilliseconds(double.Parse({input}.NewString(), CultureInfo.InvariantCulture));"; case "DateTime": - return $"{output} = Util.UnixTimeStart.AddSeconds(double.Parse({input}, CultureInfo.InvariantCulture));"; + return $"{output} = Util.UnixTimeStart.AddSeconds(double.Parse({input}.NewString(), CultureInfo.InvariantCulture));"; case "string": case "UidT": return $"{output} = Ts3String.Unescape({input});"; @@ -88,14 +92,14 @@ namespace TS3Client.Messages case "TextMessageTargetMode": case "PermissionGroupDatabaseType": case "GroupNamingMode": - return $"{{ if (!Enum.TryParse({input}, out {fld.fldType} val)) throw new FormatException(); {output} = val; }}"; + return $"{{ if (!Enum.TryParse({input}.NewString(), out {fld.fldType} val)) throw new FormatException(); {output} = val; }}"; case "Ts3ErrorCode": case "LicenseType": - return $"{output} = ({fld.fldType})ushort.Parse({input}, CultureInfo.InvariantCulture);"; + return $"{output} = ({fld.fldType})ushort.Parse({input}.NewString(), CultureInfo.InvariantCulture);"; case "PermissionId": - return $"{output} = ({fld.fldType})int.Parse({input}, CultureInfo.InvariantCulture);"; + return $"{output} = ({fld.fldType})int.Parse({input}.NewString(), CultureInfo.InvariantCulture);"; case "IconHash": - return $"{output} = unchecked((int)ulong.Parse({input}, CultureInfo.InvariantCulture));"; + return $"{output} = unchecked((int)ulong.Parse({input}.NewString(), CultureInfo.InvariantCulture));"; default: return "#error missing deserializer"; } @@ -154,7 +158,7 @@ namespace TS3Client.Messages } #> - public void SetField(string name, string value) + public void SetField(string name, ReadOnlySpan value) { <# if (param.Length > 2) {#> @@ -213,16 +217,22 @@ namespace TS3Client.Messages } } #> + public enum NotificationType + { + Unknown, +<# foreach(var kvp in ntfyDict) { #> + <#= kvp.Value.enumName #>,<# + } +#> + } + public static class MessageHelper { public static NotificationType GetNotificationType(string name) { switch(name) { -<# - foreach(var kvp in ntfyDict) - { -#> +<# foreach(var kvp in ntfyDict) { #> case "<#= kvp.Key #>": return NotificationType.<#= kvp.Value.enumName #>;<# } #> diff --git a/TS3Client/Generated/Permissions.cs b/TS3Client/Generated/Permissions.cs index 27f5772e..49431099 100644 --- a/TS3Client/Generated/Permissions.cs +++ b/TS3Client/Generated/Permissions.cs @@ -12,6 +12,9 @@ + + + namespace TS3Client { using Helper; diff --git a/TS3Client/Generated/Versions.cs b/TS3Client/Generated/Versions.cs index e27361a9..97203b81 100644 --- a/TS3Client/Generated/Versions.cs +++ b/TS3Client/Generated/Versions.cs @@ -14,6 +14,9 @@ + + + namespace TS3Client.Full { using System; @@ -64,6 +67,7 @@ public VersionSign(string name, string plattform, string sign) public static readonly VersionSign VER_LIN_3_1_6 = new VersionSign("3.1.6 [Build: 1502873983]", ClientPlattform.Linux, "o+l92HKfiUF+THx2rBsuNjj/S1QpxG1fd5o3Q7qtWxkviR3LI3JeWyc26eTmoQoMTgI3jjHV7dCwHsK1BVu6Aw=="); public static readonly VersionSign VER_WIN_3_1_7 = new VersionSign("3.1.7 [Build: 1507896705]", ClientPlattform.Windows, "Iks42KIMcmFv5vzPLhziqahcPD2AHygkepr8xHNCbqx+li5n7Htbq5LE9e1YYhRhLoS4e2HqOpKkt+/+LC8EDA=="); public static readonly VersionSign VER_OSX_3_1_7 = new VersionSign("3.1.7 [Build: 1507896705]", ClientPlattform.Osx, "iM0IyUpaH9ak0gTtrHlRT0VGZa4rC51iZwSFwifK6iFqciSba/WkIQDWk9GUJN0OCCfatoc/fmlq8TPBnE5XCA=="); + public static readonly VersionSign VER_LIN_3_1_7 = new VersionSign("3.1.7 [Build: 1513163251]", ClientPlattform.Linux, "/j5TZqPuOU8yMYPdGehvijYvU74KefRrKO5sgTUrkpeslNFiy4XfU7quKW0diLHQoPQn1t3KArdfzOAMk8dlAg=="); public static readonly VersionSign VER_WIN_3_X_X = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Windows, "DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="); public static readonly VersionSign VER_AND_3_X_X = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Android, "AWb948BY32Z7bpIyoAlQguSmxOGcmjESPceQe1DpW5IZ4+AW1KfTk2VUIYNfUPsxReDJMCtlhVKslzhR2lf0AA=="); public static readonly VersionSign VER_IOS_3_X_X = new VersionSign("3.?.? [Build: 5680278000]", ClientPlattform.Ios, "XrAf+Buq6Eb0ehEW/niFp06YX+nGGOS0Ke4MoUBzn+cX9q6G5C0A/d5XtgcNMe8r9jJgV/adIYVpsGS3pVlSAA=="); diff --git a/TS3Client/Helper/Extensions.cs b/TS3Client/Helper/Extensions.cs new file mode 100644 index 00000000..e9332142 --- /dev/null +++ b/TS3Client/Helper/Extensions.cs @@ -0,0 +1,52 @@ +// 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 System; + using System.Collections.Generic; + using System.Linq; + using Messages; + + /// Provides useful extension methods and error formatting. + public static class Extensions + { + public static string ErrorFormat(this CommandError error) + { + if (error.MissingPermissionId > PermissionId.unknown) + 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(this R, CommandError> 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); + } + + internal static R, CommandError> UnwrapNotification(this R result) where T : class + { + if (!result.Ok) + return result.Error; + return R, CommandError>.OkR(result.Value.Notifications.Cast()); + } + + internal static string NewString(this ReadOnlySpan span) => new string(span.ToArray()); + } +} diff --git a/TS3Client/Helper/SpanSplitter.cs b/TS3Client/Helper/SpanSplitter.cs new file mode 100644 index 00000000..e801b1e6 --- /dev/null +++ b/TS3Client/Helper/SpanSplitter.cs @@ -0,0 +1,50 @@ +// 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 System; + using System.Runtime.CompilerServices; + + internal class SpanSplitter + { + public bool HasNext => NextIndex >= 0; + public int NextIndex { get; private set; } + private char splitchar; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan First(string str, char split) + { + splitchar = split; + var span = str.AsSpan(); + NextIndex = span.IndexOf(split); + return span; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void First(ReadOnlySpan span, char split) + { + splitchar = split; + NextIndex = span.IndexOf(split); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan Next(ReadOnlySpan current) + { + if(!HasNext) + throw new InvalidOperationException("No next element in span split"); + var ret = current.Slice(NextIndex + 1); + NextIndex = ret.IndexOf(splitchar); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan Trim(ReadOnlySpan current) => HasNext ? current.Slice(0, NextIndex) : current; + } +} diff --git a/TS3Client/Helper/Util.cs b/TS3Client/Helper/Util.cs index f165c2bf..2fb6835e 100644 --- a/TS3Client/Helper/Util.cs +++ b/TS3Client/Helper/Util.cs @@ -57,41 +57,7 @@ internal sealed class MissingEnumCaseException : Exception public MissingEnumCaseException(string enumTypeName, string valueName) : base($"The the switch does not handle the value \"{valueName}\" from \"{enumTypeName}\".") { } public MissingEnumCaseException(string message, Exception inner) : base(message, inner) { } } - - /// Provides useful extension methods and error formatting. - public static class Extensions - { - public static string ErrorFormat(this CommandError error) - { - if (error.MissingPermissionId > PermissionId.unknown) - 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(this R, CommandError> 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); - } - - internal static R, CommandError> UnwrapNotification(this R result) where T : class - { - if (!result.Ok) - return result.Error; - return R, CommandError>.OkR(result.Value.Notifications.Cast()); - } - } - + internal static class DebugUtil { public static string DebugToHex(byte[] bytes) => bytes == null ? "" : DebugToHex(bytes.AsSpan()); diff --git a/TS3Client/Messages/BaseTypes.cs b/TS3Client/Messages/BaseTypes.cs index 405432d9..0d420815 100644 --- a/TS3Client/Messages/BaseTypes.cs +++ b/TS3Client/Messages/BaseTypes.cs @@ -9,9 +9,11 @@ namespace TS3Client.Messages { + using System; + public interface IQueryMessage { - void SetField(string name, string value); + void SetField(string name, ReadOnlySpan value); } public interface INotification : IQueryMessage diff --git a/TS3Client/Messages/Deserializer.cs b/TS3Client/Messages/Deserializer.cs index bb566ba7..50b11e22 100644 --- a/TS3Client/Messages/Deserializer.cs +++ b/TS3Client/Messages/Deserializer.cs @@ -9,10 +9,10 @@ namespace TS3Client.Messages { + using Helper; using System; using System.Collections.Generic; using System.Linq; - using KVEnu = System.Collections.Generic.IEnumerable>; public static class Deserializer { @@ -39,7 +39,7 @@ internal static R GenerateSingleNotification(string lineDataPart, throw new ArgumentNullException(nameof(lineDataPart)); var notification = MessageHelper.GenerateNotificationType(ntfyType); - return ParseKeyValueLine(notification, lineDataPart, false); + return ParseKeyValueLine(notification, lineDataPart); } // data to response @@ -48,40 +48,48 @@ internal static R GenerateSingleNotification(string lineDataPart, if (string.IsNullOrWhiteSpace(line)) return Enumerable.Empty(); var messageList = line.Split('|'); - return messageList.Select(msg => ParseKeyValueLine(new T(), msg, false)).Where(x => x.Ok).Select(x => x.Value); + return messageList.Select(msg => ParseKeyValueLine(new T(), msg)).Where(x => x.Ok).Select(x => x.Value); } - private static R ParseKeyValueLine(T qm, string line, bool ignoreFirst) where T : IQueryMessage + private static R ParseKeyValueLine(T qm, string line) where T : IQueryMessage { if (string.IsNullOrWhiteSpace(line)) return R.Err("Empty"); - // can be optimized by using Span (wating for more features) - var splitValues = line.Split(' '); - string key = null, value = null; + var ss = new SpanSplitter(); + var lineSpan = ss.First(line, ' '); + var key = default(ReadOnlySpan); + var value = default(ReadOnlySpan); try { - for (int i = ignoreFirst ? 1 : 0; i < splitValues.Length; i++) + do { - var keyValuePair = splitValues[i].Split(new[] { '=' }, 2); - key = keyValuePair[0]; - value = keyValuePair.Length > 1 ? keyValuePair[1] : string.Empty; - qm.SetField(key, value); - } + var param = ss.Trim(lineSpan); + var kvpSplitIndex = param.IndexOf('='); + var skey = kvpSplitIndex >= 0 ? param.Slice(0, kvpSplitIndex) : ReadOnlySpan.Empty; + value = kvpSplitIndex <= param.Length - 1 ? param.Slice(kvpSplitIndex + 1) : ReadOnlySpan.Empty; + + qm.SetField(skey.NewString(), value); + + if (!ss.HasNext) + break; + lineSpan = ss.Next(lineSpan); + } while (lineSpan.Length > 0); return R.OkR(qm); } - catch (Exception) { OnError?.Invoke(null, new Error(qm.GetType().Name, line, key, value)); } + catch (Exception ex) { OnError?.Invoke(null, new Error(qm.GetType().Name, line, key.NewString(), value.NewString(), ex)); } return R.Err("Error"); } public class Error : EventArgs { - public string Class { get; set; } - public string Message { get; set; } + public string Class { get; } + public string Message { get; } public string Field { get; } public string Value { get; } + public Exception Exception { get; } - public Error(string classname, string message, string field, string value) { Class = classname; Message = message; Field = field; Value = value; } + public Error(string classname, string message, string field, string value, Exception ex = null) { Class = classname; Message = message; Field = field; Value = value; Exception = ex; } public override string ToString() => $"Deserealization format error. Data: class:{Class} field:{Field} value:{Value} msg:{Message}"; } diff --git a/TS3Client/Messages/ResponseDictionary.cs b/TS3Client/Messages/ResponseDictionary.cs index d0f6df5c..af80c9f4 100644 --- a/TS3Client/Messages/ResponseDictionary.cs +++ b/TS3Client/Messages/ResponseDictionary.cs @@ -7,6 +7,8 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . +using TS3Client.Helper; + namespace TS3Client.Messages { using System; @@ -22,7 +24,11 @@ public class ResponseDictionary : IDictionary, IResponse public ResponseDictionary() { data = new Dictionary(); } public ResponseDictionary(IDictionary dataDict) { data = dataDict; } - public ValueType this[KeyType key] { get { return data[key]; } set { throw new NotSupportedException(); } } + public ValueType this[KeyType key] + { + get => data[key]; + set => throw new NotSupportedException(); + } public int Count => data.Count; public bool IsReadOnly => true; public ICollection Keys => data.Keys; @@ -39,17 +45,17 @@ public class ResponseDictionary : IDictionary, IResponse public bool TryGetValue(KeyType key, out ValueType value) => data.TryGetValue(key, out value); IEnumerator IEnumerable.GetEnumerator() => data.GetEnumerator(); - public void SetField(string name, string value) => data[name] = value; + public void SetField(string name, ReadOnlySpan value) => data[name] = value.NewString(); public string ReturnCode { - get { return data.ContainsKey("return_code") ? data["return_code"] : string.Empty; } - set { data["return_code"] = value; } + get => data.ContainsKey("return_code") ? data["return_code"] : string.Empty; + set => data["return_code"] = value; } } public sealed class ResponseVoid : IResponse { public string ReturnCode { get; set; } - public void SetField(string name, string value) { } + public void SetField(string name, ReadOnlySpan value) { } } } diff --git a/TS3Client/OwnEnums.cs b/TS3Client/OwnEnums.cs index 68d36ee8..e3324415 100644 --- a/TS3Client/OwnEnums.cs +++ b/TS3Client/OwnEnums.cs @@ -54,54 +54,6 @@ public enum GroupNamingMode After } - public enum NotificationType - { - Unknown, - Error, - // Official notifies, used by client and query - ChannelCreated, - ChannelDeleted, - ChannelChanged, - ChannelEdited, - ChannelMoved, - ChannelPasswordChanged, - ClientEnterView, - ClientLeftView, - ClientMoved, - ServerEdited, - TextMessage, - TokenUsed, - - // Internal notifies, used by client - InitIvExpand, - InitServer, - ChannelList, - ChannelListFinished, - ClientNeededPermissions, - ClientChannelGroupChanged, - ClientServerGroupAdded, - ConnectionInfoRequest, - ConnectionInfo, - ChannelSubscribed, - ChannelUnsubscribed, - ClientChatComposing, - ServerGroupList, - ServerGroupsByClientId, - StartUpload, - StartDownload, - FileTransfer, - FileTransferStatus, - FileList, - FileListFinished, - FileInfo, - // TODO: notifyclientchatclosed - // TODO: notifyclientpoke - // TODO: notifyclientupdated - // TODO: notifyclientchannelgroupchanged - // TODO: notifychannelpasswordchanged - // TODO: notifychanneldescriptionchanged - } - // ReSharper disable UnusedMember.Global public enum MoveReason { diff --git a/TS3Client/TS3Client.csproj b/TS3Client/TS3Client.csproj index e146de50..353621f5 100644 --- a/TS3Client/TS3Client.csproj +++ b/TS3Client/TS3Client.csproj @@ -93,6 +93,7 @@ True Versions.tt + @@ -107,13 +108,14 @@ - + - + + @@ -147,7 +149,6 @@ Designer - diff --git a/TS3Client/ts3protocol.md b/TS3Client/ts3protocol.md deleted file mode 100644 index b16c94d4..00000000 --- a/TS3Client/ts3protocol.md +++ /dev/null @@ -1,644 +0,0 @@ -TS3 PROTOCOL PAPER -================== - -# 0. Naming Conventions -- `(Client -> Server)` denotes packets from client to server. -- `(Client <- Server)` denotes packets from server to client. -- All datatypes are sent in network order (Big Endian) unless otherwise -specified. -- Datatypes are declared with a prefixing `u` or `i` for unsigned and signed -and a number for the bitlength. -For example `u8` would be be the C equivalent of `uint8` or `unsigned char` -- Arrays are represented by the underlying datatype in square brackets, -additionally if the length is known it is added in the brackets, separated by -a semicolon. Eg: `[u8]`, `[i32; 16]` -- Array ranges (parts of an array) are specified in square brackets with the -included lower bound, a minus and the excluded upper bound. Eg: `[0-10]` - - -# 1. Low-Level Packets -## 1.1 Packet structure -- The packets are build in a fixed scheme, -though have differences depending in which direction. -- Every column here represents 1 byte. -- The entire packet size must be at max 500 bytes. - -### 1.1.1 (Client -> Server) - +--+--+--+--+--+--+--+--+--+--+--+--+--+---------//----------+ - | MAC | PId | CId |PT| Data | - +--+--+--+--+--+--+--+--+--+--+--+--+--+---------//----------+ - | \ Meta | - \ Header / - -| Name | Size | Datatype | Explanation | -|------|-------------|----------|---------------------------------| -| MAC | 8 bytes | [u8] | EAX Message Authentication Code | -| PId | 2 bytes | u16 | Packet Id | -| CId | 2 bytes | u16 | Client Id | -| PT | 1 byte | u8 | Packet Type + Flags | -| Data | <=487 bytes | [u8] | The packet payload | - -### 1.1.2 (Client <- Server) - +--+--+--+--+--+--+--+--+--+--+--+------------//-------------+ - | MAC | PId |PT| Data | - +--+--+--+--+--+--+--+--+--+--+--+------------//-------------+ - | \ Meta | - \ Header / - -| Name | Size | Datatype | Explanation | -|------|-------------|----------|---------------------------------| -| MAC | 8 bytes | [u8] | EAX Message Authentication Code | -| PId | 2 bytes | u16 | Packet Id | -| PT | 1 byte | u8 | Packet Type + Flags | -| Data | <=489 bytes | [u8] | The packet payload | - -## 1.2 Packet Types -- `0x00` Voice -- `0x01` VoiceWhisper -- `0x02` Command -- `0x03` CommandLow -- `0x04` Ping -- `0x05` Pong -- `0x06` Ack -- `0x07` AckLow -- `0x08` Init1 - -## 1.3 Packet Type + Flags byte -The final byte then looks like this - - MSB LSB - +--+--+--+--+--+--+--+--+ - |UE|CP|NP|FR| Type | - +--+--+--+--+--+--+--+--+ - -| Name | Size | Hex | Explanation | -|------|-------|------|-----------------| -| UE | 1 bit | 0x80 | Unencrypted | -| CP | 1 bit | 0x40 | Compressed | -| NP | 1 bit | 0x20 | Newprotocol | -| FR | 1 bit | 0x10 | Fragmented | -| Type | 4 bit | 0-8 | The packet type | - -## 1.4 Packet Compression -To reduce a packet size the data can be compressed. -When the data is compressed the `Compressed` flag must be set. -The algorithm "QuickLZ" with Level 1 is used for compression. - -## 1.5 Packet Splitting -When the packet payload exceeds the maximum datablock size the data can be -split up across multiple packets. -When splitting occurs, the `Fragmented` flag must be set on the first and -the last packet. Other flags, if set, are only set on the first packet. -The data can additionally be compressed before splitting. - -## 1.6 Packet Encryption -When a packet is not encrypted the `Unencrypted` flag is set. For encrypted -packets the flag gets cleared. -Packets get encrypted with EAX mode (AES-CTR with OMAC). -The en/decryption parameters get generated for each packet as follows - -### 1.6.1 Inputs -| Name | Type | Explanation | -|------|----------|---------------------------------| -| PT | u8 | Packet Type | -| PId | u16 | Packet Id | -| PGId | u32 | Packet GenerationId (see 1.9.2) | -| PD | bool | Packet Direction | -| SIV | [u8; 20] | Shared IV (see 3.2) | - -### 1.6.2 Generation pseudocode - let temporary: [u8; 26] - temporary[0] = 0x30 if (Client <- Server) - 0x31 if (Client -> Server) - temporary[1] = PT - temporary[2-6] = (PGId in network order)[0-4] - temporary[6-26] = SIV[0-20] - - let keynonce: [u8; 32] - keynonce = SHA256(temporary) - - key: [u8; 16] = keynonce[0-16] - nonce: [u8; 16] = keynonce[16-32] - key[0] = key[0] xor ((PId & 0xFF00) >> 8) - key[1] = key[1] xor ((PId & 0x00FF) >> 0) - -### 1.6.3 Encryption -The data can now be encrypted with the `key` and `nonce` from (see 1.6.2) as the -EAX key and nonce and the packet `Meta` as defined in (see 1.1) as the EAX -header (sometimes called "Associated Text"). The resulting EAX mac (sometimes -called "Tag") will be stored in the `MAC` field as defined in (see 2.1). - -### 1.6.4 Not encrypted packets -When a packet is not encrypted no MAC can be generated by EAX. Therefore -the SharedMac (see 3.2) will be used. - -## 1.7 Packet Stack Wrap-up -This stack is a reference for the execution order of the set data operations. -For incoming packets the stack is executed bot to top, for outgoing packets -top to bot. - - +-----------+ - | Data | - +-----------+ - | Compress | - +-----------+ - | Split | - +-----------+ - | Encrypt | - +-----------+ - -## 1.8 Packet Types Data Structures -The following chapter describes the data structure for different packet types. - -### 1.8.1.1 Voice (Client -> Server) - +--+--+--+---------//---------+ - | VId |C | Data | - +--+--+--+---------//---------+ - -| Name | Type | Explanation | -|------|------|-----------------| -| VId | u16 | Voice Packet Id | -| C | u8 | Codec Type | -| Data | var | Voice Data | - -### 1.8.1.2 Voice (Client <- Server) - +--+--+--+--+--+---------//---------+ - | VId | CId |C | Data | - +--+--+--+--+--+---------//---------+ - -| Name | Type | Explanation | -|------|------|-----------------| -| VId | u16 | Voice Packet Id | -| CId | u16 | Talking Client | -| C | u8 | Codec Type | -| Data | var | Voice Data | - -### 1.8.2.1 VoiceWhisper (Client -> Server) - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ - | VId |C |N |M | U* | T* | Data | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ - -For direct user/channel targeting -The `Newprotocol` Flag must be *unset* - -| Name | Type | Explanation | -|------|-------|---------------------------------------| -| VId | u16 | Voice Packet Id | -| C | u8 | Codec Type | -| N | u8 | Count of ChannelIds to send to | -| M | u8 | Count of ClientIds to send to | -| U | [u64] | Targeted ChannelIds, repeated N times | -| T | [u16] | Targeted ClientIds, repeated M times | -| Data | var | Voice Data | - -OR - - +--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ - | VId |C |TY|TA| U | Data | - +--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+ - -For targeting special groups -The `Newprotocol` Flag must be *set* - -| Name | Type | Explanation | -|------|-------|---------------------------------------------------------| -| VId | u16 | Voice Packet Id | -| C | u8 | Codec Type | -| TY | u8 | GroupWhisperType (see below) | -| TA | u8 | GroupWhisperTarget (see below) | -| U | u64 | the targeted channelId or groupId (0 if not applicable) | -| Data | var | Voice Data | - -``` -enum GroupWhisperType : u8 -{ - // Targets all users in the specified server group. - ServerGroup = 0 /* U = servergroup targetId */, - // Targets all users in the specified channel group. - ChannelGroup = 1 /* U = channelgroup targetId */, - // Targets all users with channel commander. - ChannelCommander = 2, /* U = 0 (ignored) */, - // Targets all users on the server. - AllClients = 3, /* U = 0 (ignored) */, -} - -enum GroupWhisperTarget : u8 -{ - AllChannels = 0, - CurrentChannel = 1, - ParentChannel = 2, - AllParentChannel = 3, - ChannelFamily = 4, - CompleteChannelFamily = 5, - Subchannels = 6, -} -``` - -### 1.8.2.2 VoiceWhisper (Client <- Server) - +--+--+--+--+--+---------//---------+ - | VId | CId |C | Data | - +--+--+--+--+--+---------//---------+ - -| Name | Type | Explanation | -|------|------|-----------------| -| VId | u16 | Voice Packet Id | -| CId | u16 | Talking Client | -| C | u8 | Codec Type | -| Data | var | Voice Data | - -### 1.8.3-4 Command and CommandLow -The TeamSpeak3 Query like command string encoded in UTF-8 - -### 1.8.5 Ping -Empty. - -### 1.8.6-8 Pong, Ack and AckLow - +--+--+ - | PId | - +--+--+ - -| Name | Type | Explanation | -|------|------|------------------------------------| -| PId | u16 | The packet id that is acknowledged | - -- In case of `Pong` a matching ping packet id is acknowledged. -- In case of `Ack` or `AckLow` a matching Command or CommandLow packet id -respectively is acknowledged. - -### 1.8.9 Init1 -(see 2.1)-(see 2.5) - -## 1.9 Packet Ids and Generations -### 1.9.1 Packet Ids -Each packet type and packet direction must be maintained by an own packet id -counter. -This means the client has 9 different packet id counter for outgoing packets. - -For each new packet the counter gets increased by 1. This also applies to -splitted packets. - -The client must also maintain packet ids for incoming packets in case of -packets arriving out of order. - -All Packet Ids start at 1 unless otherwise specified. - -### 1.9.2 Generations -Packet Ids are stored as u16, this mean that they range from 0-65536. - -When the packet id overflows from 65535 to 0 at a packet, -the generation counter for this packet type gets increased by 1. - -Note that the new generation id immediately applies to the 'overflowing' packet. - -The generation id counter is solely used for encryption (see 1.6). - -## 1.10 Packet Acknowledgement / Packet Loss -In order to reliably send packets over UPD some packet types must get -acknowledged when received (see 1.11). - -The protocol uses selective repeat for lost packets. This means each packet has -its own timeout. Already acknowledged later packets must not be resent. -When a packet times out, the exact same packet should be resent until properly -acknowledged by the server. -If after 30 seconds no resent packet gets acknowledged the connection should be -closed. -Packet resend timeouts should be calculated with an exponential backoff to -prevent network congestion. - -## 1.11 Wrap-up -| Type | Acknowledged (by) | Resend | Encrypted | Splittable | Compressable | -|--------------|-------------------|--------|-----------|------------|--------------| -| Voice | ✗ | ✗ | Optional | ✗ | ✗ | -| VoiceWhisper | ✗ | ✗ | Optional | ✗ | ✗ | -| Command | ✓ (Ack) | ✓ | ✓ | ✓ | ✓ | -| CommandLow | ✓ (AckLow) | ✓ | ✓ | ✓ | ✓ | -| Ping | ✓ (Pong) | ✗ | ✗ | ✗ | ✗ | -| Pong | ✗ | ✗ | ✗ | ✗ | ✗ | -| Ack | ✗ | ✓ | ✓ | ✗ | ✗ | -| AckLow | ✗ | ✓ | ✓ | ✗ | ✗ | -| Init1 | ✓ (next Init1) | ✓ | ✗ | ✗ | ✗ | - -# 2. The (Low-Level) Initiation/Handshake -A connection is started from the client by sending the first handshake -packet. The handshake process consists of 5 different init packets. This -includes the so called RSA puzzle to prevent DOS attacks. - -The packet header values are set as following for all packets here: - -| Parameter | Value | -|-----------|--------------------------------------------------------| -| MAC | [u8]{ 0x54, 0x53, 0x33, 0x49, 0x4E, 0x49, 0x54, 0x31 } | -| key | N/A | -| nonce | N/A | -| Type | Init1 | -| Encrypted | ✗ | -| Packet Id | u16: 101 | -| Client Id | u16: 0 | - -## 2.1 Packet 0 (Client -> Server) - 04 bytes : Version of the TeamSpeak client as timestamp - Example: { 0x06, 0x3b, 0xec, 0xe9 } - 01 bytes : Init-packet step number - Const: 0x00 - 04 bytes : Current timestamp in unix format - 04 bytes : Random bytes := [A0] - 08 bytes : Zeros, reserved. - -## 2.2 Packet 1 (Client <- Server) - 01 bytes : Init-packet step number - Const: 0x01 - 16 bytes : Server stuff := [A1] - 04 bytes : The bytes from [A0] in reversed order - -## 2.3 Packet 2 (Client -> Server) - 04 bytes : Version of the TeamSpeak client as timestamp - 01 bytes : Init-packet step number - Const: 0x02 - 16 bytes : The bytes from [A1] - 04 bytes : The bytes from [A0] in reversed order - -## 2.4 Packet 3 (Client <- Server) - 01 bytes : Init-packet step number - Const: 0x03 - 64 bytes : 'x', an unsigned BigInteger - 64 bytes : 'n', an unsigned BigInteger - 04 bytes : 'level' a u32 - 100 bytes : Server stuff := [A2] - -## 2.5 Packet 4 (Client -> Server) - 04 bytes : Version of the TeamSpeak client as timestamp - 01 bytes : Init-packet step number - Const: 0x04 - 64 bytes : the received 'x' - 64 bytes : the received 'n' - 04 bytes : the received 'level' - 100 bytes : The bytes from [A2] - 64 bytes : 'y' which is the result of x ^ (2 ^ level) % n as an unsigned - BigInteger. Padded from the lower side with '0x00' when shorter - than 64 bytes. - Example: { 0x00, 0x00, data ... data} - var bytes : The clientinitiv command data as explained in (see 3.1) - - -# 3. The (High-Level) Initiation/Handshake -In this phase the client and server exchange basic information and -agree on/calculate the symmetric AES encryption key with the ECDH -public/private key exchange technique. - -Both the client and the server will need a EC public/private key. This key -is also the identity which the server uses to recognize a user again. -The curve used is 'prime256v1'. - -All high level packets specified in this chapter are sent as `Command` Type -packets as explained in (see 2.8.3). Additionally the `Newprotocol` flag -(see 2.3) must be set on all `Command`, `CommandLow` and `Init1` packets. - -The packet header/encryption values for (see 3.1) and (see 3.2) are as following: - -| Parameter | Value | -|-----------|------------------------------------------------------------------------------------------------------| -| MAC | (Generated by EAX) | -| key | [u8]{0x63, 0x3A, 0x5C, 0x77, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x5C, 0x73, 0x79, 0x73, 0x74, 0x65} | -| nonce | [u8]{0x6D, 0x5C, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6C, 0x6C, 0x33, 0x32, 0x2E, 0x63, 0x70, 0x6C} | -| Type | Command | -| Encrypted | ✓ | -| Packet Id | u16: 0 | -| Client Id | u16: 0 | - -The acknowledgement packets use the same parameters as the commands, except with -the Type `Ack`. - -_(Maybe add a #3.0 Prelude for required cryptographic values, if yes move the -omega ASN.1 encoding here)_ - -## 3.1 clientinitiv (Client -> Server) -The first packet is sent (Client -> Server) although this is only sent for -legacy reasons since newer servers (at least 3.0.13.0?) use the data part -embedded in the last `Init1` packet from the low-level handshake (see 2.5). - -The ip parameter is added but left without value for legacy reasons. - - clientinitiv alpha={alpha} omega={omega} ip - -- `alpha` is set to `base64(random[u8; 10])` - which are 10 random bytes for entropy. -- `omega` is set to `base64(publicKey[u8])` - omega is an ASN.1-DER encoded public key from the ECDH parameters as following: - - | Type | Value | Explanation | - |------------|----------------|-------------------------------------------| - | BIT STRING | 1bit, Value: 0 | LibTomCrypt uses 0 for a public key | - | INTEGER | 32 | The LibTomCrypt used keysize | - | INTEGER | publicKey.x | The affine X-Coordinate of the public key | - | INTEGER | publicKey.y | The affine Y-Coordinate of the public key | - -## 3.2 initivexpand (Client <- Server) -The server responds with this command. - - initivexpand alpha={alpha} beta={beta} omega={omega} - -- `alpha` must have the same value as sent to the server in the previous step. -- `beta` is set to `base64(random[u8; 10])` - by the server. -- `omega` is set to `base64(publicKey[u8])` - with the public Key from the server, encoded same as in (see 3.1) - -With this information the client now must calculate the shared secret. - - let sharedSecret: ECPoint - let x: [u8] - let sharedData: [u8; 32] - let SharedIV: [u8; 20] - let SharedMac: [u8; 8] - let ECDH(A, B) := (A * B).Normalize - - sharedSecret = ECDH(serverPublicKey, ownPrivateKey) - x = sharedSecret.x.AsByteArray() - if x.length < 32 - sharedData[0-(32-x.length)] = [0..0] - sharedData[(32-x.length)-32] = x[0-x.length] - if x.length == 32 - sharedData[0-32] = x[0-32] - if x.length > 32 - sharedData[0-32] = x[(x.length-32)-x.length] - SharedIV = SHA1(sharedData) - SharedIV[0-10] = SharedIV[0-10] xor alpha.decode64() - SharedIV[10-20] = SharedIV[10-20] xor beta.decode64() - SharedMac[0-8] = SHA1(SharedIV)[0-8] - -**Notes**: -- Only `SharedIV` and `SharedMac` are needed. The other values can be discarded. -- The crypto handshake is now completed. The normal encryption scheme (see 1.6) is - from now on used. -- All `Command`, `CommandLow`, `Ack` and `AckLow` packets must get encrypted. -- `Voice` packets (and `VoiceWhisper` when wanted) should be encrypted when the -channel encryption or server wide encryption flag is set. -- `Ping` and `Pong` must not be encrypted. - -## 3.3 clientinit (Client -> Server) - clientinit client_nickname client_version client_platform client_input_hardware client_output_hardware client_default_channel client_default_channel_password client_server_password client_meta_data client_version_sign client_key_offset client_nickname_phonetic client_default_token hwid - -- `client_nickname` the desired nickname -- `client_version` the client version -- `client_platform` the client platform -- `client_input_hardware` whether a input device is available -- `client_output_hardware` whether a output device is available -- `client_default_channel` the default channel to join. This can be a channel - path or `/` (eg `/1`) for a channel id. -- `client_default_channel_password` the password for the join channel, prepared - the following way `base64(sha1(password))` -- `client_server_password` the password to enter the server, prepared the - following way `base64(sha1(password))` -- `client_meta_data` (can be left empty) -- `client_version_sign` a cryptographic sign to verify the genuinity of the - client -- `client_key_offset` the number offset used to calculate the hashcash (see 4.1) - value of the used identity -- `client_nickname_phonetic` the phonetic nickname for text-to-speech -- `client_default_token` permission token to be used when connecting to a server -- `hwid` hardware identification string - -**Notes**: -- Since client signs are only generated and distributed by TeamSpeak systems, - this the recommended client triple, as it is the reference for this paper - - Version: `3.0.19.3 [Build: 1466672534]` - - Platform: `Windows` - - Sign: `a1OYzvM18mrmfUQBUgxYBxYz2DUU6y5k3/mEL6FurzU0y97Bd1FL7+PRpcHyPkg4R+kKAFZ1nhyzbgkGphDWDg==` -- The `hwid` is usually around 30 characters long, but strings as short as only - few characters like `123,456` are accepted -- Parameters which are empty or not used must be declared but left without - value and the `=` character - -## 3.4 initserver (Client <- Server) - initserver virtualserver_welcomemessage virtualserver_platform virtualserver_version virtualserver_maxclients virtualserver_created virtualserver_hostmessage virtualserver_hostmessage_mode virtualserver_id virtualserver_ip virtualserver_ask_for_privilegekey acn aclid pv lt client_talk_power client_needed_serverquery_view_power virtualserver_name virtualserver_codec_encryption_mode virtualserver_default_server_group virtualserver_default_channel_group virtualserver_hostbanner_url virtualserver_hostbanner_gfx_url virtualserver_hostbanner_gfx_interval virtualserver_priority_speaker_dimm_modificator virtualserver_hostbutton_tooltip virtualserver_hostbutton_url virtualserver_hostbutton_gfx_url virtualserver_name_phonetic virtualserver_icon_id virtualserver_hostbanner_mode virtualserver_channel_temp_delete_delay_default - -- `virtualserver_welcomemessage` the welcome message of the sever -- `virtualserver_platform` the plattform the server is running on -- `virtualserver_version` the verison of the server -- `virtualserver_maxclients` the maximum allowed clients on this server -- `virtualserver_created` the start date of the server -- `virtualserver_hostmessage` -- `virtualserver_hostmessage_mode` -- `virtualserver_id` -- `virtualserver_ip` -- `virtualserver_ask_for_privilegekey` -- `acn` the accepted client nickname, this might differ from the desired - nickname if it's already in use -- `aclid` the assigned client Id -- `pv` Protocol Version -- `lt` License Type of the server -- `client_talk_power` the initial talk power -- `client_needed_serverquery_view_power` -- `virtualserver_name` -- `virtualserver_codec_encryption_mode` see CodecEncryptionMode from the - official query documentation -- `virtualserver_default_server_group` -- `virtualserver_default_channel_group` -- `virtualserver_hostbanner_url` -- `virtualserver_hostbanner_gfx_url` -- `virtualserver_hostbanner_gfx_interval` -- `virtualserver_priority_speaker_dimm_modificator` -- `virtualserver_hostbutton_tooltip` -- `virtualserver_hostbutton_url` -- `virtualserver_hostbutton_gfx_url` -- `virtualserver_name_phonetic` -- `virtualserver_icon_id` -- `virtualserver_hostbanner_mode` -- `virtualserver_channel_temp_delete_delay_default` - -Note: -- From this point on the client knows his client id, therefore it must be set - in the header of each packet - -## 3.5 Further notifications -The server will now send all needed information to display the entire -server properly. Those notifications are in no fixed order, although they - are most of the time sent in the here declared order. - -### 3.5.1 channellist and channellistfinished -See the official query documentation to get further details to this -notifications parameter. - - channellist cid cpid channel_name channel_topic channel_codec channel_codec_quality channel_maxclients channel_maxfamilyclients channel_order channel_flag_permanent channel_flag_semi_permanent channel_flag_default channel_flag_password channel_codec_latency_factor channel_codec_is_unencrypted channel_delete_delay channel_flag_maxclients_unlimited channel_flag_maxfamilyclients_unlimited channel_flag_maxfamilyclients_inherited channel_needed_talk_power channel_forced_silence channel_name_phonetic channel_icon_id channel_flag_private - -- `cid` Channel id -- `cpid` Channel parent id -- `channel_name` -- `channel_topic` -- `channel_codec` see the Codec enum from the official query documentation -- `channel_codec_quality` value between 0-10 representing a bitrate (see XXX) -- `channel_maxclients` -- `channel_maxfamilyclients` -- `channel_order` -- `channel_flag_permanent` -- `channel_flag_semi_permanent` -- `channel_flag_default` -- `channel_flag_password` -- `channel_codec_latency_factor` -- `channel_codec_is_unencrypted` -- `channel_delete_delay` -- `channel_flag_maxclients_unlimited` -- `channel_flag_maxfamilyclients_unlimited` -- `channel_flag_maxfamilyclients_inherited` -- `channel_needed_talk_power` -- `channel_forced_silence` -- `channel_name_phonetic` -- `channel_icon_id` -- `channel_flag_private` - -After the last `channellist` notification the server will send - - channellistfinished - -### 3.5.2 notifycliententerview -Same as the query notification. - -### 3.5.3 notifychannelgrouplist -### 3.5.4 notifyservergrouplist -### 3.5.5 notifyclientneededpermissions - -# 4. Further concepts -## 4.1 Hashcash -To prevent client spamming (connecting to a server with many different clients) -the server requires a certain hashcash level on each identity. This level has a -exponentially growing calculation time with increasing level. This ensures that -a user wanting to spam a certain server needs to invest some time into -calculating the required level. - -- The publicKey is a string encoded as in (see 3.1) the omega value. -- The key offset is a u64 number, which gets converted to a string when -concatenated. - -The first step is to calculate a hash as following - - let data: [u8; 20] = SHA1(publicKey + keyOffset) - -The level can now be calculated by counting the continuous leading zero bits in -the data array. -The bytes in the array get counted from 0 to 20 and the bits in each byte -from least significant to most significant. - -## 4.2 Uid -To calculate the uid of an identity the public key is required. Therefore you -can only calculate the uid of your own identity and the servers identity you -are connecting to. - -The publicKey is a string encoded as in (see 3.1) the omega value. - -The uid can be calculated as following - - let uid: string = base64(sha1(publicKey)) - -## 4.3 Ping/Pong -The server will regularly send ping packets to check if a client is still alive. -The client must answer them with the according pong packet. - -The client should also send ping packets to the server to check for connection. -They will be answered with according pong packets. - -Sending ping packets from the client side should not be started before the -crypto handshake has been completed (see 3.3) - -## 4.? Differences between Query and Full Client -- notifyconnectioninforequest -- => setconnectioninfo diff --git a/Ts3ClientTests/Program.cs b/Ts3ClientTests/Program.cs index eb2cf449..9299290a 100644 --- a/Ts3ClientTests/Program.cs +++ b/Ts3ClientTests/Program.cs @@ -23,16 +23,16 @@ static void Main() client.OnDisconnected += Client_OnDisconnected; client.OnErrorEvent += Client_OnErrorEvent; client.OnTextMessageReceived += Client_OnTextMessageReceived; - var data = Ts3Crypt.LoadIdentity("MG8DAgeAAgEgAiEAqNonGuL0w/8kLlgLbl4UkH8DQQJ7fEu1tLt+mx1E+XkCIQDgQoIGcBVvAvNoiDT37iWbPQb2kYe0+VKLg67OH2eQQwIgTyijCKx7QB/xQSnIW5uIkVmcm3UU5P2YnobR9IEEYPg=", 64, 0); - con = new ConnectionDataFull() { Address = "127.0.0.1", Username = "TestClient", Identity = data, Password = "qwer" }; + var data = Ts3Crypt.LoadIdentity("MCkDAgbAAgEgAiBPKKMIrHtAH/FBKchbm4iRWZybdRTk/ZiehtH0gQRg+A==", 64, 0); + con = new ConnectionDataFull() { Address = "127.0.0.1", Username = "TestClient", Identity = data, Password = "qwer", VersionSign = VersionSign.VER_WIN_3_1_7 }; client.Connect(con); clients.Add(client); } Console.WriteLine("End"); - Console.ReadLine(); + //Console.ReadLine(); } - + private static void Client_OnDisconnected(object sender, DisconnectEventArgs e) { var client = (Ts3FullClient)sender; @@ -44,6 +44,7 @@ private static void Client_OnConnected(object sender, EventArgs e) var client = (Ts3FullClient)sender; Console.WriteLine("Connected id {0}", client.ClientId); var data = client.ClientInfo(client.ClientId); + client.Disconnect(); client.Connect(con); } From 7dbae26d0079e9df990160b574ced6015cf5ee62 Mon Sep 17 00:00:00 2001 From: Splamy Date: Wed, 14 Feb 2018 01:25:21 +0100 Subject: [PATCH 42/48] Some cleanup and log improvements --- TS3AudioBot.sln | 2 +- TS3AudioBot/Audio/FfmpegProducer.cs | 2 + TS3AudioBot/Commands.cs | 7 +- TS3AudioBot/Core.cs | 25 ++- TS3AudioBot/Helper/Environment/SystemData.cs | 198 +++++++++++++++++++ TS3AudioBot/Helper/ImageUtil.cs | 8 +- TS3AudioBot/Helper/NativeWinDllLoader.cs | 42 ---- TS3AudioBot/Helper/Util.cs | 100 ---------- TS3AudioBot/NLog.config | 38 ++-- TS3AudioBot/TS3AudioBot.csproj | 3 +- TS3AudioBot/Ts3Full.cs | 13 +- TS3AudioBot/Web/WebManager.cs | 5 +- TS3Client/Full/Audio/EncoderPipe.cs | 18 +- TS3Client/Helper/NativeWinDllLoader.cs | 11 +- TS3Client/Messages/ResponseDictionary.cs | 3 +- 15 files changed, 281 insertions(+), 194 deletions(-) create mode 100644 TS3AudioBot/Helper/Environment/SystemData.cs delete mode 100644 TS3AudioBot/Helper/NativeWinDllLoader.cs diff --git a/TS3AudioBot.sln b/TS3AudioBot.sln index 5bfa0af1..51a1a8b0 100644 --- a/TS3AudioBot.sln +++ b/TS3AudioBot.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 +VisualStudioVersion = 15.0.27130.2027 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TS3AudioBot", "TS3AudioBot\TS3AudioBot.csproj", "{0ECC38F3-DE6E-4D7F-81EB-58B15F584635}" EndProject diff --git a/TS3AudioBot/Audio/FfmpegProducer.cs b/TS3AudioBot/Audio/FfmpegProducer.cs index ba21914e..5977be48 100644 --- a/TS3AudioBot/Audio/FfmpegProducer.cs +++ b/TS3AudioBot/Audio/FfmpegProducer.cs @@ -11,6 +11,7 @@ namespace TS3AudioBot.Audio { using Helper; using System; + using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; @@ -159,6 +160,7 @@ public R StartFfmpegProcess(string url, string extraPreParam = null, string extr return R.OkR; } } + catch (Win32Exception ex) { return $"Ffmpeg could not be found ({ex.Message})"; } catch (Exception ex) { return $"Unable to create stream ({ex.Message})"; } } diff --git a/TS3AudioBot/Commands.cs b/TS3AudioBot/Commands.cs index c02cb7ff..68cfbb13 100644 --- a/TS3AudioBot/Commands.cs +++ b/TS3AudioBot/Commands.cs @@ -11,6 +11,7 @@ namespace TS3AudioBot { using CommandSystem; using Helper; + using Helper.Environment; using History; using Plugins; using System; @@ -44,7 +45,7 @@ public static void CommandAdd(ExecutionInformation info, string parameter) => info.Bot.PlayManager.Enqueue(info.InvokerData, parameter).UnwrapThrow(); [Command("api token", "Generates an api token.")] - [Usage("[]", "Optionally specifies a duration this key is valid in hours.")] + [Usage("[]", "Optionally specifies a duration this key is valid in hours.")] [RequiredParameters(0)] public static JsonObject CommandApiToken(ExecutionInformation info, double? validHours) { @@ -1178,8 +1179,8 @@ public static void CommandUnsubscribeTemporary(ExecutionInformation info) [Command("version", "Gets the current build version.")] public static JsonObject CommandVersion() { - var data = Util.GetAssemblyData(); - return new JsonSingleValue(data.ToLongString(), data); + var data = SystemData.AssemblyData; + return new JsonSingleValue(data.ToLongString(), data); } [Command("volume", "Sets the volume level of the music.")] diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index a18e2f7a..b0772a0f 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -12,6 +12,7 @@ namespace TS3AudioBot using CommandSystem; using Dependency; using Helper; + using Helper.Environment; using History; using NLog; using Plugins; @@ -143,7 +144,7 @@ private bool ReadParameter(string[] args) case "-V": case "--version": - Console.WriteLine(Util.GetAssemblyData().ToLongString()); + Console.WriteLine(SystemData.AssemblyData.ToLongString()); return false; default: @@ -176,12 +177,28 @@ private R InitializeCore() Log.Info("[============ TS3AudioBot started =============]"); Log.Info("[=== Date/Time: {0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString()); - Log.Info("[=== Version: {0}", Util.GetAssemblyData()); - Log.Info("[=== Platform: {0}", Util.GetPlattformData()); + Log.Info("[=== Version: {0}", SystemData.AssemblyData); + Log.Info("[=== Platform: {0}", SystemData.PlattformData); + Log.Info("[=== Runtime: {0}", SystemData.RuntimeData.FullName); + Log.Info("[=== Opus: {0}", TS3Client.Full.Audio.Opus.NativeMethods.Info); Log.Info("[==============================================]"); + if (SystemData.RuntimeData.Runtime == Runtime.Mono) + { + if (SystemData.RuntimeData.SemVer == null) + { + Log.Warn("Could not find your running mono version!"); + Log.Warn("This version might not work properly."); + Log.Warn("If you encounter any problems, try installing the latest mono version by following http://www.mono-project.com/download/"); + } + else if (SystemData.RuntimeData.SemVer.Major < 5) + { + Log.Error("You are running a mono version below 5.0.0!"); + Log.Error("This version is not supported and will not work properly."); + Log.Error("Install the latest mono version by following http://www.mono-project.com/download/"); + } + } Log.Info("[============ Initializing Modules ============]"); - Log.Info("Using opus version: {0}", TS3Client.Full.Audio.Opus.NativeMethods.Info); TS3Client.Messages.Deserializer.OnError += (s, e) => Log.Error(e.ToString()); Injector = new CoreInjector(); diff --git a/TS3AudioBot/Helper/Environment/SystemData.cs b/TS3AudioBot/Helper/Environment/SystemData.cs new file mode 100644 index 00000000..f20845ac --- /dev/null +++ b/TS3AudioBot/Helper/Environment/SystemData.cs @@ -0,0 +1,198 @@ +// 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.Helper.Environment +{ + using System; + using System.Diagnostics; + using System.Reflection; + using System.Text.RegularExpressions; + using Version = System.ValueTuple; + + public static class SystemData + { + private static readonly Regex PlattformRegex = new Regex(@"(\w+)=(.*)", Util.DefaultRegexConfig | RegexOptions.Multiline); + private static readonly Regex SemVerRegex = new Regex(@"(\d+)(?:\.(\d+)){2,3}", Util.DefaultRegexConfig | 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() + { + var gitInfoType = Assembly.GetExecutingAssembly().GetType(nameof(TS3AudioBot) + ".GitVersionInformation"); + if (gitInfoType == null) + return new BuildData(); + + return new BuildData + { + Version = (string)gitInfoType.GetField("SemVer", BindingFlags.Static | BindingFlags.Public)?.GetValue(null), + Branch = (string)gitInfoType.GetField("BranchName", BindingFlags.Static | BindingFlags.Public)?.GetValue(null), + CommitSha = (string)gitInfoType.GetField("Sha", BindingFlags.Static | BindingFlags.Public)?.GetValue(null), + }; + } + + public static string PlattformData { get; } = GenPlattformDat(); + private static string GenPlattformDat() + { + string plattform = null; + string version = ""; + string bitness = Environment.Is64BitProcess ? "64bit" : "32bit"; + + if (IsLinux) + { + try + { + var p = new Process() + { + StartInfo = new ProcessStartInfo() + { + FileName = "bash", + Arguments = "-c \"cat /etc/*[_-]release\"", + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardOutput = true, + } + }; + p.Start(); + p.WaitForExit(100); + + while (p.StandardOutput.Peek() > -1) + { + var infoLine = p.StandardOutput.ReadLine(); + if (string.IsNullOrEmpty(infoLine)) + continue; + var match = PlattformRegex.Match(infoLine); + if (!match.Success) + continue; + + switch (match.Groups[1].Value.ToUpper()) + { + case "DISTRIB_ID": + plattform = match.Groups[2].Value; + break; + case "DISTRIB_RELEASE": + version = match.Groups[2].Value; + break; + } + } + } + catch (Exception) { } + + if (plattform == null) + plattform = "Linux"; + } + else + { + plattform = "Windows"; + version = Environment.OSVersion.Version.ToString(); + } + + return $"{plattform} {version} ({bitness})"; + } + + public static (Runtime Runtime, string FullName, SemVer SemVer) RuntimeData { get; } = GenRuntimeData(); + private static Version GenRuntimeData() + { + var ver = GetNetCoreVersion(); + if (ver.HasValue) + return ver.Value; + + ver = GetMonoVersion(); + if (ver.HasValue) + return ver.Value; + + ver = GetNetFrameworkVersion(); + if (ver.HasValue) + return ver.Value; + + return (Runtime.Unknown, "? (?)", null); + } + + private static Version? GetNetCoreVersion() + { + var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex <= 0 || netCoreAppIndex >= assemblyPath.Length - 2) + return null; + var version = assemblyPath[netCoreAppIndex + 1]; + var semVer = ParseToSemVer(version); + return (Runtime.Core, $".NET Core ({version})", semVer); + } + + private static Version? GetMonoVersion() + { + var type = Type.GetType("Mono.Runtime"); + if (type == null) + return null; + var displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); + if (displayName == null) + return (Runtime.Mono, "Mono (?)", null); + var version = displayName.Invoke(null, null) as string; + var semVer = ParseToSemVer(version); + return (Runtime.Mono, $"Mono ({version})", semVer); + } + + private static Version? GetNetFrameworkVersion() + { + var version = Environment.Version.ToString(); + var semVer = ParseToSemVer(version); + return (Runtime.Net, $".NET Framework {version}", semVer); + } + + private static SemVer ParseToSemVer(string version) + { + var semMatch = SemVerRegex.Match(version); + if (!semMatch.Success) + return null; + + var semVer = new SemVer(); + if (int.TryParse(semMatch.Groups[1].Value, out var major)) semVer.Major = major; + if (int.TryParse(semMatch.Groups[2].Captures[0].Value, out var minor)) semVer.Minor = minor; + if (int.TryParse(semMatch.Groups[2].Captures[1].Value, out var patch)) semVer.Patch = patch; + if (semMatch.Groups[2].Captures.Count > 2 && + int.TryParse(semMatch.Groups[2].Captures[2].Value, out var revision)) semVer.Revision = revision; + else semVer.Revision = null; + return semVer; + } + } + + public enum Runtime + { + Unknown, + Net, + Core, + Mono, + } + + public class BuildData + { + public string Version = ""; + public string Branch = ""; + public string CommitSha = ""; + + public string ToLongString() => $"\nVersion: {Version}\nBranch: {Branch}\nCommitHash: {CommitSha}"; + public override string ToString() => $"{Version}/{Branch}/{(CommitSha.Length > 8 ? CommitSha.Substring(0, 8) : CommitSha)}"; + } + + public class SemVer + { + public int Major { get; set; } + public int Minor { get; set; } + public int Patch { get; set; } + + // Not used in SemVer + public int? Revision { get; set; } + + public override string ToString() => $"{Major}.{Minor}.{Patch}" + (Revision.HasValue ? $".{Revision}" : null); + } +} diff --git a/TS3AudioBot/Helper/ImageUtil.cs b/TS3AudioBot/Helper/ImageUtil.cs index 4e2f8973..026f7550 100644 --- a/TS3AudioBot/Helper/ImageUtil.cs +++ b/TS3AudioBot/Helper/ImageUtil.cs @@ -7,8 +7,10 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . + namespace TS3AudioBot.Helper { + using Environment; using System; using System.Collections.Generic; using System.Drawing; @@ -27,7 +29,7 @@ public static Image BuildStringImage(string str, Image img, int resizeMaxWidth = using (var graphics = Graphics.FromImage(img)) { - if (Util.IsLinux) + if (SystemData.IsLinux) { BuildStringImageLinux(str, graphics, imgRect); } @@ -42,7 +44,7 @@ public static Image BuildStringImage(string str, Image img, int resizeMaxWidth = graphics.TextRenderingHint = TextRenderingHint.AntiAlias; graphics.CompositingQuality = CompositingQuality.HighQuality; - using (Pen AvatarTextOutline = new Pen(Color.Black, 4) {LineJoin = LineJoin.Round}) + using (Pen AvatarTextOutline = new Pen(Color.Black, 4) { LineJoin = LineJoin.Round }) { graphics.DrawPath(AvatarTextOutline, gp); } @@ -131,7 +133,7 @@ private static void BuildStringImageLinux(string str, Graphics target, Rectangle gp.AddString(part, FontFamily.GenericMonospace, 0, 15, buildRect, AvatarTextFormat); bg.Clear(Color.Transparent); - using (Pen AvatarTextOutline = new Pen(Color.Black, 4) {LineJoin = LineJoin.Round}) + using (Pen AvatarTextOutline = new Pen(Color.Black, 4) { LineJoin = LineJoin.Round }) { bg.DrawPath(AvatarTextOutline, gp); } diff --git a/TS3AudioBot/Helper/NativeWinDllLoader.cs b/TS3AudioBot/Helper/NativeWinDllLoader.cs deleted file mode 100644 index 4edf852f..00000000 --- a/TS3AudioBot/Helper/NativeWinDllLoader.cs +++ /dev/null @@ -1,42 +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.Helper -{ - using System; - using System.IO; - using System.Runtime.InteropServices; - - internal static class NativeWinDllLoader - { - [DllImport("kernel32.dll")] - private static extern IntPtr LoadLibrary(string dllToLoad); - - public static void DirectLoadLibrary(string lib) - { - if (Util.IsLinux) - return; - - string libPath = Path.Combine(ArchFolder, lib); - LoadLibrary(libPath); - } - - public static string ArchFolder - { - get - { - if (IntPtr.Size == 8) - return "x64"; - if (IntPtr.Size == 4) - return "x86"; - return "xOther"; - } - } - } -} diff --git a/TS3AudioBot/Helper/Util.cs b/TS3AudioBot/Helper/Util.cs index 105ae76a..e4c2063d 100644 --- a/TS3AudioBot/Helper/Util.cs +++ b/TS3AudioBot/Helper/Util.cs @@ -12,7 +12,6 @@ namespace TS3AudioBot.Helper using CommandSystem; using Newtonsoft.Json.Linq; using System; - using System.Diagnostics; using System.IO; using System.Reflection; using System.Security.Principal; @@ -26,15 +25,6 @@ public static class Util private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); public const RegexOptions DefaultRegexConfig = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ECMAScript; - public static bool IsLinux - { - get - { - int p = (int)Environment.OSVersion.Platform; - return (p == 4) || (p == 6) || (p == 128); - } - } - /// Blocks the thread while the predicate returns false or until the timeout runs out. /// Check function that will be called every millisecond. /// Timeout in millisenconds. @@ -63,8 +53,6 @@ public static void WaitForThreadEnd(Thread thread, TimeSpan timeout) public static Random Random { get; } = new Random(); - //public static JavaScriptSerializer Serializer { get; } = new JavaScriptSerializer(); - public static Encoding Utf8Encoder { get; } = new UTF8Encoding(false, false); public static bool IsAdmin @@ -170,94 +158,6 @@ public static Stream GetEmbeddedFile(string name) return assembly.GetManifestResourceStream(name); } - private static BuildData buildData; - public static BuildData GetAssemblyData() - { - if (buildData == null) - { - var gitInfoType = Assembly.GetExecutingAssembly().GetType("TS3AudioBot.GitVersionInformation"); - if (gitInfoType == null) - { - buildData = new BuildData(); - } - else - { - buildData = new BuildData - { - Version = (string)gitInfoType.GetField("SemVer", BindingFlags.Static | BindingFlags.Public).GetValue(null), - Branch = (string)gitInfoType.GetField("BranchName", BindingFlags.Static | BindingFlags.Public).GetValue(null), - CommitSha = (string)gitInfoType.GetField("Sha", BindingFlags.Static | BindingFlags.Public).GetValue(null), - }; - } - } - return buildData; - } - - public class BuildData - { - public string Version = ""; - public string Branch = ""; - public string CommitSha = ""; - - public string ToLongString() => $"\nVersion: {Version}\nBranch: {Branch}\nCommitHash: {CommitSha}"; - public override string ToString() => $"{Version}/{Branch}/{(CommitSha.Length > 8 ? CommitSha.Substring(0, 8) : CommitSha)}"; - } - - static readonly Regex PlattformRegex = new Regex(@"(\w+)=(.*)", DefaultRegexConfig | RegexOptions.Multiline); - - public static string GetPlattformData() - { - string plattform = null; - string version = ""; - string bitness = Environment.Is64BitProcess ? "64bit" : "32bit"; - - if (IsLinux) - { - try - { - var p = new Process() - { - StartInfo = new ProcessStartInfo() - { - FileName = "bash", - Arguments = "-c \"cat /etc/*[_-]release\"", - CreateNoWindow = true, - UseShellExecute = false, - RedirectStandardOutput = true, - } - }; - p.Start(); - p.WaitForExit(100); - - while (p.StandardOutput.Peek() > -1) - { - var infoLine = p.StandardOutput.ReadLine(); - if (string.IsNullOrEmpty(infoLine)) - continue; - var match = PlattformRegex.Match(infoLine); - if (!match.Success) - continue; - - switch (match.Groups[1].Value.ToUpper()) - { - case "DISTRIB_ID": plattform = match.Groups[2].Value; break; - case "DISTRIB_RELEASE": version = match.Groups[2].Value; break; - } - } - } - catch (Exception) { } - if (plattform == null) - plattform = "Linux"; - } - else - { - plattform = "Windows"; - version = Environment.OSVersion.Version.ToString(); - } - - return $"{plattform} {version} ({bitness})"; - } - public static R TryCast(this JToken token, string key) { var value = token.SelectToken(key); diff --git a/TS3AudioBot/NLog.config b/TS3AudioBot/NLog.config index 4489227c..1bfd4389 100644 --- a/TS3AudioBot/NLog.config +++ b/TS3AudioBot/NLog.config @@ -3,23 +3,25 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" throwConfigExceptions="true"> - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + + + diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index 8ff7e81a..d46b3107 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -112,7 +112,7 @@ - + @@ -212,6 +212,7 @@ PreserveNewest + Designer diff --git a/TS3AudioBot/Ts3Full.cs b/TS3AudioBot/Ts3Full.cs index bd4356a3..abcf2ce9 100644 --- a/TS3AudioBot/Ts3Full.cs +++ b/TS3AudioBot/Ts3Full.cs @@ -9,15 +9,16 @@ namespace TS3AudioBot { + using Audio; using Helper; + using Helper.Environment; using System; - using Audio; using System.Linq; using System.Reflection; using TS3Client; - using TS3Client.Helper; using TS3Client.Full; using TS3Client.Full.Audio; + using TS3Client.Helper; using TS3Client.Messages; internal sealed class Ts3Full : TeamspeakControl, IPlayerConnection @@ -65,7 +66,7 @@ public Ts3Full(Ts3FullClientData tfcd) : base(ClientType.Full) ffmpegProducer = new FfmpegProducer(tfcd); volumePipe = new VolumePipe(); encoderPipe = new EncoderPipe(SendCodec) { Bitrate = ts3FullClientData.AudioBitrate * 1000 }; - timePipe = new PreciseTimedPipe { ReadBufferSize = encoderPipe.OptimalPacketSize }; + timePipe = new PreciseTimedPipe { ReadBufferSize = encoderPipe.PacketSize }; timePipe.Initialize(encoderPipe); TargetPipe = new CustomTargetPipe(tsFullClient); @@ -165,7 +166,7 @@ private void ConnectClient() verionSign = VersionSign.VER_WIN_3_X_X; } } - else if (Util.IsLinux) + else if (SystemData.IsLinux) verionSign = VersionSign.VER_LIN_3_0_19_4; else verionSign = VersionSign.VER_WIN_3_0_19_4; @@ -265,6 +266,8 @@ public override R GetSelf() private void AudioSend() { + // TODO Make a pipe for this + // Save cpu when we know there is noone to send to bool doSend = true; var SendMode = TargetSendMode.None; @@ -297,8 +300,6 @@ private void AudioSend() default: throw new InvalidOperationException(); } - - // Save cpu when we know there is noone to send to } #region IPlayerConnection diff --git a/TS3AudioBot/Web/WebManager.cs b/TS3AudioBot/Web/WebManager.cs index 2a78490c..07e5fca4 100644 --- a/TS3AudioBot/Web/WebManager.cs +++ b/TS3AudioBot/Web/WebManager.cs @@ -10,6 +10,7 @@ namespace TS3AudioBot.Web { using Helper; + using Helper.Environment; using System; using System.Globalization; using System.Net; @@ -59,7 +60,7 @@ private void InitializeSubcomponents() startWebServer = true; } - if(startWebServer) + if (startWebServer) { webListener = new HttpListener { @@ -74,7 +75,7 @@ private void ReloadHostPaths() { localhost = new Uri($"http://localhost:{webData.Port}/"); - if (Util.IsAdmin || Util.IsLinux) // todo: hostlist + if (Util.IsAdmin || SystemData.IsLinux) // todo: hostlist { var addrs = webData.HostAddress.SplitNoEmpty(' '); hostPaths = new Uri[addrs.Length + 1]; diff --git a/TS3Client/Full/Audio/EncoderPipe.cs b/TS3Client/Full/Audio/EncoderPipe.cs index 1333db2f..af3df79e 100644 --- a/TS3Client/Full/Audio/EncoderPipe.cs +++ b/TS3Client/Full/Audio/EncoderPipe.cs @@ -21,15 +21,15 @@ public class EncoderPipe : IAudioPipe, IDisposable, ISampleInfo public int Channels { get; } public int BitsPerSample { get; } - public int OptimalPacketSize { get; } + public int PacketSize { get; } public int Bitrate { get => opusEncoder.Bitrate; set => opusEncoder.Bitrate = value; } // opus private readonly OpusEncoder opusEncoder; private const int SegmentFrames = 960; + // todo add upper limit to buffer size and drop everying over private byte[] soundBuffer = new byte[0]; - private int soundBufferLength; private byte[] notEncodedBuffer = new byte[0]; private int notEncodedBufferLength; private readonly byte[] encodedBuffer; @@ -70,7 +70,7 @@ public EncoderPipe(Codec codec) } BitsPerSample = 16; - OptimalPacketSize = opusEncoder.FrameByteCount(SegmentFrames); + PacketSize = opusEncoder.FrameByteCount(SegmentFrames); encodedBuffer = new byte[opusEncoder.MaxDataBytes]; } @@ -82,14 +82,14 @@ public void Write(Span data, Meta meta) int newSoundBufferLength = data.Length + notEncodedBufferLength; if (newSoundBufferLength > soundBuffer.Length) soundBuffer = new byte[newSoundBufferLength]; - soundBufferLength = newSoundBufferLength; + int soundBufferLength = newSoundBufferLength; + // TODO use buffer swap to save one copy call Array.Copy(notEncodedBuffer, 0, soundBuffer, 0, notEncodedBufferLength); data.CopyTo(new Span(soundBuffer, notEncodedBufferLength)); - - int packetSize = OptimalPacketSize; - int segmentCount = soundBufferLength / packetSize; - int segmentsEnd = segmentCount * packetSize; + + int segmentCount = soundBufferLength / PacketSize; + int segmentsEnd = segmentCount * PacketSize; int newNotEncodedBufferLength = soundBufferLength - segmentsEnd; if (newNotEncodedBufferLength > notEncodedBuffer.Length) notEncodedBuffer = new byte[newNotEncodedBufferLength]; @@ -98,7 +98,7 @@ public void Write(Span data, Meta meta) for (int i = 0; i < segmentCount; i++) { - var encodedData = opusEncoder.Encode(soundBuffer.AsSpan().Slice(i * packetSize, packetSize), packetSize, encodedBuffer); + var encodedData = opusEncoder.Encode(soundBuffer.AsSpan().Slice(i * PacketSize, PacketSize), PacketSize, encodedBuffer); if (meta != null) meta.Codec = Codec; // TODO copy ? OutStream?.Write(encodedData, meta); diff --git a/TS3Client/Helper/NativeWinDllLoader.cs b/TS3Client/Helper/NativeWinDllLoader.cs index a5c9031a..8d700800 100644 --- a/TS3Client/Helper/NativeWinDllLoader.cs +++ b/TS3Client/Helper/NativeWinDllLoader.cs @@ -15,7 +15,9 @@ namespace TS3Client.Helper internal static class NativeWinDllLoader { - [DllImport("kernel32.dll")] + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + + [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LoadLibrary(string dllToLoad); public static void DirectLoadLibrary(string lib) @@ -23,8 +25,11 @@ public static void DirectLoadLibrary(string lib) if (Util.IsLinux) return; - string libPath = Path.Combine(ArchFolder, lib); - LoadLibrary(libPath); + var libPath = Path.Combine(ArchFolder, lib); + Log.Debug("Loading \"{0}\" from \"{1}\"", lib, libPath); + var handle = LoadLibrary(libPath); + if (handle == IntPtr.Zero) + Log.Error("Failed to load library \"{0}\" from \"{1}\", error: {2}", lib, libPath, Marshal.GetLastWin32Error()); } public static string ArchFolder diff --git a/TS3Client/Messages/ResponseDictionary.cs b/TS3Client/Messages/ResponseDictionary.cs index af80c9f4..43acb072 100644 --- a/TS3Client/Messages/ResponseDictionary.cs +++ b/TS3Client/Messages/ResponseDictionary.cs @@ -7,10 +7,9 @@ // You should have received a copy of the Open Software License along with this // program. If not, see . -using TS3Client.Helper; - namespace TS3Client.Messages { + using Helper; using System; using System.Collections; using System.Collections.Generic; From 32a71d310a405d25f234264b4fba4db97617e1a2 Mon Sep 17 00:00:00 2001 From: Splamy Date: Fri, 16 Feb 2018 03:41:54 +0100 Subject: [PATCH 43/48] Implemented dependency injector --- TS3ABotUnitTests/UnitTests.cs | 3 +- TS3AudioBot/Audio/CustomTargetPipe.cs | 2 +- TS3AudioBot/Bot.cs | 61 +++- TS3AudioBot/BotManager.cs | 20 +- TS3AudioBot/CommandSystem/CommandManager.cs | 2 +- TS3AudioBot/Core.cs | 88 ++--- TS3AudioBot/DbStore.cs | 4 +- TS3AudioBot/Dependency/DependencyInjector.cs | 179 ---------- TS3AudioBot/Dependency/DependencyRealm.cs | 132 ++++++++ TS3AudioBot/Dependency/ITabModule.cs | 24 -- TS3AudioBot/Dependency/Module.cs | 73 ++++ TS3AudioBot/Helper/ConfigFile.cs | 4 +- TS3AudioBot/History/HistoryManager.cs | 49 +-- TS3AudioBot/NLog.config | 4 +- TS3AudioBot/PlayManager.cs | 17 +- TS3AudioBot/PlaylistManager.cs | 6 - TS3AudioBot/Plugins/ITabPlugin.cs | 6 + TS3AudioBot/Plugins/PluginManager.cs | 2 +- .../ResourceFactoryManager.cs | 8 +- TS3AudioBot/Rights/RightsManager.cs | 2 +- TS3AudioBot/Sessions/SessionManager.cs | 13 +- TS3AudioBot/TS3AudioBot.csproj | 17 +- TS3AudioBot/TargetScript.cs | 14 +- TS3AudioBot/Web/WebManager.cs | 4 +- TS3AudioBot/packages.config | 6 +- TS3Client/Declarations | 2 +- TS3Client/Full/Ed25519.cs | 311 ++++++++++++++++++ TS3Client/Full/Ts3Crypt.cs | 100 +++++- TS3Client/Full/Ts3FullClient.cs | 20 +- TS3Client/Generated/Versions.cs | 32 +- TS3Client/Generated/Versions.tt | 2 +- TS3Client/TS3Client.csproj | 2 + Ts3ClientTests/Program.cs | 7 + Ts3ClientTests/Ts3ClientTests.csproj | 4 +- 34 files changed, 838 insertions(+), 382 deletions(-) delete mode 100644 TS3AudioBot/Dependency/DependencyInjector.cs create mode 100644 TS3AudioBot/Dependency/DependencyRealm.cs delete mode 100644 TS3AudioBot/Dependency/ITabModule.cs create mode 100644 TS3AudioBot/Dependency/Module.cs create mode 100644 TS3Client/Full/Ed25519.cs diff --git a/TS3ABotUnitTests/UnitTests.cs b/TS3ABotUnitTests/UnitTests.cs index e7f86a81..953a65ad 100644 --- a/TS3ABotUnitTests/UnitTests.cs +++ b/TS3ABotUnitTests/UnitTests.cs @@ -70,7 +70,8 @@ void CreateDbStore() { db = new DbStore() {Config = memcfg}; db.Initialize(); - hf = new HistoryManager(hmf, db); + hf = new HistoryManager(hmf) { Database = db }; + hf.Initialize(); } CreateDbStore(); diff --git a/TS3AudioBot/Audio/CustomTargetPipe.cs b/TS3AudioBot/Audio/CustomTargetPipe.cs index a827222d..18e7affc 100644 --- a/TS3AudioBot/Audio/CustomTargetPipe.cs +++ b/TS3AudioBot/Audio/CustomTargetPipe.cs @@ -45,7 +45,7 @@ public void Write(Span data, Meta meta) { UpdatedSubscriptionCache(); - var codec = meta?.Codec ?? Codec.OpusMusic; // a bit hacky + var codec = meta?.Codec ?? Codec.OpusMusic; // XXX a bit hacky switch (SendMode) { case TargetSendMode.None: diff --git a/TS3AudioBot/Bot.cs b/TS3AudioBot/Bot.cs index 63dd3ee1..ae17c6be 100644 --- a/TS3AudioBot/Bot.cs +++ b/TS3AudioBot/Bot.cs @@ -32,18 +32,22 @@ public sealed class Bot : IDisposable internal object SyncRoot { get; } = new object(); internal bool IsDisposed { get; private set; } - internal DependencyRealm Injector { get; set; } + internal BotInjector Injector { get; set; } internal TargetScript TargetScript { get; set; } /// Mangement for playlists. - public PlaylistManager PlaylistManager { get; private set; } + public PlaylistManager PlaylistManager { get; set; } /// Connection object for the current client. - public TeamspeakControl QueryConnection { get; private set; } + public TeamspeakControl QueryConnection { get; set; } /// Management for clients talking with the bot. - public SessionManager SessionManager { get; private set; } + public SessionManager SessionManager { get; set; } private HistoryManager historyManager = null; /// Stores all played songs. Can be used to search and restore played songs. - public HistoryManager HistoryManager => historyManager ?? throw new CommandException("History has not been enabled", CommandExceptionReason.NotSupported); + public HistoryManager HistoryManager + { + get => historyManager ?? throw new CommandException("History has not been enabled", CommandExceptionReason.NotSupported); + set => historyManager = value; + } /// Redirects playing, enqueing and song events. public PlayManager PlayManager { get; private set; } /// Used to specify playing mode and active targets to send to. @@ -59,12 +63,12 @@ public Bot(Core core) this.core = core; } - public bool InitializeBot() + public R InitializeBot() { Log.Info("Bot connecting..."); // Read Config File - var conf = core.ConfigManager; + var conf = Injector.GetModule().Value; // XXX var afd = conf.GetDataStruct("AudioFramework", true); var tfcd = conf.GetDataStruct("QueryConnection", true); var hmd = conf.GetDataStruct("HistoryManager", true); @@ -72,16 +76,41 @@ public bool InitializeBot() mainBotData = conf.GetDataStruct("MainBot", true); AudioValues.audioFrameworkData = afd; + + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + + Injector.RegisterModule(this); + Injector.RegisterModule(Injector); + Injector.RegisterModule(new PlaylistManager(pld)); var teamspeakClient = new Ts3Full(tfcd); - QueryConnection = teamspeakClient; - PlayerConnection = teamspeakClient; - PlaylistManager = new PlaylistManager(pld); - SessionManager = new SessionManager(core.Database); + Injector.RegisterModule(teamspeakClient); + Injector.RegisterModule(new SessionManager(), x => x.Initialize()); if (hmd.EnableHistory) - historyManager = new HistoryManager(hmd, core.Database); - PlayManager = new PlayManager(core, this); + Injector.RegisterModule(new HistoryManager(hmd), x => x.Initialize()); + Injector.RegisterModule(new PlayManager()); + Injector.RegisterModule(new TargetScript()); + TargetManager = teamspeakClient.TargetPipe; - TargetScript = new TargetScript(core, this); + + if (!Injector.AllResolved()) + { + // TODO detailed log + for inner if + Log.Warn("Cyclic bot module dependency"); + Injector.ForceCyclicResolve(); + if (!Injector.AllResolved()) + { + Log.Error("Missing bot module dependency"); + return "Could not load all bot modules"; + } + } PlayerConnection.OnSongEnd += PlayManager.SongStoppedHook; PlayManager.BeforeResourceStarted += TargetScript.BeforeResourceStarted; @@ -107,9 +136,9 @@ public bool InitializeBot() catch (Ts3Exception qcex) { Log.Info(qcex, "There is either a problem with your connection configuration, or the query has not all permissions it needs."); - return false; + return "Query error"; } - return true; + return R.OkR; } private void OnBotConnected(object sender, EventArgs e) diff --git a/TS3AudioBot/BotManager.cs b/TS3AudioBot/BotManager.cs index f110d5e8..c2d34ec0 100644 --- a/TS3AudioBot/BotManager.cs +++ b/TS3AudioBot/BotManager.cs @@ -9,18 +9,21 @@ namespace TS3AudioBot { + using Dependency; using Helper; using System; using System.Collections.Generic; using System.Threading; - public class BotManager : Dependency.ICoreModule, IDisposable + public class BotManager : IDisposable { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + private bool isRunning; - public Core Core { get; set; } private List activeBots; private readonly object lockObj = new object(); + + public CoreInjector CoreInjector { get; set; } public BotManager() { @@ -28,8 +31,6 @@ public BotManager() Util.Init(out activeBots); } - public void Initialize() { } - public void WatchBots() { while (isRunning) @@ -46,7 +47,7 @@ public void WatchBots() } CleanStrayBots(); - Thread.Sleep(200); + Thread.Sleep(1000); } } @@ -75,7 +76,12 @@ private void CleanStrayBots() public bool CreateBot(/*Ts3FullClientData bot*/) { bool removeBot = false; - var bot = new Bot(Core); + var core = CoreInjector.GetModule().Value; // XXX + var bot = new Bot(core) + { + Injector = CoreInjector.CloneRealm() + }; + lock (bot.SyncRoot) { if (bot.InitializeBot()) @@ -145,7 +151,7 @@ public void Dispose() public class BotLock : IDisposable { - private Bot bot; + private readonly Bot bot; public bool IsValid { get; private set; } public Bot Bot => IsValid ? bot : throw new InvalidOperationException("The bot lock is not valid."); diff --git a/TS3AudioBot/CommandSystem/CommandManager.cs b/TS3AudioBot/CommandSystem/CommandManager.cs index 995cafda..e9ada556 100644 --- a/TS3AudioBot/CommandSystem/CommandManager.cs +++ b/TS3AudioBot/CommandSystem/CommandManager.cs @@ -16,7 +16,7 @@ namespace TS3AudioBot.CommandSystem using System.Reflection; using System.Text.RegularExpressions; - public class CommandManager : Dependency.ICoreModule + public class CommandManager { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private static readonly Regex CommandNamespaceValidator = diff --git a/TS3AudioBot/Core.cs b/TS3AudioBot/Core.cs index b0772a0f..d73509a0 100644 --- a/TS3AudioBot/Core.cs +++ b/TS3AudioBot/Core.cs @@ -22,10 +22,11 @@ namespace TS3AudioBot using System.Threading; using Web; - public sealed class Core : IDisposable, ICoreModule + public sealed class Core : IDisposable { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private string configFilePath; + private bool forceNextExit; internal static void Main(string[] args) { @@ -54,30 +55,8 @@ internal static void Main(string[] args) } var core = new Core(); - - AppDomain.CurrentDomain.UnhandledException += (s, e) => - { - Log.Fatal(e.ExceptionObject as Exception, "Critical program failure!."); - core.Dispose(); - }; - - bool forceNextExit = false; - Console.CancelKeyPress += (s, e) => - { - if (e.SpecialKey == ConsoleSpecialKey.ControlC) - { - if (!forceNextExit) - { - e.Cancel = true; - core.Dispose(); - forceNextExit = true; - } - else - { - Environment.Exit(0); - } - } - }; + AppDomain.CurrentDomain.UnhandledException += core.ExceptionHandler; + Console.CancelKeyPress += core.ConsoleInterruptHandler; if (!core.ReadParameter(args)) return; @@ -155,8 +134,6 @@ private bool ReadParameter(string[] args) return true; } - public void Initialize() { } - private R InitializeCore() { ConfigManager = ConfigFile.OpenOrCreate(configFilePath) ?? ConfigFile.CreateDummy(); @@ -202,28 +179,38 @@ private R InitializeCore() TS3Client.Messages.Deserializer.OnError += (s, e) => Log.Error(e.ToString()); Injector = new CoreInjector(); + + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterType(); + Injector.RegisterModule(this); Injector.RegisterModule(ConfigManager); Injector.RegisterModule(Injector); - Database = Injector.Create(); - PluginManager = Injector.Create(); - CommandManager = Injector.Create(); - FactoryManager = Injector.Create(); - WebManager = Injector.Create(); - RightsManager = Injector.Create(); - Bots = Injector.Create(); - - Injector.SkipInitialized(this); + Injector.RegisterModule(new DbStore(), x => x.Initialize()); + Injector.RegisterModule(new PluginManager(), x => x.Initialize()); + Injector.RegisterModule(new CommandManager(), x => x.Initialize()); + Injector.RegisterModule(new ResourceFactoryManager(), x => x.Initialize()); + Injector.RegisterModule(new WebManager(), x => x.Initialize()); + Injector.RegisterModule(new RightsManager(), x => x.Initialize()); + Injector.RegisterModule(new BotManager()); if (!Injector.AllResolved()) { // TODO detailed log + for inner if - Log.Warn("Cyclic module dependency"); + Log.Debug("Cyclic core module dependency"); Injector.ForceCyclicResolve(); if (!Injector.AllResolved()) { - Log.Error("Missing module dependency"); - return "Could not load all modules"; + Log.Error("Missing core module dependency"); + return "Could not load all core modules"; } } @@ -236,6 +223,29 @@ private void Run() Bots.WatchBots(); } + public void ExceptionHandler(object sender, UnhandledExceptionEventArgs e) + { + Log.Fatal(e.ExceptionObject as Exception, "Critical program failure!."); + Dispose(); + } + + public void ConsoleInterruptHandler(object sender, ConsoleCancelEventArgs e) + { + if (e.SpecialKey == ConsoleSpecialKey.ControlC) + { + if (!forceNextExit) + { + e.Cancel = true; + forceNextExit = true; + Dispose(); + } + else + { + Environment.Exit(0); + } + } + } + public void Dispose() { Log.Info("TS3AudioBot shutting down."); diff --git a/TS3AudioBot/DbStore.cs b/TS3AudioBot/DbStore.cs index 194ff993..70065c56 100644 --- a/TS3AudioBot/DbStore.cs +++ b/TS3AudioBot/DbStore.cs @@ -14,7 +14,7 @@ namespace TS3AudioBot using System; using System.IO; - public class DbStore : Dependency.ICoreModule, IDisposable + public class DbStore : IDisposable { private const string DbMetaInformationTable = "dbmeta"; @@ -23,8 +23,6 @@ public class DbStore : Dependency.ICoreModule, IDisposable private LiteDatabase database; private LiteCollection metaTable; - public DbStore() { } - public void Initialize() { var hmd = Config.GetDataStruct("HistoryManager", true); diff --git a/TS3AudioBot/Dependency/DependencyInjector.cs b/TS3AudioBot/Dependency/DependencyInjector.cs deleted file mode 100644 index 143160b9..00000000 --- a/TS3AudioBot/Dependency/DependencyInjector.cs +++ /dev/null @@ -1,179 +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.Dependency -{ - using Helper; - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - - public sealed class CoreInjector : DependencyRealm { } - - public class Module - { - private static readonly ConcurrentDictionary TypeData = new ConcurrentDictionary(); - - public bool IsInitialized { get; set; } - public object Obj { get; } - public Type BaseType { get; } - // object SyncContext; - - public Module(object obj, Type baseType) - { - IsInitialized = false; - Obj = obj; - BaseType = baseType; - } - - public Type[] GetDependants() => GetDependants(Obj.GetType()); - - private static Type[] GetDependants(Type type) - { - if (!TypeData.TryGetValue(type, out var depArr)) - { - depArr = GetModuleProperties(type).Select(p => p.PropertyType).ToArray(); - TypeData[type] = depArr; - } - return depArr; - } - - public IEnumerable GetModuleProperties() => GetModuleProperties(Obj.GetType()); - - private static IEnumerable GetModuleProperties(IReflect type) => type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(p => p.CanRead && p.CanWrite && typeof(ITabModule).IsAssignableFrom(p.PropertyType)); - } - - public class DependencyRealm where TRealm : ITabModule - { - private readonly ConcurrentDictionary loaded; - private readonly List waiting; - - public DependencyRealm() - { - Util.Init(out loaded); - Util.Init(out waiting); - } - - public TModule Create() where TModule : TRealm => (TModule)CreateFromType(typeof(TModule)); - public object CreateFromType(Type type) - { - var obj = Activator.CreateInstance(type); - RegisterInjectable(obj, false); - return obj; - } - public void RegisterModule(TModule obj, bool initialized = false) => RegisterInjectable(obj, initialized, typeof(TModule)); - public void RegisterInjectable(object obj, bool initialized = false, Type baseType = null) - { - var modType = baseType ?? obj.GetType(); - var mod = new Module(obj, modType) { IsInitialized = initialized }; - if (initialized) - SetInitalized(mod); - else - waiting.Add(mod); - DoQueueInitialize(); - } - - public void SkipInitialized(object obj) - { - var (mod, idx) = waiting.Select((m, i) => (mod: m, idx: i)).FirstOrDefault(t => t.mod.Obj == obj); - if (mod == null) - return; - - waiting.RemoveAt(idx); - SetInitalized(mod); - - DoQueueInitialize(); - } - - public void ForceCyclicResolve() - { - // TODO - } - - private bool SetInitalized(Module module) - { - if (!module.IsInitialized && module.Obj is ITabModule tabModule) - { - tabModule.Initialize(); - } - module.IsInitialized = true; - loaded[module.BaseType] = module; - return true; - } - - private void DoQueueInitialize() - { - bool changed; - do - { - changed = false; - for (int i = 0; i < waiting.Count; i++) - { - var mod = waiting[i]; - if (IsResolvable(mod)) - { - if (!DoResolve(mod)) - { - // TODO warn - continue; - } - changed = true; - waiting.RemoveAt(i); - i--; - } - } - } while (changed); - } - - private bool IsResolvable(Module module) - { - var deps = module.GetDependants(); - foreach (var depeningType in deps) - { - if (!loaded.ContainsKey(depeningType)) // todo maybe to some linear inheritance checking - return false; - } - return true; - } - - private bool DoResolve(Module module) - { - var props = module.GetModuleProperties(); - foreach (var prop in props) - { - if (loaded.TryGetValue(prop.PropertyType, out var depModule)) - { - prop.SetValue(module.Obj, depModule.Obj); - } - else - { - return false; - } - } - return SetInitalized(module); - } - - public R GetModule() where TModule : TRealm - { - if (loaded.TryGetValue(typeof(TModule), out var mod)) - return (TModule)mod.Obj; - return "Module not found"; - } - - public bool AllResolved() => waiting.Count == 0; - - public void Unregister(Type type) - { - throw new NotImplementedException(); - } - } -} diff --git a/TS3AudioBot/Dependency/DependencyRealm.cs b/TS3AudioBot/Dependency/DependencyRealm.cs new file mode 100644 index 00000000..bc5b3d1e --- /dev/null +++ b/TS3AudioBot/Dependency/DependencyRealm.cs @@ -0,0 +1,132 @@ +// 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.Dependency +{ + using Helper; + using System; + using System.Collections.Generic; + using System.Linq; + + public sealed class CoreInjector : DependencyRealm { } + public sealed class BotInjector : DependencyRealm { } + + public class DependencyRealm + { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); + + private readonly HashSet registeredTypes; + private readonly List modules; + + public DependencyRealm() + { + Util.Init(out registeredTypes); + Util.Init(out modules); + } + + public void RegisterType() => RegisterType(typeof(TModule)); + private void RegisterType(Type modType) + { + if (registeredTypes.Contains(modType)) + return; + registeredTypes.Add(modType); + } + + public void RegisterModule(TMod module, Action onInit = null) where TMod : class + { + var onInitObject = onInit != null ? new Action(x => onInit((TMod)x)) : null; + var mod = new Module(module, onInitObject); + modules.Add(mod); + DoQueueInitialize(false); + } + + // Maybe in future update child realm when parent gets updated + public T CloneRealm() where T : DependencyRealm, new() + { + var child = new T(); + child.modules.AddRange(modules); + child.registeredTypes.UnionWith(registeredTypes); + return child; + } + + public void ForceCyclicResolve() + { + DoQueueInitialize(true); + } + + private void DoQueueInitialize(bool force) + { + bool changed; + do + { + changed = false; + foreach (var mod in modules) + { + if (mod.Status == InitState.Done) + continue; + + if (!TryResolve(mod, force)) + continue; + changed = true; + } + } while (changed); + } + + private IEnumerable GetUnresolvedResolvable(Module module) + => module.GetDependants(registeredTypes).Where(dep => FindInjectableModule(dep, module.Status, false) == null); + + private Module FindInjectableModule(Type type, InitState state, bool force) + => modules.FirstOrDefault( + x => (x.Status == InitState.Done || x.Status == InitState.SetOnly && state == InitState.SetOnly || force) && type.IsAssignableFrom(x.Type)); + + private bool TryResolve(Module module, bool force) + { + var props = module.GetModuleProperties(registeredTypes); + foreach (var prop in props) + { + var depModule = FindInjectableModule(prop.PropertyType, module.Status, force); + if (depModule != null) + { + prop.SetValue(module.Obj, depModule.Obj); + } + else + { + Log.ConditionalTrace("Module {0} waiting for {1}", module, string.Join(", ", GetUnresolvedResolvable(module).Select(x => x.Name))); + return false; + } + } + module.SetInitalized(); + Log.ConditionalTrace("Module {0} added", module); + return true; + } + + public R GetModule() where TModule : class + { + var mod = FindInjectableModule(typeof(TModule), InitState.Done, false); + if (mod != null) + return (TModule)mod.Obj; + return "Module not found"; + } + + public bool AllResolved() => modules.All(x => x.Status == InitState.Done); + + public void Unregister(Type type) + { + throw new NotImplementedException(); + } + + public override string ToString() + { + int done = modules.Count(x => x.Status == InitState.Done); + int set = modules.Count(x => x.Status == InitState.SetOnly); + int setinit = modules.Count(x => x.Status == InitState.SetAndInit); + return $"Done: {done} Set: {set} SetInit: {setinit}"; + } + } +} diff --git a/TS3AudioBot/Dependency/ITabModule.cs b/TS3AudioBot/Dependency/ITabModule.cs deleted file mode 100644 index e3d14ffe..00000000 --- a/TS3AudioBot/Dependency/ITabModule.cs +++ /dev/null @@ -1,24 +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.Dependency -{ - public interface ITabModule - { - void Initialize(); - } - - public interface ICoreModule : ITabModule - { - } - - public interface IBotModule : ICoreModule - { - } -} diff --git a/TS3AudioBot/Dependency/Module.cs b/TS3AudioBot/Dependency/Module.cs new file mode 100644 index 00000000..7aca6443 --- /dev/null +++ b/TS3AudioBot/Dependency/Module.cs @@ -0,0 +1,73 @@ +// 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.Dependency +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + + internal class Module + { + public InitState Status { get; private set; } + public object Obj { get; } + public Type Type => Obj.GetType(); + public Action Initializer { get; } + // object SyncContext; + + public Module(object obj, Action initializer) + { + Status = initializer == null ? InitState.SetOnly : InitState.SetAndInit; + Initializer = initializer; + Obj = obj; + } + + public IEnumerable GetDependants(HashSet deps) + { + var type = Obj.GetType(); + return GetModuleProperties(deps, type).Select(p => p.PropertyType); + } + + public IEnumerable GetModuleProperties(HashSet deps) => GetModuleProperties(deps, Obj.GetType()); + + private static IEnumerable GetModuleProperties(HashSet deps, IReflect type) => + type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(p => p.CanRead && p.CanWrite && deps.Any(x => x.IsAssignableFrom(p.PropertyType))); + + public void SetInitalized() + { + if (Status == InitState.SetAndInit) + Initializer?.Invoke(Obj); + Status = InitState.Done; + } + + public override string ToString() + { + var strb = new StringBuilder(); + strb.Append(Type.Name); + switch (Status) + { + case InitState.Done: strb.Append("+"); break; + case InitState.SetOnly: strb.Append("*"); break; + case InitState.SetAndInit: strb.Append("-"); break; + default: throw new ArgumentOutOfRangeException(); + } + return strb.ToString(); + } + } + + internal enum InitState + { + Done, + SetOnly, + SetAndInit, + } +} diff --git a/TS3AudioBot/Helper/ConfigFile.cs b/TS3AudioBot/Helper/ConfigFile.cs index 454df5f1..c0c4fe40 100644 --- a/TS3AudioBot/Helper/ConfigFile.cs +++ b/TS3AudioBot/Helper/ConfigFile.cs @@ -18,7 +18,7 @@ namespace TS3AudioBot.Helper using System.Reflection; using System.Linq; - public abstract class ConfigFile : Dependency.ICoreModule + public abstract class ConfigFile { private const char SplitChar = '='; private static readonly char[] SplitCharArr = { SplitChar }; @@ -28,8 +28,6 @@ public abstract class ConfigFile : Dependency.ICoreModule private bool changed; private readonly Dictionary confObjects; - public void Initialize() { } - protected ConfigFile() { Util.Init(out confObjects); diff --git a/TS3AudioBot/History/HistoryManager.cs b/TS3AudioBot/History/HistoryManager.cs index 690684bb..905a97a7 100644 --- a/TS3AudioBot/History/HistoryManager.cs +++ b/TS3AudioBot/History/HistoryManager.cs @@ -23,7 +23,7 @@ public sealed class HistoryManager private const string AudioLogEntriesTable = "audioLogEntries"; private const string ResourceTitleQueryColumn = "lowTitle"; - private readonly LiteCollection audioLogEntries; + private LiteCollection audioLogEntries; private readonly HistoryManagerData historyManagerData; private readonly LinkedList unusedIds; private readonly object dbLock = new object(); @@ -31,20 +31,25 @@ public sealed class HistoryManager public IHistoryFormatter Formatter { get; private set; } public uint HighestId => (uint)audioLogEntries.Max().AsInt32; + public DbStore Database { get; set; } + static HistoryManager() { BsonMapper.Global.Entity() .Id(x => x.Id); } - public HistoryManager(HistoryManagerData hmd, DbStore database) + public HistoryManager(HistoryManagerData hmd) { Formatter = new SmartHistoryFormatter(); historyManagerData = hmd; Util.Init(out unusedIds); + } - audioLogEntries = database.GetCollection(AudioLogEntriesTable); + public void Initialize() + { + audioLogEntries = Database.GetCollection(AudioLogEntriesTable); audioLogEntries.EnsureIndex(x => x.AudioResource.UniqueId, true); audioLogEntries.EnsureIndex(x => x.Timestamp); audioLogEntries.EnsureIndex(ResourceTitleQueryColumn, @@ -54,32 +59,32 @@ public HistoryManager(HistoryManagerData hmd, DbStore database) // Content upgrade - var meta = database.GetMetaData(AudioLogEntriesTable); + var meta = Database.GetMetaData(AudioLogEntriesTable); if (meta.Version >= CurrentHistoryVersion) return; switch (meta.Version) { - case 0: - var all = audioLogEntries.FindAll().ToArray(); - foreach (var audioLogEntry in all) - { - switch (audioLogEntry.AudioResource.AudioType) + case 0: + var all = audioLogEntries.FindAll().ToArray(); + foreach (var audioLogEntry in all) { - case "MediaLink": audioLogEntry.AudioResource.AudioType = "media"; break; - case "Youtube": audioLogEntry.AudioResource.AudioType = "youtube"; break; - case "Soundcloud": audioLogEntry.AudioResource.AudioType = "soundcloud"; break; - case "Twitch": audioLogEntry.AudioResource.AudioType = "twitch"; break; + switch (audioLogEntry.AudioResource.AudioType) + { + case "MediaLink": audioLogEntry.AudioResource.AudioType = "media"; break; + case "Youtube": audioLogEntry.AudioResource.AudioType = "youtube"; break; + case "Soundcloud": audioLogEntry.AudioResource.AudioType = "soundcloud"; break; + case "Twitch": audioLogEntry.AudioResource.AudioType = "twitch"; break; + } } - } - audioLogEntries.Update(all); - meta.Version = 1; - database.UpdateMetaData(meta); - goto default; - - default: - Log.Info("Database table \"{0}\" upgraded to {1}", AudioLogEntriesTable, meta.Version); - break; + audioLogEntries.Update(all); + meta.Version = 1; + Database.UpdateMetaData(meta); + goto default; + + default: + Log.Info("Database table \"{0}\" upgraded to {1}", AudioLogEntriesTable, meta.Version); + break; } } diff --git a/TS3AudioBot/NLog.config b/TS3AudioBot/NLog.config index 1bfd4389..618fedb3 100644 --- a/TS3AudioBot/NLog.config +++ b/TS3AudioBot/NLog.config @@ -21,7 +21,7 @@ - - + + diff --git a/TS3AudioBot/PlayManager.cs b/TS3AudioBot/PlayManager.cs index bc577827..2661509f 100644 --- a/TS3AudioBot/PlayManager.cs +++ b/TS3AudioBot/PlayManager.cs @@ -18,12 +18,11 @@ namespace TS3AudioBot public class PlayManager { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); - private readonly Core core; - private readonly Bot botParent; - private IPlayerConnection PlayerConnection => botParent.PlayerConnection; - private PlaylistManager PlaylistManager => botParent.PlaylistManager; - private ResourceFactoryManager ResourceFactoryManager => core.FactoryManager; - private HistoryManager HistoryManager => botParent.HistoryManager; + + public IPlayerConnection PlayerConnection { get; set; } + public PlaylistManager PlaylistManager { get; set; } + public HistoryManager HistoryManager { get; set; } + public ResourceFactoryManager ResourceFactoryManager { get; set; } public PlayInfoEventArgs CurrentPlayData { get; private set; } public bool IsPlaying => CurrentPlayData != null; @@ -33,12 +32,6 @@ public class PlayManager public event EventHandler BeforeResourceStopped; public event EventHandler AfterResourceStopped; - public PlayManager(Core core, Bot parent) - { - this.core = core; - botParent = parent; - } - public R Enqueue(InvokerData invoker, AudioResource ar) => EnqueueInternal(invoker, new PlaylistItem(ar)); public R Enqueue(InvokerData invoker, string message, string audioType = null) { diff --git a/TS3AudioBot/PlaylistManager.cs b/TS3AudioBot/PlaylistManager.cs index d11479bd..5fd693ff 100644 --- a/TS3AudioBot/PlaylistManager.cs +++ b/TS3AudioBot/PlaylistManager.cs @@ -25,12 +25,6 @@ public sealed class PlaylistManager private static readonly Regex ValidPlistName = new Regex(@"^[\w-]+$", Util.DefaultRegexConfig); private static readonly Regex CleansePlaylistName = new Regex(@"[^\w-]", Util.DefaultRegexConfig); - // get video info - // https://www.googleapis.com/youtube/v3/videos?id=...,...&part=contentDetails&key=... - - // get playlist videos - // https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails&maxResults=50&playlistId=...&key=... - private readonly PlaylistManagerData data; private static readonly Encoding FileEncoding = Util.Utf8Encoder; private readonly Playlist freeList; diff --git a/TS3AudioBot/Plugins/ITabPlugin.cs b/TS3AudioBot/Plugins/ITabPlugin.cs index 69e75851..03dac156 100644 --- a/TS3AudioBot/Plugins/ITabPlugin.cs +++ b/TS3AudioBot/Plugins/ITabPlugin.cs @@ -15,4 +15,10 @@ public interface ITabPlugin : IDisposable { void Initialize(Core core); } + + // TODO + + public interface ICoreModule { } + + public interface IBotModule { } } diff --git a/TS3AudioBot/Plugins/PluginManager.cs b/TS3AudioBot/Plugins/PluginManager.cs index 7697eeb6..cb0f5d49 100644 --- a/TS3AudioBot/Plugins/PluginManager.cs +++ b/TS3AudioBot/Plugins/PluginManager.cs @@ -30,7 +30,7 @@ namespace TS3AudioBot.Plugins // - Add commands to command manager // - Start config to system? - internal class PluginManager : IDisposable, Dependency.ICoreModule + internal class PluginManager : IDisposable { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); public Core Core { get; set; } diff --git a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs index 0bb3066f..87c64e64 100644 --- a/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs +++ b/TS3AudioBot/ResourceFactories/ResourceFactoryManager.cs @@ -17,8 +17,9 @@ namespace TS3AudioBot.ResourceFactories using System.Linq; using System.Reflection; - public sealed class ResourceFactoryManager : Dependency.ICoreModule, IDisposable + public sealed class ResourceFactoryManager : IDisposable { + private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private const string CmdResPrepath = "from "; private const string CmdListPrepath = "list from "; @@ -37,7 +38,7 @@ public ResourceFactoryManager() Util.Init(out listFactories); } - void Dependency.ITabModule.Initialize() + public void Initialize() { var yfd = Config.GetDataStruct("YoutubeFactory", true); var mfd = Config.GetDataStruct("MediaFactory", true); @@ -76,7 +77,7 @@ orderby facCertain descending private static IEnumerable FilterUsable(IEnumerable<(T, MatchCertainty)> enu) { - MatchCertainty highestCertainty = MatchCertainty.Never; + var highestCertainty = MatchCertainty.Never; foreach (var (fac, cert) in enu) { if ((highestCertainty == MatchCertainty.Always && cert < MatchCertainty.Always) @@ -140,6 +141,7 @@ public R Load(string message, string audioType = null) foreach (var factory in factories) { var result = factory.GetResource(netlinkurl); + Log.Trace("Factory {0} tried, result: {1}", factory.FactoryFor, result.Ok ? "Ok" : result.Error); if (result) return result; } diff --git a/TS3AudioBot/Rights/RightsManager.cs b/TS3AudioBot/Rights/RightsManager.cs index 90823d60..2171eaa0 100644 --- a/TS3AudioBot/Rights/RightsManager.cs +++ b/TS3AudioBot/Rights/RightsManager.cs @@ -19,7 +19,7 @@ namespace TS3AudioBot.Rights using System.Text; using TS3Client; - public class RightsManager : Dependency.ICoreModule + public class RightsManager { private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private const int RuleLevelSize = 2; diff --git a/TS3AudioBot/Sessions/SessionManager.cs b/TS3AudioBot/Sessions/SessionManager.cs index a29f8832..2adf274c 100644 --- a/TS3AudioBot/Sessions/SessionManager.cs +++ b/TS3AudioBot/Sessions/SessionManager.cs @@ -20,24 +20,29 @@ public class SessionManager private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger(); private const string TokenFormat = "{0}:" + Web.WebManager.WebRealm + ":{1}"; + public DbStore Database { get; set; } + // Map: Id => UserSession private readonly Dictionary openSessions; // Map: Uid => InvokerData private const string ApiTokenTable = "apiToken"; - private readonly LiteCollection dbTokenList; + private LiteCollection dbTokenList; private readonly Dictionary liveTokenList; - public SessionManager(DbStore database) + public SessionManager() { Util.Init(out openSessions); Util.Init(out liveTokenList); + } - dbTokenList = database.GetCollection(ApiTokenTable); + public void Initialize() + { + dbTokenList = Database.GetCollection(ApiTokenTable); dbTokenList.EnsureIndex(x => x.UserUid, true); dbTokenList.EnsureIndex(x => x.Token, true); - database.GetMetaData(ApiTokenTable); + Database.GetMetaData(ApiTokenTable); } public UserSession CreateSession(Bot bot, ClientData client) diff --git a/TS3AudioBot/TS3AudioBot.csproj b/TS3AudioBot/TS3AudioBot.csproj index d46b3107..d23524aa 100644 --- a/TS3AudioBot/TS3AudioBot.csproj +++ b/TS3AudioBot/TS3AudioBot.csproj @@ -58,8 +58,8 @@ - - ..\packages\LiteDB.4.1.0\lib\net40\LiteDB.dll + + ..\packages\LiteDB.4.1.1\lib\net40\LiteDB.dll ..\packages\Nett.0.8.0\lib\Net40\Nett.dll @@ -70,9 +70,8 @@ ..\packages\NLog.4.4.12\lib\net45\NLog.dll - - ..\packages\PropertyChanged.Fody.2.1.4\lib\netstandard1.0\PropertyChanged.dll - False + + ..\packages\PropertyChanged.Fody.2.2.5\lib\net452\PropertyChanged.dll @@ -107,8 +106,8 @@ - - + + @@ -283,7 +282,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - +