Merge remote-tracking branch 'refs/remotes/Kwoth/master'

This commit is contained in:
appelemac 2016-04-02 22:14:16 +02:00
commit d82cd19b7b
16 changed files with 634 additions and 235 deletions

View File

@ -0,0 +1,120 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Classes.Music
{
/// <summary>
/// 💩
/// </summary>
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 <Writepos
// buffer is in its inverted state
// A: if i can read as much as possible without hitting the buffer.length, read that
if (count + ReadPosition <= BufferSize)
{
Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, count);
ReadPosition += count;
//Console.WriteLine($"Read only normally2 {count}[{ReadPosition - count} to {ReadPosition}]");
return count;
}
// B: if i can't read as much, read to the end,
var readNormaly = BufferSize - ReadPosition;
Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, readNormaly);
//Console.WriteLine($"Read normaly {count}[{ReadPosition} to {ReadPosition + readNormaly}]");
//then read the remaining amount from the start
var readFromStart = count - readNormaly;
Buffer.BlockCopy(ringBuffer, 0, buffer, readNormaly, readFromStart);
//Console.WriteLine($"Read From start {readFromStart}[{0} to {readFromStart}]");
ReadPosition = readFromStart;
return count;
}
}
public async Task WriteAsync(byte[] buffer, int count, CancellationToken cancelToken)
{
if (count > 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;
}
}
}
}

View File

