Skip to content
Open
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
47 changes: 47 additions & 0 deletions YoutubeExplode.Tests/SearchSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using FluentAssertions;
using Xunit;
using YoutubeExplode.Common;
using YoutubeExplode.Search;

namespace YoutubeExplode.Tests;

Expand Down Expand Up @@ -125,4 +126,50 @@ public async Task I_can_get_channel_results_from_a_search_query()
// Assert
channels.Should().NotBeEmpty();
}

[Fact]
public async Task I_can_get_results_from_a_search_query_with_localization()
{
// Arrange
using var youtube = new YoutubeClient();

// Act
var results = await youtube.Search.GetResultsAsync("news", SearchFilter.None, "uk", "UA");

// Assert
results.Should().NotBeEmpty();
}

[Fact]
public async Task I_can_get_video_results_from_a_search_query_with_localization()
{
// Arrange
using var youtube = new YoutubeClient();

// Act
var videos = await youtube.Search.GetVideosAsync("music", "fr", "FR");

// Assert
videos.Should().NotBeEmpty();
}

[Fact]
public async Task I_can_get_results_from_a_search_query_with_advanced_filters()
{
// Arrange
using var youtube = new YoutubeClient();

var filter = new SearchFilter(
Type: SearchFilterType.Video,
UploadDate: SearchFilterUploadDate.ThisWeek,
Duration: SearchFilterDuration.Short,
SortBy: SearchFilterSortBy.UploadDate
);

// Act
var results = await youtube.Search.GetResultsAsync("news", filter);

// Assert
results.Should().NotBeEmpty();
}
}
37 changes: 21 additions & 16 deletions YoutubeExplode/Search/SearchClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public class SearchClient(HttpClient http)
/// </summary>
public async IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(
string searchQuery,
SearchFilter searchFilter,
SearchFilter searchFilter = default,
string hl = "en",
string gl = "US",
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
Comment on lines 25 to 31
{
Expand All @@ -39,13 +41,15 @@ public async IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(
searchQuery,
searchFilter,
continuationToken,
hl,
gl,
cancellationToken
);

// Video results
foreach (var videoData in searchResults.Videos)
{
if (searchFilter is not SearchFilter.None and not SearchFilter.Video)
if (searchFilter.Type is not null && searchFilter.Type != SearchFilterType.Video)
{
Debug.Fail("Did not expect videos in search results.");
break;
Expand Down Expand Up @@ -120,7 +124,7 @@ public async IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(
// Playlist results
foreach (var playlistData in searchResults.Playlists)
{
if (searchFilter is not SearchFilter.None and not SearchFilter.Playlist)
if (searchFilter.Type is not null && searchFilter.Type != SearchFilterType.Playlist)
{
Debug.Fail("Did not expect playlists in search results.");
break;
Expand Down Expand Up @@ -185,7 +189,7 @@ public async IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(
// Channel results
foreach (var channelData in searchResults.Channels)
{
if (searchFilter is not SearchFilter.None and not SearchFilter.Channel)
if (searchFilter.Type is not null && searchFilter.Type != SearchFilterType.Channel)
{
Debug.Fail("Did not expect channels in search results.");
break;
Expand Down Expand Up @@ -237,30 +241,27 @@ public async IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(
} while (!string.IsNullOrWhiteSpace(continuationToken));
}

/// <summary>
/// Enumerates batches of search results returned by the specified query.
/// </summary>
public IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(
string searchQuery,
CancellationToken cancellationToken = default
) => GetResultBatchesAsync(searchQuery, SearchFilter.None, cancellationToken);

/// <summary>
/// Enumerates search results returned by the specified query.
/// </summary>
public IAsyncEnumerable<ISearchResult> GetResultsAsync(
string searchQuery,
SearchFilter searchFilter = default,
string hl = "en",
string gl = "US",
Comment on lines +250 to +251

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does localization affect only how the results are presented, or does it filter by language?

If it filters too, then it should probably be included within SearchFilter.

If it's just for presetation, then it would make sense to configure it on the *Client object, since it can probably be relevant to all requests.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It affects the search results, which is useful if you actually want a spanish video, so maybe SearchFilter would be better.

CancellationToken cancellationToken = default
) => GetResultBatchesAsync(searchQuery, cancellationToken).FlattenAsync();
) => GetResultBatchesAsync(searchQuery, searchFilter, hl, gl, cancellationToken).FlattenAsync();

/// <summary>
/// Enumerates video search results returned by the specified query.
/// </summary>
public IAsyncEnumerable<VideoSearchResult> GetVideosAsync(
string searchQuery,
string hl = "en",
string gl = "US",
CancellationToken cancellationToken = default
) =>
GetResultBatchesAsync(searchQuery, SearchFilter.Video, cancellationToken)
GetResultBatchesAsync(searchQuery, SearchFilter.Video, hl, gl, cancellationToken)
.FlattenAsync()
.OfTypeAsync<VideoSearchResult>();

Expand All @@ -269,9 +270,11 @@ public IAsyncEnumerable<VideoSearchResult> GetVideosAsync(
/// </summary>
public IAsyncEnumerable<PlaylistSearchResult> GetPlaylistsAsync(
string searchQuery,
string hl = "en",
string gl = "US",
CancellationToken cancellationToken = default
) =>
GetResultBatchesAsync(searchQuery, SearchFilter.Playlist, cancellationToken)
GetResultBatchesAsync(searchQuery, SearchFilter.Playlist, hl, gl, cancellationToken)
.FlattenAsync()
.OfTypeAsync<PlaylistSearchResult>();

Expand All @@ -280,9 +283,11 @@ public IAsyncEnumerable<PlaylistSearchResult> GetPlaylistsAsync(
/// </summary>
public IAsyncEnumerable<ChannelSearchResult> GetChannelsAsync(
string searchQuery,
string hl = "en",
string gl = "US",
CancellationToken cancellationToken = default
) =>
GetResultBatchesAsync(searchQuery, SearchFilter.Channel, cancellationToken)
GetResultBatchesAsync(searchQuery, SearchFilter.Channel, hl, gl, cancellationToken)
.FlattenAsync()
.OfTypeAsync<ChannelSearchResult>();
}
17 changes: 8 additions & 9 deletions YoutubeExplode/Search/SearchController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public async ValueTask<SearchResponse> GetSearchResponseAsync(
string searchQuery,
SearchFilter searchFilter,
string? continuationToken,
string hl,
string gl,
CancellationToken cancellationToken = default
)
{
Expand All @@ -20,25 +22,22 @@ public async ValueTask<SearchResponse> GetSearchResponseAsync(
"https://www.youtube.com/youtubei/v1/search"
);

var filter = searchFilter.ToString();

request.Content = new StringContent(
// lang=json
$$"""
{
"query": {{Json.Encode(searchQuery)}},
"params": {{Json.Encode(searchFilter switch
{
SearchFilter.Video => "EgIQAQ%3D%3D",
SearchFilter.Playlist => "EgIQAw%3D%3D",
SearchFilter.Channel => "EgIQAg%3D%3D",
_ => null
})}},
"params": {{Json.Encode(!string.IsNullOrWhiteSpace(filter) ? filter : null)}},
"continuation": {{Json.Encode(continuationToken)}},
"context": {
"client": {
"clientName": "WEB",
"clientVersion": "2.20210408.08.00",
"hl": "en",
"gl": "US",
"hl": {{Json.Encode(hl)}},
"gl": {{Json.Encode(gl)}},
"persist_hl": "1",
"utcOffsetMinutes": 0
}
}
Expand Down
Loading