From 3432e3fde3fb7b23ca7a33edb47e512947a367be Mon Sep 17 00:00:00 2001 From: appelemac Date: Sun, 21 Aug 2016 19:31:13 +0200 Subject: [PATCH 1/9] calculate --- .../Modules/Searches/Commands/CalcCommand.cs | 30 ++ src/NadekoBot/Modules/Searches/Searches.cs | 9 + src/NadekoBot/NadekoBot.cs | 2 +- src/NadekoBot/project.json | 7 +- src/NadekoBot/project.lock.json | 269 +++++++++++++++++- 5 files changed, 311 insertions(+), 6 deletions(-) create mode 100644 src/NadekoBot/Modules/Searches/Commands/CalcCommand.cs diff --git a/src/NadekoBot/Modules/Searches/Commands/CalcCommand.cs b/src/NadekoBot/Modules/Searches/Commands/CalcCommand.cs new file mode 100644 index 00000000..e5d51d8f --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/CalcCommand.cs @@ -0,0 +1,30 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches.Commands +{ + public partial class Searches + { + public static async Task Calc(IMessage msg, [Remainder] string expression) + { + var expr = new NCalc.Expression(expression); + //expr.EvaluateParameter += delegate (string name, NCalc.ParameterArgs args) + //{ + // if (name.ToLowerInvariant() == "pi") args.Result = Math.PI; + //}; + var result = expr.Evaluate(); + + await msg.Reply(string.Format("Your expression evaluated to: {0}", expr.Error ?? result)); + } + } + class ExpressionContext + { + public double Pi { get; set; } = Math.PI; + } + +} diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 8854093b..e423207c 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -13,6 +13,7 @@ using NadekoBot.Extensions; using System.Text.RegularExpressions; using System.Net; using NadekoBot.Modules.Searches.Commands.Models; +using NCalc; namespace NadekoBot.Modules.Searches { @@ -26,6 +27,14 @@ namespace NadekoBot.Modules.Searches _yt = youtube; } + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Calc(IMessage msg, [Remainder] string calculation) + { + var channel = msg.Channel as ITextChannel; + + } + [LocalizedCommand, LocalizedDescription, LocalizedSummary] [RequireContext(ContextType.Guild)] public async Task Weather(IMessage imsg, string city, string country) diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index 42b0c927..9acc34ac 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -29,7 +29,7 @@ namespace NadekoBot public async Task RunAsync(string[] args) { SetupLogger(); - + _log.Debug("Logger created, starting client"); //create client Client = new DiscordSocketClient(new DiscordSocketConfig { diff --git a/src/NadekoBot/project.json b/src/NadekoBot/project.json index e72bf3c9..adb85ef6 100644 --- a/src/NadekoBot/project.json +++ b/src/NadekoBot/project.json @@ -18,12 +18,13 @@ "Microsoft.Extensions.PlatformAbstractions": "1.0.0", "Newtonsoft.Json": "9.0.1", "Microsoft.Extensions.DependencyInjection": "1.0.0", - "Discord.Net": "1.0.0-dev", - "Discord.Net.Commands": "1.0.0-dev", + "Discord.Net.Commands": "1.0.0-dev-*", "System.Resources.ResourceWriter": "4.0.0-beta-22816", "Google.Apis.YouTube.v3": "1.15.0.582", "System.Diagnostics.Contracts": "4.0.1", - "NLog": "4.4.0-betaV15" + "NLog": "4.4.0-betaV15", + "Discord.Net": "1.0.0-dev-*", + "CoreCLR-NCalc": "2.1.0" }, "frameworks": { diff --git a/src/NadekoBot/project.lock.json b/src/NadekoBot/project.lock.json index 316e9f09..f056f4bd 100644 --- a/src/NadekoBot/project.lock.json +++ b/src/NadekoBot/project.lock.json @@ -3,6 +3,26 @@ "version": 2, "targets": { ".NETCoreApp,Version=v1.0": { + "CoreCLR-NCalc/2.1.0": { + "type": "package", + "dependencies": { + "NETStandard.Library": "1.6.0", + "System.Diagnostics.Debug": "4.0.11", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Serialization.Xml": "4.1.1", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading.Thread": "4.0.0" + }, + "compile": { + "lib/netstandard1.3/NCalc.dll": {} + }, + "runtime": { + "lib/netstandard1.3/NCalc.dll": {} + } + }, "Google.Apis/1.15.0": { "type": "package", "dependencies": { @@ -1626,6 +1646,41 @@ "lib/netstandard1.3/System.ObjectModel.dll": {} } }, + "System.Private.DataContractSerialization/4.1.1": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Collections.Concurrent": "4.0.12", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Linq": "4.1.0", + "System.Reflection": "4.1.0", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Emit.Lightweight": "4.0.1", + "System.Reflection.Extensions": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Serialization.Primitives": "4.1.1", + "System.Text.Encoding": "4.0.11", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11", + "System.Xml.XmlDocument": "4.0.1", + "System.Xml.XmlSerializer": "4.0.11" + }, + "compile": { + "ref/netstandard/_._": {} + }, + "runtime": { + "lib/netstandard1.3/System.Private.DataContractSerialization.dll": {} + } + }, "System.Reflection/4.1.0": { "type": "package", "dependencies": { @@ -1922,6 +1977,23 @@ "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": {} } }, + "System.Runtime.Serialization.Xml/4.1.1": { + "type": "package", + "dependencies": { + "System.IO": "4.1.0", + "System.Private.DataContractSerialization": "4.1.1", + "System.Runtime": "4.1.0", + "System.Runtime.Serialization.Primitives": "4.1.1", + "System.Text.Encoding": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.Runtime.Serialization.Xml.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Runtime.Serialization.Xml.dll": {} + } + }, "System.Security.Claims/4.0.1": { "type": "package", "dependencies": { @@ -2469,6 +2541,34 @@ "lib/netstandard1.3/System.Xml.XmlDocument.dll": {} } }, + "System.Xml.XmlSerializer/4.0.11": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Linq": "4.1.0", + "System.Reflection": "4.1.0", + "System.Reflection.Emit": "4.0.1", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Extensions": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11", + "System.Xml.XmlDocument": "4.0.1" + }, + "compile": { + "ref/netstandard1.3/_._": {} + }, + "runtime": { + "lib/netstandard1.3/System.Xml.XmlSerializer.dll": {} + } + }, "System.Xml.XPath/4.0.1": { "type": "package", "dependencies": { @@ -2552,6 +2652,17 @@ } }, "libraries": { + "CoreCLR-NCalc/2.1.0": { + "sha512": "GUPPo99NUeAgLR5oIOLrApJx3Mx5BZEaKkK9OlDd/CmAYaACLHo68FnO+kCamsLH2+rvr6Rw3hAwzap4GVFV8Q==", + "type": "package", + "path": "CoreCLR-NCalc/2.1.0", + "files": [ + "CoreCLR-NCalc.2.1.0.nupkg.sha512", + "CoreCLR-NCalc.nuspec", + "lib/net451/NCalc.dll", + "lib/netstandard1.3/NCalc.dll" + ] + }, "Google.Apis/1.15.0": { "sha512": "B//vbZgUsR0jdJztCJ0ORmVcAzhoiisIsxwc1libVjoZzu+kxUKNJKUl5Wlkj7V28kauS56y3hJUj3FMsgaJZQ==", "type": "package", @@ -5677,6 +5788,20 @@ "ref/xamarinwatchos10/_._" ] }, + "System.Private.DataContractSerialization/4.1.1": { + "sha512": "lcqFBUaCZxPiUkA4dlSOoPZGtZsAuuElH2XHgLwGLxd7ZozWetV5yiz0qGAV2AUYOqw97MtZBjbLMN16Xz4vXA==", + "type": "package", + "path": "System.Private.DataContractSerialization/4.1.1", + "files": [ + "System.Private.DataContractSerialization.4.1.1.nupkg.sha512", + "System.Private.DataContractSerialization.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.3/System.Private.DataContractSerialization.dll", + "ref/netstandard/_._", + "runtimes/aot/lib/netcore50/System.Private.DataContractSerialization.dll" + ] + }, "System.Reflection/4.1.0": { "sha512": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "type": "package", @@ -6617,6 +6742,76 @@ "runtimes/aot/lib/netcore50/System.Runtime.Serialization.Primitives.dll" ] }, + "System.Runtime.Serialization.Xml/4.1.1": { + "sha512": "yqfKHkWUAdI0hdDIdD9KDzluKtZ8IIqLF3O7xIZlt6UTs1bOvFRpCvRTvGQva3Ak/ZM9/nq9IHBJ1tC4Ybcrjg==", + "type": "package", + "path": "System.Runtime.Serialization.Xml/4.1.1", + "files": [ + "System.Runtime.Serialization.Xml.4.1.1.nupkg.sha512", + "System.Runtime.Serialization.Xml.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net46/System.Runtime.Serialization.Xml.dll", + "lib/netcore50/System.Runtime.Serialization.Xml.dll", + "lib/netstandard1.3/System.Runtime.Serialization.Xml.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net46/System.Runtime.Serialization.Xml.dll", + "ref/netcore50/System.Runtime.Serialization.Xml.dll", + "ref/netcore50/System.Runtime.Serialization.Xml.xml", + "ref/netcore50/de/System.Runtime.Serialization.Xml.xml", + "ref/netcore50/es/System.Runtime.Serialization.Xml.xml", + "ref/netcore50/fr/System.Runtime.Serialization.Xml.xml", + "ref/netcore50/it/System.Runtime.Serialization.Xml.xml", + "ref/netcore50/ja/System.Runtime.Serialization.Xml.xml", + "ref/netcore50/ko/System.Runtime.Serialization.Xml.xml", + "ref/netcore50/ru/System.Runtime.Serialization.Xml.xml", + "ref/netcore50/zh-hans/System.Runtime.Serialization.Xml.xml", + "ref/netcore50/zh-hant/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/System.Runtime.Serialization.Xml.dll", + "ref/netstandard1.0/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/de/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/es/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/fr/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/it/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/ja/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/ko/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/ru/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/zh-hans/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.0/zh-hant/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/System.Runtime.Serialization.Xml.dll", + "ref/netstandard1.3/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/de/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/es/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/fr/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/it/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/ja/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/ko/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/ru/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/zh-hans/System.Runtime.Serialization.Xml.xml", + "ref/netstandard1.3/zh-hant/System.Runtime.Serialization.Xml.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, "System.Security.Claims/4.0.1": { "sha512": "4Jlp0OgJLS/Voj1kyFP6MJlIYp3crgfH8kNQk2p7+4JYfc1aAmh9PZyAMMbDhuoolGNtux9HqSOazsioRiDvCw==", "type": "package", @@ -7766,6 +7961,75 @@ "ref/xamarinwatchos10/_._" ] }, + "System.Xml.XmlSerializer/4.0.11": { + "sha512": "FrazwwqfIXTfq23mfv4zH+BjqkSFNaNFBtjzu3I9NRmG8EELYyrv/fJnttCIwRMFRR/YKXF1hmsMmMEnl55HGw==", + "type": "package", + "path": "System.Xml.XmlSerializer/4.0.11", + "files": [ + "System.Xml.XmlSerializer.4.0.11.nupkg.sha512", + "System.Xml.XmlSerializer.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Xml.XmlSerializer.dll", + "lib/netstandard1.3/System.Xml.XmlSerializer.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Xml.XmlSerializer.dll", + "ref/netcore50/System.Xml.XmlSerializer.xml", + "ref/netcore50/de/System.Xml.XmlSerializer.xml", + "ref/netcore50/es/System.Xml.XmlSerializer.xml", + "ref/netcore50/fr/System.Xml.XmlSerializer.xml", + "ref/netcore50/it/System.Xml.XmlSerializer.xml", + "ref/netcore50/ja/System.Xml.XmlSerializer.xml", + "ref/netcore50/ko/System.Xml.XmlSerializer.xml", + "ref/netcore50/ru/System.Xml.XmlSerializer.xml", + "ref/netcore50/zh-hans/System.Xml.XmlSerializer.xml", + "ref/netcore50/zh-hant/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/System.Xml.XmlSerializer.dll", + "ref/netstandard1.0/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/de/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/es/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/fr/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/it/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/ja/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/ko/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/ru/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/zh-hans/System.Xml.XmlSerializer.xml", + "ref/netstandard1.0/zh-hant/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/System.Xml.XmlSerializer.dll", + "ref/netstandard1.3/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/de/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/es/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/fr/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/it/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/ja/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/ko/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/ru/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/zh-hans/System.Xml.XmlSerializer.xml", + "ref/netstandard1.3/zh-hant/System.Xml.XmlSerializer.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/aot/lib/netcore50/System.Xml.XmlSerializer.dll" + ] + }, "System.Xml.XPath/4.0.1": { "sha512": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "type": "package", @@ -7853,8 +8117,9 @@ }, "projectFileDependencyGroups": { "": [ - "Discord.Net >= 1.0.0-dev", - "Discord.Net.Commands >= 1.0.0-dev", + "CoreCLR-NCalc >= 2.1.0", + "Discord.Net >= 1.0.0-dev-*", + "Discord.Net.Commands >= 1.0.0-dev-*", "Google.Apis.YouTube.v3 >= 1.15.0.582", "Microsoft.Extensions.DependencyInjection >= 1.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions >= 1.0.0", From 5d5b06ddd32b897edffc02afc32cefe427185bfc Mon Sep 17 00:00:00 2001 From: appelemac Date: Sun, 21 Aug 2016 19:45:15 +0200 Subject: [PATCH 2/9] necessary things --- src/NadekoBot/NadekoBot.cs | 5 ++++- src/NadekoBot/Services/Impl/BotCredentials.cs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index dda384bd..7d26f6bb 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -46,7 +46,7 @@ namespace NadekoBot Localizer = new Localization(); Youtube = new YoutubeService(); Stats = new StatsService(Client); - _log = LogManager.GetCurrentClassLogger(); + //setup DI var depMap = new DependencyMap(); @@ -73,6 +73,7 @@ namespace NadekoBot private void SetupLogger() { + try { var logConfig = new LoggingConfiguration(); @@ -85,10 +86,12 @@ namespace NadekoBot logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); LogManager.Configuration = logConfig; + } catch (Exception ex) { Console.WriteLine(ex); } + _log = LogManager.GetCurrentClassLogger(); } private Task Client_MessageReceived(IMessage imsg) diff --git a/src/NadekoBot/Services/Impl/BotCredentials.cs b/src/NadekoBot/Services/Impl/BotCredentials.cs index 2f4b5f6a..0b825b55 100644 --- a/src/NadekoBot/Services/Impl/BotCredentials.cs +++ b/src/NadekoBot/Services/Impl/BotCredentials.cs @@ -27,6 +27,7 @@ namespace NadekoBot.Services.Impl public BotCredentials() { + File.WriteAllText("./credentials_example.json", JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented)); _log = LogManager.GetCurrentClassLogger(); if (File.Exists("./credentials.json")) { From 5648b58d070bd814d683afc45a45e29de36047bd Mon Sep 17 00:00:00 2001 From: appelemac Date: Mon, 22 Aug 2016 11:56:26 +0200 Subject: [PATCH 3/9] Added functions and constants --- .../Modules/Searches/Commands/CalcCommand.cs | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Commands/CalcCommand.cs b/src/NadekoBot/Modules/Searches/Commands/CalcCommand.cs index e5d51d8f..3353a9ec 100644 --- a/src/NadekoBot/Modules/Searches/Commands/CalcCommand.cs +++ b/src/NadekoBot/Modules/Searches/Commands/CalcCommand.cs @@ -1,25 +1,79 @@ using Discord; using Discord.Commands; +using NadekoBot.Attributes; using NadekoBot.Extensions; using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Text; using System.Threading.Tasks; -namespace NadekoBot.Modules.Searches.Commands +namespace NadekoBot.Modules.Searches { + [Group] public partial class Searches { - public static async Task Calc(IMessage msg, [Remainder] string expression) + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public static async Task Calculate(IMessage msg, [Remainder] string expression) { - var expr = new NCalc.Expression(expression); - //expr.EvaluateParameter += delegate (string name, NCalc.ParameterArgs args) - //{ - // if (name.ToLowerInvariant() == "pi") args.Result = Math.PI; - //}; - var result = expr.Evaluate(); - await msg.Reply(string.Format("Your expression evaluated to: {0}", expr.Error ?? result)); + try + { + + + var expr = new NCalc.Expression(expression, NCalc.EvaluateOptions.IgnoreCase); + expr.EvaluateParameter += Expr_EvaluateParameter; + expr.EvaluateFunction += Expr_EvaluateFunction; + var result = expr.Evaluate(); + await msg.Reply(string.Format("Your expression evaluated to: {0}", expr.Error ?? result)); + } + catch (Exception e) + { + await msg.Reply($"Your expression failed to evaluate: {e.Message} "); + } + + + } + + private static void Expr_EvaluateFunction(string name, NCalc.FunctionArgs args) + { + switch (name.ToLowerInvariant()) + { + + } + } + + private static void Expr_EvaluateParameter(string name, NCalc.ParameterArgs args) + { + switch (name.ToLowerInvariant()) { + case "pi": args.Result= Math.PI; + break; + case "e": args.Result = Math.E; + break; + + } + + } + + [Command("calcOperations")] + [RequireContext(ContextType.Guild)] + public async Task calcOperations(IMessage msg) + { + StringBuilder builder = new StringBuilder(); + var selection = typeof(Math).GetTypeInfo().GetMethods().Except(typeof(object).GetTypeInfo().GetMethods()).Select(x => + { + var name = x.Name; + if (x.GetParameters().Any()) + { + name += " (" + string.Join(", ", x.GetParameters().Select(y => y.IsOptional ? $"[{y.ParameterType.Name + " " + y.Name }]" : y.ParameterType.Name + " " + y.Name)) + ")"; + } + + return name; + }); + foreach (var method in selection) builder.AppendLine(method); + await msg.ReplyLong(builder.ToString()); } } class ExpressionContext From bdd9aef7ac816013227e680a6384aed1ed03726e Mon Sep 17 00:00:00 2001 From: appelemac Date: Mon, 22 Aug 2016 12:01:08 +0200 Subject: [PATCH 4/9] stage for merge --- src/NadekoBot/Services/Impl/BotCredentials.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/NadekoBot/Services/Impl/BotCredentials.cs b/src/NadekoBot/Services/Impl/BotCredentials.cs index a8c4f80f..f2515391 100644 --- a/src/NadekoBot/Services/Impl/BotCredentials.cs +++ b/src/NadekoBot/Services/Impl/BotCredentials.cs @@ -24,10 +24,11 @@ namespace NadekoBot.Services.Impl public ulong[] OwnerIds { get; } public string LoLApiKey { get; } + public string OsuApiKey { get; } + public string SoundCloudClientId { get; } public BotCredentials() { - File.WriteAllText("./credentials_example.json", JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented)); _log = LogManager.GetCurrentClassLogger(); if (File.Exists("./credentials.json")) { @@ -37,17 +38,22 @@ namespace NadekoBot.Services.Impl LoLApiKey = cm.LoLApiKey; GoogleApiKey = cm.GoogleApiKey; MashapeKey = cm.MashapeKey; + OsuApiKey = cm.OsuApiKey; + SoundCloudClientId = cm.SoundCloudClientId; } else _log.Fatal("credentials.json is missing. Failed to start."); } - private class CredentialsModel { + private class CredentialsModel + { public string Token { get; set; } public ulong[] OwnerIds { get; set; } public string LoLApiKey { get; set; } public string GoogleApiKey { get; set; } public string MashapeKey { get; set; } + public string OsuApiKey { get; set; } + public string SoundCloudClientId { get; set; } } public bool IsOwner(IUser u) => OwnerIds.Contains(u.Id); From fd5916a54dbc2244fe3e996cac2aa7925e37394a Mon Sep 17 00:00:00 2001 From: appelemac Date: Mon, 22 Aug 2016 12:06:29 +0200 Subject: [PATCH 5/9] remove files? --- .../_Modules/Music/Classes/MusicControls.cs | 283 ------------ .../Music/Classes/PlaylistFullException.cs | 12 - src/NadekoBot/_Modules/Music/Classes/Song.cs | 417 ------------------ .../_Modules/Music/Classes/SongBuffer.cs | 159 ------- .../_Modules/Music/Classes/SoundCloud.cs | 129 ------ 5 files changed, 1000 deletions(-) delete mode 100644 src/NadekoBot/_Modules/Music/Classes/MusicControls.cs delete mode 100644 src/NadekoBot/_Modules/Music/Classes/PlaylistFullException.cs delete mode 100644 src/NadekoBot/_Modules/Music/Classes/Song.cs delete mode 100644 src/NadekoBot/_Modules/Music/Classes/SongBuffer.cs delete mode 100644 src/NadekoBot/_Modules/Music/Classes/SoundCloud.cs diff --git a/src/NadekoBot/_Modules/Music/Classes/MusicControls.cs b/src/NadekoBot/_Modules/Music/Classes/MusicControls.cs deleted file mode 100644 index e13239cf..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/MusicControls.cs +++ /dev/null @@ -1,283 +0,0 @@ -using Discord; -using Discord.Audio; -using NadekoBot.Extensions; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -namespace NadekoBot.Modules.Music.Classes -{ - - public enum MusicType - { - Radio, - Normal, - Local, - Soundcloud - } - - public enum StreamState - { - Resolving, - Queued, - Playing, - Completed - } - - public class MusicPlayer - { - private IAudioClient audioClient { get; set; } - - private readonly List playlist = new List(); - public IReadOnlyCollection Playlist => playlist; - - public Song CurrentSong { get; private set; } - public CancellationTokenSource SongCancelSource { get; private set; } - private CancellationToken cancelToken { get; set; } - - public bool Paused { get; set; } - - public float Volume { get; private set; } - - public event EventHandler OnCompleted = delegate { }; - public event EventHandler OnStarted = delegate { }; - - public Channel PlaybackVoiceChannel { get; private set; } - - private bool Destroyed { get; set; } = false; - public bool RepeatSong { get; private set; } = false; - public bool RepeatPlaylist { get; private set; } = false; - public bool Autoplay { get; set; } = false; - public uint MaxQueueSize { get; set; } = 0; - - private ConcurrentQueue actionQueue { get; set; } = new ConcurrentQueue(); - - public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) - { - if (startingVoiceChannel == null) - throw new ArgumentNullException(nameof(startingVoiceChannel)); - if (startingVoiceChannel.Type != ChannelType.Voice) - throw new ArgumentException("Channel must be of type voice"); - Volume = defaultVolume ?? 1.0f; - - PlaybackVoiceChannel = startingVoiceChannel; - SongCancelSource = new CancellationTokenSource(); - cancelToken = SongCancelSource.Token; - - Task.Run(async () => - { - try - { - while (!Destroyed) - { - try - { - Action action; - if (actionQueue.TryDequeue(out action)) - { - action(); - } - } - finally - { - await Task.Delay(100).ConfigureAwait(false); - } - } - } - catch (Exception ex) - { - Console.WriteLine("Action queue crashed"); - Console.WriteLine(ex); - } - }).ConfigureAwait(false); - - var t = new Thread(new ThreadStart(async () => - { - try - { - while (!Destroyed) - { - try - { - if (audioClient?.State != ConnectionState.Connected) - { - audioClient = await PlaybackVoiceChannel.JoinAudio(); - continue; - } - - CurrentSong = GetNextSong(); - RemoveSongAt(0); - - if (CurrentSong == null) - continue; - - - OnStarted(this, CurrentSong); - await CurrentSong.Play(audioClient, cancelToken); - - OnCompleted(this, CurrentSong); - - if (RepeatPlaylist) - AddSong(CurrentSong, CurrentSong.QueuerName); - - if (RepeatSong) - AddSong(CurrentSong, 0); - - } - finally - { - if (!cancelToken.IsCancellationRequested) - { - SongCancelSource.Cancel(); - } - SongCancelSource = new CancellationTokenSource(); - cancelToken = SongCancelSource.Token; - CurrentSong = null; - await Task.Delay(300).ConfigureAwait(false); - } - } - } - catch (Exception ex) { - Console.WriteLine("Music thread crashed."); - Console.WriteLine(ex); - } - })); - - t.Start(); - } - - public void Next() - { - actionQueue.Enqueue(() => - { - Paused = false; - SongCancelSource.Cancel(); - }); - } - - public void Stop() - { - actionQueue.Enqueue(() => - { - RepeatPlaylist = false; - RepeatSong = false; - playlist.Clear(); - if (!SongCancelSource.IsCancellationRequested) - SongCancelSource.Cancel(); - }); - } - - public void TogglePause() => Paused = !Paused; - - public int SetVolume(int volume) - { - if (volume < 0) - volume = 0; - if (volume > 100) - volume = 100; - - Volume = volume / 100.0f; - return volume; - } - - private Song GetNextSong() => - playlist.FirstOrDefault(); - - public void Shuffle() - { - actionQueue.Enqueue(() => - { - playlist.Shuffle(); - }); - } - - public void AddSong(Song s, string username) - { - if (s == null) - throw new ArgumentNullException(nameof(s)); - ThrowIfQueueFull(); - actionQueue.Enqueue(() => - { - s.MusicPlayer = this; - s.QueuerName = username.TrimTo(10); - playlist.Add(s); - }); - } - - public void AddSong(Song s, int index) - { - if (s == null) - throw new ArgumentNullException(nameof(s)); - actionQueue.Enqueue(() => - { - playlist.Insert(index, s); - }); - } - - public void RemoveSong(Song s) - { - if (s == null) - throw new ArgumentNullException(nameof(s)); - actionQueue.Enqueue(() => - { - playlist.Remove(s); - }); - } - - public void RemoveSongAt(int index) - { - actionQueue.Enqueue(() => - { - if (index < 0 || index >= playlist.Count) - return; - playlist.RemoveAt(index); - }); - } - - internal void ClearQueue() - { - actionQueue.Enqueue(() => - { - playlist.Clear(); - }); - } - - public void Destroy() - { - actionQueue.Enqueue(() => - { - RepeatPlaylist = false; - RepeatSong = false; - Destroyed = true; - playlist.Clear(); - if (!SongCancelSource.IsCancellationRequested) - SongCancelSource.Cancel(); - audioClient.Disconnect(); - }); - } - - internal Task MoveToVoiceChannel(Channel voiceChannel) - { - if (audioClient?.State != ConnectionState.Connected) - throw new InvalidOperationException("Can't move while bot is not connected to voice channel."); - PlaybackVoiceChannel = voiceChannel; - return PlaybackVoiceChannel.JoinAudio(); - } - - internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong; - - internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist; - - internal bool ToggleAutoplay() => this.Autoplay = !this.Autoplay; - - internal void ThrowIfQueueFull() - { - if (MaxQueueSize == 0) - return; - if (playlist.Count >= MaxQueueSize) - throw new PlaylistFullException(); - } - } -} diff --git a/src/NadekoBot/_Modules/Music/Classes/PlaylistFullException.cs b/src/NadekoBot/_Modules/Music/Classes/PlaylistFullException.cs deleted file mode 100644 index 15541d42..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/PlaylistFullException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace NadekoBot.Modules.Music.Classes -{ - class PlaylistFullException : Exception - { - public PlaylistFullException(string message) : base(message) - { - } - public PlaylistFullException() : base("Queue is full.") { } - } -} diff --git a/src/NadekoBot/_Modules/Music/Classes/Song.cs b/src/NadekoBot/_Modules/Music/Classes/Song.cs deleted file mode 100644 index 2d808736..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/Song.cs +++ /dev/null @@ -1,417 +0,0 @@ -using Discord.Audio; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using System; -using System.Diagnostics; -using System.Diagnostics.Contracts; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using VideoLibrary; - -namespace NadekoBot.Modules.Music.Classes -{ - public class SongInfo - { - public string Provider { get; internal set; } - public MusicType ProviderType { get; internal set; } - /// - /// Will be set only if the providertype is normal - /// - public string Query { get; internal set; } - public string Title { get; internal set; } - public string Uri { get; internal set; } - } - public class Song - { - public StreamState State { get; internal set; } - public string PrettyName => - $"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}` `by {QueuerName}`"; - public SongInfo SongInfo { get; } - public string QueuerName { get; set; } - - public MusicPlayer MusicPlayer { get; set; } - - public string PrettyCurrentTime() - { - var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50); - return $"【{(int)time.TotalMinutes}m {time.Seconds}s】"; - } - - private ulong bytesSent { get; set; } = 0; - - public bool PrintStatusMessage { get; set; } = true; - - private int skipTo = 0; - public int SkipTo { - get { return SkipTo; } - set { - skipTo = value; - bytesSent = (ulong)skipTo * 3840 * 50; - } - } - - public Song(SongInfo songInfo) - { - this.SongInfo = songInfo; - } - - public Song Clone() - { - var s = new Song(SongInfo); - s.MusicPlayer = MusicPlayer; - s.State = StreamState.Queued; - return s; - } - - public Song SetMusicPlayer(MusicPlayer mp) - { - this.MusicPlayer = mp; - return this; - } - - internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) - { - var filename = Path.Combine(MusicModule.MusicDataPath, DateTime.Now.UnixTimestamp().ToString()); - - SongBuffer sb = new SongBuffer(filename, SongInfo, skipTo); - var bufferTask = sb.BufferSong(cancelToken).ConfigureAwait(false); - - var inStream = new FileStream(sb.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); ; - - bytesSent = 0; - - try - { - var attempt = 0; - - var prebufferingTask = CheckPrebufferingAsync(inStream, sb, cancelToken); - var sw = new Stopwatch(); - sw.Start(); - var t = await Task.WhenAny(prebufferingTask, Task.Delay(5000, cancelToken)); - if (t != prebufferingTask) - { - Console.WriteLine("Prebuffering timed out or canceled. Cannot get any data from the stream."); - return; - } - else if(prebufferingTask.IsCanceled) - { - Console.WriteLine("Prebuffering timed out. Cannot get any data from the stream."); - return; - } - sw.Stop(); - Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed); - - const int blockSize = 3840; - byte[] buffer = new byte[blockSize]; - while (!cancelToken.IsCancellationRequested) - { - //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); - var read = inStream.Read(buffer, 0, buffer.Length); - //await inStream.CopyToAsync(voiceClient.OutputStream); - unchecked - { - bytesSent += (ulong)read; - } - if (read < blockSize) - { - if (sb.IsNextFileReady()) - { - inStream.Dispose(); - inStream = new FileStream(sb.GetNextFile(), FileMode.Open, FileAccess.Read, FileShare.Write); - read += inStream.Read(buffer, read, buffer.Length - read); - attempt = 0; - } - if (read == 0) - { - if (sb.BufferingCompleted) - break; - if (attempt++ == 20) - { - voiceClient.Wait(); - MusicPlayer.SongCancelSource.Cancel(); - break; - } - else - await Task.Delay(100, cancelToken).ConfigureAwait(false); - } - else - attempt = 0; - } - else - attempt = 0; - - while (this.MusicPlayer.Paused) - await Task.Delay(200, cancelToken).ConfigureAwait(false); - - buffer = AdjustVolume(buffer, MusicPlayer.Volume); - voiceClient.Send(buffer, 0, read); - } - } - finally - { - await bufferTask; - await Task.Run(() => voiceClient.Clear()); - if(inStream != null) - inStream.Dispose(); - Console.WriteLine("l"); - sb.CleanFiles(); - } - } - - private async Task CheckPrebufferingAsync(Stream inStream, SongBuffer sb, CancellationToken cancelToken) - { - while (!sb.BufferingCompleted && inStream.Length < 2.MiB()) - { - await Task.Delay(100, cancelToken); - } - Console.WriteLine("Buffering successfull"); - } - - /* - //stackoverflow ftw - private static byte[] AdjustVolume(byte[] audioSamples, float volume) - { - if (Math.Abs(volume - 1.0f) < 0.01f) - return audioSamples; - var array = new byte[audioSamples.Length]; - for (var i = 0; i < array.Length; i += 2) - { - - // convert byte pair to int - short buf1 = audioSamples[i + 1]; - short buf2 = audioSamples[i]; - - buf1 = (short)((buf1 & 0xff) << 8); - buf2 = (short)(buf2 & 0xff); - - var res = (short)(buf1 | buf2); - res = (short)(res * volume); - - // convert back - array[i] = (byte)res; - array[i + 1] = (byte)(res >> 8); - - } - return array; - } - */ - - //aidiakapi ftw - public unsafe static byte[] AdjustVolume(byte[] audioSamples, float volume) - { - Contract.Requires(audioSamples != null); - Contract.Requires(audioSamples.Length % 2 == 0); - Contract.Requires(volume >= 0f && volume <= 1f); - Contract.Assert(BitConverter.IsLittleEndian); - - if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples; - - // 16-bit precision for the multiplication - int volumeFixed = (int)Math.Round(volume * 65536d); - - int count = audioSamples.Length / 2; - - fixed (byte* srcBytes = audioSamples) - { - short* src = (short*)srcBytes; - - for (int i = count; i != 0; i--, src++) - *src = (short)(((*src) * volumeFixed) >> 16); - } - - return audioSamples; - } - - public static async Task ResolveSong(string query, MusicType musicType = MusicType.Normal) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - - if (musicType != MusicType.Local && IsRadioLink(query)) - { - musicType = MusicType.Radio; - query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query; - } - - try - { - switch (musicType) - { - case MusicType.Local: - return new Song(new SongInfo - { - Uri = "\"" + Path.GetFullPath(query) + "\"", - Title = Path.GetFileNameWithoutExtension(query), - Provider = "Local File", - ProviderType = musicType, - Query = query, - }); - case MusicType.Radio: - return new Song(new SongInfo - { - Uri = query, - Title = $"{query}", - Provider = "Radio Stream", - ProviderType = musicType, - Query = query - }); - } - if (SoundCloud.Default.IsSoundCloudLink(query)) - { - var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false); - return new Song(new SongInfo - { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = svideo.StreamLink, - ProviderType = musicType, - Query = svideo.TrackLink, - }); - } - - if (musicType == MusicType.Soundcloud) - { - var svideo = await SoundCloud.Default.GetVideoByQueryAsync(query).ConfigureAwait(false); - return new Song(new SongInfo - { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = svideo.StreamLink, - ProviderType = MusicType.Normal, - Query = svideo.TrackLink, - }); - } - - var link = await SearchHelper.FindYoutubeUrlByKeywords(query).ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(link)) - throw new OperationCanceledException("Not a valid youtube query."); - var allVideos = await Task.Factory.StartNew(async () => await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false)).Unwrap().ConfigureAwait(false); - var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio); - var video = videos - .Where(v => v.AudioBitrate < 192) - .OrderByDescending(v => v.AudioBitrate) - .FirstOrDefault(); - - if (video == null) // do something with this error - throw new Exception("Could not load any video elements based on the query."); - var m = Regex.Match(query, @"\?t=(?\d*)"); - int gotoTime = 0; - if (m.Captures.Count > 0) - int.TryParse(m.Groups["t"].ToString(), out gotoTime); - var song = new Song(new SongInfo - { - Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" - Provider = "YouTube", - Uri = video.Uri, - Query = link, - ProviderType = musicType, - }); - song.SkipTo = gotoTime; - return song; - } - catch (Exception ex) - { - Console.WriteLine($"Failed resolving the link.{ex.Message}"); - return null; - } - } - - private static async Task HandleStreamContainers(string query) - { - string file = null; - try - { - file = await http.GetStringAsync(query).ConfigureAwait(false); - } - catch - { - return query; - } - if (query.Contains(".pls")) - { - //File1=http://armitunes.com:8000/ - //Regex.Match(query) - try - { - var m = Regex.Match(file, "File1=(?.*?)\\n"); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - Console.WriteLine($"Failed reading .pls:\n{file}"); - return null; - } - } - if (query.Contains(".m3u")) - { - /* -# This is a comment - C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 - C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 - */ - try - { - var m = Regex.Match(file, "(?^[^#].*)", RegexOptions.Multiline); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - Console.WriteLine($"Failed reading .m3u:\n{file}"); - return null; - } - - } - if (query.Contains(".asx")) - { - // - try - { - var m = Regex.Match(file, ".*?)\""); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - Console.WriteLine($"Failed reading .asx:\n{file}"); - return null; - } - } - if (query.Contains(".xspf")) - { - /* - - - - file:///mp3s/song_1.mp3 - */ - try - { - var m = Regex.Match(file, "(?.*?)"); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - Console.WriteLine($"Failed reading .xspf:\n{file}"); - return null; - } - } - - return query; - } - - private static bool IsRadioLink(string query) => - (query.StartsWith("http") || - query.StartsWith("ww")) - && - (query.Contains(".pls") || - query.Contains(".m3u") || - query.Contains(".asx") || - query.Contains(".xspf")); - } -} diff --git a/src/NadekoBot/_Modules/Music/Classes/SongBuffer.cs b/src/NadekoBot/_Modules/Music/Classes/SongBuffer.cs deleted file mode 100644 index d9192940..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/SongBuffer.cs +++ /dev/null @@ -1,159 +0,0 @@ -using NadekoBot.Extensions; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Music.Classes -{ - /// - /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. - /// It also help for large music by deleting files that are already seen. - /// - class SongBuffer - { - - public SongBuffer(string basename, SongInfo songInfo, int skipTo) - { - Basename = basename; - SongInfo = songInfo; - SkipTo = skipTo; - } - - private string Basename; - - private SongInfo SongInfo; - - private int SkipTo; - - private static int MAX_FILE_SIZE = 20.MiB(); - - private long FileNumber = -1; - - private long NextFileToRead = 0; - - public bool BufferingCompleted { get; private set;} = false; - - private ulong CurrentBufferSize = 0; - - public Task BufferSong(CancellationToken cancelToken) => - Task.Factory.StartNew(async () => - { - Process p = null; - FileStream outStream = null; - try - { - p = Process.Start(new ProcessStartInfo - { - FileName = "ffmpeg", - Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = false, - CreateNoWindow = true, - }); - - byte[] buffer = new byte[81920]; - int currentFileSize = 0; - ulong prebufferSize = 100ul.MiB(); - - outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); - while (!p.HasExited) //Also fix low bandwidth - { - int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false); - if (currentFileSize >= MAX_FILE_SIZE) - { - try - { - outStream.Dispose(); - }catch { } - outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); - currentFileSize = bytesRead; - } - else - { - currentFileSize += bytesRead; - } - CurrentBufferSize += Convert.ToUInt64(bytesRead); - await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); - while (CurrentBufferSize > prebufferSize) - await Task.Delay(100, cancelToken); - } - BufferingCompleted = true; - } - catch (System.ComponentModel.Win32Exception) - { - var oldclr = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(@"You have not properly installed or configured FFMPEG. -Please install and configure FFMPEG to play music. -Check the guides for your platform on how to setup ffmpeg correctly: - Windows Guide: https://goo.gl/SCv72y - Linux Guide: https://goo.gl/rRhjCp"); - Console.ForegroundColor = oldclr; - } - catch (Exception ex) - { - Console.WriteLine($"Buffering stopped: {ex.Message}"); - } - finally - { - if(outStream != null) - outStream.Dispose(); - Console.WriteLine($"Buffering done."); - if (p != null) - { - try - { - p.Kill(); - } - catch { } - p.Dispose(); - } - } - }, TaskCreationOptions.LongRunning); - - /// - /// Return the next file to read, and delete the old one - /// - /// Name of the file to read - public string GetNextFile() - { - string filename = Basename + "-" + NextFileToRead; - - if (NextFileToRead != 0) - { - try - { - CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length); - File.Delete(Basename + "-" + (NextFileToRead - 1)); - } - catch { } - } - NextFileToRead++; - return filename; - } - - public bool IsNextFileReady() - { - return NextFileToRead <= FileNumber; - } - - public void CleanFiles() - { - for (long i = NextFileToRead - 1 ; i <= FileNumber; i++) - { - try - { - File.Delete(Basename + "-" + i); - } - catch { } - } - } - } -} diff --git a/src/NadekoBot/_Modules/Music/Classes/SoundCloud.cs b/src/NadekoBot/_Modules/Music/Classes/SoundCloud.cs deleted file mode 100644 index ed829adb..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/SoundCloud.cs +++ /dev/null @@ -1,129 +0,0 @@ -using NadekoBot.Classes; -using Newtonsoft.Json; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Music.Classes -{ - public class SoundCloud - { - private static readonly SoundCloud _instance = new SoundCloud(); - public static SoundCloud Default => _instance; - - static SoundCloud() { } - public SoundCloud() { } - - public async Task ResolveVideoAsync(string url) - { - if (string.IsNullOrWhiteSpace(url)) - throw new ArgumentNullException(nameof(url)); - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.SoundCloudClientID)) - throw new ArgumentNullException(nameof(NadekoBot.Creds.SoundCloudClientID)); - - var response = await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false); - - var responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject(response); - if (responseObj?.Kind != "track") - throw new InvalidOperationException("Url is either not a track, or it doesn't exist."); - - return responseObj; - } - - public bool IsSoundCloudLink(string url) => - System.Text.RegularExpressions.Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)"); - - internal async Task GetVideoByQueryAsync(string query) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.SoundCloudClientID)) - throw new ArgumentNullException(nameof(NadekoBot.Creds.SoundCloudClientID)); - - var response = await http.GetStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false); - - var responseObj = JsonConvert.DeserializeObject(response).Where(s => s.Streamable).FirstOrDefault(); - if (responseObj?.Kind != "track") - throw new InvalidOperationException("Query yielded no results."); - - return responseObj; - } - } - - public class SoundCloudVideo - { - public string Kind { get; set; } = ""; - public long Id { get; set; } = 0; - public SoundCloudUser User { get; set; } = new SoundCloudUser(); - public string Title { get; set; } = ""; - [JsonIgnore] - public string FullName => User.Name + " - " + Title; - public bool Streamable { get; set; } = false; - [JsonProperty("permalink_url")] - public string TrackLink { get; set; } = ""; - [JsonIgnore] - public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Creds.SoundCloudClientID}"; - } - public class SoundCloudUser - { - [Newtonsoft.Json.JsonProperty("username")] - public string Name { get; set; } - } - /* - {"kind":"track", - "id":238888167, - "created_at":"2015/12/24 01:04:52 +0000", - "user_id":43141975, - "duration":120852, - "commentable":true, - "state":"finished", - "original_content_size":4834829, - "last_modified":"2015/12/24 01:17:59 +0000", - "sharing":"public", - "tag_list":"Funky", - "permalink":"18-fd", - "streamable":true, - "embeddable_by":"all", - "downloadable":false, - "purchase_url":null, - "label_id":null, - "purchase_title":null, - "genre":"Disco", - "title":"18 Ж", - "description":"", - "label_name":null, - "release":null, - "track_type":null, - "key_signature":null, - "isrc":null, - "video_url":null, - "bpm":null, - "release_year":null, - "release_month":null, - "release_day":null, - "original_format":"mp3", - "license":"all-rights-reserved", - "uri":"https://api.soundcloud.com/tracks/238888167", - "user":{ - "id":43141975, - "kind":"user", - "permalink":"mrb00gi", - "username":"Mrb00gi", - "last_modified":"2015/12/01 16:06:57 +0000", - "uri":"https://api.soundcloud.com/users/43141975", - "permalink_url":"http://soundcloud.com/mrb00gi", - "avatar_url":"https://a1.sndcdn.com/images/default_avatar_large.png" - }, - "permalink_url":"http://soundcloud.com/mrb00gi/18-fd", - "artwork_url":null, - "waveform_url":"https://w1.sndcdn.com/gsdLfvEW1cUK_m.png", - "stream_url":"https://api.soundcloud.com/tracks/238888167/stream", - "playback_count":7, - "download_count":0, - "favoritings_count":1, - "comment_count":0, - "attachments_uri":"https://api.soundcloud.com/tracks/238888167/attachments"} - - */ - -} From aa6970376876370906eebe1dc0868df423da2e22 Mon Sep 17 00:00:00 2001 From: appelemac Date: Mon, 22 Aug 2016 12:06:44 +0200 Subject: [PATCH 6/9] add files? --- .../Modules/Music/Classes/MusicControls.cs | 281 ++++++++++++ .../Music/Classes/PlaylistFullException.cs | 12 + src/NadekoBot/Modules/Music/Classes/Song.cs | 422 ++++++++++++++++++ .../Modules/Music/Classes/SongBuffer.cs | 159 +++++++ .../Modules/Music/Classes/SoundCloud.cs | 141 ++++++ 5 files changed, 1015 insertions(+) create mode 100644 src/NadekoBot/Modules/Music/Classes/MusicControls.cs create mode 100644 src/NadekoBot/Modules/Music/Classes/PlaylistFullException.cs create mode 100644 src/NadekoBot/Modules/Music/Classes/Song.cs create mode 100644 src/NadekoBot/Modules/Music/Classes/SongBuffer.cs create mode 100644 src/NadekoBot/Modules/Music/Classes/SoundCloud.cs diff --git a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs new file mode 100644 index 00000000..03aaccef --- /dev/null +++ b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -0,0 +1,281 @@ +using Discord; +using Discord.Audio; +using NadekoBot.Extensions; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +namespace NadekoBot.Modules.Music.Classes +{ + + public enum MusicType + { + Radio, + Normal, + Local, + Soundcloud + } + + public enum StreamState + { + Resolving, + Queued, + Playing, + Completed + } + + public class MusicPlayer + { + private IAudioClient audioClient { get; set; } + + private readonly List playlist = new List(); + public IReadOnlyCollection Playlist => playlist; + + public Song CurrentSong { get; private set; } + public CancellationTokenSource SongCancelSource { get; private set; } + private CancellationToken cancelToken { get; set; } + + public bool Paused { get; set; } + + public float Volume { get; private set; } + + public event EventHandler OnCompleted = delegate { }; + public event EventHandler OnStarted = delegate { }; + + public IVoiceChannel PlaybackVoiceChannel { get; private set; } + + private bool Destroyed { get; set; } = false; + public bool RepeatSong { get; private set; } = false; + public bool RepeatPlaylist { get; private set; } = false; + public bool Autoplay { get; set; } = false; + public uint MaxQueueSize { get; set; } = 0; + + private ConcurrentQueue actionQueue { get; set; } = new ConcurrentQueue(); + + public MusicPlayer(IVoiceChannel startingVoiceChannel, float? defaultVolume) + { + if (startingVoiceChannel == null) + throw new ArgumentNullException(nameof(startingVoiceChannel)); + Volume = defaultVolume ?? 1.0f; + + PlaybackVoiceChannel = startingVoiceChannel; + SongCancelSource = new CancellationTokenSource(); + cancelToken = SongCancelSource.Token; + + Task.Run(async () => + { + try + { + while (!Destroyed) + { + try + { + Action action; + if (actionQueue.TryDequeue(out action)) + { + action(); + } + } + finally + { + await Task.Delay(100).ConfigureAwait(false); + } + } + } + catch (Exception ex) + { + Console.WriteLine("Action queue crashed"); + Console.WriteLine(ex); + } + }).ConfigureAwait(false); + + var t = new Thread(new ThreadStart(async () => + { + try + { + while (!Destroyed) + { + try + { + if (audioClient?.ConnectionState != ConnectionState.Connected) + { + audioClient = await PlaybackVoiceChannel.ConnectAsync().ConfigureAwait(false); + continue; + } + + CurrentSong = GetNextSong(); + RemoveSongAt(0); + + if (CurrentSong == null) + continue; + + + OnStarted(this, CurrentSong); + await CurrentSong.Play(audioClient, cancelToken); + + OnCompleted(this, CurrentSong); + + if (RepeatPlaylist) + AddSong(CurrentSong, CurrentSong.QueuerName); + + if (RepeatSong) + AddSong(CurrentSong, 0); + + } + finally + { + if (!cancelToken.IsCancellationRequested) + { + SongCancelSource.Cancel(); + } + SongCancelSource = new CancellationTokenSource(); + cancelToken = SongCancelSource.Token; + CurrentSong = null; + await Task.Delay(300).ConfigureAwait(false); + } + } + } + catch (Exception ex) { + Console.WriteLine("Music thread crashed."); + Console.WriteLine(ex); + } + })); + + t.Start(); + } + + public void Next() + { + actionQueue.Enqueue(() => + { + Paused = false; + SongCancelSource.Cancel(); + }); + } + + public void Stop() + { + actionQueue.Enqueue(() => + { + RepeatPlaylist = false; + RepeatSong = false; + playlist.Clear(); + if (!SongCancelSource.IsCancellationRequested) + SongCancelSource.Cancel(); + }); + } + + public void TogglePause() => Paused = !Paused; + + public int SetVolume(int volume) + { + if (volume < 0) + volume = 0; + if (volume > 100) + volume = 100; + + Volume = volume / 100.0f; + return volume; + } + + private Song GetNextSong() => + playlist.FirstOrDefault(); + + public void Shuffle() + { + actionQueue.Enqueue(() => + { + playlist.Shuffle(); + }); + } + + public void AddSong(Song s, string username) + { + if (s == null) + throw new ArgumentNullException(nameof(s)); + ThrowIfQueueFull(); + actionQueue.Enqueue(() => + { + s.MusicPlayer = this; + s.QueuerName = username.TrimTo(10); + playlist.Add(s); + }); + } + + public void AddSong(Song s, int index) + { + if (s == null) + throw new ArgumentNullException(nameof(s)); + actionQueue.Enqueue(() => + { + playlist.Insert(index, s); + }); + } + + public void RemoveSong(Song s) + { + if (s == null) + throw new ArgumentNullException(nameof(s)); + actionQueue.Enqueue(() => + { + playlist.Remove(s); + }); + } + + public void RemoveSongAt(int index) + { + actionQueue.Enqueue(() => + { + if (index < 0 || index >= playlist.Count) + return; + playlist.RemoveAt(index); + }); + } + + internal void ClearQueue() + { + actionQueue.Enqueue(() => + { + playlist.Clear(); + }); + } + + public void Destroy() + { + actionQueue.Enqueue(async () => + { + RepeatPlaylist = false; + RepeatSong = false; + Destroyed = true; + playlist.Clear(); + if (!SongCancelSource.IsCancellationRequested) + SongCancelSource.Cancel(); + await audioClient.DisconnectAsync(); + }); + } + + internal Task MoveToVoiceChannel(IVoiceChannel voiceChannel) + { + if (audioClient?.ConnectionState != ConnectionState.Connected) + throw new InvalidOperationException("Can't move while bot is not connected to voice channel."); + PlaybackVoiceChannel = voiceChannel; + return PlaybackVoiceChannel.ConnectAsync(); + } + + internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong; + + internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist; + + internal bool ToggleAutoplay() => this.Autoplay = !this.Autoplay; + + internal void ThrowIfQueueFull() + { + if (MaxQueueSize == 0) + return; + if (playlist.Count >= MaxQueueSize) + throw new PlaylistFullException(); + } + } +} diff --git a/src/NadekoBot/Modules/Music/Classes/PlaylistFullException.cs b/src/NadekoBot/Modules/Music/Classes/PlaylistFullException.cs new file mode 100644 index 00000000..15541d42 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Classes/PlaylistFullException.cs @@ -0,0 +1,12 @@ +using System; + +namespace NadekoBot.Modules.Music.Classes +{ + class PlaylistFullException : Exception + { + public PlaylistFullException(string message) : base(message) + { + } + public PlaylistFullException() : base("Queue is full.") { } + } +} diff --git a/src/NadekoBot/Modules/Music/Classes/Song.cs b/src/NadekoBot/Modules/Music/Classes/Song.cs new file mode 100644 index 00000000..db4b06ea --- /dev/null +++ b/src/NadekoBot/Modules/Music/Classes/Song.cs @@ -0,0 +1,422 @@ +using Discord.Audio; +using NadekoBot.Classes; +using NadekoBot.Extensions; +using System; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using VideoLibrary; + +namespace NadekoBot.Modules.Music.Classes +{ + public class SongInfo + { + public string Provider { get; internal set; } + public MusicType ProviderType { get; internal set; } + /// + /// Will be set only if the providertype is normal + /// + public string Query { get; internal set; } + public string Title { get; internal set; } + public string Uri { get; internal set; } + } + public class Song + { + public StreamState State { get; internal set; } + public string PrettyName => + $"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}` `by {QueuerName}`"; + public SongInfo SongInfo { get; } + public string QueuerName { get; set; } + + public MusicPlayer MusicPlayer { get; set; } + + public string PrettyCurrentTime() + { + var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50); + return $"【{(int)time.TotalMinutes}m {time.Seconds}s】"; + } + + private ulong bytesSent { get; set; } = 0; + + public bool PrintStatusMessage { get; set; } = true; + + private int skipTo = 0; + public int SkipTo { + get { return SkipTo; } + set { + skipTo = value; + bytesSent = (ulong)skipTo * 3840 * 50; + } + } + + public Song(SongInfo songInfo) + { + this.SongInfo = songInfo; + } + + public Song Clone() + { + var s = new Song(SongInfo); + s.MusicPlayer = MusicPlayer; + s.State = StreamState.Queued; + return s; + } + + public Song SetMusicPlayer(MusicPlayer mp) + { + this.MusicPlayer = mp; + return this; + } + + internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) + { + var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString()); + + SongBuffer sb = new SongBuffer(filename, SongInfo, skipTo); + var bufferTask = sb.BufferSong(cancelToken).ConfigureAwait(false); + + var inStream = new FileStream(sb.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); ; + + bytesSent = 0; + + try + { + var attempt = 0; + + var prebufferingTask = CheckPrebufferingAsync(inStream, sb, cancelToken); + var sw = new Stopwatch(); + sw.Start(); + var t = await Task.WhenAny(prebufferingTask, Task.Delay(5000, cancelToken)); + if (t != prebufferingTask) + { + Console.WriteLine("Prebuffering timed out or canceled. Cannot get any data from the stream."); + return; + } + else if(prebufferingTask.IsCanceled) + { + Console.WriteLine("Prebuffering timed out. Cannot get any data from the stream."); + return; + } + sw.Stop(); + Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed); + + + var outStream = voiceClient.CreatePCMStream(3840); + + const int blockSize = 3840; + byte[] buffer = new byte[blockSize]; + while (!cancelToken.IsCancellationRequested) + { + //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); + var read = inStream.Read(buffer, 0, buffer.Length); + //await inStream.CopyToAsync(voiceClient.OutputStream); + unchecked + { + bytesSent += (ulong)read; + } + if (read < blockSize) + { + if (sb.IsNextFileReady()) + { + inStream.Dispose(); + inStream = new FileStream(sb.GetNextFile(), FileMode.Open, FileAccess.Read, FileShare.Write); + read += inStream.Read(buffer, read, buffer.Length - read); + attempt = 0; + } + if (read == 0) + { + if (sb.BufferingCompleted) + break; + if (attempt++ == 20) + { + MusicPlayer.SongCancelSource.Cancel(); + break; + } + else + await Task.Delay(100, cancelToken).ConfigureAwait(false); + } + else + attempt = 0; + } + else + attempt = 0; + + while (this.MusicPlayer.Paused) + await Task.Delay(200, cancelToken).ConfigureAwait(false); + + buffer = AdjustVolume(buffer, MusicPlayer.Volume); + await outStream.WriteAsync(buffer, 0, read); + } + } + finally + { + await bufferTask; + if(inStream != null) + inStream.Dispose(); + Console.WriteLine("l"); + sb.CleanFiles(); + } + } + + private async Task CheckPrebufferingAsync(Stream inStream, SongBuffer sb, CancellationToken cancelToken) + { + while (!sb.BufferingCompleted && inStream.Length < 2.MiB()) + { + await Task.Delay(100, cancelToken); + } + Console.WriteLine("Buffering successfull"); + } + + /* + //stackoverflow ftw + private static byte[] AdjustVolume(byte[] audioSamples, float volume) + { + if (Math.Abs(volume - 1.0f) < 0.01f) + return audioSamples; + var array = new byte[audioSamples.Length]; + for (var i = 0; i < array.Length; i += 2) + { + + // convert byte pair to int + short buf1 = audioSamples[i + 1]; + short buf2 = audioSamples[i]; + + buf1 = (short)((buf1 & 0xff) << 8); + buf2 = (short)(buf2 & 0xff); + + var res = (short)(buf1 | buf2); + res = (short)(res * volume); + + // convert back + array[i] = (byte)res; + array[i + 1] = (byte)(res >> 8); + + } + return array; + } + */ + + //aidiakapi ftw + public unsafe static byte[] AdjustVolume(byte[] audioSamples, float volume) + { + Contract.Requires(audioSamples != null); + Contract.Requires(audioSamples.Length % 2 == 0); + Contract.Requires(volume >= 0f && volume <= 1f); + Contract.Assert(BitConverter.IsLittleEndian); + + if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples; + + // 16-bit precision for the multiplication + int volumeFixed = (int)Math.Round(volume * 65536d); + + int count = audioSamples.Length / 2; + + fixed (byte* srcBytes = audioSamples) + { + short* src = (short*)srcBytes; + + for (int i = count; i != 0; i--, src++) + *src = (short)(((*src) * volumeFixed) >> 16); + } + + return audioSamples; + } + + public static async Task ResolveSong(string query, MusicType musicType = MusicType.Normal) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + + if (musicType != MusicType.Local && IsRadioLink(query)) + { + musicType = MusicType.Radio; + query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query; + } + + try + { + switch (musicType) + { + case MusicType.Local: + return new Song(new SongInfo + { + Uri = "\"" + Path.GetFullPath(query) + "\"", + Title = Path.GetFileNameWithoutExtension(query), + Provider = "Local File", + ProviderType = musicType, + Query = query, + }); + case MusicType.Radio: + return new Song(new SongInfo + { + Uri = query, + Title = $"{query}", + Provider = "Radio Stream", + ProviderType = musicType, + Query = query + }); + } + if (SoundCloud.Default.IsSoundCloudLink(query)) + { + var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false); + return new Song(new SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = svideo.StreamLink, + ProviderType = musicType, + Query = svideo.TrackLink, + }); + } + + if (musicType == MusicType.Soundcloud) + { + var svideo = await SoundCloud.Default.GetVideoByQueryAsync(query).ConfigureAwait(false); + return new Song(new SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = svideo.StreamLink, + ProviderType = MusicType.Normal, + Query = svideo.TrackLink, + }); + } + + var link = (await NadekoBot.Google.GetVideosByKeywordsAsync(query).ConfigureAwait(false)).FirstOrDefault(); + if (string.IsNullOrWhiteSpace(link)) + throw new OperationCanceledException("Not a valid youtube query."); + var allVideos = await Task.Run(async () => await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false)).ConfigureAwait(false); + var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio); + var video = videos + .Where(v => v.AudioBitrate < 192) + .OrderByDescending(v => v.AudioBitrate) + .FirstOrDefault(); + + if (video == null) // do something with this error + throw new Exception("Could not load any video elements based on the query."); + var m = Regex.Match(query, @"\?t=(?\d*)"); + int gotoTime = 0; + if (m.Captures.Count > 0) + int.TryParse(m.Groups["t"].ToString(), out gotoTime); + var song = new Song(new SongInfo + { + Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" + Provider = "YouTube", + Uri = video.Uri, + Query = link, + ProviderType = musicType, + }); + song.SkipTo = gotoTime; + return song; + } + catch (Exception ex) + { + Console.WriteLine($"Failed resolving the link.{ex.Message}"); + return null; + } + } + + private static async Task HandleStreamContainers(string query) + { + string file = null; + try + { + using (var http = new HttpClient()) + { + file = await http.GetStringAsync(query).ConfigureAwait(false); + } + } + catch + { + return query; + } + if (query.Contains(".pls")) + { + //File1=http://armitunes.com:8000/ + //Regex.Match(query) + try + { + var m = Regex.Match(file, "File1=(?.*?)\\n"); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + Console.WriteLine($"Failed reading .pls:\n{file}"); + return null; + } + } + if (query.Contains(".m3u")) + { + /* +# This is a comment + C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 + C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 + */ + try + { + var m = Regex.Match(file, "(?^[^#].*)", RegexOptions.Multiline); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + Console.WriteLine($"Failed reading .m3u:\n{file}"); + return null; + } + + } + if (query.Contains(".asx")) + { + // + try + { + var m = Regex.Match(file, ".*?)\""); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + Console.WriteLine($"Failed reading .asx:\n{file}"); + return null; + } + } + if (query.Contains(".xspf")) + { + /* + + + + file:///mp3s/song_1.mp3 + */ + try + { + var m = Regex.Match(file, "(?.*?)"); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + Console.WriteLine($"Failed reading .xspf:\n{file}"); + return null; + } + } + + return query; + } + + private static bool IsRadioLink(string query) => + (query.StartsWith("http") || + query.StartsWith("ww")) + && + (query.Contains(".pls") || + query.Contains(".m3u") || + query.Contains(".asx") || + query.Contains(".xspf")); + } +} diff --git a/src/NadekoBot/Modules/Music/Classes/SongBuffer.cs b/src/NadekoBot/Modules/Music/Classes/SongBuffer.cs new file mode 100644 index 00000000..d9192940 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Classes/SongBuffer.cs @@ -0,0 +1,159 @@ +using NadekoBot.Extensions; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Classes +{ + /// + /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. + /// It also help for large music by deleting files that are already seen. + /// + class SongBuffer + { + + public SongBuffer(string basename, SongInfo songInfo, int skipTo) + { + Basename = basename; + SongInfo = songInfo; + SkipTo = skipTo; + } + + private string Basename; + + private SongInfo SongInfo; + + private int SkipTo; + + private static int MAX_FILE_SIZE = 20.MiB(); + + private long FileNumber = -1; + + private long NextFileToRead = 0; + + public bool BufferingCompleted { get; private set;} = false; + + private ulong CurrentBufferSize = 0; + + public Task BufferSong(CancellationToken cancelToken) => + Task.Factory.StartNew(async () => + { + Process p = null; + FileStream outStream = null; + try + { + p = Process.Start(new ProcessStartInfo + { + FileName = "ffmpeg", + Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = false, + CreateNoWindow = true, + }); + + byte[] buffer = new byte[81920]; + int currentFileSize = 0; + ulong prebufferSize = 100ul.MiB(); + + outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); + while (!p.HasExited) //Also fix low bandwidth + { + int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false); + if (currentFileSize >= MAX_FILE_SIZE) + { + try + { + outStream.Dispose(); + }catch { } + outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); + currentFileSize = bytesRead; + } + else + { + currentFileSize += bytesRead; + } + CurrentBufferSize += Convert.ToUInt64(bytesRead); + await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); + while (CurrentBufferSize > prebufferSize) + await Task.Delay(100, cancelToken); + } + BufferingCompleted = true; + } + catch (System.ComponentModel.Win32Exception) + { + var oldclr = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(@"You have not properly installed or configured FFMPEG. +Please install and configure FFMPEG to play music. +Check the guides for your platform on how to setup ffmpeg correctly: + Windows Guide: https://goo.gl/SCv72y + Linux Guide: https://goo.gl/rRhjCp"); + Console.ForegroundColor = oldclr; + } + catch (Exception ex) + { + Console.WriteLine($"Buffering stopped: {ex.Message}"); + } + finally + { + if(outStream != null) + outStream.Dispose(); + Console.WriteLine($"Buffering done."); + if (p != null) + { + try + { + p.Kill(); + } + catch { } + p.Dispose(); + } + } + }, TaskCreationOptions.LongRunning); + + /// + /// Return the next file to read, and delete the old one + /// + /// Name of the file to read + public string GetNextFile() + { + string filename = Basename + "-" + NextFileToRead; + + if (NextFileToRead != 0) + { + try + { + CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length); + File.Delete(Basename + "-" + (NextFileToRead - 1)); + } + catch { } + } + NextFileToRead++; + return filename; + } + + public bool IsNextFileReady() + { + return NextFileToRead <= FileNumber; + } + + public void CleanFiles() + { + for (long i = NextFileToRead - 1 ; i <= FileNumber; i++) + { + try + { + File.Delete(Basename + "-" + i); + } + catch { } + } + } + } +} diff --git a/src/NadekoBot/Modules/Music/Classes/SoundCloud.cs b/src/NadekoBot/Modules/Music/Classes/SoundCloud.cs new file mode 100644 index 00000000..88c5bdbe --- /dev/null +++ b/src/NadekoBot/Modules/Music/Classes/SoundCloud.cs @@ -0,0 +1,141 @@ +using NadekoBot.Classes; +using Newtonsoft.Json; +using System; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Classes +{ + public class SoundCloud + { + private static readonly SoundCloud _instance = new SoundCloud(); + public static SoundCloud Default => _instance; + + static SoundCloud() { } + public SoundCloud() { } + + public async Task ResolveVideoAsync(string url) + { + if (string.IsNullOrWhiteSpace(url)) + throw new ArgumentNullException(nameof(url)); + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId)) + throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId)); + + string response = ""; + + using (var http = new HttpClient()) + { + response = await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false); + + } + + + var responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject(response); + if (responseObj?.Kind != "track") + throw new InvalidOperationException("Url is either not a track, or it doesn't exist."); + + return responseObj; + } + + public bool IsSoundCloudLink(string url) => + System.Text.RegularExpressions.Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)"); + + internal async Task GetVideoByQueryAsync(string query) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId)) + throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId)); + + var response = ""; + using (var http = new HttpClient()) + { + await http.GetStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false); + } + + var responseObj = JsonConvert.DeserializeObject(response).Where(s => s.Streamable).FirstOrDefault(); + if (responseObj?.Kind != "track") + throw new InvalidOperationException("Query yielded no results."); + + return responseObj; + } + } + + public class SoundCloudVideo + { + public string Kind { get; set; } = ""; + public long Id { get; set; } = 0; + public SoundCloudUser User { get; set; } = new SoundCloudUser(); + public string Title { get; set; } = ""; + [JsonIgnore] + public string FullName => User.Name + " - " + Title; + public bool Streamable { get; set; } = false; + [JsonProperty("permalink_url")] + public string TrackLink { get; set; } = ""; + [JsonIgnore] + public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Credentials.SoundCloudClientId}"; + } + public class SoundCloudUser + { + [Newtonsoft.Json.JsonProperty("username")] + public string Name { get; set; } + } + /* + {"kind":"track", + "id":238888167, + "created_at":"2015/12/24 01:04:52 +0000", + "user_id":43141975, + "duration":120852, + "commentable":true, + "state":"finished", + "original_content_size":4834829, + "last_modified":"2015/12/24 01:17:59 +0000", + "sharing":"public", + "tag_list":"Funky", + "permalink":"18-fd", + "streamable":true, + "embeddable_by":"all", + "downloadable":false, + "purchase_url":null, + "label_id":null, + "purchase_title":null, + "genre":"Disco", + "title":"18 Ж", + "description":"", + "label_name":null, + "release":null, + "track_type":null, + "key_signature":null, + "isrc":null, + "video_url":null, + "bpm":null, + "release_year":null, + "release_month":null, + "release_day":null, + "original_format":"mp3", + "license":"all-rights-reserved", + "uri":"https://api.soundcloud.com/tracks/238888167", + "user":{ + "id":43141975, + "kind":"user", + "permalink":"mrb00gi", + "username":"Mrb00gi", + "last_modified":"2015/12/01 16:06:57 +0000", + "uri":"https://api.soundcloud.com/users/43141975", + "permalink_url":"http://soundcloud.com/mrb00gi", + "avatar_url":"https://a1.sndcdn.com/images/default_avatar_large.png" + }, + "permalink_url":"http://soundcloud.com/mrb00gi/18-fd", + "artwork_url":null, + "waveform_url":"https://w1.sndcdn.com/gsdLfvEW1cUK_m.png", + "stream_url":"https://api.soundcloud.com/tracks/238888167/stream", + "playback_count":7, + "download_count":0, + "favoritings_count":1, + "comment_count":0, + "attachments_uri":"https://api.soundcloud.com/tracks/238888167/attachments"} + + */ + +} From f5cb629a6362c72ffe94c250a9bb5fb6e5da0a27 Mon Sep 17 00:00:00 2001 From: appelemac Date: Mon, 22 Aug 2016 12:07:40 +0200 Subject: [PATCH 7/9] remove this? --- src/NadekoBot/Services/IYoutubeService.cs | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/NadekoBot/Services/IYoutubeService.cs diff --git a/src/NadekoBot/Services/IYoutubeService.cs b/src/NadekoBot/Services/IYoutubeService.cs deleted file mode 100644 index bf511f00..00000000 --- a/src/NadekoBot/Services/IYoutubeService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace NadekoBot.Services -{ - public interface IYoutubeService - { - Task> FindVideosByKeywordsAsync(string keywords, int count = 1); - Task> FindPlaylistIdsByKeywordsAsync(string keywords, int count = 1); - Task> FindRelatedVideosAsync(string url, int count = 1); - } -} From f66d42b891dc9690c5b60b0b00c3637fd07c19ff Mon Sep 17 00:00:00 2001 From: appelemac Date: Mon, 22 Aug 2016 12:09:36 +0200 Subject: [PATCH 8/9] stage for PR --- src/NadekoBot/Modules/Searches/Searches.cs | 1 + src/NadekoBot/NadekoBot.cs | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index b2159604..1f587161 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -25,6 +25,7 @@ namespace NadekoBot.Modules.Searches { _google = youtube; } + [LocalizedCommand, LocalizedDescription, LocalizedSummary] [RequireContext(ContextType.Guild)] public async Task Weather(IMessage imsg, string city, string country) diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index 7f5bbbdf..d71c5364 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -29,7 +29,7 @@ namespace NadekoBot public async Task RunAsync(string[] args) { SetupLogger(); - _log.Debug("Logger created, starting client"); + //create client Client = new DiscordSocketClient(new DiscordSocketConfig { @@ -45,7 +45,7 @@ namespace NadekoBot Localizer = new Localization(); Google = new GoogleApiService(); Stats = new StatsService(Client); - + _log = LogManager.GetCurrentClassLogger(); //setup DI var depMap = new DependencyMap(); @@ -72,7 +72,6 @@ namespace NadekoBot private void SetupLogger() { - try { var logConfig = new LoggingConfiguration(); @@ -85,12 +84,10 @@ namespace NadekoBot logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); LogManager.Configuration = logConfig; - } catch (Exception ex) { Console.WriteLine(ex); } - _log = LogManager.GetCurrentClassLogger(); } private Task Client_MessageReceived(IMessage imsg) From 9bd919b450b2a5a69c05a81e98254f27d0195ddf Mon Sep 17 00:00:00 2001 From: Nitix Date: Mon, 22 Aug 2016 16:52:13 +0200 Subject: [PATCH 9/9] Fix appveyor build --- appveyor.yml | 11 +++++++++++ global.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..1b709532 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,11 @@ +version: 1.0.{build} +before_build: +- cmd: >- + git submodule update --init --recursive + + dotnet restore + + cd src/NadekoBot/ +build_script: +- cmd: >- + dotnet build \ No newline at end of file diff --git a/global.json b/global.json index 7e16c1f1..18abbe56 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { - "projects": [ "discord.net/src/Discord.Net", "discord.net/src/Discord.Net.Commands", "src" ], + "projects": [ "discord.net/src", "src" ], "sdk": { "version": "1.0.0-preview2-003121" }