Merge pull request #10 from Kwoth/dev

Dev
This commit is contained in:
miraai 2016-08-01 21:40:26 +02:00 committed by GitHub
commit a1da64e4ff
15 changed files with 310 additions and 112 deletions

47
DockerGuide.md Normal file
View File

@ -0,0 +1,47 @@
## Docker guide with digitalocean
#####Prerequisites:
- Digital ocean account (you can use my reflink to support the project and get 10$ after you register http://m.do.co/c/46b4d3d44795/ )
- Putty (get it here http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)
- A bot account - (follow http://discord.kongslien.net/guide.html)
- Common sense
Click on the create droplet button
![img](http://i.imgur.com/g2ayOcC.png)
pick one click apps and select docker on 14.04
![img](http://imgur.com/065Xkme.png)
- pick any droplet size you want (5$ will work ok-ish on a few servers)
- pick location closest to your discord server's location
- Pick a hostname
![img](http://imgur.com/ifPKB6p.png)
- click create
You will get an email from digitalocean with your creds now.
Open putty and type ip adress **you got in your email** with port 22
![img](http://imgur.com/Mh5ehsh.png)
console will open and you will be prompted for a username, type `root`
type in the password you got in the email
confirm the password you just typed in
type in the new password
confirm new password
when you are successfully logged in, type
`docker run --name nadeko -v /nadeko:/config uirel/nadeko`
wait for it to download and at one point it is going to start throwing errors due to credentials.json being empty
CTRL+C to exit that
type `docker stop nadeko`
type `nano /nadeko/credentials.json` and type in your credentials
CTRL+X then CTRL+Y to save
type `docker start nadeko`
Your bot is running, enjoy
*When you want to update the bot, just type `docker restart nadeko` as it always downloads latest prerelease*

View File

@ -175,7 +175,7 @@ namespace NadekoBot.Classes
return null;
}
public static async Task<string> GetRelatedVideoId(string id)
public static async Task<IEnumerable<string>> GetRelatedVideoIds(string id, int count = 1)
{
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException(nameof(id));
@ -186,20 +186,14 @@ namespace NadekoBot.Classes
}
var response = await GetResponseStringAsync(
$"https://www.googleapis.com/youtube/v3/search?" +
$"part=snippet&maxResults=1&type=video" +
$"part=snippet&maxResults={count}&type=video" +
$"&relatedToVideoId={id}" +
$"&key={NadekoBot.Creds.GoogleAPIKey}").ConfigureAwait(false);
JObject obj = JObject.Parse(response);
var data = JsonConvert.DeserializeObject<YoutubeVideoSearch>(response);
if (data.items.Length > 0)
{
var toReturn = "http://www.youtube.com/watch?v=" + data.items[0].id.videoId.ToString();
return toReturn;
}
else
return null;
return data.items.Select(v => "http://www.youtube.com/watch?v=" + v.id.videoId);
}
public static async Task<string> GetPlaylistIdByKeyword(string query)

View File

@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Administration.Commands
{
cgb.CreateCommand(Module.Prefix + "autoassignrole")
.Alias(Module.Prefix + "aar")
.Description($"Automaticaly assigns a specified role to every user who joins the server. Type `{Prefix}aar` to disable, `{Prefix}aar Role Name` to enable")
.Description($"Automaticaly assigns a specified role to every user who joins the server. |`{Prefix}aar` to disable, `{Prefix}aar Role Name` to enable")
.Parameter("role", ParameterType.Unparsed)
.AddCheck(new SimpleCheckers.ManageRoles())
.Do(async e =>

View File

@ -4,7 +4,10 @@ using NadekoBot.Classes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions.Classes;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration.Commands
{
@ -12,6 +15,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】";
private ConcurrentBag<KeyValuePair<Channel, string>> voicePresenceUpdates = new ConcurrentBag<KeyValuePair<Channel, string>>();
public LogCommand(DiscordModule module) : base(module)
{
NadekoBot.Client.MessageReceived += MsgRecivd;
@ -45,6 +50,36 @@ namespace NadekoBot.Modules.Administration.Commands
}
catch { }
};
// start the userpresence queue
NadekoBot.OnReady += () => Task.Run(async () =>
{
while (true)
{
var toSend = new Dictionary<Channel, string>();
//take everything from the queue and merge the messages which are going to the same channel
KeyValuePair<Channel, string> item;
while (voicePresenceUpdates.TryTake(out item))
{
if (toSend.ContainsKey(item.Key))
{
toSend[item.Key] = toSend[item.Key] + Environment.NewLine + item.Value;
}
else
{
toSend.Add(item.Key, item.Value);
}
}
//send merged messages to each channel
foreach (var k in toSend)
{
try { await k.Key.SendMessage(Environment.NewLine + k.Value).ConfigureAwait(false); } catch { }
}
await Task.Delay(5000);
}
});
}
private async void ChannelUpdated(object sender, ChannelUpdatedEventArgs e)
@ -177,13 +212,13 @@ namespace NadekoBot.Modules.Administration.Commands
if (!string.IsNullOrWhiteSpace(e.Message.Text))
{
await ch.SendMessage(
$@"🕔`{prettyCurrentTime}` **New Message** `#{e.Channel.Name}`
$@"🕔`{prettyCurrentTime}` **New Message** `#{e.Channel.Name}`
👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Text.Unmention()}").ConfigureAwait(false);
}
else
{
await ch.SendMessage(
$@"🕔`{prettyCurrentTime}` **File Uploaded** `#{e.Channel.Name}`
$@"🕔`{prettyCurrentTime}` **File Uploaded** `#{e.Channel.Name}`
👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Attachments.FirstOrDefault()?.ProxyUrl}").ConfigureAwait(false);
}
@ -206,13 +241,13 @@ namespace NadekoBot.Modules.Administration.Commands
if (!string.IsNullOrWhiteSpace(e.Message.Text))
{
await ch.SendMessage(
$@"🕔`{prettyCurrentTime}` **Message** 🚮 `#{e.Channel.Name}`
$@"🕔`{prettyCurrentTime}` **Message** 🚮 `#{e.Channel.Name}`
👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Text.Unmention()}").ConfigureAwait(false);
}
else
{
await ch.SendMessage(
$@"🕔`{prettyCurrentTime}` **File Deleted** `#{e.Channel.Name}`
$@"🕔`{prettyCurrentTime}` **File Deleted** `#{e.Channel.Name}`
👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Attachments.FirstOrDefault()?.ProxyUrl}").ConfigureAwait(false);
}
}
@ -232,7 +267,7 @@ namespace NadekoBot.Modules.Administration.Commands
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return;
await ch.SendMessage(
$@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
$@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
👤`{e.User?.ToString() ?? ("NULL")}`
`Old:` {e.Before.Text.Unmention()}
`New:` {e.After.Text.Unmention()}").ConfigureAwait(false);
@ -252,7 +287,7 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
{
if (e.Before.Status != e.After.Status)
{
await ch.SendMessage($"`{prettyCurrentTime}`**{e.Before.Name}** is now **{e.After.Status}**.").ConfigureAwait(false);
voicePresenceUpdates.Add(new KeyValuePair<Channel, string>(ch, $"`{prettyCurrentTime}`**{e.Before.Name}** is now **{e.After.Status}**."));
}
}
}
@ -372,7 +407,7 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return;
SpecificConfigurations.Default.Of (e.Server.Id).LogServerChannel = null;
SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = null;
await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false);
});

View File

@ -57,7 +57,7 @@ namespace NadekoBot.Modules.Administration.Commands
cgb.CreateCommand(Module.Prefix + "repeatinvoke")
.Alias(Module.Prefix + "repinv")
.Description("Immediately shows the repeat message and restarts the timer. | `{Prefix}repinv`")
.Description($"Immediately shows the repeat message and restarts the timer. | `{Prefix}repinv`")
.AddCheck(SimpleCheckers.ManageMessages())
.Do(async e =>
{

View File

@ -20,7 +20,7 @@ namespace NadekoBot.Modules.Games.Commands
cgb.CreateCommand(Module.Prefix + "betray")
.Description("BETRAY GAME. Betray nadeko next turn." +
"If Nadeko cooperates - you get extra points, nadeko loses a LOT." +
"If Nadeko betrays - you both lose some points. | `{Prefix}betray`")
$"If Nadeko betrays - you both lose some points. | `{Prefix}betray`")
.Do(async e =>
{
await ReceiveAnswer(e, Answers.Betray).ConfigureAwait(false);
@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Games.Commands
cgb.CreateCommand(Module.Prefix + "cooperate")
.Description("BETRAY GAME. Cooperate with nadeko next turn." +
"If Nadeko cooperates - you both get bonus points." +
"If Nadeko betrays - you lose A LOT, nadeko gets extra. | `{Prefix}cooperater`")
$"If Nadeko betrays - you lose A LOT, nadeko gets extra. | `{Prefix}cooperater`")
.Do(async e =>
{

View File

@ -48,12 +48,11 @@ namespace NadekoBot.Classes.Help.Commands
public Action<CommandEventArgs> DoGitFunc() => e =>
{
string helpstr =
$@"######For more information and how to setup your own NadekoBot, go to: **http://github.com/Kwoth/NadekoBot/**
######You can donate on patreon: `https://patreon.com/nadekobot`
$@"######For more information and how to setup your own NadekoBot, go to: <http://github.com/Kwoth/NadekoBot/wiki>
######You can donate on patreon: <https://patreon.com/nadekobot>
######or paypal: `nadekodiscordbot@gmail.com`
#NadekoBot List Of Commands
Version: `{NadekoStats.Instance.BotVersion}`";
#NadekoBot List Of Commands ";
string lastCategory = "";
@ -81,7 +80,7 @@ Version: `{NadekoStats.Instance.BotVersion}`";
{
cgb.CreateCommand(Module.Prefix + "h")
.Alias(Module.Prefix + "help", NadekoBot.BotMention + " help", NadekoBot.BotMention + " h", "~h")
.Description("Either shows a help for a single command, or PMs you help link if no arguments are specified. | `-h !m q` or just `-h` ")
.Description($"Either shows a help for a single command, or PMs you help link if no arguments are specified. | `{Prefix}h !m q` or just `{Prefix}h` ")
.Parameter("command", ParameterType.Unparsed)
.Do(HelpFunc());
cgb.CreateCommand(Module.Prefix + "hgit")
@ -103,7 +102,7 @@ Version: `{NadekoStats.Instance.BotVersion}`";
cgb.CreateCommand(Module.Prefix + "donate")
.Alias("~donate")
.Description("Instructions for helping the project! | `{Prefix}donate` or `~donate`")
.Description($"Instructions for helping the project! | `{Prefix}donate` or `~donate`")
.Do(async e =>
{
await e.Channel.SendMessage(

View File

@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Help
cgb.CreateCommand(Prefix + "modules")
.Alias(".modules")
.Description("List all bot modules. | `{Prefix}modules` or `.modules`")
.Description($"List all bot modules. | `{Prefix}modules` or `.modules`")
.Do(async e =>
{
await e.Channel.SendMessage("`List of modules:` \n• " + string.Join("\n• ", NadekoBot.Client.GetService<ModuleService>().Modules.Select(m => m.Name)) + $"\n`Type \"{Prefix}commands module_name\" to get a list of commands in that module.`")
@ -38,7 +38,7 @@ namespace NadekoBot.Modules.Help
cgb.CreateCommand(Prefix + "commands")
.Alias(".commands")
.Description("List all of the bot's commands from a certain module. | `{Prefix}commands` or `.commands`")
.Description($"List all of the bot's commands from a certain module. | `{Prefix}commands` or `.commands`")
.Parameter("module", ParameterType.Unparsed)
.Do(async e =>
{

View File

@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Music.Classes
public IReadOnlyCollection<Song> Playlist => playlist;
public Song CurrentSong { get; private set; }
private CancellationTokenSource SongCancelSource { get; set; }
public CancellationTokenSource SongCancelSource { get; private set; }
private CancellationToken cancelToken { get; set; }
public bool Paused { get; set; }

View File

@ -32,7 +32,6 @@ namespace NadekoBot.Modules.Music.Classes
public SongInfo SongInfo { get; }
public string QueuerName { get; set; }
private bool bufferingCompleted { get; set; } = false;
public MusicPlayer MusicPlayer { get; set; }
public string PrettyCurrentTime()
@ -73,78 +72,22 @@ namespace NadekoBot.Modules.Music.Classes
return this;
}
private Task BufferSong(string filename, CancellationToken cancelToken) =>
Task.Factory.StartNew(async () =>
{
Process p = null;
try
{
p = Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = $"-ss {skipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = false,
CreateNoWindow = true,
});
var prebufferSize = 100ul.MiB();
using (var outStream = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read))
{
byte[] buffer = new byte[81920];
int bytesRead;
while ((bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false)) != 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false);
while ((ulong)outStream.Length - bytesSent > prebufferSize)
await Task.Delay(100, cancelToken);
}
}
bufferingCompleted = true;
}
catch (System.ComponentModel.Win32Exception) {
var oldclr = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(@"You have not properly installed or configured FFMPEG.
Please install and configure FFMPEG to play music.
Check the guides for your platform on how to setup ffmpeg correctly:
Windows Guide: https://goo.gl/SCv72y
Linux Guide: https://goo.gl/rRhjCp");
Console.ForegroundColor = oldclr;
}
catch (Exception ex)
{
Console.WriteLine($"Buffering stopped: {ex.Message}");
}
finally
{
Console.WriteLine($"Buffering done.");
if (p != null)
{
try
{
p.Kill();
}
catch { }
p.Dispose();
}
}
}, TaskCreationOptions.LongRunning);
internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
{
var filename = Path.Combine(MusicModule.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
var bufferTask = BufferSong(filename, cancelToken).ConfigureAwait(false);
SongBuffer sb = new SongBuffer(filename, SongInfo, skipTo);
var bufferTask = sb.BufferSong(cancelToken).ConfigureAwait(false);
var inStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
var inStream = new FileStream(sb.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); ;
bytesSent = 0;
try
{
var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken);
var attempt = 0;
var prebufferingTask = CheckPrebufferingAsync(inStream, sb, cancelToken);
var sw = new Stopwatch();
sw.Start();
var t = await Task.WhenAny(prebufferingTask, Task.Delay(5000, cancelToken));
@ -162,7 +105,6 @@ Check the guides for your platform on how to setup ffmpeg correctly:
Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed);
const int blockSize = 3840;
var attempt = 0;
byte[] buffer = new byte[blockSize];
while (!cancelToken.IsCancellationRequested)
{
@ -173,14 +115,31 @@ Check the guides for your platform on how to setup ffmpeg correctly:
{
bytesSent += (ulong)read;
}
if (read == 0)
if (attempt++ == 20)
if (read < blockSize)
{
if (sb.IsNextFileReady())
{
voiceClient.Wait();
break;
inStream.Dispose();
inStream = new FileStream(sb.GetNextFile(), FileMode.Open, FileAccess.Read, FileShare.Write);
read += inStream.Read(buffer, read, buffer.Length - read);
attempt = 0;
}
if (read == 0)
{
if (sb.BufferingCompleted)
break;
if (attempt++ == 20)
{
voiceClient.Wait();
MusicPlayer.SongCancelSource.Cancel();
break;
}
else
await Task.Delay(100, cancelToken).ConfigureAwait(false);
}
else
await Task.Delay(100, cancelToken).ConfigureAwait(false);
attempt = 0;
}
else
attempt = 0;
@ -195,14 +154,16 @@ Check the guides for your platform on how to setup ffmpeg correctly:
{
await bufferTask;
await Task.Run(() => voiceClient.Clear());
inStream.Dispose();
try { File.Delete(filename); } catch { }
if(inStream != null)
inStream.Dispose();
Console.WriteLine("l");
sb.CleanFiles();
}
}
private async Task CheckPrebufferingAsync(Stream inStream, CancellationToken cancelToken)
private async Task CheckPrebufferingAsync(Stream inStream, SongBuffer sb, CancellationToken cancelToken)
{
while (!bufferingCompleted && inStream.Length < 2.MiB())
while (!sb.BufferingCompleted && inStream.Length < 2.MiB())
{
await Task.Delay(100, cancelToken);
}

View File

@ -0,0 +1,159 @@
using NadekoBot.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Music.Classes
{
/// <summary>
/// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space.
/// It also help for large music by deleting files that are already seen.
/// </summary>
class SongBuffer
{
public SongBuffer(string basename, SongInfo songInfo, int skipTo)
{
Basename = basename;
SongInfo = songInfo;
SkipTo = skipTo;
}
private string Basename;
private SongInfo SongInfo;
private int SkipTo;
private static int MAX_FILE_SIZE = 20.MiB();
private long FileNumber = -1;
private long NextFileToRead = 0;
public bool BufferingCompleted { get; private set;} = false;
private ulong CurrentBufferSize = 0;
public Task BufferSong(CancellationToken cancelToken) =>
Task.Factory.StartNew(async () =>
{
Process p = null;
FileStream outStream = null;
try
{
p = Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = false,
CreateNoWindow = true,
});
byte[] buffer = new byte[81920];
int currentFileSize = 0;
ulong prebufferSize = 100ul.MiB();
outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
while (!p.HasExited) //Also fix low bandwidth
{
int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false);
if (currentFileSize >= MAX_FILE_SIZE)
{
try
{
outStream.Dispose();
}catch { }
outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
currentFileSize = bytesRead;
}
else
{
currentFileSize += bytesRead;
}
CurrentBufferSize += Convert.ToUInt64(bytesRead);
await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false);
while (CurrentBufferSize > prebufferSize)
await Task.Delay(100, cancelToken);
}
BufferingCompleted = true;
}
catch (System.ComponentModel.Win32Exception)
{
var oldclr = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(@"You have not properly installed or configured FFMPEG.
Please install and configure FFMPEG to play music.
Check the guides for your platform on how to setup ffmpeg correctly:
Windows Guide: https://goo.gl/SCv72y
Linux Guide: https://goo.gl/rRhjCp");
Console.ForegroundColor = oldclr;
}
catch (Exception ex)
{
Console.WriteLine($"Buffering stopped: {ex.Message}");
}
finally
{
if(outStream != null)
outStream.Dispose();
Console.WriteLine($"Buffering done.");
if (p != null)
{
try
{
p.Kill();
}
catch { }
p.Dispose();
}
}
}, TaskCreationOptions.LongRunning);
/// <summary>
/// Return the next file to read, and delete the old one
/// </summary>
/// <returns>Name of the file to read</returns>
public string GetNextFile()
{
string filename = Basename + "-" + NextFileToRead;
if (NextFileToRead != 0)
{
try
{
CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length);
File.Delete(Basename + "-" + (NextFileToRead - 1));
}
catch { }
}
NextFileToRead++;
return filename;
}
public bool IsNextFileReady()
{
return NextFileToRead <= FileNumber;
}
public void CleanFiles()
{
for (long i = NextFileToRead - 1 ; i <= FileNumber; i++)
{
try
{
File.Delete(Basename + "-" + i);
}
catch { }
}
}
}
}

View File

@ -773,11 +773,13 @@ namespace NadekoBot.Modules.Music
{
await e.Channel.SendMessage("Could not select song, likely wrong index");
} else
}
else
{
await e.Channel.SendMessage($"🎶`Selected song {selSong.SongInfo.Title}:` <{selSong.SongInfo.Query}>").ConfigureAwait(false);
}
} else
}
else
{
var curSong = musicPlayer.CurrentSong;
if (curSong == null)
@ -837,7 +839,7 @@ namespace NadekoBot.Modules.Music
lastFinishedMessage = await textCh.SendMessage($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false);
if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube")
{
await QueueSong(queuer, textCh, voiceCh, await SearchHelper.GetRelatedVideoId(song.SongInfo.Query), silent, musicType).ConfigureAwait(false);
await QueueSong(queuer.Server.CurrentUser, textCh, voiceCh, (await SearchHelper.GetRelatedVideoIds(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false);
}
}
catch (Exception e)

View File

@ -13,7 +13,7 @@ namespace NadekoBot.Modules.Translator
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "translangs")
.Description("List the valid languages for translation. | `{Prefix}translangs` or `{Prefix}translangs language`")
.Description($"List the valid languages for translation. | `{Prefix}translangs` or `{Prefix}translangs language`")
.Parameter("search", ParameterType.Optional)
.Do(ListLanguagesFunc());
}

View File

@ -92,7 +92,7 @@ namespace NadekoBot.Modules.Trello
});
cgb.CreateCommand(Prefix + "unbind")
.Description("Unbinds a bot from the channel and board.")
.Description($"Unbinds a bot from the channel and board. | `{Prefix}unbind`")
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id)) return;
@ -106,7 +106,7 @@ namespace NadekoBot.Modules.Trello
cgb.CreateCommand(Prefix + "lists")
.Alias(Prefix + "list")
.Description("Lists all lists yo ;)")
.Description($"Lists all lists yo ;) | {Prefix}list")
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id)) return;

View File

@ -231,6 +231,7 @@
<Compile Include="Modules\Music\Classes\MusicControls.cs" />
<Compile Include="Modules\Music\Classes\PoopyBuffer.cs" />
<Compile Include="Modules\Music\Classes\Song.cs" />
<Compile Include="Modules\Music\Classes\SongBuffer.cs" />
<Compile Include="Modules\Music\Classes\SoundCloud.cs" />
<Compile Include="Modules\Permissions\Classes\PermissionChecker.cs" />
<Compile Include="Modules\Permissions\Classes\PermissionHelper.cs" />