Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BoomboxYouTube v1.5.0
plugins/BoomboxYouTube.dll
Decompiled 4 months agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Logging; using BoomboxYouTubeMod; using GameNetcodeStuff; using HarmonyLib; using LethalCompanyInputUtils.Api; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.Networking; using UnityEngine.SceneManagement; using YoutubeExplode; using YoutubeExplode.Videos; using YoutubeExplode.Videos.Streams; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("BoomboxYouTube")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BoomboxYouTube")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("1fb03771-856b-48ae-8838-d36f13ee393c")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] public class BoomboxKeybinds : LcInputActions { public static BoomboxKeybinds Instance { get; private set; } [InputAction("<Keyboard>/f8", Name = "Toggle UI")] public InputAction ToggleUI { get; private set; } [InputAction("<Keyboard>/p", Name = "Play/Pause")] public InputAction PlayPause { get; private set; } [InputAction("<Keyboard>/o", Name = "Stop")] public InputAction Stop { get; private set; } [InputAction("<Keyboard>/]", Name = "Next Track")] public InputAction NextTrack { get; private set; } [InputAction("<Keyboard>/[", Name = "Previous Track")] public InputAction PreviousTrack { get; private set; } [InputAction("<Keyboard>/=", Name = "Volume Up")] public InputAction VolumeUp { get; private set; } [InputAction("<Keyboard>/-", Name = "Volume Down")] public InputAction VolumeDown { get; private set; } public BoomboxKeybinds() { Instance = this; } } [RequireComponent(typeof(NetworkObject))] [RequireComponent(typeof(AudioSource))] public class BoomboxNetwork : NetworkBehaviour { [CompilerGenerated] private sealed class <>c__DisplayClass7_0 { public Task<StreamManifest> manifestTask; public CancellationToken token; internal bool <Stream>b__0() { return manifestTask.IsCompleted || token.IsCancellationRequested; } } [CompilerGenerated] private sealed class <Stream>d__7 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string url; public CancellationToken token; public BoomboxNetwork <>4__this; private <>c__DisplayClass7_0 <>8__1; private StreamManifest <manifest>5__2; private IAudioStreamInfo <streamInfo>5__3; private Exception <ex>5__4; private IEnumerable<AudioOnlyStreamInfo> <audioStreams>5__5; private string <codec>5__6; private string <container>5__7; private double <bitrateKbps>5__8; private Exception <ex>5__9; private UnityWebRequest <www>5__10; private UnityWebRequestAsyncOperation <req>5__11; private AudioClip <clip>5__12; private Exception <ex>5__13; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <Stream>d__7(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 2) { try { } finally { <>m__Finally1(); } } <>8__1 = null; <manifest>5__2 = null; <streamInfo>5__3 = null; <ex>5__4 = null; <audioStreams>5__5 = null; <codec>5__6 = null; <container>5__7 = null; <ex>5__9 = null; <www>5__10 = null; <req>5__11 = null; <clip>5__12 = null; <ex>5__13 = null; <>1__state = -2; } private bool MoveNext() { //IL_047b: Unknown result type (might be due to invalid IL or missing references) //IL_0481: Invalid comparison between Unknown and I4 //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Expected O, but got Unknown //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_0342: Unknown result type (might be due to invalid IL or missing references) bool result; try { switch (<>1__state) { default: result = false; goto end_IL_0000; case 0: <>1__state = -1; <>8__1 = new <>c__DisplayClass7_0(); <>8__1.token = token; if (string.IsNullOrWhiteSpace(url)) { Debug.LogWarning((object)"[BoomboxNetwork] Provided URL is empty or null."); result = false; } else if ((Object)(object)BoomboxYouTube.Instance == (Object)null || BoomboxYouTube.Instance.Youtube == null) { Debug.LogError((object)"[BoomboxNetwork] Plugin/YouTube client not initialized yet."); result = false; } else { if (!((Object)(object)<>4__this._source == (Object)null)) { goto IL_0102; } <>4__this._source = ((Component)<>4__this).GetComponent<AudioSource>(); if (!((Object)(object)<>4__this._source == (Object)null)) { goto IL_0102; } Debug.LogError((object)"[BoomboxNetwork] AudioSource is missing—cannot play audio."); result = false; } goto end_IL_0000; case 1: <>1__state = -1; if (<>8__1.token.IsCancellationRequested) { result = false; } else if (<>8__1.manifestTask.IsFaulted) { Debug.LogError((object)$"[BoomboxNetwork] GetManifestAsync faulted: {<>8__1.manifestTask.Exception}"); result = false; } else { <manifest>5__2 = <>8__1.manifestTask.Result; if (<manifest>5__2 == null) { Debug.LogError((object)"[BoomboxNetwork] Manifest result is null."); result = false; } else { try { <audioStreams>5__5 = <manifest>5__2.GetAudioOnlyStreams(); <streamInfo>5__3 = (IAudioStreamInfo)(object)((from s in <audioStreams>5__5.Where(delegate(AudioOnlyStreamInfo s) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Container container2 = s.Container; return ((object)(Container)(ref container2)).ToString().IndexOf("aac", StringComparison.OrdinalIgnoreCase) >= 0; }) orderby s.Bitrate descending select s).FirstOrDefault() ?? <audioStreams>5__5.OrderByDescending((AudioOnlyStreamInfo s) => s.Bitrate).FirstOrDefault()); IAudioStreamInfo obj = <streamInfo>5__3; <codec>5__6 = ((obj != null) ? obj.AudioCodec.ToString() : null) ?? "<unknown>"; IAudioStreamInfo obj2 = <streamInfo>5__3; object obj3; if (obj2 == null) { obj3 = null; } else { Container container = ((IStreamInfo)obj2).Container; obj3 = ((Container)(ref container)).Name; } if (obj3 == null) { obj3 = "<unknown>"; } <container>5__7 = (string)obj3; double num; if (<streamInfo>5__3 == null) { num = 0.0; } else { Bitrate bitrate = ((IStreamInfo)<streamInfo>5__3).Bitrate; num = (double)((Bitrate)(ref bitrate)).BitsPerSecond / 1000.0; } <bitrateKbps>5__8 = num; Debug.Log((object)$"[BoomboxNetwork] Selected stream: codec={<codec>5__6}, container={<container>5__7}, bitrate={<bitrateKbps>5__8:F1} kbps"); <audioStreams>5__5 = null; <codec>5__6 = null; <container>5__7 = null; } catch (Exception ex) { <ex>5__9 = ex; Debug.LogError((object)$"[BoomboxNetwork] Failed to choose audio stream: {<ex>5__9}"); result = false; goto end_IL_0000; } if (<streamInfo>5__3 != null) { <www>5__10 = UnityWebRequestMultimedia.GetAudioClip(((IStreamInfo)<streamInfo>5__3).Url, (AudioType)13); <>1__state = -3; <req>5__11 = <www>5__10.SendWebRequest(); break; } Debug.LogError((object)"[BoomboxNetwork] No audio-only streams available for the provided URL."); result = false; } } goto end_IL_0000; case 2: { <>1__state = -3; break; } IL_0102: try { <>8__1.manifestTask = BoomboxYouTube.Instance.Youtube.Videos.Streams.GetManifestAsync(VideoId.op_Implicit(url), default(CancellationToken)).AsTask(); } catch (Exception ex) { <ex>5__4 = ex; Debug.LogError((object)$"[BoomboxNetwork] Failed to start GetManifestAsync: {<ex>5__4}"); result = false; goto end_IL_0000; } <>2__current = (object)new WaitUntil((Func<bool>)(() => <>8__1.manifestTask.IsCompleted || <>8__1.token.IsCancellationRequested)); <>1__state = 1; result = true; goto end_IL_0000; } if (!((AsyncOperation)<req>5__11).isDone) { if (<>8__1.token.IsCancellationRequested) { <www>5__10.Abort(); result = false; goto IL_058c; } <>2__current = null; <>1__state = 2; result = true; } else { if ((int)<www>5__10.result != 1) { Debug.LogError((object)("[BoomboxNetwork] Audio download failed: " + <www>5__10.error + ". Hint: If the stream is m4a/webm (AAC/Opus), Unity may not parse it without a decoder.")); result = false; goto IL_058c; } try { <clip>5__12 = DownloadHandlerAudioClip.GetContent(<www>5__10); } catch (Exception ex) { <ex>5__13 = ex; Debug.LogError((object)($"[BoomboxNetwork] Failed to parse audio clip: {<ex>5__13}. " + "This often happens when the container/codec isn't supported by Unity.")); result = false; goto IL_058c; } if ((Object)(object)<clip>5__12 == (Object)null) { Debug.LogError((object)"[BoomboxNetwork] Downloaded audio clip is null."); result = false; goto IL_058c; } <>4__this._source.Stop(); <>4__this._source.loop = true; <>4__this._source.clip = <clip>5__12; <>4__this._source.Play(); Debug.Log((object)"[BoomboxNetwork] Audio playback started."); <req>5__11 = null; <clip>5__12 = null; <>m__Finally1(); <www>5__10 = null; result = false; } goto end_IL_0000; IL_058c: <>m__Finally1(); end_IL_0000:; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<www>5__10 != null) { ((IDisposable)<www>5__10).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private AudioSource _source; private Coroutine _currentStreamRoutine; private CancellationTokenSource _cts; static BoomboxNetwork() { try { Environment.SetEnvironmentVariable("SLAVA_UKRAINI", "1", EnvironmentVariableTarget.Process); } catch { } } private void Awake() { try { Environment.SetEnvironmentVariable("SLAVA_UKRAINI", "1", EnvironmentVariableTarget.Process); string environmentVariable = Environment.GetEnvironmentVariable("SLAVA_UKRAINI", EnvironmentVariableTarget.Process); Debug.Log((object)("[BoomboxNetwork] Set 'SLAVA_UKRAINI'='" + (environmentVariable ?? "<null>") + "' (Process).")); } catch (Exception arg) { Debug.LogError((object)$"[BoomboxNetwork] Failed to set environment variable: {arg}"); } _source = ((Component)this).GetComponent<AudioSource>(); if ((Object)(object)_source == (Object)null) { Debug.LogWarning((object)"[BoomboxNetwork] No AudioSource found on GameObject. Audio playback will fail."); return; } _source.playOnAwake = false; _source.loop = false; } [ServerRpc(RequireOwnership = false)] public void PlayYouTubeServerRpc(string url) { NetworkObject component = ((Component)this).GetComponent<NetworkObject>(); if ((Object)(object)component == (Object)null || !component.IsSpawned) { Debug.LogError((object)"[BoomboxNetwork] NetworkObject not spawned—cannot route RPC."); } else { PlayYouTubeClientRpc(url); } } [ClientRpc] private void PlayYouTubeClientRpc(string url) { if (_currentStreamRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_currentStreamRoutine); _currentStreamRoutine = null; } _cts?.Cancel(); _cts = new CancellationTokenSource(); _currentStreamRoutine = ((MonoBehaviour)this).StartCoroutine(Stream(url, _cts.Token)); } [IteratorStateMachine(typeof(<Stream>d__7))] private IEnumerator Stream(string url, CancellationToken token) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <Stream>d__7(0) { <>4__this = this, url = url, token = token }; } public void StopPlayback() { if (_currentStreamRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_currentStreamRoutine); _currentStreamRoutine = null; } _cts?.Cancel(); if ((Object)(object)_source != (Object)null && _source.isPlaying) { _source.Stop(); _source.clip = null; } Debug.Log((object)"[BoomboxNetwork] Playback stopped."); } [ServerRpc(RequireOwnership = false)] public void PlayPauseServerRpc() { NetworkObject component = ((Component)this).GetComponent<NetworkObject>(); if ((Object)(object)component == (Object)null || !component.IsSpawned) { Debug.LogError((object)"[BoomboxNetwork] NetworkObject not spawned—cannot route PlayPauseServerRpc."); } else { PlayPauseClientRpc(); } } [ClientRpc] private void PlayPauseClientRpc() { if ((Object)(object)_source == (Object)null) { _source = ((Component)this).GetComponent<AudioSource>(); } if ((Object)(object)_source == (Object)null || (Object)(object)_source.clip == (Object)null) { Debug.LogWarning((object)"[BoomboxNetwork] PlayPauseClientRpc: No audio to play/pause."); } else if (_source.isPlaying) { _source.Pause(); Debug.Log((object)"[BoomboxNetwork] Audio paused."); } else { _source.Play(); Debug.Log((object)"[BoomboxNetwork] Audio resumed."); } } [ServerRpc(RequireOwnership = false)] public void StopServerRpc() { NetworkObject component = ((Component)this).GetComponent<NetworkObject>(); if ((Object)(object)component == (Object)null || !component.IsSpawned) { Debug.LogError((object)"[BoomboxNetwork] NetworkObject not spawned—cannot route StopServerRpc."); } else { StopClientRpc(); } } [ClientRpc] private void StopClientRpc() { StopPlayback(); } [ServerRpc(RequireOwnership = false)] public void AdjustVolumeServerRpc(float delta) { NetworkObject component = ((Component)this).GetComponent<NetworkObject>(); if ((Object)(object)component == (Object)null || !component.IsSpawned) { Debug.LogError((object)"[BoomboxNetwork] NetworkObject not spawned—cannot route AdjustVolumeServerRpc."); } else { AdjustVolumeClientRpc(delta); } } [ClientRpc] private void AdjustVolumeClientRpc(float delta) { if ((Object)(object)_source == (Object)null) { _source = ((Component)this).GetComponent<AudioSource>(); } if ((Object)(object)_source == (Object)null) { Debug.LogWarning((object)"[BoomboxNetwork] AdjustVolumeClientRpc: No AudioSource."); return; } float volume = _source.volume; _source.volume = Mathf.Clamp01(_source.volume + delta); Debug.Log((object)$"[BoomboxNetwork] Volume changed: {volume:F2} -> {_source.volume:F2}"); } [ServerRpc(RequireOwnership = false)] public void NextTrackServerRpc() { NetworkObject component = ((Component)this).GetComponent<NetworkObject>(); if ((Object)(object)component == (Object)null || !component.IsSpawned) { Debug.LogError((object)"[BoomboxNetwork] NetworkObject not spawned—cannot route NextTrackServerRpc."); } else { NextTrackClientRpc(); } } [ClientRpc] private void NextTrackClientRpc() { Debug.Log((object)"[BoomboxNetwork] NextTrackClientRpc called, but no playlist system is implemented yet."); } [ServerRpc(RequireOwnership = false)] public void PreviousTrackServerRpc() { NetworkObject component = ((Component)this).GetComponent<NetworkObject>(); if ((Object)(object)component == (Object)null || !component.IsSpawned) { Debug.LogError((object)"[BoomboxNetwork] NetworkObject not spawned—cannot route PreviousTrackServerRpc."); } else { PreviousTrackClientRpc(); } } [ClientRpc] private void PreviousTrackClientRpc() { Debug.Log((object)"[BoomboxNetwork] PreviousTrackClientRpc called, but no playlist system is implemented yet."); } } [HarmonyPatch(typeof(BoomboxItem))] internal class BoomboxPatch { [HarmonyPatch("StartMusic")] [HarmonyPrefix] private static bool DisableVanillaMusic() { return false; } [HarmonyPatch("Awake")] [HarmonyPostfix] private static void InjectNetworkEarly(BoomboxItem __instance) { try { GameObject val = ((__instance != null) ? ((Component)__instance).gameObject : null); BoomboxNetwork boomboxNetwork = default(BoomboxNetwork); if (!((Object)(object)val == (Object)null) && (!val.TryGetComponent<BoomboxNetwork>(ref boomboxNetwork) || (Object)(object)boomboxNetwork == (Object)null)) { boomboxNetwork = val.AddComponent<BoomboxNetwork>(); Debug.Log((object)"[BoomboxPatch] Injected BoomboxNetwork in Awake (pre-spawn)."); } } catch (Exception arg) { Debug.LogError((object)$"[BoomboxPatch] Failed to inject BoomboxNetwork in Awake: {arg}"); } } [HarmonyPatch("EquipItem")] [HarmonyPostfix] private static void InjectNetworkOnEquip(BoomboxItem __instance) { try { if ((Object)(object)__instance == (Object)null) { Debug.LogWarning((object)"[BoomboxPatch] EquipItem postfix: __instance is null."); return; } GameObject gameObject = ((Component)__instance).gameObject; BoomboxNetwork boomboxNetwork = default(BoomboxNetwork); if ((Object)(object)gameObject == (Object)null) { Debug.LogWarning((object)"[BoomboxPatch] EquipItem postfix: BoomboxItem GameObject is null."); } else if (!gameObject.TryGetComponent<BoomboxNetwork>(ref boomboxNetwork) || (Object)(object)boomboxNetwork == (Object)null) { boomboxNetwork = gameObject.AddComponent<BoomboxNetwork>(); Debug.Log((object)"[BoomboxPatch] Injected BoomboxNetwork on EquipItem (fallback)."); } } catch (Exception arg) { Debug.LogError((object)$"[BoomboxPatch] Failed to inject BoomboxNetwork on Equip: {arg}"); } } } public class BoomboxUI : MonoBehaviour { private Rect _window = new Rect(20f, 20f, 420f, 200f); private bool _showWindow = true; private bool _justOpened = false; private const int WindowId = 180015745; private string _ytUrl = string.Empty; private BoomboxNetwork _activeBox; private void Awake() { BoomboxUI[] array = Object.FindObjectsOfType<BoomboxUI>(true); if (array.Length > 1) { for (int i = 1; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null && (Object)(object)array[i] != (Object)(object)array[0]) { Object.Destroy((Object)(object)((Component)array[i]).gameObject); } } if ((Object)(object)this != (Object)(object)array[0]) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } } Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); SceneManager.activeSceneChanged += OnSceneChanged; } private void OnDestroy() { SceneManager.activeSceneChanged -= OnSceneChanged; } private void OnSceneChanged(Scene oldScene, Scene newScene) { ResolveActiveBoombox(); } public void ToggleWindow() { _showWindow = !_showWindow; _justOpened = _showWindow; Debug.Log((object)("[BoomboxUI] Window toggled: " + (_showWindow ? "shown" : "hidden"))); } public void ShowWindow() { _showWindow = true; _justOpened = true; } public void HideWindow() { _showWindow = false; _justOpened = false; } private void OnGUI() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (_showWindow) { _window = GUI.Window(180015745, _window, new WindowFunction(DrawWindow), "Boombox YouTube"); ((Rect)(ref _window)).x = Mathf.Clamp(((Rect)(ref _window)).x, 0f, (float)Screen.width - ((Rect)(ref _window)).width); ((Rect)(ref _window)).y = Mathf.Clamp(((Rect)(ref _window)).y, 0f, (float)Screen.height - ((Rect)(ref _window)).height); } } private void DrawWindow(int id) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Invalid comparison between Unknown and I4 //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Expected O, but got Unknown //IL_0219: Unknown result type (might be due to invalid IL or missing references) float num = 24f; Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref _window)).width - 28f, 2f, 24f, 20f); if (GUI.Button(val, "X")) { ToggleWindow(); return; } GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); GUILayout.Label("YouTube URL:", Array.Empty<GUILayoutOption>()); GUI.SetNextControlName("ytField"); _ytUrl = GUILayout.TextField(_ytUrl, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinWidth(280f) }); if (_justOpened && (int)Event.current.type == 7) { GUI.FocusControl("ytField"); _justOpened = false; } GUILayout.Space(6f); Color color = GUI.color; if ((Object)(object)_activeBox == (Object)null) { GUI.color = Color.yellow; GUILayout.Label("No Boombox selected. Hold a Boombox to link.", Array.Empty<GUILayoutOption>()); } else { GUI.color = color; GUILayout.Label("Boombox connected.", Array.Empty<GUILayoutOption>()); } GUI.color = color; GUILayout.Space(4f); HorizontalScope val2 = new HorizontalScope(Array.Empty<GUILayoutOption>()); try { if (GUILayout.Button("Play", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f) })) { TryPlayUrl(); } if (GUILayout.Button("Stop", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(80f), GUILayout.Height(26f) })) { StopActiveBoombox(); } if (GUILayout.Button(_showWindow ? "Hide" : "Show", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(80f), GUILayout.Height(26f) })) { ToggleWindow(); } } finally { ((IDisposable)val2)?.Dispose(); } GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _window)).width, num)); } private void Update() { ResolveActiveBoombox(); } private void ResolveActiveBoombox() { try { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { _activeBox = null; return; } GrabbableObject val2 = val.currentlyHeldObjectServer ?? val.currentlyHeldObject; BoomboxItem val3 = (BoomboxItem)(object)((val2 is BoomboxItem) ? val2 : null); if (val3 != null) { BoomboxNetwork boomboxNetwork = default(BoomboxNetwork); if (!((Component)val3).gameObject.TryGetComponent<BoomboxNetwork>(ref boomboxNetwork) || (Object)(object)boomboxNetwork == (Object)null) { boomboxNetwork = ((Component)val3).gameObject.AddComponent<BoomboxNetwork>(); } _activeBox = boomboxNetwork; } else { _activeBox = null; } } catch (Exception ex) { Debug.LogWarning((object)("[BoomboxUI] Failed to resolve active boombox: " + ex.Message)); _activeBox = null; } } private static bool LooksLikeYouTube(string url) { if (string.IsNullOrWhiteSpace(url)) { return false; } if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return false; } string text = result.Host.ToLowerInvariant(); if (!text.Contains("youtube.com") && !text.Contains("youtu.be")) { return false; } return result.Scheme == Uri.UriSchemeHttp || result.Scheme == Uri.UriSchemeHttps; } private bool HasRpcAuthority() { return true; } private void TryPlayUrl() { if ((Object)(object)_activeBox == (Object)null) { Debug.LogWarning((object)"[BoomboxUI] No active boombox to play on."); return; } if (!HasRpcAuthority()) { Debug.LogWarning((object)"[BoomboxUI] No RPC authority."); return; } string text = _ytUrl?.Trim(); if (string.IsNullOrEmpty(text)) { Debug.LogWarning((object)"[BoomboxUI] URL is empty."); return; } if (!LooksLikeYouTube(text)) { Debug.LogWarning((object)"[BoomboxUI] URL does not look like a valid YouTube link."); } try { _activeBox.PlayYouTubeServerRpc(text); Debug.Log((object)("[BoomboxUI] Requested playback for URL: " + text)); } catch (Exception arg) { Debug.LogError((object)$"[BoomboxUI] Failed to send PlayYouTubeServerRpc: {arg}"); } } public void PlayPauseActiveBoombox() { if ((Object)(object)_activeBox == (Object)null) { return; } if (!HasRpcAuthority()) { Debug.LogWarning((object)"[BoomboxUI] PlayPause: No RPC authority."); return; } try { _activeBox.PlayPauseServerRpc(); } catch (Exception arg) { Debug.LogError((object)$"[BoomboxUI] PlayPauseServerRpc failed: {arg}"); } } public void StopActiveBoombox() { if ((Object)(object)_activeBox == (Object)null) { return; } if (!HasRpcAuthority()) { Debug.LogWarning((object)"[BoomboxUI] Stop: No RPC authority."); return; } try { _activeBox.StopServerRpc(); } catch (Exception arg) { Debug.LogError((object)$"[BoomboxUI] StopServerRpc failed: {arg}"); } } public void NextTrackActiveBoombox() { if ((Object)(object)_activeBox == (Object)null) { return; } if (!HasRpcAuthority()) { Debug.LogWarning((object)"[BoomboxUI] NextTrack: No RPC authority."); return; } try { _activeBox.NextTrackServerRpc(); } catch (Exception arg) { Debug.LogError((object)$"[BoomboxUI] NextTrackServerRpc failed: {arg}"); } } public void PreviousTrackActiveBoombox() { if ((Object)(object)_activeBox == (Object)null) { return; } if (!HasRpcAuthority()) { Debug.LogWarning((object)"[BoomboxUI] PreviousTrack: No RPC authority."); return; } try { _activeBox.PreviousTrackServerRpc(); } catch (Exception arg) { Debug.LogError((object)$"[BoomboxUI] PreviousTrackServerRpc failed: {arg}"); } } public void AdjustVolumeActiveBoombox(float delta) { if ((Object)(object)_activeBox == (Object)null) { return; } if (!HasRpcAuthority()) { Debug.LogWarning((object)"[BoomboxUI] Volume: No RPC authority."); return; } try { _activeBox.AdjustVolumeServerRpc(delta); } catch (Exception arg) { Debug.LogError((object)$"[BoomboxUI] AdjustVolumeServerRpc failed: {arg}"); } } } namespace BoomboxYouTubeMod; [BepInPlugin("blaide.boomboxyoutube", "Boombox YouTube", "1.5.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class BoomboxYouTube : BaseUnityPlugin { internal static ManualLogSource Log; private BoomboxUI _ui; public static BoomboxYouTube Instance { get; private set; } public YoutubeClient Youtube { get; private set; } static BoomboxYouTube() { try { Environment.SetEnvironmentVariable("SLAVA_UKRAINI", "1", EnvironmentVariableTarget.Process); } catch { } } private void Awake() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; Instance = this; _ = BoomboxKeybinds.Instance; try { Youtube = new YoutubeClient(); Log.LogInfo((object)"[BoomboxYouTube] YoutubeClient created."); } catch (Exception arg) { Log.LogError((object)$"[BoomboxYouTube] YoutubeClient creation failed: {arg}"); } try { new Harmony("blaide.boomboxyoutube").PatchAll(); Log.LogInfo((object)"[BoomboxYouTube] Harmony patches applied."); } catch (Exception arg2) { Log.LogError((object)$"[BoomboxYouTube] Harmony patching failed: {arg2}"); } EnsureUiInstance(); Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); Log.LogInfo((object)"Boombox YouTube loaded."); } private void Update() { BoomboxKeybinds instance = BoomboxKeybinds.Instance; if (instance == null) { return; } if (instance.ToggleUI.WasPressedThisFrame()) { if ((Object)(object)_ui == (Object)null) { EnsureUiInstance(); } if ((Object)(object)_ui != (Object)null && !TryToggleUiViaMethod(_ui)) { TryToggleUiViaField(_ui); } } if (!((Object)(object)_ui == (Object)null)) { if (instance.PlayPause.WasPressedThisFrame()) { _ui.PlayPauseActiveBoombox(); } if (instance.Stop.WasPressedThisFrame()) { _ui.StopActiveBoombox(); } if (instance.NextTrack.WasPressedThisFrame()) { _ui.NextTrackActiveBoombox(); } if (instance.PreviousTrack.WasPressedThisFrame()) { _ui.PreviousTrackActiveBoombox(); } if (instance.VolumeUp.WasPressedThisFrame()) { _ui.AdjustVolumeActiveBoombox(0.1f); } if (instance.VolumeDown.WasPressedThisFrame()) { _ui.AdjustVolumeActiveBoombox(-0.1f); } } } private void EnsureUiInstance() { try { BoomboxUI[] array = Object.FindObjectsOfType<BoomboxUI>(true); if (array != null && array.Length != 0) { _ui = array[0]; for (int i = 1; i < array.Length; i++) { Object.Destroy((Object)(object)array[i]); } Log.LogInfo((object)"[BoomboxYouTube] Reusing existing BoomboxUI instance."); } else { _ui = ((Component)this).gameObject.AddComponent<BoomboxUI>(); Log.LogInfo((object)"[BoomboxYouTube] BoomboxUI added to plugin GameObject."); } if ((Object)(object)_ui != (Object)null) { Object.DontDestroyOnLoad((Object)(object)((Component)_ui).gameObject); } } catch (Exception arg) { Log.LogWarning((object)$"[BoomboxYouTube] EnsureUiInstance failed: {arg}"); } } private bool TryToggleUiViaMethod(BoomboxUI ui) { try { MethodInfo method = ((object)ui).GetType().GetMethod("ToggleWindow", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { method.Invoke(ui, null); Log.LogDebug((object)"[BoomboxYouTube] Toggled UI via ToggleWindow()."); return true; } } catch (Exception ex) { Log.LogWarning((object)("[BoomboxYouTube] ToggleWindow invocation failed: " + ex.Message)); } return false; } private bool TryToggleUiViaField(BoomboxUI ui) { try { FieldInfo fieldInfo = ((object)ui).GetType().GetField("_showWindow", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? ((object)ui).GetType().GetField("showWindow", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo != null && fieldInfo.FieldType == typeof(bool)) { bool flag = (bool)fieldInfo.GetValue(ui); fieldInfo.SetValue(ui, !flag); Log.LogDebug((object)$"[BoomboxYouTube] Toggled UI by flipping field '{fieldInfo.Name}': {flag} -> {!flag}"); return true; } } catch (Exception ex) { Log.LogWarning((object)("[BoomboxYouTube] Field-based toggle failed: " + ex.Message)); } Log.LogWarning((object)"[BoomboxYouTube] UI toggle failed—add a public ToggleWindow() method or a bool '_showWindow' field to BoomboxUI."); return false; } private void OnDestroy() { try { } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)$"[BoomboxYouTube] Cleanup failed: {arg}"); } } } }
plugins/YoutubeExplode.dll
Decompiled 4 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Sources; using System.Xml.Linq; using AngleSharp.Dom; using AngleSharp.Html.Dom; using AngleSharp.Html.Parser; using Deorcify; using Microsoft.CodeAnalysis; using YoutubeExplode.Bridge; using YoutubeExplode.Bridge.Cipher; using YoutubeExplode.Channels; using YoutubeExplode.Common; using YoutubeExplode.Exceptions; using YoutubeExplode.Playlists; using YoutubeExplode.Search; using YoutubeExplode.Utils; using YoutubeExplode.Utils.Extensions; using YoutubeExplode.Videos; using YoutubeExplode.Videos.ClosedCaptions; using YoutubeExplode.Videos.Streams; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("Tyrrrz")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (C) Oleksii Holub")] [assembly: AssemblyDescription("Abstraction layer over YouTube's internal API. Note: this package has limited availability in Russia and Belarus.")] [assembly: AssemblyFileVersion("6.5.6.0")] [assembly: AssemblyInformationalVersion("6.5.6+b0f37bcc16cb8210130887c40c796f82b4af2357")] [assembly: AssemblyProduct("YoutubeExplode")] [assembly: AssemblyTitle("YoutubeExplode")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/Tyrrrz/YoutubeExplode")] [assembly: AssemblyVersion("6.5.6.0")] [module: RefSafetyRules(11)] internal class <Module> { static <Module>() { Initializer.Execute(); } } namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class IsByRefLikeAttribute : Attribute { } [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; } } [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 NativeIntegerAttribute : Attribute { public readonly bool[] TransformFlags; public NativeIntegerAttribute() { TransformFlags = new bool[1] { true }; } public NativeIntegerAttribute(bool[] P_0) { TransformFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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; } } } [ExcludeFromCodeCoverage] internal static class PolyfillExtensions { public static async Task<Stream> GetStreamAsync(this HttpClient httpClient, string requestUri, CancellationToken cancellationToken = default(CancellationToken)) { _ = 1; try { HttpResponseMessage obj = await httpClient.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); obj.EnsureSuccessStatusCode(); return await ReadAsStreamAsync(obj.Content, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } catch (OperationCanceledException ex) when (ex.CancellationToken != cancellationToken && cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(ex.Message, ex.InnerException, cancellationToken); } } public static async Task<Stream> GetStreamAsync(this HttpClient httpClient, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken)) { return await GetStreamAsync(httpClient, requestUri.ToString(), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static async Task<byte[]> GetByteArrayAsync(this HttpClient httpClient, string requestUri, CancellationToken cancellationToken = default(CancellationToken)) { _ = 1; try { using HttpResponseMessage response = await httpClient.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); response.EnsureSuccessStatusCode(); return await ReadAsByteArrayAsync(response.Content, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } catch (OperationCanceledException ex) when (ex.CancellationToken != cancellationToken && cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(ex.Message, ex.InnerException, cancellationToken); } } public static async Task<byte[]> GetByteArrayAsync(this HttpClient httpClient, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken)) { return await GetByteArrayAsync(httpClient, requestUri.ToString(), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static async Task<string> GetStringAsync(this HttpClient httpClient, string requestUri, CancellationToken cancellationToken = default(CancellationToken)) { _ = 1; try { using HttpResponseMessage response = await httpClient.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); response.EnsureSuccessStatusCode(); return await ReadAsStringAsync(response.Content, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } catch (OperationCanceledException ex) when (ex.CancellationToken != cancellationToken && cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(ex.Message, ex.InnerException, cancellationToken); } } public static async Task<string> GetStringAsync(this HttpClient httpClient, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken)) { return await GetStringAsync(httpClient, requestUri.ToString(), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static async Task<Stream> ReadAsStreamAsync(this HttpContent httpContent, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); return await httpContent.ReadAsStreamAsync().ConfigureAwait(continueOnCapturedContext: false); } public static async Task<byte[]> ReadAsByteArrayAsync(this HttpContent httpContent, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); return await httpContent.ReadAsByteArrayAsync().ConfigureAwait(continueOnCapturedContext: false); } public static async Task<string> ReadAsStringAsync(this HttpContent httpContent, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); return await httpContent.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); } public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken)) { TaskCompletionSource<object?> tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); try { process.EnableRaisingEvents = true; } catch when (process.HasExited) { return; } process.Exited += HandleExited; try { using (cancellationToken.Register(delegate { tcs.TrySetCanceled(cancellationToken); })) { await tcs.Task; } } finally { process.Exited -= HandleExited; } void HandleExited(object? sender, EventArgs args) { tcs.TrySetResult(null); } } public static bool IsAssignableTo(this Type type, Type? otherType) { return otherType?.IsAssignableFrom(type) ?? false; } public static string ReplaceLineEndings(this string str, string replacementText) { return Replace(Replace(Replace(str, "\r\n", "\n", StringComparison.Ordinal), "\r", "\n", StringComparison.Ordinal), "\n", replacementText, StringComparison.Ordinal); } public static string ReplaceLineEndings(this string str) { return ReplaceLineEndings(str, Environment.NewLine); } public static async Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken) { Task cancellationTask = Task.Delay(timeout, cancellationToken); Task finishedTask = await Task.WhenAny(new Task[2] { task, cancellationTask }).ConfigureAwait(continueOnCapturedContext: false); await finishedTask.ConfigureAwait(continueOnCapturedContext: false); if (finishedTask == cancellationTask) { throw new TimeoutException("The operation has timed out."); } } public static async Task WaitAsync(this Task task, CancellationToken cancellationToken) { await WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static async Task WaitAsync(this Task task, TimeSpan timeout) { await WaitAsync(task, timeout, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false); } public static async Task<T> WaitAsync<T>(this Task<T> task, TimeSpan timeout, CancellationToken cancellationToken) { Task cancellationTask = Task.Delay(timeout, cancellationToken); Task finishedTask = await Task.WhenAny(new Task[2] { task, cancellationTask }).ConfigureAwait(continueOnCapturedContext: false); await finishedTask.ConfigureAwait(continueOnCapturedContext: false); if (finishedTask == cancellationTask) { throw new TimeoutException("The operation has timed out."); } return task.Result; } public static async Task<T> WaitAsync<T>(this Task<T> task, CancellationToken cancellationToken) { return await WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static async Task<T> WaitAsync<T>(this Task<T> task, TimeSpan timeout) { return await WaitAsync(task, timeout, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false); } public static int ReadAtLeast(this Stream stream, byte[] buffer, int minimumBytes, bool throwOnEndOfStream = true) { int i; int num; for (i = 0; i < buffer.Length; i += num) { num = stream.Read(buffer, i, Math.Min(minimumBytes, buffer.Length - i)); if (num <= 0) { break; } } if (i < minimumBytes && throwOnEndOfStream) { throw new EndOfStreamException(); } return i; } public static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) { int num; for (int i = 0; i < count; i += num) { num = stream.Read(buffer, offset + i, count - i); if (num <= 0) { throw new EndOfStreamException(); } } } public static void ReadExactly(this Stream stream, byte[] buffer) { stream.ReadExactly(buffer, 0, buffer.Length); } public static async Task<int> ReadAtLeastAsync(this Stream stream, byte[] buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default(CancellationToken)) { int totalBytesRead; int num; for (totalBytesRead = 0; totalBytesRead < buffer.Length; totalBytesRead += num) { num = await stream.ReadAsync(buffer, totalBytesRead, Math.Min(minimumBytes, buffer.Length - totalBytesRead), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (num <= 0) { break; } } if (totalBytesRead < minimumBytes && throwOnEndOfStream) { throw new EndOfStreamException(); } return totalBytesRead; } public static async Task ReadExactlyAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken = default(CancellationToken)) { int num; for (int totalBytesRead = 0; totalBytesRead < count; totalBytesRead += num) { num = await stream.ReadAsync(buffer, offset + totalBytesRead, count - totalBytesRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (num <= 0) { throw new EndOfStreamException(); } } } public static async Task ReadExactlyAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default(CancellationToken)) { await stream.ReadExactlyAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static int ReadAtLeast(this Stream stream, Span<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true) { int i; int num; for (i = 0; i < buffer.Length; i += num) { num = Read(stream, buffer.Slice(i)); if (num <= 0) { break; } } if (i < minimumBytes && throwOnEndOfStream) { throw new EndOfStreamException(); } return i; } public static void ReadExactly(this Stream stream, Span<byte> buffer) { byte[] array = buffer.ToArray(); stream.ReadExactly(array, 0, array.Length); array.CopyTo(buffer); } public static async Task<int> ReadAtLeastAsync(this Stream stream, Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default(CancellationToken)) { int totalBytesRead; int num; for (totalBytesRead = 0; totalBytesRead < buffer.Length; totalBytesRead += num) { num = await ReadAsync(stream, buffer.Slice(totalBytesRead), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (num <= 0) { break; } } if (totalBytesRead < minimumBytes && throwOnEndOfStream) { throw new EndOfStreamException(); } return totalBytesRead; } public static async Task ReadExactlyAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken)) { byte[] bufferArray = buffer.ToArray(); await stream.ReadExactlyAsync(bufferArray, 0, bufferArray.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); bufferArray.CopyTo(buffer); } public static Task CancelAsync(this CancellationTokenSource cts) { cts.Cancel(); return Task.CompletedTask; } public static void Deconstruct(this DictionaryEntry entry, out object key, out object? value) { key = entry.Key; value = entry.Value; } public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pair, out TKey key, out TValue value) { key = pair.Key; value = pair.Value; } public static IEnumerable<Match> AsEnumerable(this MatchCollection matchCollection) { return matchCollection.Cast<Match>(); } public static IEnumerator<Match> GetEnumerator(this MatchCollection matchCollection) { return matchCollection.AsEnumerable().GetEnumerator(); } public static Match[] ToArray(this MatchCollection matchCollection) { return matchCollection.AsEnumerable().ToArray(); } public static bool StartsWith(this string str, char c) { if (str.Length > 0) { return str[0] == c; } return false; } public static bool EndsWith(this string str, char c) { if (str.Length > 0) { return str[str.Length - 1] == c; } return false; } public static bool Contains(this string str, char c) { return str.IndexOf(c) >= 0; } public static string Replace(this string str, string oldValue, string? newValue, StringComparison comparison) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; int num2 = 0; while (true) { num = str.IndexOf(oldValue, num, comparison); if (num < 0) { break; } stringBuilder.Append(str, num2, num - num2); stringBuilder.Append(newValue); num += oldValue.Length; num2 = num; } stringBuilder.Append(str, num2, str.Length - num2); return stringBuilder.ToString(); } public static string Replace(this string str, string oldValue, string? newValue, bool ignoreCase, CultureInfo? culture) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; int num2 = 0; while (true) { num = (culture ?? CultureInfo.CurrentCulture).CompareInfo.IndexOf(str, oldValue, num, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); if (num < 0) { break; } stringBuilder.Append(str, num2, num - num2); stringBuilder.Append(newValue); num += oldValue.Length; num2 = num; } stringBuilder.Append(str, num2, str.Length - num2); return stringBuilder.ToString(); } public static string[] Split(this string str, char separator, int count, StringSplitOptions options = StringSplitOptions.None) { return str.Split(new char[1] { separator }, count, options); } public static string[] Split(this string str, char separator, StringSplitOptions options = StringSplitOptions.None) { return str.Split(new char[1] { separator }, options); } public static string[] Split(this string str, string? separator, int count, StringSplitOptions options = StringSplitOptions.None) { return str.Split(new string[1] { separator ?? "" }, count, options); } public static string[] Split(this string str, string? separator, StringSplitOptions options = StringSplitOptions.None) { return str.Split(new string[1] { separator ?? "" }, options); } public static void NextBytes(this Random random, Span<byte> buffer) { byte[] array = buffer.ToArray(); random.NextBytes(array); array.CopyTo(buffer); } public static int Read(this Stream stream, byte[] buffer) { return stream.Read(buffer, 0, buffer.Length); } public static void Write(this Stream stream, byte[] buffer) { stream.Write(buffer, 0, buffer.Length); } public static async Task CopyToAsync(this Stream stream, Stream destination, CancellationToken cancellationToken = default(CancellationToken)) { await stream.CopyToAsync(destination, 81920, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static async Task<int> ReadAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default(CancellationToken)) { return await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static async Task WriteAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default(CancellationToken)) { await stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static int Read(this Stream stream, Span<byte> buffer) { byte[] array = buffer.ToArray(); int result = stream.Read(array, 0, array.Length); array.CopyTo(buffer); return result; } public static void Write(this Stream stream, ReadOnlySpan<byte> buffer) { byte[] array = buffer.ToArray(); stream.Write(array, 0, array.Length); } public static async Task<int> ReadAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken)) { byte[] bufferArray = buffer.ToArray(); int result = await stream.ReadAsync(bufferArray, 0, bufferArray.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); bufferArray.CopyTo(buffer); return result; } public static async Task WriteAsync(this Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken)) { byte[] array = buffer.ToArray(); await stream.WriteAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public static int Read(this StreamReader reader, char[] buffer) { return reader.Read(buffer, 0, buffer.Length); } public static async Task<int> ReadAsync(this StreamReader reader, char[] buffer, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); return await reader.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(continueOnCapturedContext: false); } public static int Read(this StreamReader reader, Span<char> buffer) { char[] array = buffer.ToArray(); int result = reader.Read(array, 0, array.Length); array.CopyTo(buffer); return result; } public static async Task<int> ReadAsync(this StreamReader reader, Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) { char[] bufferArray = buffer.ToArray(); cancellationToken.ThrowIfCancellationRequested(); int result = await reader.ReadAsync(bufferArray, 0, bufferArray.Length).ConfigureAwait(continueOnCapturedContext: false); bufferArray.CopyTo(buffer); return result; } public static void Write(this StreamWriter writer, ReadOnlySpan<char> buffer) { char[] array = buffer.ToArray(); writer.Write(array, 0, array.Length); } public static async Task WriteAsync(this StreamWriter writer, Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) { char[] array = buffer.ToArray(); cancellationToken.ThrowIfCancellationRequested(); await writer.WriteAsync(array, 0, array.Length).ConfigureAwait(continueOnCapturedContext: false); } public static bool Contains(this string str, char c, StringComparison comparison) { return Contains(str, c.ToString(), comparison); } public static bool Contains(this string str, string sub, StringComparison comparison) { return str.IndexOf(sub, comparison) >= 0; } } namespace YoutubeExplode { public class YoutubeClient : IDisposable { private readonly HttpClient _youtubeHttp; public VideoClient Videos { get; } public PlaylistClient Playlists { get; } public ChannelClient Channels { get; } public SearchClient Search { get; } public YoutubeClient(HttpClient http, IReadOnlyList<Cookie> initialCookies) { _youtubeHttp = new HttpClient(new YoutubeHttpHandler(http, initialCookies), disposeHandler: true); Videos = new VideoClient(_youtubeHttp); Playlists = new PlaylistClient(_youtubeHttp); Channels = new ChannelClient(_youtubeHttp); Search = new SearchClient(_youtubeHttp); } public YoutubeClient(HttpClient http) : this(http, Array.Empty<Cookie>()) { } public YoutubeClient(IReadOnlyList<Cookie> initialCookies) : this(Http.Client, initialCookies) { } public YoutubeClient() : this(Http.Client) { } public void Dispose() { _youtubeHttp.Dispose(); } } internal class YoutubeHttpHandler : ClientDelegatingHandler { private readonly CookieContainer _cookieContainer = new CookieContainer(); public YoutubeHttpHandler(HttpClient http, IReadOnlyList<Cookie> initialCookies, bool disposeClient = false) : base(http, disposeClient) { _cookieContainer.Add(new Cookie("SOCS", "CAISEwgDEgk4MTM4MzYzNTIaAmVuIAEaBgiApPzGBg") { Domain = "youtube.com" }); foreach (Cookie initialCookie in initialCookies) { _cookieContainer.Add(initialCookie); } } private string? TryGenerateAuthHeaderValue(Uri uri) { Cookie[] source = _cookieContainer.GetCookies(uri).Cast<Cookie>().ToArray(); string text = source.FirstOrDefault((Cookie c) => string.Equals(c.Name, "__Secure-3PAPISID", StringComparison.Ordinal))?.Value ?? source.FirstOrDefault((Cookie c) => string.Equals(c.Name, "SAPISID", StringComparison.Ordinal))?.Value; if (string.IsNullOrWhiteSpace(text)) { return null; } long num = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); string domain = uri.GetDomain(); string s = $"{num} {text} {domain}"; string arg = Hash.Compute(SHA1.Create(), Encoding.UTF8.GetBytes(s)).ToHex(); return $"SAPISIDHASH {num}_{arg}"; } private HttpRequestMessage HandleRequest(HttpRequestMessage request) { if ((object)request.RequestUri == null) { return request; } if (request.RequestUri.AbsolutePath.StartsWith("/youtubei/", StringComparison.Ordinal) && !UrlEx.ContainsQueryParameter(request.RequestUri.Query, "key")) { request.RequestUri = new Uri(UrlEx.SetQueryParameter(request.RequestUri.OriginalString, "key", "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w")); } if (!UrlEx.ContainsQueryParameter(request.RequestUri.Query, "hl")) { request.RequestUri = new Uri(UrlEx.SetQueryParameter(request.RequestUri.OriginalString, "hl", "en")); } if (!request.Headers.Contains("Origin")) { request.Headers.Add("Origin", request.RequestUri.GetDomain()); } if (!request.Headers.Contains("User-Agent")) { request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"); } if (!request.Headers.Contains("Cookie") && _cookieContainer.Count > 0) { string cookieHeader = _cookieContainer.GetCookieHeader(request.RequestUri); if (!string.IsNullOrWhiteSpace(cookieHeader)) { request.Headers.Add("Cookie", cookieHeader); } } if (!request.Headers.Contains("Authorization")) { string text = TryGenerateAuthHeaderValue(request.RequestUri); if (text != null) { request.Headers.Add("Authorization", text); } } return request; } private HttpResponseMessage HandleResponse(HttpResponseMessage response) { if ((object)response.RequestMessage?.RequestUri == null) { return response; } if (response.StatusCode == HttpStatusCode.TooManyRequests) { throw new RequestLimitExceededException("Exceeded request rate limit. Please try again in a few hours. Alternatively, inject cookies corresponding to a pre-authenticated user when initializing an instance of `YoutubeClient`."); } if (response.Headers.TryGetValues("Set-Cookie", out IEnumerable<string> values)) { foreach (string item in values) { try { _cookieContainer.SetCookies(response.RequestMessage.RequestUri, item); } catch (CookieException) { } } } return response; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { int retriesRemaining = 5; HttpResponseMessage httpResponseMessage; while (true) { httpResponseMessage = HandleResponse(await base.SendAsync(HandleRequest(request), cancellationToken)); if (httpResponseMessage.StatusCode < HttpStatusCode.InternalServerError || retriesRemaining <= 0) { break; } httpResponseMessage.Dispose(); retriesRemaining--; } return httpResponseMessage; } } } namespace YoutubeExplode.Utils { internal abstract class ClientDelegatingHandler : HttpMessageHandler { [CompilerGenerated] private HttpClient <http>P; [CompilerGenerated] private bool <disposeClient>P; protected ClientDelegatingHandler(HttpClient http, bool disposeClient = false) { <http>P = http; <disposeClient>P = disposeClient; base..ctor(); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { using HttpRequestMessage clonedRequest = request.Clone(); return await <http>P.SendAsync(clonedRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken); } protected override void Dispose(bool disposing) { if (disposing && <disposeClient>P) { <http>P.Dispose(); } base.Dispose(disposing); } } internal static class Hash { public static byte[] Compute(HashAlgorithm algorithm, byte[] data) { using (algorithm) { return algorithm.ComputeHash(data); } } } internal static class Html { private static readonly HtmlParser HtmlParser = new HtmlParser(); public static IHtmlDocument Parse(string source) { return HtmlParser.ParseDocument(source); } } internal static class Http { private static readonly Lazy<HttpClient> HttpClientLazy = new Lazy<HttpClient>(() => new HttpClient()); public static HttpClient Client => HttpClientLazy.Value; } internal static class Json { public static string Extract(string source) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; bool flag = false; foreach (var item3 in source.Index()) { int item = item3.index; char item2 = item3.value; char c = ((item > 0) ? source[item - 1] : '\0'); stringBuilder.Append(item2); if (item2 == '"' && c != '\\') { flag = !flag; } else if (item2 == '{' && !flag) { num++; } else if (item2 == '}' && !flag) { num--; } if (num == 0) { break; } } return stringBuilder.ToString(); } public static JsonElement Parse(string source) { using JsonDocument jsonDocument = JsonDocument.Parse(source); return jsonDocument.RootElement.Clone(); } public static JsonElement? TryParse(string source) { try { return Parse(source); } catch (JsonException) { return null; } } public static string Encode(string value) { StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { switch (c) { case '\n': stringBuilder.Append("\\n"); break; case '\r': stringBuilder.Append("\\r"); break; case '\t': stringBuilder.Append("\\t"); break; case '\\': stringBuilder.Append("\\\\"); break; case '"': stringBuilder.Append("\\\""); break; default: stringBuilder.Append(c); break; } } return stringBuilder.ToString(); } public static string Serialize(string? value) { if (value == null) { return "null"; } return "\"" + Encode(value) + "\""; } public static string Serialize(int? value) { if (!value.HasValue) { return "null"; } return value.Value.ToString(CultureInfo.InvariantCulture); } } internal static class UrlEx { [CompilerGenerated] private sealed class <EnumerateQueryParameters>d__0 : IEnumerable<KeyValuePair<string, string>>, IEnumerable, IEnumerator<KeyValuePair<string, string>>, IEnumerator, IDisposable { private int <>1__state; private KeyValuePair<string, string> <>2__current; private int <>l__initialThreadId; private string url; public string <>3__url; private string[] <>7__wrap1; private int <>7__wrap2; KeyValuePair<string, string> IEnumerator<KeyValuePair<string, string>>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <EnumerateQueryParameters>d__0(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_00b8; } <>1__state = -1; string text = (PolyfillExtensions.Contains(url, '?') ? url.SubstringAfter("?") : url); <>7__wrap1 = text.Split(new char[1] { '&' }); <>7__wrap2 = 0; goto IL_00c6; IL_00b8: <>7__wrap2++; goto IL_00c6; IL_00c6: if (<>7__wrap2 < <>7__wrap1.Length) { string str = <>7__wrap1[<>7__wrap2]; string text2 = WebUtility.UrlDecode(str.SubstringUntil("=")); string value = WebUtility.UrlDecode(str.SubstringAfter("=")); if (!string.IsNullOrWhiteSpace(text2)) { <>2__current = new KeyValuePair<string, string>(text2, value); <>1__state = 1; return true; } goto IL_00b8; } <>7__wrap1 = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator() { <EnumerateQueryParameters>d__0 <EnumerateQueryParameters>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <EnumerateQueryParameters>d__ = this; } else { <EnumerateQueryParameters>d__ = new <EnumerateQueryParameters>d__0(0); } <EnumerateQueryParameters>d__.url = <>3__url; return <EnumerateQueryParameters>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<KeyValuePair<string, string>>)this).GetEnumerator(); } } [IteratorStateMachine(typeof(<EnumerateQueryParameters>d__0))] private static IEnumerable<KeyValuePair<string, string>> EnumerateQueryParameters(string url) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <EnumerateQueryParameters>d__0(-2) { <>3__url = url }; } public static IReadOnlyDictionary<string, string> GetQueryParameters(string url) { return EnumerateQueryParameters(url).ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> kvp) => kvp.Key, (KeyValuePair<string, string> kvp) => kvp.Value); } private static KeyValuePair<string, string>? TryGetQueryParameter(string url, string key) { foreach (KeyValuePair<string, string> item in EnumerateQueryParameters(url)) { if (string.Equals(item.Key, key, StringComparison.Ordinal)) { return item; } } return null; } public static string? TryGetQueryParameterValue(string url, string key) { return TryGetQueryParameter(url, key)?.Value; } public static bool ContainsQueryParameter(string url, string key) { return TryGetQueryParameterValue(url, key) != null; } public static string RemoveQueryParameter(string url, string key) { if (!ContainsQueryParameter(url, key)) { return url; } UriBuilder uriBuilder = new UriBuilder(url); StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair<string, string> item in EnumerateQueryParameters(url)) { if (!string.Equals(item.Key, key, StringComparison.Ordinal)) { stringBuilder.Append((stringBuilder.Length > 0) ? '&' : '?'); stringBuilder.Append(Uri.EscapeDataString(item.Key)); stringBuilder.Append('='); stringBuilder.Append(Uri.EscapeDataString(item.Value)); } } uriBuilder.Query = stringBuilder.ToString(); return uriBuilder.ToString(); } public static string SetQueryParameter(string url, string key, string value) { string text = RemoveQueryParameter(url, key); bool flag = PolyfillExtensions.Contains(text, '?'); return text + (flag ? '&' : '?') + Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value); } } internal static class Xml { public static XElement Parse(string source) { return XElement.Parse(source, LoadOptions.PreserveWhitespace).StripNamespaces(); } } } namespace YoutubeExplode.Utils.Extensions { internal static class AsyncCollectionExtensions { public static async IAsyncEnumerable<T> TakeAsync<T>(this IAsyncEnumerable<T> source, int count) { int currentCount = 0; await foreach (T item in source) { if (currentCount >= count) { break; } yield return item; currentCount++; } } public static async IAsyncEnumerable<T> SelectManyAsync<TSource, T>(this IAsyncEnumerable<TSource> source, Func<TSource, IEnumerable<T>> transform) { await foreach (TSource item in source) { foreach (T item2 in transform(item)) { yield return item2; } } } public static async IAsyncEnumerable<T> OfTypeAsync<T>(this IAsyncEnumerable<object> source) { await foreach (object item in source) { if (item is T) { yield return (T)item; } } } public static async ValueTask<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> source) { List<T> list = new List<T>(); await foreach (T item in source) { list.Add(item); } return list; } public static ValueTaskAwaiter<List<T>> GetAwaiter<T>(this IAsyncEnumerable<T> source) { return source.ToListAsync().GetAwaiter(); } } internal static class BinaryExtensions { public static string ToHex(this byte[] data, bool isUpperCase = true) { StringBuilder stringBuilder = new StringBuilder(2 * data.Length); foreach (byte b in data) { stringBuilder.Append(b.ToString(isUpperCase ? "X2" : "x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } } internal static class CollectionExtensions { [CompilerGenerated] private sealed class <WhereNotNull>d__0<T> : IEnumerable<T>, IEnumerable, IEnumerator<T>, IEnumerator, IDisposable where T : class { private int <>1__state; private T <>2__current; private int <>l__initialThreadId; private IEnumerable<T?> source; public IEnumerable<T?> <>3__source; private IEnumerator<T?> <>7__wrap1; T IEnumerator<T>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WhereNotNull>d__0(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>7__wrap1 = source.GetEnumerator(); <>1__state = -3; break; case 1: <>1__state = -3; break; } while (<>7__wrap1.MoveNext()) { T current = <>7__wrap1.Current; if (current != null) { <>2__current = current; <>1__state = 1; return true; } } <>m__Finally1(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 != null) { <>7__wrap1.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<T> IEnumerable<T>.GetEnumerator() { <WhereNotNull>d__0<T> <WhereNotNull>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <WhereNotNull>d__ = this; } else { <WhereNotNull>d__ = new <WhereNotNull>d__0<T>(0); } <WhereNotNull>d__.source = <>3__source; return <WhereNotNull>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<T>)this).GetEnumerator(); } } [CompilerGenerated] private sealed class <WhereNotNull>d__1<T> : IEnumerable<T>, IEnumerable, IEnumerator<T>, IEnumerator, IDisposable where T : struct { private int <>1__state; private T <>2__current; private int <>l__initialThreadId; private IEnumerable<T?> source; public IEnumerable<T?> <>3__source; private IEnumerator<T?> <>7__wrap1; T IEnumerator<T>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WhereNotNull>d__1(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>7__wrap1 = source.GetEnumerator(); <>1__state = -3; break; case 1: <>1__state = -3; break; } while (<>7__wrap1.MoveNext()) { T? current = <>7__wrap1.Current; if (current.HasValue) { <>2__current = current.Value; <>1__state = 1; return true; } } <>m__Finally1(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 != null) { <>7__wrap1.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<T> IEnumerable<T>.GetEnumerator() { <WhereNotNull>d__1<T> <WhereNotNull>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <WhereNotNull>d__ = this; } else { <WhereNotNull>d__ = new <WhereNotNull>d__1<T>(0); } <WhereNotNull>d__.source = <>3__source; return <WhereNotNull>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<T>)this).GetEnumerator(); } } [IteratorStateMachine(typeof(<WhereNotNull>d__0<>))] public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) where T : class { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WhereNotNull>d__0<T>(-2) { <>3__source = source }; } [IteratorStateMachine(typeof(<WhereNotNull>d__1<>))] public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) where T : struct { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WhereNotNull>d__1<T>(-2) { <>3__source = source }; } public static T? ElementAtOrNull<T>(this IEnumerable<T> source, int index) where T : struct { IReadOnlyList<T> readOnlyList = (source as IReadOnlyList<T>) ?? source.ToArray(); if (index >= readOnlyList.Count) { return null; } return readOnlyList[index]; } public static T? FirstOrNull<T>(this IEnumerable<T> source) where T : struct { using (IEnumerator<T> enumerator = source.GetEnumerator()) { if (enumerator.MoveNext()) { return enumerator.Current; } } return null; } } internal static class GenericExtensions { public static TOut Pipe<TIn, TOut>(this TIn input, Func<TIn, TOut> transform) { return transform(input); } } internal static class HttpExtensions { private class NonDisposableHttpContent : HttpContent { [CompilerGenerated] private HttpContent <content>P; public NonDisposableHttpContent(HttpContent content) { <content>P = content; base..ctor(); } protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) { await <content>P.CopyToAsync(stream); } protected override bool TryComputeLength(out long length) { length = 0L; return false; } } public static HttpRequestMessage Clone(this HttpRequestMessage request) { HttpRequestMessage httpRequestMessage = new HttpRequestMessage(request.Method, request.RequestUri) { Version = request.Version, Content = ((request.Content != null) ? new NonDisposableHttpContent(request.Content) : null) }; string key; IEnumerable<string> value; foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers) { PolyfillExtensions.Deconstruct(header, out key, out value); string name = key; IEnumerable<string> values = value; httpRequestMessage.Headers.TryAddWithoutValidation(name, values); } if (request.Content != null && httpRequestMessage.Content != null) { foreach (KeyValuePair<string, IEnumerable<string>> header2 in request.Content.Headers) { PolyfillExtensions.Deconstruct(header2, out key, out value); string name2 = key; IEnumerable<string> values2 = value; httpRequestMessage.Content.Headers.TryAddWithoutValidation(name2, values2); } } return httpRequestMessage; } public static async ValueTask<HttpResponseMessage> HeadAsync(this HttpClient http, string requestUri, CancellationToken cancellationToken = default(CancellationToken)) { using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Head, requestUri); return await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); } } internal static class JsonExtensions { [CompilerGenerated] private sealed class <>c__DisplayClass9_0 { public string propertyName; internal IEnumerable<JsonElement> <EnumerateDescendantProperties>b__0(JsonElement j) { return j.EnumerateDescendantProperties(propertyName); } internal IEnumerable<JsonElement> <EnumerateDescendantProperties>b__1(JsonProperty j) { return j.Value.EnumerateDescendantProperties(propertyName); } } [CompilerGenerated] private sealed class <EnumerateDescendantProperties>d__9 : IEnumerable<JsonElement>, IEnumerable, IEnumerator<JsonElement>, IEnumerator, IDisposable { private int <>1__state; private JsonElement <>2__current; private int <>l__initialThreadId; private string propertyName; public string <>3__propertyName; private JsonElement element; public JsonElement <>3__element; private <>c__DisplayClass9_0 <>8__1; private IEnumerator<JsonElement> <>7__wrap1; JsonElement IEnumerator<JsonElement>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <EnumerateDescendantProperties>d__9(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { switch (<>1__state) { case -3: case 2: try { } finally { <>m__Finally1(); } break; case -4: case 3: try { } finally { <>m__Finally2(); } break; } <>8__1 = null; <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { try { IEnumerable<JsonElement> enumerable; IEnumerable<JsonElement> enumerable2; switch (<>1__state) { default: return false; case 0: { <>1__state = -1; <>8__1 = new <>c__DisplayClass9_0(); <>8__1.propertyName = propertyName; JsonElement? propertyOrNull = element.GetPropertyOrNull(<>8__1.propertyName); if (propertyOrNull.HasValue) { <>2__current = propertyOrNull.Value; <>1__state = 1; return true; } goto IL_0089; } case 1: <>1__state = -1; goto IL_0089; case 2: <>1__state = -3; goto IL_00f1; case 3: { <>1__state = -4; break; } IL_0089: enumerable = element.EnumerateArrayOrEmpty().SelectMany((JsonElement j) => j.EnumerateDescendantProperties(<>8__1.propertyName)); <>7__wrap1 = enumerable.GetEnumerator(); <>1__state = -3; goto IL_00f1; IL_00f1: if (<>7__wrap1.MoveNext()) { JsonElement current = <>7__wrap1.Current; <>2__current = current; <>1__state = 2; return true; } <>m__Finally1(); <>7__wrap1 = null; enumerable2 = element.EnumerateObjectOrEmpty().SelectMany((JsonProperty j) => j.Value.EnumerateDescendantProperties(<>8__1.propertyName)); <>7__wrap1 = enumerable2.GetEnumerator(); <>1__state = -4; break; } if (<>7__wrap1.MoveNext()) { JsonElement current2 = <>7__wrap1.Current; <>2__current = current2; <>1__state = 3; return true; } <>m__Finally2(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 != null) { <>7__wrap1.Dispose(); } } private void <>m__Finally2() { <>1__state = -1; if (<>7__wrap1 != null) { <>7__wrap1.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<JsonElement> IEnumerable<JsonElement>.GetEnumerator() { <EnumerateDescendantProperties>d__9 <EnumerateDescendantProperties>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <EnumerateDescendantProperties>d__ = this; } else { <EnumerateDescendantProperties>d__ = new <EnumerateDescendantProperties>d__9(0); } <EnumerateDescendantProperties>d__.element = <>3__element; <EnumerateDescendantProperties>d__.propertyName = <>3__propertyName; return <EnumerateDescendantProperties>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<JsonElement>)this).GetEnumerator(); } } public static JsonElement? GetPropertyOrNull(this JsonElement element, string propertyName) { if (element.ValueKind != JsonValueKind.Object) { return null; } if (element.TryGetProperty(propertyName, out var value) && value.ValueKind != JsonValueKind.Null && value.ValueKind != 0) { return value; } return null; } public static bool? GetBooleanOrNull(this JsonElement element) { return element.ValueKind switch { JsonValueKind.True => true, JsonValueKind.False => false, _ => null, }; } public static string? GetStringOrNull(this JsonElement element) { if (element.ValueKind != JsonValueKind.String) { return null; } return element.GetString(); } public static int? GetInt32OrNull(this JsonElement element) { if (element.ValueKind != JsonValueKind.Number || !element.TryGetInt32(out var value)) { return null; } return value; } public static long? GetInt64OrNull(this JsonElement element) { if (element.ValueKind != JsonValueKind.Number || !element.TryGetInt64(out var value)) { return null; } return value; } public static JsonElement.ArrayEnumerator? EnumerateArrayOrNull(this JsonElement element) { if (element.ValueKind != JsonValueKind.Array) { return null; } return element.EnumerateArray(); } public static JsonElement.ArrayEnumerator EnumerateArrayOrEmpty(this JsonElement element) { return element.EnumerateArrayOrNull().GetValueOrDefault(); } public static JsonElement.ObjectEnumerator? EnumerateObjectOrNull(this JsonElement element) { if (element.ValueKind != JsonValueKind.Object) { return null; } return element.EnumerateObject(); } public static JsonElement.ObjectEnumerator EnumerateObjectOrEmpty(this JsonElement element) { return element.EnumerateObjectOrNull().GetValueOrDefault(); } [IteratorStateMachine(typeof(<EnumerateDescendantProperties>d__9))] public static IEnumerable<JsonElement> EnumerateDescendantProperties(this JsonElement element, string propertyName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <EnumerateDescendantProperties>d__9(-2) { <>3__element = element, <>3__propertyName = propertyName }; } } internal static class StreamExtensions { public static async ValueTask CopyToAsync(this Stream source, Stream destination, IProgress<double>? progress = null, CancellationToken cancellationToken = default(CancellationToken)) { using IMemoryOwner<byte> buffer = MemoryPool<byte>.Shared.Rent(81920); long totalBytesRead = 0L; while (true) { int bytesRead = await PolyfillExtensions.ReadAsync(source, buffer.Memory, cancellationToken); if (bytesRead <= 0) { break; } await PolyfillExtensions.WriteAsync(destination, buffer.Memory.Slice(0, bytesRead), cancellationToken); totalBytesRead += bytesRead; progress?.Report(1.0 * (double)totalBytesRead / (double)source.Length); } } } internal static class StringExtensions { public static string? NullIfWhiteSpace(this string str) { if (string.IsNullOrWhiteSpace(str)) { return null; } return str; } public static string SubstringUntil(this string str, string sub, StringComparison comparison = StringComparison.Ordinal) { int num = str.IndexOf(sub, comparison); if (num >= 0) { return str.Substring(0, num); } return str; } public static string SubstringAfter(this string str, string sub, StringComparison comparison = StringComparison.Ordinal) { int num = str.IndexOf(sub, comparison); if (num >= 0) { return str.Substring(num + sub.Length, str.Length - num - sub.Length); } return string.Empty; } public static string StripNonDigit(this string str) { StringBuilder stringBuilder = new StringBuilder(); foreach (char item in str.Where(char.IsDigit)) { stringBuilder.Append(item); } return stringBuilder.ToString(); } public static string Reverse(this string str) { StringBuilder stringBuilder = new StringBuilder(str.Length); for (int num = str.Length - 1; num >= 0; num--) { stringBuilder.Append(str[num]); } return stringBuilder.ToString(); } public static string SwapChars(this string str, int firstCharIndex, int secondCharIndex) { return new StringBuilder(str) { [firstCharIndex] = str[secondCharIndex], [secondCharIndex] = str[firstCharIndex] }.ToString(); } public static int? ParseIntOrNull(this string str) { if (!int.TryParse(str, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result)) { return null; } return result; } public static int ParseInt(this string str) { return str.ParseIntOrNull() ?? throw new FormatException("Cannot parse integer number from string '" + str + "'."); } public static long? ParseLongOrNull(this string str) { if (!long.TryParse(str, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result)) { return null; } return result; } public static double? ParseDoubleOrNull(this string str) { if (!double.TryParse(str, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo, out var result)) { return null; } return result; } public static TimeSpan? ParseTimeSpanOrNull(this string str, string[] formats) { if (!TimeSpan.TryParseExact(str, formats, DateTimeFormatInfo.InvariantInfo, out var result)) { return null; } return result; } public static DateTimeOffset? ParseDateTimeOffsetOrNull(this string str) { if (!DateTimeOffset.TryParse(str, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var result)) { return null; } return result; } public static string ConcatToString<T>(this IEnumerable<T> source) { return string.Concat(source); } } internal static class UriExtensions { public static string GetDomain(this Uri uri) { return uri.Scheme + Uri.SchemeDelimiter + uri.Host; } } internal static class XElementExtensions { public static XElement StripNamespaces(this XElement element) { XElement xElement = new XElement(element); foreach (XElement item in xElement.DescendantsAndSelf()) { item.Name = XNamespace.None.GetName(item.Name.LocalName); item.ReplaceAttributes(from a in item.Attributes() where !a.IsNamespaceDeclaration where a.Name.Namespace != XNamespace.Xml && a.Name.Namespace != XNamespace.Xmlns select new XAttribute(XNamespace.None.GetName(a.Name.LocalName), a.Value)); } return xElement; } } } namespace YoutubeExplode.Search { public class ChannelSearchResult : ISearchResult, IBatchItem, IChannel { public ChannelId Id { get; } public string Url => $"https://www.youtube.com/channel/{Id}"; public string Title { get; } public IReadOnlyList<Thumbnail> Thumbnails { get; } public ChannelSearchResult(ChannelId id, string title, IReadOnlyList<Thumbnail> thumbnails) { Id = id; Title = title; Thumbnails = thumbnails; base..ctor(); } [ExcludeFromCodeCoverage] public override string ToString() { return "Channel (" + Title + ")"; } } public interface ISearchResult : IBatchItem { string Url { get; } string Title { get; } } public class PlaylistSearchResult : ISearchResult, IBatchItem, IPlaylist { public PlaylistId Id { get; } public string Url => $"https://www.youtube.com/playlist?list={Id}"; public string Title { get; } public Author? Author { get; } public IReadOnlyList<Thumbnail> Thumbnails { get; } public PlaylistSearchResult(PlaylistId id, string title, Author? author, IReadOnlyList<Thumbnail> thumbnails) { Id = id; Title = title; Author = author; Thumbnails = thumbnails; base..ctor(); } [ExcludeFromCodeCoverage] public override string ToString() { return "Playlist (" + Title + ")"; } } public class SearchClient { [CompilerGenerated] private sealed class <GetResultBatchesAsync>d__2 : IAsyncEnumerable<Batch<ISearchResult>>, IAsyncEnumerator<Batch<ISearchResult>>, IAsyncDisposable, IValueTaskSource<bool>, IValueTaskSource, IAsyncStateMachine { public int <>1__state; public AsyncIteratorMethodBuilder <>t__builder; public ManualResetValueTaskSourceCore<bool> <>v__promiseOfValueOrEnd; private Batch<ISearchResult> <>2__current; private bool <>w__disposeMode; private CancellationTokenSource <>x__combinedTokens; private int <>l__initialThreadId; public SearchClient <>4__this; private string searchQuery; public string <>3__searchQuery; private SearchFilter searchFilter; public SearchFilter <>3__searchFilter; private CancellationToken cancellationToken; public CancellationToken <>3__cancellationToken; private HashSet<string> <encounteredIds>5__2; private List<ISearchResult> <results>5__3; private SearchResponse <searchResults>5__4; private ValueTaskAwaiter<SearchResponse> <>u__1; Batch<ISearchResult> IAsyncEnumerator<Batch<ISearchResult>>.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <GetResultBatchesAsync>d__2(int <>1__state) { <>t__builder = AsyncIteratorMethodBuilder.Create(); this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } private void MoveNext() { int num = <>1__state; SearchClient searchClient = <>4__this; try { string text; ValueTaskAwaiter<SearchResponse> awaiter; switch (num) { default: if (!<>w__disposeMode) { num = (<>1__state = -1); <encounteredIds>5__2 = new HashSet<string>(StringComparer.Ordinal); text = null; goto IL_0053; } goto end_IL_000e; case 0: awaiter = <>u__1; <>u__1 = default(ValueTaskAwaiter<SearchResponse>); num = (<>1__state = -1); break; case -4: { num = (<>1__state = -1); if (!<>w__disposeMode) { text = <searchResults>5__4.ContinuationToken; <results>5__3 = null; <searchResults>5__4 = null; if (!string.IsNullOrWhiteSpace(text)) { goto IL_0053; } } goto end_IL_000e; } IL_0053: <results>5__3 = new List<ISearchResult>(); <>2__current = null; awaiter = searchClient._controller.GetSearchResponseAsync(searchQuery, searchFilter, text, cancellationToken).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <GetResultBatchesAsync>d__2 stateMachine = this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } break; } SearchResponse result = awaiter.GetResult(); <searchResults>5__4 = result; IEnumerator<SearchResponse.VideoData> enumerator = <searchResults>5__4.Videos.GetEnumerator(); try { while (enumerator.MoveNext()) { SearchResponse.VideoData current = enumerator.Current; if (searchFilter != 0 && searchFilter != SearchFilter.Video) { break; } string text2 = current.Id ?? throw new YoutubeExplodeException("Failed to extract the video ID."); if (<encounteredIds>5__2.Add(text2)) { string title = current.Title ?? throw new YoutubeExplodeException("Failed to extract the video title."); string channelTitle = current.Author ?? throw new YoutubeExplodeException("Failed to extract the video author."); string text3 = current.ChannelId ?? throw new YoutubeExplodeException("Failed to extract the video channel ID."); Thumbnail[] thumbnails = current.Thumbnails.Select(delegate(ThumbnailData t) { string? url3 = t.Url ?? throw new YoutubeExplodeException("Failed to extract the video thumbnail URL."); int width3 = t.Width ?? throw new YoutubeExplodeException("Failed to extract the video thumbnail width."); int height3 = t.Height ?? throw new YoutubeExplodeException("Failed to extract the video thumbnail height."); Resolution resolution3 = new Resolution(width3, height3); return new Thumbnail(url3, resolution3); }).Concat(Thumbnail.GetDefaultSet(text2)).ToArray(); VideoSearchResult item = new VideoSearchResult(text2, title, new Author(text3, channelTitle), current.Duration, thumbnails); <results>5__3.Add(item); } } } finally { if (num == -1) { enumerator?.Dispose(); } } if (!<>w__disposeMode) { IEnumerator<SearchResponse.PlaylistData> enumerator2 = <searchResults>5__4.Playlists.GetEnumerator(); try { while (enumerator2.MoveNext()) { SearchResponse.PlaylistData current2 = enumerator2.Current; if (searchFilter != 0 && searchFilter != SearchFilter.Playlist) { break; } string text4 = current2.Id ?? throw new YoutubeExplodeException("Failed to extract the playlist ID."); if (<encounteredIds>5__2.Add(text4)) { string title2 = current2.Title ?? throw new YoutubeExplodeException("Failed to extract the playlist title."); Author author = ((!string.IsNullOrWhiteSpace(current2.ChannelId) && !string.IsNullOrWhiteSpace(current2.Author)) ? new Author(current2.ChannelId, current2.Author) : null); Thumbnail[] thumbnails2 = current2.Thumbnails.Select(delegate(ThumbnailData t) { string? url2 = t.Url ?? throw new YoutubeExplodeException("Failed to extract the playlist thumbnail URL."); int width2 = t.Width ?? throw new YoutubeExplodeException("Failed to extract the playlist thumbnail width."); int height2 = t.Height ?? throw new YoutubeExplodeException("Failed to extract the playlist thumbnail height."); Resolution resolution2 = new Resolution(width2, height2); return new Thumbnail(url2, resolution2); }).ToArray(); PlaylistSearchResult item2 = new PlaylistSearchResult(text4, title2, author, thumbnails2); <results>5__3.Add(item2); } } } finally { if (num == -1) { enumerator2?.Dispose(); } } if (!<>w__disposeMode) { IEnumerator<SearchResponse.ChannelData> enumerator3 = <searchResults>5__4.Channels.GetEnumerator(); try { while (enumerator3.MoveNext()) { SearchResponse.ChannelData current3 = enumerator3.Current; if (searchFilter == SearchFilter.None || searchFilter == SearchFilter.Channel) { string text5 = current3.Id ?? throw new YoutubeExplodeException("Failed to extract the channel ID."); string title3 = current3.Title ?? throw new YoutubeExplodeException("Failed to extract the channel title."); Thumbnail[] thumbnails3 = current3.Thumbnails.Select(delegate(ThumbnailData t) { string? url = t.Url ?? throw new YoutubeExplodeException("Failed to extract the channel thumbnail URL."); int width = t.Width ?? throw new YoutubeExplodeException("Failed to extract the channel thumbnail width."); int height = t.Height ?? throw new YoutubeExplodeException("Failed to extract the channel thumbnail height."); Resolution resolution = new Resolution(width, height); return new Thumbnail(url, resolution); }).ToArray(); ChannelSearchResult item3 = new ChannelSearchResult(text5, title3, thumbnails3); <results>5__3.Add(item3); continue; } break; } } finally { if (num == -1) { enumerator3?.Dispose(); } } if (!<>w__disposeMode) { <>2__current = Batch.Create(<results>5__3); num = (<>1__state = -4); goto IL_054d; } } } end_IL_000e:; } catch (Exception exception) { <>1__state = -2; <encounteredIds>5__2 = null; <results>5__3 = null; <searchResults>5__4 = null; if (<>x__combinedTokens != null) { <>x__combinedTokens.Dispose(); <>x__combinedTokens = null; } <>2__current = null; <>t__builder.Complete(); <>v__promiseOfValueOrEnd.SetException(exception); return; } <>1__state = -2; <encounteredIds>5__2 = null; <results>5__3 = null; <searchResults>5__4 = null; if (<>x__combinedTokens != null) { <>x__combinedTokens.Dispose(); <>x__combinedTokens = null; } <>2__current = null; <>t__builder.Complete(); <>v__promiseOfValueOrEnd.SetResult(result: false); return; IL_054d: <>v__promiseOfValueOrEnd.SetResult(result: true); } void IAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } [DebuggerHidden] IAsyncEnumerator<Batch<ISearchResult>> IAsyncEnumerable<Batch<ISearchResult>>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken)) { <GetResultBatchesAsync>d__2 <GetResultBatchesAsync>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = -3; <>t__builder = AsyncIteratorMethodBuilder.Create(); <>w__disposeMode = false; <GetResultBatchesAsync>d__ = this; } else { <GetResultBatchesAsync>d__ = new <GetResultBatchesAsync>d__2(-3) { <>4__this = <>4__this }; } <GetResultBatchesAsync>d__.searchQuery = <>3__searchQuery; <GetResultBatchesAsync>d__.searchFilter = <>3__searchFilter; if (<>3__cancellationToken.Equals(default(CancellationToken))) { <GetResultBatchesAsync>d__.cancellationToken = cancellationToken; } else if (cancellationToken.Equals(<>3__cancellationToken) || cancellationToken.Equals(default(CancellationToken))) { <GetResultBatchesAsync>d__.cancellationToken = <>3__cancellationToken; } else { <>x__combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(<>3__cancellationToken, cancellationToken); <GetResultBatchesAsync>d__.cancellationToken = <>x__combinedTokens.Token; } return <GetResultBatchesAsync>d__; } [DebuggerHidden] ValueTask<bool> IAsyncEnumerator<Batch<ISearchResult>>.MoveNextAsync() { if (<>1__state == -2) { return default(ValueTask<bool>); } <>v__promiseOfValueOrEnd.Reset(); <GetResultBatchesAsync>d__2 stateMachine = this; <>t__builder.MoveNext(ref stateMachine); short version = <>v__promiseOfValueOrEnd.Version; if (<>v__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded) { return new ValueTask<bool>(<>v__promiseOfValueOrEnd.GetResult(version)); } return new ValueTask<bool>(this, version); } [DebuggerHidden] bool IValueTaskSource<bool>.GetResult(short token) { return <>v__promiseOfValueOrEnd.GetResult(token); } [DebuggerHidden] ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token) { return <>v__promiseOfValueOrEnd.GetStatus(token); } [DebuggerHidden] void IValueTaskSource<bool>.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { <>v__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags); } [DebuggerHidden] void IValueTaskSource.GetResult(short token) { <>v__promiseOfValueOrEnd.GetResult(token); } [DebuggerHidden] ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) { return <>v__promiseOfValueOrEnd.GetStatus(token); } [DebuggerHidden] void IValueTaskSource.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { <>v__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags); } [DebuggerHidden] ValueTask IAsyncDisposable.DisposeAsync() { if (<>1__state >= -1) { throw new NotSupportedException(); } if (<>1__state == -2) { return default(ValueTask); } <>w__disposeMode = true; <>v__promiseOfValueOrEnd.Reset(); <GetResultBatchesAsync>d__2 stateMachine = this; <>t__builder.MoveNext(ref stateMachine); return new ValueTask(this, <>v__promiseOfValueOrEnd.Version); } } private readonly SearchController _controller = new SearchController(http); public SearchClient(HttpClient http) { } [AsyncIteratorStateMachine(typeof(<GetResultBatchesAsync>d__2))] public IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(string searchQuery, SearchFilter searchFilter, [EnumeratorCancellation] CancellationToken cancellationToken = default(CancellationToken)) { return new <GetResultBatchesAsync>d__2(-2) { <>4__this = this, <>3__searchQuery = searchQuery, <>3__searchFilter = searchFilter, <>3__cancellationToken = cancellationToken }; } public IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken)) { return GetResultBatchesAsync(searchQuery, SearchFilter.None, cancellationToken); } public IAsyncEnumerable<ISearchResult> GetResultsAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken)) { return GetResultBatchesAsync(searchQuery, cancellationToken).FlattenAsync(); } public IAsyncEnumerable<VideoSearchResult> GetVideosAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken)) { return GetResultBatchesAsync(searchQuery, SearchFilter.Video, cancellationToken).FlattenAsync().OfTypeAsync<VideoSearchResult>(); } public IAsyncEnumerable<PlaylistSearchResult> GetPlaylistsAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken)) { return GetResultBatchesAsync(searchQuery, SearchFilter.Playlist, cancellationToken).FlattenAsync().OfTypeAsync<PlaylistSearchResult>(); } public IAsyncEnumerable<ChannelSearchResult> GetChannelsAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken)) { return GetResultBatchesAsync(searchQuery, SearchFilter.Channel, cancellationToken).FlattenAsync().OfTypeAsync<ChannelSearchResult>(); } } internal class SearchController { [CompilerGenerated] private HttpClient <http>P; public SearchController(HttpClient http) { <http>P = http; base..ctor(); } public async ValueTask<SearchResponse> GetSearchResponseAsync(string searchQuery, SearchFilter searchFilter, string? continuationToken, CancellationToken cancellationToken = default(CancellationToken)) { using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://www.youtube.com/youtubei/v1/search"); HttpRequestMessage httpRequestMessage = request; string text = Json.Serialize(searchQuery); string value = searchFilter switch { SearchFilter.Video => "EgIQAQ%3D%3D", SearchFilter.Playlist => "EgIQAw%3D%3D", SearchFilter.Channel => "EgIQAg%3D%3D", _ => null, }; httpRequestMessage.Content = new StringContent("{\n \"query\": " + text + ",\n \"params\": " + Json.Serialize(value) + ",\n \"continuation\": " + Json.Serialize(continuationToken) + ",\n \"context\": {\n \"client\": {\n \"clientName\": \"WEB\",\n \"clientVersion\": \"2.20210408.08.00\",\n \"hl\": \"en\",\n \"gl\": \"US\",\n \"utcOffsetMinutes\": 0\n }\n }\n}"); using HttpResponseMessage response = await <http>P.SendAsync(request, cancellationToken); response.EnsureSuccessStatusCode(); return SearchResponse.Parse(await PolyfillExtensions.ReadAsStringAsync(response.Content, cancellationToken)); } } public enum SearchFilter { None, Video, Playlist, Channel } public class VideoSearchResult : ISearchResult, IBatchItem, IVideo { public VideoId Id { get; } public string Url => $"https://www.youtube.com/watch?v={Id}"; public string Title { get; } public Author Author { get; } public TimeSpan? Duration { get; } public IReadOnlyList<Thumbnail> Thumbnails { get; } public VideoSearchResult(VideoId id, string title, Author author, TimeSpan? duration, IReadOnlyList<Thumbnail> thumbnails) { Id = id; Title = title; Author = author; Duration = duration; Thumbnails = thumbnails; base..ctor(); } [ExcludeFromCodeCoverage] public override string ToString() { return "Video (" + Title + ")"; } } } namespace YoutubeExplode.Playlists { public interface IPlaylist { PlaylistId Id { get; } string Url { get; } string Title { get; } Author? Author { get; } IReadOnlyList<Thumbnail> Thumbnails { get; } } public class Playlist : IPlaylist { public PlaylistId Id { get; } public string Url => $"https://www.youtube.com/playlist?list={Id}"; public string Title { get; } public Author? Author { get; } public string Description { get; } public int? Count { get; } public IReadOnlyList<Thumbnail> Thumbnails { get; } public Playlist(PlaylistId id, string title, Author? author, string description, int? count, IReadOnlyList<Thumbnail> thumbnails) { Id = id; Title = title; Author = author; Description = description; Count = count; Thumbnails = thumbnails; base..ctor(); } [ExcludeFromCodeCoverage] public override string ToString() { return "Playlist (" + Title + ")"; } } public class PlaylistClient { [CompilerGenerated] private sealed class <GetVideoBatchesAsync>d__3 : IAsyncEnumerable<Batch<PlaylistVideo>>, IAsyncEnumerator<Batch<PlaylistVideo>>, IAsyncDisposable, IValueTaskSource<bool>, IValueTaskSource, IAsyncStateMachine { public int <>1__state; public AsyncIteratorMethodBuilder <>t__builder; public ManualResetValueTaskSourceCore<bool> <>v__promiseOfValueOrEnd; private Batch<PlaylistVideo> <>2__current; private bool <>w__disposeMode; private CancellationTokenSource <>x__combinedTokens; private int <>l__initialThreadId; public PlaylistClient <>4__this; private PlaylistId playlistId; public PlaylistId <>3__playlistId; private CancellationToken cancellationToken; public CancellationToken <>3__cancellationToken; private HashSet<VideoId> <encounteredIds>5__2; private VideoId? <lastVideoId>5__3; private int <lastVideoIndex>5__4; private string <visitorData>5__5; private PlaylistNextResponse <response>5__6; private ValueTaskAwaiter<PlaylistNextResponse> <>u__1; Batch<PlaylistVideo> IAsyncEnumerator<Batch<PlaylistVideo>>.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <GetVideoBatchesAsync>d__3(int <>1__state) { <>t__builder = AsyncIteratorMethodBuilder.Create(); this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } private void MoveNext() { int num = <>1__state; PlaylistClient playlistClient = <>4__this; try { ValueTaskAwaiter<PlaylistNextResponse> awaiter; switch (num) { default: if (!<>w__disposeMode) { num = (<>1__state = -1); <encounteredIds>5__2 = new HashSet<VideoId>(); <lastVideoId>5__3 = null; <lastVideoIndex>5__4 = 0; <visitorData>5__5 = null; goto IL_0066; } goto end_IL_000e; case 0: awaiter = <>u__1; <>u__1 = default(ValueTaskAwaiter<PlaylistNextResponse>); num = (<>1__state = -1); break; case -4: { num = (<>1__state = -1); if (!<>w__disposeMode) { if (<visitorData>5__5 == null) { <visitorData>5__5 = <response>5__6.VisitorData; } <response>5__6 = null; goto IL_0066; } goto end_IL_000e; } IL_0066: <>2__current = null; awaiter = playlistClient._controller.GetPlaylistNextResponseAsync(playlistId, <lastVideoId>5__3, <lastVideoIndex>5__4, <visitorData>5__5, cancellationToken).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <GetVideoBatchesAsync>d__3 stateMachine = this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } break; } PlaylistNextResponse result = awaiter.GetResult(); <response>5__6 = result; List<PlaylistVideo> list = new List<PlaylistVideo>(); IEnumerator<PlaylistVideoData> enumerator = <response>5__6.Videos.GetEnumerator(); try { while (enumerator.MoveNext()) { PlaylistVideoData current = enumerator.Current; string text = current.Id ?? throw new YoutubeExplodeException("Failed to extract the video ID."); <lastVideoId>5__3 = text; <lastVideoIndex>5__4 = current.Index ?? throw new YoutubeExplodeException("Failed to extract the video index."); if (<encounteredIds>5__2.Add(text)) { string title = current.Title ?? ""; string channelTitle = current.Author ?? throw new YoutubeExplodeException("Failed to extract the video author."); string text2 = current.ChannelId ?? throw new YoutubeExplodeException("Failed to extract the video channel ID."); Thumbnail[] thumbnails = current.Thumbnails.Select(delegate(ThumbnailData t) { string? url = t.Url ?? throw new YoutubeExplodeException("Failed to extract the thumbnail URL."); int width = t.Width ?? throw new YoutubeExplodeException("Failed to extract the thumbnail width."); int height = t.Height ?? throw new YoutubeExplodeException("Failed to extract the thumbnail height."); Resolution resolution = new Resolution(width, height); return new Thumbnail(url, resolution); }).Concat(Thumbnail.GetDefaultSet(text)).ToArray(); PlaylistVideo item = new PlaylistVideo(playlistId, text, title, new Author(text2, channelTitle), current.Duration, thumbnails); list.Add(item); } } } finally { if (num == -1) { enumerator?.Dispose(); } } if (!<>w__disposeMode && list.Any()) { <>2__current = Batch.Create(list); num = (<>1__state = -4); goto IL_0384; } end_IL_000e:; } catch (Exception exception) { <>1__state = -2; <encounteredIds>5__2 = null; <visitorData>5__5 = null; <response>5__6 = null; if (<>x__combinedTokens != null) { <>x__combinedTokens.Dispose(); <>x__combinedTokens = null; } <>2__current = null; <>t__builder.Complete(); <>v__promiseOfValueOrEnd.SetException(exception); return; } <>1__state = -2; <encounteredIds>5__2 = null; <visitorData>5__5 = null; <response>5__6 = null; if (<>x__combinedTokens != null) { <>x__combinedTokens.Dispose(); <>x__combinedTokens = null; } <>2__current = null; <>t__builder.Complete(); <>v__promiseOfValueOrEnd.SetResult(result: false); return; IL_0384: <>v__promiseOfValueOrEnd.SetResult(result: true); } void IAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } [DebuggerHidden] IAsyncEnumerator<Batch<PlaylistVideo>> IAsyncEnumerable<Batch<PlaylistVideo>>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken)) { <GetVideoBatchesAsync>d__3 <GetVideoBatchesAsync>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = -3; <>t__builder = AsyncIteratorMethodBuilder.Create(); <>w__disposeMode = false; <GetVideoBatchesAsync>d__ = this; } else { <GetVideoBatchesAsync>d__ = new <GetVideoBatchesAsync>d__3(-3) { <>4__this = <>4__this }; } <GetVideoBatchesAsync>d__.playlistId = <>3__playlistId; if (<>3__cancellationToken.Equals(default(CancellationToken))) { <GetVideoBatchesAsync>d__.cancellationToken = cancellationToken; } else if (cancellationToken.Equals(<>3__cancellationToken) || cancellationToken.Equals(default(CancellationToken))) { <GetVideoBatchesAsync>d__.cancellationToken = <>3__cancellationToken; } else { <>x__combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(<>3__cancellationToken, cancellationToken); <GetVideoBatchesAsync>d__.cancellationToken = <>x__combinedTokens.Token; } return <GetVideoBatchesAsync>d__; } [DebuggerHidden] ValueTask<bool> IAsyncEnumerator<Batch<PlaylistVideo>>.MoveNextAsync() { if (<>1__state == -2) { return default(ValueTask<bool>); } <>v__promiseOfValueOrEnd.Reset(); <GetVideoBatchesAsync>d__3 stateMachine = this; <>t__builder.MoveNext(ref stateMachine); short version = <>v__promiseOfValueOrEnd.Version; if (<>v__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded) { return new ValueTask<bool>(<>v__promiseOfValueOrEnd.GetResult(version)); } return new ValueTask<bool>(this, version); } [DebuggerHidden] bool IValueTaskSource<bool>.GetResult(short token) { return <>v__promiseOfValueOrEnd.GetResult(token); } [DebuggerHidden] ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token) { return <>v__promiseOfValueOrEnd.GetStatus(token); } [DebuggerHidden] void IValueTaskSource<bool>.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { <>v__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags); } [DebuggerHidden] void IValueTaskSource.GetResult(short token) { <>v__promiseOfValueOrEnd.GetResult(token); } [DebuggerHidden] ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) { return <>v__promiseOfValueOrEnd.GetStatus(token); } [DebuggerHidden] void IValueTaskSource.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { <>v__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags); } [DebuggerHidden] ValueTask IAsyncDisposable.DisposeAsync() { if (<>1__state >= -1) { throw new NotSupportedException(); } if (<>1__state == -2) { return default(ValueTask); } <>w__disposeMode = true; <>v__promiseOfValueOrEnd.Reset(); <GetVideoBatchesAsync>d__3 stateMachine = this; <>t__builder.MoveNext(ref stateMachine); return new ValueTask(this, <>v__promiseOfValueOrEnd.Version); } } private readonly PlaylistController _controller = new PlaylistController(http); public PlaylistClient(HttpClient http) { } public async ValueTask<Playlist> GetAsync(PlaylistId playlistId, CancellationToken cancellationToken = default(CancellationToken)) { IPlaylistData obj = await _controller.GetPlaylistResponseAsync(playlistId, cancellationToken); string title = obj.Title ?? throw new YoutubeExplodeException("Failed to extract the playlist title."); string channelId = obj.ChannelId; string author = obj.Author; Author author2 = ((channelId != null && author != null) ? new Author(channelId, author) : null); string description = obj.Description ?? ""; int? count = obj.Count; Thumbnail[] thumbnails = obj.Thumbnails.Select(delegate(ThumbnailData t) { string? url = t.Url ?? throw new YoutubeExplodeException("Failed to extract the thumbnail URL."); int width = t.Width ?? throw new YoutubeExplodeException("Failed to extract the thumbnail width."); int height = t.Height ?? throw new YoutubeExplodeException("Failed to extract the thumbnail height."); Resolution resolution = new Resolution(width, height); return new Thumbnail(url, resolution); }).ToArray(); return new Playlist(playlistId, title, author2, description, count, thumbnails); } [AsyncIteratorStateMachine(typeof(<GetVideoBatchesAsync>d__3))] public IAsyncEnumerable<Batch<PlaylistVideo>> GetVideoBatchesAsync(PlaylistId playlistId, [EnumeratorCancellation] CancellationToken cancellationToken = default(CancellationToken)) { return new <GetVideoBatchesAsync>d__3(-2) { <>4__this = this, <>3__playlistId = playlistId, <>3__cancellationToken = cancellationToken }; } public IAsyncEnumerable<PlaylistVideo> GetVideosAsync(PlaylistId playlistId, CancellationToken cancellationToken = default(CancellationToken)) { return GetVideoBatchesAsync(playlistId, cancellationToken).FlattenAsync(); } } internal class PlaylistController { [CompilerGenerated] private HttpClient <http>P; public PlaylistController(HttpClient http) { <http>P = http; base..ctor(); } public async ValueTask<PlaylistBrowseResponse> GetPlaylistBrowseResponseAsync(PlaylistId playlistId, CancellationToken cancellationToken = default(CancellationToken)) { using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://www.youtube.com/youtubei/v1/browse"); request.Content = new StringContent("{\n \"browseId\": " + Json.Serialize("VL" + playlistId) + ",\n \"context\": {\n \"client\": {\n \"clientName\": \"WEB\",\n \"clientVersion\": \"2.20210408.08.00\",\n \"hl\": \"en\",\n \"gl\": \"US\",\n \"utcOffsetMinutes\": 0\n }\n }\n}"); using HttpResponseMessage response = await <http>P.SendAsync(request, cancellationToken); response.EnsureSuccessStatusCode(); PlaylistBrowseResponse playlistBrowseResponse = PlaylistBrowseResponse.Parse(await PolyfillExtensions.ReadAsStringAsync(response.Content, cancellationToken)); if (!playlistBrowseResponse.IsAvailable) { throw new PlaylistUnavailableException($"Playlist '{playlistId}' is not available."); } return playlistBrowseResponse; } public async ValueTask<PlaylistNextResponse> GetPlaylistNextResponseAsync(PlaylistId playlistId, VideoId? videoId = null, int index = 0, string? visitorData = null, CancellationToken cancellationToken = default(CancellationToken)) { int retriesRemaining = 5; while (true) { using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://www.youtube.com/youtubei/v1/next")) { string[] obj = new string[9] { "{\n \"playlistId\": ", Json.Serialize(playlistId), ",\n \"videoId\": ", null, null, null, null, null, null }; VideoId? videoId2 = videoId; obj[3] = Json.Serialize(videoId2.HasValue ? ((string)videoId2.GetValueOrDefault()) : null); obj[4] = ",\n \"playlistIndex\": "; obj[5] = Json.Serialize(index); obj[6] = ",\n \"context\": {\n \"client\": {\n \"clientName\": \"WEB\",\n \"clientVersion\": \"2.20210408.08.00\",\n \"hl\": \"en\",\n \"gl\": \"US\",\n \"utcOffsetMinutes\": 0,\n \"visitorData\": "; obj[7] = Json.Serialize(visitorData); obj[8] = "\n }\n }\n}"; request.Content = new StringContent(string.Concat(obj)); using HttpResponseMessage response = await <http>P.SendAsync(request, cancellationToken); response.EnsureSuccessStatusCode(); PlaylistNextResponse playlistNextResponse = PlaylistNextResponse.Parse(await PolyfillExtensions.ReadAsStringAsync(response.Content, cancellationToken)); if (playlistNextResponse.IsAvailable) { return playlistNextResponse; } if (index <= 0 || string.IsNullOrWhiteSpace(visitorData) || retriesRemaining <= 0) { if (index > 0 || !string.IsNullOrWhiteSpace(visitorData) || retriesRemaining < 5) { throw new PlaylistUnavailableException($"Playlist '{playlistId}' is not available."); } using (await <http>P.GetAsync($"https://youtube.com/playlist?list={playlistId}", cancellationToken)) { } } } retriesRemaining--; } } public async ValueTask<IPlaylistData> GetPlaylistResponseAsync(PlaylistId playlistId, CancellationToken cancellationToken = default(CancellationToken)) { IPlaylistData result = default(IPlaylistData); int num; try { result = await GetPlaylistBrowseResponseAsync(playlistId, cancellationToken); return result; } catch (PlaylistUnavailableException) { num = 1; } if (num != 1) { return result; } return await GetPlaylistNextResponseAsync(playlistId, null, 0, null, cancellationToken); } } public readonly struct PlaylistId : IEquatable<PlaylistId> { public string Value { get; } public PlaylistId(string value) { Value = value; } public override string ToString() { return Value; } private static bool IsValid(string playlistId) { if (playlistId.Length >= 2) { return playlistId.All(delegate(char c) { bool flag = char.IsLetterOrDigit(c); if (!flag) { bool flag2 = ((c == '-' || c == '_') ? true : false); flag = flag2; } return flag; }); } return false; } private static string? TryNormalize(string? playlistIdOrUrl) { if (string.IsNullOrWhiteSpace(playlistIdOrUrl)) { return null; } if (IsValid(playlistIdOrUrl)) { return playlistIdOrUrl; } string text = Regex.Match(playlistIdOrUrl, "youtube\\..+?/playlist.*?list=(.*?)(?:&|/|$)").Groups[1].Value.Pipe(WebUtility.UrlDecode); if (!string.IsNullOrWhiteSpace(text) && IsValid(text)) { return text; } string text2 = Regex.Match(playlistIdOrUrl, "youtube\\..+?/watch.*?list=(.*?)(?:&|/|$)").Groups[1].Value.Pipe(WebUtility.UrlDecode); if (!string.IsNullOrWhiteSpace(text2) && IsValid(text2)) { return text2; } string text3 = Regex.Match(playlistIdOrUrl, "youtu\\.be/.*?/.*?list=(.*?)(?:&|/|$)").Groups[1].Value.Pipe(WebUtility.UrlDecode); if (!string.IsNullOrWhiteSpace(text3) && IsValid(text3)) { return text3; } string text4 = Regex.Match(playlistIdOrUrl, "youtube\\..+?/embed/.*?/.*?list=(.*?)(?:&|/|$)").Groups[1].Value.Pipe(WebUtility.UrlDecode); if (!string.IsNullOrWhiteSpace(text4) && IsValid(text4)) { return text4; } return null; } public static PlaylistId? TryParse(string? playlistIdOrUrl) { return TryNormalize(playlistIdOrUrl)?.Pipe((string id) => new PlaylistId(id)); } public static PlaylistId Parse(string playlistIdOrUrl) { return TryParse(playlistIdOrUrl) ?? throw new ArgumentException("Invalid YouTube playlist ID or URL '" + playlistIdOrUrl + "'."); } public static implicit operator PlaylistId(string playlistIdOrUrl) { return Parse(playlistIdOrUrl); } public static implicit operator string(PlaylistId playlistId) { return playlistId.ToString(); } public bool Equals(PlaylistId other) { return StringComparer.Ordinal.Equals(Value, other.Value); } public override bool Equals(object? obj) { if (obj is PlaylistId other) { return Equals(other); } return false; } public override int GetHashCode() { return StringComparer.Ordinal.GetHashCode(Value); } public static bool operator ==(PlaylistId left, PlaylistId right) { return left.Equals(right); } public static bool operator !=(PlaylistId left, PlaylistId right) { return !(left == right); } } public class PlaylistVideo : IVideo, IBatchItem { public PlaylistId PlaylistId { get; } public VideoId Id { get; } public string Url => $"https://www.youtube.com/watch?v={Id}&list={PlaylistId}"; public string Title { get; } public Author Author { get; } public TimeSpan? Duration { get; } public IReadOnlyList<Thumbnail> Thumbnails { get; } public PlaylistVideo(PlaylistId playlistId, VideoId id, string title, Author author, TimeSpan? duration, IReadOnlyList<Thumbnail> thumbnails) { PlaylistId = playlistId; Id = id; Title = title; Author = author; Duration = duration; Thumbnails = thumbnails; base..ctor(); } [Obsolete("Use the other constructor instead.")] [ExcludeFromCodeCoverage] public PlaylistVideo(VideoId id, string title, Author author, TimeSpan? duration, IReadOnlyList<Thumbnail> thumbnails) : this(default(PlaylistId), id, title, author, duration, thumbnails) { } [ExcludeFromCodeCoverage] public override string ToString() { return "Video (" + Title + ")"; } } } namespace YoutubeExplode.Exceptions { public class PlaylistUnavailableException : YoutubeExplodeException { public PlaylistUnavailableException(string message) : base(message) { } } public class RequestLimitExceededException : YoutubeExplodeException { public RequestLimitExceededException(string message) : base(message) { } } public class VideoRequiresPurchaseException : VideoUnplayableException { public VideoId PreviewVideoId { get; } public VideoRequiresPurchaseException(string message, VideoId previewVideoId) { PreviewVideoId = previewVideoId; base..ctor(message); } } public class VideoUnavailableException : VideoUnplayableException { public VideoUnavailableException(string message) : base(message) { } } public class VideoUnplayableException : YoutubeExplodeException { public VideoUnplayableException(string message) : base(message) { } } public class YoutubeExplodeException : Exception { public YoutubeExplodeException(string message) : base(message) { } } } namespace YoutubeExplode.Videos { public class Engagement { public long ViewCount { get; } public long LikeCount { get; } public long DislikeCount { get; } public double AverageRating { get { if (LikeCount + DislikeCount == 0L) { return 0.0; } return 1.0 + 4.0 * (double)LikeCount / (double)(LikeCount + DislikeCount); } } public Engagement(long viewCount, long likeCount, long dislikeCount) { ViewCount = viewCount; LikeCount = likeCount; DislikeCount = dislikeCount; base..ctor(); } [ExcludeFromCodeCoverage] public override string ToString() { return $"Rating: {AverageRating:N1}"; } } public interface IVideo { VideoId Id { get; } string Url { get; } string Title { get; } Author Author { get; } TimeSpan? Duration { get; } IReadOnlyList<Thumbnail> Thumbnails { get; } } public class Video : IVideo { public VideoId Id { get; } public string Url => $"https://www.youtube.com/watch?v={Id}"; public string Title { get; } public Author Author { get; } public DateTimeOffset UploadDate { get; } public string Description { get; } public TimeSpan? Duration { get; } public IReadOnlyList<Thumbnail> Thumbnails { get; } public IReadOnlyList<string> Keywords { get; } public Engagement Engagement { get; } public Video(VideoId id, string title, Author author, DateTimeOffset uploadDate, string description, TimeSpan? duration, IReadOnlyList<Thumbnail> thumbnails, IReadOnlyList<string> keywords, Engagement engagement) { Id = id; Title = title; Author = author; UploadDate = uploadDate; Description = description; Duration = duration; Thumbnails = thumbnails; Keywords = keywords; Engagement = engagement; base..ctor(); } [ExcludeFromCodeCoverage] public override string ToString() { return "Video (" + Title + ")"; } } public class VideoClient { private readonly VideoController _controller = new VideoController(http); public StreamClient Streams { get; } = new StreamClient(http); public ClosedCaptionClient ClosedCaptions { get; } = new ClosedCaptionClient(http); public VideoClient(HttpClient http) { } public async ValueTask<Video> GetAsync(VideoId videoId, CancellationToken cancellationToken = default(CancellationToken)) { VideoWatchPage watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken); PlayerResponse playerResponse = watchPage.PlayerResponse; if (playerResponse == null) { playerResponse = await _controller.GetPlayerResponseAsync(videoId, cancellationToken); } PlayerResponse playerResponse2 = playerResponse; string title = playerResponse2.Title ?? ""; string channelTitle = playerResponse2.Author ?? throw new YoutubeExplodeException("Failed to extract the video author."); string text = playerResponse2.ChannelId ?? throw new YoutubeExplodeException("Failed to extract the video channel ID."); DateTimeOffset uploadDate = playerResponse2.UploadDate ?? watchPage.UploadDate ?? throw new YoutubeExplodeException("Failed to extract the video upload date."); Thumbnail[] thumbnails = playerResponse2.Thumbnails.Select(delegate(ThumbnailData t) { string? url = t.Url ?? throw new YoutubeExplodeException("Failed to extract the thumbnail URL."); int width = t.Width ?? throw new YoutubeExplodeException("Failed to extract the thumbnail width."); int height = t.Height ?? throw new YoutubeExplodeException("Failed to extract the thumbnail height."); Resolution resolution = new Resolution(width, height); return new Thumbnail(url, resolution); }).Concat(Thumbnail.GetDefaultSet(videoId)).ToArray(); return new Video(videoId, title, new Author(text, channelTitle), uploadDate, playerResponse2.Description ?? "", playerResponse2.Duration, thumbnails, playerResponse2.Keywords, new Engagement(playerResponse2.ViewCount.GetValueOrDefault(), watchPage.LikeCount.GetValueOrDefault(), watchPage.DislikeCount.GetValueOrDefault())); } } internal class VideoController { private string? _visitorData; protected HttpClient Http { get; } public VideoController(HttpClient http) { Http = http; base..ctor(); } private async ValueTask<string> ResolveVisitorDataAsync(CancellationToken cancellationToken = default(CancellationToken)) { if (!string.IsNullOrWhiteSpace(_visitorData))