📄 src/Infrastructure/YouTube/YouTubeUploader.cs
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Upload;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Slopper.Domain;

namespace Slopper.Infrastructure.YouTube;

internal sealed class YouTubeUploader(TimeProvider timeProvider, YouTubeService youTubeService) : IUploader
{
    public async Task<Upload> Upload(Clip clip, DateTimeOffset publishAt, CancellationToken cancellationToken)
    {
        var video = new Video()
        {
            Snippet = new()
            {
                Title = clip.Caption,
                Tags = [.. clip.Tags.Select(t => t.Value)],
                CategoryId = "1",
            },
            Status = new() { PrivacyStatus = "private", PublishAtDateTimeOffset = publishAt },
        };

        (string, DateTimeOffset)? result = null;
        using (var videoStream = File.OpenRead(clip.Path))
        {
            var request = youTubeService.Videos.Insert(video, "snippet,status", videoStream, "video/mp4");
            request.ResponseReceived += v =>
            {
                if (v.Snippet.PublishedAtDateTimeOffset is not { } publishedAt)
                {
                    throw new Exception("Received no published at datetime from YouTube API");
                }
                result = (v.Id, publishedAt);
            };
            var progress = await request.UploadAsync(cancellationToken);
            progress.ThrowOnFailure();
        }

        if (result is not var (id, publishedAt))
        {
            throw new Exception("Received no result from YouTube upload");
        }

        return new(new($"https://www.youtube.com/shorts/{id}"), timeProvider.GetUtcNow(), publishedAt, "YouTube");
    }
}