Decompiled source of ytModule v1.0.6

plugins/onlystar-ytModule.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Web;
using BepInEx;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("onlystar")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.6.0")]
[assembly: AssemblyInformationalVersion("1.0.6+b87414552e5874a8f69eb175a28f6c8fe21b09d5")]
[assembly: AssemblyProduct("onlystar-ytModule")]
[assembly: AssemblyTitle("YTModule")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.6.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace ytModule
{
	[BepInPlugin("onlystar.ytModule", "ytModule", "1.0.6")]
	public class ytModulePlugin : BaseUnityPlugin
	{
		public const string PluginGUID = "onlystar.ytModule";

		public const string PluginName = "ytModule";

		public const string PluginVersion = "1.0.6";

		internal static ManualLogSource Logger;

		private string ytDlpPath;

		private string ffmpegPath;

		public static bool Prod { get; set; } = true;

		internal static ytModulePlugin Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
			Logger = ((BaseUnityPlugin)this).Logger;
			string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			ytDlpPath = Path.Combine(directoryName, "yt-dlp.exe");
			ffmpegPath = Path.Combine(directoryName, "ffmpeg.exe");
			string path = Path.Combine(directoryName, "deno.exe");
			if (!File.Exists(ytDlpPath))
			{
				Logger.LogError((object)"[ytModule] yt-dlp.exe not found!");
			}
			if (!File.Exists(ffmpegPath))
			{
				Logger.LogError((object)"[ytModule] ffmpeg.exe not found!");
			}
			if (!File.Exists(path))
			{
				Logger.LogWarning((object)"[ytModule] deno.exe not found. Note that it is optional for now, but it might be a requirement in the future.");
			}
			string environmentVariable = Environment.GetEnvironmentVariable("PATH");
			Environment.SetEnvironmentVariable("PATH", directoryName + Path.PathSeparator + environmentVariable);
			Logger.LogInfo((object)"ytModule 1.0.6 loaded successfully!");
		}

		public static ytModuleAPI GetAPI()
		{
			if ((Object)(object)Instance == (Object)null)
			{
				ManualLogSource logger = Logger;
				if (logger != null)
				{
					logger.LogError((object)"[ytModule] ytModule plugin not found!");
				}
				return null;
			}
			return new ytModuleAPI(Instance);
		}

		public static bool IsModuleReady()
		{
			if ((Object)(object)Instance == (Object)null)
			{
				return false;
			}
			string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)Instance).Info.Location);
			string path = Path.Combine(directoryName, "yt-dlp.exe");
			string path2 = Path.Combine(directoryName, "ffmpeg.exe");
			string path3 = Path.Combine(directoryName, "deno.exe");
			bool result = File.Exists(path) && File.Exists(path2);
			if (!File.Exists(path3))
			{
				ManualLogSource logger = Logger;
				if (logger != null)
				{
					logger.LogWarning((object)"[ytModule] Deno runtime not found. YouTube downloads may have an increased chance to fail.");
				}
			}
			return result;
		}
	}
	public class ytModuleAPI
	{
		public enum Platform
		{
			YouTube,
			SoundCloud,
			Vimeo,
			Bandcamp,
			Other
		}

		private readonly ytModulePlugin plugin;

		private readonly string ytDlpPath;

		private readonly string ffmpegPath;

		private readonly string tempDir;

		internal ytModuleAPI(ytModulePlugin plugin)
		{
			this.plugin = plugin;
			string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)plugin).Info.Location);
			ytDlpPath = Path.Combine(directoryName, "yt-dlp.exe");
			ffmpegPath = Path.Combine(directoryName, "ffmpeg.exe");
			tempDir = Path.Combine(Path.GetTempPath(), "sPEAKerYT");
			Directory.CreateDirectory(tempDir);
		}

		public IEnumerator GetMetadata(string url, Action<VideoMetadata> onComplete, Action<string> onError = null)
		{
			List<string> args = BuildMetadataArgs(url);
			yield return ((MonoBehaviour)plugin).StartCoroutine(ExecuteYtDlp(args, delegate(string output)
			{
				try
				{
					VideoMetadata obj = ParseMetadata(output);
					onComplete?.Invoke(obj);
				}
				catch (Exception ex)
				{
					onError?.Invoke("Metadata parsing failed: " + ex.Message);
				}
			}, onError));
		}

		public IEnumerator DownloadAudio(string url, string outputPath, DownloadOptions options, Action<DownloadResult> onComplete, Action<string> onError = null)
		{
			List<string> args = BuildDownloadArgs(url, outputPath, options);
			yield return ((MonoBehaviour)plugin).StartCoroutine(ExecuteYtDlp(args, delegate(string output)
			{
				DownloadResult downloadResult = new DownloadResult
				{
					FilePath = outputPath,
					Success = File.Exists(outputPath + ".ogg")
				};
				try
				{
					downloadResult.Metadata = ParseMetadata(output);
				}
				catch (Exception ex)
				{
					ytModulePlugin.Logger.LogWarning((object)("[ytModule] Metadata parsing failed: " + ex.Message));
					downloadResult.Metadata = null;
				}
				onComplete?.Invoke(downloadResult);
			}, onError));
		}

		public IEnumerator DownloadAudioOnly(string url, string outputPath, string metadataPath, int maxSizeMb = 100, Action<string> onError = null)
		{
			DownloadOptions options = new DownloadOptions
			{
				AudioFormat = "vorbis",
				Quality = 5,
				MaxSizeMb = maxSizeMb
			};
			yield return ((MonoBehaviour)plugin).StartCoroutine(DownloadAudio(url, outputPath, options, delegate(DownloadResult result)
			{
				ytModulePlugin.Logger.LogInfo((object)$"[ytModule] Download complete - Success: {result.Success}, Metadata null: {result.Metadata == null}");
				if (result.Metadata != null)
				{
					ytModulePlugin.Logger.LogInfo((object)("[ytModule] Metadata title: '" + result.Metadata.title + "'"));
				}
				if (result.Metadata != null)
				{
					SongMetadata songMetadata = new SongMetadata
					{
						title = (result.Metadata.title ?? "Unknown")
					};
					string text = JsonUtility.ToJson((object)songMetadata);
					ytModulePlugin.Logger.LogInfo((object)("[ytModule] Writing metadata to: " + metadataPath + ", JSON: " + text));
					File.WriteAllText(metadataPath, text);
				}
				else
				{
					ytModulePlugin.Logger.LogWarning((object)$"[ytModule] Metadata not written - Success: {result.Success}, Metadata: {result.Metadata != null}");
				}
			}, onError));
		}

		public IEnumerator DownloadVideo(string url, string outputPath, VideoDownloadOptions options, Action<DownloadResult> onComplete, Action<string> onError = null)
		{
			List<string> args = BuildVideoDownloadArgs(url, outputPath, options);
			yield return ((MonoBehaviour)plugin).StartCoroutine(ExecuteYtDlp(args, delegate(string output)
			{
				DownloadResult downloadResult = new DownloadResult
				{
					FilePath = outputPath,
					Success = File.Exists(outputPath + "." + options.Container)
				};
				try
				{
					downloadResult.Metadata = ParseMetadata(output);
				}
				catch (Exception ex)
				{
					ytModulePlugin.Logger.LogWarning((object)("[ytModule] Metadata parsing failed: " + ex.Message));
					downloadResult.Metadata = null;
				}
				onComplete?.Invoke(downloadResult);
			}, onError));
		}

		public IEnumerator GetRelatedVideos(string sourceUrl, int maxCount = 10, Action<List<string>> onComplete = null, Action<string> onError = null, string playlistId = null)
		{
			List<PlaylistEntry> entries = null;
			yield return ((MonoBehaviour)plugin).StartCoroutine(GetRelatedVideosWithMetadata(sourceUrl, maxCount, delegate(List<PlaylistEntry> result)
			{
				entries = result;
			}, onError, playlistId));
			if (entries == null)
			{
				yield break;
			}
			string sourceId = (string.IsNullOrEmpty(playlistId) ? GetVideoIdFromUrl(sourceUrl) : null);
			List<string> urls = new List<string>(entries.Count);
			foreach (PlaylistEntry e in entries)
			{
				if (sourceId == null || !(GetVideoIdFromUrl(e.url) == sourceId))
				{
					urls.Add(e.url);
				}
			}
			onComplete?.Invoke(urls);
		}

		public IEnumerator GetRelatedVideosWithMetadata(string sourceUrl, int maxCount = 10, Action<List<PlaylistEntry>> onComplete = null, Action<string> onError = null, string playlistId = null)
		{
			string targetUrl;
			if (!string.IsNullOrEmpty(playlistId))
			{
				targetUrl = "https://www.youtube.com/playlist?list=" + playlistId;
			}
			else
			{
				string videoId = GetVideoIdFromUrl(sourceUrl);
				if (string.IsNullOrEmpty(videoId))
				{
					onError?.Invoke("Invalid YouTube URL");
					yield break;
				}
				targetUrl = "https://www.youtube.com/watch?v=" + videoId + "&list=RD" + videoId;
			}
			List<string> args = new List<string> { "--extractor-args", "youtube:player_client=web,default", "--flat-playlist", "--dump-json", targetUrl };
			AddCookieArgs(args);
			if (string.IsNullOrEmpty(playlistId))
			{
				args.Add("--playlist-end");
				args.Add(maxCount.ToString());
			}
			yield return ((MonoBehaviour)plugin).StartCoroutine(ExecuteYtDlp(args, delegate(string output)
			{
				List<PlaylistEntry> list = new List<PlaylistEntry>();
				string[] array = output.Split('\n');
				string[] array2 = array;
				foreach (string text in array2)
				{
					string text2 = text.Trim();
					if (text2.StartsWith("{"))
					{
						try
						{
							PlaylistEntry playlistEntry = JsonUtility.FromJson<PlaylistEntry>(text2);
							if (!string.IsNullOrEmpty(playlistEntry.url))
							{
								list.Add(playlistEntry);
							}
						}
						catch
						{
						}
					}
				}
				onComplete?.Invoke(list);
			}, onError));
		}

		public IEnumerator GetPlaylistInfo(string playlistUrl, Action<PlaylistInfo> onComplete, Action<string> onError = null)
		{
			List<string> args = BuildPlaylistInfoArgs(playlistUrl);
			yield return ((MonoBehaviour)plugin).StartCoroutine(ExecuteYtDlp(args, delegate(string output)
			{
				try
				{
					PlaylistInfo obj = ParsePlaylistInfo(output);
					onComplete(obj);
				}
				catch (Exception ex)
				{
					onError("Playlist parsing failed: " + ex.Message);
				}
			}, onError));
		}

		public IEnumerator SearchVideos(string query, Action<List<VideoMetadata>> onComplete, Action<string> onError = null, SearchOptions options = null)
		{
			if (options == null)
			{
				options = new SearchOptions();
			}
			List<string> args = BuildSearchArgs(query, options);
			yield return ((MonoBehaviour)plugin).StartCoroutine(ExecuteYtDlp(args, delegate(string output)
			{
				try
				{
					List<VideoMetadata> obj = ParseSearchResults(output);
					onComplete?.Invoke(obj);
				}
				catch (Exception ex)
				{
					onError?.Invoke("Search parsing failed: " + ex.Message);
				}
			}, onError));
		}

		private List<string> BuildMetadataArgs(string url)
		{
			List<string> list = new List<string>(2) { "--dump-json", "--no-download" };
			AddPlatformSpecificArgs(list, url);
			AddCookieArgs(list);
			list.Add(url);
			return list;
		}

		private List<string> BuildDownloadArgs(string url, string outputPath, DownloadOptions options)
		{
			List<string> list = new List<string>
			{
				"--no-playlist",
				"--retries",
				"3",
				"--fragment-retries",
				"3",
				"--abort-on-unavailable-fragment",
				"-x",
				"--audio-format",
				options.AudioFormat,
				"--audio-quality",
				options.Quality.ToString(),
				"--ffmpeg-location",
				ffmpegPath,
				"--max-filesize",
				$"{options.MaxSizeMb}M",
				"--dump-json",
				"--no-simulate",
				"-o",
				outputPath
			};
			AddPlatformSpecificArgs(list, url);
			if (options.AudioFormat == "vorbis")
			{
				list.Add("--postprocessor-args");
				list.Add("ffmpeg:-ar 44100 -ac 2");
			}
			AddCookieArgs(list);
			list.Add(url);
			return list;
		}

		private List<string> BuildVideoDownloadArgs(string url, string outputPath, VideoDownloadOptions options)
		{
			List<string> list = new List<string>
			{
				"--no-playlist",
				"--retries",
				"3",
				"--fragment-retries",
				"3",
				"--abort-on-unavailable-fragment",
				"--format",
				options.FormatSelector,
				"--merge-output-format",
				options.Container,
				"--max-filesize",
				$"{options.MaxSizeMb}M",
				"--dump-json",
				"--no-simulate",
				"-o",
				outputPath
			};
			AddPlatformSpecificArgs(list, url);
			if (!string.IsNullOrEmpty(ffmpegPath))
			{
				list.Add("--ffmpeg-location");
				list.Add(ffmpegPath);
			}
			if (!string.IsNullOrEmpty(options.PostProcessorArgs))
			{
				list.Add("--postprocessor-args");
				list.Add(options.PostProcessorArgs);
			}
			AddCookieArgs(list);
			list.Add(url);
			return list;
		}

		private List<string> BuildPlaylistInfoArgs(string playlistUrl)
		{
			List<string> list = new List<string> { "--flat-playlist", "--dump-json", "--compat-options", "no-youtube-unavailable-videos" };
			AddPlatformSpecificArgs(list, playlistUrl);
			AddCookieArgs(list);
			list.Add(playlistUrl);
			return list;
		}

		private List<string> BuildSearchArgs(string query, SearchOptions options)
		{
			string platform = options.Platform;
			if (1 == 0)
			{
			}
			string text = platform switch
			{
				"youtube" => "ytsearch", 
				"youtube-date" => "ytsearchdate", 
				"soundcloud" => "scsearch", 
				"bandcamp" => "bcsearch", 
				"vimeo" => "vmsearch", 
				_ => "ytsearch", 
			};
			if (1 == 0)
			{
			}
			string arg = text;
			List<string> list = new List<string>
			{
				"--flat-playlist",
				"-i",
				"--dump-json",
				$"{arg}{options.MaxResults}:{query}"
			};
			AddCookieArgs(list);
			return list;
		}

		private IEnumerator ExecuteYtDlp(List<string> args, Action<string> onSuccess, Action<string> onError)
		{
			ProcessStartInfo psi = new ProcessStartInfo
			{
				FileName = ytDlpPath,
				RedirectStandardOutput = true,
				RedirectStandardError = true,
				UseShellExecute = false,
				CreateNoWindow = true
			};
			string instanceTempDir;
			if (!ytModulePlugin.Prod)
			{
				instanceTempDir = Path.Combine(path2: $"ytdlp_instance_{Guid.NewGuid():N}", path1: tempDir);
				Directory.CreateDirectory(instanceTempDir);
				psi.WorkingDirectory = instanceTempDir;
				psi.EnvironmentVariables["XDG_CACHE_HOME"] = instanceTempDir;
				psi.EnvironmentVariables["TEMP"] = instanceTempDir;
				psi.EnvironmentVariables["TMP"] = instanceTempDir;
				ytModulePlugin.Logger.LogInfo((object)("[ytModule] DEV MODE: Started process in isolated dir: " + instanceTempDir));
			}
			else
			{
				instanceTempDir = tempDir;
				psi.WorkingDirectory = tempDir;
				ytModulePlugin.Logger.LogInfo((object)"[ytModule] Started process in shared temp dir");
			}
			foreach (string arg in args)
			{
				psi.ArgumentList.Add(arg);
			}
			Process proc = new Process
			{
				StartInfo = psi
			};
			StringBuilder output = new StringBuilder();
			StringBuilder errors = new StringBuilder();
			proc.OutputDataReceived += delegate(object _, DataReceivedEventArgs e)
			{
				if (!string.IsNullOrEmpty(e.Data))
				{
					output.AppendLine(e.Data);
				}
			};
			proc.ErrorDataReceived += delegate(object _, DataReceivedEventArgs e)
			{
				if (!string.IsNullOrEmpty(e.Data))
				{
					errors.AppendLine(e.Data);
					ytModulePlugin.Logger.LogWarning((object)("[ytModule] yt-dlp: " + e.Data));
				}
			};
			proc.Start();
			proc.BeginOutputReadLine();
			proc.BeginErrorReadLine();
			yield return (object)new WaitUntil((Func<bool>)(() => proc.HasExited));
			if (!ytModulePlugin.Prod && instanceTempDir != tempDir)
			{
				try
				{
					if (Directory.Exists(instanceTempDir))
					{
						Directory.Delete(instanceTempDir, recursive: true);
					}
				}
				catch
				{
				}
			}
			if (proc.ExitCode == 0)
			{
				onSuccess?.Invoke(output.ToString());
				yield break;
			}
			string errorMsg = ((errors.Length > 0) ? errors.ToString() : "Unknown yt-dlp error");
			onError?.Invoke(errorMsg);
		}

		private VideoMetadata ParseMetadata(string jsonOutput)
		{
			string[] array = jsonOutput.Split('\n');
			string[] array2 = array;
			foreach (string text in array2)
			{
				string text2 = text.Trim();
				if (text2.StartsWith("{") && text2.EndsWith("}"))
				{
					return JsonUtility.FromJson<VideoMetadata>(text2);
				}
			}
			throw new Exception("No valid JSON metadata found");
		}

		private PlaylistInfo ParsePlaylistInfo(string jsonOutput)
		{
			string[] array = jsonOutput.Split('\n');
			List<string> list = new List<string>();
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (!string.IsNullOrWhiteSpace(text))
				{
					list.Add(text);
				}
			}
			if (list.Count == 0)
			{
				throw new Exception("No playlist data found");
			}
			PlaylistInfo playlistInfo = JsonUtility.FromJson<PlaylistInfo>(list[0]);
			List<PlaylistEntry> list2 = new List<PlaylistEntry>();
			foreach (string item in list)
			{
				string text2 = item.Trim();
				if (!text2.StartsWith("{"))
				{
					continue;
				}
				try
				{
					PlaylistEntry playlistEntry = JsonUtility.FromJson<PlaylistEntry>(text2);
					if (!string.IsNullOrEmpty(playlistEntry.url))
					{
						list2.Add(playlistEntry);
					}
				}
				catch
				{
				}
			}
			playlistInfo.entries = list2.ToArray();
			return playlistInfo;
		}

		private List<VideoMetadata> ParseSearchResults(string jsonOutput)
		{
			List<VideoMetadata> list = new List<VideoMetadata>();
			string[] array = jsonOutput.Split('\n');
			string[] array2 = array;
			foreach (string text in array2)
			{
				string text2 = text.Trim();
				if (text2.StartsWith("{") && text2.EndsWith("}"))
				{
					try
					{
						VideoMetadata item = JsonUtility.FromJson<VideoMetadata>(text2);
						list.Add(item);
					}
					catch
					{
					}
				}
			}
			return list;
		}

		public string GetVideoIdFromUrl(string url)
		{
			try
			{
				Uri uri = new Uri(url);
				if (uri.Host.Contains("youtu.be"))
				{
					return uri.AbsolutePath.Trim('/');
				}
				if (uri.Host.Contains("youtube.com"))
				{
					return HttpUtility.ParseQueryString(uri.Query)["v"];
				}
			}
			catch
			{
			}
			using SHA1 sHA = SHA1.Create();
			return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(url)), 0, 4).Replace("-", "");
		}

		public bool IsValidYouTubeUrl(string url)
		{
			if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result))
			{
				return false;
			}
			string text = result.Host.ToLowerInvariant();
			return text.Contains("youtube.com") || text.Contains("youtu.be");
		}

		private void AddCookieArgs(List<string> args)
		{
			string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)plugin).Info.Location);
			string text = FindCookiesFile(directoryName);
			if (text != null)
			{
				ytModulePlugin.Logger.LogInfo((object)("[ytModule] Using cookies from " + Path.GetFileName(text)));
				args.Add("--cookies");
				args.Add(text);
			}
		}

		private string FindCookiesFile(string directory)
		{
			if (!Directory.Exists(directory))
			{
				return null;
			}
			string text = Path.Combine(directory, "cookies.txt");
			if (File.Exists(text))
			{
				return text;
			}
			string[] files = Directory.GetFiles(directory, "*cookies.txt", SearchOption.TopDirectoryOnly);
			if (files.Length != 0)
			{
				if (files.Length > 1)
				{
					ytModulePlugin.Logger.LogWarning((object)$"[ytModule] Found {files.Length} cookie files, using: {Path.GetFileName(files[0])}");
				}
				return files[0];
			}
			return null;
		}

		public Platform DetectPlatform(string url)
		{
			if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result))
			{
				return Platform.Other;
			}
			string text = result.Host.ToLowerInvariant();
			if (text.Contains("youtube.com") || text.Contains("youtu.be"))
			{
				return Platform.YouTube;
			}
			if (text.Contains("soundcloud.com"))
			{
				return Platform.SoundCloud;
			}
			if (text.Contains("vimeo.com"))
			{
				return Platform.Vimeo;
			}
			if (text.Contains("bandcamp.com"))
			{
				return Platform.Bandcamp;
			}
			return Platform.Other;
		}

		private void AddPlatformSpecificArgs(List<string> args, string url)
		{
			if (DetectPlatform(url) == Platform.YouTube)
			{
				args.Add("--extractor-args");
				args.Add("youtube:player_client=web,default");
			}
		}
	}
	public class SearchOptions
	{
		public int MaxResults = 10;

		public string Platform = "youtube";
	}
	public class DownloadOptions
	{
		public string AudioFormat = "vorbis";

		public int Quality = 5;

		public int MaxSizeMb = 100;

		public bool ExtractMetadata = true;
	}
	public class VideoDownloadOptions
	{
		public string FormatSelector = "bestvideo+bestaudio/best";

		public string Container = "mp4";

		public int MaxSizeMb = 500;

		public string PostProcessorArgs = null;
	}
	public class DownloadResult
	{
		public string FilePath;

		public VideoMetadata Metadata;

		public bool Success;

		public string ErrorMessage;
	}
	public class VideoMetadata
	{
		public string title;

		public string id;

		public string uploader;

		public float duration;

		public string webpage_url;

		public string playlist;

		public string playlist_title;

		public int playlist_index;
	}
	public class PlaylistInfo
	{
		public string title;

		public string id;

		public string uploader;

		public int playlist_count;

		public PlaylistEntry[] entries;
	}
	public class PlaylistEntry
	{
		public string id;

		public string title;

		public string url;

		public int playlist_index;
	}
	public class SongMetadata
	{
		public string title;
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}