commit
a1da64e4ff
47
DockerGuide.md
Normal file
47
DockerGuide.md
Normal 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*
|
@ -175,7 +175,7 @@ namespace NadekoBot.Classes
|
|||||||
return null;
|
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))
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
throw new ArgumentNullException(nameof(id));
|
throw new ArgumentNullException(nameof(id));
|
||||||
@ -186,20 +186,14 @@ namespace NadekoBot.Classes
|
|||||||
}
|
}
|
||||||
var response = await GetResponseStringAsync(
|
var response = await GetResponseStringAsync(
|
||||||
$"https://www.googleapis.com/youtube/v3/search?" +
|
$"https://www.googleapis.com/youtube/v3/search?" +
|
||||||
$"part=snippet&maxResults=1&type=video" +
|
$"part=snippet&maxResults={count}&type=video" +
|
||||||
$"&relatedToVideoId={id}" +
|
$"&relatedToVideoId={id}" +
|
||||||
$"&key={NadekoBot.Creds.GoogleAPIKey}").ConfigureAwait(false);
|
$"&key={NadekoBot.Creds.GoogleAPIKey}").ConfigureAwait(false);
|
||||||
JObject obj = JObject.Parse(response);
|
JObject obj = JObject.Parse(response);
|
||||||
|
|
||||||
var data = JsonConvert.DeserializeObject<YoutubeVideoSearch>(response);
|
var data = JsonConvert.DeserializeObject<YoutubeVideoSearch>(response);
|
||||||
|
|
||||||
if (data.items.Length > 0)
|
return data.items.Select(v => "http://www.youtube.com/watch?v=" + v.id.videoId);
|
||||||
{
|
|
||||||
var toReturn = "http://www.youtube.com/watch?v=" + data.items[0].id.videoId.ToString();
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> GetPlaylistIdByKeyword(string query)
|
public static async Task<string> GetPlaylistIdByKeyword(string query)
|
||||||
|
@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Administration.Commands
|
|||||||
{
|
{
|
||||||
cgb.CreateCommand(Module.Prefix + "autoassignrole")
|
cgb.CreateCommand(Module.Prefix + "autoassignrole")
|
||||||
.Alias(Module.Prefix + "aar")
|
.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)
|
.Parameter("role", ParameterType.Unparsed)
|
||||||
.AddCheck(new SimpleCheckers.ManageRoles())
|
.AddCheck(new SimpleCheckers.ManageRoles())
|
||||||
.Do(async e =>
|
.Do(async e =>
|
||||||
|
@ -4,7 +4,10 @@ using NadekoBot.Classes;
|
|||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using NadekoBot.Modules.Permissions.Classes;
|
using NadekoBot.Modules.Permissions.Classes;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Administration.Commands
|
namespace NadekoBot.Modules.Administration.Commands
|
||||||
{
|
{
|
||||||
@ -12,6 +15,8 @@ namespace NadekoBot.Modules.Administration.Commands
|
|||||||
{
|
{
|
||||||
private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】";
|
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)
|
public LogCommand(DiscordModule module) : base(module)
|
||||||
{
|
{
|
||||||
NadekoBot.Client.MessageReceived += MsgRecivd;
|
NadekoBot.Client.MessageReceived += MsgRecivd;
|
||||||
@ -45,6 +50,36 @@ namespace NadekoBot.Modules.Administration.Commands
|
|||||||
}
|
}
|
||||||
catch { }
|
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)
|
private async void ChannelUpdated(object sender, ChannelUpdatedEventArgs e)
|
||||||
@ -252,7 +287,7 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
|
|||||||
{
|
{
|
||||||
if (e.Before.Status != e.After.Status)
|
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}**."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ namespace NadekoBot.Modules.Administration.Commands
|
|||||||
|
|
||||||
cgb.CreateCommand(Module.Prefix + "repeatinvoke")
|
cgb.CreateCommand(Module.Prefix + "repeatinvoke")
|
||||||
.Alias(Module.Prefix + "repinv")
|
.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())
|
.AddCheck(SimpleCheckers.ManageMessages())
|
||||||
.Do(async e =>
|
.Do(async e =>
|
||||||
{
|
{
|
||||||
|
@ -20,7 +20,7 @@ namespace NadekoBot.Modules.Games.Commands
|
|||||||
cgb.CreateCommand(Module.Prefix + "betray")
|
cgb.CreateCommand(Module.Prefix + "betray")
|
||||||
.Description("BETRAY GAME. Betray nadeko next turn." +
|
.Description("BETRAY GAME. Betray nadeko next turn." +
|
||||||
"If Nadeko cooperates - you get extra points, nadeko loses a LOT." +
|
"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 =>
|
.Do(async e =>
|
||||||
{
|
{
|
||||||
await ReceiveAnswer(e, Answers.Betray).ConfigureAwait(false);
|
await ReceiveAnswer(e, Answers.Betray).ConfigureAwait(false);
|
||||||
@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Games.Commands
|
|||||||
cgb.CreateCommand(Module.Prefix + "cooperate")
|
cgb.CreateCommand(Module.Prefix + "cooperate")
|
||||||
.Description("BETRAY GAME. Cooperate with nadeko next turn." +
|
.Description("BETRAY GAME. Cooperate with nadeko next turn." +
|
||||||
"If Nadeko cooperates - you both get bonus points." +
|
"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 =>
|
.Do(async e =>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -48,12 +48,11 @@ namespace NadekoBot.Classes.Help.Commands
|
|||||||
public Action<CommandEventArgs> DoGitFunc() => e =>
|
public Action<CommandEventArgs> DoGitFunc() => e =>
|
||||||
{
|
{
|
||||||
string helpstr =
|
string helpstr =
|
||||||
$@"######For more information and how to setup your own NadekoBot, go to: **http://github.com/Kwoth/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`
|
######You can donate on patreon: <https://patreon.com/nadekobot>
|
||||||
######or paypal: `nadekodiscordbot@gmail.com`
|
######or paypal: `nadekodiscordbot@gmail.com`
|
||||||
|
|
||||||
#NadekoBot List Of Commands
|
#NadekoBot List Of Commands ";
|
||||||
Version: `{NadekoStats.Instance.BotVersion}`";
|
|
||||||
|
|
||||||
|
|
||||||
string lastCategory = "";
|
string lastCategory = "";
|
||||||
@ -81,7 +80,7 @@ Version: `{NadekoStats.Instance.BotVersion}`";
|
|||||||
{
|
{
|
||||||
cgb.CreateCommand(Module.Prefix + "h")
|
cgb.CreateCommand(Module.Prefix + "h")
|
||||||
.Alias(Module.Prefix + "help", NadekoBot.BotMention + " help", NadekoBot.BotMention + " h", "~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)
|
.Parameter("command", ParameterType.Unparsed)
|
||||||
.Do(HelpFunc());
|
.Do(HelpFunc());
|
||||||
cgb.CreateCommand(Module.Prefix + "hgit")
|
cgb.CreateCommand(Module.Prefix + "hgit")
|
||||||
@ -103,7 +102,7 @@ Version: `{NadekoStats.Instance.BotVersion}`";
|
|||||||
|
|
||||||
cgb.CreateCommand(Module.Prefix + "donate")
|
cgb.CreateCommand(Module.Prefix + "donate")
|
||||||
.Alias("~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 =>
|
.Do(async e =>
|
||||||
{
|
{
|
||||||
await e.Channel.SendMessage(
|
await e.Channel.SendMessage(
|
||||||
|
@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Help
|
|||||||
|
|
||||||
cgb.CreateCommand(Prefix + "modules")
|
cgb.CreateCommand(Prefix + "modules")
|
||||||
.Alias(".modules")
|
.Alias(".modules")
|
||||||
.Description("List all bot modules. | `{Prefix}modules` or `.modules`")
|
.Description($"List all bot modules. | `{Prefix}modules` or `.modules`")
|
||||||
.Do(async e =>
|
.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.`")
|
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")
|
cgb.CreateCommand(Prefix + "commands")
|
||||||
.Alias(".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)
|
.Parameter("module", ParameterType.Unparsed)
|
||||||
.Do(async e =>
|
.Do(async e =>
|
||||||
{
|
{
|
||||||
|
@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Music.Classes
|
|||||||
public IReadOnlyCollection<Song> Playlist => playlist;
|
public IReadOnlyCollection<Song> Playlist => playlist;
|
||||||
|
|
||||||
public Song CurrentSong { get; private set; }
|
public Song CurrentSong { get; private set; }
|
||||||
private CancellationTokenSource SongCancelSource { get; set; }
|
public CancellationTokenSource SongCancelSource { get; private set; }
|
||||||
private CancellationToken cancelToken { get; set; }
|
private CancellationToken cancelToken { get; set; }
|
||||||
|
|
||||||
public bool Paused { get; set; }
|
public bool Paused { get; set; }
|
||||||
|
@ -32,7 +32,6 @@ namespace NadekoBot.Modules.Music.Classes
|
|||||||
public SongInfo SongInfo { get; }
|
public SongInfo SongInfo { get; }
|
||||||
public string QueuerName { get; set; }
|
public string QueuerName { get; set; }
|
||||||
|
|
||||||
private bool bufferingCompleted { get; set; } = false;
|
|
||||||
public MusicPlayer MusicPlayer { get; set; }
|
public MusicPlayer MusicPlayer { get; set; }
|
||||||
|
|
||||||
public string PrettyCurrentTime()
|
public string PrettyCurrentTime()
|
||||||
@ -73,78 +72,22 @@ namespace NadekoBot.Modules.Music.Classes
|
|||||||
return this;
|
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)
|
internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
var filename = Path.Combine(MusicModule.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
|
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;
|
bytesSent = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken);
|
var attempt = 0;
|
||||||
|
|
||||||
|
var prebufferingTask = CheckPrebufferingAsync(inStream, sb, cancelToken);
|
||||||
var sw = new Stopwatch();
|
var sw = new Stopwatch();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
var t = await Task.WhenAny(prebufferingTask, Task.Delay(5000, cancelToken));
|
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);
|
Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed);
|
||||||
|
|
||||||
const int blockSize = 3840;
|
const int blockSize = 3840;
|
||||||
var attempt = 0;
|
|
||||||
byte[] buffer = new byte[blockSize];
|
byte[] buffer = new byte[blockSize];
|
||||||
while (!cancelToken.IsCancellationRequested)
|
while (!cancelToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@ -173,14 +115,31 @@ Check the guides for your platform on how to setup ffmpeg correctly:
|
|||||||
{
|
{
|
||||||
bytesSent += (ulong)read;
|
bytesSent += (ulong)read;
|
||||||
}
|
}
|
||||||
|
if (read < blockSize)
|
||||||
|
{
|
||||||
|
if (sb.IsNextFileReady())
|
||||||
|
{
|
||||||
|
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 (read == 0)
|
||||||
|
{
|
||||||
|
if (sb.BufferingCompleted)
|
||||||
|
break;
|
||||||
if (attempt++ == 20)
|
if (attempt++ == 20)
|
||||||
{
|
{
|
||||||
voiceClient.Wait();
|
voiceClient.Wait();
|
||||||
|
MusicPlayer.SongCancelSource.Cancel();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
await Task.Delay(100, cancelToken).ConfigureAwait(false);
|
await Task.Delay(100, cancelToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
attempt = 0;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
attempt = 0;
|
attempt = 0;
|
||||||
|
|
||||||
@ -195,14 +154,16 @@ Check the guides for your platform on how to setup ffmpeg correctly:
|
|||||||
{
|
{
|
||||||
await bufferTask;
|
await bufferTask;
|
||||||
await Task.Run(() => voiceClient.Clear());
|
await Task.Run(() => voiceClient.Clear());
|
||||||
|
if(inStream != null)
|
||||||
inStream.Dispose();
|
inStream.Dispose();
|
||||||
try { File.Delete(filename); } catch { }
|
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);
|
await Task.Delay(100, cancelToken);
|
||||||
}
|
}
|
||||||
|
159
NadekoBot/Modules/Music/Classes/SongBuffer.cs
Normal file
159
NadekoBot/Modules/Music/Classes/SongBuffer.cs
Normal 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 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -773,11 +773,13 @@ namespace NadekoBot.Modules.Music
|
|||||||
{
|
{
|
||||||
await e.Channel.SendMessage("Could not select song, likely wrong index");
|
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);
|
await e.Channel.SendMessage($"🎶`Selected song {selSong.SongInfo.Title}:` <{selSong.SongInfo.Query}>").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var curSong = musicPlayer.CurrentSong;
|
var curSong = musicPlayer.CurrentSong;
|
||||||
if (curSong == null)
|
if (curSong == null)
|
||||||
@ -837,7 +839,7 @@ namespace NadekoBot.Modules.Music
|
|||||||
lastFinishedMessage = await textCh.SendMessage($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false);
|
lastFinishedMessage = await textCh.SendMessage($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false);
|
||||||
if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube")
|
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)
|
catch (Exception e)
|
||||||
|
@ -13,7 +13,7 @@ namespace NadekoBot.Modules.Translator
|
|||||||
internal override void Init(CommandGroupBuilder cgb)
|
internal override void Init(CommandGroupBuilder cgb)
|
||||||
{
|
{
|
||||||
cgb.CreateCommand(Module.Prefix + "translangs")
|
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)
|
.Parameter("search", ParameterType.Optional)
|
||||||
.Do(ListLanguagesFunc());
|
.Do(ListLanguagesFunc());
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ namespace NadekoBot.Modules.Trello
|
|||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand(Prefix + "unbind")
|
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 =>
|
.Do(async e =>
|
||||||
{
|
{
|
||||||
if (!NadekoBot.IsOwner(e.User.Id)) return;
|
if (!NadekoBot.IsOwner(e.User.Id)) return;
|
||||||
@ -106,7 +106,7 @@ namespace NadekoBot.Modules.Trello
|
|||||||
|
|
||||||
cgb.CreateCommand(Prefix + "lists")
|
cgb.CreateCommand(Prefix + "lists")
|
||||||
.Alias(Prefix + "list")
|
.Alias(Prefix + "list")
|
||||||
.Description("Lists all lists yo ;)")
|
.Description($"Lists all lists yo ;) | {Prefix}list")
|
||||||
.Do(async e =>
|
.Do(async e =>
|
||||||
{
|
{
|
||||||
if (!NadekoBot.IsOwner(e.User.Id)) return;
|
if (!NadekoBot.IsOwner(e.User.Id)) return;
|
||||||
|
@ -231,6 +231,7 @@
|
|||||||
<Compile Include="Modules\Music\Classes\MusicControls.cs" />
|
<Compile Include="Modules\Music\Classes\MusicControls.cs" />
|
||||||
<Compile Include="Modules\Music\Classes\PoopyBuffer.cs" />
|
<Compile Include="Modules\Music\Classes\PoopyBuffer.cs" />
|
||||||
<Compile Include="Modules\Music\Classes\Song.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\Music\Classes\SoundCloud.cs" />
|
||||||
<Compile Include="Modules\Permissions\Classes\PermissionChecker.cs" />
|
<Compile Include="Modules\Permissions\Classes\PermissionChecker.cs" />
|
||||||
<Compile Include="Modules\Permissions\Classes\PermissionHelper.cs" />
|
<Compile Include="Modules\Permissions\Classes\PermissionHelper.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user