From 2dc1f86229f10c064df5d675fe415a48cc40c9e7 Mon Sep 17 00:00:00 2001 From: 72yoi <72yoi@local> Date: Mon, 9 Mar 2026 20:15:32 +0900 Subject: [PATCH] =?UTF-8?q?=E8=AA=BF=E6=9F=BB=20gas=E7=BF=BB=E8=A8=B3?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E3=81=AE=E5=8E=9F=E5=9B=A0=E3=81=AE=E7=89=B9?= =?UTF-8?q?=E5=AE=9A=E3=81=A8=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../provider/GasTranslationProvider.java | 96 +++++++++++++++++-- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/io/github/chatglot/translation/provider/GasTranslationProvider.java b/common/src/main/java/io/github/chatglot/translation/provider/GasTranslationProvider.java index b995562..ac10333 100644 --- a/common/src/main/java/io/github/chatglot/translation/provider/GasTranslationProvider.java +++ b/common/src/main/java/io/github/chatglot/translation/provider/GasTranslationProvider.java @@ -17,6 +17,7 @@ import java.util.Locale; public final class GasTranslationProvider implements TranslationProvider { + private static final int MAX_REDIRECTS = 5; private final HttpClient httpClient = HttpClient.newHttpClient(); @Override @@ -38,14 +39,12 @@ public TranslationResult translate(TranslationRequest request, ChatglotConfig co payload.addProperty("source", normalizeLanguageCode(request.sourceLanguageHint())); } - HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(config.gasWebAppUrl.trim())) - .timeout(Duration.ofSeconds(config.requestTimeoutSeconds)) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(payload.toString(), StandardCharsets.UTF_8)) - .build(); - try { - HttpResponse response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + HttpResponse response = sendWithRedirects( + config.gasWebAppUrl.trim(), + payload.toString(), + config.requestTimeoutSeconds + ); if (response.statusCode() >= 400) { throw new TranslationException( "GAS translation request failed (" @@ -55,7 +54,24 @@ public TranslationResult translate(TranslationRequest request, ChatglotConfig co ); } - JsonObject root = JsonParser.parseString(response.body()).getAsJsonObject(); + String responseBody = response.body() == null ? "" : response.body(); + String normalizedBody = normalizeJsonBody(responseBody); + + JsonObject root; + try { + root = JsonParser.parseString(normalizedBody).getAsJsonObject(); + } catch (RuntimeException parseError) { + throw new TranslationException( + "GAS returned non-JSON response (" + + response.statusCode() + + ", " + + readContentType(response) + + "): " + + TranslationPromptBuilder.abbreviate(responseBody, 500), + parseError + ); + } + if (root.has("ok") && !root.get("ok").getAsBoolean()) { throw new TranslationException("GAS translation failed: " + extractErrorMessage(root)); } @@ -78,6 +94,70 @@ public TranslationResult translate(TranslationRequest request, ChatglotConfig co } } + private HttpResponse sendWithRedirects(String url, String requestBody, int timeoutSeconds) throws Exception { + URI currentUri = URI.create(url); + boolean usePost = true; + + for (int attempt = 0; attempt <= MAX_REDIRECTS; attempt++) { + HttpRequest.Builder builder = HttpRequest.newBuilder(currentUri) + .timeout(Duration.ofSeconds(timeoutSeconds)); + HttpRequest request; + if (usePost) { + request = builder + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) + .build(); + } else { + request = builder.GET().build(); + } + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + int status = response.statusCode(); + if (status < 300 || status >= 400) { + return response; + } + + String location = response.headers().firstValue("Location").orElse(""); + if (location.isBlank()) { + return response; + } + + // GAS /exec commonly redirects POST (302) to a googleusercontent URL that should be fetched with GET. + if (status == 301 || status == 302 || status == 303) { + usePost = false; + } + currentUri = currentUri.resolve(location.trim()); + } + + throw new TranslationException("GAS translation request failed: too many redirects."); + } + + private static String normalizeJsonBody(String body) { + if (body == null) { + return ""; + } + + String normalized = body.stripLeading(); + if (normalized.startsWith("\uFEFF")) { + normalized = normalized.substring(1).stripLeading(); + } + + if (normalized.startsWith(")]}'")) { + int lineBreak = normalized.indexOf('\n'); + if (lineBreak >= 0) { + normalized = normalized.substring(lineBreak + 1).stripLeading(); + } else { + normalized = normalized.substring(4).stripLeading(); + } + } + + return normalized; + } + + private static String readContentType(HttpResponse response) { + return response.headers().firstValue("Content-Type").orElse("unknown"); + } + private static String extractErrorMessage(JsonObject root) { String message = getString(root, "message"); String details = getString(root, "details");