remove files?
This commit is contained in:
		@@ -1,283 +0,0 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Audio;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public enum MusicType
 | 
			
		||||
    {
 | 
			
		||||
        Radio,
 | 
			
		||||
        Normal,
 | 
			
		||||
        Local,
 | 
			
		||||
        Soundcloud
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum StreamState
 | 
			
		||||
    {
 | 
			
		||||
        Resolving,
 | 
			
		||||
        Queued,
 | 
			
		||||
        Playing,
 | 
			
		||||
        Completed
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class MusicPlayer
 | 
			
		||||
    {
 | 
			
		||||
        private IAudioClient audioClient { get; set; }
 | 
			
		||||
 | 
			
		||||
        private readonly List<Song> playlist = new List<Song>();
 | 
			
		||||
        public IReadOnlyCollection<Song> Playlist => playlist;
 | 
			
		||||
 | 
			
		||||
        public Song CurrentSong { get; private set; }
 | 
			
		||||
        public CancellationTokenSource SongCancelSource { get; private set; }
 | 
			
		||||
        private CancellationToken cancelToken { get; set; }
 | 
			
		||||
 | 
			
		||||
        public bool Paused { get; set; }
 | 
			
		||||
 | 
			
		||||
        public float Volume { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public event EventHandler<Song> OnCompleted = delegate { };
 | 
			
		||||
        public event EventHandler<Song> OnStarted = delegate { };
 | 
			
		||||
 | 
			
		||||
        public Channel PlaybackVoiceChannel { get; private set; }
 | 
			
		||||
 | 
			
		||||
        private bool Destroyed { get; set; } = false;
 | 
			
		||||
        public bool RepeatSong { get; private set; } = false;
 | 
			
		||||
        public bool RepeatPlaylist { get; private set; } = false;
 | 
			
		||||
        public bool Autoplay { get; set; } = false;
 | 
			
		||||
        public uint MaxQueueSize { get; set; } = 0;
 | 
			
		||||
 | 
			
		||||
        private ConcurrentQueue<Action> actionQueue { get; set; } = new ConcurrentQueue<Action>();
 | 
			
		||||
 | 
			
		||||
        public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume)
 | 
			
		||||
        {
 | 
			
		||||
            if (startingVoiceChannel == null)
 | 
			
		||||
                throw new ArgumentNullException(nameof(startingVoiceChannel));
 | 
			
		||||
            if (startingVoiceChannel.Type != ChannelType.Voice)
 | 
			
		||||
                throw new ArgumentException("Channel must be of type voice");
 | 
			
		||||
            Volume = defaultVolume ?? 1.0f;
 | 
			
		||||
 | 
			
		||||
            PlaybackVoiceChannel = startingVoiceChannel;
 | 
			
		||||
            SongCancelSource = new CancellationTokenSource();
 | 
			
		||||
            cancelToken = SongCancelSource.Token;
 | 
			
		||||
 | 
			
		||||
            Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    while (!Destroyed)
 | 
			
		||||
                    {
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            Action action;
 | 
			
		||||
                            if (actionQueue.TryDequeue(out action))
 | 
			
		||||
                            {
 | 
			
		||||
                                action();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        finally
 | 
			
		||||
                        {
 | 
			
		||||
                            await Task.Delay(100).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    Console.WriteLine("Action queue crashed");
 | 
			
		||||
                    Console.WriteLine(ex);
 | 
			
		||||
                }
 | 
			
		||||
            }).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var t = new Thread(new ThreadStart(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    while (!Destroyed)
 | 
			
		||||
                    {
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            if (audioClient?.State != ConnectionState.Connected)
 | 
			
		||||
                            {
 | 
			
		||||
                                audioClient = await PlaybackVoiceChannel.JoinAudio();
 | 
			
		||||
                                continue;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            CurrentSong = GetNextSong();
 | 
			
		||||
                            RemoveSongAt(0);
 | 
			
		||||
 | 
			
		||||
                            if (CurrentSong == null)
 | 
			
		||||
                                continue;
 | 
			
		||||
 | 
			
		||||
                            
 | 
			
		||||
                            OnStarted(this, CurrentSong);
 | 
			
		||||
                            await CurrentSong.Play(audioClient, cancelToken);
 | 
			
		||||
 | 
			
		||||
                            OnCompleted(this, CurrentSong);
 | 
			
		||||
 | 
			
		||||
                            if (RepeatPlaylist)
 | 
			
		||||
                                AddSong(CurrentSong, CurrentSong.QueuerName);
 | 
			
		||||
 | 
			
		||||
                            if (RepeatSong)
 | 
			
		||||
                                AddSong(CurrentSong, 0);
 | 
			
		||||
                            
 | 
			
		||||
                        }
 | 
			
		||||
                        finally
 | 
			
		||||
                        {
 | 
			
		||||
                            if (!cancelToken.IsCancellationRequested)
 | 
			
		||||
                            {
 | 
			
		||||
                                SongCancelSource.Cancel();
 | 
			
		||||
                            }
 | 
			
		||||
                            SongCancelSource = new CancellationTokenSource();
 | 
			
		||||
                            cancelToken = SongCancelSource.Token;
 | 
			
		||||
                            CurrentSong = null;
 | 
			
		||||
                            await Task.Delay(300).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex) {
 | 
			
		||||
                    Console.WriteLine("Music thread crashed.");
 | 
			
		||||
                    Console.WriteLine(ex);
 | 
			
		||||
                }
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
            t.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Next()
 | 
			
		||||
        {
 | 
			
		||||
            actionQueue.Enqueue(() =>
 | 
			
		||||
            {
 | 
			
		||||
                Paused = false;
 | 
			
		||||
                SongCancelSource.Cancel();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Stop()
 | 
			
		||||
        {
 | 
			
		||||
            actionQueue.Enqueue(() =>
 | 
			
		||||
            {
 | 
			
		||||
                RepeatPlaylist = false;
 | 
			
		||||
                RepeatSong = false;
 | 
			
		||||
                playlist.Clear();
 | 
			
		||||
                if (!SongCancelSource.IsCancellationRequested)
 | 
			
		||||
                    SongCancelSource.Cancel();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void TogglePause() => Paused = !Paused;
 | 
			
		||||
 | 
			
		||||
        public int SetVolume(int volume)
 | 
			
		||||
        {
 | 
			
		||||
            if (volume < 0)
 | 
			
		||||
                volume = 0;
 | 
			
		||||
            if (volume > 100)
 | 
			
		||||
                volume = 100;
 | 
			
		||||
 | 
			
		||||
            Volume = volume / 100.0f;
 | 
			
		||||
            return volume;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Song GetNextSong() =>
 | 
			
		||||
            playlist.FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
        public void Shuffle()
 | 
			
		||||
        {
 | 
			
		||||
            actionQueue.Enqueue(() =>
 | 
			
		||||
            {
 | 
			
		||||
                playlist.Shuffle();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void AddSong(Song s, string username)
 | 
			
		||||
        {
 | 
			
		||||
            if (s == null)
 | 
			
		||||
                throw new ArgumentNullException(nameof(s));
 | 
			
		||||
            ThrowIfQueueFull();
 | 
			
		||||
            actionQueue.Enqueue(() =>
 | 
			
		||||
            {
 | 
			
		||||
                s.MusicPlayer = this;
 | 
			
		||||
                s.QueuerName = username.TrimTo(10);
 | 
			
		||||
                playlist.Add(s);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void AddSong(Song s, int index)
 | 
			
		||||
        {
 | 
			
		||||
            if (s == null)
 | 
			
		||||
                throw new ArgumentNullException(nameof(s));
 | 
			
		||||
            actionQueue.Enqueue(() =>
 | 
			
		||||
            {
 | 
			
		||||
                playlist.Insert(index, s);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void RemoveSong(Song s)
 | 
			
		||||
        {
 | 
			
		||||
            if (s == null)
 | 
			
		||||
                throw new ArgumentNullException(nameof(s));
 | 
			
		||||
            actionQueue.Enqueue(() =>
 | 
			
		||||
            {
 | 
			
		||||
                playlist.Remove(s);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void RemoveSongAt(int index)
 | 
			
		||||
        {
 | 
			
		||||
            actionQueue.Enqueue(() =>
 | 
			
		||||
            {
 | 
			
		||||
                if (index < 0 || index >= playlist.Count)
 | 
			
		||||
                    return;
 | 
			
		||||
                playlist.RemoveAt(index);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal void ClearQueue()
 | 
			
		||||
        {
 | 
			
		||||
            actionQueue.Enqueue(() =>
 | 
			
		||||
            {
 | 
			
		||||
                playlist.Clear();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Destroy()
 | 
			
		||||
        {
 | 
			
		||||
            actionQueue.Enqueue(() =>
 | 
			
		||||
            {
 | 
			
		||||
                RepeatPlaylist = false;
 | 
			
		||||
                RepeatSong = false;
 | 
			
		||||
                Destroyed = true;
 | 
			
		||||
                playlist.Clear();
 | 
			
		||||
                if (!SongCancelSource.IsCancellationRequested)
 | 
			
		||||
                    SongCancelSource.Cancel();
 | 
			
		||||
                audioClient.Disconnect();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal Task MoveToVoiceChannel(Channel voiceChannel)
 | 
			
		||||
        {
 | 
			
		||||
            if (audioClient?.State != ConnectionState.Connected)
 | 
			
		||||
                throw new InvalidOperationException("Can't move while bot is not connected to voice channel.");
 | 
			
		||||
            PlaybackVoiceChannel = voiceChannel;
 | 
			
		||||
            return PlaybackVoiceChannel.JoinAudio();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong;
 | 
			
		||||
 | 
			
		||||
        internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist;
 | 
			
		||||
 | 
			
		||||
        internal bool ToggleAutoplay() => this.Autoplay = !this.Autoplay;
 | 
			
		||||
 | 
			
		||||
        internal void ThrowIfQueueFull()
 | 
			
		||||
        {
 | 
			
		||||
            if (MaxQueueSize == 0)
 | 
			
		||||
                return;
 | 
			
		||||
            if (playlist.Count >= MaxQueueSize)
 | 
			
		||||
                throw new PlaylistFullException();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
{
 | 
			
		||||
    class PlaylistFullException : Exception
 | 
			
		||||
    {
 | 
			
		||||
        public PlaylistFullException(string message) : base(message)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
        public PlaylistFullException() : base("Queue is full.") { }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,417 +0,0 @@
 | 
			
		||||
using Discord.Audio;
 | 
			
		||||
using NadekoBot.Classes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Diagnostics.Contracts;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using VideoLibrary;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
{
 | 
			
		||||
    public class SongInfo
 | 
			
		||||
    {
 | 
			
		||||
        public string Provider { get; internal set; }
 | 
			
		||||
        public MusicType ProviderType { get; internal set; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Will be set only if the providertype is normal
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string Query { get; internal set; }
 | 
			
		||||
        public string Title { get; internal set; }
 | 
			
		||||
        public string Uri { get; internal set; }
 | 
			
		||||
    }
 | 
			
		||||
    public class Song
 | 
			
		||||
    {
 | 
			
		||||
        public StreamState State { get; internal set; }
 | 
			
		||||
        public string PrettyName =>
 | 
			
		||||
            $"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}` `by {QueuerName}`";
 | 
			
		||||
        public SongInfo SongInfo { get; }
 | 
			
		||||
        public string QueuerName { get; set; }
 | 
			
		||||
 | 
			
		||||
        public MusicPlayer MusicPlayer { get; set; }
 | 
			
		||||
 | 
			
		||||
        public string PrettyCurrentTime()
 | 
			
		||||
        {
 | 
			
		||||
            var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50);
 | 
			
		||||
            return $"【{(int)time.TotalMinutes}m {time.Seconds}s】";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private ulong bytesSent { get; set; } = 0;
 | 
			
		||||
 | 
			
		||||
        public bool PrintStatusMessage { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
        private int skipTo = 0;
 | 
			
		||||
        public int SkipTo {
 | 
			
		||||
            get { return SkipTo; }
 | 
			
		||||
            set {
 | 
			
		||||
                skipTo = value;
 | 
			
		||||
                bytesSent = (ulong)skipTo * 3840 * 50;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Song(SongInfo songInfo)
 | 
			
		||||
        {
 | 
			
		||||
            this.SongInfo = songInfo;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Song Clone()
 | 
			
		||||
        {
 | 
			
		||||
            var s = new Song(SongInfo);
 | 
			
		||||
            s.MusicPlayer = MusicPlayer;
 | 
			
		||||
            s.State = StreamState.Queued;
 | 
			
		||||
            return s;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Song SetMusicPlayer(MusicPlayer mp)
 | 
			
		||||
        {
 | 
			
		||||
            this.MusicPlayer = mp;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
 | 
			
		||||
        {
 | 
			
		||||
            var filename = Path.Combine(MusicModule.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
 | 
			
		||||
 | 
			
		||||
            SongBuffer sb = new SongBuffer(filename, SongInfo, skipTo);
 | 
			
		||||
            var bufferTask = sb.BufferSong(cancelToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var inStream = new FileStream(sb.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); ;
 | 
			
		||||
 | 
			
		||||
            bytesSent = 0;
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                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));
 | 
			
		||||
                if (t != prebufferingTask)
 | 
			
		||||
                {
 | 
			
		||||
                    Console.WriteLine("Prebuffering timed out or canceled. Cannot get any data from the stream.");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                else if(prebufferingTask.IsCanceled)
 | 
			
		||||
                {
 | 
			
		||||
                    Console.WriteLine("Prebuffering timed out. Cannot get any data from the stream.");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                sw.Stop();
 | 
			
		||||
                Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed);
 | 
			
		||||
 | 
			
		||||
                const int blockSize = 3840;
 | 
			
		||||
                byte[] buffer = new byte[blockSize];
 | 
			
		||||
                while (!cancelToken.IsCancellationRequested)
 | 
			
		||||
                {
 | 
			
		||||
                    //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------");
 | 
			
		||||
                    var read = inStream.Read(buffer, 0, buffer.Length);
 | 
			
		||||
                    //await inStream.CopyToAsync(voiceClient.OutputStream);
 | 
			
		||||
                    unchecked
 | 
			
		||||
                    {
 | 
			
		||||
                        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 (sb.BufferingCompleted)
 | 
			
		||||
                                break;
 | 
			
		||||
                            if (attempt++ == 20)
 | 
			
		||||
                            {
 | 
			
		||||
                                voiceClient.Wait();
 | 
			
		||||
                                MusicPlayer.SongCancelSource.Cancel();
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                                await Task.Delay(100, cancelToken).ConfigureAwait(false);                         
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                            attempt = 0;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                        attempt = 0;
 | 
			
		||||
 | 
			
		||||
                    while (this.MusicPlayer.Paused)
 | 
			
		||||
                        await Task.Delay(200, cancelToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    buffer = AdjustVolume(buffer, MusicPlayer.Volume);
 | 
			
		||||
                    voiceClient.Send(buffer, 0, read);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                await bufferTask;
 | 
			
		||||
                await Task.Run(() => voiceClient.Clear());
 | 
			
		||||
                if(inStream != null)
 | 
			
		||||
                    inStream.Dispose();
 | 
			
		||||
                Console.WriteLine("l");
 | 
			
		||||
                sb.CleanFiles();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task CheckPrebufferingAsync(Stream inStream, SongBuffer sb, CancellationToken cancelToken)
 | 
			
		||||
        {
 | 
			
		||||
            while (!sb.BufferingCompleted && inStream.Length < 2.MiB())
 | 
			
		||||
            {
 | 
			
		||||
                await Task.Delay(100, cancelToken);
 | 
			
		||||
            }
 | 
			
		||||
            Console.WriteLine("Buffering successfull");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
        //stackoverflow ftw
 | 
			
		||||
        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)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                // convert byte pair to int
 | 
			
		||||
                short buf1 = audioSamples[i + 1];
 | 
			
		||||
                short buf2 = audioSamples[i];
 | 
			
		||||
 | 
			
		||||
                buf1 = (short)((buf1 & 0xff) << 8);
 | 
			
		||||
                buf2 = (short)(buf2 & 0xff);
 | 
			
		||||
 | 
			
		||||
                var res = (short)(buf1 | buf2);
 | 
			
		||||
                res = (short)(res * volume);
 | 
			
		||||
 | 
			
		||||
                // convert back
 | 
			
		||||
                array[i] = (byte)res;
 | 
			
		||||
                array[i + 1] = (byte)(res >> 8);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            return array;
 | 
			
		||||
        }
 | 
			
		||||
        */
 | 
			
		||||
 | 
			
		||||
        //aidiakapi ftw
 | 
			
		||||
        public unsafe static byte[] AdjustVolume(byte[] audioSamples, float volume)
 | 
			
		||||
        {
 | 
			
		||||
            Contract.Requires(audioSamples != null);
 | 
			
		||||
            Contract.Requires(audioSamples.Length % 2 == 0);
 | 
			
		||||
            Contract.Requires(volume >= 0f && volume <= 1f);
 | 
			
		||||
            Contract.Assert(BitConverter.IsLittleEndian);
 | 
			
		||||
 | 
			
		||||
            if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples;
 | 
			
		||||
 | 
			
		||||
            // 16-bit precision for the multiplication
 | 
			
		||||
            int volumeFixed = (int)Math.Round(volume * 65536d);
 | 
			
		||||
 | 
			
		||||
            int count = audioSamples.Length / 2;
 | 
			
		||||
 | 
			
		||||
            fixed (byte* srcBytes = audioSamples)
 | 
			
		||||
            {
 | 
			
		||||
                short* src = (short*)srcBytes;
 | 
			
		||||
 | 
			
		||||
                for (int i = count; i != 0; i--, src++)
 | 
			
		||||
                    *src = (short)(((*src) * volumeFixed) >> 16);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return audioSamples;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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))
 | 
			
		||||
            {
 | 
			
		||||
                musicType = MusicType.Radio;
 | 
			
		||||
                query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                switch (musicType)
 | 
			
		||||
                {
 | 
			
		||||
                    case MusicType.Local:
 | 
			
		||||
                        return new Song(new SongInfo
 | 
			
		||||
                        {
 | 
			
		||||
                            Uri = "\"" + Path.GetFullPath(query) + "\"",
 | 
			
		||||
                            Title = Path.GetFileNameWithoutExtension(query),
 | 
			
		||||
                            Provider = "Local File",
 | 
			
		||||
                            ProviderType = musicType,
 | 
			
		||||
                            Query = query,
 | 
			
		||||
                        });
 | 
			
		||||
                    case MusicType.Radio:
 | 
			
		||||
                        return new Song(new SongInfo
 | 
			
		||||
                        {
 | 
			
		||||
                            Uri = query,
 | 
			
		||||
                            Title = $"{query}",
 | 
			
		||||
                            Provider = "Radio Stream",
 | 
			
		||||
                            ProviderType = musicType,
 | 
			
		||||
                            Query = query
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
                if (SoundCloud.Default.IsSoundCloudLink(query))
 | 
			
		||||
                {
 | 
			
		||||
                    var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false);
 | 
			
		||||
                    return new Song(new SongInfo
 | 
			
		||||
                    {
 | 
			
		||||
                        Title = svideo.FullName,
 | 
			
		||||
                        Provider = "SoundCloud",
 | 
			
		||||
                        Uri = svideo.StreamLink,
 | 
			
		||||
                        ProviderType = musicType,
 | 
			
		||||
                        Query = svideo.TrackLink,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (musicType == MusicType.Soundcloud)
 | 
			
		||||
                {
 | 
			
		||||
                    var svideo = await SoundCloud.Default.GetVideoByQueryAsync(query).ConfigureAwait(false);
 | 
			
		||||
                    return new Song(new SongInfo
 | 
			
		||||
                    {
 | 
			
		||||
                        Title = svideo.FullName,
 | 
			
		||||
                        Provider = "SoundCloud",
 | 
			
		||||
                        Uri = svideo.StreamLink,
 | 
			
		||||
                        ProviderType = MusicType.Normal,
 | 
			
		||||
                        Query = svideo.TrackLink,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var link = await SearchHelper.FindYoutubeUrlByKeywords(query).ConfigureAwait(false);
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(link))
 | 
			
		||||
                    throw new OperationCanceledException("Not a valid youtube query.");
 | 
			
		||||
                var allVideos = await Task.Factory.StartNew(async () => await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false)).Unwrap().ConfigureAwait(false);
 | 
			
		||||
                var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
 | 
			
		||||
                var video = videos
 | 
			
		||||
                    .Where(v => v.AudioBitrate < 192)
 | 
			
		||||
                    .OrderByDescending(v => v.AudioBitrate)
 | 
			
		||||
                    .FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
                if (video == null) // do something with this error
 | 
			
		||||
                    throw new Exception("Could not load any video elements based on the query.");
 | 
			
		||||
                var m = Regex.Match(query, @"\?t=(?<t>\d*)");
 | 
			
		||||
                int gotoTime = 0;
 | 
			
		||||
                if (m.Captures.Count > 0)
 | 
			
		||||
                    int.TryParse(m.Groups["t"].ToString(), out gotoTime);
 | 
			
		||||
                var song = new Song(new SongInfo
 | 
			
		||||
                {
 | 
			
		||||
                    Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
 | 
			
		||||
                    Provider = "YouTube",
 | 
			
		||||
                    Uri = video.Uri,
 | 
			
		||||
                    Query = link,
 | 
			
		||||
                    ProviderType = musicType,
 | 
			
		||||
                });
 | 
			
		||||
                song.SkipTo = gotoTime;
 | 
			
		||||
                return song;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Console.WriteLine($"Failed resolving the link.{ex.Message}");
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static async Task<string> HandleStreamContainers(string query)
 | 
			
		||||
        {
 | 
			
		||||
            string file = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                file = await http.GetStringAsync(query).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                return query;
 | 
			
		||||
            }
 | 
			
		||||
            if (query.Contains(".pls"))
 | 
			
		||||
            {
 | 
			
		||||
                //File1=http://armitunes.com:8000/
 | 
			
		||||
                //Regex.Match(query)
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var m = Regex.Match(file, "File1=(?<url>.*?)\\n");
 | 
			
		||||
                    var res = m.Groups["url"]?.ToString();
 | 
			
		||||
                    return res?.Trim();
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    Console.WriteLine($"Failed reading .pls:\n{file}");
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (query.Contains(".m3u"))
 | 
			
		||||
            {
 | 
			
		||||
                /* 
 | 
			
		||||
# This is a comment
 | 
			
		||||
                   C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
 | 
			
		||||
                   C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
 | 
			
		||||
                */
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline);
 | 
			
		||||
                    var res = m.Groups["url"]?.ToString();
 | 
			
		||||
                    return res?.Trim();
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    Console.WriteLine($"Failed reading .m3u:\n{file}");
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            if (query.Contains(".asx"))
 | 
			
		||||
            {
 | 
			
		||||
                //<ref href="http://armitunes.com:8000"/>
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
 | 
			
		||||
                    var res = m.Groups["url"]?.ToString();
 | 
			
		||||
                    return res?.Trim();
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    Console.WriteLine($"Failed reading .asx:\n{file}");
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            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
 | 
			
		||||
                {
 | 
			
		||||
                    var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
 | 
			
		||||
                    var res = m.Groups["url"]?.ToString();
 | 
			
		||||
                    return res?.Trim();
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    Console.WriteLine($"Failed reading .xspf:\n{file}");
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return query;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool IsRadioLink(string query) =>
 | 
			
		||||
            (query.StartsWith("http") ||
 | 
			
		||||
            query.StartsWith("ww"))
 | 
			
		||||
            &&
 | 
			
		||||
            (query.Contains(".pls") ||
 | 
			
		||||
            query.Contains(".m3u") ||
 | 
			
		||||
            query.Contains(".asx") ||
 | 
			
		||||
            query.Contains(".xspf"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,159 +0,0 @@
 | 
			
		||||
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 { }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,129 +0,0 @@
 | 
			
		||||
using NadekoBot.Classes;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Music.Classes
 | 
			
		||||
{
 | 
			
		||||
    public class SoundCloud
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly SoundCloud _instance = new SoundCloud();
 | 
			
		||||
        public static SoundCloud Default => _instance;
 | 
			
		||||
 | 
			
		||||
        static SoundCloud() { }
 | 
			
		||||
        public SoundCloud() { }
 | 
			
		||||
 | 
			
		||||
        public async Task<SoundCloudVideo> ResolveVideoAsync(string url)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(url))
 | 
			
		||||
                throw new ArgumentNullException(nameof(url));
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(NadekoBot.Creds.SoundCloudClientID))
 | 
			
		||||
                throw new ArgumentNullException(nameof(NadekoBot.Creds.SoundCloudClientID));
 | 
			
		||||
 | 
			
		||||
            var response = await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject<SoundCloudVideo>(response);
 | 
			
		||||
            if (responseObj?.Kind != "track")
 | 
			
		||||
                throw new InvalidOperationException("Url is either not a track, or it doesn't exist.");
 | 
			
		||||
 | 
			
		||||
            return responseObj;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool IsSoundCloudLink(string url) =>
 | 
			
		||||
            System.Text.RegularExpressions.Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)");
 | 
			
		||||
 | 
			
		||||
        internal async Task<SoundCloudVideo> GetVideoByQueryAsync(string query)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(query))
 | 
			
		||||
                throw new ArgumentNullException(nameof(query));
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(NadekoBot.Creds.SoundCloudClientID))
 | 
			
		||||
                throw new ArgumentNullException(nameof(NadekoBot.Creds.SoundCloudClientID));
 | 
			
		||||
 | 
			
		||||
            var response = await http.GetStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo[]>(response).Where(s => s.Streamable).FirstOrDefault();
 | 
			
		||||
            if (responseObj?.Kind != "track")
 | 
			
		||||
                throw new InvalidOperationException("Query yielded no results.");
 | 
			
		||||
 | 
			
		||||
            return responseObj;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class SoundCloudVideo
 | 
			
		||||
    {
 | 
			
		||||
        public string Kind { get; set; } = "";
 | 
			
		||||
        public long Id { get; set; } = 0;
 | 
			
		||||
        public SoundCloudUser User { get; set; } = new SoundCloudUser();
 | 
			
		||||
        public string Title { get; set; } = "";
 | 
			
		||||
        [JsonIgnore]
 | 
			
		||||
        public string FullName => User.Name + " - " + Title;
 | 
			
		||||
        public bool Streamable { get; set; } = false;
 | 
			
		||||
        [JsonProperty("permalink_url")]
 | 
			
		||||
        public string TrackLink { get; set; } = "";
 | 
			
		||||
        [JsonIgnore]
 | 
			
		||||
        public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Creds.SoundCloudClientID}";
 | 
			
		||||
    }
 | 
			
		||||
    public class SoundCloudUser
 | 
			
		||||
    {
 | 
			
		||||
        [Newtonsoft.Json.JsonProperty("username")]
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
    /*
 | 
			
		||||
    {"kind":"track",
 | 
			
		||||
    "id":238888167,
 | 
			
		||||
    "created_at":"2015/12/24 01:04:52 +0000",
 | 
			
		||||
    "user_id":43141975,
 | 
			
		||||
    "duration":120852,
 | 
			
		||||
    "commentable":true,
 | 
			
		||||
    "state":"finished",
 | 
			
		||||
    "original_content_size":4834829,
 | 
			
		||||
    "last_modified":"2015/12/24 01:17:59 +0000",
 | 
			
		||||
    "sharing":"public",
 | 
			
		||||
    "tag_list":"Funky",
 | 
			
		||||
    "permalink":"18-fd",
 | 
			
		||||
    "streamable":true,
 | 
			
		||||
    "embeddable_by":"all",
 | 
			
		||||
    "downloadable":false,
 | 
			
		||||
    "purchase_url":null,
 | 
			
		||||
    "label_id":null,
 | 
			
		||||
    "purchase_title":null,
 | 
			
		||||
    "genre":"Disco",
 | 
			
		||||
    "title":"18 Ж",
 | 
			
		||||
    "description":"",
 | 
			
		||||
    "label_name":null,
 | 
			
		||||
    "release":null,
 | 
			
		||||
    "track_type":null,
 | 
			
		||||
    "key_signature":null,
 | 
			
		||||
    "isrc":null,
 | 
			
		||||
    "video_url":null,
 | 
			
		||||
    "bpm":null,
 | 
			
		||||
    "release_year":null,
 | 
			
		||||
    "release_month":null,
 | 
			
		||||
    "release_day":null,
 | 
			
		||||
    "original_format":"mp3",
 | 
			
		||||
    "license":"all-rights-reserved",
 | 
			
		||||
    "uri":"https://api.soundcloud.com/tracks/238888167",
 | 
			
		||||
    "user":{
 | 
			
		||||
        "id":43141975,
 | 
			
		||||
        "kind":"user",
 | 
			
		||||
        "permalink":"mrb00gi",
 | 
			
		||||
        "username":"Mrb00gi",
 | 
			
		||||
        "last_modified":"2015/12/01 16:06:57 +0000",
 | 
			
		||||
        "uri":"https://api.soundcloud.com/users/43141975",
 | 
			
		||||
        "permalink_url":"http://soundcloud.com/mrb00gi",
 | 
			
		||||
        "avatar_url":"https://a1.sndcdn.com/images/default_avatar_large.png"
 | 
			
		||||
        },
 | 
			
		||||
    "permalink_url":"http://soundcloud.com/mrb00gi/18-fd",
 | 
			
		||||
    "artwork_url":null,
 | 
			
		||||
    "waveform_url":"https://w1.sndcdn.com/gsdLfvEW1cUK_m.png",
 | 
			
		||||
    "stream_url":"https://api.soundcloud.com/tracks/238888167/stream",
 | 
			
		||||
    "playback_count":7,
 | 
			
		||||
    "download_count":0,
 | 
			
		||||
    "favoritings_count":1,
 | 
			
		||||
    "comment_count":0,
 | 
			
		||||
    "attachments_uri":"https://api.soundcloud.com/tracks/238888167/attachments"}
 | 
			
		||||
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user