145 lines
8.6 KiB
C#
145 lines
8.6 KiB
C#
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<RemoteTaskSubmissionResponse>(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<RemoteTaskSubmissionResponse>(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<JsonElement>(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<EventList>(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<T> WebPostAsync<T>(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<T>(content, RemoteJson.Options) ?? throw new InvalidOperationException("Empty response.");
|
|
}
|
|
|
|
async Task<T> WebGetAsync<T>(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<T>(content, RemoteJson.Options) ?? throw new InvalidOperationException("Empty response.");
|
|
}
|
|
|
|
async Task<string> LoginAsync(HttpClient client, string username, string password)
|
|
{
|
|
var result = await WebPostAsync<LoginResponse>(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<EventView> 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);
|