Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Morpheus.Tests/YoutubeUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
}

[Theory]
[InlineData("https://www.youtube.com/@channel", "/@channel", "")]
[InlineData("youtube.com/user/channel", "/user/channel", "")]
[InlineData("https://m.youtube.com/c/channel?feature=share", "/c/channel", "")]
[InlineData("https://music.youtube.com/@channel", "/@channel", "")]
[InlineData("https://www.youtube.com/@channel", "/@channel", "?cbrd=1&ucbcb=1")]
[InlineData("youtube.com/user/channel", "/user/channel", "?cbrd=1&ucbcb=1")]
[InlineData("https://m.youtube.com/c/channel?feature=share", "/c/channel", "?cbrd=1&ucbcb=1")]
[InlineData("https://music.youtube.com/@channel", "/@channel", "?cbrd=1&ucbcb=1")]
[InlineData("https://youtu.be/dQw4w9WgXcQ?si=tracking", "/watch", "?v=dQw4w9WgXcQ")]
[InlineData("@channel", "/@channel", "")]
[InlineData("@channel", "/@channel", "?cbrd=1&ucbcb=1")]
public async Task ResolveChannelIdAsync_RequestsCanonicalYoutubeUrls(
string input,
string expectedPath,
Expand Down
9 changes: 5 additions & 4 deletions Utilities/YoutubeUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static partial class YoutubeUtils
{
private const string BrowserUserAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0 Safari/537.36";
private const string YouTubeConsentBypassQuery = "?cbrd=1&ucbcb=1";

[GeneratedRegex("^UC[0-9A-Za-z_-]{22}$")]
private static partial Regex ChannelIdRegex();
Expand Down Expand Up @@ -130,15 +131,15 @@ public static partial class YoutubeUtils
if (!IsYoutubeHost(uri.Host) || !IsSupportedYoutubePath(uri.AbsolutePath))
return null;

// Canonicalize the host and discard user-controlled query/fragment values. The
// resolver only needs the channel path to scrape the corresponding YouTube page.
return new Uri($"https://www.youtube.com{uri.AbsolutePath}");
// cbrd/ucbcb keeps unauthenticated server-side requests on the channel page
// instead of redirecting them to YouTube's consent page.
return new Uri($"https://www.youtube.com{uri.AbsolutePath}{YouTubeConsentBypassQuery}");
}

private static Uri? BuildHandleUri(string handle) =>
string.IsNullOrWhiteSpace(handle) || handle.Contains('/')
? null
: new Uri($"https://www.youtube.com/@{Uri.EscapeDataString(handle)}");
: new Uri($"https://www.youtube.com/@{Uri.EscapeDataString(handle)}{YouTubeConsentBypassQuery}");

private static bool IsYoutubeHost(string host) =>
host.Equals("youtube.com", StringComparison.OrdinalIgnoreCase) ||
Expand Down