Switch MAX bridge to PyMax only

This commit is contained in:
sevenhill
2026-07-07 14:18:49 +03:00
parent 8efeff334a
commit af84343e19
19 changed files with 314 additions and 8088 deletions
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
@@ -6,6 +7,7 @@ using QMax.Api.Configuration;
using QMax.Api.Contracts;
using QMax.Api.Data;
using QMax.Api.Data.Entities;
using QMax.Api.Infrastructure.Hubs;
using QMax.Api.Infrastructure.Max;
using QMax.Api.Services;
@@ -18,6 +20,7 @@ public sealed class MaxController(
IMaxBridgeClient maxBridge,
MaxBridgeSyncService syncService,
QMaxDbContext db,
IHubContext<QMaxHub> hubContext,
IOptions<QMaxOptions> options) : ControllerBase
{
private readonly QMaxOptions _options = options.Value;
@@ -82,6 +85,7 @@ public sealed class MaxController(
state.LastError = status.LastError;
state.UpdatedAt = DateTimeOffset.UtcNow;
await db.SaveChangesAsync(cancellationToken);
await hubContext.Clients.All.SendAsync("MaxStatusChanged", ToDto(status), cancellationToken);
}
private static MaxBridgeStatusDto ToDto(MaxBridgeStatus status)
@@ -15,7 +15,7 @@ public sealed class MockMaxBridgeClient : IMaxBridgeClient
"max",
"MAX",
false,
"Mock mode is active. Switch QMax:MaxMode to Playwright for real web.max.ru.",
"Mock mode is active. Switch QMax:MaxMode to Worker and use qmax-pymax-worker for real MAX traffic.",
DateTimeOffset.UtcNow)
])
];
@@ -110,6 +110,6 @@ public sealed class MockMaxBridgeClient : IMaxBridgeClient
private static string MockChatUrl(string externalChatId)
{
return $"https://web.max.ru/mock/{Uri.EscapeDataString(externalChatId)}";
return $"pymax://mock/{Uri.EscapeDataString(externalChatId)}";
}
}
@@ -40,7 +40,7 @@ public sealed class WorkerMaxBridgeClient(
public async Task<IReadOnlyList<MaxChatUpdate>> FetchUpdatesAsync(CancellationToken cancellationToken)
{
return await SendAsync<IReadOnlyList<MaxChatUpdate>>(HttpMethod.Get, "/updates", null, cancellationToken)
?? Array.Empty<MaxChatUpdate>();
?? throw new InvalidOperationException("MAX worker returned an empty updates response.");
}
public async Task<MaxChatUpdate?> FetchChatHistoryAsync(string externalChatId, string? chatUrl, CancellationToken cancellationToken)
@@ -1,5 +1,8 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using QMax.Api.Configuration;
using QMax.Api.Contracts;
using QMax.Api.Data;
using QMax.Api.Data.Entities;
using QMax.Api.Infrastructure.Hubs;
@@ -29,10 +32,41 @@ public sealed class MaxBridgeSyncService(
catch (Exception ex)
{
logger.LogWarning(ex, "MAX sync failed.");
await PublishCurrentMaxStatusAsync(cancellationToken);
return 0;
}
}
private async Task PublishCurrentMaxStatusAsync(CancellationToken cancellationToken)
{
try
{
var status = await maxBridgeClient.GetStatusAsync(cancellationToken);
await using var scope = scopeFactory.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<QMaxDbContext>();
var options = scope.ServiceProvider.GetRequiredService<IOptions<QMaxOptions>>().Value;
var state = await db.MaxAccountStates.FirstOrDefaultAsync(x => x.Id == 1, cancellationToken);
if (state is null)
{
state = new MaxAccountState { Id = 1 };
db.MaxAccountStates.Add(state);
}
state.PhoneNumber = options.MaxPhoneNumber;
state.Status = status.Status;
state.IsAuthorized = status.IsAuthorized;
state.LastUrl = status.Url;
state.LastError = status.LastError;
state.UpdatedAt = DateTimeOffset.UtcNow;
await db.SaveChangesAsync(cancellationToken);
await hubContext.Clients.All.SendAsync("MaxStatusChanged", ToDto(status), cancellationToken);
}
catch (Exception statusError)
{
logger.LogDebug(statusError, "Unable to publish current MAX status after sync failure.");
}
}
public async Task<int> SyncChatHistoryAsync(string externalChatId, string? chatUrl, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(externalChatId))
@@ -1387,6 +1421,11 @@ public sealed class MaxBridgeSyncService(
};
}
private static MaxBridgeStatusDto ToDto(MaxBridgeStatus status)
{
return new MaxBridgeStatusDto(status.Mode, status.IsAuthorized, status.LoginStage, status.Status, status.Url, status.Title, status.LastError, status.UpdatedAt);
}
private sealed record LastKnownMessageSnapshot(
MessageDirection Direction,
string? Text,