Dink0 commited on
Commit
e91560b
·
verified ·
1 Parent(s): 92cad08

Verify Cloudflare runtime compatibility and add read-only relay diagnostics

Browse files
Files changed (3) hide show
  1. README.md +4 -0
  2. receiver.py +12 -4
  3. telegram-relay/worker.mjs +14 -4
README.md CHANGED
@@ -63,6 +63,10 @@ Telegram as before. The Worker holds its own bot token and allows only this
63
  monitor's five Bot API methods for one numeric chat ID. Both normal delivery and
64
  authenticated diagnostics use the configured transport.
65
 
 
 
 
 
66
  See `telegram-relay/README.md` for deployment and secret configuration. Verify
67
  the relay's read-only getMe, getChat and getChatMember checks before rebuilding
68
  the Space, then verify an actual publication arrives after deployment. Neither
 
63
  monitor's five Bot API methods for one numeric chat ID. Both normal delivery and
64
  authenticated diagnostics use the configured transport.
65
 
66
+ When the relay is configured, read-only `/diagnostics` and `/status` also accept
67
+ `Authorization: Bearer TELEGRAM_RELAY_SECRET`. The existing webhook-secret
68
+ routes continue to work. This does not authorize incoming publication webhooks.
69
+
70
  See `telegram-relay/README.md` for deployment and secret configuration. Verify
71
  the relay's read-only getMe, getChat and getChatMember checks before rebuilding
72
  the Space, then verify an actual publication arrives after deployment. Neither
receiver.py CHANGED
@@ -28,7 +28,7 @@ from urllib.parse import urlsplit
28
  from zoneinfo import ZoneInfo
29
 
30
  LOG = logging.getLogger("monitor")
31
- VERSION = "1.2-delivery-health"
32
  MAX_BYTES = 1024 * 1024
33
  AI_CHUNK = 12000
34
  DEFAULT_PROMPT = """Ты классификатор публикаций для мониторинга нового внешнего вида
@@ -331,7 +331,9 @@ def network_error_category(exc):
331
 
332
  def request_json(url, payload, headers=None, service="api", timeout=45):
333
  request = urllib.request.Request(url, data=json.dumps(payload, ensure_ascii=False).encode(),
334
- headers={"Content-Type": "application/json", **(headers or {})}, method="POST")
 
 
335
  try:
336
  with urllib.request.urlopen(request, timeout=timeout) as response:
337
  result = json.loads(response.read(2 * MAX_BYTES))
@@ -596,15 +598,21 @@ def make_server(monitor, host="0.0.0.0", port=7860):
596
  def authorized(self, prefix):
597
  return secrets.compare_digest(urlsplit(self.path).path.encode(), (prefix + monitor.config.secret).encode())
598
 
 
 
 
 
 
599
  def do_GET(self):
600
  if urlsplit(self.path).path == "/live":
601
  return self.reply(200, {"status": "alive", "version": VERSION})
602
  if urlsplit(self.path).path in ("/", "/health"):
603
  health = monitor.health()
604
  return self.reply(503 if health["status"] in ("storage_unavailable", "worker_unavailable") else 200, health)
605
- if self.authorized("/diagnostics/"):
606
  return self.reply(200, monitor.clients.diagnose_telegram())
607
- if self.authorized("/status/") or self.authorized("/samples/"):
 
608
  try:
609
  return self.reply(200, monitor.status())
610
  except Exception:
 
28
  from zoneinfo import ZoneInfo
29
 
30
  LOG = logging.getLogger("monitor")
31
+ VERSION = "1.3-telegram-relay"
32
  MAX_BYTES = 1024 * 1024
33
  AI_CHUNK = 12000
34
  DEFAULT_PROMPT = """Ты классификатор публикаций для мониторинга нового внешнего вида
 
331
 
332
  def request_json(url, payload, headers=None, service="api", timeout=45):