@ -10,116 +10,17 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using VideoLibrary; using VideoLibrary;
namespace NadekoBot.Classes.Music { namespace NadekoBot.Classes.Music
public class SongInfo { {
public class SongInfo
{
public string Provider { get; internal set; } public string Provider { get; internal set; }
public MusicType ProviderType { get; internal set; } public MusicType ProviderType { get; internal set; }
public string Title { get; internal set; } public string Title { get; internal set; }
public string Uri { get; internal set; } public string Uri { get; internal set; }
} }
/// <summary> public class Song
/// 💩 {
/// </summary>
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 <Writepos
// buffer is in its inverted state
// A: if i can read as much as possible without hitting the buffer.length, read that
if (count + ReadPosition <= BufferSize) {
Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, count);
ReadPosition += count;
//Console.WriteLine($"Read only normally2 {count}[{ReadPosition - count} to {ReadPosition}]");
return count;
}
// B: if i can't read as much, read to the end,
var readNormaly = BufferSize - ReadPosition;
Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, readNormaly);
//Console.WriteLine($"Read normaly {count}[{ReadPosition} to {ReadPosition + readNormaly}]");
//then read the remaining amount from the start
var readFromStart = count - readNormaly;
Buffer.BlockCopy(ringBuffer, 0, buffer, readNormaly, readFromStart);
//Console.WriteLine($"Read From start {readFromStart}[{0} to {readFromStart}]");
ReadPosition = readFromStart;
return count;
}
}
public async Task WriteAsync(byte[] buffer, int count, CancellationToken cancelToken) {
if (count > 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 StreamState State { get; internal set; } public StreamState State { get; internal set; }
public string PrettyName => public string PrettyName =>
$"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}`"; $"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}`";
@ -130,22 +31,27 @@ namespace NadekoBot.Classes.Music {
private bool prebufferingComplete { get; set; } = false; private bool prebufferingComplete { get; set; } = false;
public MusicPlayer MusicPlayer { get; set; } public MusicPlayer MusicPlayer { get; set; }
public string PrettyCurrentTime() { public string PrettyCurrentTime()
{
var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50); var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50);
return $"【{(int)time.TotalMinutes}m {time.Seconds}s】"; return $"【{(int)time.TotalMinutes}m {time.Seconds}s】";
} }
private ulong bytesSent { get; set; } = 0; private ulong bytesSent { get; set; } = 0;
private Song(SongInfo songInfo) { private Song(SongInfo songInfo)
{
this.SongInfo = songInfo; this.SongInfo = songInfo;
} }
private Task BufferSong(CancellationToken cancelToken) => private Task BufferSong(CancellationToken cancelToken) =>
Task.Run(async () => { Task.Factory.StartNew(async () =>
{
Process p = null; Process p = null;
try { try
p = Process.Start(new ProcessStartInfo { {
p = Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg", FileName = "ffmpeg",
Arguments = $"-i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet", Arguments = $"-i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet",
UseShellExecute = false, UseShellExecute = false,
@ -156,11 +62,15 @@ namespace NadekoBot.Classes.Music {
const int blockSize = 3840; const int blockSize = 3840;
var buffer = new byte[blockSize]; var buffer = new byte[blockSize];
var attempt = 0; var attempt = 0;
while (!cancelToken.IsCancellationRequested) { while (!cancelToken.IsCancellationRequested)
{
var read = 0; var read = 0;
try { try
{
read = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize, cancelToken); read = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize, cancelToken);
} catch { }
catch
{
return; return;
} }
if (read == 0) if (read == 0)
@ -168,7 +78,8 @@ namespace NadekoBot.Classes.Music {
break; break;
else else
await Task.Delay(100, cancelToken); await Task.Delay(100, cancelToken);
else { else
{
attempt = 0; attempt = 0;
await Task.Delay(5, cancelToken); await Task.Delay(5, cancelToken);
} }
@ -176,24 +87,35 @@ namespace NadekoBot.Classes.Music {
if (songBuffer.ContentLength > 2.MB()) if (songBuffer.ContentLength > 2.MB())
prebufferingComplete = true; prebufferingComplete = true;
} }
} catch (Exception ex) { }
catch (Exception ex)
{
Console.WriteLine($"Buffering errored: {ex.Message}"); Console.WriteLine($"Buffering errored: {ex.Message}");
} finally { }
finally
{
Console.WriteLine($"Buffering done." + $" [{songBuffer.ContentLength}]"); Console.WriteLine($"Buffering done." + $" [{songBuffer.ContentLength}]");
if (p != null) { if (p != null)
try { {
try
{
p.Kill(); p.Kill();
} catch { } }
catch { }
p.Dispose(); p.Dispose();
} }
} }
}); }, TaskCreationOptions.LongRunning);
internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) { internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
{
var bufferTask = new ConfiguredTaskAwaitable(); var bufferTask = new ConfiguredTaskAwaitable();
try { try
{
bufferTask = BufferSong(cancelToken).ConfigureAwait(false); bufferTask = BufferSong(cancelToken).ConfigureAwait(false);
} catch (Exception ex) { }
catch (Exception ex)
{
var clr = Console.ForegroundColor; var clr = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"ERR BUFFER START : {ex.Message}\n{ex}"); Console.WriteLine($"ERR BUFFER START : {ex.Message}\n{ex}");
@ -202,26 +124,31 @@ namespace NadekoBot.Classes.Music {
var bufferAttempts = 0; var bufferAttempts = 0;
const int waitPerAttempt = 500; const int waitPerAttempt = 500;
var toAttemptTimes = SongInfo.ProviderType != MusicType.Normal ? 5 : 9; var toAttemptTimes = SongInfo.ProviderType != MusicType.Normal ? 5 : 9;
while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes) { while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes)
{
await Task.Delay(waitPerAttempt, cancelToken); await Task.Delay(waitPerAttempt, cancelToken);
} }
cancelToken.ThrowIfCancellationRequested(); cancelToken.ThrowIfCancellationRequested();
Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}"); Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}");
const int blockSize = 3840; const int blockSize = 3840;
var attempt = 0; var attempt = 0;
while (!cancelToken.IsCancellationRequested) { while (!cancelToken.IsCancellationRequested)
{
//Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------");
byte[] buffer = new byte[blockSize]; byte[] buffer = new byte[blockSize];
var read = songBuffer.Read(buffer, blockSize); var read = songBuffer.Read(buffer, blockSize);
unchecked { unchecked
{
bytesSent += (ulong)read; bytesSent += (ulong)read;
} }
if (read == 0) if (read == 0)
if (attempt++ == 20) { if (attempt++ == 20)
{
voiceClient.Wait(); voiceClient.Wait();
Console.WriteLine($"Song finished. [{songBuffer.ContentLength}]"); Console.WriteLine($"Song finished. [{songBuffer.ContentLength}]");
break; break;
} else }
else
await Task.Delay(100, cancelToken); await Task.Delay(100, cancelToken);
else else
attempt = 0; attempt = 0;
@ -239,11 +166,13 @@ namespace NadekoBot.Classes.Music {
} }
//stackoverflow ftw //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) if (Math.Abs(volume - 1.0f) < 0.01f)
return audioSamples; return audioSamples;
var array = new byte[audioSamples.Length]; 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 // convert byte pair to int
short buf1 = audioSamples[i + 1]; short buf1 = audioSamples[i + 1];
@ -263,35 +192,43 @@ namespace NadekoBot.Classes.Music {
return array; return array;
} }
public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal) { public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal)
{
if (string.IsNullOrWhiteSpace(query)) if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query)); throw new ArgumentNullException(nameof(query));
if (musicType != MusicType.Local && IsRadioLink(query)) { if (musicType != MusicType.Local && IsRadioLink(query))
{
musicType = MusicType.Radio; musicType = MusicType.Radio;
query = await HandleStreamContainers(query) ?? query; query = await HandleStreamContainers(query) ?? query;
} }
try { try
switch (musicType) { {
switch (musicType)
{
case MusicType.Local: case MusicType.Local:
return new Song(new SongInfo { return new Song(new SongInfo
{
Uri = "\"" + Path.GetFullPath(query) + "\"", Uri = "\"" + Path.GetFullPath(query) + "\"",
Title = Path.GetFileNameWithoutExtension(query), Title = Path.GetFileNameWithoutExtension(query),
Provider = "Local File", Provider = "Local File",
ProviderType = musicType, ProviderType = musicType,
}); });
case MusicType.Radio: case MusicType.Radio:
return new Song(new SongInfo { return new Song(new SongInfo
{
Uri = query, Uri = query,
Title = $"{query}", Title = $"{query}",
Provider = "Radio Stream", Provider = "Radio Stream",
ProviderType = musicType, ProviderType = musicType,
}); });
} }
if (SoundCloud.Default.IsSoundCloudLink(query)) { if (SoundCloud.Default.IsSoundCloudLink(query))
{
var svideo = await SoundCloud.Default.GetVideoAsync(query); var svideo = await SoundCloud.Default.GetVideoAsync(query);
return new Song(new SongInfo { return new Song(new SongInfo
{
Title = svideo.FullName, Title = svideo.FullName,
Provider = "SoundCloud", Provider = "SoundCloud",
Uri = svideo.StreamLink, Uri = svideo.StreamLink,
@ -310,76 +247,99 @@ namespace NadekoBot.Classes.Music {
if (video == null) // do something with this error if (video == null) // do something with this error
throw new Exception("Could not load any video elements based on the query."); 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" Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
Provider = "YouTube", Provider = "YouTube",
Uri = video.Uri, Uri = video.Uri,
ProviderType = musicType, ProviderType = musicType,
}); });
} catch (Exception ex) { }
catch (Exception ex)
{
Console.WriteLine($"Failed resolving the link.{ex.Message}"); Console.WriteLine($"Failed resolving the link.{ex.Message}");
return null; return null;
} }
} }
private static async Task<string> HandleStreamContainers(string query) { private static async Task<string> HandleStreamContainers(string query)
{
string file = null; string file = null;
try { try
{
file = await SearchHelper.GetResponseStringAsync(query); file = await SearchHelper.GetResponseStringAsync(query);
} catch { }
catch
{
return query; return query;
} }
if (query.Contains(".pls")) { if (query.Contains(".pls"))
{
//File1=http://armitunes.com:8000/ //File1=http://armitunes.com:8000/
//Regex.Match(query) //Regex.Match(query)
try { try
{
var m = Regex.Match(file, "File1=(?<url>.*?)\\n"); var m = Regex.Match(file, "File1=(?<url>.*?)\\n");
var res = m.Groups["url"]?.ToString(); var res = m.Groups["url"]?.ToString();
return res?.Trim(); return res?.Trim();
} catch { }
catch
{
Console.WriteLine($"Failed reading .pls:\n{file}"); Console.WriteLine($"Failed reading .pls:\n{file}");
return null; return null;
} }
} }
if (query.Contains(".m3u")) { if (query.Contains(".m3u"))
{
/* /*
# This is a comment # This is a comment
C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
*/ */
try { try
{
var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline); var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline);
var res = m.Groups["url"]?.ToString(); var res = m.Groups["url"]?.ToString();
return res?.Trim(); return res?.Trim();
} catch { }
catch
{
Console.WriteLine($"Failed reading .m3u:\n{file}"); Console.WriteLine($"Failed reading .m3u:\n{file}");
return null; return null;
} }
} }
if (query.Contains(".asx")) { if (query.Contains(".asx"))
{
//<ref href="http://armitunes.com:8000"/> //<ref href="http://armitunes.com:8000"/>
try { try
{
var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\""); var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
var res = m.Groups["url"]?.ToString(); var res = m.Groups["url"]?.ToString();
return res?.Trim(); return res?.Trim();
} catch { }
catch
{
Console.WriteLine($"Failed reading .asx:\n{file}"); Console.WriteLine($"Failed reading .asx:\n{file}");
return null; return null;
} }
} }
if (query.Contains(".xspf")) { if (query.Contains(".xspf"))
{
/* /*
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<playlist version="1" xmlns="http://xspf.org/ns/0/"> <playlist version="1" xmlns="http://xspf.org/ns/0/">
<trackList> <trackList>
<track><location>file:///mp3s/song_1.mp3</location></track> <track><location>file:///mp3s/song_1.mp3</location></track>
*/ */
try { try
{
var m = Regex.Match(file, "<location>(?<url>.*?)</location>"); var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
var res = m.Groups["url"]?.ToString(); var res = m.Groups["url"]?.ToString();
return res?.Trim(); return res?.Trim();
} catch { }
catch
{
Console.WriteLine($"Failed reading .xspf:\n{file}"); Console.WriteLine($"Failed reading .xspf:\n{file}");
return null; return null;
} }

View File

@ -1,35 +1,41 @@
using System; using Discord.Commands;
using System.Threading.Tasks;
using Discord.Commands;
using System.Collections.Concurrent;
using Discord;
using NadekoBot.Modules; using NadekoBot.Modules;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using TriviaGame = NadekoBot.Classes.Trivia.TriviaGame; using TriviaGame = NadekoBot.Classes.Trivia.TriviaGame;
namespace NadekoBot.Commands { namespace NadekoBot.Commands
internal class Trivia : DiscordCommand { {
internal class Trivia : DiscordCommand
{
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias = new ConcurrentDictionary<ulong, TriviaGame>(); public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias = new ConcurrentDictionary<ulong, TriviaGame>();
public Func<CommandEventArgs, Task> DoFunc() => async e => { public Func<CommandEventArgs, Task> DoFunc() => async e =>
{
TriviaGame trivia; TriviaGame trivia;
if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) { if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia))
{
var triviaGame = new TriviaGame(e); var triviaGame = new TriviaGame(e);
if (RunningTrivias.TryAdd(e.Server.Id, triviaGame)) 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.**"); 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 else
await triviaGame.StopGame(); await triviaGame.StopGame();
} else }
else
await e.Channel.SendMessage("Trivia game is already running on this server.\n" + trivia.CurrentQuestion); 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") cgb.CreateCommand(Module.Prefix + "t")
.Description("Starts a game of trivia.") .Description("Starts a game of trivia.")
.Do(DoFunc()); .Do(DoFunc());
cgb.CreateCommand(Module.Prefix + "tl") cgb.CreateCommand(Module.Prefix + "tl")
.Description("Shows a current trivia leaderboard.") .Description("Shows a current trivia leaderboard.")
.Do(async e=> { .Do(async e =>
{
TriviaGame trivia; TriviaGame trivia;
if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) if (RunningTrivias.TryGetValue(e.Server.Id, out trivia))
await e.Channel.SendMessage(trivia.GetLeaderboard()); await e.Channel.SendMessage(trivia.GetLeaderboard());
@ -39,15 +45,18 @@ namespace NadekoBot.Commands {
cgb.CreateCommand(Module.Prefix + "tq") cgb.CreateCommand(Module.Prefix + "tq")
.Description("Quits current trivia after current question.") .Description("Quits current trivia after current question.")
.Do(async e=> { .Do(async e =>
{
TriviaGame trivia; TriviaGame trivia;
if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) { if (RunningTrivias.TryGetValue(e.Server.Id, out trivia))
{
await trivia.StopGame(); await trivia.StopGame();
} else }
else
await e.Channel.SendMessage("No trivia is running on this server."); await e.Channel.SendMessage("No trivia is running on this server.");
}); });
} }
public Trivia(DiscordModule module) : base(module) {} public Trivia(DiscordModule module) : base(module) { }
} }
} }

View File

@ -148,8 +148,6 @@ namespace NadekoBot.Modules.Administration.Commands
str += $"`New name:` **{e.After.Name}**"; str += $"`New name:` **{e.After.Name}**";
else if (e.Before.AvatarUrl != e.After.AvatarUrl) else if (e.Before.AvatarUrl != e.After.AvatarUrl)
str += $"`New Avatar:` {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 else
return; return;
await ch.SendMessage(str); await ch.SendMessage(str);

View File

@ -20,6 +20,7 @@ namespace NadekoBot.Modules.Administration.Commands
public ulong RepeatingServerId { get; set; } public ulong RepeatingServerId { get; set; }
public ulong RepeatingChannelId { get; set; } public ulong RepeatingChannelId { get; set; }
public Message lastMessage { get; set; } = null;
public string RepeatingMessage { get; set; } public string RepeatingMessage { get; set; }
public int Interval { get; set; } public int Interval { get; set; }
@ -34,7 +35,13 @@ namespace NadekoBot.Modules.Administration.Commands
{ {
try try
{ {
await ch.SendMessage(msg); if (lastMessage != null)
await lastMessage.Delete();
}
catch { }
try
{
lastMessage = await ch.SendMessage(msg);
} }
catch { } catch { }
} }

View File

@ -74,6 +74,10 @@ namespace NadekoBot.Modules.Administration.Commands
internal override void Init(CommandGroupBuilder cgb) internal override void Init(CommandGroupBuilder cgb)
{ {
cgb.CreateCommand(Module.Prefix + "remind") 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("meorchannel", ParameterType.Required)
.Parameter("time", ParameterType.Required) .Parameter("time", ParameterType.Required)
.Parameter("message", ParameterType.Unparsed) .Parameter("message", ParameterType.Unparsed)

View File

@ -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 ?? "⬜";
}
}

View File

@ -2,21 +2,23 @@
using Discord.Modules; using Discord.Modules;
using NadekoBot.Commands; using NadekoBot.Commands;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Commands;
using System; using System;
using System.Linq; using System.Linq;
namespace NadekoBot.Modules namespace NadekoBot.Modules.Games
{ {
internal class Games : DiscordModule internal class GamesModule : DiscordModule
{ {
private readonly Random rng = new Random(); private readonly Random rng = new Random();
public Games() public GamesModule()
{ {
commands.Add(new Trivia(this)); commands.Add(new Trivia(this));
commands.Add(new SpeedTyping(this)); commands.Add(new SpeedTyping(this));
commands.Add(new PollCommand(this)); commands.Add(new PollCommand(this));
commands.Add(new PlantPick(this)); commands.Add(new PlantPick(this));
commands.Add(new Bomberman(this));
//commands.Add(new BetrayGame(this)); //commands.Add(new BetrayGame(this));
} }

View File

@ -71,7 +71,7 @@ namespace NadekoBot.Modules
cgb.CreateCommand("n") cgb.CreateCommand("n")
.Alias("next") .Alias("next")
.Alias("skip") .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 => .Do(e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -81,7 +81,7 @@ namespace NadekoBot.Modules
cgb.CreateCommand("s") cgb.CreateCommand("s")
.Alias("stop") .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 => .Do(async e =>
{ {
await Task.Run(() => await Task.Run(() =>
@ -94,7 +94,8 @@ namespace NadekoBot.Modules
cgb.CreateCommand("d") cgb.CreateCommand("d")
.Alias("destroy") .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 => .Do(async e =>
{ {
await Task.Run(() => await Task.Run(() =>
@ -107,7 +108,7 @@ namespace NadekoBot.Modules
cgb.CreateCommand("p") cgb.CreateCommand("p")
.Alias("pause") .Alias("pause")
.Description("Pauses or Unpauses the song.") .Description("Pauses or Unpauses the song.\n**Usage**: `!m p`")
.Do(async e => .Do(async e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -121,7 +122,8 @@ namespace NadekoBot.Modules
cgb.CreateCommand("q") cgb.CreateCommand("q")
.Alias("yq") .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) .Parameter("query", ParameterType.Unparsed)
.Do(async e => .Do(async e =>
{ {
@ -130,7 +132,7 @@ namespace NadekoBot.Modules
cgb.CreateCommand("lq") cgb.CreateCommand("lq")
.Alias("ls").Alias("lp") .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 => .Do(async e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -158,7 +160,7 @@ namespace NadekoBot.Modules
cgb.CreateCommand("np") cgb.CreateCommand("np")
.Alias("playing") .Alias("playing")
.Description("Shows the song currently playing.") .Description("Shows the song currently playing.\n**Usage**: `!m np`")
.Do(async e => .Do(async e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -172,7 +174,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("vol") 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) .Parameter("val", ParameterType.Required)
.Do(async e => .Do(async e =>
{ {
@ -192,7 +194,8 @@ namespace NadekoBot.Modules
cgb.CreateCommand("dv") cgb.CreateCommand("dv")
.Alias("defvol") .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) .Parameter("val", ParameterType.Required)
.Do(async e => .Do(async e =>
{ {
@ -208,7 +211,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("min").Alias("mute") 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 => .Do(e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -218,7 +221,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("max") 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 => .Do(e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -228,7 +231,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("half") cgb.CreateCommand("half")
.Description("Sets the music volume to 50%.") .Description("Sets the music volume to 50%.\n**Usage**: `!m half`")
.Do(e => .Do(e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -238,7 +241,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("sh") cgb.CreateCommand("sh")
.Description("Shuffles the current playlist.") .Description("Shuffles the current playlist.\n**Usage**: `!m sh`")
.Do(async e => .Do(async e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -254,18 +257,8 @@ namespace NadekoBot.Modules
await e.Channel.SendMessage("🎵 `Songs shuffled.`"); 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") 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) .Parameter("playlist", ParameterType.Unparsed)
.Do(async e => .Do(async e =>
{ {
@ -292,7 +285,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("lopl") 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) .Parameter("directory", ParameterType.Unparsed)
.AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly()) .AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly())
.Do(async e => .Do(async e =>
@ -313,7 +306,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("radio").Alias("ra") 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) .Parameter("radio_link", ParameterType.Required)
.Do(async e => .Do(async e =>
{ {
@ -326,7 +319,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("lo") 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) .Parameter("path", ParameterType.Unparsed)
.AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly()) .AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly())
.Do(async e => .Do(async e =>
@ -338,7 +331,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("mv") 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 => .Do(e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -349,7 +342,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("rm") 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) .Parameter("num", ParameterType.Required)
.Do(async e => .Do(async e =>
{ {
@ -378,7 +371,7 @@ namespace NadekoBot.Modules
}); });
cgb.CreateCommand("cleanup") 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()) .AddCheck(SimpleCheckers.OwnerOnly())
.Do(e => .Do(e =>
{ {
@ -397,7 +390,7 @@ namespace NadekoBot.Modules
cgb.CreateCommand("rcs") cgb.CreateCommand("rcs")
.Alias("repeatcurrentsong") .Alias("repeatcurrentsong")
.Description("Toggles repeat of current song.") .Description("Toggles repeat of current song.\n**Usage**: `!m rcs`")
.Do(async e => .Do(async e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;
@ -414,7 +407,7 @@ namespace NadekoBot.Modules
cgb.CreateCommand("rpl") cgb.CreateCommand("rpl")
.Alias("repeatplaylist") .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 => .Do(async e =>
{ {
MusicPlayer musicPlayer; MusicPlayer musicPlayer;

View File

@ -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<UnitTable>();
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<CommandEventArgs, Task> 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<CommandEventArgs, Task> 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<UnitTable> unitTables;
private static CurrencyExchangeRatesProvider exchangeRateProvider;
private static CurrencyExchangeTable currTable;
private static DateTime lastChanged;
}
}

View File

@ -5,6 +5,7 @@ using NadekoBot.Classes.IMDB;
using NadekoBot.Classes.JSONModels; using NadekoBot.Classes.JSONModels;
using NadekoBot.Commands; using NadekoBot.Commands;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Search.Commands;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
@ -23,6 +24,7 @@ namespace NadekoBot.Modules
{ {
commands.Add(new LoLCommands(this)); commands.Add(new LoLCommands(this));
commands.Add(new StreamNotifications(this)); commands.Add(new StreamNotifications(this));
commands.Add(new ConverterCommand(this));
rng = new Random(); rng = new Random();
} }

View File

@ -7,6 +7,7 @@ using NadekoBot.Commands;
using NadekoBot.Modules; using NadekoBot.Modules;
using NadekoBot.Modules.Administration; using NadekoBot.Modules.Administration;
using NadekoBot.Modules.Gambling; using NadekoBot.Modules.Gambling;
using NadekoBot.Modules.Games;
using NadekoBot.Modules.Pokemon; using NadekoBot.Modules.Pokemon;
using NadekoBot.Modules.Translator; using NadekoBot.Modules.Translator;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -163,7 +164,7 @@ namespace NadekoBot
modules.Add(new PermissionModule(), "Permissions", ModuleFilter.None); modules.Add(new PermissionModule(), "Permissions", ModuleFilter.None);
modules.Add(new Conversations(), "Conversations", ModuleFilter.None); modules.Add(new Conversations(), "Conversations", ModuleFilter.None);
modules.Add(new GamblingModule(), "Gambling", 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 Music(), "Music", ModuleFilter.None);
modules.Add(new Searches(), "Searches", ModuleFilter.None); modules.Add(new Searches(), "Searches", ModuleFilter.None);
modules.Add(new NSFW(), "NSFW", ModuleFilter.None); modules.Add(new NSFW(), "NSFW", ModuleFilter.None);

View File

@ -97,6 +97,10 @@
<HintPath>..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll</HintPath> <HintPath>..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="ScaredFingers.UnitsConversion, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>lib\ScaredFingers.UnitsConversion.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
@ -130,6 +134,7 @@
<Compile Include="Classes\JSONModels\_JSONModels.cs" /> <Compile Include="Classes\JSONModels\_JSONModels.cs" />
<Compile Include="Classes\Leet.cs" /> <Compile Include="Classes\Leet.cs" />
<Compile Include="Classes\Music\MusicControls.cs" /> <Compile Include="Classes\Music\MusicControls.cs" />
<Compile Include="Classes\Music\PoopyBuffer.cs" />
<Compile Include="Classes\Music\Song.cs" /> <Compile Include="Classes\Music\Song.cs" />
<Compile Include="Classes\Music\StreamRequest.cs" /> <Compile Include="Classes\Music\StreamRequest.cs" />
<Compile Include="Classes\Music\SoundCloud.cs" /> <Compile Include="Classes\Music\SoundCloud.cs" />
@ -186,7 +191,8 @@
<Compile Include="Modules\Conversations.cs" /> <Compile Include="Modules\Conversations.cs" />
<Compile Include="Modules\DiscordModule.cs" /> <Compile Include="Modules\DiscordModule.cs" />
<Compile Include="Modules\Gambling\GamblingModule.cs" /> <Compile Include="Modules\Gambling\GamblingModule.cs" />
<Compile Include="Modules\Games.cs" /> <Compile Include="Modules\Games\Commands\Bomberman.cs" />
<Compile Include="Modules\Games\GamesModule.cs" />
<Compile Include="Modules\Help.cs" /> <Compile Include="Modules\Help.cs" />
<Compile Include="Modules\Music.cs" /> <Compile Include="Modules\Music.cs" />
<Compile Include="Commands\PollCommand.cs" /> <Compile Include="Commands\PollCommand.cs" />
@ -217,6 +223,7 @@
<Compile Include="Modules\Pokemon\PokemonTypes\NormalType.cs" /> <Compile Include="Modules\Pokemon\PokemonTypes\NormalType.cs" />
<Compile Include="Modules\Pokemon\PokeStats.cs" /> <Compile Include="Modules\Pokemon\PokeStats.cs" />
<Compile Include="Modules\Searches.cs" /> <Compile Include="Modules\Searches.cs" />
<Compile Include="Modules\Search\Commands\ConverterCommand.cs" />
<Compile Include="Modules\Translator\Helpers\GoogleTranslator.cs" /> <Compile Include="Modules\Translator\Helpers\GoogleTranslator.cs" />
<Compile Include="Modules\Translator\TranslateCommand.cs" /> <Compile Include="Modules\Translator\TranslateCommand.cs" />
<Compile Include="Modules\Translator\TranslatorModule.cs" /> <Compile Include="Modules\Translator\TranslatorModule.cs" />
@ -478,6 +485,9 @@
<Name>Discord.Net</Name> <Name>Discord.Net</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="lib\ScaredFingers.UnitsConversion.dll" />
</ItemGroup>
<ItemGroup /> <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.

Binary file not shown.

Binary file not shown.

View File

@ -2,7 +2,7 @@
######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` ######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa`
#NadekoBot List Of Commands #NadekoBot List Of Commands
Version: `NadekoBot v0.9.5933.23628` Version: `NadekoBot v0.9.5936.28974`
### Administration ### Administration
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
@ -31,7 +31,7 @@ Command and aliases | Description | Usage
`.lsar` | Lits all self-assignable roles. `.lsar` | Lits all self-assignable roles.
`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | .iam Gamer `.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | .iam Gamer
`.iamn`, `.iamnot` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | .iamn Gamer `.iamn`, `.iamnot` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | .iamn Gamer
`.remind` | `.remind` | 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. | `.remind me 1d5h Do something` or `.remind #general Start now!`
`.sr`, `.setrole` | Sets a role for a given user. | .sr @User Guest `.sr`, `.setrole` | Sets a role for a given user. | .sr @User Guest
`.rr`, `.removerole` | Removes a role from a given user. | .rr @User Admin `.rr`, `.removerole` | Removes a role from a given user. | .rr @User Admin
`.r`, `.role`, `.cr` | Creates a role with a given name. | .r Awesome Role `.r`, `.role`, `.cr` | Creates a role with a given name. | .r Awesome Role
@ -166,7 +166,7 @@ Command and aliases | Description | Usage
`$draw` | Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck. | $draw [x] `$draw` | Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck. | $draw [x]
`$shuffle`, `$sh` | Reshuffles all cards back into the deck. `$shuffle`, `$sh` | Reshuffles all cards back into the deck.
`$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3` `$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3`
`$roll` | Rolls 2 dice from 0-10. If you supply a number [x] it rolls up to 30 normal dice. | $roll [x] `$roll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | $roll or $roll 7 or $roll 3d5
`$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15` `$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15`
`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. `$raffle` | Prints a name and ID of a random user from the online list from the (optional) role.
`$$$` | Check how much NadekoFlowers you have. `$$$` | Check how much NadekoFlowers you have.
@ -185,6 +185,8 @@ Command and aliases | Description | Usage
`>typeadd` | Adds a new article to the typing contest. Owner only. `>typeadd` | Adds a new article to the typing contest. Owner only.
`>poll` | Creates a poll, only person who has manage server permission can do it. | >poll Question?;Answer1;Answ 2;A_3 `>poll` | Creates a poll, only person who has manage server permission can do it. | >poll Question?;Answer1;Answ 2;A_3
`>pollend` | Stops active poll on this server and prints the results in this channel. `>pollend` | Stops active poll on this server and prints the results in this channel.
`>pick` | Picks a flower planted in this channel.
`>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost)
`>choose` | Chooses a thing from a list of things | >choose Get up;Sleep;Sleep more `>choose` | Chooses a thing from a list of things | >choose Get up;Sleep;Sleep more
`>8ball` | Ask the 8ball a yes/no question. `>8ball` | Ask the 8ball a yes/no question.
`>rps` | Play a game of rocket paperclip scissors with Nadeko. | >rps scissors `>rps` | Play a game of rocket paperclip scissors with Nadeko. | >rps scissors
@ -193,27 +195,28 @@ Command and aliases | Description | Usage
### Music ### Music
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`!m n`, `!m next`, `!m skip` | Goes to the next song in the queue. `!m n`, `!m next`, `!m skip` | Goes to the next song in the queue. | `!m n`
`!m s`, `!m stop` | Stops the music and clears the playlist. Stays in the channel. `!m s`, `!m stop` | Stops the music and clears the playlist. Stays in the channel. | `!m s`
`!m d`, `!m destroy` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) `!m d`, `!m destroy` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!m d`
`!m p`, `!m pause` | Pauses or Unpauses the song. `!m p`, `!m pause` | Pauses or Unpauses the song. | `!m p`
`!m q`, `!m yq` | Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**. | `!m q Dream Of Venice` `!m q`, `!m yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!m q Dream Of Venice`
`!m lq`, `!m ls`, `!m lp` | Lists up to 15 currently queued songs. `!m lq`, `!m ls`, `!m lp` | Lists up to 15 currently queued songs. | `!m lq`
`!m np`, `!m playing` | Shows the song currently playing. `!m np`, `!m playing` | Shows the song currently playing. | `!m np`
`!m vol` | Sets the music volume 0-150% `!m vol` | Sets the music volume 0-150% | `!m vol 50`
`!m dv`, `!m defvol` | Sets the default music volume when music playback is started (0-100). Does not persist through restarts. | !m dv 80 `!m dv`, `!m defvol` | Sets the default music volume when music playback is started (0-100). Does not persist through restarts. | `!m dv 80`
`!m min`, `!m mute` | Sets the music volume to 0% `!m min`, `!m mute` | Sets the music volume to 0% | `!m min`
`!m max` | Sets the music volume to 100% (real max is actually 150%). `!m max` | Sets the music volume to 100% (real max is actually 150%). | `!m max`
`!m half` | Sets the music volume to 50%. `!m half` | Sets the music volume to 50%. | `!m half`
`!m sh` | Shuffles the current playlist. `!m sh` | Shuffles the current playlist. | `!m sh`
`!m setgame` | Sets the game of the bot to the number of songs playing. **Owner only** `!m pl` | Queues up to 25 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name`
`!m pl` | Queues up to 25 songs from a youtube playlist specified by a link, or keywords. `!m lopl` | Queues up to 50 songs from a directory. **Owner Only!** | `!m lopl C:/music/classical`
`!m lopl` | Queues up to 50 songs from a directory. **Owner Only!** `!m radio`, `!m ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf | `!m ra radio link here`
`!m radio`, `!m ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf `!m lo` | Queues a local file by specifying a full path. **Owner Only!** | `!m ra C:/music/mysong.mp3`
`!m lo` | Queues a local file by specifying a full path. **Owner Only!** `!m mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv`
`!m mv` | Moves the bot to your voice channel. (works only if music is already playing) `!m rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5`
`!m rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. `!m cleanup` | Cleans up hanging voice connections. **Owner Only!** | `!m cleanup`
`!m cleanup` | Cleans up hanging voice connections. **Owner Only!** `!m rcs`, `!m repeatcurrentsong` | Toggles repeat of current song. | `!m rcs`
`!m rpl`, `!m repeatplaylist` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!m rpl`
### Searches ### Searches
Command and aliases | Description | Usage Command and aliases | Description | Usage
@ -225,6 +228,8 @@ Command and aliases | Description | Usage
`~beam`, `~bm` | Notifies this channel when a certain user starts streaming. | ~beam SomeStreamer `~beam`, `~bm` | Notifies this channel when a certain user starts streaming. | ~beam SomeStreamer
`~removestream`, `~rms` | Removes notifications of a certain streamer on this channel. | ~rms SomeGuy `~removestream`, `~rms` | Removes notifications of a certain streamer on this channel. | ~rms SomeGuy
`~liststreams`, `~ls` | Lists all streams you are following on this server. | ~ls `~liststreams`, `~ls` | Lists all streams you are following on this server. | ~ls
`~convert` | Convert quantities from>to. Like `~convert m>km 1000`
`~convertlist` | List of the convertable dimensions and currencies.
`~we` | Shows weather data for a specified city and a country BOTH ARE REQUIRED. Weather api is very random if you make a mistake. `~we` | Shows weather data for a specified city and a country BOTH ARE REQUIRED. Weather api is very random if you make a mistake.
`~yt` | Searches youtubes and shows the first result `~yt` | Searches youtubes and shows the first result
`~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. `~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result.
@ -243,6 +248,7 @@ Command and aliases | Description | Usage
`~yomama`, `~ym` | Shows a random joke from <http://api.yomomma.info/> `~yomama`, `~ym` | Shows a random joke from <http://api.yomomma.info/>
`~randjoke`, `~rj` | Shows a random joke from <http://tambal.azurewebsites.net/joke/random> `~randjoke`, `~rj` | Shows a random joke from <http://tambal.azurewebsites.net/joke/random>
`~chucknorris`, `~cn` | Shows a random chucknorris joke from <http://tambal.azurewebsites.net/joke/random> `~chucknorris`, `~cn` | Shows a random chucknorris joke from <http://tambal.azurewebsites.net/joke/random>
`~mi`, `magicitem` | Shows a random magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items>
`~revav` | Returns a google reverse image search for someone's avatar. `~revav` | Returns a google reverse image search for someone's avatar.
### NSFW ### NSFW