YouTube Extension V0 Complete

This commit is contained in:
Ethan Fang
2024-09-19 01:31:48 -05:00
parent e9ee4046df
commit 0a6b500e1a
13 changed files with 499 additions and 111 deletions

View File

@@ -10,7 +10,7 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.CmdPal.Extensions.Helpers;
namespace YouTubeExtension.Helper;
namespace YouTubeExtension.Actions;
internal sealed partial class GetVideoInfoAction : InvokableCommand
{

View File

@@ -10,22 +10,22 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.CmdPal.Extensions.Helpers;
namespace YouTubeExtension.Helper;
namespace YouTubeExtension.Actions;
internal sealed partial class OpenChannelLinkAction : InvokableCommand
{
private readonly YouTubeVideo _video;
private readonly string _channelurl;
internal OpenChannelLinkAction(YouTubeVideo video)
internal OpenChannelLinkAction(string url)
{
this._video = video;
this._channelurl = url;
this.Name = "Open channel";
this.Icon = new("\uF131");
}
public override CommandResult Invoke()
{
Process.Start(new ProcessStartInfo(_video.ChannelUrl) { UseShellExecute = true });
Process.Start(new ProcessStartInfo(_channelurl) { UseShellExecute = true });
return CommandResult.KeepOpen();
}
}

View File

@@ -10,22 +10,22 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.CmdPal.Extensions.Helpers;
namespace YouTubeExtension.Helper;
namespace YouTubeExtension.Actions;
internal sealed partial class OpenVideoLinkAction : InvokableCommand
{
private readonly YouTubeVideo _video;
private readonly string _videourl;
internal OpenVideoLinkAction(YouTubeVideo video)
internal OpenVideoLinkAction(string url)
{
this._video = video;
this._videourl = url;
this.Name = "Open video";
this.Icon = new("\uE714");
}
public override CommandResult Invoke()
{
Process.Start(new ProcessStartInfo(_video.Link) { UseShellExecute = true });
Process.Start(new ProcessStartInfo(_videourl) { UseShellExecute = true });
return CommandResult.KeepOpen();
}
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace YouTubeExtension.Actions;
public sealed class YouTubeChannel
{
// The name of the channel
public string Name { get; set; } = string.Empty;
// The unique id of the channel
public string ChannelId { get; set; } = string.Empty;
// The URL link to the channel
public string ChannelUrl { get; set; } = string.Empty;
// The URL to the profile picture of the channel
public string ProfilePicUrl { get; set; } = string.Empty;
// The description of the channel
public string Description { get; set; } = string.Empty;
// Number of subscribers the channel has
public long SubscriberCount { get; set; }
// Number of views the channel has
public long ViewCount { get; set; }
// Number of videos the channel has
public long VideoCount { get; set; }
}

View File

@@ -9,7 +9,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace YouTubeExtension.Helper;
namespace YouTubeExtension.Actions;
internal sealed class YouTubeHelper
{

View File

@@ -8,31 +8,46 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace YouTubeExtension.Helper;
namespace YouTubeExtension.Actions;
public sealed class YouTubeVideo
{
// The title of the video
public string Title { get; init; } = string.Empty;
public string Title { get; set; } = string.Empty;
// The unique id of the video
public string VideoId { get; set; } = string.Empty;
// The URL link to the video
public string Link { get; init; } = string.Empty;
public string Link { get; set; } = string.Empty;
// The author or channel name of the video
public string Author { get; init; } = string.Empty;
public string Channel { get; set; } = string.Empty;
// The channel id (needed for the channel URL)
public string ChannelId { get; set; }
public string ChannelId { get; set; } = string.Empty;
// The URL link to the channel
public string ChannelUrl { get; set; }
public string ChannelUrl { get; set; } = string.Empty;
// The URL to the profile picture of the channel
public string ChannelProfilePicUrl { get; set; }
// Number of subscribers the channel has
public long SubscriberCount { get; set; }
// The URL to the thumbnail image of the video
public string ThumbnailUrl { get; init; } = string.Empty;
public string ThumbnailUrl { get; set; } = string.Empty;
// Captions or subtitles associated with the video
public string Captions { get; init; } = string.Empty;
public string Caption { get; set; } = string.Empty;
// The date and time the video was published
public DateTime PublishedAt { get; set; }
public DateTime PublishedAt { get; set; } = DateTime.MinValue;
// Number of views the video has
public long ViewCount { get; set; }
// Number of likes the video has
public long LikeCount { get; set; }
}

View File

@@ -11,7 +11,7 @@ using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.CmdPal.Extensions.Helpers;
using YouTubeExtension.Helper;
using YouTubeExtension.Actions;
namespace YouTubeExtension.Pages;

View File

@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Net.Http;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
using YouTubeExtension.Actions;
namespace YouTubeExtension.Pages;
internal sealed partial class YouTubeChannelInfoMarkdownPage : MarkdownPage
{
private readonly YouTubeChannel _channel;
private string _markdown = string.Empty;
public YouTubeChannelInfoMarkdownPage(YouTubeChannel channel)
{
Icon = new("\uE946");
Name = "See more information about this channel";
_channel = channel;
}
public override string[] Bodies()
{
var state = File.ReadAllText(YouTubeHelper.StateJsonPath());
var jsonState = JsonNode.Parse(state);
var apiKey = jsonState["apiKey"]?.ToString() ?? string.Empty;
FillInChannelDetailsAsync(_channel, apiKey).GetAwaiter().GetResult();
// Define the markdown content using the channel information
_markdown = $@"
# {_channel.Name}
![Profile Picture]({_channel.ProfilePicUrl})
---
**Channel Description**
{_channel.Description}
---
**Key Stats**
- **Subscribers:** {_channel.SubscriberCount:N0}
- **Total Views:** {_channel.ViewCount:N0}
- **Total Videos:** {_channel.VideoCount:N0}
[Visit Channel]({_channel.ChannelUrl})
---
_Last updated: {DateTime.Now:MMMM dd, yyyy}_
_Data sourced via YouTube API_
";
return new string[] { _markdown };
}
private async Task<YouTubeChannel> FillInChannelDetailsAsync(YouTubeChannel channel, string apiKey)
{
try
{
using var httpClient = new HttpClient();
// Fetch channel details from YouTube API
var channelUrl = $"https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics&id={channel.ChannelId}&key={apiKey}";
var channelResponse = await httpClient.GetStringAsync(channelUrl);
var channelData = JsonNode.Parse(channelResponse);
if (channelData?["items"]?.AsArray().Count > 0)
{
var channelSnippet = channelData?["items"]?[0]?["snippet"];
var channelStatistics = channelData?["items"]?[0]?["statistics"];
// Update statistics
channel.ViewCount = long.TryParse(channelStatistics?["viewCount"]?.ToString(), out var views) ? views : 0;
channel.VideoCount = long.TryParse(channelStatistics?["videoCount"]?.ToString(), out var videos) ? videos : 0;
}
}
catch (Exception ex)
{
// Handle exceptions (e.g., log the error)
Console.WriteLine($"An error occurred while fetching channel details: {ex.Message}");
}
return channel;
}
}

View File

@@ -0,0 +1,145 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
using YouTubeExtension.Actions;
namespace YouTubeExtension.Pages;
internal sealed partial class YouTubeChannelVideosPage : DynamicListPage
{
private readonly string _channelId;
private readonly string _channelName;
public YouTubeChannelVideosPage(string channelId = null, string channelName = null)
{
Icon = new("https://www.youtube.com/favicon.ico");
Name = $"YouTube ({channelName ?? "Channel Video Search"})";
this.ShowDetails = true;
// Ensure either a ChannelId or ChannelName is provided
if (string.IsNullOrEmpty(channelId) && string.IsNullOrEmpty(channelName))
{
throw new ArgumentException("Either channelId or channelName must be provided.");
}
_channelId = channelId;
_channelName = channelName;
}
public override ISection[] GetItems(string query)
{
return DoGetItems(query).GetAwaiter().GetResult(); // Fetch and await the task synchronously
}
private async Task<ISection[]> DoGetItems(string query)
{
// Fetch YouTube videos scoped to the channel
List<YouTubeVideo> items = await GetYouTubeChannelVideos(query, _channelId, _channelName);
// Create a section and populate it with the video results
var section = new ListSection()
{
Title = $"Videos from {_channelName ?? _channelId}",
Items = items.Select(video => new ListItem(new OpenVideoLinkAction(video.Link))
{
Title = video.Title,
Subtitle = $"{video.Channel}",
Details = new Details()
{
Title = video.Title,
HeroImage = new(video.ThumbnailUrl),
Body = $"{video.Channel}",
},
Tags = [
new Tag()
{
Text = video.PublishedAt.ToString("MMMM dd, yyyy", CultureInfo.InvariantCulture), // Show the date of the video post
}
],
MoreCommands = [
new CommandContextItem(new OpenChannelLinkAction(video.ChannelUrl)),
new CommandContextItem(new YouTubeVideoInfoMarkdownPage(video)),
new CommandContextItem(new YouTubeAPIPage()),
],
}).ToArray(),
};
return new[] { section }; // Properly return an array of sections
}
// Method to fetch videos from a specific channel
private static async Task<List<YouTubeVideo>> GetYouTubeChannelVideos(string query, string channelId, string channelName)
{
var state = File.ReadAllText(YouTubeHelper.StateJsonPath());
var jsonState = JsonNode.Parse(state);
var apiKey = jsonState["apiKey"]?.ToString() ?? string.Empty;
var videos = new List<YouTubeVideo>();
using HttpClient client = new HttpClient();
{
try
{
// Build the YouTube API URL for fetching channel-specific videos
string requestUrl;
if (!string.IsNullOrEmpty(channelId))
{
// If ChannelId is provided, filter by channelId
requestUrl = $"https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&channelId={channelId}&q={query}&key={apiKey}&maxResults=10";
}
else
{
// If ChannelName is provided, search by the channel name
requestUrl = $"https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&q={query}+{channelName}&key={apiKey}&maxResults=10";
}
// Send the request to the YouTube API
var response = await client.GetStringAsync(requestUrl);
var json = JsonNode.Parse(response);
// Parse the response
if (json?["items"] is JsonArray itemsArray)
{
foreach (var item in itemsArray)
{
// Add each video to the list with title, link, author, thumbnail, and captions (if available)
videos.Add(new YouTubeVideo
{
Title = item["snippet"]?["title"]?.ToString() ?? string.Empty,
VideoId = item["id"]?["videoId"]?.ToString() ?? string.Empty,
Link = $"https://www.youtube.com/watch?v={item["id"]?["videoId"]?.ToString()}",
Channel = item["snippet"]?["channelTitle"]?.ToString() ?? string.Empty,
ChannelId = item["snippet"]?["channelId"]?.ToString() ?? string.Empty,
ChannelUrl = $"https://www.youtube.com/channel/{item["snippet"]?["channelId"]?.ToString()}" ?? string.Empty,
ThumbnailUrl = item["snippet"]?["thumbnails"]?["default"]?["url"]?.ToString() ?? string.Empty, // Get the default thumbnail URL
PublishedAt = DateTime.Parse(item["snippet"]?["publishedAt"]?.ToString(), CultureInfo.InvariantCulture), // Use CultureInfo.InvariantCulture
});
}
}
}
catch (Exception ex)
{
// Handle any errors from the API call or parsing
videos.Add(new YouTubeVideo
{
Title = "Error fetching data",
Channel = $"Error: {ex.Message}",
});
}
}
return videos;
}
}

View File

@@ -8,13 +8,11 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
using YouTubeExtension.Helper;
using YouTubeExtension.Actions;
namespace YouTubeExtension.Pages;
@@ -23,7 +21,7 @@ internal sealed partial class YouTubeChannelsPage : DynamicListPage
public YouTubeChannelsPage()
{
Icon = new("https://www.youtube.com/favicon.ico");
Name = "YouTube";
Name = "YouTube (Channel Search)";
this.ShowDetails = true;
}
@@ -34,49 +32,49 @@ internal sealed partial class YouTubeChannelsPage : DynamicListPage
private async Task<ISection[]> DoGetItems(string query)
{
// Fetch YouTube videos based on the query
List<YouTubeVideo> items = await GetYouTubeVideos(query);
// Fetch YouTube channels based on the query
List<YouTubeChannel> items = await GetYouTubeChannels(query);
// Create a section and populate it with the video results
// Create a section and populate it with the channel results
var section = new ListSection()
{
Title = "Search Results",
Items = items.Select(video => new ListItem(new OpenVideoLinkAction(video))
Items = items.Select(channel => new ListItem(new OpenChannelLinkAction(channel.ChannelUrl))
{
Title = video.Title,
Subtitle = $"{video.Author}",
Title = channel.Name,
Subtitle = $"{channel.SubscriberCount} subscribers",
Details = new Details()
{
Title = video.Title,
HeroImage = new(video.ThumbnailUrl),
Body = $"{video.Author}",
Title = channel.Name,
HeroImage = new(channel.ProfilePicUrl),
Body = $"Subscribers: {channel.SubscriberCount}\nChannel Description: {channel.Description}",
},
Tags = [new Tag()
{
Text = video.PublishedAt.ToString("MMMM dd, yyyy", CultureInfo.InvariantCulture), // Show the date of the video post
}
],
MoreCommands = [
new CommandContextItem(new YouTubeChannelInfoMarkdownPage(channel)),
new CommandContextItem(new YouTubeChannelVideosPage(channel.ChannelId, channel.Name)),
new CommandContextItem(new YouTubeAPIPage()),
],
}).ToArray(),
};
return new[] { section }; // Properly return an array of sections
}
// Method to fetch videos from YouTube API
private static async Task<List<YouTubeVideo>> GetYouTubeVideos(string query)
// Method to fetch channels from YouTube API
private static async Task<List<YouTubeChannel>> GetYouTubeChannels(string query)
{
var state = File.ReadAllText(YouTubeHelper.StateJsonPath());
var jsonState = JsonNode.Parse(state);
var apiKey = jsonState["apiKey"]?.ToString() ?? string.Empty;
var videos = new List<YouTubeVideo>();
var channels = new List<YouTubeChannel>();
using HttpClient client = new HttpClient();
using (HttpClient client = new HttpClient())
{
try
{
// Send the request to the YouTube API with the provided query
var response = await client.GetStringAsync($"https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&q={query}&key={apiKey}&maxResults=2");
// Send the request to the YouTube API with the provided query to search for channels
var response = await client.GetStringAsync($"https://www.googleapis.com/youtube/v3/search?part=snippet&type=channel&q={query}&key={apiKey}&maxResults=5");
var json = JsonNode.Parse(response);
// Parse the response
@@ -84,15 +82,15 @@ internal sealed partial class YouTubeChannelsPage : DynamicListPage
{
foreach (var item in itemsArray)
{
// Add each video to the list with title, link, author, thumbnail, and captions (if available)
videos.Add(new YouTubeVideo
// Add each channel to the list with channel details
channels.Add(new YouTubeChannel
{
Title = item["snippet"]?["title"]?.ToString() ?? string.Empty,
Link = $"https://www.youtube.com/watch?v={item["id"]?["videoId"]?.ToString()}",
Author = item["snippet"]?["channelTitle"]?.ToString() ?? string.Empty,
ThumbnailUrl = item["snippet"]?["thumbnails"]?["default"]?["url"]?.ToString() ?? string.Empty, // Get the default thumbnail URL
Captions = "Captions not available", // Placeholder for captions; You can integrate with another API if needed
PublishedAt = DateTime.Parse(item["snippet"]?["publishedAt"]?.ToString(), CultureInfo.InvariantCulture), // Use CultureInfo.InvariantCulture
Name = item["snippet"]?["channelTitle"]?.ToString() ?? string.Empty,
ChannelId = item["snippet"]?["channelId"]?.ToString() ?? string.Empty,
ChannelUrl = $"https://www.youtube.com/channel/{item["snippet"]?["channelId"]?.ToString()}" ?? string.Empty,
ProfilePicUrl = item["snippet"]?["thumbnails"]?["default"]?["url"]?.ToString() ?? string.Empty, // Get the default profile picture
Description = item["snippet"]?["description"]?.ToString() ?? string.Empty,
SubscriberCount = await GetChannelSubscriberCount(apiKey, item["snippet"]?["channelId"]?.ToString()), // Fetch the subscriber count
});
}
}
@@ -100,18 +98,43 @@ internal sealed partial class YouTubeChannelsPage : DynamicListPage
catch (Exception ex)
{
// Handle any errors from the API call or parsing
videos.Add(new YouTubeVideo
channels.Add(new YouTubeChannel
{
Title = "Error fetching data",
Link = string.Empty,
Author = $"Error: {ex.Message}",
ThumbnailUrl = string.Empty,
Captions = string.Empty,
PublishedAt = DateTime.MinValue,
Name = "Error fetching data",
Description = $"Error: {ex.Message}",
});
}
}
return videos;
return channels;
}
// Fetch subscriber count for each channel using a separate API call
private static async Task<long> GetChannelSubscriberCount(string apiKey, string channelId)
{
using HttpClient client = new HttpClient();
{
try
{
var response = await client.GetStringAsync($"https://www.googleapis.com/youtube/v3/channels?part=statistics&id={channelId}&key={apiKey}");
var json = JsonNode.Parse(response);
if (json?["items"] is JsonArray itemsArray && itemsArray.Count > 0)
{
var statistics = itemsArray[0]?["statistics"];
if (long.TryParse(statistics?["subscriberCount"]?.ToString(), out var subscriberCount))
{
return subscriberCount;
}
}
}
catch
{
// In case of any error, return 0 subscribers
return 0;
}
}
return 0;
}
}

View File

@@ -3,49 +3,125 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.RegularExpressions;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
using Microsoft.UI.Windowing;
using YouTubeExtension.Actions;
namespace SamplePagesExtension;
namespace YouTubeExtension.Pages;
internal sealed partial class YouTubeVideoInfoMarkdownPage : MarkdownPage
{
private readonly string _markdown = @"
# Markdown Guide
private readonly YouTubeVideo _video;
private string _markdown = string.Empty;
Markdown is a lightweight markup language with plain text formatting syntax. It's often used to format readme files, for writing messages in online forums, and to create rich text using a simple, plain text editor.
---
## Headings
You can create headings using the `#` symbol, with the number of `#` determining the heading level.
```markdown
# H1 Heading
## H2 Heading
### H3 Heading
#### H4 Heading
";
public YouTubeVideoInfoMarkdownPage()
public YouTubeVideoInfoMarkdownPage(YouTubeVideo video)
{
Icon = new("\uE946");
Name = "See more information";
Name = "See more information about this video";
_video = video;
}
public override string[] Bodies()
{
return [_markdown];
var state = File.ReadAllText(YouTubeHelper.StateJsonPath());
var jsonState = JsonNode.Parse(state);
var apiKey = jsonState["apiKey"]?.ToString() ?? string.Empty;
FillInVideoDetailsAsync(_video, apiKey).GetAwaiter().GetResult();
// Refined markdown content for user-focused display
_markdown = $@"
# {_video.Title}
![Thumbnail]({_video.ThumbnailUrl})
---
**Video Description**
{_video.Caption}
---
**Key Stats**
- **Views:** {_video.ViewCount:N0}
- **Likes:** {_video.LikeCount:N0}
- **Published on:** {_video.PublishedAt:MMMM dd, yyyy}
---
**Channel Info**
- **Channel Name:** {_video.Channel}
- **Subscribers:** {_video.SubscriberCount:N0}
![Channel Profile Picture]({_video.ChannelProfilePicUrl})
[Visit Channel]({_video.ChannelUrl})
---
[Watch Video]({_video.Link})
---
_Last updated: {DateTime.Now:MMMM dd, yyyy}_
_Data sourced via YouTube API_
";
return new string[] { _markdown };
}
private async Task<YouTubeVideo> FillInVideoDetailsAsync(YouTubeVideo video, string apiKey)
{
try
{
using var httpClient = new HttpClient();
// Fetch channel details to get ChannelProfilePicUrl and SubscriberCount
var channelUrl = $"https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics&id={video.ChannelId}&key={apiKey}";
var channelResponse = await httpClient.GetStringAsync(channelUrl);
var channelData = JsonNode.Parse(channelResponse);
if (channelData?["items"]?.AsArray().Count > 0)
{
var channelSnippet = channelData?["items"]?[0]?["snippet"];
var channelStatistics = channelData?["items"]?[0]?["statistics"];
// Update ChannelProfilePicUrl and SubscriberCount
video.ChannelProfilePicUrl = channelSnippet?["thumbnails"]?["default"]?["url"]?.ToString() ?? string.Empty;
video.SubscriberCount = long.TryParse(channelStatistics?["subscriberCount"]?.ToString(), out var subscribers) ? subscribers : 0;
}
// Fetch video details to get ViewCount, LikeCount, and Captions (description)
var videoUrl = $"https://www.googleapis.com/youtube/v3/videos?part=statistics,snippet&id={video.VideoId}&key={apiKey}";
var videoResponse = await httpClient.GetStringAsync(videoUrl);
var videoData = JsonNode.Parse(videoResponse);
if (videoData?["items"]?.AsArray().Count > 0)
{
var videoSnippet = videoData?["items"]?[0]?["snippet"];
var videoStatistics = videoData?["items"]?[0]?["statistics"];
// Update ViewCount and LikeCount
video.ViewCount = long.TryParse(videoStatistics?["viewCount"]?.ToString(), out var views) ? views : 0;
video.LikeCount = long.TryParse(videoStatistics?["likeCount"]?.ToString(), out var likes) ? likes : 0;
// Update Captions (description)
video.Caption = videoSnippet?["description"]?.ToString() ?? string.Empty;
}
}
catch (Exception ex)
{
// Handle errors
Console.WriteLine($"An error occurred while fetching video details: {ex.Message}");
}
return video;
}
}

View File

@@ -13,18 +13,16 @@ using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
using SamplePagesExtension;
using YouTubeExtension.Helper;
using YouTubeExtension.Pages;
using YouTubeExtension.Actions;
namespace YouTubeExtension;
namespace YouTubeExtension.Pages;
internal sealed partial class YouTubeVideosPage : DynamicListPage
{
public YouTubeVideosPage()
{
Icon = new("https://www.youtube.com/favicon.ico");
Name = "YouTube";
Name = "YouTube (Video Search)";
this.ShowDetails = true;
}
@@ -42,15 +40,15 @@ internal sealed partial class YouTubeVideosPage : DynamicListPage
var section = new ListSection()
{
Title = "Search Results",
Items = items.Select(video => new ListItem(new OpenVideoLinkAction(video))
Items = items.Select(video => new ListItem(new OpenVideoLinkAction(video.Link))
{
Title = video.Title,
Subtitle = $"{video.Author}",
Subtitle = $"{video.Channel}",
Details = new Details()
{
Title = video.Title,
HeroImage = new(video.ThumbnailUrl),
Body = $"{video.Author}",
Body = $"{video.Channel}",
},
Tags = [new Tag()
{
@@ -58,8 +56,8 @@ internal sealed partial class YouTubeVideosPage : DynamicListPage
}
],
MoreCommands = [
new CommandContextItem(new OpenChannelLinkAction(video)),
new CommandContextItem(new YouTubeVideoInfoMarkdownPage()),
new CommandContextItem(new OpenChannelLinkAction(video.ChannelUrl)),
new CommandContextItem(new YouTubeVideoInfoMarkdownPage(video)),
new CommandContextItem(new YouTubeAPIPage()),
],
}).ToArray(),
@@ -77,7 +75,7 @@ internal sealed partial class YouTubeVideosPage : DynamicListPage
var videos = new List<YouTubeVideo>();
using (HttpClient client = new HttpClient())
using HttpClient client = new HttpClient();
{
try
{
@@ -94,12 +92,12 @@ internal sealed partial class YouTubeVideosPage : DynamicListPage
videos.Add(new YouTubeVideo
{
Title = item["snippet"]?["title"]?.ToString() ?? string.Empty,
VideoId = item["id"]?["videoId"]?.ToString() ?? string.Empty,
Link = $"https://www.youtube.com/watch?v={item["id"]?["videoId"]?.ToString()}",
Author = item["snippet"]?["channelTitle"]?.ToString() ?? string.Empty,
Channel = item["snippet"]?["channelTitle"]?.ToString() ?? string.Empty,
ChannelId = item["snippet"]?["channelId"]?.ToString() ?? string.Empty,
ChannelUrl = $"https://www.youtube.com/channel/{item["snippet"]?["channelId"]?.ToString()}" ?? string.Empty,
ThumbnailUrl = item["snippet"]?["thumbnails"]?["default"]?["url"]?.ToString() ?? string.Empty, // Get the default thumbnail URL
Captions = "Captions not available", // Placeholder for captions; You can integrate with another API if needed
PublishedAt = DateTime.Parse(item["snippet"]?["publishedAt"]?.ToString(), CultureInfo.InvariantCulture), // Use CultureInfo.InvariantCulture
});
}
@@ -111,11 +109,7 @@ internal sealed partial class YouTubeVideosPage : DynamicListPage
videos.Add(new YouTubeVideo
{
Title = "Error fetching data",
Link = string.Empty,
Author = $"Error: {ex.Message}",
ThumbnailUrl = string.Empty,
Captions = string.Empty,
PublishedAt = DateTime.MinValue,
Channel = $"Error: {ex.Message}",
});
}
}

View File

@@ -12,7 +12,7 @@ using System.Threading.Tasks;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
using Windows.UI.ApplicationSettings;
using YouTubeExtension.Helper;
using YouTubeExtension.Actions;
using YouTubeExtension.Pages;
namespace YouTubeExtension;
@@ -24,8 +24,8 @@ public partial class YouTubeExtensionActionsProvider : ICommandProvider
public IconDataType Icon => new(string.Empty);
private readonly IListItem[] _commands = [
new ListItem(new YouTubeVideosPage()) { Title = "Search Videos", Subtitle = "YouTube" },
new ListItem(new YouTubeChannelsPage()) { Title = "Search Channels", Subtitle = "YouTube" },
new ListItem(new YouTubeVideosPage()) { Title = "Search Videos on YouTube", Subtitle = "YouTube" },
new ListItem(new YouTubeChannelsPage()) { Title = "Search Channels on YouTube", Subtitle = "YouTube" },
];
private readonly YouTubeAPIPage apiPage = new();