Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
698866c
initial avalonia port attempt full send
WolfwithSword Jul 25, 2026
1fa2597
migrate webserver to kestrel for more reliable xplat stuff
WolfwithSword Jul 25, 2026
6726444
fix webserver, optimize db contexts
WolfwithSword Jul 25, 2026
d60459b
fix blerp unit tests
WolfwithSword Jul 25, 2026
8ac102c
fix linux build
WolfwithSword Jul 25, 2026
1ce8fea
add adjustable build targets
WolfwithSword Jul 25, 2026
8a70302
fix build singles
WolfwithSword Jul 25, 2026
6ee5101
better shutdown behaviour
WolfwithSword Jul 25, 2026
a3f19b7
modify dispatch to wiki
WolfwithSword Jul 25, 2026
34b98de
fix wiki workflow
WolfwithSword Jul 25, 2026
eb07c4a
attempt fix linux webview
WolfwithSword Jul 25, 2026
5b94506
fix linux sys theme
WolfwithSword Jul 25, 2026
46527ba
try fix theme
WolfwithSword Jul 26, 2026
971560b
test webkitgtk for linux
WolfwithSword Jul 26, 2026
2e37788
fallback for linux browser previewer
WolfwithSword Jul 26, 2026
958cf1c
try gtk fix
WolfwithSword Jul 26, 2026
4f34ec4
undo but keep the weird experimental offscreen bit
WolfwithSword Jul 26, 2026
40e45b5
attempt fix webview linux
WolfwithSword Jul 26, 2026
b02b5c4
add new overlay tab buttons
WolfwithSword Jul 26, 2026
9c11826
fix scaling and dragging in webview
WolfwithSword Jul 27, 2026
94b3c11
retest update in place launch behaviour
WolfwithSword Jul 27, 2026
f67ce71
update build
WolfwithSword Jul 27, 2026
ea12003
prompt telemetry on new install
WolfwithSword Jul 27, 2026
116a67f
migrate :>
WolfwithSword Jul 27, 2026
413a2a8
prettify build output maybe
WolfwithSword Jul 27, 2026
6813b3f
fix output
WolfwithSword Jul 27, 2026
7da0bb2
try fix nc upload
WolfwithSword Jul 27, 2026
3e4fc10
try fix nc upload
WolfwithSword Jul 27, 2026
40280d6
fix telemetry optout and adjust macos warning for web preview
WolfwithSword Jul 28, 2026
942e767
attempt mac scaling fix
WolfwithSword Jul 28, 2026
cab1d41
make other icons for linux
WolfwithSword Jul 28, 2026
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
646 changes: 519 additions & 127 deletions .github/workflows/build.yml

Large diffs are not rendered by default.

27 changes: 23 additions & 4 deletions .github/workflows/wiki-drawio-upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ jobs:
name: Update Wiki Documentation
runs-on: ubuntu-latest
steps:
- name: Normalize release tag
id: version
run: |
TAG="${{ inputs.release_tag || 'nightly' }}"

