From 32f0bc07b8a0e7fae91564d4e618dd664565a23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E9=88=9E?= Date: Sat, 2 Dec 2023 20:51:23 +0800 Subject: [PATCH 1/2] Hello .NET 8! --- GlobalSuppressions.cs | 8 +++ Helper.cs | 24 +++---- LiveChatMonitorWorker.cs | 107 ++++++++++++++-------------- Program.cs | 3 +- Services/LiveChatDownloadService.cs | 50 ++++++------- YoutubeLiveChatToDiscord.csproj | 13 ++-- 6 files changed, 102 insertions(+), 103 deletions(-) create mode 100644 GlobalSuppressions.cs diff --git a/GlobalSuppressions.cs b/GlobalSuppressions.cs new file mode 100644 index 0000000..3530bc6 --- /dev/null +++ b/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0046:轉換至條件運算式", Justification = "<暫止>", Scope = "member", Target = "~M:YoutubeLiveChatToDiscord.Helper.GetOriginalImage(System.String)~System.String")] diff --git a/Helper.cs b/Helper.cs index bb68996..a8aaf4e 100644 --- a/Helper.cs +++ b/Helper.cs @@ -15,7 +15,7 @@ internal static class ApplicationLogging internal static ILogger CreateLogger(string categoryName) => LoggerFactory.CreateLogger(categoryName); } - private static readonly ILogger logger = ApplicationLogging.CreateLogger("Helper"); + private static readonly ILogger _logger = ApplicationLogging.CreateLogger("Helper"); /// /// 尋找yt-dlp程式路徑 @@ -25,14 +25,14 @@ public static string WhereIsYt_dlp() { // https://stackoverflow.com/a/63021455 string file = "yt-dlp"; - string[] paths = Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? Array.Empty(); - string[] extensions = Environment.GetEnvironmentVariable("PATHEXT")?.Split(';') ?? Array.Empty(); + string[] paths = Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? []; + string[] extensions = Environment.GetEnvironmentVariable("PATHEXT")?.Split(';') ?? []; string YtdlPath = (from p in new[] { Environment.CurrentDirectory }.Concat(paths) from e in extensions let path = Path.Combine(p.Trim(), file + e.ToLower()) where File.Exists(path) select path)?.FirstOrDefault() ?? "/usr/bin/yt-dlp"; - logger.LogDebug("Found yt-dlp at {path}", YtdlPath); + _logger.LogDebug("Found yt-dlp at {path}", YtdlPath); return YtdlPath; } @@ -42,33 +42,31 @@ where File.Exists(path) /// /// internal static Task DiscordWebhookClient_Log(LogMessage arg) - { - return Task.Run(() => + => Task.Run(() => { switch (arg.Severity) { case LogSeverity.Critical: - logger.LogCritical("{message}", arg); + _logger.LogCritical("{message}", arg); break; case LogSeverity.Error: - logger.LogError("{message}", arg); + _logger.LogError("{message}", arg); break; case LogSeverity.Warning: - logger.LogWarning("{message}", arg); + _logger.LogWarning("{message}", arg); break; case LogSeverity.Info: - logger.LogInformation("{message}", arg); + _logger.LogInformation("{message}", arg); break; case LogSeverity.Verbose: - logger.LogTrace("{message}", arg); + _logger.LogTrace("{message}", arg); break; case LogSeverity.Debug: default: - logger.LogDebug("{message}", arg); + _logger.LogDebug("{message}", arg); break; } }); - } /// /// 處理Youtube的圖片url,取得原始尺寸圖片 diff --git a/LiveChatMonitorWorker.cs b/LiveChatMonitorWorker.cs index ed7f44f..2c3b3bb 100644 --- a/LiveChatMonitorWorker.cs +++ b/LiveChatMonitorWorker.cs @@ -8,26 +8,26 @@ namespace YoutubeLiveChatToDiscord { public class LiveChatMonitorWorker : BackgroundService { - private readonly ILogger logger; - private readonly string id; - private readonly DiscordWebhookClient client; - private readonly FileInfo liveChatFileInfo; - private long position = 0; - private readonly LiveChatDownloadService liveChatDownloadService; + private readonly ILogger _logger; + private readonly string _id; + private readonly DiscordWebhookClient _client; + private readonly FileInfo _liveChatFileInfo; + private long _position = 0; + private readonly LiveChatDownloadService _liveChatDownloadService; public LiveChatMonitorWorker( - ILogger _logger, - DiscordWebhookClient _client, - LiveChatDownloadService _liveChatDownloadService + ILogger logger, + DiscordWebhookClient client, + LiveChatDownloadService liveChatDownloadService ) { - (logger, client, liveChatDownloadService) = (_logger, _client, _liveChatDownloadService); - client.Log += Helper.DiscordWebhookClient_Log; + (_logger, _client, _liveChatDownloadService) = (logger, client, liveChatDownloadService); + _client.Log += Helper.DiscordWebhookClient_Log; - id = Environment.GetEnvironmentVariable("VIDEO_ID") ?? ""; - if (string.IsNullOrEmpty(id)) throw new ArgumentException(nameof(id)); + _id = Environment.GetEnvironmentVariable("VIDEO_ID") ?? ""; + if (string.IsNullOrEmpty(_id)) throw new ArgumentException(nameof(_id)); - liveChatFileInfo = new($"{id}.live_chat.json"); + _liveChatFileInfo = new($"{_id}.live_chat.json"); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -36,38 +36,35 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { - if (liveChatDownloadService.DownloadProcess.IsCompleted) + if (_liveChatDownloadService.downloadProcess.IsCompleted) { - _ = liveChatDownloadService.ExecuteAsync(stoppingToken) - .ContinueWith((_) => - { - logger.LogInformation("yt-dlp is stopped."); - }, stoppingToken); + _ = _liveChatDownloadService.ExecuteAsync(stoppingToken) + .ContinueWith((_) => _logger.LogInformation("yt-dlp is stopped."), stoppingToken); } - logger.LogInformation("Wait 10 seconds."); + _logger.LogInformation("Wait 10 seconds."); await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); - liveChatFileInfo.Refresh(); + _liveChatFileInfo.Refresh(); try { - if (!liveChatFileInfo.Exists) + if (!_liveChatFileInfo.Exists) { - throw new FileNotFoundException(null, liveChatFileInfo.FullName); + throw new FileNotFoundException(null, _liveChatFileInfo.FullName); } await Monitoring(stoppingToken); } catch (FileNotFoundException e) { - logger.LogWarning("Json file not found. {FileName}", e.FileName); + _logger.LogWarning("Json file not found. {FileName}", e.FileName); } } } catch (TaskCanceledException) { } finally { - logger.LogError("Wait 10 seconds before closing the program. This is to prevent a restart loop from hanging the machine."); + _logger.LogError("Wait 10 seconds before closing the program. This is to prevent a restart loop from hanging the machine."); #pragma warning disable CA2016 // 將 'CancellationToken' 參數轉送給方法 await Task.Delay(TimeSpan.FromSeconds(10)); #pragma warning restore CA2016 // 將 'CancellationToken' 參數轉送給方法 @@ -87,32 +84,32 @@ private async Task Monitoring(CancellationToken stoppingToken) #if !DEBUG if (null == Environment.GetEnvironmentVariable("SKIP_STARTUP_WAITING")) { - logger.LogInformation("Wait 1 miunute to skip old chats"); + _logger.LogInformation("Wait 1 miunute to skip old chats"); await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); - liveChatFileInfo.Refresh(); + _liveChatFileInfo.Refresh(); } #endif - position = liveChatFileInfo.Length; - logger.LogInformation("Start at position: {position}", position); - logger.LogInformation("Start Monitoring!"); + _position = _liveChatFileInfo.Length; + _logger.LogInformation("Start at position: {position}", _position); + _logger.LogInformation("Start Monitoring!"); while (!stoppingToken.IsCancellationRequested) { - liveChatFileInfo.Refresh(); - if (liveChatFileInfo.Length > position) + _liveChatFileInfo.Refresh(); + if (_liveChatFileInfo.Length > _position) { await ProcessChats(stoppingToken); } - else if (liveChatDownloadService.DownloadProcess.IsCompleted) + else if (_liveChatDownloadService.downloadProcess.IsCompleted) { - logger.LogInformation("Download process is stopped. Restart monitoring."); + _logger.LogInformation("Download process is stopped. Restart monitoring."); return; } else { - position = liveChatFileInfo.Length; - logger.LogTrace("No new chat. Wait 10 seconds."); + _position = _liveChatFileInfo.Length; + _logger.LogTrace("No new chat. Wait 10 seconds."); // 每10秒檢查一次json檔 await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); } @@ -127,14 +124,14 @@ private async Task Monitoring(CancellationToken stoppingToken) /// private async Task GetVideoInfo(CancellationToken stoppingToken) { - FileInfo videoInfo = new($"{id}.info.json"); + FileInfo videoInfo = new($"{_id}.info.json"); if (!videoInfo.Exists) { // Chat json file 在 VideoInfo json file之後被產生,理論上這段不會進來 throw new FileNotFoundException(null, videoInfo.FullName); } - Info? info = JsonConvert.DeserializeObject(await new StreamReader(videoInfo.OpenRead()).ReadToEndAsync()); + Info? info = JsonConvert.DeserializeObject(await new StreamReader(videoInfo.OpenRead()).ReadToEndAsync(stoppingToken)); string? Title = info?.title; string? ChannelId = info?.channel_id; string? thumb = info?.thumbnail; @@ -155,17 +152,17 @@ private async Task ProcessChats(CancellationToken stoppingToken) // 這是.NET Core在Linux、Windows上關於鎖定設計的描述: https://github.com/dotnet/runtime/pull/55256 // 如果要繞過這個問題,從.NET 6開始,可以加上環境變數「DOTNET_SYSTEM_IO_DISABLEFILELOCKING」讓FileStream「不」遵守鎖定。 // (本專案已在Dockerfile加上此環境變數) - using FileStream fs = new(liveChatFileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using FileStream fs = new(_liveChatFileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using StreamReader sr = new(fs); - sr.BaseStream.Seek(position, SeekOrigin.Begin); - while (position < sr.BaseStream.Length) + sr.BaseStream.Seek(_position, SeekOrigin.Begin); + while (_position < sr.BaseStream.Length) { string? str = ""; try { - str = await sr.ReadLineAsync(); - position = sr.BaseStream.Position; + str = await sr.ReadLineAsync(stoppingToken); + _position = sr.BaseStream.Position; if (string.IsNullOrEmpty(str)) continue; Chat? chat = JsonConvert.DeserializeObject(str); @@ -175,17 +172,17 @@ private async Task ProcessChats(CancellationToken stoppingToken) } catch (JsonSerializationException e) { - logger.LogError("{error}", e.Message); - logger.LogError("{originalString}", str); + _logger.LogError("{error}", e.Message); + _logger.LogError("{originalString}", str); } catch (ArgumentException e) { - logger.LogError("{error}", e.Message); - logger.LogError("{originalString}", str); + _logger.LogError("{error}", e.Message); + _logger.LogError("{originalString}", str); } catch (IOException e) { - logger.LogError("{error}", e.Message); + _logger.LogError("{error}", e.Message); break; } } @@ -202,7 +199,7 @@ private async Task BuildRequestAndSendToDiscord(Chat chat, CancellationToken sto { EmbedBuilder eb = new(); eb.WithTitle(Environment.GetEnvironmentVariable("TITLE") ?? "") - .WithUrl($"https://youtu.be/{id}") + .WithUrl($"https://youtu.be/{_id}") .WithThumbnailUrl(Helper.GetOriginalImage(Environment.GetEnvironmentVariable("VIDEO_THUMB"))); string author = ""; @@ -397,13 +394,13 @@ private async Task BuildRequestAndSendToDiscord(Chat chat, CancellationToken sto ) { return; } else { - logger.LogWarning("Message type not supported, skip sending to discord."); + _logger.LogWarning("Message type not supported, skip sending to discord."); throw new ArgumentException("Message type not supported", nameof(chat)); } if (stoppingToken.IsCancellationRequested) return; - logger.LogDebug("Sending Request to Discord: {author}: {message}", author, eb.Description); + _logger.LogDebug("Sending Request to Discord: {author}: {message}", author, eb.Description); try { @@ -420,15 +417,15 @@ private async Task BuildRequestAndSendToDiscord(Chat chat, CancellationToken sto // The rate for Discord webhooks are 30 requests/minute per channel. // Be careful when you run multiple instances in the same channel! - logger.LogTrace("Wait 2 seconds for discord webhook rate limit"); + _logger.LogTrace("Wait 2 seconds for discord webhook rate limit"); await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken); Task SendMessage() - => client.SendMessageAsync(embeds: new Embed[] { eb.Build() }) + => _client.SendMessageAsync(embeds: new Embed[] { eb.Build() }) .ContinueWith(async p => { ulong messageId = await p; - logger.LogDebug("Message sent to discord, message id: {messageId}", messageId); + _logger.LogDebug("Message sent to discord, message id: {messageId}", messageId); }, stoppingToken); } } diff --git a/Program.cs b/Program.cs index 48566b0..d7daae5 100644 --- a/Program.cs +++ b/Program.cs @@ -17,8 +17,7 @@ { services.AddHostedService() .AddSingleton() - .AddSingleton((service) => - new DiscordWebhookClient(Environment.GetEnvironmentVariable("WEBHOOK"))); + .AddSingleton((service) => new DiscordWebhookClient(Environment.GetEnvironmentVariable("WEBHOOK"))); }) .Build(); diff --git a/Services/LiveChatDownloadService.cs b/Services/LiveChatDownloadService.cs index e26a8fb..b04fb34 100644 --- a/Services/LiveChatDownloadService.cs +++ b/Services/LiveChatDownloadService.cs @@ -5,22 +5,22 @@ namespace YoutubeLiveChatToDiscord.Services; public class LiveChatDownloadService { - private readonly ILogger logger; - private readonly string id; - public Task DownloadProcess = Task.FromResult(0); + private readonly ILogger _logger; + private readonly string _id; + public Task downloadProcess = Task.FromResult(0); - public LiveChatDownloadService(ILogger _logger) + public LiveChatDownloadService(ILogger logger) { - logger = _logger; + _logger = logger; - id = Environment.GetEnvironmentVariable("VIDEO_ID") ?? ""; - if (string.IsNullOrEmpty(id)) throw new ArgumentException(nameof(id)); + _id = Environment.GetEnvironmentVariable("VIDEO_ID") ?? ""; + if (string.IsNullOrEmpty(_id)) throw new ArgumentException(nameof(_id)); } public Task ExecuteAsync(CancellationToken stoppingToken) { - DownloadProcess = ExecuteAsyncInternal(stoppingToken); - return DownloadProcess; + downloadProcess = ExecuteAsyncInternal(stoppingToken); + return downloadProcess; } private Task ExecuteAsyncInternal(CancellationToken stoppingToken) @@ -28,14 +28,14 @@ private Task ExecuteAsyncInternal(CancellationToken stoppingToken) OptionSet live_chatOptionSet = new() { IgnoreConfig = true, - WriteSub = true, - SubLang = "live_chat", + WriteSubs = true, + SubLangs = "live_chat", SkipDownload = true, NoPart = true, NoContinue = true, - Output = "%(id)s" + Output = "%(id)s", + IgnoreNoFormatsError = true }; - live_chatOptionSet.AddCustomOption("--ignore-no-formats-error", true); OptionSet info_jsonOptionSet = new() { @@ -43,9 +43,9 @@ private Task ExecuteAsyncInternal(CancellationToken stoppingToken) WriteInfoJson = true, SkipDownload = true, NoPart = true, - Output = "%(id)s" + Output = "%(id)s", + IgnoreNoFormatsError = true }; - info_jsonOptionSet.AddCustomOption("--ignore-no-formats-error", true); if (File.Exists("cookies.txt")) { @@ -54,18 +54,18 @@ private Task ExecuteAsyncInternal(CancellationToken stoppingToken) } YoutubeDLProcess ytdlProc = new(Helper.WhereIsYt_dlp()); - ytdlProc.OutputReceived += (o, e) => logger.LogTrace("{message}", e.Data); - ytdlProc.ErrorReceived += (o, e) => logger.LogError("{error}", e.Data); + ytdlProc.OutputReceived += (o, e) => _logger.LogTrace("{message}", e.Data); + ytdlProc.ErrorReceived += (o, e) => _logger.LogError("{error}", e.Data); - string url = $"https://www.youtube.com/watch?v={id}"; - logger.LogInformation("Start yt-dlp with url: {url}", url); + string url = $"https://www.youtube.com/watch?v={_id}"; + _logger.LogInformation("Start yt-dlp with url: {url}", url); return ytdlProc.RunAsync(new string[] { url }, - info_jsonOptionSet, - stoppingToken) - .ContinueWith((e) => ytdlProc.RunAsync(new string[] { url }, - live_chatOptionSet, - stoppingToken)) - .Unwrap(); + info_jsonOptionSet, + stoppingToken) + .ContinueWith((e) => ytdlProc.RunAsync(new string[] { url }, + live_chatOptionSet, + stoppingToken)) + .Unwrap(); } } diff --git a/YoutubeLiveChatToDiscord.csproj b/YoutubeLiveChatToDiscord.csproj index beb59b4..a3c0e15 100644 --- a/YoutubeLiveChatToDiscord.csproj +++ b/YoutubeLiveChatToDiscord.csproj @@ -1,7 +1,6 @@ - - net6.0 + net8.0 enable enable dotnet-LiveChatToDiscord-ACE24696-7DD5-4164-8805-CF76B90CBA6C @@ -9,11 +8,9 @@ . false - - - - - + + + - + \ No newline at end of file From 47dae03c2bed4bd142e013fba1c21e7bc0fbdcd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E9=88=9E?= Date: Sat, 2 Dec 2023 22:19:31 +0800 Subject: [PATCH 2/2] build: update Dockerfile and project file references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ignore the `.dockerignore` file in the git diff - Update the base image in the `Dockerfile` from `dotnet/runtime:6.0-alpine` to `dotnet/runtime:8.0-alpine` - Adjust the build configuration in the `Dockerfile` to use the `BUILD_CONFIGURATION` argument - Update the `dotnet restore` command in the `Dockerfile` to remove the directory path - Update the build command in the `Dockerfile` to use the `BUILD_CONFIGURATION` argument - Adjust the publish command in the `Dockerfile` to use the `BUILD_CONFIGURATION` argument and disable the app host usage - Remove the user creation and ownership changes in the `Dockerfile` - Update the project file `YoutubeLiveChatToDiscord.csproj` to include a reference to `Microsoft.VisualStudio.Azure.Containers.Tools.Targets` version 1.19.5 Signed-off-by: 陳鈞 --- .dockerignore | 6 +++++- Dockerfile | 23 ++++++++++------------- YoutubeLiveChatToDiscord.csproj | 1 + 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.dockerignore b/.dockerignore index 3729ff0..a54bf73 100644 --- a/.dockerignore +++ b/.dockerignore @@ -22,4 +22,8 @@ **/secrets.dev.yaml **/values.dev.yaml LICENSE -README.md \ No newline at end of file +README.md +**/helm-chart +**/.github +.dockerignore +.gitignore diff --git a/Dockerfile b/Dockerfile index e8b364e..2f29950 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/runtime:6.0-alpine AS base +FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine AS base WORKDIR /app RUN apk add --no-cache --virtual build-deps musl-dev gcc g++ python3-dev &&\ apk add --no-cache py3-pip tzdata &&\ @@ -12,24 +12,21 @@ ENV TZ=Asia/Taipei # https://github.com/dotnet/runtime/issues/34126#issuecomment-1104981659 ENV DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build +ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["YoutubeLiveChatToDiscord.csproj", "."] -RUN dotnet restore "./YoutubeLiveChatToDiscord.csproj" +RUN dotnet restore "YoutubeLiveChatToDiscord.csproj" COPY . . -WORKDIR "/src/." -RUN dotnet build "YoutubeLiveChatToDiscord.csproj" -c Release -o /app/build +RUN dotnet build "YoutubeLiveChatToDiscord.csproj" -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish -RUN dotnet publish "YoutubeLiveChatToDiscord.csproj" -c Release -o /app/publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "YoutubeLiveChatToDiscord.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . - -RUN addgroup -g 1000 docker && \ - adduser -u 1000 -G docker -h /home/docker -s /bin/sh -D docker \ - && chown -R 1000:1000 . -USER docker - +RUN mkdir -p /app && chown -R app:app /app +USER app ENTRYPOINT ["dotnet", "YoutubeLiveChatToDiscord.dll"] \ No newline at end of file diff --git a/YoutubeLiveChatToDiscord.csproj b/YoutubeLiveChatToDiscord.csproj index a3c0e15..633538f 100644 --- a/YoutubeLiveChatToDiscord.csproj +++ b/YoutubeLiveChatToDiscord.csproj @@ -10,6 +10,7 @@ +