diff --git a/NadekoBot/Classes/Extensions.cs b/NadekoBot/Classes/Extensions.cs index 92fcdddc..5ab8cabf 100644 --- a/NadekoBot/Classes/Extensions.cs +++ b/NadekoBot/Classes/Extensions.cs @@ -29,7 +29,6 @@ namespace NadekoBot.Extensions { if (letters[i] != ' ') letters[i] = '_'; - } return "`"+string.Join(" ", letters)+"`"; } @@ -153,7 +152,7 @@ namespace NadekoBot.Extensions { /// /// /// - public static string ShortenUrl(this string str) => Searches.ShortenUrl(str); + public static async Task ShortenUrl(this string str) => await Searches.ShortenUrl(str); /// /// Gets the program runtime diff --git a/NadekoBot/Classes/Music/MusicControls.cs b/NadekoBot/Classes/Music/MusicControls.cs index cc70a683..97e0bb03 100644 --- a/NadekoBot/Classes/Music/MusicControls.cs +++ b/NadekoBot/Classes/Music/MusicControls.cs @@ -15,6 +15,7 @@ namespace NadekoBot.Classes.Music { public bool Pause = false; public List SongQueue = new List(); public StreamRequest CurrentSong = null; + public float Volume { get; set; } = 1.0f; public bool IsPaused { get; internal set; } = false; public bool Stopped { get; private set; } @@ -67,7 +68,7 @@ namespace NadekoBot.Classes.Music { VoiceClient = await NadekoBot.client.Audio().Join(VoiceChannel); Console.WriteLine($"Joined voicechannel [{DateTime.Now.Second}]"); } - await CurrentSong.Start(); + await Task.Factory.StartNew(async () => await CurrentSong.Start(), TaskCreationOptions.LongRunning).Unwrap(); } catch (Exception ex) { Console.WriteLine($"Starting failed: {ex}"); CurrentSong?.Stop(); @@ -77,10 +78,6 @@ namespace NadekoBot.Classes.Music { internal void Stop() { Stopped = true; - foreach (var kvp in SongQueue) { - if (kvp != null) - kvp.Stop(); - } SongQueue.Clear(); CurrentSong?.Stop(); CurrentSong = null; @@ -91,6 +88,14 @@ namespace NadekoBot.Classes.Music { MusicModule.musicPlayers.TryRemove(_e.Server, out throwAwayValue); } + public void SetVolume(int value) { + if (value < 0) + value = 0; + if (value > 150) + value = 150; + this.Volume = value/100f; + } + internal bool TogglePause() => IsPaused = !IsPaused; } } diff --git a/NadekoBot/Classes/Music/StreamRequest.cs b/NadekoBot/Classes/Music/StreamRequest.cs index 34bb1a7d..6b1449cf 100644 --- a/NadekoBot/Classes/Music/StreamRequest.cs +++ b/NadekoBot/Classes/Music/StreamRequest.cs @@ -36,6 +36,8 @@ namespace NadekoBot.Classes.Music { public bool IsPaused => MusicControls.IsPaused; + public float Volume => MusicControls?.Volume ?? 1.0f; + public MusicControls MusicControls; public StreamRequest(CommandEventArgs e, string query, MusicControls mc) { @@ -50,17 +52,17 @@ namespace NadekoBot.Classes.Music { mc.SongQueue.Add(this); } - private void ResolveStreamLink() { + private async void ResolveStreamLink() { Video video = null; try { if (OnResolving != null) OnResolving(); - Console.WriteLine("Resolving video link"); - - video = YouTube.Default.GetAllVideos(Searches.FindYoutubeUrlByKeywords(Query)) - .Where(v => v.AdaptiveKind == AdaptiveKind.Audio) - .OrderByDescending(v => v.AudioBitrate) - .FirstOrDefault(); + var links = await Searches.FindYoutubeUrlByKeywords(Query); + var videos = await YouTube.Default.GetAllVideosAsync(links); + video = videos + .Where(v => v.AdaptiveKind == AdaptiveKind.Audio) + .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."); @@ -77,7 +79,6 @@ namespace NadekoBot.Classes.Music { musicStreamer = new MusicStreamer(this, video.Uri); if (OnQueued != null) OnQueued(); - return; } internal string PrintStats() => musicStreamer?.Stats(); @@ -98,14 +99,11 @@ namespace NadekoBot.Classes.Music { } internal async Task Start() { - Console.WriteLine("Start called."); - int attemptsLeft = 4; //wait for up to 4 seconds to resolve a link try { while (State == StreamState.Resolving) { await Task.Delay(1000); - Console.WriteLine("Resolving..."); if (--attemptsLeft == 0) { throw new TimeoutException("Resolving timed out."); } @@ -137,7 +135,6 @@ namespace NadekoBot.Classes.Music { this.parent = parent; this.buffer = new DualStream(); this.Url = directUrl; - Console.WriteLine("Created new streamer"); State = StreamState.Queued; } @@ -149,7 +146,6 @@ namespace NadekoBot.Classes.Music { "--------------------------------\n"; private async Task BufferSong() { - Console.WriteLine("Buffering..."); //start feeding the buffer var p = Process.Start(new ProcessStartInfo { FileName = "ffmpeg", @@ -162,11 +158,11 @@ namespace NadekoBot.Classes.Music { }); int attempt = 0; while (true) { - + int magickBuffer = 1; //wait for the read pos to catch up with write pos - while (buffer.writePos - buffer.readPos > 5.MB() && State != StreamState.Completed) { + while (buffer.writePos - buffer.readPos > 1.MB() && State != StreamState.Completed) { prebufferingComplete = true; - await Task.Delay(200); + await Task.Delay(150); } if (State == StreamState.Completed) { @@ -178,10 +174,8 @@ namespace NadekoBot.Classes.Music { return; } - if (buffer.readPos > 5.MiB()) { // if buffer is over 5 MiB, create new one - Console.WriteLine("Buffer over 5 megs, clearing."); - - var skip = 5.MB(); //remove only 5 MB, just in case + if (buffer.readPos > 1.MiB() && buffer.writePos > 1.MiB()) { // if buffer is over 5 MiB, create new one + var skip = 1.MB(); //remove only 5 MB, just in case var newBuffer = new DualStream(); lock (_bufferLock) { @@ -225,7 +219,10 @@ namespace NadekoBot.Classes.Music { State = StreamState.Playing; if (parent.OnBuffering != null) parent.OnBuffering(); - BufferSong(); + + Task.Factory.StartNew(async () => { + await BufferSong(); + }, TaskCreationOptions.LongRunning).ConfigureAwait(false); // prebuffering wait stuff start int bufferAttempts = 0; @@ -253,6 +250,8 @@ namespace NadekoBot.Classes.Music { int attempt = 0; while (!IsCanceled) { int readCount = 0; + //adjust volume + lock (_bufferLock) { readCount = buffer.Read(voiceBuffer, 0, voiceBuffer.Length); } @@ -272,7 +271,7 @@ namespace NadekoBot.Classes.Music { Console.WriteLine("Canceled"); break; } - + voiceBuffer = adjustVolume(voiceBuffer, parent.Volume); parent.MusicControls.VoiceClient.Send(voiceBuffer, 0, voiceBuffer.Length); while (IsPaused) { @@ -288,11 +287,36 @@ namespace NadekoBot.Classes.Music { } internal void Stop() { - Console.WriteLine($"Stopping playback [{DateTime.Now.Second}]"); - if (State != StreamState.Completed) { - State = StreamState.Completed; - parent.OnCompleted(); + if (State == StreamState.Completed) return; + var oldState = State; + State = StreamState.Completed; + if (oldState == StreamState.Playing) + if (parent.OnCompleted != null) + parent.OnCompleted(); + } + //stackoverflow ftw + private byte[] adjustVolume(byte[] audioSamples, float volume) { + if (volume == 1.0f) + return audioSamples; + byte[] array = new byte[audioSamples.Length]; + for (int 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); + + short res = (short)(buf1 | buf2); + res = (short)(res * volume); + + // convert back + array[i] = (byte)res; + array[i + 1] = (byte)(res >> 8); + } + return array; } } diff --git a/NadekoBot/Modules/Conversations.cs b/NadekoBot/Modules/Conversations.cs index 12b1d9ab..01572359 100644 --- a/NadekoBot/Modules/Conversations.cs +++ b/NadekoBot/Modules/Conversations.cs @@ -266,44 +266,6 @@ namespace NadekoBot.Modules { await e.Send("Invalid code."); } }); - - cgb.CreateCommand("save") - .Description("Saves something for the owner in a file.") - .Parameter("all", ParameterType.Unparsed) - .Do(async e => { - if (e.User.Id == NadekoBot.OwnerID) { - string m = ""; - try { - FileStream f = File.OpenWrite("saves.txt"); - m = e.Args[0]; - byte[] b = Encoding.ASCII.GetBytes(m + "\n"); - f.Seek(f.Length, SeekOrigin.Begin); - f.Write(b, 0, b.Length); - f.Close(); - } catch (Exception) { - await e.Send("Error saving. Sorry :("); - } - if (m.Length > 0) - await e.Send("I saved this for you: " + Environment.NewLine + "```" + m + "```"); - else - await e.Send("No point in saving empty message..."); - } else await e.Send("Not for you, only my Master <3"); - }); - - cgb.CreateCommand("ls") - .Description("Shows all saved items.") - .Do(async e => { - FileStream f = File.OpenRead("saves.txt"); - if (f.Length == 0) { - await e.Send("Saves are empty."); - return; - } - byte[] b = new byte[f.Length / sizeof(byte)]; - f.Read(b, 0, b.Length); - f.Close(); - string str = Encoding.ASCII.GetString(b); - await e.User.Send("```" + (str.Length < 1950 ? str : str.Substring(0, 1950)) + "```"); - }); cgb.CreateCommand("slm") .Description("Shows the message where you were last mentioned in this channel (checks last 10k messages)") .Do(async e => { @@ -331,12 +293,6 @@ namespace NadekoBot.Modules { else await e.Send("I can't find a message mentioning you."); }); - cgb.CreateCommand("cs") - .Description("Deletes all saves") - .Do(async e => { - File.Delete("saves.txt"); - await e.Send("Cleared all saves."); - }); cgb.CreateCommand("bb") .Description("Says bye to someone. **Usage**: @NadekoBot bb @X") @@ -416,8 +372,7 @@ namespace NadekoBot.Modules { await e.Send("Invalid user specified."); return; } - string av = usr.AvatarUrl; - await e.Send(Searches.ShortenUrl(av)); + await e.Send(await usr.AvatarUrl.ShortenUrl()); }); //TODO add eval diff --git a/NadekoBot/Modules/Gambling.cs b/NadekoBot/Modules/Gambling.cs index dff0ffa6..b947fa09 100644 --- a/NadekoBot/Modules/Gambling.cs +++ b/NadekoBot/Modules/Gambling.cs @@ -20,10 +20,9 @@ namespace NadekoBot.Modules commands.ForEach(com => com.Init(cgb)); cgb.CreateCommand("$raffle") - .Description("Mentions a user from the online list from the (optional) role.") + .Description("Prints a name and ID of a random user from the online list from the (optional) role.") .Parameter("role", ParameterType.Optional) .Do(async e => { - if (!e.User.ServerPermissions.MentionEveryone) return; var arg = string.IsNullOrWhiteSpace(e.GetArg("role")) ? "@everyone" : e.GetArg("role"); var role = e.Server.FindRoles(arg).FirstOrDefault(); if (role == null) { diff --git a/NadekoBot/Modules/Music.cs b/NadekoBot/Modules/Music.cs index 0c0d4e22..674531eb 100644 --- a/NadekoBot/Modules/Music.cs +++ b/NadekoBot/Modules/Music.cs @@ -7,6 +7,7 @@ using NadekoBot.Extensions; using System.Collections.Concurrent; using NadekoBot.Classes.Music; using Timer = System.Timers.Timer; +using System.Threading.Tasks; namespace NadekoBot.Modules { class Music : DiscordModule { @@ -20,19 +21,6 @@ namespace NadekoBot.Modules { } public Music() : base() { - /*Timer cleaner = new Timer(); - cleaner.Elapsed += (s, e) => System.Threading.Tasks.Task.Run(() => CleanMusicPlayers()); - cleaner.Interval = 10000; - cleaner.Start(); - - Timer statPrinter = new Timer(); - NadekoBot.client.Connected += (s, e) => { - if (statPrinter.Enabled) return; - statPrinter.Elapsed += (se, ev) => { Console.WriteLine($"<<--Music-->> {musicPlayers.Count} songs playing."); musicPlayers.ForEach(kvp => Console.WriteLine(kvp.Value?.CurrentSong?.PrintStats())); Console.WriteLine("<<--Music END-->>"); }; - statPrinter.Interval = 5000; - statPrinter.Start(); - }; - */ } public override void Install(ModuleManager manager) { @@ -45,9 +33,9 @@ namespace NadekoBot.Modules { cgb.CreateCommand("n") .Alias("next") .Description("Goes to the next song in the queue.") - .Do(e => { + .Do(async e => { if (musicPlayers.ContainsKey(e.Server) == false) return; - musicPlayers[e.Server].LoadNextSong(); + await musicPlayers[e.Server].LoadNextSong(); }); cgb.CreateCommand("s") @@ -73,67 +61,7 @@ namespace NadekoBot.Modules { .Alias("yq") .Description("Queue a song using keywords or link. **You must be in a voice channel**.\n**Usage**: `!m q Dream Of Venice`") .Parameter("query", ParameterType.Unparsed) - .Do(async e => { - if (e.User.VoiceChannel?.Server != e.Server) { - await e.Send(":anger: You need to be in the voice channel on this server."); - return; - } - if (musicPlayers.ContainsKey(e.Server) == false) - if (!musicPlayers.TryAdd(e.Server, new MusicControls(e.User.VoiceChannel, e))) { - await e.Send("Failed to create a music player for this server"); - return; - } - if (e.GetArg("query") == null || e.GetArg("query").Length < 4) - return; - - var player = musicPlayers[e.Server]; - - if (player.SongQueue.Count > 25) { - await e.Send("Music player supports up to 25 songs atm. Contant the owner if you think this is not enough :warning:"); - } - - try { - - Message qmsg = await e.Channel.SendMessage(":musical_note: **Searching...**"); - - var sr = new StreamRequest(e, e.GetArg("query"), player); - - if (sr == null) - throw new NullReferenceException("StreamRequest is null."); - Message msg = null; - sr.OnResolving += async () => { - await qmsg.Edit($":musical_note: **Resolving**... \"{e.GetArg("query")}\""); - }; - sr.OnResolvingFailed += async (err) => { - await qmsg.Edit($":anger: :musical_note: **Resolving failed** for `{e.GetArg("query")}`"); - }; - sr.OnQueued += async () => { - await qmsg.Edit($":musical_note:**Queued** {sr.Title.TrimTo(55)}"); - }; - sr.OnCompleted += async () => { - MusicControls mc; - if (musicPlayers.TryGetValue(e.Server, out mc)) { - if (mc.SongQueue.Count == 0) - mc.Stop(); - } - await e.Send($":musical_note:**Finished playing** {sr.Title.TrimTo(55)}"); - }; - sr.OnStarted += async () => { - if (msg == null) - await e.Send($":musical_note:**Playing ** {sr.Title.TrimTo(55)}"); - else - await msg.Edit($":musical_note:**Playing ** {sr.Title.TrimTo(55)}"); - qmsg?.Delete(); - }; - sr.OnBuffering += async () => { - msg = await e.Send($":musical_note:**Buffering...** {sr.Title.TrimTo(55)}"); - }; - } catch (Exception ex) { - Console.WriteLine(); - await e.Send($":anger: {ex.Message}"); - return; - } - }); + .Do(async e => await QueueSong(e,e.GetArg("query"))); cgb.CreateCommand("lq") .Alias("ls").Alias("lp") @@ -141,8 +69,10 @@ namespace NadekoBot.Modules { .Do(async e => { if (musicPlayers.ContainsKey(e.Server) == false) await e.Send(":musical_note: No active music player."); var player = musicPlayers[e.Server]; - - await e.Send(":musical_note: " + player.SongQueue.Count + " videos currently queued."); + string toSend = ":musical_note: " + player.SongQueue.Count + " videos currently queued. "; + if (player.SongQueue.Count >= 25) + toSend += "**Song queue is full!**\n"; + await e.Send(toSend); int number = 1; await e.Send(string.Join("\n", player.SongQueue.Select(v => $"**#{number++}** {v.Title.TrimTo(60)}").Take(10))); }); @@ -156,6 +86,45 @@ namespace NadekoBot.Modules { await e.Send($"Now Playing **{player.CurrentSong.Title}**"); }); + cgb.CreateCommand("vol") + .Description("Sets the music volume 0-150%") + .Parameter("val", ParameterType.Required) + .Do(async e => { + if (musicPlayers.ContainsKey(e.Server) == false) return; + var player = musicPlayers[e.Server]; + var arg = e.GetArg("val"); + int volume; + if (!int.TryParse(arg, out volume)) { + await e.Send("Volume number invalid."); + return; + } + player.SetVolume(volume); + }); + + cgb.CreateCommand("min").Alias("mute") + .Description("Sets the music volume to 0%") + .Do(e => { + if (musicPlayers.ContainsKey(e.Server) == false) return; + var player = musicPlayers[e.Server]; + player.SetVolume(0); + }); + + cgb.CreateCommand("max") + .Description("Sets the music volume to 100% (real max is actually 150%).") + .Do(e => { + if (musicPlayers.ContainsKey(e.Server) == false) return; + var player = musicPlayers[e.Server]; + player.SetVolume(100); + }); + + cgb.CreateCommand("half") + .Description("Sets the music volume to 50%.") + .Do(e => { + if (musicPlayers.ContainsKey(e.Server) == false) return; + var player = musicPlayers[e.Server]; + player.SetVolume(50); + }); + cgb.CreateCommand("sh") .Description("Shuffles the current playlist.") .Do(async e => { @@ -191,6 +160,19 @@ namespace NadekoBot.Modules { await e.Send("Music status " + (setgameEnabled ? "enabled" : "disabled")); }); + cgb.CreateCommand("pl") + .Description("Queues up to 25 songs from a youtube playlist") + .Parameter("playlist", ParameterType.Unparsed) + .Do(async e => { + var ids = await Searches.GetVideoIDs(await Searches.GetPlaylistIdByKeyword(e.GetArg("playlist"))); + //todo TEMPORARY SOLUTION, USE RESOLVE QUEUE IN THE FUTURE + await e.Send($"Attempting to queue {ids.Count} songs".SnPl(ids.Count)); + foreach (var id in ids) { + Task.Run(async () => await QueueSong(e, id, true)).ConfigureAwait(false); + await Task.Delay(150); + } + }); + cgb.CreateCommand("debug") .Description("Writes some music data to console. **BOT OWNER ONLY**") .Do(e => { @@ -200,5 +182,67 @@ namespace NadekoBot.Modules { }); }); } + + private async Task QueueSong(CommandEventArgs e, string query, bool silent = false) { + if (e.User.VoiceChannel?.Server != e.Server) { + await e.Send(":anger: You need to be in the voice channel on this server."); + return; + } + if (musicPlayers.ContainsKey(e.Server) == false) + if (!musicPlayers.TryAdd(e.Server, new MusicControls(e.User.VoiceChannel, e))) { + await e.Send("Failed to create a music player for this server"); + return; + } + if (query == null || query.Length < 4) + return; + + var player = musicPlayers[e.Server]; + + if (player.SongQueue.Count >= 25) return; + + try { + var sr = await Task.Run(() => new StreamRequest(e, query, player)); + + if (sr == null) + throw new NullReferenceException("StreamRequest is null."); + + Message qmsg = null; + Message msg = null; + if (!silent) { + qmsg = await e.Channel.SendMessage(":musical_note: **Searching...**"); + sr.OnResolving += async () => { + await qmsg.Edit($":musical_note: **Resolving**... \"{query}\""); + }; + sr.OnResolvingFailed += async (err) => { + await qmsg.Edit($":anger: :musical_note: **Resolving failed** for `{query}`"); + }; + sr.OnQueued += async () => { + await qmsg.Edit($":musical_note:**Queued** {sr.Title.TrimTo(55)}"); + }; + } + sr.OnCompleted += async () => { + MusicControls mc; + if (musicPlayers.TryGetValue(e.Server, out mc)) { + if (mc.SongQueue.Count == 0) + mc.Stop(); + } + await e.Send($":musical_note:**Finished playing** {sr.Title.TrimTo(55)}"); + }; + sr.OnStarted += async () => { + if (msg == null) + await e.Send($":musical_note:**Playing ** {sr.Title.TrimTo(55)} **Volume:** {(int)(player.Volume * 100)}%"); + else + await msg.Edit($":musical_note:**Playing ** {sr.Title.TrimTo(55)} **Volume:** {(int)(player.Volume * 100)}%"); + qmsg?.Delete(); + }; + sr.OnBuffering += async () => { + msg = await e.Send($":musical_note:**Buffering...** {sr.Title.TrimTo(55)}"); + }; + } catch (Exception ex) { + Console.WriteLine(); + await e.Send($":anger: {ex.Message}"); + return; + } + } } } diff --git a/NadekoBot/Modules/Searches.cs b/NadekoBot/Modules/Searches.cs index 8c9cac5c..8c203c34 100644 --- a/NadekoBot/Modules/Searches.cs +++ b/NadekoBot/Modules/Searches.cs @@ -24,12 +24,12 @@ namespace NadekoBot.Modules { commands.ForEach(cmd => cmd.Init(cgb)); cgb.CreateCommand("~yt") - .Parameter("query", Discord.Commands.ParameterType.Unparsed) - .Description("Queries youtubes and embeds the first result") + .Parameter("query", ParameterType.Unparsed) + .Description("Searches youtubes and shows the first result") .Do(async e => { if (!(await ValidateQuery(e.Channel, e.GetArg("query")))) return; - var str = ShortenUrl(FindYoutubeUrlByKeywords(e.GetArg("query"))); + var str = await ShortenUrl(await FindYoutubeUrlByKeywords(e.GetArg("query"))); if (string.IsNullOrEmpty(str.Trim())) { await e.Send("Query failed"); return; @@ -39,12 +39,12 @@ namespace NadekoBot.Modules { cgb.CreateCommand("~ani") .Alias("~anime").Alias("~aq") - .Parameter("query", Discord.Commands.ParameterType.Unparsed) + .Parameter("query", ParameterType.Unparsed) .Description("Queries anilist for an anime and shows the first result.") .Do(async e => { if (!(await ValidateQuery(e.Channel, e.GetArg("query")))) return; - var result = GetAnimeQueryResultLink(e.GetArg("query")); + var result = await GetAnimeQueryResultLink(e.GetArg("query")); if (result == null) { await e.Send("Failed to find that anime."); return; @@ -55,12 +55,12 @@ namespace NadekoBot.Modules { cgb.CreateCommand("~mang") .Alias("~manga").Alias("~mq") - .Parameter("query", Discord.Commands.ParameterType.Unparsed) + .Parameter("query", ParameterType.Unparsed) .Description("Queries anilist for a manga and shows the first result.") .Do(async e => { if (!(await ValidateQuery(e.Channel, e.GetArg("query")))) return; - var result = GetMangaQueryResultLink(e.GetArg("query")); + var result = await GetMangaQueryResultLink(e.GetArg("query")); if (result == null) { await e.Send("Failed to find that anime."); return; @@ -105,8 +105,8 @@ namespace NadekoBot.Modules { string tag = e.GetArg("tag"); if (tag == null) tag = ""; - await e.Send(":heart: Gelbooru: " + GetGelbooruImageLink(tag)); - await e.Send(":heart: Danbooru: " + GetDanbooruImageLink(tag)); + await e.Send(":heart: Gelbooru: " + await GetGelbooruImageLink(tag)); + await e.Send(":heart: Danbooru: " + await GetDanbooruImageLink(tag)); }); cgb.CreateCommand("~danbooru") .Description("Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered.\n**Usage**: ~hentai yuri") @@ -115,7 +115,7 @@ namespace NadekoBot.Modules { string tag = e.GetArg("tag"); if (tag == null) tag = ""; - await e.Send(GetDanbooruImageLink(tag)); + await e.Send(await GetDanbooruImageLink(tag)); }); cgb.CreateCommand("~gelbooru") .Description("Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered.\n**Usage**: ~hentai yuri") @@ -124,7 +124,7 @@ namespace NadekoBot.Modules { string tag = e.GetArg("tag"); if (tag == null) tag = ""; - await e.Send(GetGelbooruImageLink(tag)); + await e.Send(await GetGelbooruImageLink(tag)); }); cgb.CreateCommand("~cp") .Description("We all know where this will lead you to.") @@ -137,21 +137,21 @@ namespace NadekoBot.Modules { .Parameter("ffs", ParameterType.Unparsed) .Do(async e => { if (e.GetArg("ffs") == null || e.GetArg("ffs").Length < 1) return; - await e.Send($"http://lmgtfy.com/?q={ Uri.EscapeUriString(e.GetArg("ffs").ToString()) }".ShortenUrl()); + await e.Send(await $"http://lmgtfy.com/?q={ Uri.EscapeUriString(e.GetArg("ffs").ToString()) }".ShortenUrl()); }); }); } - public static string MakeRequestAndGetResponse(string v) => - new StreamReader(((HttpWebRequest)WebRequest.Create(v)).GetResponse().GetResponseStream()).ReadToEnd(); + public static async Task GetResponseAsync(string v) => + await new StreamReader((await ((HttpWebRequest)WebRequest.Create(v)).GetResponseAsync()).GetResponseStream()).ReadToEndAsync(); private string token = ""; - private AnimeResult GetAnimeQueryResultLink(string query) { + private async Task GetAnimeQueryResultLink(string query) { try { var cl = new RestSharp.RestClient("http://anilist.co/api"); var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST); - RefreshToken(); + RefreshAnilistToken(); rq = new RestSharp.RestRequest("/anime/search/" + Uri.EscapeUriString(query)); rq.AddParameter("access_token", token); @@ -160,15 +160,15 @@ namespace NadekoBot.Modules { rq = new RestSharp.RestRequest("anime/" + smallObj["id"]); rq.AddParameter("access_token", token); - return JsonConvert.DeserializeObject(cl.Execute(rq).Content); + return await Task.Run(() => JsonConvert.DeserializeObject(cl.Execute(rq).Content)); } catch (Exception) { return null; } } - - private MangaResult GetMangaQueryResultLink(string query) { + //todo kick out RestSharp and make it truly async + private async Task GetMangaQueryResultLink(string query) { try { - RefreshToken(); + RefreshAnilistToken(); var cl = new RestSharp.RestClient("http://anilist.co/api"); var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST); @@ -179,27 +179,31 @@ namespace NadekoBot.Modules { rq = new RestSharp.RestRequest("manga/" + smallObj["id"]); rq.AddParameter("access_token", token); - return JsonConvert.DeserializeObject(cl.Execute(rq).Content); + return await Task.Run(() => JsonConvert.DeserializeObject(cl.Execute(rq).Content)); } catch (Exception ex) { Console.WriteLine(ex.ToString()); return null; } } - private void RefreshToken() { - var cl = new RestSharp.RestClient("http://anilist.co/api"); - var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST); - rq.AddParameter("grant_type", "client_credentials"); - rq.AddParameter("client_id", "kwoth-w0ki9"); - rq.AddParameter("client_secret", "Qd6j4FIAi1ZK6Pc7N7V4Z"); - var exec = cl.Execute(rq); - /* - Console.WriteLine($"Server gave me content: { exec.Content }\n{ exec.ResponseStatus } -> {exec.ErrorMessage} "); - Console.WriteLine($"Err exception: {exec.ErrorException}"); - Console.WriteLine($"Inner: {exec.ErrorException.InnerException}"); - */ + private void RefreshAnilistToken() { + try { + var cl = new RestSharp.RestClient("http://anilist.co/api"); + var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST); + rq.AddParameter("grant_type", "client_credentials"); + rq.AddParameter("client_id", "kwoth-w0ki9"); + rq.AddParameter("client_secret", "Qd6j4FIAi1ZK6Pc7N7V4Z"); + var exec = cl.Execute(rq); + /* + Console.WriteLine($"Server gave me content: { exec.Content }\n{ exec.ResponseStatus } -> {exec.ErrorMessage} "); + Console.WriteLine($"Err exception: {exec.ErrorException}"); + Console.WriteLine($"Inner: {exec.ErrorException.InnerException}"); + */ - token = JObject.Parse(exec.Content)["access_token"].ToString(); + token = JObject.Parse(exec.Content)["access_token"].ToString(); + } catch (Exception ex) { + Console.WriteLine($"Failed refreshing anilist token:\n {ex}"); + } } private static async Task ValidateQuery(Discord.Channel ch, string query) { @@ -210,7 +214,7 @@ namespace NadekoBot.Modules { return true; } - public static string FindYoutubeUrlByKeywords(string v) { + public static async Task FindYoutubeUrlByKeywords(string v) { if (NadekoBot.GoogleAPIKey == "" || NadekoBot.GoogleAPIKey == null) { Console.WriteLine("ERROR: No google api key found. Playing `Never gonna give you up`."); return @"https://www.youtube.com/watch?v=dQw4w9WgXcQ"; @@ -223,21 +227,19 @@ namespace NadekoBot.Modules { return str; } - WebRequest wr = WebRequest.Create("https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=1&q=" + Uri.EscapeDataString(v) + "&key=" + NadekoBot.GoogleAPIKey); - var sr = new StreamReader(wr.GetResponse().GetResponseStream()); + var sr = new StreamReader((await wr.GetResponseAsync()).GetResponseStream()); - dynamic obj = JObject.Parse(sr.ReadToEnd()); - string toReturn = "http://www.youtube.com/watch?v=" + obj.items[0].id.videoId.ToString(); - return toReturn; + dynamic obj = JObject.Parse(await sr.ReadToEndAsync()); + return "http://www.youtube.com/watch?v=" + obj.items[0].id.videoId.ToString(); } catch (Exception ex) { Console.WriteLine($"Error in findyoutubeurl: {ex.Message}"); return string.Empty; } } - public static string GetPlaylistIdByKeyword(string v) { + public static async Task GetPlaylistIdByKeyword(string v) { if (NadekoBot.GoogleAPIKey == "" || NadekoBot.GoogleAPIKey == null) { Console.WriteLine("ERROR: No google api key found. Playing `Never gonna give you up`."); return string.Empty; @@ -245,33 +247,32 @@ namespace NadekoBot.Modules { try { WebRequest wr = WebRequest.Create($"https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=1&q={Uri.EscapeDataString(v)}&type=playlist&key={NadekoBot.creds.GoogleAPIKey}"); - var sr = new StreamReader(wr.GetResponse().GetResponseStream()); + var sr = new StreamReader((await wr.GetResponseAsync()).GetResponseStream()); - dynamic obj = JObject.Parse(sr.ReadToEnd()); - string toReturn = obj.items[0].id.playlistId.ToString(); - return toReturn; + dynamic obj = JObject.Parse(await sr.ReadToEndAsync()); + return obj.items[0].id.playlistId.ToString(); } catch (Exception ex) { Console.WriteLine($"Error in GetPlaylistId: {ex.Message}"); return string.Empty; } } - public static List GetVideoIDs(string v) { + public static async Task> GetVideoIDs(string v) { List toReturn = new List(); if (NadekoBot.GoogleAPIKey == "" || NadekoBot.GoogleAPIKey == null) { Console.WriteLine("ERROR: No google api key found. Playing `Never gonna give you up`."); return toReturn; } try { - - WebRequest wr = WebRequest.Create($"https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails&maxResults={25}&playlistId=PL8lZieNFgOdmrNGTqwjqYJpJ_2nw_O_M2&key={ NadekoBot.creds.GoogleAPIKey }"); - var sr = new StreamReader(wr.GetResponse().GetResponseStream()); + WebRequest wr = WebRequest.Create($"https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails&maxResults={25}&playlistId={v}&key={ NadekoBot.creds.GoogleAPIKey }"); - dynamic obj = JObject.Parse(sr.ReadToEnd()); + var sr = new StreamReader((await wr.GetResponseAsync()).GetResponseStream()); + + dynamic obj = JObject.Parse(await sr.ReadToEndAsync()); foreach (var item in obj.items) { - toReturn.Add("http://www.youtube.com/watch?v=" + item.id.contentDetails.videoId); + toReturn.Add("http://www.youtube.com/watch?v=" + item.contentDetails.videoId); } return toReturn; } catch (Exception ex) { @@ -281,30 +282,30 @@ namespace NadekoBot.Modules { } - public string GetDanbooruImageLink(string tag) { + public async Task GetDanbooruImageLink(string tag) { try { var rng = new Random(); if (tag == "loli") //loli doesn't work for some reason atm tag = "flat_chest"; - var webpage = MakeRequestAndGetResponse($"http://danbooru.donmai.us/posts?page={ rng.Next(0, 30) }&tags={ tag.Replace(" ", "_") }"); + var webpage = await GetResponseAsync($"http://danbooru.donmai.us/posts?page={ rng.Next(0, 30) }&tags={ tag.Replace(" ", "_") }"); var matches = Regex.Matches(webpage, "data-large-file-url=\"(?.*?)\""); - return $"http://danbooru.donmai.us{ matches[rng.Next(0, matches.Count)].Groups["id"].Value }".ShortenUrl(); + return await $"http://danbooru.donmai.us{ matches[rng.Next(0, matches.Count)].Groups["id"].Value }".ShortenUrl(); } catch (Exception) { return null; } } - public string GetGelbooruImageLink(string tag) { + public async Task GetGelbooruImageLink(string tag) { try { var rng = new Random(); var url = $"http://gelbooru.com/index.php?page=post&s=list&pid={ rng.Next(0, 15) * 42 }&tags={ tag.Replace(" ", "_") }"; - var webpage = MakeRequestAndGetResponse(url); // first extract the post id and go to that posts page + var webpage = await GetResponseAsync(url); // first extract the post id and go to that posts page var matches = Regex.Matches(webpage, "span id=\"s(?\\d*)\""); var postLink = $"http://gelbooru.com/index.php?page=post&s=view&id={ matches[rng.Next(0, matches.Count)].Groups["id"].Value }"; - webpage = MakeRequestAndGetResponse(postLink); + webpage = await GetResponseAsync(postLink); //now extract the image from post page var match = Regex.Match(webpage, "\"(?http://simg4.gelbooru.com//images.*?)\""); return match.Groups["url"].Value; @@ -313,21 +314,21 @@ namespace NadekoBot.Modules { } } - public static string ShortenUrl(string url) { + public static async Task ShortenUrl(string url) { if (NadekoBot.GoogleAPIKey == null || NadekoBot.GoogleAPIKey == "") return url; - - var httpWebRequest = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/urlshortener/v1/url?key=" + NadekoBot.GoogleAPIKey); - httpWebRequest.ContentType = "application/json"; - httpWebRequest.Method = "POST"; - - using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream())) { - string json = "{\"longUrl\":\"" + url + "\"}"; - streamWriter.Write(json); - } try { - var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); + var httpWebRequest = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/urlshortener/v1/url?key=" + NadekoBot.GoogleAPIKey); + httpWebRequest.ContentType = "application/json"; + httpWebRequest.Method = "POST"; + + using (var streamWriter = new StreamWriter(await httpWebRequest.GetRequestStreamAsync())) { + string json = "{\"longUrl\":\"" + url + "\"}"; + streamWriter.Write(json); + } + + var httpResponse = (await httpWebRequest.GetResponseAsync()) as HttpWebResponse; using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) { - var responseText = streamReader.ReadToEnd(); + string responseText = await streamReader.ReadToEndAsync(); string MATCH_PATTERN = @"""id"": ?""(?.+)"""; return Regex.Match(responseText, MATCH_PATTERN).Groups["id"].Value; }