dragg2 commited on
Commit
d4caa8a
·
verified ·
1 Parent(s): 2ae4069

Upload 4 files

Browse files
Files changed (1) hide show
  1. app.py +66 -41
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import os
 
2
  import json
3
 
4
  from fastapi import FastAPI, Request
@@ -10,13 +11,61 @@ import httpx
10
  UPSTREAM = f"http://127.0.0.1:{os.environ.get('TELEGRAM_UPSTREAM_PORT', '8081')}"
11
  app = FastAPI()
12
 
13
- @app.get("/")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  async def root():
15
  return {"ok": True, "hint": "use /tg/ prefix, e.g. /tg/bot<TOKEN>/getMe"}
16
 
17
  @app.api_route("/tg/{path:path}", methods=["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"])
18
  async def proxy(path: str, request: Request):
19
- url = f"{UPSTREAM}/{path}"
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  params = list(request.query_params.multi_items())
22
 
@@ -30,8 +79,8 @@ async def proxy(path: str, request: Request):
30
  # 关键修复:
31
  # - Bot API 的 /bot... 响应是小 JSON(sendDocument/getFile/getMe),直接“读全量再返回”,避免流式上下文提前关闭导致空响应。
32
  # - Bot API 的 /file/... 响应是大文件,才需要流式透传。
33
- is_file_download = path.startswith("file/") or path.startswith("/file/")
34
- is_get_file = "/getFile" in ("/" + path)
35
 
36
  passthrough_allow = {
37
  "content-type",
@@ -69,43 +118,19 @@ async def proxy(path: str, request: Request):
69
  if isinstance(payload, dict) and payload.get("ok") is True and isinstance(payload.get("result"), dict):
70
  result = payload.get("result") or {}
71
  fp = result.get("file_path")
72
- if isinstance(fp, str) and fp:
73
- # path 里提取 token(URL path 中是 url-encoded 的形式,正好能匹配 file_path 里的 token 段)
74
- token_enc = None
75
- p = path.lstrip("/")
76
- if p.startswith("bot"):
77
- token_enc = p[3:].split("/", 1)[0] or None
78
-
79
- raw_fp = fp.lstrip("/")
80
- # 优先用 “/<token>/” 作为分隔点,截掉前面的工作目录
81
- if token_enc:
82
- marker = f"/{token_enc}/"
83
- idx = raw_fp.find(marker)
84
- if idx >= 0:
85
- raw_fp = raw_fp[idx + len(marker) :]
86
- elif raw_fp.startswith(f"{token_enc}/"):
87
- raw_fp = raw_fp[len(token_enc) + 1 :]
88
-
89
- # 最终只保留常见的相对目录开头(documents/photos/videos/voice/audio/animations/stickers 等)
90
- for prefix in (
91
- "documents/",
92
- "photos/",
93
- "videos/",
94
- "video_notes/",
95
- "voice/",
96
- "audio/",
97
- "animations/",
98
- "stickers/",
99
- "profile_photos/",
100
- "thumbnails/",
101
- ):
102
- if raw_fp.startswith(prefix):
103
- result["file_path"] = raw_fp
104
- payload["result"] = result
105
- body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
106
- # 确保 JSON 的 content-type 不被冲掉
107
- resp_headers.setdefault("content-type", "application/json")
108
- return Response(content=body, status_code=r.status_code, headers=resp_headers)
109
  except Exception:
110
  pass
111
 
 
1
  import os
2
+ import os
3
  import json
4
 
5
  from fastapi import FastAPI, Request
 
11
  UPSTREAM = f"http://127.0.0.1:{os.environ.get('TELEGRAM_UPSTREAM_PORT', '8081')}"
12
  app = FastAPI()
13
 
14
+ def _normalize_bot_api_file_path(raw_fp: str | None, token_enc: str | None) -> str:
15
+ if not raw_fp:
16
+ return ""
17
+
18
+ fp = str(raw_fp).replace("\\", "/").lstrip("/")
19
+
20
+ # 有些 Local Bot API Server 会把 token 段也带进 file_path,先把它裁掉
21
+ if token_enc:
22
+ marker = f"/{token_enc}/"
23
+ idx = fp.find(marker)
24
+ if idx >= 0:
25
+ fp = fp[idx + len(marker) :]
26
+ elif fp.startswith(f"{token_enc}/"):
27
+ fp = fp[len(token_enc) + 1 :]
28
+
29
+ parts = [p for p in fp.split("/") if p]
30
+ roots = {
31
+ "documents",
32
+ "photos",
33
+ "videos",
34
+ "video_notes",
35
+ "voice",
36
+ "audio",
37
+ "animation",
38
+ "animations",
39
+ "stickers",
40
+ "files",
41
+ "thumbnails",
42
+ "profile_photos",
43
+ }
44
+ for i, p in enumerate(parts):
45
+ if p in roots:
46
+ return "/".join(parts[i:])
47
+ return fp
48
+
49
+ @app.get("/")
50
  async def root():
51
  return {"ok": True, "hint": "use /tg/ prefix, e.g. /tg/bot<TOKEN>/getMe"}
52
 
53
  @app.api_route("/tg/{path:path}", methods=["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"])
54
  async def proxy(path: str, request: Request):
55
+ # 兜底:如果下游请求把 file_path 带成了“工作目录前缀”,这里直接改写成相对路径再转发给 telegram-bot-api
56
+ path_for_upstream = path.lstrip("/")
57
+ if path_for_upstream.startswith("file/"):
58
+ # 形如:file/bot<TOKEN>/<file_path>
59
+ # 我们只需要把 <file_path> 规范化成 documents/... 这种相对路径
60
+ rest = path_for_upstream[len("file/") :]
61
+ if rest.startswith("bot"):
62
+ token_enc = rest[3:].split("/", 1)[0] or None
63
+ after_bot = rest.split("/", 1)[1] if "/" in rest else ""
64
+ fixed = _normalize_bot_api_file_path(after_bot, token_enc)
65
+ if fixed:
66
+ path_for_upstream = f"file/bot{token_enc}/{fixed}"
67
+
68
+ url = f"{UPSTREAM}/{path_for_upstream}"
69
 
70
  params = list(request.query_params.multi_items())
71
 
 
79
  # 关键修复:
80
  # - Bot API 的 /bot... 响应是小 JSON(sendDocument/getFile/getMe),直接“读全量再返回”,避免流式上下文提前关闭导致空响应。
81
  # - Bot API 的 /file/... 响应是大文件,才需要流式透传。
82
+ is_file_download = path_for_upstream.startswith("file/")
83
+ is_get_file = "/getFile" in ("/" + path_for_upstream)
84
 
85
  passthrough_allow = {
86
  "content-type",
 
118
  if isinstance(payload, dict) and payload.get("ok") is True and isinstance(payload.get("result"), dict):
119
  result = payload.get("result") or {}
120
  fp = result.get("file_path")
121
+ token_enc = None
122
+ p = path_for_upstream.lstrip("/")
123
+ if p.startswith("bot"):
124
+ token_enc = p[3:].split("/", 1)[0] or None
125
+
126
+ fixed = _normalize_bot_api_file_path(fp if isinstance(fp, str) else None, token_enc)
127
+ if fixed:
128
+ result["file_path"] = fixed
129
+ payload["result"] = result
130
+ body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
131
+ # 确保 JSON 的 content-type 不被冲掉
132
+ resp_headers.setdefault("content-type", "application/json")
133
+ return Response(content=body, status_code=r.status_code, headers=resp_headers)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  except Exception:
135
  pass
136