diff --git a/NadekoBot/Classes/Music/PoopyBuffer.cs b/NadekoBot/Classes/Music/PoopyBuffer.cs new file mode 100644 index 00000000..9d59d35b --- /dev/null +++ b/NadekoBot/Classes/Music/PoopyBuffer.cs @@ -0,0 +1,120 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Classes.Music +{ + + /// + /// 💩 + /// + public class PoopyBuffer + { + + private readonly byte[] ringBuffer; + + public int WritePosition { get; private set; } = 0; + public int ReadPosition { get; private set; } = 0; + + public int ContentLength => (WritePosition >= ReadPosition ? + WritePosition - ReadPosition : + (BufferSize - ReadPosition) + WritePosition); + + public int BufferSize { get; } + + private readonly object readWriteLock = new object(); + + public PoopyBuffer(int size) + { + if (size <= 0) + throw new ArgumentException(); + BufferSize = size; + ringBuffer = new byte[size]; + } + + public int Read(byte[] buffer, int count) + { + if (buffer.Length < count) + throw new ArgumentException(); + //Console.WriteLine($"***\nRead: {ReadPosition}\nWrite: {WritePosition}\nContentLength:{ContentLength}\n***"); + lock (readWriteLock) + { + //read as much as you can if you're reading too much + if (count > ContentLength) + count = ContentLength; + //if nothing to read, return 0 + if (WritePosition == ReadPosition) + return 0; + // if buffer is in the "normal" state, just read + if (WritePosition > ReadPosition) + { + Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, count); + ReadPosition += count; + //Console.WriteLine($"Read only normally1 {count}[{ReadPosition - count} to {ReadPosition}]"); + return count; + } + //else ReadPos buffer.Length) + throw new ArgumentException(); + while (ContentLength + count > BufferSize) + { + await Task.Delay(20, cancelToken); + if (cancelToken.IsCancellationRequested) + return; + } + //the while above assures that i cannot write past readposition with my write, so i don't have to check + // *unless its multithreaded or task is not awaited + lock (readWriteLock) + { + // if i can just write without hitting buffer.length, do it + if (WritePosition + count < BufferSize) + { + Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, count); + WritePosition += count; + //Console.WriteLine($"Wrote only normally {count}[{WritePosition - count} to {WritePosition}]"); + return; + } + // otherwise, i have to write to the end, then write the rest from the start + + var wroteNormaly = BufferSize - WritePosition; + Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, wroteNormaly); + + //Console.WriteLine($"Wrote normally {wroteNormaly}[{WritePosition} to {BufferSize}]"); + + var wroteFromStart = count - wroteNormaly; + Buffer.BlockCopy(buffer, wroteNormaly, ringBuffer, 0, wroteFromStart); + + //Console.WriteLine($"and from start {wroteFromStart} [0 to {wroteFromStart}"); + + WritePosition = wroteFromStart; + } + } + } +} diff --git a/NadekoBot/Classes/Music/Song.cs b/NadekoBot/Classes/Music/Song.cs index 54a001fc..dab40651 100644 --- a/NadekoBot/Classes/Music/Song.cs +++ b/NadekoBot/Classes/Music/Song.cs @@ -10,116 +10,17 @@ using System.Threading; using System.Threading.Tasks; using VideoLibrary; -namespace NadekoBot.Classes.Music { - public class SongInfo { +namespace NadekoBot.Classes.Music +{ + public class SongInfo + { public string Provider { get; internal set; } public MusicType ProviderType { get; internal set; } public string Title { get; internal set; } public string Uri { get; internal set; } } - /// - /// 💩 - /// - public class PoopyBuffer { - - private readonly byte[] ringBuffer; - - public int WritePosition { get; private set; } = 0; - public int ReadPosition { get; private set; } = 0; - - public int ContentLength => (WritePosition >= ReadPosition ? - WritePosition - ReadPosition : - (BufferSize - ReadPosition) + WritePosition); - - public int BufferSize { get; } - - private readonly object readWriteLock = new object(); - - public PoopyBuffer(int size) { - if (size <= 0) - throw new ArgumentException(); - BufferSize = size; - ringBuffer = new byte[size]; - } - - public int Read(byte[] buffer, int count) { - if (buffer.Length < count) - throw new ArgumentException(); - //Console.WriteLine($"***\nRead: {ReadPosition}\nWrite: {WritePosition}\nContentLength:{ContentLength}\n***"); - lock (readWriteLock) { - //read as much as you can if you're reading too much - if (count > ContentLength) - count = ContentLength; - //if nothing to read, return 0 - if (WritePosition == ReadPosition) - return 0; - // if buffer is in the "normal" state, just read - if (WritePosition > ReadPosition) { - Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, count); - ReadPosition += count; - //Console.WriteLine($"Read only normally1 {count}[{ReadPosition - count} to {ReadPosition}]"); - return count; - } - //else ReadPos buffer.Length) - throw new ArgumentException(); - while (ContentLength + count > BufferSize) { - await Task.Delay(20, cancelToken); - if (cancelToken.IsCancellationRequested) - return; - } - //the while above assures that i cannot write past readposition with my write, so i don't have to check - // *unless its multithreaded or task is not awaited - lock (readWriteLock) { - // if i can just write without hitting buffer.length, do it - if (WritePosition + count < BufferSize) { - Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, count); - WritePosition += count; - //Console.WriteLine($"Wrote only normally {count}[{WritePosition - count} to {WritePosition}]"); - return; - } - // otherwise, i have to write to the end, then write the rest from the start - - var wroteNormaly = BufferSize - WritePosition; - Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, wroteNormaly); - - //Console.WriteLine($"Wrote normally {wroteNormaly}[{WritePosition} to {BufferSize}]"); - - var wroteFromStart = count - wroteNormaly; - Buffer.BlockCopy(buffer, wroteNormaly, ringBuffer, 0, wroteFromStart); - - //Console.WriteLine($"and from start {wroteFromStart} [0 to {wroteFromStart}"); - - WritePosition = wroteFromStart; - } - } - } - public class Song { + public class Song + { public StreamState State { get; internal set; } public string PrettyName => $"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}`"; @@ -130,22 +31,27 @@ namespace NadekoBot.Classes.Music { private bool prebufferingComplete { get; set; } = false; public MusicPlayer MusicPlayer { get; set; } - public string PrettyCurrentTime() { + public string PrettyCurrentTime() + { var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50); return $"【{(int)time.TotalMinutes}m {time.Seconds}s】"; } private ulong bytesSent { get; set; } = 0; - private Song(SongInfo songInfo) { + private Song(SongInfo songInfo) + { this.SongInfo = songInfo; } private Task BufferSong(CancellationToken cancelToken) => - Task.Run(async () => { + Task.Factory.StartNew(async () => + { Process p = null; - try { - p = Process.Start(new ProcessStartInfo { + try + { + p = Process.Start(new ProcessStartInfo + { FileName = "ffmpeg", Arguments = $"-i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet", UseShellExecute = false, @@ -156,11 +62,15 @@ namespace NadekoBot.Classes.Music { const int blockSize = 3840; var buffer = new byte[blockSize]; var attempt = 0; - while (!cancelToken.IsCancellationRequested) { + while (!cancelToken.IsCancellationRequested) + { var read = 0; - try { + try + { read = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize, cancelToken); - } catch { + } + catch + { return; } if (read == 0) @@ -168,7 +78,8 @@ namespace NadekoBot.Classes.Music { break; else await Task.Delay(100, cancelToken); - else { + else + { attempt = 0; await Task.Delay(5, cancelToken); } @@ -176,24 +87,35 @@ namespace NadekoBot.Classes.Music { if (songBuffer.ContentLength > 2.MB()) prebufferingComplete = true; } - } catch (Exception ex) { + } + catch (Exception ex) + { Console.WriteLine($"Buffering errored: {ex.Message}"); - } finally { + } + finally + { Console.WriteLine($"Buffering done." + $" [{songBuffer.ContentLength}]"); - if (p != null) { - try { + if (p != null) + { + try + { p.Kill(); - } catch { } + } + catch { } p.Dispose(); } } - }); + }, TaskCreationOptions.LongRunning); - internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) { + internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) + { var bufferTask = new ConfiguredTaskAwaitable(); - try { + try + { bufferTask = BufferSong(cancelToken).ConfigureAwait(false); - } catch (Exception ex) { + } + catch (Exception ex) + { var clr = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"ERR BUFFER START : {ex.Message}\n{ex}"); @@ -202,26 +124,31 @@ namespace NadekoBot.Classes.Music { var bufferAttempts = 0; const int waitPerAttempt = 500; var toAttemptTimes = SongInfo.ProviderType != MusicType.Normal ? 5 : 9; - while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes) { + while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes) + { await Task.Delay(waitPerAttempt, cancelToken); } cancelToken.ThrowIfCancellationRequested(); Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}"); const int blockSize = 3840; var attempt = 0; - while (!cancelToken.IsCancellationRequested) { + while (!cancelToken.IsCancellationRequested) + { //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); byte[] buffer = new byte[blockSize]; var read = songBuffer.Read(buffer, blockSize); - unchecked { + unchecked + { bytesSent += (ulong)read; } if (read == 0) - if (attempt++ == 20) { + if (attempt++ == 20) + { voiceClient.Wait(); Console.WriteLine($"Song finished. [{songBuffer.ContentLength}]"); break; - } else + } + else await Task.Delay(100, cancelToken); else attempt = 0; @@ -239,11 +166,13 @@ namespace NadekoBot.Classes.Music { } //stackoverflow ftw - private static byte[] AdjustVolume(byte[] audioSamples, float volume) { + 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) { + for (var i = 0; i < array.Length; i += 2) + { // convert byte pair to int short buf1 = audioSamples[i + 1]; @@ -263,35 +192,43 @@ namespace NadekoBot.Classes.Music { return array; } - public static async Task ResolveSong(string query, MusicType musicType = MusicType.Normal) { + 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)) { + if (musicType != MusicType.Local && IsRadioLink(query)) + { musicType = MusicType.Radio; query = await HandleStreamContainers(query) ?? query; } - try { - switch (musicType) { + try + { + switch (musicType) + { case MusicType.Local: - return new Song(new SongInfo { + return new Song(new SongInfo + { Uri = "\"" + Path.GetFullPath(query) + "\"", Title = Path.GetFileNameWithoutExtension(query), Provider = "Local File", ProviderType = musicType, }); case MusicType.Radio: - return new Song(new SongInfo { + return new Song(new SongInfo + { Uri = query, Title = $"{query}", Provider = "Radio Stream", ProviderType = musicType, }); } - if (SoundCloud.Default.IsSoundCloudLink(query)) { + if (SoundCloud.Default.IsSoundCloudLink(query)) + { var svideo = await SoundCloud.Default.GetVideoAsync(query); - return new Song(new SongInfo { + return new Song(new SongInfo + { Title = svideo.FullName, Provider = "SoundCloud", Uri = svideo.StreamLink, @@ -310,76 +247,99 @@ namespace NadekoBot.Classes.Music { if (video == null) // do something with this error throw new Exception("Could not load any video elements based on the query."); - return new Song(new SongInfo { + return new Song(new SongInfo + { Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" Provider = "YouTube", Uri = video.Uri, ProviderType = musicType, }); - } catch (Exception ex) { + } + catch (Exception ex) + { Console.WriteLine($"Failed resolving the link.{ex.Message}"); return null; } } - private static async Task HandleStreamContainers(string query) { + private static async Task HandleStreamContainers(string query) + { string file = null; - try { + try + { file = await SearchHelper.GetResponseStringAsync(query); - } catch { + } + catch + { return query; } - if (query.Contains(".pls")) { + if (query.Contains(".pls")) + { //File1=http://armitunes.com:8000/ //Regex.Match(query) - try { + try + { var m = Regex.Match(file, "File1=(?.*?)\\n"); var res = m.Groups["url"]?.ToString(); return res?.Trim(); - } catch { + } + catch + { Console.WriteLine($"Failed reading .pls:\n{file}"); return null; } } - if (query.Contains(".m3u")) { + if (query.Contains(".m3u")) + { /* # This is a comment C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 */ - try { + try + { var m = Regex.Match(file, "(?^[^#].*)", RegexOptions.Multiline); var res = m.Groups["url"]?.ToString(); return res?.Trim(); - } catch { + } + catch + { Console.WriteLine($"Failed reading .m3u:\n{file}"); return null; } } - if (query.Contains(".asx")) { + if (query.Contains(".asx")) + { // - try { + try + { var m = Regex.Match(file, ".*?)\""); var res = m.Groups["url"]?.ToString(); return res?.Trim(); - } catch { + } + catch + { Console.WriteLine($"Failed reading .asx:\n{file}"); return null; } } - if (query.Contains(".xspf")) { + if (query.Contains(".xspf")) + { /* file:///mp3s/song_1.mp3 */ - try { + try + { var m = Regex.Match(file, "(?.*?)"); var res = m.Groups["url"]?.ToString(); return res?.Trim(); - } catch { + } + catch + { Console.WriteLine($"Failed reading .xspf:\n{file}"); return null; } diff --git a/NadekoBot/Commands/TriviaCommand.cs b/NadekoBot/Commands/TriviaCommand.cs index c2398e81..865361f5 100644 --- a/NadekoBot/Commands/TriviaCommand.cs +++ b/NadekoBot/Commands/TriviaCommand.cs @@ -1,35 +1,41 @@ -using System; -using System.Threading.Tasks; -using Discord.Commands; -using System.Collections.Concurrent; -using Discord; +using Discord.Commands; using NadekoBot.Modules; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; using TriviaGame = NadekoBot.Classes.Trivia.TriviaGame; -namespace NadekoBot.Commands { - internal class Trivia : DiscordCommand { +namespace NadekoBot.Commands +{ + internal class Trivia : DiscordCommand + { public static ConcurrentDictionary RunningTrivias = new ConcurrentDictionary(); - public Func DoFunc() => async e => { + public Func DoFunc() => async e => + { TriviaGame trivia; - if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) { + if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) + { var triviaGame = new TriviaGame(e); if (RunningTrivias.TryAdd(e.Server.Id, triviaGame)) await e.Channel.SendMessage("**Trivia game started!**\nFirst player to get to 10 points wins! You have 30 seconds per question.\nUse command `tq` if game was started by accident.**"); else await triviaGame.StopGame(); - } else + } + else await e.Channel.SendMessage("Trivia game is already running on this server.\n" + trivia.CurrentQuestion); }; - internal override void Init(CommandGroupBuilder cgb) { + internal override void Init(CommandGroupBuilder cgb) + { cgb.CreateCommand(Module.Prefix + "t") .Description("Starts a game of trivia.") .Do(DoFunc()); cgb.CreateCommand(Module.Prefix + "tl") .Description("Shows a current trivia leaderboard.") - .Do(async e=> { + .Do(async e => + { TriviaGame trivia; if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) await e.Channel.SendMessage(trivia.GetLeaderboard()); @@ -39,15 +45,18 @@ namespace NadekoBot.Commands { cgb.CreateCommand(Module.Prefix + "tq") .Description("Quits current trivia after current question.") - .Do(async e=> { + .Do(async e => + { TriviaGame trivia; - if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) { + if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) + { await trivia.StopGame(); - } else + } + else await e.Channel.SendMessage("No trivia is running on this server."); }); } - public Trivia(DiscordModule module) : base(module) {} + public Trivia(DiscordModule module) : base(module) { } } } diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 8febca20..6bea42b7 100644 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -148,8 +148,6 @@ namespace NadekoBot.Modules.Administration.Commands str += $"`New name:` **{e.After.Name}**"; else if (e.Before.AvatarUrl != e.After.AvatarUrl) str += $"`New Avatar:` {e.After.AvatarUrl}"; - else if (e.Before.Status != e.After.Status) - str += $"Status `{e.Before.Status}` -> `{e.After.Status}`"; else return; await ch.SendMessage(str); diff --git a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs b/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs index c742ead5..a511479b 100644 --- a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs +++ b/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs @@ -20,6 +20,7 @@ namespace NadekoBot.Modules.Administration.Commands public ulong RepeatingServerId { get; set; } public ulong RepeatingChannelId { get; set; } + public Message lastMessage { get; set; } = null; public string RepeatingMessage { get; set; } public int Interval { get; set; } @@ -34,7 +35,13 @@ namespace NadekoBot.Modules.Administration.Commands { try { - await ch.SendMessage(msg); + if (lastMessage != null) + await lastMessage.Delete(); + } + catch { } + try + { + lastMessage = await ch.SendMessage(msg); } catch { } } diff --git a/NadekoBot/Modules/Administration/Commands/Remind.cs b/NadekoBot/Modules/Administration/Commands/Remind.cs index be16f374..1c38edcd 100644 --- a/NadekoBot/Modules/Administration/Commands/Remind.cs +++ b/NadekoBot/Modules/Administration/Commands/Remind.cs @@ -74,6 +74,10 @@ namespace NadekoBot.Modules.Administration.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "remind") + .Description("Sends a message to you or a channel after certain amount of time. " + + "First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. " + + "Third argument is a (multiword)message. " + + "\n**Usage**: `.remind me 1d5h Do something` or `.remind #general Start now!`") .Parameter("meorchannel", ParameterType.Required) .Parameter("time", ParameterType.Required) .Parameter("message", ParameterType.Unparsed) diff --git a/NadekoBot/Modules/Games/Commands/Bomberman.cs b/NadekoBot/Modules/Games/Commands/Bomberman.cs new file mode 100644 index 00000000..fe6c882d --- /dev/null +++ b/NadekoBot/Modules/Games/Commands/Bomberman.cs @@ -0,0 +1,125 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Commands; +using System.Text; +using System.Timers; +using static NadekoBot.Modules.Games.Commands.Bomberman; + +namespace NadekoBot.Modules.Games.Commands +{ + class Bomberman : DiscordCommand + { + public Field[,] board = new Field[15, 15]; + + public BombermanPlayer[] players = new BombermanPlayer[4]; + + public Channel gameChannel = null; + + public Message godMsg = null; + + public int curI = 5; + public int curJ = 5; + + + public Bomberman(DiscordModule module) : base(module) + { + for (int i = 0; i < 15; i++) + { + for (int j = 0; j < 15; j++) + { + board[i, j] = new Field(); + } + } + NadekoBot.Client.MessageReceived += (s, e) => + { + if (e.Channel != gameChannel || + e.User.Id == NadekoBot.Client.CurrentUser.Id) + return; + + if (e.Message.Text == "w") + { + board[curI - 1, curJ] = board[curI--, curJ]; + board[curI + 1, curJ].player = null; + } + else if (e.Message.Text == "s") + { + board[curI + 1, curJ] = board[curI++, curJ]; + board[curI - 1, curJ].player = null; + } + else if (e.Message.Text == "a") + { + board[curI, curJ - 1] = board[curI, curJ--]; + board[curI, curJ + 1].player = null; + } + else if (e.Message.Text == "d") + { + board[curI, curJ + 1] = board[curI, curJ++]; + board[curI, curJ - 1].player = null; + } + + e.Message.Delete(); + }; + + var t = new Timer(); + t.Elapsed += async (s, e) => + { + if (gameChannel == null) + return; + + var boardStr = new StringBuilder(); + + for (int i = 0; i < 15; i++) + { + for (int j = 0; j < 15; j++) + { + boardStr.Append(board[i, j].ToString()); + } + boardStr.AppendLine(); + } + if (godMsg.Id != 0) + await godMsg.Edit(boardStr.ToString()); + + }; + t.Interval = 1000; + t.Start(); + + } + + internal override void Init(CommandGroupBuilder cgb) + { + //cgb.CreateCommand(Module.Prefix + "bomb") + // .Description("Bomberman start") + // .Do(async e => + // { + // if (gameChannel != null) + // return; + // godMsg = await e.Channel.SendMessage("GAME START IN 1 SECOND...."); + // gameChannel = e.Channel; + // players[0] = new BombermanPlayer + // { + // User = e.User, + // }; + + // board[5, 5].player = players[0]; + // }); + } + + public class BombermanPlayer + { + public User User = null; + public string Icon = "👳"; + + internal void MoveLeft() + { + + } + } + } + + internal struct Field + { + public BombermanPlayer player; + + public override string ToString() => player?.Icon ?? "⬜"; + } +} diff --git a/NadekoBot/Modules/Games.cs b/NadekoBot/Modules/Games/GamesModule.cs similarity index 96% rename from NadekoBot/Modules/Games.cs rename to NadekoBot/Modules/Games/GamesModule.cs index 6d2e39c7..14c13917 100644 --- a/NadekoBot/Modules/Games.cs +++ b/NadekoBot/Modules/Games/GamesModule.cs @@ -2,21 +2,23 @@ using Discord.Modules; using NadekoBot.Commands; using NadekoBot.Extensions; +using NadekoBot.Modules.Games.Commands; using System; using System.Linq; -namespace NadekoBot.Modules +namespace NadekoBot.Modules.Games { - internal class Games : DiscordModule + internal class GamesModule : DiscordModule { private readonly Random rng = new Random(); - public Games() + public GamesModule() { commands.Add(new Trivia(this)); commands.Add(new SpeedTyping(this)); commands.Add(new PollCommand(this)); commands.Add(new PlantPick(this)); + commands.Add(new Bomberman(this)); //commands.Add(new BetrayGame(this)); } diff --git a/NadekoBot/Modules/Music.cs b/NadekoBot/Modules/Music.cs index 7b50a3a2..4a974c42 100644 --- a/NadekoBot/Modules/Music.cs +++ b/NadekoBot/Modules/Music.cs @@ -71,7 +71,7 @@ namespace NadekoBot.Modules cgb.CreateCommand("n") .Alias("next") .Alias("skip") - .Description("Goes to the next song in the queue.") + .Description("Goes to the next song in the queue.**Usage**: `!m n`") .Do(e => { MusicPlayer musicPlayer; @@ -81,7 +81,7 @@ namespace NadekoBot.Modules cgb.CreateCommand("s") .Alias("stop") - .Description("Stops the music and clears the playlist. Stays in the channel.") + .Description("Stops the music and clears the playlist. Stays in the channel.\n**Usage**: `!m s`") .Do(async e => { await Task.Run(() => @@ -94,7 +94,8 @@ namespace NadekoBot.Modules cgb.CreateCommand("d") .Alias("destroy") - .Description("Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour)") + .Description("Completely stops the music and unbinds the bot from the channel. " + + "(may cause weird behaviour)\n**Usage**: `!m d`") .Do(async e => { await Task.Run(() => @@ -107,7 +108,7 @@ namespace NadekoBot.Modules cgb.CreateCommand("p") .Alias("pause") - .Description("Pauses or Unpauses the song.") + .Description("Pauses or Unpauses the song.\n**Usage**: `!m p`") .Do(async e => { MusicPlayer musicPlayer; @@ -121,7 +122,8 @@ namespace NadekoBot.Modules cgb.CreateCommand("q") .Alias("yq") - .Description("Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**.\n**Usage**: `!m q Dream Of Venice`") + .Description("Queue a song using keywords or a link. Bot will join your voice channel." + + "**You must be in a voice channel**.\n**Usage**: `!m q Dream Of Venice`") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -130,7 +132,7 @@ namespace NadekoBot.Modules cgb.CreateCommand("lq") .Alias("ls").Alias("lp") - .Description("Lists up to 15 currently queued songs.") + .Description("Lists up to 15 currently queued songs.\n**Usage**: `!m lq`") .Do(async e => { MusicPlayer musicPlayer; @@ -158,7 +160,7 @@ namespace NadekoBot.Modules cgb.CreateCommand("np") .Alias("playing") - .Description("Shows the song currently playing.") + .Description("Shows the song currently playing.\n**Usage**: `!m np`") .Do(async e => { MusicPlayer musicPlayer; @@ -172,7 +174,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("vol") - .Description("Sets the music volume 0-150%") + .Description("Sets the music volume 0-150%\n**Usage**: `!m vol 50`") .Parameter("val", ParameterType.Required) .Do(async e => { @@ -192,7 +194,8 @@ namespace NadekoBot.Modules cgb.CreateCommand("dv") .Alias("defvol") - .Description("Sets the default music volume when music playback is started (0-100). Does not persist through restarts.\n**Usage**: !m dv 80") + .Description("Sets the default music volume when music playback is started (0-100)." + + " Does not persist through restarts.\n**Usage**: `!m dv 80`") .Parameter("val", ParameterType.Required) .Do(async e => { @@ -208,7 +211,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("min").Alias("mute") - .Description("Sets the music volume to 0%") + .Description("Sets the music volume to 0%\n**Usage**: `!m min`") .Do(e => { MusicPlayer musicPlayer; @@ -218,7 +221,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("max") - .Description("Sets the music volume to 100% (real max is actually 150%).") + .Description("Sets the music volume to 100% (real max is actually 150%).\n**Usage**: `!m max`") .Do(e => { MusicPlayer musicPlayer; @@ -228,7 +231,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("half") - .Description("Sets the music volume to 50%.") + .Description("Sets the music volume to 50%.\n**Usage**: `!m half`") .Do(e => { MusicPlayer musicPlayer; @@ -238,7 +241,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("sh") - .Description("Shuffles the current playlist.") + .Description("Shuffles the current playlist.\n**Usage**: `!m sh`") .Do(async e => { MusicPlayer musicPlayer; @@ -254,18 +257,8 @@ namespace NadekoBot.Modules await e.Channel.SendMessage("🎵 `Songs shuffled.`"); }); - cgb.CreateCommand("setgame") - .Description("Sets the game of the bot to the number of songs playing. **Owner only**") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - await e.Channel.SendMessage("❗This command is deprecated. " + - "Use:\n `.ropl`\n `.adpl %playing% songs, %queued% queued.` instead.\n " + - "It even persists through restarts."); - }); - cgb.CreateCommand("pl") - .Description("Queues up to 25 songs from a youtube playlist specified by a link, or keywords.") + .Description("Queues up to 25 songs from a youtube playlist specified by a link, or keywords.\n**Usage**: `!m pl playlist link or name`") .Parameter("playlist", ParameterType.Unparsed) .Do(async e => { @@ -292,7 +285,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("lopl") - .Description("Queues up to 50 songs from a directory. **Owner Only!**") + .Description("Queues up to 50 songs from a directory. **Owner Only!**\n**Usage**: `!m lopl C:/music/classical`") .Parameter("directory", ParameterType.Unparsed) .AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly()) .Do(async e => @@ -313,7 +306,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("radio").Alias("ra") - .Description("Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf") + .Description("Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf\n**Usage**: `!m ra radio link here`") .Parameter("radio_link", ParameterType.Required) .Do(async e => { @@ -326,7 +319,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("lo") - .Description("Queues a local file by specifying a full path. **Owner Only!**") + .Description("Queues a local file by specifying a full path. **Owner Only!**\n**Usage**: `!m ra C:/music/mysong.mp3`") .Parameter("path", ParameterType.Unparsed) .AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly()) .Do(async e => @@ -338,7 +331,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("mv") - .Description("Moves the bot to your voice channel. (works only if music is already playing)") + .Description("Moves the bot to your voice channel. (works only if music is already playing)\n**Usage**: `!m mv`") .Do(e => { MusicPlayer musicPlayer; @@ -349,7 +342,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("rm") - .Description("Remove a song by its # in the queue, or 'all' to remove whole queue.") + .Description("Remove a song by its # in the queue, or 'all' to remove whole queue.\n**Usage**: `!m rm 5`") .Parameter("num", ParameterType.Required) .Do(async e => { @@ -378,7 +371,7 @@ namespace NadekoBot.Modules }); cgb.CreateCommand("cleanup") - .Description("Cleans up hanging voice connections. **Owner Only!**") + .Description("Cleans up hanging voice connections. **Owner Only!**\n**Usage**: `!m cleanup`") .AddCheck(SimpleCheckers.OwnerOnly()) .Do(e => { @@ -397,7 +390,7 @@ namespace NadekoBot.Modules cgb.CreateCommand("rcs") .Alias("repeatcurrentsong") - .Description("Toggles repeat of current song.") + .Description("Toggles repeat of current song.\n**Usage**: `!m rcs`") .Do(async e => { MusicPlayer musicPlayer; @@ -414,7 +407,7 @@ namespace NadekoBot.Modules cgb.CreateCommand("rpl") .Alias("repeatplaylist") - .Description("Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue).") + .Description("Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue).\n**Usage**: `!m rpl`") .Do(async e => { MusicPlayer musicPlayer; diff --git a/NadekoBot/Modules/Search/Commands/ConverterCommand.cs b/NadekoBot/Modules/Search/Commands/ConverterCommand.cs new file mode 100644 index 00000000..bb08c5af --- /dev/null +++ b/NadekoBot/Modules/Search/Commands/ConverterCommand.cs @@ -0,0 +1,162 @@ +using Discord.Commands; +using NadekoBot.Commands; +using ScaredFingers.UnitsConversion; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Search.Commands +{ + class ConverterCommand : DiscordCommand + { + + public ConverterCommand(DiscordModule module) : base(module) + { + if (unitTables == null) + { + CultureInfo ci = new CultureInfo("en-US"); + Thread.CurrentThread.CurrentCulture = ci; + unitTables = new List(); + unitTables.Add(UnitTable.LengthTable); + unitTables.Add(UnitTable.TemperatureTable); + unitTables.Add(UnitTable.VolumeTable); + unitTables.Add(UnitTable.WeightTable); + reInitCurrencyConverterTable(); + } + + } + + + internal override void Init(CommandGroupBuilder cgb) + { + cgb.CreateCommand(Module.Prefix + "convert") + .Description("Convert quantities from>to. Like `~convert m>km 1000`") + .Parameter("from-to", ParameterType.Required) + .Parameter("quantity", ParameterType.Optional) + .Do(ConvertFunc()); + cgb.CreateCommand(Module.Prefix + "convertlist") + .Description("List of the convertable dimensions and currencies.") + .Do(ConvertListFunc()); + } + + private Func ConvertListFunc() => + async e => + { + reInitCurrencyConverterTable(); + string msg = ""; + foreach (var tmpTable in unitTables) + { + int i = 1; + while (tmpTable.IsKnownUnit(i)) + { + msg += tmpTable.GetUnitName(i) + " (" + tmpTable.GetUnitSymbol(i) + "); "; + i++; + } + msg += "\n"; + } + foreach (var curr in exchangeRateProvider.Currencies) + { + msg += curr + "; "; + } + + await e.Channel.SendMessage(msg); + }; + + private Func ConvertFunc() => + async e => + { + try + { + await e.Channel.SendIsTyping(); + + string from = e.GetArg("from-to").ToLowerInvariant().Split('>')[0]; + string to = e.GetArg("from-to").ToLowerInvariant().Split('>')[1]; + + float quantity = 1.0f; + if (!float.TryParse(e.GetArg("quantity"), out quantity)) + { + quantity = 1.0f; + } + + int fromCode, toCode = 0; + UnitTable table = null; + ResolveUnitCodes(from, to, out table, out fromCode, out toCode); + + if (table != null) + { + Unit inUnit = new Unit(fromCode, quantity, table); + Unit outUnit = inUnit.Convert(toCode); + await e.Channel.SendMessage(inUnit.ToString() + " = " + outUnit.ToString()); + } + else + { + reInitCurrencyConverterTable(); + Unit inUnit = currTable.CreateUnit(quantity, from.ToUpperInvariant()); + Unit outUnit = inUnit.Convert(currTable.CurrencyCode(to.ToUpperInvariant())); + await e.Channel.SendMessage(inUnit.ToString() + " = " + outUnit.ToString()); + } + } + catch //(Exception ex) + { + //Console.WriteLine(ex.ToString()); + await e.Channel.SendMessage("Bad input format, or sth went wrong... Try to list them with `" + Module.Prefix + "`convertlist"); + } + }; + + private void reInitCurrencyConverterTable() + { + if (lastChanged == null || lastChanged.DayOfYear != DateTime.Now.DayOfYear) + { + exchangeRateProvider = new WebExchangeRatesProvider(); + currTable = new CurrencyExchangeTable(exchangeRateProvider); + lastChanged = DateTime.Now; + } + } + + private void ResolveUnitCodes(string from, string to, out UnitTable table, out int fromCode, out int toCode) + { + foreach (var tmpTable in unitTables) + { + int f = LookupUnit(tmpTable, from); + int t = LookupUnit(tmpTable, to); + if (f > 0 && t > 0) + { + table = tmpTable; + fromCode = f; + toCode = t; + return; + } + } + table = null; + fromCode = 0; + toCode = 0; + } + + private int LookupUnit(UnitTable table, string lookup) + { + string wellformedLookup = lookup.ToLowerInvariant().Replace("°", ""); + int i = 1; + while (table.IsKnownUnit(i)) + { + if (wellformedLookup == table.GetUnitName(i).ToLowerInvariant().Replace("°", "") || + wellformedLookup == table.GetUnitPlural(i).ToLowerInvariant().Replace("°", "") || + wellformedLookup == table.GetUnitSymbol(i).ToLowerInvariant().Replace("°", "")) + { + return i; + } + i++; + } + return 0; + } + + private static List unitTables; + + private static CurrencyExchangeRatesProvider exchangeRateProvider; + + private static CurrencyExchangeTable currTable; + + private static DateTime lastChanged; + } +} diff --git a/NadekoBot/Modules/Searches.cs b/NadekoBot/Modules/Searches.cs index 0693141e..8a522423 100644 --- a/NadekoBot/Modules/Searches.cs +++ b/NadekoBot/Modules/Searches.cs @@ -5,6 +5,7 @@ using NadekoBot.Classes.IMDB; using NadekoBot.Classes.JSONModels; using NadekoBot.Commands; using NadekoBot.Extensions; +using NadekoBot.Modules.Search.Commands; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -23,6 +24,7 @@ namespace NadekoBot.Modules { commands.Add(new LoLCommands(this)); commands.Add(new StreamNotifications(this)); + commands.Add(new ConverterCommand(this)); rng = new Random(); } diff --git a/NadekoBot/NadekoBot.cs b/NadekoBot/NadekoBot.cs index 2fbb48f4..6442839f 100644 --- a/NadekoBot/NadekoBot.cs +++ b/NadekoBot/NadekoBot.cs @@ -7,6 +7,7 @@ using NadekoBot.Commands; using NadekoBot.Modules; using NadekoBot.Modules.Administration; using NadekoBot.Modules.Gambling; +using NadekoBot.Modules.Games; using NadekoBot.Modules.Pokemon; using NadekoBot.Modules.Translator; using Newtonsoft.Json; @@ -163,7 +164,7 @@ namespace NadekoBot modules.Add(new PermissionModule(), "Permissions", ModuleFilter.None); modules.Add(new Conversations(), "Conversations", ModuleFilter.None); modules.Add(new GamblingModule(), "Gambling", ModuleFilter.None); - modules.Add(new Games(), "Games", ModuleFilter.None); + modules.Add(new GamesModule(), "Games", ModuleFilter.None); modules.Add(new Music(), "Music", ModuleFilter.None); modules.Add(new Searches(), "Searches", ModuleFilter.None); modules.Add(new NSFW(), "NSFW", ModuleFilter.None); diff --git a/NadekoBot/NadekoBot.csproj b/NadekoBot/NadekoBot.csproj index 81c190bb..4d233b8c 100644 --- a/NadekoBot/NadekoBot.csproj +++ b/NadekoBot/NadekoBot.csproj @@ -97,6 +97,10 @@ ..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll True + + False + lib\ScaredFingers.UnitsConversion.dll + @@ -130,6 +134,7 @@ + @@ -186,7 +191,8 @@ - + + @@ -217,6 +223,7 @@ + @@ -478,6 +485,9 @@ Discord.Net + + +