fix: Enhance Apps Script language handling and improve response parsing logic

This commit is contained in:
Abolfazl
2026-05-13 05:44:22 +03:30
parent 45c39af110
commit 00edfe95c2
2 changed files with 35 additions and 12 deletions
+30 -9
View File
@@ -110,6 +110,7 @@ class DomainFronter:
"sec-fetch-site", "sec-fetch-site",
) )
_SAFE_RETRY_METHODS = {"GET", "HEAD", "OPTIONS"} _SAFE_RETRY_METHODS = {"GET", "HEAD", "OPTIONS"}
_APPS_SCRIPT_DEFAULT_LANG = "en"
def __init__(self, config: dict): def __init__(self, config: dict):
self.connect_host = config.get("google_ip", "216.239.38.120") self.connect_host = config.get("google_ip", "216.239.38.120")
@@ -128,6 +129,9 @@ class DomainFronter:
self._script_idx = 0 self._script_idx = 0
self.script_id = self._script_ids[0] # backward compat / logging self.script_id = self._script_ids[0] # backward compat / logging
self._dev_available = False # True if /dev endpoint works (no redirect, ~400ms faster) self._dev_available = False # True if /dev endpoint works (no redirect, ~400ms faster)
self._apps_script_lang = str(
config.get("apps_script_lang", self._APPS_SCRIPT_DEFAULT_LANG)
).strip().lower() or self._APPS_SCRIPT_DEFAULT_LANG
# Simple execution monitor: log total consumed Apps Script executions. # Simple execution monitor: log total consumed Apps Script executions.
self._execution_report_interval = 5.0 self._execution_report_interval = 5.0
@@ -597,10 +601,13 @@ class DomainFronter:
payload = json.dumps( payload = json.dumps(
{"m": "GET", "u": "http://example.com/", "k": self.auth_key} {"m": "GET", "u": "http://example.com/", "k": self.auth_key}
).encode() ).encode()
path = f"/macros/s/{sid}/exec?hl={self._apps_script_lang}"
request = ( request = (
f"POST /macros/s/{sid}/exec HTTP/1.1\r\n" f"POST {path} HTTP/1.1\r\n"
f"Host: {self.http_host}\r\n" f"Host: {self.http_host}\r\n"
"Content-Type: application/json\r\n" "Content-Type: application/json\r\n"
"Accept: application/json,text/plain,*/*\r\n"
"Accept-Language: en-US,en;q=0.9\r\n"
f"Content-Length: {len(payload)}\r\n" f"Content-Length: {len(payload)}\r\n"
"Connection: close\r\n\r\n" "Connection: close\r\n\r\n"
).encode() + payload ).encode() + payload
@@ -952,7 +959,17 @@ class DomainFronter:
def _exec_path_for_sid(self, sid: str) -> str: def _exec_path_for_sid(self, sid: str) -> str:
"""Build the /macros/s/<sid>/(dev|exec) path for a specific script ID.""" """Build the /macros/s/<sid>/(dev|exec) path for a specific script ID."""
return f"/macros/s/{sid}/{'dev' if self._dev_available else 'exec'}" endpoint = "dev" if self._dev_available else "exec"
# Force Google Apps Script UI/errors to English for stable diagnostics.
return f"/macros/s/{sid}/{endpoint}?hl={self._apps_script_lang}"
def _apps_script_headers(self) -> dict[str, str]:
"""Headers for Apps Script relay calls (control-plane, not target origin)."""
return {
"content-type": "application/json",
"accept": "application/json,text/plain,*/*",
"accept-language": "en-US,en;q=0.9",
}
async def _flush_pool(self): async def _flush_pool(self):
"""Close all pooled connections (they may be stale after errors).""" """Close all pooled connections (they may be stale after errors)."""
async with self._pool_lock: async with self._pool_lock:
@@ -1140,13 +1157,13 @@ class DomainFronter:
payload = json.dumps( payload = json.dumps(
{"m": "GET", "u": "http://example.com/", "k": self.auth_key} {"m": "GET", "u": "http://example.com/", "k": self.auth_key}
).encode() ).encode()
hdrs = {"content-type": "application/json"} hdrs = self._apps_script_headers()
sid = self._script_ids[0] sid = self._script_ids[0]
# Test /dev endpoint — returns data inline (no 302 redirect). # Test /dev endpoint — returns data inline (no 302 redirect).
# If it works, saves ~400ms per request by eliminating one round trip. # If it works, saves ~400ms per request by eliminating one round trip.
try: try:
dev_path = f"/macros/s/{sid}/dev" dev_path = f"/macros/s/{sid}/dev?hl={self._apps_script_lang}"
t0 = time.perf_counter() t0 = time.perf_counter()
self._record_execution(sid) self._record_execution(sid)
status, _, body = await asyncio.wait_for( status, _, body = await asyncio.wait_for(
@@ -1167,7 +1184,7 @@ class DomainFronter:
# Fallback: warm up with /exec # Fallback: warm up with /exec
try: try:
exec_path = f"/macros/s/{sid}/exec" exec_path = f"/macros/s/{sid}/exec?hl={self._apps_script_lang}"
t0 = time.perf_counter() t0 = time.perf_counter()
self._record_execution(sid) self._record_execution(sid)
await asyncio.wait_for( await asyncio.wait_for(
@@ -1233,7 +1250,7 @@ class DomainFronter:
await asyncio.wait_for( await asyncio.wait_for(
self._h2.request( self._h2.request(
method="POST", path=path, host=self.http_host, method="POST", path=path, host=self.http_host,
headers={"content-type": "application/json"}, headers=self._apps_script_headers(),
body=json.dumps(payload).encode(), body=json.dumps(payload).encode(),
), ),
timeout=20, timeout=20,
@@ -2595,7 +2612,7 @@ class DomainFronter:
t0 = time.perf_counter() t0 = time.perf_counter()
status, headers, body = await (self._pick_h2() or self._h2).request( status, headers, body = await (self._pick_h2() or self._h2).request(
method="POST", path=path, host=self.http_host, method="POST", path=path, host=self.http_host,
headers={"content-type": "application/json"}, headers=self._apps_script_headers(),
body=json_body, body=json_body,
timeout=self._relay_timeout, timeout=self._relay_timeout,
) )
@@ -2626,7 +2643,7 @@ class DomainFronter:
status, headers, body = await (self._pick_h2() or self._h2).request( status, headers, body = await (self._pick_h2() or self._h2).request(
method="POST", path=path, host=self.http_host, method="POST", path=path, host=self.http_host,
headers={"content-type": "application/json"}, headers=self._apps_script_headers(),
body=json_body, body=json_body,
timeout=self._relay_timeout, timeout=self._relay_timeout,
) )
@@ -2664,6 +2681,8 @@ class DomainFronter:
request_lines = [ request_lines = [
f"{redirect_method} {rpath} HTTP/1.1", f"{redirect_method} {rpath} HTTP/1.1",
f"Host: {parsed.netloc}", f"Host: {parsed.netloc}",
"Accept: application/json,text/plain,*/*",
"Accept-Language: en-US,en;q=0.9",
"Accept-Encoding: gzip", "Accept-Encoding: gzip",
"Connection: keep-alive", "Connection: keep-alive",
] ]
@@ -2693,6 +2712,8 @@ class DomainFronter:
f"POST {path} HTTP/1.1\r\n" f"POST {path} HTTP/1.1\r\n"
f"Host: {self.http_host}\r\n" f"Host: {self.http_host}\r\n"
f"Content-Type: application/json\r\n" f"Content-Type: application/json\r\n"
f"Accept: application/json,text/plain,*/*\r\n"
f"Accept-Language: en-US,en;q=0.9\r\n"
f"Content-Length: {len(json_body)}\r\n" f"Content-Length: {len(json_body)}\r\n"
f"Accept-Encoding: gzip\r\n" f"Accept-Encoding: gzip\r\n"
f"Connection: keep-alive\r\n" f"Connection: keep-alive\r\n"
@@ -2745,7 +2766,7 @@ class DomainFronter:
status, headers, body = await asyncio.wait_for( status, headers, body = await asyncio.wait_for(
(self._pick_h2() or self._h2).request( (self._pick_h2() or self._h2).request(
method="POST", path=path, host=self.http_host, method="POST", path=path, host=self.http_host,
headers={"content-type": "application/json"}, headers=self._apps_script_headers(),
body=json_body, body=json_body,
timeout=batch_timeout, timeout=batch_timeout,
), ),
+5 -3
View File
@@ -787,11 +787,13 @@ def parse_relay_response(body: bytes, max_body_bytes: int) -> bytes:
elif preview_lower.startswith("<"): elif preview_lower.startswith("<"):
# HTML response from script.google.com usually indicates that # HTML response from script.google.com usually indicates that
# Deployment ID is wrong/archived or the deployment was not updated. # Deployment ID is wrong/archived or the deployment was not updated.
# This signature commonly appears as a generic Google Docs wrapper. # Match only Apps Script-specific wrappers to avoid false positives
# when the destination site itself is RTL or hosted on docs.google.com.
if any(sig in preview_lower for sig in ( if any(sig in preview_lower for sig in (
"web word processing, presentations and spreadsheets", "web word processing, presentations and spreadsheets",
"docs.google.com", "goog.script.init",
"google docs", "script.google.com/macros",
"/macros/s/",
)): )):
error_msg = ( error_msg = (
"Wrong Apps Script deployment (script_id). " "Wrong Apps Script deployment (script_id). "