diff --git a/src/NadekoBot/_Modules/Music/Classes/MusicControls.cs b/src/NadekoBot/_Modules/Music/Classes/MusicControls.cs deleted file mode 100644 index e13239cf..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/MusicControls.cs +++ /dev/null @@ -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 playlist = new List(); - public IReadOnlyCollection 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 OnCompleted = delegate { }; - public event EventHandler 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 actionQueue { get; set; } = new ConcurrentQueue(); - - 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(); - } - } -} diff --git a/src/NadekoBot/_Modules/Music/Classes/PlaylistFullException.cs b/src/NadekoBot/_Modules/Music/Classes/PlaylistFullException.cs deleted file mode 100644 index 15541d42..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/PlaylistFullException.cs +++ /dev/null @@ -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.") { } - } -} diff --git a/src/NadekoBot/_Modules/Music/Classes/Song.cs b/src/NadekoBot/_Modules/Music/Classes/Song.cs deleted file mode 100644 index 2d808736..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/Song.cs +++ /dev/null @@ -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; } - /// - /// Will be set only if the providertype is normal - /// - 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 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=(?\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 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=(?.*?)\\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, "(?^[^#].*)", 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")) - { - // - try - { - var m = Regex.Match(file, ".*?)\""); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - Console.WriteLine($"Failed reading .asx:\n{file}"); - return null; - } - } - if (query.Contains(".xspf")) - { - /* - - - - file:///mp3s/song_1.mp3 - */ - try - { - var m = Regex.Match(file, "(?.*?)"); - 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")); - } -} diff --git a/src/NadekoBot/_Modules/Music/Classes/SongBuffer.cs b/src/NadekoBot/_Modules/Music/Classes/SongBuffer.cs deleted file mode 100644 index d9192940..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/SongBuffer.cs +++ /dev/null @@ -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 -{ - /// - /// 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. - /// - 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); - - /// - /// Return the next file to read, and delete the old one - /// - /// Name of the file to read - 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 { } - } - } - } -} diff --git a/src/NadekoBot/_Modules/Music/Classes/SoundCloud.cs b/src/NadekoBot/_Modules/Music/Classes/SoundCloud.cs deleted file mode 100644 index ed829adb..00000000 --- a/src/NadekoBot/_Modules/Music/Classes/SoundCloud.cs +++ /dev/null @@ -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 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(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 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(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"} - - */ - -}