using System.Net.Http.Headers; using System.Text; using System.Text.Json; using WxAgent.Core; var baseUrl = Required("--base-url"); var nodeId = Required("--node-id"); var nodeToken = Required("--node-token"); var webUser = Required("--web-user"); var webPassword = Required("--web-password"); var accountId = Get("--account-id") ?? "account-a"; var allowedChatId = Get("--allowed-chat-id") ?? "allowed-chat"; var blockedChatId = Get("--blocked-chat-id") ?? "blocked-chat"; using var http = new HttpClient { BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/") }; var webToken = await LoginAsync(http, webUser, webPassword); var config = new ReportingConfig { Enabled = true, ConfigVersion = 1, Accounts = [new AccountReportingConfig { AccountId = accountId, Enabled = true, AllowedChats = [new AllowedChat { Type = ReportingChatType.Group, ChatId = allowedChatId, Enabled = true, IdentityVerified = true }] }] }; using var node = new RemoteControlClient(new RemoteAgentOptions { AuthAddress = baseUrl, Token = nodeToken, NodeId = nodeId, ActiveAccountId = accountId }, http); await node.RegisterAsync(new RemoteNodeRegistration(nodeId, "integration", RemoteProtocol.Version, ["heartbeat", "poll-tasks", "send-text", "report-message"], config.ConfigVersion, [new RemoteAccountSummary(accountId, true, true, 1, 0)])); await node.HeartbeatAsync(new RemoteHeartbeat(nodeId, "integration", RemoteProtocol.Version, RemoteNodeStatus.Online, true, true, false, accountId, 0, config.ConfigVersion, "integration-heartbeat")); var payload = JsonDocument.Parse("{\"target_id\":\"target-chat\",\"text\":\"integration task\",\"confirmed\":true}").RootElement.Clone(); var taskResponse = await WebPostAsync(http, "/v1/tasks", webToken, new RemoteTaskSubmission( nodeId, accountId, "send-text", "integration-task-1", payload)); var task = (await node.PollTasksAsync(accountId)).Single(item => item.TaskId == taskResponse.TaskId); var accepted = await node.AcknowledgeTaskAsync(task); var started = await node.StartTaskAsync(accepted); await node.SendTaskResultAsync(new RemoteTaskResult(started.TaskId, accountId, started.LeaseGeneration, RemoteTaskStatus.Succeeded, null, null, true, null, "integration-result"), config); var readResponse = await WebPostAsync(http, "/v1/reads/sessions", webToken, new { node_id = nodeId, account_id = accountId, idempotency_key = "integration-read-1", limit = 20, offset = 0 }); var readTask = (await node.PollTasksAsync(accountId)).Single(item => item.TaskId == readResponse.TaskId); var readAccepted = await node.AcknowledgeTaskAsync(readTask); var readStarted = await node.StartTaskAsync(readAccepted); using var readContentDocument = JsonDocument.Parse("{\"items\":[{\"automationId\":\"allowed-chat\"}],\"limit\":20,\"offset\":0,\"hasMore\":false}"); await node.SendTaskResultAsync(new RemoteTaskResult(readStarted.TaskId, accountId, readStarted.LeaseGeneration, RemoteTaskStatus.Succeeded, null, null, false, readContentDocument.RootElement.Clone(), "integration-read-result"), config, chatScopes: [new RemoteReportingScope(allowedChatId, ReportingChatType.Group)]); var storedRead = await WebGetAsync(http, "/v1/tasks/" + readResponse.TaskId, webToken); if (!storedRead.TryGetProperty("result", out var readResult) || !readResult.TryGetProperty("content", out var readContent) || readContent.ValueKind != JsonValueKind.Object) throw new InvalidOperationException("Remote read result content was not retained."); var denied = await node.SubmitEventAsync(config, new RemoteMessageEvent(nodeId, accountId, blockedChatId, ReportingChatType.Private, 1, "message", DateTimeOffset.UtcNow, "blocked-content", config.ConfigVersion, config.ConfigVersion, "integration-denied")); var acceptedEvent = await node.SubmitEventAsync(config, new RemoteMessageEvent(nodeId, accountId, allowedChatId, ReportingChatType.Group, 1, "message", DateTimeOffset.UtcNow, "allowed-content", config.ConfigVersion, config.ConfigVersion, "integration-allowed")); var events = await WebGetAsync(http, "/v1/events?limit=20", webToken); if (denied.Accepted || !acceptedEvent.Accepted || events.Events.Count != 1 || events.Events[0].ChatId != allowedChatId) throw new InvalidOperationException("Remote whitelist integration assertion failed."); Console.WriteLine(JsonSerializer.Serialize(new { status = "ok", node = nodeId, task = taskResponse.TaskId, taskStatus = RemoteTaskStatus.Succeeded.ToString(), readTask = readResponse.TaskId, readContentRetained = true, deniedEventAccepted = denied.Accepted, acceptedEvent = acceptedEvent.EventId, storedEventCount = events.Events.Count, privacy = "blocked event content was not sent" }, new JsonSerializerOptions { WriteIndented = true })); async Task WebPostAsync(HttpClient client, string path, string token, object value) { using var request = new HttpRequestMessage(HttpMethod.Post, path); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); request.Content = new StringContent(JsonSerializer.Serialize(value, RemoteJson.Options), Encoding.UTF8, "application/json"); using var response = await client.SendAsync(request); var content = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) throw new InvalidOperationException($"HTTP {(int)response.StatusCode}: {content}"); return JsonSerializer.Deserialize(content, RemoteJson.Options) ?? throw new InvalidOperationException("Empty response."); } async Task WebGetAsync(HttpClient client, string path, string token) { using var request = new HttpRequestMessage(HttpMethod.Get, path); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); using var response = await client.SendAsync(request); var content = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) throw new InvalidOperationException($"HTTP {(int)response.StatusCode}: {content}"); return JsonSerializer.Deserialize(content, RemoteJson.Options) ?? throw new InvalidOperationException("Empty response."); } async Task LoginAsync(HttpClient client, string username, string password) { var result = await WebPostAsync(client, "/v1/auth/login", "", new { username, password }); return result.AccessToken; } string Required(string name) => Get(name) ?? throw new ArgumentException($"Missing {name}."); string? Get(string name) { var index = Array.IndexOf(args, name); return index >= 0 && index + 1 < args.Length ? args[index + 1] : null; } sealed record LoginResponse( [property: System.Text.Json.Serialization.JsonPropertyName("access_token")] string AccessToken, [property: System.Text.Json.Serialization.JsonPropertyName("token_type")] string TokenType, [property: System.Text.Json.Serialization.JsonPropertyName("expires_in")] int ExpiresIn, [property: System.Text.Json.Serialization.JsonPropertyName("correlation_id")] string CorrelationId); sealed record EventList([property: System.Text.Json.Serialization.JsonPropertyName("events")] IReadOnlyList Events); sealed record EventView( [property: System.Text.Json.Serialization.JsonPropertyName("event_id")] string EventId, [property: System.Text.Json.Serialization.JsonPropertyName("node_id")] string NodeId, [property: System.Text.Json.Serialization.JsonPropertyName("account_id")] string AccountId, [property: System.Text.Json.Serialization.JsonPropertyName("chat_id")] string ChatId, [property: System.Text.Json.Serialization.JsonPropertyName("chat_type")] string ChatType, [property: System.Text.Json.Serialization.JsonPropertyName("event_seq")] long EventSeq, [property: System.Text.Json.Serialization.JsonPropertyName("event_type")] string EventType, [property: System.Text.Json.Serialization.JsonPropertyName("occurred_at")] DateTimeOffset OccurredAt, [property: System.Text.Json.Serialization.JsonPropertyName("content")] string? Content, [property: System.Text.Json.Serialization.JsonPropertyName("config_version")] long ConfigVersion, [property: System.Text.Json.Serialization.JsonPropertyName("authorization_version")] long AuthorizationVersion, [property: System.Text.Json.Serialization.JsonPropertyName("correlation_id")] string CorrelationId, [property: System.Text.Json.Serialization.JsonPropertyName("authorized")] bool Authorized, [property: System.Text.Json.Serialization.JsonPropertyName("content_hash")] string ContentHash, [property: System.Text.Json.Serialization.JsonPropertyName("received_at")] DateTimeOffset ReceivedAt);