Decompiled source of YoutubeDownloader v0.0.4

YoutubeDownloader.dll

Decompiled 9 months ago
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BestestTVModPlugin;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using YoutubeDLSharp;
using YoutubeDLSharp.Metadata;
using YoutubeDLSharp.Options;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("YoutubeDownloader")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+0e14460f7cfc4423db0502c178e3415750870e39")]
[assembly: AssemblyProduct("YoutubeDownloader")]
[assembly: AssemblyTitle("YoutubeDownloader")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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;
		}
	}
}
namespace YoutubeDownloader
{
	[BepInPlugin("DeathWrench.YoutubeDownloader", "\u200bYoutubeDownloader", "0.0.4")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		private ConfigEntry<bool> playlistModeActivated;

		private ConfigEntry<bool> skipDownloadEnabled;

		private ConfigEntry<bool> deleteTelevisionVideos;

		private ConfigEntry<Key> downloadKeyBind;

		private ConfigEntry<string> defaultVideoUrl;

		private ConfigEntry<int> startOfPlaylist;

		private ConfigEntry<int> endOfPlaylist;

		public static ManualLogSource Log = new ManualLogSource("YoutubeDownloader");

		private static readonly Harmony Harmony = new Harmony("DeathWrench.YoutubeDownloader");

		private YoutubeDL ytdl;

		private InputAction downloadAction;

		private static string pluginPath = Paths.PluginPath + Path.DirectorySeparatorChar + "DeathWrench-YoutubeDownloader";

		private static string libraryPath = Path.Combine(pluginPath, "lib");

		private string ytDlpPath = Path.Combine(libraryPath, "yt-dlp.exe");

		private string televisionVideosPath = Path.Combine(pluginPath, "Television Videos");

		private async void Start()
		{
			Log.LogInfo((object)"Plugin DeathWrench.YoutubeDownloader is loaded!");
			playlistModeActivated = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Playlist Mode", false, "Download playlists?");
			skipDownloadEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Skip Downloads", false, "Enable skipping downloads if the file already exists. This is ignored in Playlist Mode.");
			downloadKeyBind = ((BaseUnityPlugin)this).Config.Bind<Key>("Settings", "Download Keybind", (Key)6, "Which key to press to initiate YouTube video download.");
			defaultVideoUrl = ((BaseUnityPlugin)this).Config.Bind<string>("Settings", "Video URL", "https://www.youtube.com/watch?v=4Nty0riqSOs", "YouTube video URL for downloading.");
			startOfPlaylist = ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "Start of Playlist", 1, "Select specific videos in a playlist to download. Setting to 5 will download the 5th video in the playlist.");
			endOfPlaylist = ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "End of Playlist", 999999, "Which video to stop downloading at. Setting to 7 with start set to 5 will only download the 5th, 6th, and 7th videos.");
			deleteTelevisionVideos = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Delete Videos", false, "Delete the downloaded videos every time the game starts?");
			if (!Directory.Exists(televisionVideosPath))
			{
				Directory.CreateDirectory(televisionVideosPath);
			}
			if (deleteTelevisionVideos.Value)
			{
				string[] filesToDelete = Directory.GetFiles(televisionVideosPath, "*.mp4");
				string[] array = filesToDelete;
				foreach (string fileToDelete in array)
				{
					File.Delete(fileToDelete);
				}
			}
			if (!Directory.Exists(libraryPath))
			{
				Directory.CreateDirectory(libraryPath);
			}
			if (!File.Exists(libraryPath + "\\yt-dlp.exe"))
			{
				await DownloadLatestYtDlpRelease(libraryPath + "\\yt-dlp.exe");
			}
			else
			{
				Log.LogInfo((object)"yt-dlp already exists. Skipping download.");
			}
			if (!File.Exists(libraryPath + "\\ffmpeg.exe"))
			{
				await DownloadAndExtractFFmpeg(libraryPath + "\\ffmpeg.exe");
			}
			else
			{
				Log.LogInfo((object)"FFmpeg already exists. Skipping download.");
			}
			ytdl = new YoutubeDL((byte)4);
			ytdl.YoutubeDLPath = libraryPath + "\\yt-dlp.exe";
			ytdl.FFmpegPath = libraryPath + "\\ffmpeg.exe";
			Key downloadKey = downloadKeyBind.Value;
			downloadAction = new InputAction((string)null, (InputActionType)0, $"<Keyboard>/{downloadKey}", "press", (string)null, (string)null);
			downloadAction.Enable();
			downloadAction.performed += async delegate(CallbackContext ctx)
			{
				//IL_0019: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				if (((CallbackContext)(ref ctx)).ReadValueAsButton())
				{
					if (CheckDiskSpace(videoSize: await GetVideoSize(defaultVideoUrl.Value), path: televisionVideosPath))
					{
						if (!playlistModeActivated.Value)
						{
							HUDManager.Instance.DisplayTip("Download in Progress", "Please wait while the video is being downloaded...", false, false, "DownloadProgressTip");
							await DownloadVideo(defaultVideoUrl.Value, televisionVideosPath);
						}
						else
						{
							HUDManager.Instance.DisplayTip("Download in Progress", "Please wait while the playlist is being downloaded...", false, false, "DownloadProgressTip");
							await DownloadPlaylist(defaultVideoUrl.Value, televisionVideosPath);
						}
					}
					else
					{
						HUDManager.Instance.DisplayTip("Download Error", "Not enough free disk space to download the video.", false, false, "DownloadErrorTip");
					}
				}
			};
			Harmony.PatchAll();
		}

		private void Update()
		{
			if (CheckForConfigChanges())
			{
				UpdateSettings();
			}
			if (CheckForNewFiles())
			{
				HandleNewFiles();
			}
			if (Keyboard.current.ctrlKey.isPressed && ((ButtonControl)Keyboard.current.vKey).wasPressedThisFrame)
			{
				PasteTextFromClipboard();
			}
		}

		private void PasteTextFromClipboard()
		{
			string systemCopyBuffer = GUIUtility.systemCopyBuffer;
			Debug.Log((object)("Pasting text from clipboard: " + systemCopyBuffer));
		}

		private bool CheckForConfigChanges()
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			if (playlistModeActivated.Value != ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Playlist Mode", playlistModeActivated.Value, (ConfigDescription)null).Value || skipDownloadEnabled.Value != ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Skip Downloads", skipDownloadEnabled.Value, (ConfigDescription)null).Value || downloadKeyBind.Value != ((BaseUnityPlugin)this).Config.Bind<Key>("Settings", "Download Keybind", downloadKeyBind.Value, (ConfigDescription)null).Value || defaultVideoUrl.Value != ((BaseUnityPlugin)this).Config.Bind<string>("Settings", "Video URL", defaultVideoUrl.Value, (ConfigDescription)null).Value || startOfPlaylist.Value != ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "Start of Playlist", startOfPlaylist.Value, (ConfigDescription)null).Value || endOfPlaylist.Value != ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "End of Playlist", endOfPlaylist.Value, (ConfigDescription)null).Value || deleteTelevisionVideos.Value != ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Delete Videos", deleteTelevisionVideos.Value, (ConfigDescription)null).Value)
			{
				return true;
			}
			return false;
		}

		private void UpdateSettings()
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			playlistModeActivated.Value = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Playlist Mode", playlistModeActivated.Value, (ConfigDescription)null).Value;
			skipDownloadEnabled.Value = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Skip Downloads", skipDownloadEnabled.Value, (ConfigDescription)null).Value;
			downloadKeyBind.Value = ((BaseUnityPlugin)this).Config.Bind<Key>("Settings", "Download Keybind", downloadKeyBind.Value, (ConfigDescription)null).Value;
			defaultVideoUrl.Value = ((BaseUnityPlugin)this).Config.Bind<string>("Settings", "Video URL", defaultVideoUrl.Value, (ConfigDescription)null).Value;
			startOfPlaylist.Value = ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "Start of Playlist", startOfPlaylist.Value, (ConfigDescription)null).Value;
			endOfPlaylist.Value = ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "End of Playlist", endOfPlaylist.Value, (ConfigDescription)null).Value;
			deleteTelevisionVideos.Value = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Delete Videos", deleteTelevisionVideos.Value, (ConfigDescription)null).Value;
		}

		private bool CheckForNewFiles()
		{
			string[] files = Directory.GetFiles(televisionVideosPath, "*.mp4");
			if (files.Length > VideoManager.Videos.Count)
			{
				return true;
			}
			return false;
		}

		private void HandleNewFiles()
		{
			VideoManager.Videos.Clear();
			VideoManager.Load();
			if (ConfigManager.reloadedVideosHUD.Value)
			{
				HUDManager.Instance.DisplayTip("Reloaded Videos", "Video list has been reloaded.", false, false, "ReloadVideosTip");
			}
		}

		private async Task DownloadLatestYtDlpRelease(string destinationFolder)
		{
			string latestReleaseUrl = "https://github.com/yt-dlp/yt-dlp/releases/latest";
			HttpWebRequest request = (HttpWebRequest)WebRequest.Create(latestReleaseUrl);
			request.AllowAutoRedirect = true;
			try
			{
				using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
				string redirectedUrl = response.ResponseUri.AbsoluteUri;
				string releaseDateOrVersion = redirectedUrl.Split('/')[^1];
				string downloadUrl = "https://github.com/yt-dlp/yt-dlp/releases/download/" + releaseDateOrVersion + "/yt-dlp.exe";
				string downloadPath = ytDlpPath;
				using WebClient client = new WebClient();
				client.DownloadFile(downloadUrl, downloadPath);
				Log.LogInfo((object)"Download completed successfully.");
			}
			catch (Exception ex2)
			{
				Exception ex = ex2;
				Log.LogError((object)("Error: " + ex.Message));
			}
		}

		private async Task DownloadAndExtractFFmpeg(string ffmpegPath)
		{
			Log.LogInfo((object)"Downloading FFmpeg...");
			string releaseUrl = "https://github.com/GyanD/codexffmpeg/releases/latest/";
			HttpWebRequest request = (HttpWebRequest)WebRequest.Create(releaseUrl);
			request.AllowAutoRedirect = true;
			string actualReleaseUrl;
			try
			{
				using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
				actualReleaseUrl = response.ResponseUri.AbsoluteUri;
			}
			catch (Exception ex4)
			{
				Exception ex3 = ex4;
				Log.LogError((object)("Error getting release URL: " + ex3.Message));
				return;
			}
			string releaseVersion = actualReleaseUrl.Split('/')[^1];
			string ffmpegZipUrl = "https://github.com/GyanD/codexffmpeg/releases/download/" + releaseVersion + "/ffmpeg-" + releaseVersion + "-essentials_build.zip";
			string tempZipPath = Path.Combine(pluginPath, "ffmpeg-" + releaseVersion + "-essentials_build.zip");
			using (WebClient client = new WebClient())
			{
				await client.DownloadFileTaskAsync(ffmpegZipUrl, tempZipPath);
			}
			try
			{
				ZipFile.ExtractToDirectory(tempZipPath, pluginPath);
				Log.LogInfo((object)"FFmpeg downloaded and extracted successfully.");
				string binDirectory = Path.Combine(pluginPath, "ffmpeg-" + releaseVersion + "-essentials_build", "bin");
				string[] files = Directory.GetFiles(binDirectory);
				foreach (string filePath in files)
				{
					string destinationPath = Path.Combine(path2: Path.GetFileName(filePath), path1: libraryPath);
					File.Move(filePath, destinationPath);
					Log.LogInfo((object)("FFmpeg binary moved to: " + destinationPath));
				}
			}
			catch (Exception ex2)
			{
				Log.LogError((object)("Failed to extract FFmpeg zip: " + ex2.Message));
			}
			try
			{
				File.Delete(tempZipPath);
				Directory.Delete(Path.Combine(pluginPath, "ffmpeg-" + releaseVersion + "-essentials_build"), recursive: true);
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Failed to delete temporary files: " + ex.Message));
			}
		}

		private async Task<long> GetVideoSize(string videoUrl)
		{
			using HttpClient client = new HttpClient();
			HttpRequestMessage headRequest = new HttpRequestMessage(HttpMethod.Head, videoUrl);
			HttpResponseMessage response = await client.SendAsync(headRequest);
			if (response.IsSuccessStatusCode && response.Content.Headers.ContentLength.HasValue)
			{
				long videoSize = response.Content.Headers.ContentLength.Value;
				Log.LogInfo((object)$"Video size obtained: {videoSize} bytes");
				return videoSize;
			}
			Log.LogError((object)"Failed to obtain video size. Default size set to 0 bytes");
			return 0L;
		}

		private bool CheckDiskSpace(string path, long videoSize)
		{
			DriveInfo driveInfo = new DriveInfo(Path.GetPathRoot(path));
			long availableFreeSpace = driveInfo.AvailableFreeSpace;
			bool flag = availableFreeSpace >= videoSize;
			if (!flag)
			{
				Log.LogWarning((object)"Not enough free disk space to download the video.");
				Log.LogWarning((object)$"Required space: {videoSize} bytes, Available space: {availableFreeSpace} bytes");
			}
			else
			{
				Log.LogInfo((object)"Sufficient disk space available for download.");
				Log.LogInfo((object)$"Required space: {videoSize} bytes, Available space: {availableFreeSpace} bytes");
			}
			return flag;
		}

		private async Task<bool> IsVideoAlreadyDownloaded(string videoUrl, string destinationFolder)
		{
			RunResult<VideoData> res = await ytdl.RunVideoDataFetch(videoUrl, default(CancellationToken), true, false, (OptionSet)null);
			if (res == null)
			{
				Log.LogError((object)("Failed to fetch video metadata for URL: " + videoUrl));
				return false;
			}
			VideoData video = res.Data;
			string title = video.Title;
			string videoId = video.ID;
			string fileNamePattern = "\\[" + Regex.Escape(videoId) + "\\]\\.mp4";
			Regex regex = new Regex(fileNamePattern);
			string[] files = Directory.GetFiles(destinationFolder);
			string[] array = files;
			foreach (string filePath in array)
			{
				string fileName = Path.GetFileName(filePath);
				if (regex.IsMatch(fileName))
				{
					Log.LogInfo((object)("Video '" + title + "' already exists. Skipping download."));
					return true;
				}
			}
			return false;
		}

		private async Task DownloadPlaylist(string playlistUrl, string destinationFolder)
		{
			Log.LogInfo((object)"Downloading playlist...");
			ytdl.OutputFolder = destinationFolder;
			OptionSet options = new OptionSet
			{
				Format = "best",
				RecodeVideo = (VideoRecodeFormat)1
			};
			if (await ytdl.RunVideoPlaylistDownload(playlistUrl, (int?)startOfPlaylist.Value, (int?)endOfPlaylist.Value, (int[])null, "bestvideo+bestaudio/best", (VideoRecodeFormat)0, default(CancellationToken), (IProgress<DownloadProgress>)null, (IProgress<string>)null, options) == null)
			{
				HUDManager.Instance.DisplayTip("Download Error", "Failed to download the playlist.", false, false, "DownloadErrorTip");
				return;
			}
			HUDManager.Instance.DisplayTip("Download Complete", "The playlist has been successfully downloaded.", false, false, "DownloadCompleteTip");
			VideoManager.Videos.Clear();
			VideoManager.Load();
		}

		private async Task DownloadVideo(string videoUrl, string destinationFolder)
		{
			Log.LogInfo((object)"Downloading video...");
			if (skipDownloadEnabled.Value && await IsVideoAlreadyDownloaded(videoUrl, destinationFolder))
			{
				HUDManager.Instance.DisplayTip("Download Skipped", "The video is already downloaded.", false, false, "DownloadSkippedTip");
				return;
			}
			ytdl.OutputFolder = destinationFolder;
			OptionSet options = new OptionSet
			{
				Format = "best",
				RecodeVideo = (VideoRecodeFormat)1
			};
			if (await ytdl.RunVideoDownload(videoUrl, "bestvideo+bestaudio/best", (DownloadMergeFormat)0, (VideoRecodeFormat)0, default(CancellationToken), (IProgress<DownloadProgress>)null, (IProgress<string>)null, options) == null)
			{
				HUDManager.Instance.DisplayTip("Download Error", "Failed to download the video.", false, false, "DownloadErrorTip");
				return;
			}
			HUDManager.Instance.DisplayTip("Download Complete", "The video has been successfully downloaded.", false, false, "DownloadCompleteTip");
			VideoManager.Videos.Clear();
			VideoManager.Load();
		}

		private void OnDestroy()
		{
			downloadAction.Disable();
			Log.LogInfo((object)"YoutubeDownloader plugin destroyed or disabled. Download action disabled.");
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}