if [[ "$TAG" == "nightly" || "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "release_tag=$TAG" >> "$GITHUB_OUTPUT"
else
echo "Invalid release tag '$TAG', using nightly."
echo "release_tag=nightly" >> "$GITHUB_OUTPUT"
fi

- name: Check if wiki repository exists
id: wiki-check
run: |
Expand Down Expand Up @@ -76,22 +88,29 @@ jobs:
if git diff --cached --quiet; then
echo "No changes detected, skipping commit."
else
git commit -m "Update Subathon_Manager.drawio for release ${{ inputs.release_tag || 'nightly' }}"
git commit -m "Update Subathon_Manager.drawio for release ${{ steps.version.outputs.release_tag }}"
git push
fi

outputs:
release_tag: ${{ steps.version.outputs.release_tag }}

trigger-docs-release:
runs-on: ubuntu-latest
needs: update
if: ${{ (inputs.release_tag || 'nightly') != 'nightly' }}
if: ${{ needs.update.result == 'success' && needs.version.outputs.release_tag != 'nightly' && needs.version.outputs.release_tag != '' }}
steps:
- name: Dispatch docs release
uses: actions/github-script@v9
env:
VERSION: ${{ needs.version.outputs.release_tag }}
with:
github-token: ${{ secrets.DOCS_DISPATCH_TOKEN }}
script: |
const version = context.payload.inputs?.release_tag || 'nightly';
const version = process.env.VERSION;
if (!version || version === 'nightly') {
core.setFailed(`Refusing to dispatch a docs release for version '${version}'.`);
return;
}
await github.rest.repos.createDispatchEvent({
owner: 'WolfwithSword',
repo: 'SubathonManager-docs',
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
presets/harley/
presets/honse/

bin/
obj/
/packages/
Expand Down
25 changes: 16 additions & 9 deletions SubathonManager.Core/AppServices.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Updatum;
using System.Reflection;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
namespace SubathonManager.Core {

Expand All @@ -26,9 +26,22 @@ public static string AppVersion
{
AssetExtensionFilter = "zip",
CurrentVersion = GetVersion(),
FetchOnlyLatestRelease = true
FetchOnlyLatestRelease = true,
AssetRegexPattern = GetAssetRegexPattern(),
};

private static string GetAssetRegexPattern()
{
string rid =
OperatingSystem.IsWindows() ? "win-x64" :
OperatingSystem.IsLinux() ? "linux-x64" :
OperatingSystem.IsMacOS()
? (RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "osx-arm64" : "osx-x64")
: string.Empty;

return string.IsNullOrEmpty(rid) ? string.Empty : $@"(?i).*{rid}.*\.zip$";
}

private static Version GetVersion()
{
if (string.IsNullOrWhiteSpace(AppVersion))
Expand Down Expand Up @@ -96,13 +109,7 @@ public static async Task<bool> InstallUpdate(UpdatumDownloadedAsset? asset, ILog

try
{
Process.Start(new ProcessStartInfo
{
FileName = Config.AppFolder,
UseShellExecute = true,
Verb = "open"
});
await AppUpdater.InstallUpdateAsync(asset, true, UpdatumManager.NoRunAfterUpgradeToken);
await AppUpdater.InstallUpdateAsync(asset, true);
return true;
}
catch (Exception ex)
Expand Down
4 changes: 4 additions & 0 deletions SubathonManager.Core/Enums/SubathonPromptType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ public static SubathonPromptSubType[] GetValidSubTypes(
return type.GetValidSubTypes();

if (filterEventType.IsSubscription())
{
if (filterEventType == SubathonEventType.YouTubeGiftMembership) // no tier selection
return [SubathonPromptSubType.Default];
return [SubathonPromptSubType.Default, SubathonPromptSubType.ByTier];
}
if (filterEventType.IsOrder())
{
// makeship and juniper only ever track unit counts between polls, but do it diff
Expand Down
4 changes: 3 additions & 1 deletion SubathonManager.Core/Objects/WidgetMeta.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using SubathonManager.Core.Enums;

Expand All @@ -13,6 +14,7 @@ public class WidgetMeta
public Dictionary<string, WidgetMetaVar> Vars { get; set; } = new();
}

[ExcludeFromCodeCoverage]
public class WidgetMetaVar
{
[JsonIgnore]
Expand Down
12 changes: 11 additions & 1 deletion SubathonManager.Integration/ExternalEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,17 @@ public static bool ProcessExternalSub(Dictionary<string, JsonElement> data)
}
}

subathonEvent.Amount = elemAmount.ValueKind == JsonValueKind.Number ? elemAmount.GetInt16() : 1;
if (elemAmount.ValueKind == JsonValueKind.String)
{
subathonEvent.Amount = 1;
if (int.TryParse(elemAmount.GetString(), out var amtInt) && amtInt > 0)
subathonEvent.Amount = amtInt;
}
else
{
subathonEvent.Amount = elemAmount.ValueKind == JsonValueKind.Number ? elemAmount.GetInt16() : 1;
}

subathonEvent.Source = user == "SYSTEM" ? SubathonEventSource.Simulated : ((SubathonEventType?)type).GetSource();
subathonEvent.EventType = type;

Expand Down
3 changes: 2 additions & 1 deletion SubathonManager.Integration/TwitchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,8 @@ private Task HandleMessageCmdReceived(object? s, OnMessageReceivedArgs e)
e.ChatMessage.Username, // DisplayName
isBroadcaster, isMod, isVip, DateTime.Now);
}
else if (e.ChatMessage.DisplayName.Equals("blerp", StringComparison.InvariantCultureIgnoreCase))
else if (e.ChatMessage.DisplayName.Equals("blerp", StringComparison.InvariantCultureIgnoreCase)
&& config.GetBool("Extensions", "Blerp.Enabled", true))
{
BlerpChatService.ParseMessage(e.ChatMessage.Message, SubathonEventSource.Twitch);
}
Expand Down
3 changes: 2 additions & 1 deletion SubathonManager.Integration/YouTubeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,8 @@ private void OnChatReceived(object? sender, ChatReceivedEventArgs e)
CommandService.ChatCommandRequest(SubathonEventSource.YouTube, messagePreview, user,
item.IsOwner, item.IsModerator, false,
item.Timestamp.DateTime.ToLocalTime(), Utils.CreateGuidFromUniqueString(item.Id));
else if (user.Equals("blerp", StringComparison.OrdinalIgnoreCase))
else if (user.Equals("blerp", StringComparison.OrdinalIgnoreCase)
&& _config.GetBool("Extensions", "Blerp.Enabled", true))
{
BlerpChatService.ParseMessage(messagePreview, SubathonEventSource.YouTube);
}
Expand Down
64 changes: 64 additions & 0 deletions SubathonManager.Server/AspNetContextAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Diagnostics.CodeAnalysis;
using System.Net.WebSockets;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace SubathonManager.Server;

[ExcludeFromCodeCoverage]
public sealed class AspNetContextAdapter(HttpContext ctx) : IHttpContext
{
public string Method => ctx.Request.Method;
public string Path => ctx.Request.Path.Value ?? "/";
public string QueryString => ctx.Request.QueryString.Value?.TrimStart('?') ?? string.Empty;
public Stream Body => ctx.Request.Body;
public Encoding Encoding => Encoding.UTF8;
public bool IsWebSocket => ctx.WebSockets.IsWebSocketRequest;

public IReadOnlyDictionary<string, string> Headers
{
get
{
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var (key, value) in ctx.Request.Headers)
dict[key] = value.ToString();
return dict;
}
}

public Task<WebSocket>? AcceptWebSocketAsync(string? subProtocol = null)
=> ctx.WebSockets.IsWebSocketRequest
? ctx.WebSockets.AcceptWebSocketAsync(subProtocol)
: null;

public async Task WriteResponse(int code, string body, bool addCors = false, string? contentType = null)
{
ctx.Response.StatusCode = code;
if (contentType != null)
ctx.Response.ContentType = contentType;
if (addCors)
AddCorsHeaders(ctx.Response);
await ctx.Response.WriteAsync(body, Encoding.UTF8);
}

public async Task ServeFile(string fullPath, string contentType)
{
ctx.Response.ContentType = contentType;
AddCorsHeaders(ctx.Response);
await ctx.Response.SendFileAsync(fullPath);
}

private static void AddCorsHeaders(HttpResponse response)
{
response.Headers["Access-Control-Allow-Origin"] = "*";
response.Headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS";
response.Headers["Access-Control-Allow-Headers"] = "Content-Type";
}
}

internal sealed class NoopHostLifetime : IHostLifetime
{
public Task WaitForStartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
83 changes: 0 additions & 83 deletions SubathonManager.Server/HttpListenerContextAdapter.cs

This file was deleted.

4 changes: 4 additions & 0 deletions SubathonManager.Server/SubathonManager.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<NoWarn>$(NoWarn);CS8981</NoWarn>
</PropertyGroup>

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

<ItemGroup>
<ProjectReference Include="..\SubathonManager.Core\SubathonManager.Core.csproj" />
<ProjectReference Include="..\SubathonManager.Data\SubathonManager.Data.csproj" />
Expand Down
Loading
Loading