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
36 changes: 36 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Stage 1: build .NET
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish TS3AudioBot \
-c Release \
-f net10.0 \
-r linux-x64 \
--self-contained true \
-o /app/publish

# Stage 2: build WebInterface
FROM node:18-slim AS webui
WORKDIR /webui
COPY WebInterface/package*.json ./
RUN npm ci --ignore-scripts
COPY WebInterface/ ./
RUN npm run build

# Stage 3: runtime
FROM debian:trixie-slim AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ffmpeg \
libicu-dev \
libopus0 \
yt-dlp \
&& rm -rf /var/lib/apt/lists/*

COPY --from=build /app/publish /app
COPY --from=webui /webui/dist /app/WebInterface

WORKDIR /data
EXPOSE 58913

ENTRYPOINT ["/app/TS3AudioBot", "--non-interactive", "--config", "/data/ts3audiobot.toml"]
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ For further reading check out the [CommandSystem](https://github.com/Splamy/TS3A
Download the git repository with `git clone --recurse-submodules https://github.com/Splamy/TS3AudioBot.git`.

#### Linux
1. Get the latest `dotnet core 3.1` version by following [this tutorial](https://docs.microsoft.com/dotnet/core/install/linux-package-managers) and choose your platform
1. Get the latest `.NET 10 SDK` version by following the official install instructions for your platform
1. Go into the directory of the repository with `cd TS3AudioBot`
1. Execute `dotnet build --framework netcoreapp3.1 --configuration Release TS3AudioBot` to build the AudioBot
1. The binary will be in `./TS3AudioBot/bin/Release/netcoreapp3.1` and can be run with `dotnet TS3AudioBot.dll`
1. Execute `dotnet build --framework net10.0 --configuration Release TS3AudioBot` to build the AudioBot
1. The binary will be in `./TS3AudioBot/bin/Release/net10.0` and can be run with `dotnet TS3AudioBot.dll`

#### Windows
1. Make sure you have `Visual Studio` with the `dotnet core 3.1` development toolchain installed
1. Make sure you have `Visual Studio` or the `.NET 10 SDK` installed
1. Build the AudioBot with Visual Studio.

### Building the WebInterface
Expand Down
108 changes: 108 additions & 0 deletions TS3ABotUnitTests/Net10RegressionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using Microsoft.AspNetCore.Http;
using NUnit.Framework;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TS3AudioBot.Environment;
using TS3AudioBot.Web.Api;

namespace TS3ABotUnitTests
{
[TestFixture]
public class Net10RegressionTests
{
[Test]
public void BuildRequestUrl_UsesRawTargetWhenAvailable()
{
var uri = WebApi.BuildRequestUrl("/api/help?scope=all", PathString.Empty, "/api/bot/list", QueryString.Empty);

Assert.AreEqual("/api/help?scope=all", uri.PathAndQuery);
}

[Test]
public void BuildRequestUrl_ReconstructsApiPathsWhenRawTargetIsMissing()
{
var helpUri = WebApi.BuildRequestUrl(null, PathString.Empty, "/api/help", QueryString.Empty);
var botListUri = WebApi.BuildRequestUrl(null, PathString.Empty, "/api/bot/list", QueryString.Empty);

Assert.AreEqual("/api/help", helpUri.PathAndQuery);
Assert.AreEqual("/api/bot/list", botListUri.PathAndQuery);
}

[Test]
public async Task WriteResponseBodyAsync_UsesAsynchronousBodyWrites()
{
var context = new DefaultHttpContext();
var stream = new AsyncOnlyWriteStream();
context.Response.Body = stream;

await WebApi.WriteResponseBodyAsync(context.Response, "{\"ok\":true}");

Assert.IsFalse(stream.SyncWriteCalled);
Assert.AreEqual("{\"ok\":true}", stream.GetWrittenText());
}

[Test]
public void ParseNetRuntimeDescription_ParsesModernDotNetDescriptions()
{
var runtime = SystemData.ParseNetRuntimeDescription(".NET 10.0.3");

Assert.NotNull(runtime);
Assert.AreEqual(Runtime.Core, runtime.Runtime);
Assert.AreEqual(".NET (10.0.3)", runtime.FullName);
Assert.AreEqual(new Version(10, 0, 3, 0), runtime.SemVer);
}

[Test]
public void ParseNetRuntimeDescription_DoesNotTreatDotNetFrameworkAsDotNetCore()
{
Assert.IsNull(SystemData.ParseNetRuntimeDescription(".NET Framework 4.8.1"));
}

[Test]
public void RuntimeData_DoesNotReportDotNetFrameworkOnNet10()
{
Assert.AreEqual(Runtime.Core, SystemData.RuntimeData.Runtime);
Assert.IsTrue(SystemData.RuntimeData.FullName.StartsWith(".NET (", StringComparison.Ordinal));
Assert.IsFalse(SystemData.RuntimeData.FullName.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase));
}

private sealed class AsyncOnlyWriteStream : Stream
{
private readonly MemoryStream inner = new MemoryStream();

public bool SyncWriteCalled { get; private set; }

public string GetWrittenText() => Encoding.UTF8.GetString(inner.ToArray());

public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => inner.Length;
public override long Position { get => inner.Position; set => throw new NotSupportedException(); }

public override void Flush() { }
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => inner.SetLength(value);

public override void Write(byte[] buffer, int offset, int count)
{
SyncWriteCalled = true;
throw new InvalidOperationException("Synchronous writes are not allowed in this test.");
}

public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
return inner.WriteAsync(buffer, cancellationToken);
}

public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return inner.WriteAsync(buffer, offset, count, cancellationToken);
}
}
}
}
8 changes: 4 additions & 4 deletions TS3ABotUnitTests/TS3ABotUnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
<TargetFramework>net10.0</TargetFramework>

<LangVersion>8.0</LangVersion>
<Nullable>disable</Nullable>
Expand All @@ -11,9 +11,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
</ItemGroup>

<ItemGroup>
Expand Down
23 changes: 16 additions & 7 deletions TS3AudioBot/Environment/SystemData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using TS3AudioBot.Helper;
using TSLib.Helper;
Expand Down Expand Up @@ -132,16 +133,24 @@ private static PlatformVersion GenRuntimeData()

private static PlatformVersion? GetNetCoreVersion()
{
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.CodeBase?.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
if (assemblyPath is null)
return ParseNetRuntimeDescription(RuntimeInformation.FrameworkDescription);
}

internal static PlatformVersion? ParseNetRuntimeDescription(string? frameworkDescription)
{
if (string.IsNullOrEmpty(frameworkDescription)
|| !frameworkDescription.StartsWith(".NET", StringComparison.OrdinalIgnoreCase)
|| frameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase))
return null;
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex <= 0 || netCoreAppIndex >= assemblyPath.Length - 2)

var version = frameworkDescription.Length > ".NET ".Length
? frameworkDescription.Substring(".NET ".Length)
: null;
if (string.IsNullOrEmpty(version))
return null;
var version = assemblyPath[netCoreAppIndex + 1];

var semVer = ParseToSemVer(version);
return new PlatformVersion(Runtime.Core, $".NET Core ({version})", semVer);
return new PlatformVersion(Runtime.Core, $".NET ({version})", semVer);
}

private static PlatformVersion? GetMonoVersion()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private static bool TestLfsr(int mask, int max)

private static int NumberOfSetBits(int i)
{
#if NETCOREAPP3_1
#if NET10_0_OR_GREATER
if (System.Runtime.Intrinsics.X86.Popcnt.IsSupported)
return unchecked((int)System.Runtime.Intrinsics.X86.Popcnt.PopCount((uint)i));
#endif
Expand Down
19 changes: 14 additions & 5 deletions TS3AudioBot/ResourceFactories/YoutubeDlHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -28,7 +29,7 @@ public static class YoutubeDlHelper
public static ConfPath? DataObj { private get; set; }
private static string? YoutubeDlPath => DataObj?.Path.Value;

private const string ParamGetSingleVideo = " --no-warnings --dump-json --id --";
private const string ParamGetSingleVideo = " --dump-json --id --";
private const string ParamGetPlaylist = "--no-warnings --yes-playlist --flat-playlist --dump-single-json --id --";
private const string ParamGetSearch = "--no-warnings --flat-playlist --dump-single-json -- ytsearch10:";

Expand Down Expand Up @@ -153,8 +154,16 @@ public static async Task<T> RunYoutubeDl<T>(string path, string args) where T :

if (stdErr.Length > 0)
{
Log.Debug("youtube-dl failed to load the resource:\n{0}", stdErr);
throw Error.LocalStr(strings.error_ytdl_song_failed_to_load);
Log.Debug("youtube-dl stderr:\n{0}", stdErr);
var errLines = stdErr.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Where(l => l.StartsWith("ERROR"))
.ToList();
if (errLines.Count > 0)
{
Log.Debug("youtube-dl failed to load the resource:\n{0}", string.Join("\n", errLines));
throw Error.LocalStr(strings.error_ytdl_song_failed_to_load);
}
}

return ParseResponse<T>(stdOut.ToString());
Expand Down Expand Up @@ -185,7 +194,7 @@ public static T ParseResponse<T>(string? json) where T : notnull

public static JsonYtdlFormat? FilterBest(IEnumerable<JsonYtdlFormat>? formats)
{
Log.Debug("Picking from options: {@formats}", formats);
Log.Debug("Picking from options: {@Formats}", formats);

if (formats is null)
return null;
Expand All @@ -203,7 +212,7 @@ public static T ParseResponse<T>(string? json) where T : notnull
}
}

Log.Debug("Picked: {@format}", best);
Log.Debug("Picked: {@Format}", best);
return best;
}

Expand Down
18 changes: 10 additions & 8 deletions TS3AudioBot/TS3AudioBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
<AssemblyName>TS3AudioBot</AssemblyName>
<OutputType>Exe</OutputType>
<LangVersion>8.0</LangVersion>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifiers>win-x64;linux-x64;linux-arm;linux-arm64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<RollForward>Major</RollForward>

Expand Down Expand Up @@ -38,20 +39,21 @@
<PackageReference Include="CliWrap" Version="3.1.0" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="NLog" Version="4.7.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="LiteDB" Version="4.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0">
<ExcludeAssets>analyzers</ExcludeAssets>
</PackageReference>
<PackageReference Include="Nett" Version="0.15.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.11" />
<TrimmerRootAssembly Include="System.Text.Json" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Resources\DefaultRights.toml" />
<EmbeddedResource Include="Media\SleepingKitty.png" />
Expand Down Expand Up @@ -98,7 +100,7 @@
<VersionBuildOutputFile Include="$(IntermediateOutputPath)Version.g.cs" />
</ItemGroup>
<Exec Command="dotnet tool install --global dotnet-script" IgnoreExitCode="true" />
<Exec Command="dotnet tool install --global GitVersion.Tool" IgnoreExitCode="true" />
<Exec Command="dotnet tool install --global GitVersion.Tool --version 5.12.0" IgnoreExitCode="true" />
<Exec Command="dotnet script @(VersionBuildScript) -- @(VersionBuildOutputFile) $(Configuration)" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="GitverExit" />
</Exec>
Expand Down
Loading