const allowedMethods = new Set(["getMe", "getChat", "getChatMember", "sendMessage", "editMessageText"]); const maxBytes = 65536; function reply(status, description) { return Response.json({ ok: false, description }, { status, headers: { "Cache-Control": "no-store" }, }); } async function authorized(actual, expected) { const encoder = new TextEncoder(); const [actualHash, expectedHash] = await Promise.all([ crypto.subtle.digest("SHA-256", encoder.encode(actual)), crypto.subtle.digest("SHA-256", encoder.encode(expected)), ]); const actualBytes = new Uint8Array(actualHash); const expectedBytes = new Uint8Array(expectedHash); let difference = 0; for (let index = 0; index < actualBytes.length; index += 1) { difference |= actualBytes[index] ^ expectedBytes[index]; } return difference === 0; } async function readBody(request) { if (!request.body) throw new Error("empty_body"); const reader = request.body.getReader(); const chunks = []; let size = 0; while (true) { const { value, done } = await reader.read(); if (done) break; size += value.byteLength; if (size > maxBytes) { await reader.cancel(); throw new Error("payload_too_large"); } chunks.push(value); } const body = new Uint8Array(size); let offset = 0; for (const chunk of chunks) { body.set(chunk, offset); offset += chunk.byteLength; } return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(body)); } export default { async fetch(request, environment) { const url = new URL(request.url); if (url.pathname !== "/telegram" || url.search || request.method !== "POST") { return reply(404, "not_found"); } if (!/^\d+:[A-Za-z0-9_-]+$/.test(environment.TELEGRAM_BOT_TOKEN || "") || !/^[A-Za-z0-9_-]{32,}$/.test(environment.RELAY_SECRET || "") || !/^-?\d+$/.test(environment.TELEGRAM_CHAT_ID || "")) { return reply(503, "configuration_required"); } if (!await authorized(request.headers.get("Authorization") || "", "Bearer " + environment.RELAY_SECRET)) { return reply(401, "unauthorized"); } let input; try { input = await readBody(request); } catch (error) { return reply(error.message === "payload_too_large" ? 413 : 400, "invalid_payload"); } if (!input || typeof input !== "object" || !allowedMethods.has(input.method) || !input.payload || typeof input.payload !== "object" || Array.isArray(input.payload)) { return reply(400, "invalid_method_or_payload"); } if (input.method === "getMe") { input.payload = {}; } else if (String(input.payload.chat_id) !== environment.TELEGRAM_CHAT_ID) { return reply(403, "chat_not_allowed"); } try { const response = await fetch( "https://api.telegram.org/bot" + environment.TELEGRAM_BOT_TOKEN + "/" + input.method, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input.payload), redirect: "manual", signal: AbortSignal.timeout(8000), }, ); if (response.status >= 300 && response.status < 400) return reply(502, "telegram_unexpected_redirect"); const body = await response.text(); let data; try { data = JSON.parse(body); } catch { return reply(502, "telegram_upstream_non_json_http_" + response.status); } if (!data || typeof data.ok !== "boolean") return reply(502, "telegram_invalid_response"); return new Response(body, { status: response.status, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }, }); } catch (error) { if (typeof AbortSignal.timeout !== "function") return reply(502, "telegram_timeout_api_unavailable"); if (String(error.message).includes("Network connection lost")) return reply(502, "telegram_network_connection_lost"); if (String(error.message).includes("resolve")) return reply(502, "telegram_dns_error"); const category = ["TimeoutError", "AbortError", "TypeError"].includes(error.name) ? error.name : "unknown"; return reply(502, "telegram_upstream_" + category); } }, };