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;
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -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 =>
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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 =>
|
||||
{
|
||||
|
@ -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 =>
|
||||
{
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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 =>
|
||||
{
|
||||
|
@ -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; }
|
||||
|
@ -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);
|
||||
}
|
||||
|
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");
|
||||
|
||||
} 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)
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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" />
|
||||
|
Loading…
Reference in New Issue
Block a user