Merge remote-tracking branch 'refs/remotes/Kwoth/master'
This commit is contained in:
commit
d82cd19b7b
120
NadekoBot/Classes/Music/PoopyBuffer.cs
Normal file
120
NadekoBot/Classes/Music/PoopyBuffer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal) {
|
||||
public static async Task<Song> 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<string> HandleStreamContainers(string query) {
|
||||
private static async Task<string> 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=(?<url>.*?)\\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, "(?<url>^[^#].*)", 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"))
|
||||
{
|
||||
//<ref href="http://armitunes.com:8000"/>
|
||||
try {
|
||||
try
|
||||
{
|
||||
var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
|
||||
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"))
|
||||
{
|
||||
/*
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<playlist version="1" xmlns="http://xspf.org/ns/0/">
|
||||
<trackList>
|
||||
<track><location>file:///mp3s/song_1.mp3</location></track>
|
||||
*/
|
||||
try {
|
||||
try
|
||||
{
|
||||
var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
|
||||
var res = m.Groups["url"]?.ToString();
|
||||
return res?.Trim();
|
||||
} catch {
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"Failed reading .xspf:\n{file}");
|
||||
return null;
|
||||
}
|
||||
|
@ -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<ulong, TriviaGame> RunningTrivias = new ConcurrentDictionary<ulong, TriviaGame>();
|
||||
|
||||
public Func<CommandEventArgs, Task> DoFunc() => async e => {
|
||||
public Func<CommandEventArgs, Task> 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) { }
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 { }
|
||||
}
|
||||
|
@ -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)
|
||||
|
125
NadekoBot/Modules/Games/Commands/Bomberman.cs
Normal file
125
NadekoBot/Modules/Games/Commands/Bomberman.cs
Normal 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 ?? "⬜";
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
||||
}
|
@ -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;
|
||||
|
162
NadekoBot/Modules/Search/Commands/ConverterCommand.cs
Normal file
162
NadekoBot/Modules/Search/Commands/ConverterCommand.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -97,6 +97,10 @@
|
||||
<HintPath>..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</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.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
@ -130,6 +134,7 @@
|
||||
<Compile Include="Classes\JSONModels\_JSONModels.cs" />
|
||||
<Compile Include="Classes\Leet.cs" />
|
||||
<Compile Include="Classes\Music\MusicControls.cs" />
|
||||
<Compile Include="Classes\Music\PoopyBuffer.cs" />
|
||||
<Compile Include="Classes\Music\Song.cs" />
|
||||
<Compile Include="Classes\Music\StreamRequest.cs" />
|
||||
<Compile Include="Classes\Music\SoundCloud.cs" />
|
||||
@ -186,7 +191,8 @@
|
||||
<Compile Include="Modules\Conversations.cs" />
|
||||
<Compile Include="Modules\DiscordModule.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\Music.cs" />
|
||||
<Compile Include="Commands\PollCommand.cs" />
|
||||
@ -217,6 +223,7 @@
|
||||
<Compile Include="Modules\Pokemon\PokemonTypes\NormalType.cs" />
|
||||
<Compile Include="Modules\Pokemon\PokeStats.cs" />
|
||||
<Compile Include="Modules\Searches.cs" />
|
||||
<Compile Include="Modules\Search\Commands\ConverterCommand.cs" />
|
||||
<Compile Include="Modules\Translator\Helpers\GoogleTranslator.cs" />
|
||||
<Compile Include="Modules\Translator\TranslateCommand.cs" />
|
||||
<Compile Include="Modules\Translator\TranslatorModule.cs" />
|
||||
@ -478,6 +485,9 @@
|
||||
<Name>Discord.Net</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="lib\ScaredFingers.UnitsConversion.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
BIN
NadekoBot/lib/ScaredFingers.UnitsConversion.dll
Normal file
BIN
NadekoBot/lib/ScaredFingers.UnitsConversion.dll
Normal file
Binary file not shown.
BIN
NadekoBot/lib/ScaredFingers.UnitsConversion.pdb
Normal file
BIN
NadekoBot/lib/ScaredFingers.UnitsConversion.pdb
Normal file
Binary file not shown.
@ -2,7 +2,7 @@
|
||||
######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa`
|
||||
|
||||
#NadekoBot List Of Commands
|
||||
Version: `NadekoBot v0.9.5933.23628`
|
||||
Version: `NadekoBot v0.9.5936.28974`
|
||||
### Administration
|
||||
Command and aliases | Description | Usage
|
||||
----------------|--------------|-------
|
||||
@ -31,7 +31,7 @@ Command and aliases | Description | Usage
|
||||
`.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
|
||||
`.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
|
||||
`.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
|
||||
@ -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]
|
||||
`$shuffle`, `$sh` | Reshuffles all cards back into the deck.
|
||||
`$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`
|
||||
`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role.
|
||||
`$$$` | 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.
|
||||
`>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.
|
||||
`>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
|
||||
`>8ball` | Ask the 8ball a yes/no question.
|
||||
`>rps` | Play a game of rocket paperclip scissors with Nadeko. | >rps scissors
|
||||
@ -193,27 +195,28 @@ Command and aliases | Description | Usage
|
||||
### Music
|
||||
Command and aliases | Description | Usage
|
||||
----------------|--------------|-------
|
||||
`!m n`, `!m next`, `!m skip` | Goes to the next song in the queue.
|
||||
`!m s`, `!m stop` | Stops the music and clears the playlist. Stays in the channel.
|
||||
`!m d`, `!m destroy` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour)
|
||||
`!m p`, `!m pause` | Pauses or Unpauses the song.
|
||||
`!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 np`, `!m playing` | Shows the song currently playing.
|
||||
`!m vol` | Sets the music volume 0-150%
|
||||
`!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 max` | Sets the music volume to 100% (real max is actually 150%).
|
||||
`!m half` | Sets the music volume to 50%.
|
||||
`!m sh` | Shuffles the current playlist.
|
||||
`!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 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 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 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 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 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 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 np`, `!m playing` | Shows the song currently playing. | `!m np`
|
||||
`!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 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`
|
||||
`!m half` | Sets the music volume to 50%. | `!m half`
|
||||
`!m sh` | Shuffles the current playlist. | `!m sh`
|
||||
`!m pl` | Queues up to 25 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name`
|
||||
`!m lopl` | Queues up to 50 songs from a directory. **Owner Only!** | `!m lopl C:/music/classical`
|
||||
`!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 lo` | Queues a local file by specifying a full path. **Owner Only!** | `!m ra C:/music/mysong.mp3`
|
||||
`!m mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv`
|
||||
`!m rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5`
|
||||
`!m cleanup` | Cleans up hanging voice connections. **Owner Only!** | `!m cleanup`
|
||||
`!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
|
||||
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
|
||||
`~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
|
||||
`~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.
|
||||
`~yt` | Searches youtubes 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/>
|
||||
`~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>
|
||||
`~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.
|
||||
|
||||
### NSFW
|
||||
|
Loading…
Reference in New Issue
Block a user