using System; using System.IO; using System.Collections.Generic; using System.Threading.Tasks; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json.Nodes; var origin = (Environment.GetEnvironmentVariable("STASHBAY_ORIGIN") ?? "https://stashbay.net").TrimEnd('/'); var key = Environment.GetEnvironmentVariable("STASHBAY_API_KEY") ?? throw new Exception("Set STASHBAY_API_KEY"); if (args.Length == 0) throw new Exception("Pass the file path as an argument"); using var client = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false }) { Timeout = TimeSpan.FromMinutes(5) }; async Task Api(string route, object body) { using var request = new HttpRequestMessage(HttpMethod.Post, origin + "/api/v1" + route); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key); request.Content = new StringContent(System.Text.Json.JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); using var response = await client.SendAsync(request); var text = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) throw new Exception($"HTTP {(int)response.StatusCode}: {text}"); return JsonNode.Parse(text)!; } await using var file = File.OpenRead(args[0]); var size = file.Length; var plan = await Api("/uploads/init", new { filename = Path.GetFileName(args[0]), sizeBytes = size }); var fileId = plan["fileId"]!.GetValue(); Console.Error.WriteLine($"Upload ID: {fileId}"); var partSize = plan["partSize"]!.GetValue(); if (partSize < 1 || partSize > 64 * 1024 * 1024) throw new Exception("Unexpected part size"); var single = plan["single"]!.GetValue(); var strategy = plan["strategy"]!.GetValue(); var parts = new List(); for (var number = 1; number <= plan["partCount"]!.GetValue(); number++) { var bytes = new byte[(int)Math.Min(partSize, size - (long)(number - 1) * partSize)]; await file.ReadExactlyAsync(bytes); var address = single ? plan["uploadUrl"]!.GetValue() : (await Api("/uploads/parts", new { fileId, from = number, count = 1 }))["parts"]![0]!["url"]!.GetValue(); var url = new Uri(new Uri(origin), address); using var request = new HttpRequestMessage(HttpMethod.Put, url); if (strategy == "proxy" && url.GetLeftPart(UriPartial.Authority) == origin) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key); else if (strategy != "presigned" || url.Scheme != "https") throw new Exception("Unexpected upload URL or strategy"); if (single && strategy == "presigned") request.Headers.TryAddWithoutValidation("If-None-Match", "*"); request.Content = new ByteArrayContent(bytes); using var response = await client.SendAsync(request); var text = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) throw new Exception($"PUT {(int)response.StatusCode}: {text}"); if (!single) { var etag = strategy == "proxy" ? JsonNode.Parse(text)?["etag"]?.GetValue() : response.Headers.ETag?.Tag; if (string.IsNullOrEmpty(etag)) throw new Exception("Missing part ETag"); parts.Add(new { partNumber = number, etag = etag.Trim('"') }); } } var result = await Api("/uploads/complete", new { fileId, parts }); Console.WriteLine(result["shareUrl"]?.GetValue() ?? result.ToJsonString());