Skip to content

Commit 00edfe9

Browse files
committed
fix: Enhance Apps Script language handling and improve response parsing logic
1 parent 45c39af commit 00edfe9

2 files changed

Lines changed: 35 additions & 12 deletions

File tree

src/relay/domain_fronter.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class DomainFronter:
110110
"sec-fetch-site",
111111
)
112112
_SAFE_RETRY_METHODS = {"GET", "HEAD", "OPTIONS"}
113+
_APPS_SCRIPT_DEFAULT_LANG = "en"
113114

114115
def __init__(self, config: dict):
115116
self.connect_host = config.get("google_ip", "216.239.38.120")
@@ -128,6 +129,9 @@ def __init__(self, config: dict):
128129
self._script_idx = 0
129130
self.script_id = self._script_ids[0] # backward compat / logging
130131
self._dev_available = False # True if /dev endpoint works (no redirect, ~400ms faster)
132+
self._apps_script_lang = str(
133+
config.get("apps_script_lang", self._APPS_SCRIPT_DEFAULT_LANG)
134+
).strip().lower() or self._APPS_SCRIPT_DEFAULT_LANG
131135

132136
# Simple execution monitor: log total consumed Apps Script executions.
133137
self._execution_report_interval = 5.0
@@ -597,10 +601,13 @@ async def _probe_sni_latency_once(self, sni: str, sid: str) -> float | None:
597601
payload = json.dumps(
598602
{"m": "GET", "u": "http://example.com/", "k": self.auth_key}
599603
).encode()
604+
path = f"/macros/s/{sid}/exec?hl={self._apps_script_lang}"
600605
request = (
601-
f"POST /macros/s/{sid}/exec HTTP/1.1\r\n"
606+
f"POST {path} HTTP/1.1\r\n"
602607
f"Host: {self.http_host}\r\n"
603608
"Content-Type: application/json\r\n"
609+
"Accept: application/json,text/plain,*/*\r\n"
610+
"Accept-Language: en-US,en;q=0.9\r\n"
604611
f"Content-Length: {len(payload)}\r\n"
605612
"Connection: close\r\n\r\n"
606613
).encode() + payload
@@ -952,7 +959,17 @@ def _exec_path(self, url_or_host: str | None = None) -> str:
952959

953960
def _exec_path_for_sid(self, sid: str) -> str:
954961
"""Build the /macros/s/<sid>/(dev|exec) path for a specific script ID."""
955-
return f"/macros/s/{sid}/{'dev' if self._dev_available else 'exec'}"
962+
endpoint = "dev" if self._dev_available else "exec"
963+
# Force Google Apps Script UI/errors to English for stable diagnostics.
964+
return f"/macros/s/{sid}/{endpoint}?hl={self._apps_script_lang}"
965+
966+
def _apps_script_headers(self) -> dict[str, str]:
967+
"""Headers for Apps Script relay calls (control-plane, not target origin)."""
968+
return {
969+
"content-type": "application/json",
970+
"accept": "application/json,text/plain,*/*",
971+
"accept-language": "en-US,en;q=0.9",
972+
}
956973
async def _flush_pool(self):
957974
"""Close all pooled connections (they may be stale after errors)."""
958975
async with self._pool_lock:
@@ -1140,13 +1157,13 @@ async def _prewarm_script(self):
11401157
payload = json.dumps(
11411158
{"m": "GET", "u": "http://example.com/", "k": self.auth_key}
11421159
).encode()
1143-
hdrs = {"content-type": "application/json"}
1160+
hdrs = self._apps_script_headers()
11441161
sid = self._script_ids[0]
11451162

