diff --git a/Dockerfile b/Dockerfile index c480d5b..809b038 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,13 @@ #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. ### Base image for yt-dlp -FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine AS base +FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine AS base WORKDIR /app RUN apk add --no-cache tzdata python3 && \ apk add --no-cache --virtual build-deps musl-dev gcc g++ python3-dev py3-pip && \ python3 -m venv /venv && \ source /venv/bin/activate && \ - pip install yt-dlp && \ + pip install --no-cache-dir yt-dlp && \ pip uninstall -y setuptools pip && \ apk del build-deps @@ -20,26 +20,49 @@ ENV DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true ### Debug image (same as base image) ### Rename for VS fast mode stage debugging -FROM base AS debug +FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine AS debug + +WORKDIR /app +RUN apk add --no-cache tzdata python3 && \ + apk add --no-cache --virtual build-deps musl-dev gcc g++ python3-dev py3-pip && \ + python3 -m venv /venv && \ + source /venv/bin/activate && \ + pip install --no-cache-dir yt-dlp && \ + pip uninstall -y setuptools pip && \ + apk del build-deps + +ENV PATH="/venv/bin:$PATH" +ENV TZ=Asia/Taipei + +# Disable file locking on Unix +# https://github.com/dotnet/runtime/issues/34126#issuecomment-1104981659 +ENV DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true + ### Build .NET -FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build ARG BUILD_CONFIGURATION=Release +ARG TARGETARCH WORKDIR /src + COPY ["YoutubeLiveChatToDiscord.csproj", "."] -ARG TARGETPLATFORM -RUN dotnet restore "YoutubeLiveChatToDiscord.csproj" +RUN dotnet restore -a $TARGETARCH "YoutubeLiveChatToDiscord.csproj" FROM build AS publish COPY . . ARG BUILD_CONFIGURATION=Release -ARG TARGETPLATFORM -RUN dotnet publish "YoutubeLiveChatToDiscord.csproj" --no-self-contained -p:PublishTrimmed=false -c $BUILD_CONFIGURATION -o /app/publish +ARG TARGETARCH +RUN dotnet publish "YoutubeLiveChatToDiscord.csproj" -a $TARGETARCH -c $BUILD_CONFIGURATION -o /app/publish --self-contained true + ### Final image FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -RUN chown -R 1001:1001 /app -USER 1001 -ENTRYPOINT ["dotnet", "YoutubeLiveChatToDiscord.dll"] \ No newline at end of file + +ENV PATH="/app:$PATH" + +RUN mkdir -p /app && chown -R $APP_UID:$APP_UID /app +COPY --from=publish --chown=$APP_UID:$APP_UID /app/publish/YoutubeLiveChatToDiscord /app/YoutubeLiveChatToDiscord +COPY --from=publish --chown=$APP_UID:$APP_UID /app/publish/appsettings.json /app/appsettings.json + +USER $APP_UID +ENTRYPOINT ["/app/YoutubeLiveChatToDiscord"] \ No newline at end of file diff --git a/Dockerfile.ubi b/Dockerfile.ubi index c341fa2..0a48839 100644 --- a/Dockerfile.ubi +++ b/Dockerfile.ubi @@ -13,8 +13,8 @@ RUN microdnf -y install python3.11 python3.11-pip && \ microdnf -y clean all RUN python3.11 -m venv /venv && \ - source /venv/bin/activate &&\ - pip3.11 install yt-dlp && \ + source /venv/bin/activate && \ + pip3.11 install --no-cache-dir yt-dlp && \ pip3.11 uninstall -y setuptools pip && \ microdnf -y remove python3.11-pip && \ microdnf -y clean all @@ -44,29 +44,21 @@ RUN microdnf -y install dotnet-sdk-8.0 ### Build .NET -FROM registry.access.redhat.com/ubi8/dotnet-80 AS build +FROM --platform=$BUILDPLATFORM registry.access.redhat.com/ubi8/dotnet-80 AS build USER 0 ARG BUILD_CONFIGURATION=Release +ARG TARGETARCH WORKDIR /src -COPY YoutubeLiveChatToDiscord.csproj . -ARG TARGETPLATFORM -RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ - dotnet restore "YoutubeLiveChatToDiscord.csproj" -r linux-x64; \ - elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ - dotnet restore "YoutubeLiveChatToDiscord.csproj" -r linux-arm64; \ - fi +COPY ["YoutubeLiveChatToDiscord.csproj", "."] +RUN dotnet restore -a $TARGETARCH "YoutubeLiveChatToDiscord.csproj" FROM build AS publish COPY . . ARG BUILD_CONFIGURATION=Release -ARG TARGETPLATFORM -RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ - dotnet publish "YoutubeLiveChatToDiscord.csproj" -c $BUILD_CONFIGURATION -o /app/publish -r linux-x64; \ - elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ - dotnet publish "YoutubeLiveChatToDiscord.csproj" -c $BUILD_CONFIGURATION -o /app/publish -r linux-arm64; \ - fi +ARG TARGETARCH +RUN dotnet publish "YoutubeLiveChatToDiscord.csproj" -a $TARGETARCH -c $BUILD_CONFIGURATION -o /app/publish --self-contained true ### Final image @@ -74,8 +66,9 @@ FROM base AS final ENV PATH="/app:$PATH" -COPY --link --chown=1001:1001 --from=publish /app/publish . +RUN mkdir -p /app && chown -R 1001:1001 /app +COPY --from=publish --chown=1001:1001 /app/publish/YoutubeLiveChatToDiscord /app/YoutubeLiveChatToDiscord +COPY --from=publish --chown=1001:1001 /app/publish/appsettings.json /app/appsettings.json USER 1001 - -ENTRYPOINT ["YoutubeLiveChatToDiscord"] \ No newline at end of file +ENTRYPOINT ["/app/YoutubeLiveChatToDiscord"] \ No newline at end of file diff --git a/Json/SourceGenerationContext.cs b/Json/SourceGenerationContext.cs new file mode 100644 index 0000000..0adb2e6 --- /dev/null +++ b/Json/SourceGenerationContext.cs @@ -0,0 +1,10 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using YoutubeLiveChatToDiscord.Models; + +// Must read: +// https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation?pivots=dotnet-8-0 +[JsonSerializable(typeof(Info))] +[JsonSerializable(typeof(Chat))] +[JsonSourceGenerationOptions(WriteIndented = true, AllowTrailingCommas = true, ReadCommentHandling = JsonCommentHandling.Skip)] +internal partial class SourceGenerationContext : JsonSerializerContext { } diff --git a/LiveChatMonitorWorker.cs b/LiveChatMonitorWorker.cs index 2c3b3bb..2d75178 100644 --- a/LiveChatMonitorWorker.cs +++ b/LiveChatMonitorWorker.cs @@ -1,6 +1,7 @@ using Discord; using Discord.Webhook; -using Newtonsoft.Json; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using YoutubeLiveChatToDiscord.Models; using YoutubeLiveChatToDiscord.Services; @@ -122,6 +123,10 @@ private async Task Monitoring(CancellationToken stoppingToken) /// /// /// + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = $"{nameof(SourceGenerationContext)} is used.")] private async Task GetVideoInfo(CancellationToken stoppingToken) { FileInfo videoInfo = new($"{_id}.info.json"); @@ -131,7 +136,12 @@ private async Task GetVideoInfo(CancellationToken stoppingToken) throw new FileNotFoundException(null, videoInfo.FullName); } - Info? info = JsonConvert.DeserializeObject(await new StreamReader(videoInfo.OpenRead()).ReadToEndAsync(stoppingToken)); + Info? info = JsonSerializer.Deserialize( + await new StreamReader(videoInfo.OpenRead()).ReadToEndAsync(stoppingToken), + options: new() + { + TypeInfoResolver = SourceGenerationContext.Default + }); string? Title = info?.title; string? ChannelId = info?.channel_id; string? thumb = info?.thumbnail; @@ -141,6 +151,10 @@ private async Task GetVideoInfo(CancellationToken stoppingToken) Environment.SetEnvironmentVariable("VIDEO_THUMB", thumb); } + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = $"{nameof(SourceGenerationContext)} is used.")] private async Task ProcessChats(CancellationToken stoppingToken) { // Notice: yt-dlp在Linux會使用lock鎖定此檔案,在Windows不鎖定。 @@ -165,12 +179,17 @@ private async Task ProcessChats(CancellationToken stoppingToken) _position = sr.BaseStream.Position; if (string.IsNullOrEmpty(str)) continue; - Chat? chat = JsonConvert.DeserializeObject(str); + Chat? chat = JsonSerializer.Deserialize( + str, + options: new() + { + TypeInfoResolver = SourceGenerationContext.Default + }); if (null == chat) continue; await BuildRequestAndSendToDiscord(chat, stoppingToken); } - catch (JsonSerializationException e) + catch (JsonException e) { _logger.LogError("{error}", e.Message); _logger.LogError("{originalString}", str); diff --git a/Models/Info.cs b/Models/Info.cs index b157807..5251f1e 100644 --- a/Models/Info.cs +++ b/Models/Info.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace YoutubeLiveChatToDiscord.Models; @@ -11,17 +11,17 @@ namespace YoutubeLiveChatToDiscord.Models; #pragma warning disable IDE1006 // 命名樣式 public class HttpHeaders { - [JsonProperty("User-Agent")] + [JsonPropertyName("User-Agent")] public string? UserAgent { get; set; } public string? Accept { get; set; } - [JsonProperty("Accept-Encoding")] + [JsonPropertyName("Accept-Encoding")] public string? AcceptEncoding { get; set; } - [JsonProperty("Accept-Language")] + [JsonPropertyName("Accept-Language")] public string? AcceptLanguage { get; set; } - [JsonProperty("Sec-Fetch-Mode")] + [JsonPropertyName("Sec-Fetch-Mode")] public string? SecFetchMode { get; set; } } @@ -873,10 +873,10 @@ public class AutomaticCaptions public List? ca { get; set; } public List? ceb { get; set; } - [JsonProperty("zh-Hans")] + [JsonPropertyName("zh-Hans")] public List? ZhHans { get; set; } - [JsonProperty("zh-Hant")] + [JsonPropertyName("zh-Hant")] public List? ZhHant { get; set; } public List? co { get; set; } public List
? hr { get; set; } diff --git a/README.md b/README.md index 25c3357..8483b8d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Two parameters need to be passed in: - Discord Webhook URL ```sh -docker run -rm ghcr.io/jim60105/youtubelivechattodiscord [Video_Id] [Discord_Webhook_Url] +docker run --rm ghcr.io/jim60105/youtubelivechattodiscord [Video_Id] [Discord_Webhook_Url] ``` Also available at [quay.io](https://quay.io/jim60105/youtubelivechattodiscord) diff --git a/YoutubeLiveChatToDiscord.csproj b/YoutubeLiveChatToDiscord.csproj index c9227ed..e6223aa 100644 --- a/YoutubeLiveChatToDiscord.csproj +++ b/YoutubeLiveChatToDiscord.csproj @@ -4,13 +4,15 @@ enable enable dotnet-LiveChatToDiscord-ACE24696-7DD5-4164-8805-CF76B90CBA6C + false + true + true + true + true + true Linux . - false debug - true - true - full @@ -22,6 +24,5 @@ - \ No newline at end of file