333
  request = urllib.request.Request(url, data=json.dumps(payload, ensure_ascii=False).encode(),
334
+ headers={"Content-Type": "application/json", "Accept": "application/json",
335
+ "User-Agent": "MedialogiaMonitor/" + VERSION,
336
+ **(headers or {})}, method="POST")
337
  try:
338
  with urllib.request.urlopen(request, timeout=timeout) as response:
339
  result = json.loads(response.read(2 * MAX_BYTES))
 
598
  def authorized(self, prefix):
599
  return secrets.compare_digest(urlsplit(self.path).path.encode(), (prefix + monitor.config.secret).encode())
600
 
601
+ def relay_authorized(self):
602
+ secret = monitor.config.telegram_relay_secret
603
+ return bool(secret) and secrets.compare_digest(
604
+ self.headers.get("Authorization", "").encode(), ("Bearer " + secret).encode())
605
+
606
  def do_GET(self):
607
  if urlsplit(self.path).path == "/live":
608
  return self.reply(200, {"status": "alive", "version": VERSION})
609
  if urlsplit(self.path).path in ("/", "/health"):
610
  health = monitor.health()
611
  return self.reply(503 if health["status"] in ("storage_unavailable", "worker_unavailable") else 200, health)
612
+ if self.authorized("/diagnostics/") or (urlsplit(self.path).path == "/diagnostics" and self.relay_authorized()):
613
  return self.reply(200, monitor.clients.diagnose_telegram())
614
+ if (self.authorized("/status/") or self.authorized("/samples/")
615
+ or (urlsplit(self.path).path == "/status" and self.relay_authorized())):
616
  try:
617
  return self.reply(200, monitor.status())
618
  except Exception:
telegram-relay/worker.mjs CHANGED
@@ -83,19 +83,29 @@ export default {
83
  method: "POST",
84
  headers: { "Content-Type": "application/json" },
85
  body: JSON.stringify(input.payload),
86
- redirect: "error",
87
  signal: AbortSignal.timeout(8000),
88
  },
89
  );
 
90
  const body = await response.text();
91
- const data = JSON.parse(body);
 
 
 
 
 
92
  if (!data || typeof data.ok !== "boolean") return reply(502, "telegram_invalid_response");
93
  return new Response(body, {
94
  status: response.status,
95
  headers: { "Content-Type": "application/json", "Cache-Control": "no-store" },
96
  });
97
- } catch {
98
- return reply(502, "telegram_upstream_unavailable");
 
 
 
 
99
  }
100
  },
101
  };
 
83
  method: "POST",
84
  headers: { "Content-Type": "application/json" },
85
  body: JSON.stringify(input.payload),
86
+ redirect: "manual",
87
  signal: AbortSignal.timeout(8000),
88
  },
89
  );
90
+ if (response.status >= 300 && response.status < 400) return reply(502, "telegram_unexpected_redirect");
91
  const body = await response.text();
92
+ let data;
93
+ try {
94
+ data = JSON.parse(body);
95
+ } catch {
96
+ return reply(502, "telegram_upstream_non_json_http_" + response.status);
97
+ }
98
  if (!data || typeof data.ok !== "boolean") return reply(502, "telegram_invalid_response");
99
  return new Response(body, {
100
  status: response.status,
101
  headers: { "Content-Type": "application/json", "Cache-Control": "no-store" },
102
  });
103
+ } catch (error) {
104
+ if (typeof AbortSignal.timeout !== "function") return reply(502, "telegram_timeout_api_unavailable");
105
+ if (String(error.message).includes("Network connection lost")) return reply(502, "telegram_network_connection_lost");
106
+ if (String(error.message).includes("resolve")) return reply(502, "telegram_dns_error");
107
+ const category = ["TimeoutError", "AbortError", "TypeError"].includes(error.name) ? error.name : "unknown";
108
+ return reply(502, "telegram_upstream_" + category);
109
  }
110
  },
111
  };