11461163
# Test /dev endpoint — returns data inline (no 302 redirect).
11471164
# If it works, saves ~400ms per request by eliminating one round trip.
11481165
try:
1149-
dev_path = f"/macros/s/{sid}/dev"
1166+
dev_path = f"/macros/s/{sid}/dev?hl={self._apps_script_lang}"
11501167
t0 = time.perf_counter()
11511168
self._record_execution(sid)
11521169
status, _, body = await asyncio.wait_for(
@@ -1167,7 +1184,7 @@ async def _prewarm_script(self):
11671184

11681185
# Fallback: warm up with /exec
11691186
try:
1170-
exec_path = f"/macros/s/{sid}/exec"
1187+
exec_path = f"/macros/s/{sid}/exec?hl={self._apps_script_lang}"
11711188
t0 = time.perf_counter()
11721189
self._record_execution(sid)
11731190
await asyncio.wait_for(
@@ -1233,7 +1250,7 @@ async def _keepalive_loop(self):
12331250
await asyncio.wait_for(
12341251
self._h2.request(
12351252
method="POST", path=path, host=self.http_host,
1236-
headers={"content-type": "application/json"},
1253+
headers=self._apps_script_headers(),
12371254
body=json.dumps(payload).encode(),
12381255
),
12391256
timeout=20,
@@ -2595,7 +2612,7 @@ async def _relay_single_h2(self, payload: dict) -> bytes:
25952612
t0 = time.perf_counter()
25962613
status, headers, body = await (self._pick_h2() or self._h2).request(
25972614
method="POST", path=path, host=self.http_host,
2598-
headers={"content-type": "application/json"},
2615+
headers=self._apps_script_headers(),
25992616
body=json_body,
26002617
timeout=self._relay_timeout,
26012618
)
@@ -2626,7 +2643,7 @@ async def _relay_single_h2_with_sid(self, payload: dict,
26262643

26272644
status, headers, body = await (self._pick_h2() or self._h2).request(
26282645
method="POST", path=path, host=self.http_host,
2629-
headers={"content-type": "application/json"},
2646+
headers=self._apps_script_headers(),
26302647
body=json_body,
26312648
timeout=self._relay_timeout,
26322649
)
@@ -2664,6 +2681,8 @@ async def _follow_redirects(
26642681
request_lines = [
26652682
f"{redirect_method} {rpath} HTTP/1.1",
26662683
f"Host: {parsed.netloc}",
2684+
"Accept: application/json,text/plain,*/*",
2685+
"Accept-Language: en-US,en;q=0.9",
26672686
"Accept-Encoding: gzip",
26682687
"Connection: keep-alive",
26692688
]
@@ -2693,6 +2712,8 @@ async def _relay_single(self, payload: dict) -> bytes:
26932712
f"POST {path} HTTP/1.1\r\n"
26942713
f"Host: {self.http_host}\r\n"
26952714
f"Content-Type: application/json\r\n"
2715+
f"Accept: application/json,text/plain,*/*\r\n"
2716+
f"Accept-Language: en-US,en;q=0.9\r\n"
26962717
f"Content-Length: {len(json_body)}\r\n"
26972718
f"Accept-Encoding: gzip\r\n"
26982719
f"Connection: keep-alive\r\n"
@@ -2745,7 +2766,7 @@ async def _relay_batch(self, payloads: list[dict]) -> list[bytes]:
27452766
status, headers, body = await asyncio.wait_for(
27462767
(self._pick_h2() or self._h2).request(
27472768
method="POST", path=path, host=self.http_host,
2748-
headers={"content-type": "application/json"},
2769+
headers=self._apps_script_headers(),
27492770
body=json_body,
27502771
timeout=batch_timeout,
27512772
),

src/relay/relay_response.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -787,11 +787,13 @@ def parse_relay_response(body: bytes, max_body_bytes: int) -> bytes:
787787
elif preview_lower.startswith("<"):
788788
# HTML response from script.google.com usually indicates that
789789
# Deployment ID is wrong/archived or the deployment was not updated.
790-
# This signature commonly appears as a generic Google Docs wrapper.
790+
# Match only Apps Script-specific wrappers to avoid false positives
791+
# when the destination site itself is RTL or hosted on docs.google.com.
791792
if any(sig in preview_lower for sig in (
792793
"web word processing, presentations and spreadsheets",
793-
"docs.google.com",
794-
"google docs",
794+
"goog.script.init",
795+
"script.google.com/macros",
796+
"/macros/s/",
795797
)):
796798
error_msg = (
797799
"Wrong Apps Script deployment (script_id). "

0 commit comments

Comments
 (0)