Upload folder using huggingface_hub
Browse files- .gitattributes +4 -0
- .gitignore +17 -0
- Dockerfile +22 -0
- LICENSE +21 -0
- README.md +198 -10
- a+b.png +3 -0
- a.png +3 -0
- api.py +206 -0
- b.png +3 -0
- client.py +1583 -0
- demo_chat.py +110 -0
- get_push_id.py +156 -0
- image.png +3 -0
- requirements.txt +4 -0
- server.py +1719 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
a+b.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
a.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
b.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
image.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
๏ปฟ# Local config / secrets
|
| 2 |
+
config_data.json
|
| 3 |
+
*.local.json
|
| 4 |
+
*.secret
|
| 5 |
+
.env
|
| 6 |
+
.env.*
|
| 7 |
+
|
| 8 |
+
# Runtime logs
|
| 9 |
+
api_logs.json
|
| 10 |
+
*.log
|
| 11 |
+
|
| 12 |
+
# Python cache
|
| 13 |
+
__pycache__/
|
| 14 |
+
*.py[cod]
|
| 15 |
+
|
| 16 |
+
# Local media cache
|
| 17 |
+
media_cache/
|
Dockerfile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
# Ensure media_cache directory exists and is writable
|
| 11 |
+
RUN mkdir -p media_cache && chmod 777 media_cache
|
| 12 |
+
|
| 13 |
+
# Ensure config_data.json exists or is writable
|
| 14 |
+
# HF Spaces use a non-root user, so we need to make sure the app directory is writable
|
| 15 |
+
RUN chmod -R 777 /app
|
| 16 |
+
|
| 17 |
+
ENV PORT=7860
|
| 18 |
+
ENV PYTHONUNBUFFERED=1
|
| 19 |
+
|
| 20 |
+
EXPOSE 7860
|
| 21 |
+
|
| 22 |
+
CMD ["python", "server.py"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 erxiansheng
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,10 +1,198 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gemini Web ่ฝฌ OpenAI API
|
| 2 |
+
|
| 3 |
+
ๅบไบ Gemini ็ฝ้กต็็้ๅๅทฅ็จ๏ผๆไพ OpenAI ๅ
ผๅฎน API ๆๅกใ
|
| 4 |
+
|
| 5 |
+
## โจ ๅ่ฝ็นๆง
|
| 6 |
+
|
| 7 |
+
- โ
ๆๆฌๅฏน่ฏ & ๅค่ฝฎๅฏน่ฏ
|
| 8 |
+
- โ
ๅพ็่ฏๅซ๏ผๆฏๆ base64 ๅ URL๏ผ
|
| 9 |
+
- โ
ๅคๅพ็ๆฏๆ
|
| 10 |
+
- โ
ๅพ็็ๆ๏ผ่ชๅจไธ่ฝฝ้ซๆธ
ๆ ๆฐดๅฐๅๅพ๏ผ
|
| 11 |
+
- โ
่ง้ข็ๆ๏ผๅผๆญฅ๏ผ้ๅฐๅฎ็ฝๆฅ็๏ผ
|
| 12 |
+
- โ
Token ่ชๅจๅทๆฐ๏ผๅๅฐๅฎๆถๅทๆฐ๏ผ้ฒๆญขๅคฑๆ๏ผ
|
| 13 |
+
- โ
Tools / Function Calling ๆฏๆ
|
| 14 |
+
- โ
OpenAI SDK ๅฎๅ
จๅ
ผๅฎน
|
| 15 |
+
- โ
Web ๅๅฐ้
็ฝฎ็้ข
|
| 16 |
+
|
| 17 |
+
## ๐ ๅฟซ้ๅผๅง
|
| 18 |
+
|
| 19 |
+
### 1. ๅฎ่ฃ
ไพ่ต
|
| 20 |
+
|
| 21 |
+
```bash
|
| 22 |
+
pip install -r requirements.txt
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
### 2. ๅฏๅจๆๅก
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
python server.py
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
### 3. ้
็ฝฎ Cookie
|
| 32 |
+
|
| 33 |
+
1. ๆๅผๅๅฐ `http://localhost:8000/admin`๏ผ่ดฆๅท: admin / admin123๏ผ
|
| 34 |
+
2. ็ปๅฝ [Gemini](https://gemini.google.com)๏ผF12 โ Network โ ๅคๅถ่ฏทๆฑๅคดไธญ็ Cookie
|
| 35 |
+
3. ็ฒ่ดดๅฐๅๅฐ้
็ฝฎ้กต้ข๏ผไฟๅญๅณๅฏ
|
| 36 |
+
|
| 37 |
+
Cookie ่ทๅ็คบไพ๏ผ
|
| 38 |
+
|
| 39 |
+

|
| 40 |
+
|
| 41 |
+
### 4. ่ฐ็จ API
|
| 42 |
+
|
| 43 |
+
```python
|
| 44 |
+
from openai import OpenAI
|
| 45 |
+
|
| 46 |
+
client = OpenAI(
|
| 47 |
+
base_url="http://localhost:8000/v1",
|
| 48 |
+
api_key="sk-geminixxxxx"
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
response = client.chat.completions.create(
|
| 52 |
+
model="gemini-3.0-flash",
|
| 53 |
+
messages=[{"role": "user", "content": "ไฝ ๅฅฝ"}]
|
| 54 |
+
)
|
| 55 |
+
print(response.choices[0].message.content)
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
## ๐ก API ไฟกๆฏ
|
| 59 |
+
|
| 60 |
+
| ้กน็ฎ | ๅผ |
|
| 61 |
+
|------|-----|
|
| 62 |
+
| Base URL | `http://localhost:8000/v1` |
|
| 63 |
+
| API Key | `sk-geminixxxxx` |
|
| 64 |
+
| ๅๅฐๅฐๅ | `http://localhost:8000/admin` |
|
| 65 |
+
|
| 66 |
+
### ๅฏ็จๆจกๅ
|
| 67 |
+
|
| 68 |
+
- `gemini-3.0-flash` - ๅฟซ้ๅๅบ
|
| 69 |
+
- `gemini-3.0-flash-thinking` - ๆ่ๆจกๅผ
|
| 70 |
+
- `gemini-3.0-pro` - ไธไธ็
|
| 71 |
+
|
| 72 |
+
## ๐ฌ ไฝฟ็จ็คบไพ
|
| 73 |
+
|
| 74 |
+
### ๆๆฌๅฏน่ฏ
|
| 75 |
+
|
| 76 |
+
```python
|
| 77 |
+
from openai import OpenAI
|
| 78 |
+
|
| 79 |
+
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="sk-geminixxxxx")
|
| 80 |
+
|
| 81 |
+
response = client.chat.completions.create(
|
| 82 |
+
model="gemini-3.0-flash",
|
| 83 |
+
messages=[{"role": "user", "content": "ไฝ ๅฅฝ๏ผไป็ปไธไธไฝ ่ชๅทฑ"}]
|
| 84 |
+
)
|
| 85 |
+
print(response.choices[0].message.content)
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### ๅๅพ็่ฏๅซ
|
| 89 |
+
|
| 90 |
+
```python
|
| 91 |
+
import base64
|
| 92 |
+
from openai import OpenAI
|
| 93 |
+
|
| 94 |
+
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="sk-geminixxxxx")
|
| 95 |
+
|
| 96 |
+
def load_image_base64(path):
|
| 97 |
+
with open(path, "rb") as f:
|
| 98 |
+
return base64.b64encode(f.read()).decode()
|
| 99 |
+
|
| 100 |
+
img_b64 = load_image_base64("image.png")
|
| 101 |
+
|
| 102 |
+
response = client.chat.completions.create(
|
| 103 |
+
model="gemini-3.0-flash",
|
| 104 |
+
messages=[{
|
| 105 |
+
"role": "user",
|
| 106 |
+
"content": [
|
| 107 |
+
{"type": "text", "text": "ๆ่ฟฐ่ฟๅผ ๅพ็"},
|
| 108 |
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_b64}"}}
|
| 109 |
+
]
|
| 110 |
+
}]
|
| 111 |
+
)
|
| 112 |
+
print(response.choices[0].message.content)
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
### ๅคๅพ็้ฎ็ญ
|
| 116 |
+
|
| 117 |
+

|
| 118 |
+
|
| 119 |
+
```python
|
| 120 |
+
import base64
|
| 121 |
+
from openai import OpenAI
|
| 122 |
+
|
| 123 |
+
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="sk-geminixxxxx")
|
| 124 |
+
|
| 125 |
+
def load_image_base64(path):
|
| 126 |
+
with open(path, "rb") as f:
|
| 127 |
+
return base64.b64encode(f.read()).decode()
|
| 128 |
+
|
| 129 |
+
img1_b64 = load_image_base64("a.png")
|
| 130 |
+
img2_b64 = load_image_base64("b.png")
|
| 131 |
+
|
| 132 |
+
response = client.chat.completions.create(
|
| 133 |
+
model="gemini-3.0-pro",
|
| 134 |
+
messages=[{
|
| 135 |
+
"role": "user",
|
| 136 |
+
"content": [
|
| 137 |
+
{"type": "text", "text": "ๆ็งๆฏๆ้็็คไธฒๆขๆๅฆๅคไธๅผ ๅพ็ๆช,ๅค็ๆๅ ๅผ "},
|
| 138 |
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img1_b64}"}},
|
| 139 |
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img2_b64}"}},
|
| 140 |
+
]
|
| 141 |
+
}]
|
| 142 |
+
)
|
| 143 |
+
print(response.choices[0].message.content)
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
### ๅพ็็ๆ
|
| 147 |
+
|
| 148 |
+
```python
|
| 149 |
+
from openai import OpenAI
|
| 150 |
+
|
| 151 |
+
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="sk-geminixxxxx")
|
| 152 |
+
|
| 153 |
+
response = client.chat.completions.create(
|
| 154 |
+
model="gemini-3.0-pro",
|
| 155 |
+
messages=[{"role": "user", "content": "็ๆไธๅผ ๅฏ็ฑ็็ซๅชๅพ็"}]
|
| 156 |
+
)
|
| 157 |
+
print(response.choices[0].message.content)
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
## ๐ง Token ็ฎก็
|
| 161 |
+
|
| 162 |
+
ๅๅฐ้กต้ขๅทฆไธ่งๆพ็คบ Token ็ถๆๅๅทๆฐๆฌกๆฐใ
|
| 163 |
+
|
| 164 |
+
API ็ซฏ็น๏ผ
|
| 165 |
+
- `GET /v1/token/status` - ๆฅ็็ถๆ
|
| 166 |
+
- `POST /v1/token/refresh` - ๆๅจๅทๆฐ
|
| 167 |
+
- `POST /v1/client/reset` - ้็ฝฎๅฎขๆท็ซฏ
|
| 168 |
+
|
| 169 |
+
## โ๏ธ ้
็ฝฎ่ฏดๆ
|
| 170 |
+
|
| 171 |
+
็ผ่พ `server.py` ้กถ้จ๏ผ
|
| 172 |
+
|
| 173 |
+
```python
|
| 174 |
+
API_KEY = "sk-geminixxxxx" # API ๅฏ้ฅ
|
| 175 |
+
PORT = 8000 # ็ซฏๅฃ
|
| 176 |
+
ADMIN_USERNAME = "admin" # ๅๅฐ่ดฆๅท
|
| 177 |
+
ADMIN_PASSWORD = "admin123" # ๅๅฐๅฏ็
|
| 178 |
+
TOKEN_REFRESH_INTERVAL_MIN = 200 # ๅทๆฐ้ด้ๆๅฐ็งๆฐ
|
| 179 |
+
TOKEN_REFRESH_INTERVAL_MAX = 300 # ๅทๆฐ้ด้ๆๅคง็งๆฐ
|
| 180 |
+
MEDIA_BASE_URL = "" # ๅชไฝๅค็ฝๅฐๅ๏ผๅฆ https://your-domain.com
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
## ๐ ๆไปถ่ฏด๏ฟฝ๏ฟฝ
|
| 184 |
+
|
| 185 |
+
| ๆไปถ/ๆไปถๅคน | ่ฏดๆ |
|
| 186 |
+
|-------------|------|
|
| 187 |
+
| `server.py` | API ๆๅก + Web ๅๅฐ |
|
| 188 |
+
| `client.py` | Gemini ้ๅๅฎขๆท็ซฏ |
|
| 189 |
+
| `demo_chat.py` | ๅฎๆด่ฐ็จ็คบไพ๏ผๆๆฌ/ๅๅพ/ๅคๅพ/็ๆ๏ผ |
|
| 190 |
+
| `media_cache/` | AI ่ฟๅๅพ็็ไธญ่ฝฌ็ผๅญๆไปถๅคน |
|
| 191 |
+
| `image.png` | Cookie ่ทๅ็คบไพๅพ |
|
| 192 |
+
| `a.png` / `b.png` | ๅคๅพ้ฎ็ญ็คบๆๅพ |
|
| 193 |
+
| `requirements.txt` | Python ไพ่ต |
|
| 194 |
+
| `config_data.json` | ่ฟ่กๆถ้
็ฝฎ๏ผ่ชๅจ็ๆ๏ผ |
|
| 195 |
+
|
| 196 |
+
## ๐ License
|
| 197 |
+
|
| 198 |
+
MIT
|
a+b.png
ADDED
|
Git LFS Details
|
a.png
ADDED
|
Git LFS Details
|
api.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gemini OpenAI ๅ
ผๅฎน API
|
| 3 |
+
|
| 4 |
+
ๆไพไธ OpenAI SDK ๅฎๅ
จๅ
ผๅฎน็ๆฅๅฃ๏ผๅฏ็ดๆฅๆฟๆข openai ๅบไฝฟ็จ
|
| 5 |
+
|
| 6 |
+
ไฝฟ็จๆนๆณ:
|
| 7 |
+
from api import GeminiOpenAI
|
| 8 |
+
|
| 9 |
+
client = GeminiOpenAI()
|
| 10 |
+
response = client.chat.completions.create(
|
| 11 |
+
model="gemini",
|
| 12 |
+
messages=[{"role": "user", "content": "ไฝ ๅฅฝ"}]
|
| 13 |
+
)
|
| 14 |
+
print(response.choices[0].message.content)
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
from client import GeminiClient, ChatCompletionResponse, Message, ChatCompletionChoice, Usage
|
| 18 |
+
from config import SECURE_1PSID, SNLM0E, COOKIES_STR, PUSH_ID
|
| 19 |
+
from typing import List, Dict, Any, Optional, Union
|
| 20 |
+
import base64
|
| 21 |
+
import time
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class GeminiOpenAI:
|
| 25 |
+
"""
|
| 26 |
+
OpenAI SDK ๅ
ผๅฎน็ Gemini ๅฎขๆท็ซฏ
|
| 27 |
+
|
| 28 |
+
็จๆณไธ openai.OpenAI() ๅฎๅ
จไธ่ด
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
def __init__(
|
| 32 |
+
self,
|
| 33 |
+
cookies_str: str = None,
|
| 34 |
+
snlm0e: str = None,
|
| 35 |
+
push_id: str = None,
|
| 36 |
+
secure_1psid: str = None,
|
| 37 |
+
):
|
| 38 |
+
"""
|
| 39 |
+
ๅๅงๅๅฎขๆท็ซฏ
|
| 40 |
+
|
| 41 |
+
Args:
|
| 42 |
+
cookies_str: ๅฎๆด cookie ๅญ็ฌฆไธฒ๏ผๆจ่๏ผๅพ็ๅ่ฝๅฟ
้๏ผ
|
| 43 |
+
snlm0e: AT Token๏ผๅฟ
ๅกซ๏ผ
|
| 44 |
+
push_id: ๅพ็ไธไผ ID๏ผๅพ็ๅ่ฝๅฟ
้๏ผ
|
| 45 |
+
secure_1psid: __Secure-1PSID cookie๏ผๅฆๆไธ็จ cookies_str๏ผ
|
| 46 |
+
"""
|
| 47 |
+
self._client = GeminiClient(
|
| 48 |
+
secure_1psid=secure_1psid or SECURE_1PSID,
|
| 49 |
+
snlm0e=snlm0e or SNLM0E,
|
| 50 |
+
cookies_str=cookies_str or COOKIES_STR,
|
| 51 |
+
push_id=push_id or PUSH_ID,
|
| 52 |
+
debug=False,
|
| 53 |
+
)
|
| 54 |
+
self.chat = self._Chat(self._client)
|
| 55 |
+
|
| 56 |
+
class _Chat:
|
| 57 |
+
def __init__(self, client: GeminiClient):
|
| 58 |
+
self._client = client
|
| 59 |
+
self.completions = self._Completions(client)
|
| 60 |
+
|
| 61 |
+
class _Completions:
|
| 62 |
+
def __init__(self, client: GeminiClient):
|
| 63 |
+
self._client = client
|
| 64 |
+
|
| 65 |
+
def create(
|
| 66 |
+
self,
|
| 67 |
+
model: str = "gemini",
|
| 68 |
+
messages: List[Dict[str, Any]] = None,
|
| 69 |
+
stream: bool = False,
|
| 70 |
+
**kwargs
|
| 71 |
+
) -> ChatCompletionResponse:
|
| 72 |
+
"""
|
| 73 |
+
ๅๅปบ่ๅคฉๅฎๆ
|
| 74 |
+
|
| 75 |
+
Args:
|
| 76 |
+
model: ๆจกๅๅ็งฐ๏ผๅฟฝ็ฅ๏ผๅง็ปไฝฟ็จ Gemini๏ผ
|
| 77 |
+
messages: OpenAI ๆ ผๅผๆถๆฏๅ่กจ
|
| 78 |
+
stream: ๆฏๅฆๆตๅผ่พๅบ๏ผๆไธๆฏๆ๏ผ
|
| 79 |
+
**kwargs: ๅ
ถไปๅๆฐ๏ผๅฟฝ็ฅ๏ผ
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
ChatCompletionResponse: OpenAI ๆ ผๅผๅๅบ
|
| 83 |
+
|
| 84 |
+
Example:
|
| 85 |
+
# ็บฏๆๆฌ
|
| 86 |
+
response = client.chat.completions.create(
|
| 87 |
+
model="gemini",
|
| 88 |
+
messages=[{"role": "user", "content": "ไฝ ๅฅฝ"}]
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
# ๅธฆๅพ็
|
| 92 |
+
response = client.chat.completions.create(
|
| 93 |
+
model="gemini",
|
| 94 |
+
messages=[{
|
| 95 |
+
"role": "user",
|
| 96 |
+
"content": [
|
| 97 |
+
{"type": "text", "text": "่ฟๆฏไปไน๏ผ"},
|
| 98 |
+
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
|
| 99 |
+
]
|
| 100 |
+
}]
|
| 101 |
+
)
|
| 102 |
+
"""
|
| 103 |
+
if stream:
|
| 104 |
+
raise NotImplementedError("ๆตๅผ่พๅบๆไธๆฏๆ")
|
| 105 |
+
|
| 106 |
+
return self._client.chat(messages=messages)
|
| 107 |
+
|
| 108 |
+
def reset(self):
|
| 109 |
+
"""้็ฝฎไผ่ฏไธไธๆ"""
|
| 110 |
+
self._client.reset()
|
| 111 |
+
|
| 112 |
+
def get_history(self) -> List[Dict]:
|
| 113 |
+
"""่ทๅๆถๆฏๅๅฒ"""
|
| 114 |
+
return self._client.get_history()
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# ไพฟๆทๅฝๆฐ
|
| 118 |
+
def create_client(
|
| 119 |
+
cookies_str: str = None,
|
| 120 |
+
snlm0e: str = None,
|
| 121 |
+
push_id: str = None,
|
| 122 |
+
) -> GeminiOpenAI:
|
| 123 |
+
"""
|
| 124 |
+
ๅๅปบ Gemini ๅฎขๆท็ซฏ๏ผOpenAI ๅ
ผๅฎน๏ผ
|
| 125 |
+
|
| 126 |
+
Args:
|
| 127 |
+
cookies_str: ๅฎๆด cookie ๅญ็ฌฆไธฒ
|
| 128 |
+
snlm0e: AT Token
|
| 129 |
+
push_id: ๅพ็ไธไผ ID
|
| 130 |
+
|
| 131 |
+
Returns:
|
| 132 |
+
GeminiOpenAI: OpenAI ๅ
ผๅฎนๅฎขๆท็ซฏ
|
| 133 |
+
"""
|
| 134 |
+
return GeminiOpenAI(
|
| 135 |
+
cookies_str=cookies_str,
|
| 136 |
+
snlm0e=snlm0e,
|
| 137 |
+
push_id=push_id,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def chat(
|
| 142 |
+
message: str,
|
| 143 |
+
image: bytes = None,
|
| 144 |
+
image_path: str = None,
|
| 145 |
+
reset: bool = False,
|
| 146 |
+
) -> str:
|
| 147 |
+
"""
|
| 148 |
+
ๅฟซ้่ๅคฉๅฝๆฐ๏ผๅไพๆจกๅผ๏ผ
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
message: ๆถๆฏๆๆฌ
|
| 152 |
+
image: ๅพ็ไบ่ฟๅถๆฐๆฎ
|
| 153 |
+
image_path: ๅพ็ๆไปถ่ทฏๅพ
|
| 154 |
+
reset: ๆฏๅฆ้็ฝฎไธไธๆ
|
| 155 |
+
|
| 156 |
+
Returns:
|
| 157 |
+
str: AI ๅๅคๆๆฌ
|
| 158 |
+
|
| 159 |
+
Example:
|
| 160 |
+
from api import chat
|
| 161 |
+
|
| 162 |
+
# ็บฏๆๆฌ
|
| 163 |
+
reply = chat("ไฝ ๅฅฝ")
|
| 164 |
+
|
| 165 |
+
# ๅธฆๅพ็
|
| 166 |
+
reply = chat("่ฟๆฏไปไน๏ผ", image_path="photo.jpg")
|
| 167 |
+
|
| 168 |
+
# ้็ฝฎไธไธๆ
|
| 169 |
+
reply = chat("ๆฐ่ฏ้ข", reset=True)
|
| 170 |
+
"""
|
| 171 |
+
global _default_client
|
| 172 |
+
|
| 173 |
+
if '_default_client' not in globals() or _default_client is None:
|
| 174 |
+
_default_client = GeminiOpenAI()
|
| 175 |
+
|
| 176 |
+
if reset:
|
| 177 |
+
_default_client.reset()
|
| 178 |
+
|
| 179 |
+
# ๅค็ๅพ็
|
| 180 |
+
img_data = None
|
| 181 |
+
if image:
|
| 182 |
+
img_data = image
|
| 183 |
+
elif image_path:
|
| 184 |
+
with open(image_path, 'rb') as f:
|
| 185 |
+
img_data = f.read()
|
| 186 |
+
|
| 187 |
+
# ๆๅปบๆถๆฏ
|
| 188 |
+
if img_data:
|
| 189 |
+
messages = [{
|
| 190 |
+
"role": "user",
|
| 191 |
+
"content": [
|
| 192 |
+
{"type": "text", "text": message},
|
| 193 |
+
{
|
| 194 |
+
"type": "image_url",
|
| 195 |
+
"image_url": {"url": f"data:image/jpeg;base64,{base64.b64encode(img_data).decode()}"}
|
| 196 |
+
}
|
| 197 |
+
]
|
| 198 |
+
}]
|
| 199 |
+
else:
|
| 200 |
+
messages = [{"role": "user", "content": message}]
|
| 201 |
+
|
| 202 |
+
response = _default_client.chat.completions.create(messages=messages)
|
| 203 |
+
return response.choices[0].message.content
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
_default_client: GeminiOpenAI = None
|
b.png
ADDED
|
Git LFS Details
|
client.py
ADDED
|
@@ -0,0 +1,1583 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gemini Web Reverse Engineering Client
|
| 3 |
+
ๆฏๆๅพๆ่ฏทๆฑใไธไธๆๅฏน่ฏ๏ผOpenAI ๆ ผๅผ่พๅ
ฅ่พๅบ
|
| 4 |
+
ๆๅจ้
็ฝฎ token๏ผๆ ้ไปฃ็ ็ปๅฝ
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
import json
|
| 9 |
+
import random
|
| 10 |
+
import string
|
| 11 |
+
import base64
|
| 12 |
+
import uuid
|
| 13 |
+
import codecs
|
| 14 |
+
import httpx
|
| 15 |
+
from typing import Optional, List, Dict, Any, Union, Iterator
|
| 16 |
+
from dataclasses import dataclass, field
|
| 17 |
+
from datetime import datetime
|
| 18 |
+
import time
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class CookieExpiredError(Exception):
|
| 22 |
+
"""Cookie ่ฟๆๆๆ ๆๅผๅธธ"""
|
| 23 |
+
pass
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class ImageUploadError(Exception):
|
| 27 |
+
"""ๅพ็ไธไผ ๅคฑ่ดฅๅผๅธธ"""
|
| 28 |
+
pass
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@dataclass
|
| 32 |
+
class Message:
|
| 33 |
+
"""OpenAI ๆ ผๅผๆถๆฏ"""
|
| 34 |
+
role: str
|
| 35 |
+
content: Union[str, List[Dict[str, Any]]]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@dataclass
|
| 39 |
+
class ChatCompletionChoice:
|
| 40 |
+
index: int
|
| 41 |
+
message: Message
|
| 42 |
+
finish_reason: str = "stop"
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@dataclass
|
| 46 |
+
class Usage:
|
| 47 |
+
prompt_tokens: int = 0
|
| 48 |
+
completion_tokens: int = 0
|
| 49 |
+
total_tokens: int = 0
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@dataclass
|
| 53 |
+
class ChatCompletionResponse:
|
| 54 |
+
"""OpenAI ๆ ผๅผๅๅบ"""
|
| 55 |
+
id: str
|
| 56 |
+
object: str = "chat.completion"
|
| 57 |
+
created: int = 0
|
| 58 |
+
model: str = "gemini-web"
|
| 59 |
+
choices: List[ChatCompletionChoice] = field(default_factory=list)
|
| 60 |
+
usage: Usage = field(default_factory=Usage)
|
| 61 |
+
|
| 62 |
+
def to_dict(self) -> dict:
|
| 63 |
+
return {
|
| 64 |
+
"id": self.id,
|
| 65 |
+
"object": self.object,
|
| 66 |
+
"created": self.created,
|
| 67 |
+
"model": self.model,
|
| 68 |
+
"choices": [
|
| 69 |
+
{
|
| 70 |
+
"index": c.index,
|
| 71 |
+
"message": {"role": c.message.role, "content": c.message.content},
|
| 72 |
+
"finish_reason": c.finish_reason
|
| 73 |
+
}
|
| 74 |
+
for c in self.choices
|
| 75 |
+
],
|
| 76 |
+
"usage": {
|
| 77 |
+
"prompt_tokens": self.usage.prompt_tokens,
|
| 78 |
+
"completion_tokens": self.usage.completion_tokens,
|
| 79 |
+
"total_tokens": self.usage.total_tokens
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class GeminiClient:
|
| 85 |
+
"""
|
| 86 |
+
Gemini ็ฝ้กต็้ๅๅฎขๆท็ซฏ
|
| 87 |
+
|
| 88 |
+
ไฝฟ็จๆนๆณ:
|
| 89 |
+
1. ๆๅผ https://gemini.google.com ๅนถ็ปๅฝ
|
| 90 |
+
2. F12 ๆๅผๅผๅ่
ๅทฅๅ
ท -> Application -> Cookies
|
| 91 |
+
3. ๅคๅถไปฅไธ cookie ๅผ:
|
| 92 |
+
- __Secure-1PSID
|
| 93 |
+
- __Secure-1PSIDTS (ๅฏ้)
|
| 94 |
+
4. Network ๆ ็ญพ -> ๆพไปปๆ่ฏทๆฑ -> ๅคๅถ SNlM0e ๅผ (ๅจ้กต้ขๆบ็ ไธญๆ็ดข)
|
| 95 |
+
"""
|
| 96 |
+
|
| 97 |
+
BASE_URL = "https://gemini.google.com"
|
| 98 |
+
|
| 99 |
+
def __init__(
|
| 100 |
+
self,
|
| 101 |
+
secure_1psid: str,
|
| 102 |
+
secure_1psidts: str = None,
|
| 103 |
+
secure_1psidcc: str = None,
|
| 104 |
+
snlm0e: str = None,
|
| 105 |
+
bl: str = None,
|
| 106 |
+
cookies_str: str = None,
|
| 107 |
+
push_id: str = None,
|
| 108 |
+
model_ids: dict = None,
|
| 109 |
+
debug: bool = False,
|
| 110 |
+
media_base_url: str = None,
|
| 111 |
+
):
|
| 112 |
+
"""
|
| 113 |
+
ๅๅงๅๅฎขๆท็ซฏ - ๆๅจๅกซๅ token
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
secure_1psid: __Secure-1PSID cookie (ๅฟ
ๅกซ)
|
| 117 |
+
secure_1psidts: __Secure-1PSIDTS cookie (ๆจ่)
|
| 118 |
+
secure_1psidcc: __Secure-1PSIDCC cookie (ๆจ่)
|
| 119 |
+
snlm0e: SNlM0e token (ๅฟ
ๅกซ๏ผไป้กต้ขๆบ็ ่ทๅ)
|
| 120 |
+
bl: BL ็ๆฌๅท (ๅฏ้๏ผ่ชๅจ่ทๅ)
|
| 121 |
+
cookies_str: ๅฎๆด cookie ๅญ็ฌฆไธฒ (ๅฏ้๏ผๆฟไปฃๅ็ฌ่ฎพ็ฝฎ)
|
| 122 |
+
push_id: Push ID for image upload (ๅฟ
ๅกซ็จไบๅพ็ไธไผ )
|
| 123 |
+
model_ids: ๆจกๅ ID ๆ ๅฐ {"flash": "xxx", "pro": "xxx", "thinking": "xxx"}
|
| 124 |
+
debug: ๆฏๅฆๆๅฐ่ฐ่ฏไฟกๆฏ
|
| 125 |
+
media_base_url: ๅชไฝๆไปถ็ๅบ็ก URL (ๅฆ http://localhost:8000)๏ผ็จไบๆๅปบๅฎๆด็ๅชไฝ่ฎฟ้ฎ URL
|
| 126 |
+
"""
|
| 127 |
+
self.secure_1psid = secure_1psid
|
| 128 |
+
self.secure_1psidts = secure_1psidts
|
| 129 |
+
self.secure_1psidcc = secure_1psidcc
|
| 130 |
+
self.snlm0e = snlm0e
|
| 131 |
+
self.bl = bl
|
| 132 |
+
self.push_id = push_id
|
| 133 |
+
self.debug = debug
|
| 134 |
+
self.media_base_url = media_base_url or ""
|
| 135 |
+
|
| 136 |
+
# ๆจกๅ ID ๆ ๅฐ (็จไบ่ฏทๆฑๅคด้ๆฉๆจกๅ)
|
| 137 |
+
self.model_ids = model_ids or {
|
| 138 |
+
"flash": "56fdd199312815e2",
|
| 139 |
+
"pro": "e6fa609c3fa255c0",
|
| 140 |
+
"thinking": "e051ce1aa80aa576",
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
self.session = httpx.Client(
|
| 144 |
+
timeout=1220.0,
|
| 145 |
+
follow_redirects=True,
|
| 146 |
+
headers={
|
| 147 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
| 148 |
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
| 149 |
+
"Origin": self.BASE_URL,
|
| 150 |
+
"Referer": f"{self.BASE_URL}/",
|
| 151 |
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
| 152 |
+
},
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
# ่ฎพ็ฝฎ cookies
|
| 156 |
+
if cookies_str:
|
| 157 |
+
self._set_cookies_from_string(cookies_str)
|
| 158 |
+
else:
|
| 159 |
+
self.session.cookies.set("__Secure-1PSID", secure_1psid, domain=".google.com")
|
| 160 |
+
if secure_1psidts:
|
| 161 |
+
self.session.cookies.set("__Secure-1PSIDTS", secure_1psidts, domain=".google.com")
|
| 162 |
+
if secure_1psidcc:
|
| 163 |
+
self.session.cookies.set("__Secure-1PSIDCC", secure_1psidcc, domain=".google.com")
|
| 164 |
+
|
| 165 |
+
# ไผ่ฏไธไธๆ
|
| 166 |
+
self.conversation_id: str = ""
|
| 167 |
+
self.response_id: str = ""
|
| 168 |
+
self.choice_id: str = ""
|
| 169 |
+
self.request_count: int = 0
|
| 170 |
+
|
| 171 |
+
# ๆถๆฏๅๅฒ
|
| 172 |
+
self.messages: List[Message] = []
|
| 173 |
+
|
| 174 |
+
# ้ช่ฏๅฟ
ๅกซๅๆฐ
|
| 175 |
+
if not self.snlm0e:
|
| 176 |
+
raise ValueError(
|
| 177 |
+
"SNlM0e ๆฏๅฟ
ๅกซๅๆฐ๏ผ\n"
|
| 178 |
+
"่ทๅๆนๆณ:\n"
|
| 179 |
+
"1. ๆๅผ https://gemini.google.com ๅนถ็ปๅฝ\n"
|
| 180 |
+
"2. F12 -> ๆฅ็้กต้ขๆบไปฃ็ (Ctrl+U)\n"
|
| 181 |
+
"3. ๆ็ดข 'SNlM0e' ๆพๅฐ็ฑปไผผ: \"SNlM0e\":\"xxxxxx\"\n"
|
| 182 |
+
"4. ๅคๅถๅผๅทๅ
็ๅผ"
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
# ่ชๅจ่ทๅ bl
|
| 186 |
+
if not self.bl:
|
| 187 |
+
self._fetch_bl()
|
| 188 |
+
|
| 189 |
+
def _set_cookies_from_string(self, cookies_str: str):
|
| 190 |
+
"""ไปๅฎๆด cookie ๅญ็ฌฆไธฒ่งฃๆ"""
|
| 191 |
+
for item in cookies_str.split(";"):
|
| 192 |
+
item = item.strip()
|
| 193 |
+
if "=" in item:
|
| 194 |
+
key, value = item.split("=", 1)
|
| 195 |
+
self.session.cookies.set(key.strip(), value.strip(), domain=".google.com")
|
| 196 |
+
|
| 197 |
+
def _fetch_bl(self):
|
| 198 |
+
"""่ทๅ BL ็ๆฌๅท"""
|
| 199 |
+
try:
|
| 200 |
+
resp = self.session.get(self.BASE_URL)
|
| 201 |
+
match = re.search(r'"cfb2h":"([^"]+)"', resp.text)
|
| 202 |
+
if match:
|
| 203 |
+
self.bl = match.group(1)
|
| 204 |
+
else:
|
| 205 |
+
# ไฝฟ็จ้ป่ฎคๅผ
|
| 206 |
+
self.bl = "boq_assistant-bard-web-server_20241209.00_p0"
|
| 207 |
+
if self.debug:
|
| 208 |
+
print(f"[DEBUG] BL: {self.bl}")
|
| 209 |
+
except Exception as e:
|
| 210 |
+
self.bl = "boq_assistant-bard-web-server_20241209.00_p0"
|
| 211 |
+
if self.debug:
|
| 212 |
+
print(f"[DEBUG] ่ทๅ BL ๅคฑ่ดฅ๏ผไฝฟ็จ้ป่ฎคๅผ: {e}")
|
| 213 |
+
|
| 214 |
+
def refresh_tokens(self) -> dict:
|
| 215 |
+
"""
|
| 216 |
+
ๅทๆฐ token (SNlM0e ๅ push_id)
|
| 217 |
+
|
| 218 |
+
Returns:
|
| 219 |
+
dict: {"success": bool, "snlm0e": str, "push_id": str, "error": str}
|
| 220 |
+
"""
|
| 221 |
+
result = {"success": False, "snlm0e": "", "push_id": "", "error": ""}
|
| 222 |
+
|
| 223 |
+
try:
|
| 224 |
+
if self.debug:
|
| 225 |
+
print("[DEBUG] ๅผๅงๅทๆฐ token...")
|
| 226 |
+
|
| 227 |
+
# ่ฎฟ้ฎ Gemini ้ฆ้กตๅทๆฐ session
|
| 228 |
+
resp = self.session.get(self.BASE_URL)
|
| 229 |
+
|
| 230 |
+
if resp.status_code != 200:
|
| 231 |
+
result["error"] = f"่ฎฟ้ฎ Gemini ๅคฑ่ดฅ: HTTP {resp.status_code}"
|
| 232 |
+
return result
|
| 233 |
+
|
| 234 |
+
html = resp.text
|
| 235 |
+
|
| 236 |
+
# ๆๅๆฐ็ SNlM0e
|
| 237 |
+
snlm0e_patterns = [
|
| 238 |
+
r'"SNlM0e":"([^"]+)"',
|
| 239 |
+
r'SNlM0e["\s:]+["\']([^"\']+)["\']',
|
| 240 |
+
r'"at":"([^"]+)"',
|
| 241 |
+
]
|
| 242 |
+
new_snlm0e = ""
|
| 243 |
+
for pattern in snlm0e_patterns:
|
| 244 |
+
match = re.search(pattern, html)
|
| 245 |
+
if match:
|
| 246 |
+
new_snlm0e = match.group(1)
|
| 247 |
+
break
|
| 248 |
+
|
| 249 |
+
if new_snlm0e:
|
| 250 |
+
self.snlm0e = new_snlm0e
|
| 251 |
+
result["snlm0e"] = new_snlm0e
|
| 252 |
+
if self.debug:
|
| 253 |
+
print(f"[DEBUG] SNlM0e ๅทฒๅทๆฐ: {new_snlm0e[:30]}...")
|
| 254 |
+
|
| 255 |
+
# ๆๅๆฐ็ push_id
|
| 256 |
+
push_id_patterns = [
|
| 257 |
+
r'"push[_-]?id["\s:]+["\'](feeds/[a-z0-9]+)["\']',
|
| 258 |
+
r'push[_-]?id["\s:=]+["\'](feeds/[a-z0-9]+)["\']',
|
| 259 |
+
r'feedName["\s:]+["\'](feeds/[a-z0-9]+)["\']',
|
| 260 |
+
r'clientId["\s:]+["\'](feeds/[a-z0-9]+)["\']',
|
| 261 |
+
r'(feeds/[a-z0-9]{14,})',
|
| 262 |
+
]
|
| 263 |
+
new_push_id = ""
|
| 264 |
+
for pattern in push_id_patterns:
|
| 265 |
+
matches = re.findall(pattern, html, re.IGNORECASE)
|
| 266 |
+
if matches:
|
| 267 |
+
new_push_id = matches[0]
|
| 268 |
+
break
|
| 269 |
+
|
| 270 |
+
if new_push_id:
|
| 271 |
+
self.push_id = new_push_id
|
| 272 |
+
result["push_id"] = new_push_id
|
| 273 |
+
if self.debug:
|
| 274 |
+
print(f"[DEBUG] push_id ๅทฒๅทๆฐ: {new_push_id}")
|
| 275 |
+
|
| 276 |
+
# ๅๆถๅทๆฐ BL
|
| 277 |
+
match = re.search(r'"cfb2h":"([^"]+)"', html)
|
| 278 |
+
if match:
|
| 279 |
+
self.bl = match.group(1)
|
| 280 |
+
|
| 281 |
+
result["success"] = bool(new_snlm0e)
|
| 282 |
+
if not new_snlm0e:
|
| 283 |
+
result["error"] = "ๆ ๆณไป้กต้ขๆๅ SNlM0e๏ผCookie ๅฏ่ฝๅทฒๅฎๅ
จๅคฑๆ"
|
| 284 |
+
|
| 285 |
+
return result
|
| 286 |
+
|
| 287 |
+
except Exception as e:
|
| 288 |
+
result["error"] = f"ๅทๆฐ token ๅคฑ่ดฅ: {str(e)}"
|
| 289 |
+
if self.debug:
|
| 290 |
+
print(f"[DEBUG] ๅทๆฐๅคฑ่ดฅ: {e}")
|
| 291 |
+
return result
|
| 292 |
+
|
| 293 |
+
def check_token_valid(self) -> bool:
|
| 294 |
+
"""
|
| 295 |
+
ๆฃๆฅๅฝๅ token ๆฏๅฆๆๆ
|
| 296 |
+
|
| 297 |
+
Returns:
|
| 298 |
+
bool: True ่กจ็คบๆๆ๏ผFalse ่กจ็คบ้่ฆๅทๆฐ
|
| 299 |
+
"""
|
| 300 |
+
try:
|
| 301 |
+
resp = self.session.get(self.BASE_URL, timeout=10.0)
|
| 302 |
+
if resp.status_code != 200:
|
| 303 |
+
return False
|
| 304 |
+
|
| 305 |
+
# ๆฃๆฅ้กต้ขๆฏๅฆๅ
ๅซ็ปๅฝ็ถๆๆ ่ฏ
|
| 306 |
+
if 'SNlM0e' not in resp.text:
|
| 307 |
+
return False
|
| 308 |
+
|
| 309 |
+
return True
|
| 310 |
+
except Exception:
|
| 311 |
+
return False
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def _parse_content(self, content: Union[str, List[Dict]]) -> tuple:
|
| 315 |
+
"""่งฃๆ OpenAI ๆ ผๅผ content๏ผ่ฟๅ (text, images)"""
|
| 316 |
+
if isinstance(content, str):
|
| 317 |
+
return content, []
|
| 318 |
+
|
| 319 |
+
text_parts = []
|
| 320 |
+
images = []
|
| 321 |
+
|
| 322 |
+
for item in content:
|
| 323 |
+
if item.get("type") == "text":
|
| 324 |
+
text_parts.append(item.get("text", ""))
|
| 325 |
+
elif item.get("type") == "image_url":
|
| 326 |
+
# ๆฏๆไธค็งๆ ผๅผ: {"url": "..."} ๆ็ดๆฅๅญ็ฌฆไธฒ
|
| 327 |
+
image_url_data = item.get("image_url", {})
|
| 328 |
+
if isinstance(image_url_data, str):
|
| 329 |
+
url = image_url_data
|
| 330 |
+
else:
|
| 331 |
+
url = image_url_data.get("url", "")
|
| 332 |
+
|
| 333 |
+
if not url:
|
| 334 |
+
continue
|
| 335 |
+
|
| 336 |
+
if url.startswith("data:"):
|
| 337 |
+
# base64 ๆ ผๅผ: data:image/png;base64,xxxxx
|
| 338 |
+
match = re.match(r'data:([^;]+);base64,(.+)', url)
|
| 339 |
+
if match:
|
| 340 |
+
images.append({"mime_type": match.group(1), "data": match.group(2)})
|
| 341 |
+
elif url.startswith("http://") or url.startswith("https://"):
|
| 342 |
+
# URL ๆ ผๅผ๏ผไธ่ฝฝๅพ็
|
| 343 |
+
try:
|
| 344 |
+
if self.debug:
|
| 345 |
+
print(f"[DEBUG] ๅฐ่ฏไธ่ฝฝๅพ็ URL: {url[:100]}...")
|
| 346 |
+
resp = httpx.get(url, timeout=30, follow_redirects=True)
|
| 347 |
+
if resp.status_code == 200:
|
| 348 |
+
mime = resp.headers.get("content-type", "image/jpeg").split(";")[0]
|
| 349 |
+
images.append({"mime_type": mime, "data": base64.b64encode(resp.content).decode()})
|
| 350 |
+
if self.debug:
|
| 351 |
+
print(f"[DEBUG] ๅพ็ไธ่ฝฝๆๅ: {len(resp.content)} bytes, mime: {mime}")
|
| 352 |
+
else:
|
| 353 |
+
if self.debug:
|
| 354 |
+
print(f"[DEBUG] ๅพ็ไธ่ฝฝๅคฑ่ดฅ: HTTP {resp.status_code}")
|
| 355 |
+
except Exception as e:
|
| 356 |
+
if self.debug:
|
| 357 |
+
print(f"[DEBUG] ไธ่ฝฝๅพ็ๅคฑ่ดฅ: {e}")
|
| 358 |
+
else:
|
| 359 |
+
# ๅฏ่ฝๆฏ็บฏ base64 ๅญ็ฌฆไธฒ (ๆฒกๆ data: ๅ็ผ)
|
| 360 |
+
try:
|
| 361 |
+
# ๅฐ่ฏ่งฃ็ ้ช่ฏๆฏๅฆๆฏๆๆ base64
|
| 362 |
+
base64.b64decode(url[:100]) # ๅช้ช่ฏๅ100ๅญ็ฌฆ
|
| 363 |
+
images.append({"mime_type": "image/png", "data": url})
|
| 364 |
+
if self.debug:
|
| 365 |
+
print(f"[DEBUG] ๆฃๆตๅฐ็บฏ base64 ๅพ็ๆฐๆฎ")
|
| 366 |
+
except:
|
| 367 |
+
if self.debug:
|
| 368 |
+
print(f"[DEBUG] ๆ ๆณ่ฏๅซ็ๅพ็ๆ ผๅผ: {url[:50]}...")
|
| 369 |
+
|
| 370 |
+
return " ".join(text_parts) if text_parts else "", images
|
| 371 |
+
|
| 372 |
+
def _upload_image(self, image_data: bytes, mime_type: str = "image/jpeg") -> str:
|
| 373 |
+
"""
|
| 374 |
+
ไธไผ ๅพ็ๅฐ Gemini ๆๅกๅจ
|
| 375 |
+
|
| 376 |
+
Args:
|
| 377 |
+
image_data: ๅพ็ไบ่ฟๅถๆฐๆฎ
|
| 378 |
+
mime_type: ๅพ็ MIME ็ฑปๅ
|
| 379 |
+
|
| 380 |
+
Returns:
|
| 381 |
+
str: ไธไผ ๅ็ๅพ็่ทฏๅพ๏ผๅธฆ token๏ผ
|
| 382 |
+
"""
|
| 383 |
+
if not self.push_id:
|
| 384 |
+
raise CookieExpiredError(
|
| 385 |
+
"ๅพ็ไธไผ ้่ฆ push_id\n"
|
| 386 |
+
"่ทๅๆนๆณ: ่ฟ่ก python get_push_id.py ๆไปๆต่งๅจ Network ไธญ่ทๅ"
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
try:
|
| 390 |
+
upload_url = "https://push.clients6.google.com/upload/"
|
| 391 |
+
filename = f"image_{random.randint(100000, 999999)}.png"
|
| 392 |
+
|
| 393 |
+
# ๆต่งๅจๅฟ
้็ๅคด
|
| 394 |
+
browser_headers = {
|
| 395 |
+
"accept": "*/*",
|
| 396 |
+
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
|
| 397 |
+
"origin": "https://gemini.google.com",
|
| 398 |
+
"referer": "https://gemini.google.com/",
|
| 399 |
+
"sec-fetch-dest": "empty",
|
| 400 |
+
"sec-fetch-mode": "cors",
|
| 401 |
+
"sec-fetch-site": "same-site",
|
| 402 |
+
"x-browser-channel": "stable",
|
| 403 |
+
"x-browser-copyright": "Copyright 2025 Google LLC. All Rights reserved.",
|
| 404 |
+
"x-browser-validation": "Aj9fzfu+SaGLBY9Oqr3S7RokOtM=",
|
| 405 |
+
"x-browser-year": "2025",
|
| 406 |
+
"x-client-data": "CIa2yQEIpbbJAQipncoBCNvaygEIk6HLAQiFoM0BCJaMzwEIkZHPAQiSpM8BGOyFzwEYsobPAQ==",
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
# ็ฌฌไธๆญฅ๏ผ่ทๅ upload_id
|
| 410 |
+
init_headers = {
|
| 411 |
+
**browser_headers,
|
| 412 |
+
"content-type": "application/x-www-form-urlencoded;charset=utf-8",
|
| 413 |
+
"push-id": self.push_id,
|
| 414 |
+
"x-goog-upload-command": "start",
|
| 415 |
+
"x-goog-upload-header-content-length": str(len(image_data)),
|
| 416 |
+
"x-goog-upload-protocol": "resumable",
|
| 417 |
+
"x-tenant-id": "bard-storage",
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
init_resp = self.session.post(upload_url, data={"File name": filename}, headers=init_headers)
|
| 421 |
+
|
| 422 |
+
if self.debug:
|
| 423 |
+
print(f"[DEBUG] ๅๅงๅไธไผ ็ถๆ: {init_resp.status_code}")
|
| 424 |
+
|
| 425 |
+
# ๆฃๆฅๅๅงๅๅๅบ็ถๆ
|
| 426 |
+
if init_resp.status_code == 401 or init_resp.status_code == 403:
|
| 427 |
+
raise CookieExpiredError(
|
| 428 |
+
f"Cookie ๅทฒ่ฟๆๆๆ ๆ (HTTP {init_resp.status_code})\n"
|
| 429 |
+
"่ฏท้ๆฐ่ทๅไปฅไธไฟกๆฏ:\n"
|
| 430 |
+
"1. __Secure-1PSID\n"
|
| 431 |
+
"2. __Secure-1PSIDTS\n"
|
| 432 |
+
"3. SNlM0e\n"
|
| 433 |
+
"4. push_id"
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
upload_id = init_resp.headers.get("x-guploader-uploadid")
|
| 437 |
+
if not upload_id:
|
| 438 |
+
raise CookieExpiredError(
|
| 439 |
+
f"ๆช่ทๅๅฐ upload_id (็ถๆ็ : {init_resp.status_code})\n"
|
| 440 |
+
"ๅฏ่ฝๅๅ : Cookie ๅทฒ่ฟๆ๏ผ่ฏท้ๆฐ่ทๅๆๆ token"
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
if self.debug:
|
| 444 |
+
print(f"[DEBUG] Upload ID: {upload_id[:50]}...")
|
| 445 |
+
|
| 446 |
+
# ็ฌฌไบๆญฅ๏ผไธไผ ๅพ็ๆฐๆฎ
|
| 447 |
+
final_upload_url = f"{upload_url}?upload_id={upload_id}&upload_protocol=resumable"
|
| 448 |
+
|
| 449 |
+
upload_headers = {
|
| 450 |
+
**browser_headers,
|
| 451 |
+
"content-type": "application/x-www-form-urlencoded;charset=utf-8",
|
| 452 |
+
"push-id": self.push_id,
|
| 453 |
+
"x-goog-upload-command": "upload, finalize",
|
| 454 |
+
"x-goog-upload-offset": "0",
|
| 455 |
+
"x-tenant-id": "bard-storage",
|
| 456 |
+
"x-client-pctx": "CgcSBWjK7pYx",
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
upload_resp = self.session.post(
|
| 460 |
+
final_upload_url,
|
| 461 |
+
headers=upload_headers,
|
| 462 |
+
content=image_data
|
| 463 |
+
)
|
| 464 |
+
|
| 465 |
+
if self.debug:
|
| 466 |
+
print(f"[DEBUG] ไธไผ ๆฐๆฎ็ถๆ: {upload_resp.status_code}")
|
| 467 |
+
print(f"[DEBUG] ๅๅบๅคด: {dict(upload_resp.headers)}")
|
| 468 |
+
print(f"[DEBUG] ๅๅบๅ
ๅฎนๅฎๆด: {upload_resp.text}")
|
| 469 |
+
|
| 470 |
+
# ๆฃๆฅไธไผ ๅๅบ็ถๆ
|
| 471 |
+
if upload_resp.status_code == 401 or upload_resp.status_code == 403:
|
| 472 |
+
raise CookieExpiredError(
|
| 473 |
+
f"ไธไผ ๅพ็่ฎค่ฏๅคฑ่ดฅ (HTTP {upload_resp.status_code})\n"
|
| 474 |
+
"Cookie ๅทฒ่ฟๆ๏ผ่ฏท้ๆฐ่ทๅ"
|
| 475 |
+
)
|
| 476 |
+
|
| 477 |
+
if upload_resp.status_code != 200:
|
| 478 |
+
raise Exception(f"ไธไผ ๅพ็ๆฐๆฎๅคฑ่ดฅ: {upload_resp.status_code}, ๅๅบ: {upload_resp.text[:200] if upload_resp.text else '(empty)'}")
|
| 479 |
+
|
| 480 |
+
# ไปๅๅบไธญๆๅๅพ็่ทฏๅพ
|
| 481 |
+
response_text = upload_resp.text
|
| 482 |
+
image_path = None
|
| 483 |
+
|
| 484 |
+
# ๅฐ่ฏ่งฃๆ JSON
|
| 485 |
+
try:
|
| 486 |
+
response_json = json.loads(response_text)
|
| 487 |
+
image_path = self._extract_image_path(response_json)
|
| 488 |
+
except json.JSONDecodeError:
|
| 489 |
+
# ๅฆๆไธๆฏ JSON๏ผๅฐ่ฏไปๆๆฌไธญๆๅ่ทฏๅพ
|
| 490 |
+
match = re.search(r'/contrib_service/[^\s"\']+', response_text)
|
| 491 |
+
if match:
|
| 492 |
+
image_path = match.group(0)
|
| 493 |
+
|
| 494 |
+
# ้ช่ฏๅพ็่ทฏๅพๅฎๆดๆง
|
| 495 |
+
if not image_path:
|
| 496 |
+
raise CookieExpiredError(
|
| 497 |
+
f"ๆ ๆณไปๅๅบไธญๆๅๅพ็่ทฏๅพ\n"
|
| 498 |
+
f"ๅๅบๅ
ๅฎน: {response_text[:300]}\n"
|
| 499 |
+
"ๅฏ่ฝๅๅ : Cookie ๅทฒ่ฟๆ๏ผ่ฏท้ๆฐ่ทๅๆๆ token"
|
| 500 |
+
)
|
| 501 |
+
|
| 502 |
+
# ๆฃๆฅ่ทฏๅพๆฏๅฆๆๆ๏ผ้ฟๅบฆ่ถณๅคๅณๅฏ๏ผๆฐ็ๅฏ่ฝไธๅธฆๆฅ่ฏขๅๆฐ๏ผ
|
| 503 |
+
if "/contrib_service/" in image_path:
|
| 504 |
+
# ่ทฏๅพ้ฟๅบฆ่ณๅฐ่ฆๆไธๅฎ้ฟๅบฆๆๆฏๆๆ็
|
| 505 |
+
if len(image_path) < 40:
|
| 506 |
+
raise CookieExpiredError(
|
| 507 |
+
f"ๅพ็่ทฏๅพไธๅฎๆด\n"
|
| 508 |
+
f"่ฟๅ่ทฏๅพ: {image_path}\n"
|
| 509 |
+
"ๅๅ : Cookie ๅทฒ่ฟๆๆๆ้ไธ่ถณ\n"
|
| 510 |
+
"่งฃๅณๆนๆณ:\n"
|
| 511 |
+
"1. ้ๆฐ็ปๅฝ https://gemini.google.com\n"
|
| 512 |
+
"2. ๆดๆฐ config.py ไธญ็ๆๆ token:\n"
|
| 513 |
+
" - SECURE_1PSID\n"
|
| 514 |
+
" - SECURE_1PSIDTS\n"
|
| 515 |
+
" - SNLM0E\n"
|
| 516 |
+
" - PUSH_ID"
|
| 517 |
+
)
|
| 518 |
+
|
| 519 |
+
if self.debug:
|
| 520 |
+
print(f"[DEBUG] ๅพ็่ทฏๅพ: {image_path}")
|
| 521 |
+
|
| 522 |
+
return image_path
|
| 523 |
+
|
| 524 |
+
except CookieExpiredError:
|
| 525 |
+
raise
|
| 526 |
+
except Exception as e:
|
| 527 |
+
if self.debug:
|
| 528 |
+
print(f"[DEBUG] ไธไผ ๅคฑ่ดฅ: {e}")
|
| 529 |
+
raise Exception(f"ๅพ็ไธไผ ๅคฑ่ดฅ: {e}")
|
| 530 |
+
|
| 531 |
+
def _extract_image_path(self, data: Any) -> str:
|
| 532 |
+
"""ไปๅๅบๆฐๆฎไธญ้ๅฝๆๅๅพ็่ทฏๅพ"""
|
| 533 |
+
if isinstance(data, str):
|
| 534 |
+
if data.startswith("/contrib_service/"):
|
| 535 |
+
return data
|
| 536 |
+
elif isinstance(data, dict):
|
| 537 |
+
for value in data.values():
|
| 538 |
+
result = self._extract_image_path(value)
|
| 539 |
+
if result:
|
| 540 |
+
return result
|
| 541 |
+
elif isinstance(data, list):
|
| 542 |
+
for item in data:
|
| 543 |
+
result = self._extract_image_path(item)
|
| 544 |
+
if result:
|
| 545 |
+
return result
|
| 546 |
+
return None
|
| 547 |
+
|
| 548 |
+
def _build_request_data(self, text: str, images: List[Dict] = None, image_paths: List[str] = None, model: str = None) -> str:
|
| 549 |
+
"""ๆๅปบ่ฏทๆฑๆฐๆฎ - ๅบไบ็ๅฎ่ฏทๆฑๆ ผๅผ"""
|
| 550 |
+
# ไผ่ฏไธไธๆ (็ฉบๅญ็ฌฆไธฒ่กจ็คบๆฐๅฏน่ฏ)
|
| 551 |
+
conv_id = self.conversation_id or ""
|
| 552 |
+
resp_id = self.response_id or ""
|
| 553 |
+
choice_id = self.choice_id or ""
|
| 554 |
+
|
| 555 |
+
# ๅค็ๅพ็ๆฐๆฎ - ๆ ผๅผ: [[[path, 1, null, mime_type], filename], ...]
|
| 556 |
+
image_data = None
|
| 557 |
+
if image_paths and len(image_paths) > 0:
|
| 558 |
+
image_data = []
|
| 559 |
+
for i, path in enumerate(image_paths):
|
| 560 |
+
mime_type = images[i]["mime_type"] if images and i < len(images) else "image/png"
|
| 561 |
+
filename = f"image_{random.randint(100000, 999999)}.png"
|
| 562 |
+
image_data.append([[path, 1, None, mime_type], filename])
|
| 563 |
+
|
| 564 |
+
# ็ๆๅฏไธไผ่ฏ ID
|
| 565 |
+
session_id = str(uuid.uuid4()).upper()
|
| 566 |
+
timestamp = int(time.time() * 1000)
|
| 567 |
+
|
| 568 |
+
# ๆจกๅๆ ๅฐ: ๅฐๆจกๅๅ็งฐ่ฝฌๆขไธบ Gemini ๅ
้จๆจกๅๆ ่ฏ
|
| 569 |
+
# [[0]] = gemini-3.0-pro (Pro ็)
|
| 570 |
+
# [[1]] = gemini-3.0-flash (ๅฟซ้็๏ผ้ป่ฎค)
|
| 571 |
+
# [[3]] = gemini-3.0-flash-thinking (ๆ่็)
|
| 572 |
+
model_code = [[1]] # ้ป่ฎคๅฟซ้็
|
| 573 |
+
if model:
|
| 574 |
+
model_lower = model.lower()
|
| 575 |
+
if "pro" in model_lower:
|
| 576 |
+
model_code = [[0]] # Pro ็
|
| 577 |
+
elif "thinking" in model_lower or "think" in model_lower:
|
| 578 |
+
model_code = [[3]] # ๆ่็
|
| 579 |
+
# flash ๆๅ
ถไปๆ
ๅตไฟๆ้ป่ฎค [[1]]
|
| 580 |
+
|
| 581 |
+
# ๆๅปบๅ
้จ JSON ๆฐ็ป (ๅบไบ็ๅฎ่ฏทๆฑๆ ผๅผ)
|
| 582 |
+
# ็ฌฌไธไธชๅ
็ด : [text, 0, null, image_data, null, null, 0]
|
| 583 |
+
inner_data = [
|
| 584 |
+
[text, 0, None, image_data, None, None, 0],
|
| 585 |
+
["zh-CN"],
|
| 586 |
+
[conv_id, resp_id, choice_id, None, None, None, None, None, None, ""],
|
| 587 |
+
self.snlm0e,
|
| 588 |
+
None, # ไนๅๆฏ "test123"๏ผๆนไธบ null
|
| 589 |
+
None,
|
| 590 |
+
[1],
|
| 591 |
+
1,
|
| 592 |
+
None,
|
| 593 |
+
None,
|
| 594 |
+
1,
|
| 595 |
+
0,
|
| 596 |
+
None,
|
| 597 |
+
None,
|
| 598 |
+
None,
|
| 599 |
+
None,
|
| 600 |
+
None,
|
| 601 |
+
model_code, # ๆจกๅ้ๆฉๅญๆฎต
|
| 602 |
+
0,
|
| 603 |
+
None,
|
| 604 |
+
None,
|
| 605 |
+
None,
|
| 606 |
+
None,
|
| 607 |
+
None,
|
| 608 |
+
None,
|
| 609 |
+
None,
|
| 610 |
+
None,
|
| 611 |
+
1,
|
| 612 |
+
None,
|
| 613 |
+
None,
|
| 614 |
+
[4],
|
| 615 |
+
None,
|
| 616 |
+
None,
|
| 617 |
+
None,
|
| 618 |
+
None,
|
| 619 |
+
None,
|
| 620 |
+
None,
|
| 621 |
+
None,
|
| 622 |
+
None,
|
| 623 |
+
None,
|
| 624 |
+
None,
|
| 625 |
+
[1],
|
| 626 |
+
None,
|
| 627 |
+
None,
|
| 628 |
+
None,
|
| 629 |
+
None,
|
| 630 |
+
None,
|
| 631 |
+
None,
|
| 632 |
+
None,
|
| 633 |
+
None,
|
| 634 |
+
None,
|
| 635 |
+
None,
|
| 636 |
+
None,
|
| 637 |
+
0,
|
| 638 |
+
None,
|
| 639 |
+
None,
|
| 640 |
+
None,
|
| 641 |
+
None,
|
| 642 |
+
None,
|
| 643 |
+
session_id,
|
| 644 |
+
None,
|
| 645 |
+
[],
|
| 646 |
+
None,
|
| 647 |
+
None,
|
| 648 |
+
None,
|
| 649 |
+
None,
|
| 650 |
+
[timestamp // 1000, (timestamp % 1000) * 1000000]
|
| 651 |
+
]
|
| 652 |
+
|
| 653 |
+
# ๅบๅๅไธบ JSON ๅญ็ฌฆไธฒ
|
| 654 |
+
inner_json = json.dumps(inner_data, ensure_ascii=False, separators=(',', ':'))
|
| 655 |
+
|
| 656 |
+
# ๅคๅฑๅ
่ฃ
|
| 657 |
+
outer_data = [None, inner_json]
|
| 658 |
+
f_req_value = json.dumps(outer_data, ensure_ascii=False, separators=(',', ':'))
|
| 659 |
+
|
| 660 |
+
return f_req_value
|
| 661 |
+
|
| 662 |
+
|
| 663 |
+
def _parse_response(self, response_text: str) -> str:
|
| 664 |
+
"""่งฃๆๅๅบๆๆฌ - ไฟฎๅค็"""
|
| 665 |
+
try:
|
| 666 |
+
# ่ทณ่ฟๅ็ผๅนถๆ่ก่งฃๆ
|
| 667 |
+
lines = response_text.split("\n")
|
| 668 |
+
final_text = ""
|
| 669 |
+
generated_images_set = set() # ไฝฟ็จ set ๅ
จๅฑๅป้
|
| 670 |
+
last_inner_json = None # ไฟๅญๆๅไธไธชๆๆ็ inner_json ็จไบ่ฐ่ฏ
|
| 671 |
+
|
| 672 |
+
for line in lines:
|
| 673 |
+
line = line.strip()
|
| 674 |
+
if not line or line.startswith(")]}'"):
|
| 675 |
+
continue
|
| 676 |
+
|
| 677 |
+
# ่ทณ่ฟๆฐๅญ่ก๏ผ้ฟๅบฆๆ ่ฎฐ๏ผ
|
| 678 |
+
if line.isdigit():
|
| 679 |
+
continue
|
| 680 |
+
|
| 681 |
+
try:
|
| 682 |
+
data = json.loads(line)
|
| 683 |
+
# data ๆฏไธไธชๅตๅฅๆฐ็ป๏ผdata[0] ๆๆฏ็ๆญฃ็ๆฐๆฎ
|
| 684 |
+
if isinstance(data, list) and len(data) > 0 and isinstance(data[0], list):
|
| 685 |
+
actual_data = data[0]
|
| 686 |
+
# ๆฃๆฅๆฏๅฆๆฏ wrb.fr ๅๅบ
|
| 687 |
+
if len(actual_data) >= 3 and actual_data[0] == "wrb.fr" and actual_data[2]:
|
| 688 |
+
inner_json = json.loads(actual_data[2])
|
| 689 |
+
last_inner_json = inner_json
|
| 690 |
+
|
| 691 |
+
# ๅฐ่ฏๆๅ็ๆ็ๅพ็ URL๏ผๅๅนถๅฐๅ
จๅฑ set ไธญๅป้
|
| 692 |
+
imgs = self._extract_generated_images(inner_json)
|
| 693 |
+
if imgs:
|
| 694 |
+
for img in imgs:
|
| 695 |
+
generated_images_set.add(img)
|
| 696 |
+
if self.debug:
|
| 697 |
+
print(f"[DEBUG] ไปๅๅบไธญๆๅๅฐ {len(imgs)} ไธชๅพ็ URL๏ผๅฝๅๆปๆฐ: {len(generated_images_set)}")
|
| 698 |
+
|
| 699 |
+
# ๆๅๆๆฌๅ
ๅฎน
|
| 700 |
+
if inner_json and len(inner_json) > 4 and inner_json[4]:
|
| 701 |
+
candidates = inner_json[4]
|
| 702 |
+
if candidates and len(candidates) > 0:
|
| 703 |
+
candidate = candidates[0]
|
| 704 |
+
if candidate and len(candidate) > 1 and candidate[1]:
|
| 705 |
+
# candidate[1] ๆฏไธไธชๆฐ็ป๏ผ็ฌฌไธไธชๅ
็ด ๆฏๆๆฌ
|
| 706 |
+
text = candidate[1][0] if isinstance(candidate[1], list) else candidate[1]
|
| 707 |
+
if isinstance(text, str) and len(text) > len(final_text):
|
| 708 |
+
final_text = text
|
| 709 |
+
# ๆดๆฐไผ่ฏไธไธๆ
|
| 710 |
+
if len(inner_json) > 1 and inner_json[1]:
|
| 711 |
+
if isinstance(inner_json[1], list):
|
| 712 |
+
if len(inner_json[1]) > 0:
|
| 713 |
+
self.conversation_id = inner_json[1][0] or self.conversation_id
|
| 714 |
+
if len(inner_json[1]) > 1:
|
| 715 |
+
self.response_id = inner_json[1][1] or self.response_id
|
| 716 |
+
if len(candidate) > 0:
|
| 717 |
+
self.choice_id = candidate[0] or self.choice_id
|
| 718 |
+
except Exception as e:
|
| 719 |
+
if self.debug:
|
| 720 |
+
print(f"[DEBUG] ่งฃๆ่กๆถๅบ้: {e}")
|
| 721 |
+
continue
|
| 722 |
+
|
| 723 |
+
# ่ฝฌๆขไธบๅ่กจ
|
| 724 |
+
generated_images = list(generated_images_set)
|
| 725 |
+
|
| 726 |
+
if self.debug:
|
| 727 |
+
print(f"[DEBUG] ่งฃๆๅฎๆ: final_text้ฟๅบฆ={len(final_text)}, ๅพ็ๆฐ้={len(generated_images)}")
|
| 728 |
+
|
| 729 |
+
# ๅค็็ๆ็ๅพ็/่ง้ข - ไธ่ฝฝๅนถ็ผๅญๅฐๆฌๅฐ
|
| 730 |
+
if generated_images:
|
| 731 |
+
if self.debug:
|
| 732 |
+
print(f"[DEBUG] ๆๅๅฐ {len(generated_images)} ไธชๅชไฝ URL๏ผๅผๅงไธ่ฝฝ...")
|
| 733 |
+
|
| 734 |
+
# ไธ่ฝฝๅพ็ๅนถ่ทๅๆฌๅฐไปฃ็ URL
|
| 735 |
+
local_media_urls = []
|
| 736 |
+
for i, url in enumerate(generated_images):
|
| 737 |
+
if self.debug:
|
| 738 |
+
print(f"[DEBUG] ไธ่ฝฝๅชไฝ {i+1}/{len(generated_images)}: {url[:80]}...")
|
| 739 |
+
local_url = self._download_media_as_data_url(url)
|
| 740 |
+
if local_url:
|
| 741 |
+
local_media_urls.append(local_url)
|
| 742 |
+
if self.debug:
|
| 743 |
+
print(f"[DEBUG] ๅชไฝ {i+1} ไธ่ฝฝๆๅ: {local_url}")
|
| 744 |
+
else:
|
| 745 |
+
# ไธ่ฝฝๅคฑ่ดฅ๏ผไฝฟ็จๅๅง URL
|
| 746 |
+
local_media_urls.append(url)
|
| 747 |
+
if self.debug:
|
| 748 |
+
print(f"[DEBUG] ๅชไฝ {i+1} ไธ่ฝฝๅคฑ่ดฅ๏ผไฝฟ็จๅๅง URL")
|
| 749 |
+
|
| 750 |
+
# ๆฃๆตๅ ไฝ็ฌฆ๏ผๅฆๆๆๆๆฌ็่ฏ๏ผ
|
| 751 |
+
has_placeholder = False
|
| 752 |
+
if final_text:
|
| 753 |
+
has_placeholder = ('image_generation_content' in final_text or
|
| 754 |
+
'video_gen_chip' in final_text)
|
| 755 |
+
|
| 756 |
+
# ๆๅปบๅ
ๅซๆฌๅฐไปฃ็ URL ็ๅๅบ
|
| 757 |
+
media_parts = []
|
| 758 |
+
for i, url in enumerate(local_media_urls):
|
| 759 |
+
media_parts.append(f"")
|
| 760 |
+
|
| 761 |
+
media_text = "\n\n".join(media_parts)
|
| 762 |
+
|
| 763 |
+
if has_placeholder:
|
| 764 |
+
# ็งป้คๅ ไฝ็ฌฆ URL
|
| 765 |
+
cleaned_text = re.sub(r'https?://googleusercontent\.com/(?:image_generation_content|video_gen_chip)/\d+', '', final_text)
|
| 766 |
+
cleaned_text = re.sub(r'http://googleusercontent\.com/(?:image_generation_content|video_gen_chip)/\d+', '', cleaned_text)
|
| 767 |
+
cleaned_text = re.sub(r'!\[.*?\]\(\)', '', cleaned_text) # ็งป้ค็ฉบ็ๅพ็ๆ ่ฎฐ
|
| 768 |
+
cleaned_text = cleaned_text.strip()
|
| 769 |
+
if cleaned_text:
|
| 770 |
+
final_text = cleaned_text + "\n\n" + media_text
|
| 771 |
+
else:
|
| 772 |
+
final_text = media_text
|
| 773 |
+
elif final_text:
|
| 774 |
+
# ๆๆๆฌไฝๆฒกๆๅ ไฝ็ฌฆ๏ผ่ฟฝๅ ๅพ็
|
| 775 |
+
final_text = final_text + "\n\n" + media_text
|
| 776 |
+
else:
|
| 777 |
+
# ๆฒกๆๆๆฌ๏ผๅชๆๅพ็
|
| 778 |
+
final_text = media_text
|
| 779 |
+
|
| 780 |
+
if self.debug:
|
| 781 |
+
print(f"[DEBUG] ๅชไฝๅค็ๅฎๆ๏ผๆๅไธ่ฝฝ {len([u for u in local_media_urls if u.startswith('/media/')])} ไธช")
|
| 782 |
+
|
| 783 |
+
# ๆฃๆต่ง้ข็ๆๅ ไฝ็ฌฆ๏ผๆฟๆขไธบๆ็คบๆๆก
|
| 784 |
+
is_video_generation = False
|
| 785 |
+
if final_text and 'video_gen_chip' in final_text:
|
| 786 |
+
is_video_generation = True
|
| 787 |
+
|
| 788 |
+
# ๆธ
็ๆๆฌไธญ็ๅ ไฝ็ฌฆ URL ๅ็จๆทไธไผ ๅพ็็ URL
|
| 789 |
+
if final_text:
|
| 790 |
+
# ๆธ
็ๅ ไฝ็ฌฆ URL
|
| 791 |
+
final_text = re.sub(r'https?://googleusercontent\.com/(?:image_generation_content|video_gen_chip)/\d+\s*', '', final_text)
|
| 792 |
+
final_text = re.sub(r'http://googleusercontent\.com/(?:image_generation_content|video_gen_chip)/\d+\s*', '', final_text)
|
| 793 |
+
# ๆธ
็็จๆทไธไผ ๅพ็็ URL๏ผ/gg/ ่ทฏๅพ๏ผ้ /gg-dl/๏ผ
|
| 794 |
+
final_text = re.sub(r'!\[[^\]]*\]\(https://[^)]*googleusercontent\.com/gg/[^)]+\)', '', final_text)
|
| 795 |
+
final_text = re.sub(r'https://lh3\.googleusercontent\.com/gg/[^\s\)]+', '', final_text)
|
| 796 |
+
final_text = final_text.strip()
|
| 797 |
+
|
| 798 |
+
# ๅฆๆๆฏ่ง้ข็ๆ๏ผๆทปๅ ๆ็คบๆๆก
|
| 799 |
+
if is_video_generation:
|
| 800 |
+
video_notice = "\n\n---\n๐น ่ง้ขไธบๅผๆญฅ็ๆ๏ผ็ๆ็ปๆๅฏๅจๅฎ็ฝ่ๅคฉ็ชๅฃๆฅ็ไธ่ฝฝใ\n\nโฑ๏ธ ไฝฟ็จ้ๅถ๏ผ\n- ่ง้ข็ๆ (Veo ๆจกๅ)๏ผๆฏๅคฉๆปๅ
ฑๅฏไปฅ็ๆ 3 ๆฌก\n- ๅพ็็ๆ (Nano Banana ๆจกๅ)๏ผๆฏๅคฉๆปๅ
ฑๅฏไปฅ็ๆ 1000 ๆฌก"
|
| 801 |
+
if final_text:
|
| 802 |
+
final_text = final_text + video_notice
|
| 803 |
+
else:
|
| 804 |
+
final_text = video_notice.strip()
|
| 805 |
+
|
| 806 |
+
if final_text:
|
| 807 |
+
# ไผๅๅพ็ URL ไธบๅๅง้ซๆธ
ๅฐบๅฏธ๏ผไป
ๅฏนๆชไธ่ฝฝ็ๅๅง URL๏ผ
|
| 808 |
+
final_text = self._optimize_image_urls(final_text)
|
| 809 |
+
return final_text
|
| 810 |
+
|
| 811 |
+
# ๅฆๆๆฒกๆๆๆฌไนๆฒกๆๅพ็๏ผๅฐ่ฏไป last_inner_json ไธญๆๅๆดๅคไฟกๆฏ
|
| 812 |
+
if self.debug and last_inner_json:
|
| 813 |
+
print(f"[DEBUG] ๆ ๆณๆๅๅ
ๅฎน๏ผinner_json ็ปๆ: {str(last_inner_json)[:500]}...")
|
| 814 |
+
|
| 815 |
+
except Exception as e:
|
| 816 |
+
if self.debug:
|
| 817 |
+
print(f"[DEBUG] ่งฃๆ้่ฏฏ: {e}")
|
| 818 |
+
|
| 819 |
+
return "ๆ ๆณ่งฃๆๅๅบ"
|
| 820 |
+
|
| 821 |
+
def _extract_generated_media(self, data: Any, depth: int = 0) -> List[str]:
|
| 822 |
+
"""ไปๅๅบๆฐๆฎไธญ้ๅฝๆๅ็ๆ็ๅพ็/่ง้ข URL
|
| 823 |
+
|
| 824 |
+
Gemini ไผ่ฟๅไธคไธชๅชไฝ๏ผๅธฆๆฐดๅฐๅไธๅธฆๆฐดๅฐ๏ผ๏ผๆไปฌๅชไฟ็ๆๅไธไธช๏ผไธๅธฆๆฐดๅฐ๏ผ
|
| 825 |
+
ๅชๆๅ AI ็ๆ็ๅชไฝ (/gg-dl/ ่ทฏๅพ)๏ผไธๆๅ็จๆทไธไผ ็ๅพ็ (/gg/ ่ทฏๅพ)
|
| 826 |
+
"""
|
| 827 |
+
if depth > 30: # ้ฒๆญขๆ ้้ๅฝ
|
| 828 |
+
return []
|
| 829 |
+
|
| 830 |
+
media_urls = []
|
| 831 |
+
|
| 832 |
+
if isinstance(data, list):
|
| 833 |
+
# ๆฃๆฅๆฏๅฆๆฏๅชไฝๅฏน็ปๆ: [[null, 1, "file1.png/mp4", "url1", ...], null, null, [null, 1, "file2.png/mp4", "url2", ...]]
|
| 834 |
+
# ็ฌฌไธไธชๆฏๅธฆๆฐดๅฐ็๏ผ็ฌฌไบไธชๆฏไธๅธฆๆฐดๅฐ็
|
| 835 |
+
if (len(data) >= 1 and
|
| 836 |
+
isinstance(data[0], list) and len(data[0]) >= 4 and
|
| 837 |
+
data[0][0] is None and
|
| 838 |
+
isinstance(data[0][1], int) and
|
| 839 |
+
isinstance(data[0][2], str) and
|
| 840 |
+
isinstance(data[0][3], str) and
|
| 841 |
+
data[0][3].startswith('https://') and
|
| 842 |
+
'gg-dl/' in data[0][3]): # ๅชๅน้
AI ็ๆ็ๅชไฝ
|
| 843 |
+
# ๅฐ่ฏๆพ็ฌฌไบไธชๅชไฝ๏ผไธๅธฆๆฐดๅฐ๏ผ
|
| 844 |
+
second_url = None
|
| 845 |
+
if len(data) >= 4 and isinstance(data[3], list) and len(data[3]) >= 4:
|
| 846 |
+
if (data[3][0] is None and
|
| 847 |
+
isinstance(data[3][3], str) and
|
| 848 |
+
'gg-dl/' in data[3][3]):
|
| 849 |
+
second_url = data[3][3]
|
| 850 |
+
|
| 851 |
+
# ไผๅ
ไฝฟ็จ็ฌฌไบไธช๏ผๅฆๅ็จ็ฌฌไธไธช
|
| 852 |
+
url = second_url if second_url else data[0][3]
|
| 853 |
+
if 'image_generation_content' not in url and 'video_gen_chip' not in url:
|
| 854 |
+
media_urls.append(url)
|
| 855 |
+
return media_urls
|
| 856 |
+
|
| 857 |
+
# ๆฃๆฅๆฏๅฆๆฏๅไธชๅชไฝๆฐๆฎ็ปๆ: [null, 1, "filename.png/mp4", "https://...gg-dl/..."]
|
| 858 |
+
if (len(data) >= 4 and
|
| 859 |
+
data[0] is None and
|
| 860 |
+
isinstance(data[1], int) and
|
| 861 |
+
isinstance(data[2], str) and
|
| 862 |
+
isinstance(data[3], str) and
|
| 863 |
+
data[3].startswith('https://') and
|
| 864 |
+
'gg-dl/' in data[3]): # ๅชๅน้
AI ็ๆ็ๅชไฝ
|
| 865 |
+
url = data[3]
|
| 866 |
+
if 'image_generation_content' not in url and 'video_gen_chip' not in url:
|
| 867 |
+
media_urls.append(url)
|
| 868 |
+
return media_urls
|
| 869 |
+
|
| 870 |
+
# ้ๅฝๆ็ดข๏ผๆถ้ๆๆๅชไฝ URL
|
| 871 |
+
all_found = []
|
| 872 |
+
for item in data:
|
| 873 |
+
found = self._extract_generated_media(item, depth + 1)
|
| 874 |
+
if found:
|
| 875 |
+
all_found.extend(found)
|
| 876 |
+
|
| 877 |
+
# ๅฆๆๆพๅฐๅคไธช๏ผ่ฟๅๆๅไธไธช๏ผ้ๅธธๆฏไธๅธฆๆฐดๅฐ็๏ผ
|
| 878 |
+
if all_found:
|
| 879 |
+
seen = set()
|
| 880 |
+
unique = []
|
| 881 |
+
for u in all_found:
|
| 882 |
+
if u not in seen:
|
| 883 |
+
seen.add(u)
|
| 884 |
+
unique.append(u)
|
| 885 |
+
# ่ฟๅๆๅไธไธช๏ผไธๅธฆๆฐดๅฐ๏ผ
|
| 886 |
+
return [unique[-1]] if unique else []
|
| 887 |
+
|
| 888 |
+
elif isinstance(data, dict):
|
| 889 |
+
for value in data.values():
|
| 890 |
+
found = self._extract_generated_media(value, depth + 1)
|
| 891 |
+
if found:
|
| 892 |
+
return found
|
| 893 |
+
|
| 894 |
+
return media_urls
|
| 895 |
+
|
| 896 |
+
# ไฟๆๅๅๅ
ผๅฎน
|
| 897 |
+
def _extract_generated_images(self, data: Any, depth: int = 0) -> List[str]:
|
| 898 |
+
"""ๅๅๅ
ผๅฎน็ๅซๅ"""
|
| 899 |
+
return self._extract_generated_media(data, depth)
|
| 900 |
+
|
| 901 |
+
def _download_media_as_data_url(self, url: str) -> str:
|
| 902 |
+
"""ไธ่ฝฝๅชไฝๆไปถๅนถไฟๅญๅฐๆฌๅฐ็ผๅญ๏ผ่ฟๅๆฌๅฐไปฃ็ URL
|
| 903 |
+
|
| 904 |
+
Args:
|
| 905 |
+
url: ๅชไฝๆไปถ็ URL
|
| 906 |
+
|
| 907 |
+
Returns:
|
| 908 |
+
str: ๆฌๅฐไปฃ็ URL ๆ base64 data URL
|
| 909 |
+
ไธ่ฝฝๅคฑ่ดฅๆถ่ฟๅ็ฉบๅญ็ฌฆไธฒ
|
| 910 |
+
"""
|
| 911 |
+
try:
|
| 912 |
+
# ๅ
ไผๅ URL ่ทๅ้ซๆธ
ๅๅพ๏ผไป
ๅฏนๅพ็๏ผ
|
| 913 |
+
if ("googleusercontent" in url or "ggpht" in url) and not any(ext in url.lower() for ext in ['.mp4', '.webm', 'video']):
|
| 914 |
+
# ็งป้ค็ฐๆๅฐบๅฏธๅๆฐ๏ผๆทปๅ ๅๅงๅฐบๅฏธๅๆฐ =s0
|
| 915 |
+
url = re.sub(r'=w\d+(-h\d+)?(-[a-zA-Z]+)*$', '=s0', url)
|
| 916 |
+
url = re.sub(r'=s\d+(-[a-zA-Z]+)*$', '=s0', url)
|
| 917 |
+
url = re.sub(r'=h\d+(-[a-zA-Z]+)*$', '=s0', url)
|
| 918 |
+
# ๅฆๆ URL ๆฒกๆๅฐบๅฏธๅๆฐ๏ผๆทปๅ =s0
|
| 919 |
+
if not url.endswith('=s0') and '=' not in url.split('/')[-1]:
|
| 920 |
+
url += '=s0'
|
| 921 |
+
|
| 922 |
+
if self.debug:
|
| 923 |
+
print(f"[DEBUG] ๆญฃๅจไธ่ฝฝๅชไฝ (้ซๆธ
): {url[:100]}...")
|
| 924 |
+
|
| 925 |
+
# ไฝฟ็จๅฝๅไผ่ฏไธ่ฝฝ๏ผๅธฆ่ฎค่ฏ cookies๏ผ
|
| 926 |
+
headers = {
|
| 927 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
| 928 |
+
"Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
|
| 929 |
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
| 930 |
+
"Referer": "https://gemini.google.com/",
|
| 931 |
+
}
|
| 932 |
+
resp = self.session.get(url, timeout=60.0, headers=headers)
|
| 933 |
+
|
| 934 |
+
if self.debug:
|
| 935 |
+
print(f"[DEBUG] ไธ่ฝฝ็ถๆ: {resp.status_code}, ๅคงๅฐ: {len(resp.content)} bytes")
|
| 936 |
+
|
| 937 |
+
if resp.status_code != 200:
|
| 938 |
+
if self.debug:
|
| 939 |
+
print(f"[DEBUG] ไธ่ฝฝๅชไฝๅคฑ่ดฅ: HTTP {resp.status_code}")
|
| 940 |
+
return ""
|
| 941 |
+
|
| 942 |
+
# ๆฃๆฅๅ
ๅฎนๆฏๅฆไธบ็ฉบๆๅคชๅฐ๏ผๅฏ่ฝๆฏ้่ฏฏ้กต้ข๏ผ
|
| 943 |
+
if len(resp.content) < 100:
|
| 944 |
+
if self.debug:
|
| 945 |
+
print(f"[DEBUG] ไธ่ฝฝๅ
ๅฎนๅคชๅฐ๏ผๅฏ่ฝๆฏ้่ฏฏ: {resp.content[:100]}")
|
| 946 |
+
return ""
|
| 947 |
+
|
| 948 |
+
# ๆ นๆฎๅ
ๅฎนๆฃๆตๆไปถ็ฑปๅ
|
| 949 |
+
content = resp.content
|
| 950 |
+
if content[:8] == b'\x89PNG\r\n\x1a\n':
|
| 951 |
+
ext = ".png"
|
| 952 |
+
mime = "image/png"
|
| 953 |
+
elif content[:3] == b'\xff\xd8\xff':
|
| 954 |
+
ext = ".jpg"
|
| 955 |
+
mime = "image/jpeg"
|
| 956 |
+
elif content[:6] in (b'GIF87a', b'GIF89a'):
|
| 957 |
+
ext = ".gif"
|
| 958 |
+
mime = "image/gif"
|
| 959 |
+
elif content[:4] == b'RIFF' and content[8:12] == b'WEBP':
|
| 960 |
+
ext = ".webp"
|
| 961 |
+
mime = "image/webp"
|
| 962 |
+
elif content[4:8] == b'ftyp' or content[:4] == b'\x00\x00\x00\x1c':
|
| 963 |
+
ext = ".mp4"
|
| 964 |
+
mime = "video/mp4"
|
| 965 |
+
else:
|
| 966 |
+
ext = ".png"
|
| 967 |
+
mime = "image/png"
|
| 968 |
+
|
| 969 |
+
# ็ๆๅฏไธๆไปถๅ
|
| 970 |
+
import os
|
| 971 |
+
media_id = f"gen_{uuid.uuid4().hex[:16]}"
|
| 972 |
+
|
| 973 |
+
# ไฟๅญๅฐ็ผๅญ็ฎๅฝ
|
| 974 |
+
cache_dir = os.path.join(os.path.dirname(__file__), "media_cache")
|
| 975 |
+
os.makedirs(cache_dir, exist_ok=True)
|
| 976 |
+
file_path = os.path.join(cache_dir, media_id + ext)
|
| 977 |
+
|
| 978 |
+
with open(file_path, "wb") as f:
|
| 979 |
+
f.write(content)
|
| 980 |
+
|
| 981 |
+
if self.debug:
|
| 982 |
+
print(f"[DEBUG] ๅชไฝๅทฒไฟๅญ: {file_path}")
|
| 983 |
+
|
| 984 |
+
# ่ฟๅๅฎๆด็ๅชไฝ่ฎฟ้ฎ URL (ๅ
ๅซๅ็ผๅ)
|
| 985 |
+
media_path = f"/media/{media_id}{ext}"
|
| 986 |
+
if self.media_base_url:
|
| 987 |
+
return f"{self.media_base_url}{media_path}"
|
| 988 |
+
return media_path
|
| 989 |
+
|
| 990 |
+
except Exception as e:
|
| 991 |
+
if self.debug:
|
| 992 |
+
print(f"[DEBUG] ไธ่ฝฝๅชไฝๅผๅธธ: {e}")
|
| 993 |
+
return ""
|
| 994 |
+
|
| 995 |
+
def _optimize_image_urls(self, text: str) -> str:
|
| 996 |
+
"""ไผๅๆๆฌไธญ็ Google ๅพ็ URL ไธบๅๅง้ซๆธ
ๅฐบๅฏธ
|
| 997 |
+
|
| 998 |
+
Google ๅพ็ URL ๅๆฐ่ฏดๆ:
|
| 999 |
+
- =w400 ๆ =h400: ๆๅฎๅฎฝๅบฆๆ้ซๅบฆ
|
| 1000 |
+
- =s400: ๆๅฎๆๅคง่พน้ฟ
|
| 1001 |
+
- =s0 ๆ =w0-h0: ๅๅงๅฐบๅฏธ
|
| 1002 |
+
"""
|
| 1003 |
+
import re
|
| 1004 |
+
|
| 1005 |
+
def optimize_url(url: str) -> str:
|
| 1006 |
+
# ๅน้
googleusercontent ๆ ggpht ๅพ็ URL
|
| 1007 |
+
if "googleusercontent" not in url and "ggpht" not in url:
|
| 1008 |
+
return url
|
| 1009 |
+
# ็งป้ค็ฐๆๅฐบๅฏธๅๆฐ๏ผๆทปๅ ๅๅงๅฐบๅฏธๅๆฐ
|
| 1010 |
+
url = re.sub(r'=w\d+(-h\d+)?(-[a-zA-Z]+)*$', '=s0', url)
|
| 1011 |
+
url = re.sub(r'=s\d+(-[a-zA-Z]+)*$', '=s0', url)
|
| 1012 |
+
url = re.sub(r'=h\d+(-[a-zA-Z]+)*$', '=s0', url)
|
| 1013 |
+
# ๅฆๆ URL ๆฒกๆๅฐบๅฏธๅๆฐ๏ผๆทปๅ =s0
|
| 1014 |
+
if not url.endswith('=s0') and '=' not in url.split('/')[-1]:
|
| 1015 |
+
url += '=s0'
|
| 1016 |
+
return url
|
| 1017 |
+
|
| 1018 |
+
# ๅน้
Markdown ๅพ็่ฏญๆณๅ็บฏ URL
|
| 1019 |
+
# Markdown: 
|
| 1020 |
+
def replace_md_img(match):
|
| 1021 |
+
alt = match.group(1)
|
| 1022 |
+
url = match.group(2)
|
| 1023 |
+
return f"})"
|
| 1024 |
+
|
| 1025 |
+
text = re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', replace_md_img, text)
|
| 1026 |
+
|
| 1027 |
+
# ๅน้
็ฌ็ซ็ Google ๅพ็ URL
|
| 1028 |
+
def replace_url(match):
|
| 1029 |
+
return optimize_url(match.group(0))
|
| 1030 |
+
|
| 1031 |
+
text = re.sub(r'https?://[^\s\)]+(?:googleusercontent|ggpht)[^\s\)]*', replace_url, text)
|
| 1032 |
+
|
| 1033 |
+
return text
|
| 1034 |
+
|
| 1035 |
+
|
| 1036 |
+
def _extract_text(self, parsed_data: list) -> str:
|
| 1037 |
+
"""ไป่งฃๆๅ็ๆฐๆฎไธญๆๅๆๆฌ"""
|
| 1038 |
+
try:
|
| 1039 |
+
# ๆดๆฐไผ่ฏไธไธๆ
|
| 1040 |
+
if parsed_data and len(parsed_data) > 1:
|
| 1041 |
+
if parsed_data[1] and len(parsed_data[1]) > 0:
|
| 1042 |
+
self.conversation_id = parsed_data[1][0] or self.conversation_id
|
| 1043 |
+
if parsed_data[1] and len(parsed_data[1]) > 1:
|
| 1044 |
+
self.response_id = parsed_data[1][1] or self.response_id
|
| 1045 |
+
|
| 1046 |
+
# ๆๅๅ้ๅๅค
|
| 1047 |
+
if parsed_data and len(parsed_data) > 4 and parsed_data[4]:
|
| 1048 |
+
candidates = parsed_data[4]
|
| 1049 |
+
if candidates and len(candidates) > 0:
|
| 1050 |
+
first_candidate = candidates[0]
|
| 1051 |
+
if first_candidate and len(first_candidate) > 1:
|
| 1052 |
+
self.choice_id = first_candidate[0] or self.choice_id
|
| 1053 |
+
content_parts = first_candidate[1]
|
| 1054 |
+
if content_parts and len(content_parts) > 0:
|
| 1055 |
+
return content_parts[0] if isinstance(content_parts[0], str) else str(content_parts[0])
|
| 1056 |
+
|
| 1057 |
+
# ๅค็จๆๅ
|
| 1058 |
+
if parsed_data and len(parsed_data) > 0:
|
| 1059 |
+
def find_text(obj, depth=0):
|
| 1060 |
+
if depth > 10:
|
| 1061 |
+
return None
|
| 1062 |
+
if isinstance(obj, str) and len(obj) > 50:
|
| 1063 |
+
return obj
|
| 1064 |
+
if isinstance(obj, list):
|
| 1065 |
+
for item in obj:
|
| 1066 |
+
result = find_text(item, depth + 1)
|
| 1067 |
+
if result:
|
| 1068 |
+
return result
|
| 1069 |
+
return None
|
| 1070 |
+
|
| 1071 |
+
text = find_text(parsed_data)
|
| 1072 |
+
if text:
|
| 1073 |
+
return text
|
| 1074 |
+
|
| 1075 |
+
except Exception as e:
|
| 1076 |
+
pass
|
| 1077 |
+
|
| 1078 |
+
return "ๆ ๆณๆๅๅๅคๅ
ๅฎน"
|
| 1079 |
+
|
| 1080 |
+
def chat(
|
| 1081 |
+
self,
|
| 1082 |
+
messages: List[Dict[str, Any]] = None,
|
| 1083 |
+
message: str = None,
|
| 1084 |
+
image: bytes = None,
|
| 1085 |
+
image_url: str = None,
|
| 1086 |
+
reset_context: bool = False,
|
| 1087 |
+
model: str = None,
|
| 1088 |
+
stream: bool = False,
|
| 1089 |
+
) -> Union[ChatCompletionResponse, Iterator[Dict[str, Any]]]:
|
| 1090 |
+
"""
|
| 1091 |
+
ๅ้่ๅคฉ่ฏทๆฑ (OpenAI ๅ
ผๅฎนๆ ผๅผ)
|
| 1092 |
+
|
| 1093 |
+
Args:
|
| 1094 |
+
messages: OpenAI ๆ ผๅผๆถๆฏๅ่กจ
|
| 1095 |
+
message: ็ฎๅๆๆฌๆถๆฏ (ไธ messages ไบ้ไธ)
|
| 1096 |
+
image: ๅพ็ไบ่ฟๅถๆฐๆฎ
|
| 1097 |
+
image_url: ๅพ็ URL
|
| 1098 |
+
reset_context: ๆฏๅฆ้็ฝฎไธไธๆ
|
| 1099 |
+
model: ๆจกๅๅ็งฐ (gemini-3.0-flash/gemini-3.0-flash-thinking/gemini-3.0-pro)
|
| 1100 |
+
|
| 1101 |
+
Returns:
|
| 1102 |
+
ChatCompletionResponse: OpenAI ๆ ผๅผๅๅบ
|
| 1103 |
+
"""
|
| 1104 |
+
if reset_context:
|
| 1105 |
+
self.reset()
|
| 1106 |
+
|
| 1107 |
+
# ๅค็่พๅ
ฅ
|
| 1108 |
+
text_parts = []
|
| 1109 |
+
images = []
|
| 1110 |
+
|
| 1111 |
+
if messages:
|
| 1112 |
+
# OpenAI ๆ ผๅผ - ๅๅนถๆๆๆถๆฏ
|
| 1113 |
+
for msg in messages:
|
| 1114 |
+
role = msg.get("role", "user")
|
| 1115 |
+
content = msg.get("content", "")
|
| 1116 |
+
|
| 1117 |
+
if role == "user":
|
| 1118 |
+
t, imgs = self._parse_content(content)
|
| 1119 |
+
if t:
|
| 1120 |
+
text_parts.append(t)
|
| 1121 |
+
if imgs:
|
| 1122 |
+
images.extend(imgs)
|
| 1123 |
+
elif role == "assistant":
|
| 1124 |
+
# ๅฉๆๆถๆฏไนๅ ๅ
ฅไธไธๆ
|
| 1125 |
+
if isinstance(content, str) and content:
|
| 1126 |
+
text_parts.append(f"[ๅฉๆๅๅค]: {content}")
|
| 1127 |
+
elif role == "system":
|
| 1128 |
+
# system ๆถๆฏไฝไธบๅ็ฝฎๆไปค
|
| 1129 |
+
if isinstance(content, str) and content:
|
| 1130 |
+
text_parts.insert(0, content)
|
| 1131 |
+
|
| 1132 |
+
self.messages.append(Message(role=role, content=content))
|
| 1133 |
+
|
| 1134 |
+
text = "\n\n".join(text_parts)
|
| 1135 |
+
elif message:
|
| 1136 |
+
text = message
|
| 1137 |
+
self.messages.append(Message(role="user", content=message))
|
| 1138 |
+
|
| 1139 |
+
if image:
|
| 1140 |
+
images = [{"mime_type": "image/jpeg", "data": base64.b64encode(image).decode()}]
|
| 1141 |
+
elif image_url:
|
| 1142 |
+
if image_url.startswith("data:"):
|
| 1143 |
+
match = re.match(r'data:([^;]+);base64,(.+)', image_url)
|
| 1144 |
+
if match:
|
| 1145 |
+
images = [{"mime_type": match.group(1), "data": match.group(2)}]
|
| 1146 |
+
else:
|
| 1147 |
+
try:
|
| 1148 |
+
resp = httpx.get(image_url, timeout=30)
|
| 1149 |
+
mime = resp.headers.get("content-type", "image/jpeg").split(";")[0]
|
| 1150 |
+
images = [{"mime_type": mime, "data": base64.b64encode(resp.content).decode()}]
|
| 1151 |
+
except:
|
| 1152 |
+
pass
|
| 1153 |
+
else:
|
| 1154 |
+
text = ""
|
| 1155 |
+
|
| 1156 |
+
if not text:
|
| 1157 |
+
raise ValueError("ๆถๆฏๅ
ๅฎนไธ่ฝไธบ็ฉบ")
|
| 1158 |
+
|
| 1159 |
+
# ๅ้่ฏทๆฑ
|
| 1160 |
+
if stream:
|
| 1161 |
+
return self._send_request_stream(text, images, model)
|
| 1162 |
+
return self._send_request(text, images, model)
|
| 1163 |
+
|
| 1164 |
+
|
| 1165 |
+
def _log_gemini_call(self, request_data: dict, response_text: str, error: str = None):
|
| 1166 |
+
"""่ฎฐๅฝ Gemini ๅ
้จ่ฐ็จๆฅๅฟ"""
|
| 1167 |
+
import datetime
|
| 1168 |
+
log_entry = {
|
| 1169 |
+
"timestamp": datetime.datetime.now().isoformat(),
|
| 1170 |
+
"type": "gemini_internal",
|
| 1171 |
+
"request": request_data,
|
| 1172 |
+
"response_raw": response_text,
|
| 1173 |
+
"error": error
|
| 1174 |
+
}
|
| 1175 |
+
try:
|
| 1176 |
+
with open("api_logs.json", "a", encoding="utf-8") as f:
|
| 1177 |
+
f.write(json.dumps(log_entry, ensure_ascii=False, indent=2) + "\n---\n")
|
| 1178 |
+
except Exception as e:
|
| 1179 |
+
print(f"[LOG ERROR] ๅๅ
ฅ Gemini ๆฅๅฟๅคฑ่ดฅ: {e}")
|
| 1180 |
+
|
| 1181 |
+
def _send_request(self, text: str, images: List[Dict] = None, model: str = None) -> ChatCompletionResponse:
|
| 1182 |
+
"""ๅ้่ฏทๆฑๅฐ Gemini"""
|
| 1183 |
+
url = f"{self.BASE_URL}/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
|
| 1184 |
+
|
| 1185 |
+
params = {
|
| 1186 |
+
"bl": self.bl,
|
| 1187 |
+
"f.sid": "",
|
| 1188 |
+
"hl": "zh-CN",
|
| 1189 |
+
"_reqid": str(self.request_count * 100000 + random.randint(10000, 99999)),
|
| 1190 |
+
"rt": "c",
|
| 1191 |
+
}
|
| 1192 |
+
|
| 1193 |
+
# ๆจกๅๆ ่ฏๆ ๅฐ (้่ฟ่ฏทๆฑๅคด x-goog-ext-525001261-jspb ้ๆฉๆจกๅ)
|
| 1194 |
+
model_id = self.model_ids.get("flash", "56fdd199312815e2") # ้ป่ฎคๆ้็
|
| 1195 |
+
if model:
|
| 1196 |
+
model_lower = model.lower()
|
| 1197 |
+
if "pro" in model_lower:
|
| 1198 |
+
model_id = self.model_ids.get("pro", "e6fa609c3fa255c0")
|
| 1199 |
+
elif "thinking" in model_lower or "think" in model_lower:
|
| 1200 |
+
model_id = self.model_ids.get("thinking", "e051ce1aa80aa576")
|
| 1201 |
+
|
| 1202 |
+
# ไธไผ ๅพ็่ทๅ่ทฏๅพ
|
| 1203 |
+
image_paths = []
|
| 1204 |
+
if images and len(images) > 0:
|
| 1205 |
+
if not self.push_id:
|
| 1206 |
+
print("โ ๏ธ ๅพ็ไธไผ ้่ฆ push-id๏ผ่ฏท่ฟ่ก: python get_push_id.py")
|
| 1207 |
+
print(" ็ถๅๅฐ่ทๅ็ push-id ๆทปๅ ๅฐ config.py")
|
| 1208 |
+
else:
|
| 1209 |
+
try:
|
| 1210 |
+
for img in images:
|
| 1211 |
+
# ่งฃ็ base64 ๆฐๆฎ
|
| 1212 |
+
img_data = base64.b64decode(img["data"])
|
| 1213 |
+
# ไธไผ ๅนถ่ทๅ่ทฏๅพ
|
| 1214 |
+
path = self._upload_image(img_data, img["mime_type"])
|
| 1215 |
+
image_paths.append(path)
|
| 1216 |
+
if self.debug:
|
| 1217 |
+
print(f"[DEBUG] ๅพ็ไธไผ ๆๅ: {path[:50]}...")
|
| 1218 |
+
except Exception as e:
|
| 1219 |
+
print(f"โ ๏ธ ๅพ็ไธไผ ๅคฑ่ดฅ: {e}")
|
| 1220 |
+
image_paths = []
|
| 1221 |
+
|
| 1222 |
+
req_data = self._build_request_data(text, images, image_paths, model)
|
| 1223 |
+
|
| 1224 |
+
form_data = {
|
| 1225 |
+
"f.req": req_data,
|
| 1226 |
+
"at": self.snlm0e,
|
| 1227 |
+
}
|
| 1228 |
+
|
| 1229 |
+
# ๆจกๅ้ๆฉ่ฏทๆฑๅคด
|
| 1230 |
+
model_headers = {
|
| 1231 |
+
"x-goog-ext-525001261-jspb": json.dumps([1, None, None, None, model_id, None, None, 0, [4], None, None, 2], separators=(',', ':')),
|
| 1232 |
+
}
|
| 1233 |
+
|
| 1234 |
+
# ๆๅปบๆฅๅฟ่ฎฐๅฝ
|
| 1235 |
+
gemini_request_log = {
|
| 1236 |
+
"url": url,
|
| 1237 |
+
"params": params,
|
| 1238 |
+
"text": text,
|
| 1239 |
+
"model": model,
|
| 1240 |
+
"model_id": model_id,
|
| 1241 |
+
"has_images": len(images) > 0 if images else False,
|
| 1242 |
+
"image_paths": image_paths,
|
| 1243 |
+
"f_req_preview": req_data[:500] + "..." if len(req_data) > 500 else req_data,
|
| 1244 |
+
}
|
| 1245 |
+
|
| 1246 |
+
if self.debug:
|
| 1247 |
+
print(f"[DEBUG] ่ฏทๆฑ URL: {url}")
|
| 1248 |
+
print(f"[DEBUG] AT Token: {self.snlm0e[:30]}...")
|
| 1249 |
+
print(f"[DEBUG] ๆจกๅ: {model or '้ป่ฎค'}, ID: {model_id}")
|
| 1250 |
+
if image_paths:
|
| 1251 |
+
print(f"[DEBUG] ่ฏทๆฑๆฐๆฎๅ300ๅญ็ฌฆ: {req_data[:300]}")
|
| 1252 |
+
|
| 1253 |
+
# ้่ฏๆบๅถ
|
| 1254 |
+
max_retries = 3
|
| 1255 |
+
last_error = None
|
| 1256 |
+
|
| 1257 |
+
for attempt in range(max_retries):
|
| 1258 |
+
try:
|
| 1259 |
+
resp = self.session.post(url, params=params, data=form_data, headers=model_headers, timeout=60.0)
|
| 1260 |
+
|
| 1261 |
+
if self.debug:
|
| 1262 |
+
print(f"[DEBUG] ๅๅบ็ถๆ: {resp.status_code}")
|
| 1263 |
+
print(f"[DEBUG] ๅๅบๅ
ๅฎนๅ500ๅญ็ฌฆ: {resp.text[:500]}")
|
| 1264 |
+
# ๅง็ปไฟๅญๅฎๆดๅๅบ็จไบ่ฐ่ฏ
|
| 1265 |
+
with open("debug_image_response.txt", "w", encoding="utf-8") as f:
|
| 1266 |
+
f.write(resp.text)
|
| 1267 |
+
print(f"[DEBUG] ๅฎๆดๅๅบๅทฒไฟๅญๅฐ debug_image_response.txt")
|
| 1268 |
+
|
| 1269 |
+
# ่ฎฐๅฝ Gemini ๅฎๆดๅๅบ
|
| 1270 |
+
self._log_gemini_call(gemini_request_log, resp.text)
|
| 1271 |
+
|
| 1272 |
+
resp.raise_for_status()
|
| 1273 |
+
self.request_count += 1
|
| 1274 |
+
|
| 1275 |
+
reply_text = self._parse_response(resp.text)
|
| 1276 |
+
|
| 1277 |
+
# ไฟๅญๅฉๆๅๅค
|
| 1278 |
+
self.messages.append(Message(role="assistant", content=reply_text))
|
| 1279 |
+
|
| 1280 |
+
# ๆๅปบ OpenAI ๆ ผๅผๅๅบ
|
| 1281 |
+
return ChatCompletionResponse(
|
| 1282 |
+
id=f"chatcmpl-{self.conversation_id or 'gemini'}-{int(time.time())}",
|
| 1283 |
+
created=int(time.time()),
|
| 1284 |
+
model="gemini-web",
|
| 1285 |
+
choices=[
|
| 1286 |
+
ChatCompletionChoice(
|
| 1287 |
+
index=0,
|
| 1288 |
+
message=Message(role="assistant", content=reply_text),
|
| 1289 |
+
finish_reason="stop"
|
| 1290 |
+
)
|
| 1291 |
+
],
|
| 1292 |
+
usage=Usage(
|
| 1293 |
+
prompt_tokens=len(text),
|
| 1294 |
+
completion_tokens=len(reply_text),
|
| 1295 |
+
total_tokens=len(text) + len(reply_text)
|
| 1296 |
+
)
|
| 1297 |
+
)
|
| 1298 |
+
|
| 1299 |
+
except httpx.HTTPStatusError as e:
|
| 1300 |
+
self._log_gemini_call(gemini_request_log, e.response.text if hasattr(e, 'response') else "", error=f"HTTP {e.response.status_code}")
|
| 1301 |
+
raise Exception(f"HTTP ้่ฏฏ: {e.response.status_code}")
|
| 1302 |
+
except (httpx.RemoteProtocolError, httpx.ReadError, httpx.ConnectError) as e:
|
| 1303 |
+
# ็ฝ็ป่ฟๆฅ้ฎ้ข๏ผๅฏ้่ฏ
|
| 1304 |
+
last_error = e
|
| 1305 |
+
if attempt < max_retries - 1:
|
| 1306 |
+
wait_time = (attempt + 1) * 2 # 2, 4 ็ง
|
| 1307 |
+
print(f"โ ๏ธ ่ฟๆฅไธญๆญ๏ผ{wait_time}็งๅ้่ฏ ({attempt + 1}/{max_retries})...")
|
| 1308 |
+
time.sleep(wait_time)
|
| 1309 |
+
continue
|
| 1310 |
+
self._log_gemini_call(gemini_request_log, "", error=str(e))
|
| 1311 |
+
raise Exception(f"็ฝ็ป่ฟๆฅๅคฑ่ดฅ๏ผๅทฒ้่ฏ{max_retries}ๆฌก๏ผ: {e}")
|
| 1312 |
+
except Exception as e:
|
| 1313 |
+
self._log_gemini_call(gemini_request_log, "", error=str(e))
|
| 1314 |
+
raise Exception(f"่ฏทๆฑๅคฑ่ดฅ: {e}")
|
| 1315 |
+
|
| 1316 |
+
# ๆๆ้่ฏ้ฝๅคฑ่ดฅ
|
| 1317 |
+
if last_error:
|
| 1318 |
+
raise Exception(f"่ฏทๆฑๅคฑ่ดฅ๏ผๅทฒ้่ฏ{max_retries}ๆฌก๏ผ: {last_error}")
|
| 1319 |
+
|
| 1320 |
+
def _extract_text_from_inner_json(self, inner_json: list) -> str:
|
| 1321 |
+
"""Extract reply text from a single inner JSON packet and update session IDs."""
|
| 1322 |
+
try:
|
| 1323 |
+
if not inner_json:
|
| 1324 |
+
return ""
|
| 1325 |
+
|
| 1326 |
+
if len(inner_json) > 1 and inner_json[1]:
|
| 1327 |
+
if isinstance(inner_json[1], list):
|
| 1328 |
+
if len(inner_json[1]) > 0:
|
| 1329 |
+
self.conversation_id = inner_json[1][0] or self.conversation_id
|
| 1330 |
+
if len(inner_json[1]) > 1:
|
| 1331 |
+
self.response_id = inner_json[1][1] or self.response_id
|
| 1332 |
+
|
| 1333 |
+
if len(inner_json) > 4 and inner_json[4]:
|
| 1334 |
+
candidates = inner_json[4]
|
| 1335 |
+
if candidates and len(candidates) > 0:
|
| 1336 |
+
candidate = candidates[0]
|
| 1337 |
+
if candidate and len(candidate) > 1 and candidate[1]:
|
| 1338 |
+
if len(candidate) > 0:
|
| 1339 |
+
self.choice_id = candidate[0] or self.choice_id
|
| 1340 |
+
text = candidate[1][0] if isinstance(candidate[1], list) else candidate[1]
|
| 1341 |
+
return text if isinstance(text, str) else str(text)
|
| 1342 |
+
except Exception:
|
| 1343 |
+
pass
|
| 1344 |
+
return ""
|
| 1345 |
+
|
| 1346 |
+
def _send_request_stream(self, text: str, images: List[Dict] = None, model: str = None) -> Iterator[Dict[str, Any]]:
|
| 1347 |
+
"""True streaming path: parse Gemini length-prefixed frames and emit deltas."""
|
| 1348 |
+
url = f"{self.BASE_URL}/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
|
| 1349 |
+
|
| 1350 |
+
params = {
|
| 1351 |
+
"bl": self.bl,
|
| 1352 |
+
"f.sid": "",
|
| 1353 |
+
"hl": "zh-CN",
|
| 1354 |
+
"_reqid": str(self.request_count * 100000 + random.randint(10000, 99999)),
|
| 1355 |
+
"rt": "c",
|
| 1356 |
+
}
|
| 1357 |
+
|
| 1358 |
+
model_id = self.model_ids.get("flash", "56fdd199312815e2")
|
| 1359 |
+
if model:
|
| 1360 |
+
model_lower = model.lower()
|
| 1361 |
+
if "pro" in model_lower:
|
| 1362 |
+
model_id = self.model_ids.get("pro", "e6fa609c3fa255c0")
|
| 1363 |
+
elif "thinking" in model_lower or "think" in model_lower:
|
| 1364 |
+
model_id = self.model_ids.get("thinking", "e051ce1aa80aa576")
|
| 1365 |
+
|
| 1366 |
+
image_paths = []
|
| 1367 |
+
if images and len(images) > 0 and self.push_id:
|
| 1368 |
+
for img in images:
|
| 1369 |
+
img_data = base64.b64decode(img["data"])
|
| 1370 |
+
image_paths.append(self._upload_image(img_data, img["mime_type"]))
|
| 1371 |
+
|
| 1372 |
+
req_data = self._build_request_data(text, images, image_paths, model)
|
| 1373 |
+
form_data = {"f.req": req_data, "at": self.snlm0e}
|
| 1374 |
+
model_headers = {
|
| 1375 |
+
"x-goog-ext-525001261-jspb": json.dumps(
|
| 1376 |
+
[1, None, None, None, model_id, None, None, 0, [4], None, None, 2],
|
| 1377 |
+
separators=(",", ":"),
|
| 1378 |
+
),
|
| 1379 |
+
}
|
| 1380 |
+
|
| 1381 |
+
completion_id = f"chatcmpl-{uuid.uuid4().hex[:24]}"
|
| 1382 |
+
created = int(time.time())
|
| 1383 |
+
chunk_model = model or "gemini-web"
|
| 1384 |
+
|
| 1385 |
+
yield {
|
| 1386 |
+
"id": completion_id,
|
| 1387 |
+
"object": "chat.completion.chunk",
|
| 1388 |
+
"created": created,
|
| 1389 |
+
"model": chunk_model,
|
| 1390 |
+
"choices": [{"index": 0, "delta": {"role": "assistant"}, "finish_reason": None}],
|
| 1391 |
+
}
|
| 1392 |
+
|
| 1393 |
+
full_text = ""
|
| 1394 |
+
raw_lines: List[str] = []
|
| 1395 |
+
|
| 1396 |
+
def utf16_char_len(ch: str) -> int:
|
| 1397 |
+
return 2 if ord(ch) > 0xFFFF else 1
|
| 1398 |
+
|
| 1399 |
+
def take_utf16_units(s: str, start: int, target_units: int) -> tuple[int, int]:
|
| 1400 |
+
count = 0
|
| 1401 |
+
used = 0
|
| 1402 |
+
n = len(s)
|
| 1403 |
+
while (start + count) < n and used < target_units:
|
| 1404 |
+
c = s[start + count]
|
| 1405 |
+
u = utf16_char_len(c)
|
| 1406 |
+
if used + u > target_units:
|
| 1407 |
+
break
|
| 1408 |
+
used += u
|
| 1409 |
+
count += 1
|
| 1410 |
+
return count, used
|
| 1411 |
+
|
| 1412 |
+
def parse_length_prefixed_frames(buffer: str) -> tuple[List[Any], str]:
|
| 1413 |
+
frames: List[Any] = []
|
| 1414 |
+
pos = 0
|
| 1415 |
+
n = len(buffer)
|
| 1416 |
+
while pos < n:
|
| 1417 |
+
while pos < n and buffer[pos].isspace():
|
| 1418 |
+
pos += 1
|
| 1419 |
+
if pos >= n:
|
| 1420 |
+
break
|
| 1421 |
+
|
| 1422 |
+
m = re.match(r"(\d+)\n", buffer[pos:])
|
| 1423 |
+
if not m:
|
| 1424 |
+
break
|
| 1425 |
+
|
| 1426 |
+
frame_units = int(m.group(1))
|
| 1427 |
+
marker_len = len(m.group(1))
|
| 1428 |
+
start_content = pos + marker_len
|
| 1429 |
+
char_count, used_units = take_utf16_units(buffer, start_content, frame_units)
|
| 1430 |
+
if used_units < frame_units:
|
| 1431 |
+
break
|
| 1432 |
+
|
| 1433 |
+
end_content = start_content + char_count
|
| 1434 |
+
chunk = buffer[start_content:end_content].strip()
|
| 1435 |
+
pos = end_content
|
| 1436 |
+
|
| 1437 |
+
if not chunk:
|
| 1438 |
+
continue
|
| 1439 |
+
|
| 1440 |
+
try:
|
| 1441 |
+
parsed = json.loads(chunk)
|
| 1442 |
+
if isinstance(parsed, list):
|
| 1443 |
+
frames.extend(parsed)
|
| 1444 |
+
else:
|
| 1445 |
+
frames.append(parsed)
|
| 1446 |
+
except Exception:
|
| 1447 |
+
continue
|
| 1448 |
+
|
| 1449 |
+
return frames, buffer[pos:]
|
| 1450 |
+
|
| 1451 |
+
def calc_delta(new_text: str, sent_text: str) -> str:
|
| 1452 |
+
if not new_text:
|
| 1453 |
+
return ""
|
| 1454 |
+
if new_text.startswith(sent_text):
|
| 1455 |
+
return new_text[len(sent_text):]
|
| 1456 |
+
if sent_text.startswith(new_text):
|
| 1457 |
+
return ""
|
| 1458 |
+
common = 0
|
| 1459 |
+
max_common = min(len(new_text), len(sent_text))
|
| 1460 |
+
while common < max_common and new_text[common] == sent_text[common]:
|
| 1461 |
+
common += 1
|
| 1462 |
+
return new_text[common:]
|
| 1463 |
+
|
| 1464 |
+
def process_packet(packet: Any) -> Iterator[Dict[str, Any]]:
|
| 1465 |
+
nonlocal full_text
|
| 1466 |
+
if not isinstance(packet, list) or len(packet) < 3 or packet[0] != "wrb.fr" or not packet[2]:
|
| 1467 |
+
return
|
| 1468 |
+
try:
|
| 1469 |
+
inner_json = json.loads(packet[2])
|
| 1470 |
+
raw_lines.append(json.dumps([packet], ensure_ascii=False))
|
| 1471 |
+
text_candidate = self._extract_text_from_inner_json(inner_json)
|
| 1472 |
+
if text_candidate:
|
| 1473 |
+
delta = calc_delta(text_candidate, full_text)
|
| 1474 |
+
if delta:
|
| 1475 |
+
full_text = text_candidate
|
| 1476 |
+
yield {
|
| 1477 |
+
"id": completion_id,
|
| 1478 |
+
"object": "chat.completion.chunk",
|
| 1479 |
+
"created": created,
|
| 1480 |
+
"model": chunk_model,
|
| 1481 |
+
"choices": [{"index": 0, "delta": {"content": delta}, "finish_reason": None}],
|
| 1482 |
+
}
|
| 1483 |
+
except Exception:
|
| 1484 |
+
return
|
| 1485 |
+
|
| 1486 |
+
with self.session.stream(
|
| 1487 |
+
"POST",
|
| 1488 |
+
url,
|
| 1489 |
+
params=params,
|
| 1490 |
+
data=form_data,
|
| 1491 |
+
headers=model_headers,
|
| 1492 |
+
timeout=60.0,
|
| 1493 |
+
) as resp:
|
| 1494 |
+
resp.raise_for_status()
|
| 1495 |
+
self.request_count += 1
|
| 1496 |
+
|
| 1497 |
+
decoder = codecs.getincrementaldecoder("utf-8")(errors="replace")
|
| 1498 |
+
stream_buffer = ""
|
| 1499 |
+
|
| 1500 |
+
for chunk_bytes in resp.iter_bytes():
|
| 1501 |
+
if not chunk_bytes:
|
| 1502 |
+
continue
|
| 1503 |
+
|
| 1504 |
+
stream_buffer += decoder.decode(chunk_bytes, final=False)
|
| 1505 |
+
if stream_buffer.startswith(")]}'"):
|
| 1506 |
+
stream_buffer = stream_buffer[4:].lstrip()
|
| 1507 |
+
|
| 1508 |
+
packets, stream_buffer = parse_length_prefixed_frames(stream_buffer)
|
| 1509 |
+
for packet in packets:
|
| 1510 |
+
yield from process_packet(packet)
|
| 1511 |
+
|
| 1512 |
+
if len(stream_buffer) > 2_000_000:
|
| 1513 |
+
stream_buffer = ""
|
| 1514 |
+
|
| 1515 |
+
stream_buffer += decoder.decode(b"", final=True)
|
| 1516 |
+
tail_packets, _ = parse_length_prefixed_frames(stream_buffer)
|
| 1517 |
+
for packet in tail_packets:
|
| 1518 |
+
yield from process_packet(packet)
|
| 1519 |
+
|
| 1520 |
+
final_reply = self._parse_response("\n".join(raw_lines)) if raw_lines else full_text
|
| 1521 |
+
if not final_reply:
|
| 1522 |
+
final_reply = full_text
|
| 1523 |
+
|
| 1524 |
+
if final_reply and len(final_reply) > len(full_text) and final_reply.startswith(full_text):
|
| 1525 |
+
tail = final_reply[len(full_text):]
|
| 1526 |
+
if tail:
|
| 1527 |
+
yield {
|
| 1528 |
+
"id": completion_id,
|
| 1529 |
+
"object": "chat.completion.chunk",
|
| 1530 |
+
"created": created,
|
| 1531 |
+
"model": chunk_model,
|
| 1532 |
+
"choices": [{"index": 0, "delta": {"content": tail}, "finish_reason": None}],
|
| 1533 |
+
}
|
| 1534 |
+
|
| 1535 |
+
self.messages.append(Message(role="assistant", content=final_reply))
|
| 1536 |
+
yield {
|
| 1537 |
+
"id": completion_id,
|
| 1538 |
+
"object": "chat.completion.chunk",
|
| 1539 |
+
"created": created,
|
| 1540 |
+
"model": chunk_model,
|
| 1541 |
+
"choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
|
| 1542 |
+
}
|
| 1543 |
+
|
| 1544 |
+
def reset(self):
|
| 1545 |
+
"""้็ฝฎไผ่ฏไธไธๆ"""
|
| 1546 |
+
self.conversation_id = ""
|
| 1547 |
+
self.response_id = ""
|
| 1548 |
+
self.choice_id = ""
|
| 1549 |
+
self.messages = []
|
| 1550 |
+
|
| 1551 |
+
def get_history(self) -> List[Dict]:
|
| 1552 |
+
"""่ทๅๆถๆฏๅๅฒ (OpenAI ๆ ผๅผ)"""
|
| 1553 |
+
return [{"role": m.role, "content": m.content} for m in self.messages]
|
| 1554 |
+
|
| 1555 |
+
|
| 1556 |
+
# OpenAI ๅ
ผๅฎนๆฅๅฃ
|
| 1557 |
+
class OpenAICompatible:
|
| 1558 |
+
"""OpenAI SDK ๅ
ผๅฎนๅฐ่ฃ
"""
|
| 1559 |
+
|
| 1560 |
+
def __init__(self, client: GeminiClient):
|
| 1561 |
+
self.client = client
|
| 1562 |
+
self.chat = self.Chat(client)
|
| 1563 |
+
|
| 1564 |
+
class Chat:
|
| 1565 |
+
def __init__(self, client: GeminiClient):
|
| 1566 |
+
self.client = client
|
| 1567 |
+
self.completions = self.Completions(client)
|
| 1568 |
+
|
| 1569 |
+
class Completions:
|
| 1570 |
+
def __init__(self, client: GeminiClient):
|
| 1571 |
+
self.client = client
|
| 1572 |
+
|
| 1573 |
+
def create(
|
| 1574 |
+
self,
|
| 1575 |
+
model: str = "gemini-web",
|
| 1576 |
+
messages: List[Dict] = None,
|
| 1577 |
+
**kwargs
|
| 1578 |
+
) -> ChatCompletionResponse:
|
| 1579 |
+
return self.client.chat(
|
| 1580 |
+
messages=messages,
|
| 1581 |
+
model=model,
|
| 1582 |
+
stream=bool(kwargs.get("stream", False)),
|
| 1583 |
+
)
|
demo_chat.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
๏ปฟ"""
|
| 2 |
+
Gemini API demo with streaming responses.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import base64
|
| 6 |
+
from openai import OpenAI
|
| 7 |
+
|
| 8 |
+
# Config
|
| 9 |
+
BASE_URL = "http://127.0.0.1:8000/v1"
|
| 10 |
+
API_KEY = "sk-geminixxxxx"
|
| 11 |
+
|
| 12 |
+
client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def load_image_base64(path: str) -> str:
|
| 16 |
+
with open(path, "rb") as f:
|
| 17 |
+
return base64.b64encode(f.read()).decode()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def print_stream(stream) -> None:
|
| 21 |
+
for chunk in stream:
|
| 22 |
+
if not chunk.choices:
|
| 23 |
+
continue
|
| 24 |
+
delta = chunk.choices[0].delta
|
| 25 |
+
if delta and getattr(delta, "content", None):
|
| 26 |
+
print(delta.content, end="", flush=True)
|
| 27 |
+
print()
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def chat_text() -> None:
|
| 31 |
+
print("=" * 50)
|
| 32 |
+
print("Text chat (stream)")
|
| 33 |
+
print("=" * 50)
|
| 34 |
+
|
| 35 |
+
stream = client.chat.completions.create(
|
| 36 |
+
model="gemini-3.0-pro",
|
| 37 |
+
messages=[{"role": "user", "content": "ไฝ ๅฅฝ๏ผไป็ปไธไธไฝ ่ชๅทฑ"}],
|
| 38 |
+
stream=True,
|
| 39 |
+
)
|
| 40 |
+
print_stream(stream)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def chat_single_image() -> None:
|
| 44 |
+
print("\n" + "=" * 50)
|
| 45 |
+
print("Single image (stream)")
|
| 46 |
+
print("=" * 50)
|
| 47 |
+
|
| 48 |
+
img_b64 = load_image_base64("image.png")
|
| 49 |
+
|
| 50 |
+
stream = client.chat.completions.create(
|
| 51 |
+
model="gemini-3.0-flash",
|
| 52 |
+
messages=[
|
| 53 |
+
{
|
| 54 |
+
"role": "user",
|
| 55 |
+
"content": [
|
| 56 |
+
{"type": "text", "text": "ๆ่ฟฐ่ฟๅผ ๅพ็"},
|
| 57 |
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_b64}"}},
|
| 58 |
+
],
|
| 59 |
+
}
|
| 60 |
+
],
|
| 61 |
+
stream=True,
|
| 62 |
+
)
|
| 63 |
+
print_stream(stream)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def chat_multi_images() -> None:
|
| 67 |
+
print("\n" + "=" * 50)
|
| 68 |
+
print("Multi image (stream)")
|
| 69 |
+
print("=" * 50)
|
| 70 |
+
|
| 71 |
+
img1_b64 = load_image_base64("a.png")
|
| 72 |
+
img2_b64 = load_image_base64("b.png")
|
| 73 |
+
|
| 74 |
+
stream = client.chat.completions.create(
|
| 75 |
+
model="gemini-3.0-pro",
|
| 76 |
+
messages=[
|
| 77 |
+
{
|
| 78 |
+
"role": "user",
|
| 79 |
+
"content": [
|
| 80 |
+
{"type": "text", "text": "ๆฏ่พ่ฟไธคๅผ ๅพ็็ๅบๅซ"},
|
| 81 |
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img1_b64}"}},
|
| 82 |
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img2_b64}"}},
|
| 83 |
+
],
|
| 84 |
+
}
|
| 85 |
+
],
|
| 86 |
+
stream=True,
|
| 87 |
+
)
|
| 88 |
+
print_stream(stream)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def chat_image_generation() -> None:
|
| 92 |
+
print("\n" + "=" * 50)
|
| 93 |
+
print("Image generation (stream)")
|
| 94 |
+
print("=" * 50)
|
| 95 |
+
|
| 96 |
+
stream = client.chat.completions.create(
|
| 97 |
+
model="gemini-3.0-pro",
|
| 98 |
+
messages=[{"role": "user", "content": "็ๆไธๅผ ๅฏ็ฑ็็ซๅชๅพ็"}],
|
| 99 |
+
stream=True,
|
| 100 |
+
)
|
| 101 |
+
print_stream(stream)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
if __name__ == "__main__":
|
| 105 |
+
chat_text()
|
| 106 |
+
|
| 107 |
+
# Uncomment as needed:
|
| 108 |
+
# chat_single_image()
|
| 109 |
+
# chat_multi_images()
|
| 110 |
+
# chat_image_generation()
|
get_push_id.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
่ทๅ Gemini ็ push-id
|
| 3 |
+
|
| 4 |
+
push-id ๆฏๅพ็ไธไผ ๆ้็ๅฟ
่ฆๅๆฐ๏ผๆ ผๅผไธบ feeds/xxxxx
|
| 5 |
+
้่ฆไป Gemini ้กต้ขๆ API ่ทๅ
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import httpx
|
| 9 |
+
import re
|
| 10 |
+
from config import SECURE_1PSID, SECURE_1PSIDTS, SECURE_1PSIDCC, COOKIES_STR
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def get_push_id_from_page():
|
| 14 |
+
"""ไป Gemini ้กต้ข่ทๅ push-id"""
|
| 15 |
+
print("ๆญฃๅจ่ทๅ push-id...")
|
| 16 |
+
|
| 17 |
+
session = httpx.Client(
|
| 18 |
+
timeout=30.0,
|
| 19 |
+
follow_redirects=True,
|
| 20 |
+
headers={
|
| 21 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
| 22 |
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
| 23 |
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
| 24 |
+
}
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
# ่ฎพ็ฝฎ cookies
|
| 28 |
+
if COOKIES_STR:
|
| 29 |
+
for item in COOKIES_STR.split(";"):
|
| 30 |
+
item = item.strip()
|
| 31 |
+
if "=" in item:
|
| 32 |
+
key, value = item.split("=", 1)
|
| 33 |
+
session.cookies.set(key.strip(), value.strip(), domain=".google.com")
|
| 34 |
+
else:
|
| 35 |
+
session.cookies.set("__Secure-1PSID", SECURE_1PSID, domain=".google.com")
|
| 36 |
+
if SECURE_1PSIDTS:
|
| 37 |
+
session.cookies.set("__Secure-1PSIDTS", SECURE_1PSIDTS, domain=".google.com")
|
| 38 |
+
if SECURE_1PSIDCC:
|
| 39 |
+
session.cookies.set("__Secure-1PSIDCC", SECURE_1PSIDCC, domain=".google.com")
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
# ่ฎฟ้ฎ Gemini ไธป้กต
|
| 43 |
+
resp = session.get("https://gemini.google.com")
|
| 44 |
+
|
| 45 |
+
if resp.status_code != 200:
|
| 46 |
+
print(f"โ ่ฎฟ้ฎๅคฑ่ดฅ: {resp.status_code}")
|
| 47 |
+
return None
|
| 48 |
+
|
| 49 |
+
html = resp.text
|
| 50 |
+
|
| 51 |
+
# ๅฐ่ฏๅค็งๆจกๅผๅน้
push-id
|
| 52 |
+
patterns = [
|
| 53 |
+
r'"push[_-]?id["\s:]+["\'](feeds/[a-z0-9]+)["\']', # "push_id": "feeds/xxx"
|
| 54 |
+
r'push[_-]?id["\s:=]+["\'](feeds/[a-z0-9]+)["\']', # push_id="feeds/xxx"
|
| 55 |
+
r'feedName["\s:]+["\'](feeds/[a-z0-9]+)["\']', # "feedName": "feeds/xxx"
|
| 56 |
+
r'clientId["\s:]+["\'](feeds/[a-z0-9]+)["\']', # "clientId": "feeds/xxx"
|
| 57 |
+
r'(feeds/[a-z0-9]{14,})', # ็ดๆฅๅน้
feeds/xxx ๆ ผๅผ
|
| 58 |
+
]
|
| 59 |
+
|
| 60 |
+
for pattern in patterns:
|
| 61 |
+
matches = re.findall(pattern, html, re.IGNORECASE)
|
| 62 |
+
if matches:
|
| 63 |
+
push_id = matches[0]
|
| 64 |
+
print(f"โ
ๆพๅฐ push-id: {push_id}")
|
| 65 |
+
return push_id
|
| 66 |
+
|
| 67 |
+
# ๅฆๆๆฒกๆพๅฐ๏ผไฟๅญ้กต้ขๆบ็ ไพๅๆ
|
| 68 |
+
with open("gemini_page_debug.html", "w", encoding="utf-8") as f:
|
| 69 |
+
f.write(html)
|
| 70 |
+
print("โ ๆชๆพๅฐ push-id")
|
| 71 |
+
print(" ้กต้ขๆบ็ ๅทฒไฟๅญๅฐ gemini_page_debug.html")
|
| 72 |
+
print(" ่ฏทๆๅจๆ็ดข 'feeds/' ๆ 'push' ๅ
ณ้ฎๅญ")
|
| 73 |
+
|
| 74 |
+
return None
|
| 75 |
+
|
| 76 |
+
except Exception as e:
|
| 77 |
+
print(f"โ ้่ฏฏ: {e}")
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def get_push_id_from_api():
|
| 82 |
+
"""ๅฐ่ฏไป API ่ทๅ push-id"""
|
| 83 |
+
print("\nๅฐ่ฏไป API ่ทๅ push-id...")
|
| 84 |
+
|
| 85 |
+
session = httpx.Client(
|
| 86 |
+
timeout=30.0,
|
| 87 |
+
follow_redirects=True,
|
| 88 |
+
headers={
|
| 89 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
| 90 |
+
"Content-Type": "application/json",
|
| 91 |
+
}
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
# ่ฎพ็ฝฎ cookies
|
| 95 |
+
if COOKIES_STR:
|
| 96 |
+
for item in COOKIES_STR.split(";"):
|
| 97 |
+
item = item.strip()
|
| 98 |
+
if "=" in item:
|
| 99 |
+
key, value = item.split("=", 1)
|
| 100 |
+
session.cookies.set(key.strip(), value.strip(), domain=".google.com")
|
| 101 |
+
else:
|
| 102 |
+
session.cookies.set("__Secure-1PSID", SECURE_1PSID, domain=".google.com")
|
| 103 |
+
|
| 104 |
+
# ๅฏ่ฝ็ API ็ซฏ็น
|
| 105 |
+
endpoints = [
|
| 106 |
+
"https://gemini.google.com/_/BardChatUi/data/batchexecute",
|
| 107 |
+
"https://push.clients6.google.com/v1/feeds",
|
| 108 |
+
]
|
| 109 |
+
|
| 110 |
+
for endpoint in endpoints:
|
| 111 |
+
try:
|
| 112 |
+
resp = session.get(endpoint)
|
| 113 |
+
print(f" {endpoint}: {resp.status_code}")
|
| 114 |
+
if resp.status_code == 200:
|
| 115 |
+
# ๅฐ่ฏไปๅๅบไธญๆๅ push-id
|
| 116 |
+
text = resp.text
|
| 117 |
+
match = re.search(r'feeds/[a-z0-9]{14,}', text)
|
| 118 |
+
if match:
|
| 119 |
+
push_id = match.group(0)
|
| 120 |
+
print(f" โ
ๆพๅฐ: {push_id}")
|
| 121 |
+
return push_id
|
| 122 |
+
except Exception as e:
|
| 123 |
+
print(f" โ {endpoint}: {e}")
|
| 124 |
+
|
| 125 |
+
return None
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
if __name__ == "__main__":
|
| 129 |
+
print("=" * 60)
|
| 130 |
+
print("่ทๅ Gemini push-id")
|
| 131 |
+
print("=" * 60)
|
| 132 |
+
|
| 133 |
+
# ๆนๆณ1: ไป้กต้ข่ทๅ
|
| 134 |
+
push_id = get_push_id_from_page()
|
| 135 |
+
|
| 136 |
+
# ๆนๆณ2: ไป API ่ทๅ
|
| 137 |
+
if not push_id:
|
| 138 |
+
push_id = get_push_id_from_api()
|
| 139 |
+
|
| 140 |
+
if push_id:
|
| 141 |
+
print("\n" + "=" * 60)
|
| 142 |
+
print(f"โ
ๆๅ่ทๅ push-id: {push_id}")
|
| 143 |
+
print("=" * 60)
|
| 144 |
+
print("\n่ฏทๅฐๆญคๅผๆทปๅ ๅฐ config.py:")
|
| 145 |
+
print(f'PUSH_ID = "{push_id}"')
|
| 146 |
+
else:
|
| 147 |
+
print("\n" + "=" * 60)
|
| 148 |
+
print("โ ๆช่ฝ่ชๅจ่ทๅ push-id")
|
| 149 |
+
print("=" * 60)
|
| 150 |
+
print("\nๆๅจ่ทๅๆนๆณ:")
|
| 151 |
+
print("1. ๆๅผ https://gemini.google.com ๅนถ็ปๅฝ")
|
| 152 |
+
print("2. F12 ๆๅผๅผๅ่
ๅทฅๅ
ท -> Network ๆ ็ญพ")
|
| 153 |
+
print("3. ไธไผ ไธๅผ ๅพ็")
|
| 154 |
+
print("4. ๆฅๆพ upload ่ฏทๆฑ")
|
| 155 |
+
print("5. ๅจ่ฏทๆฑๅคดไธญๆพๅฐ push-id ๆ x-goog-upload-header-content-length")
|
| 156 |
+
print("6. ๅคๅถ feeds/xxxxx ๆ ผๅผ็ๅผ")
|
image.png
ADDED
|
Git LFS Details
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
httpx>=0.25.0
|
| 2 |
+
fastapi>=0.104.0
|
| 3 |
+
uvicorn>=0.24.0
|
| 4 |
+
openai>=1.0.0
|
server.py
ADDED
|
@@ -0,0 +1,1719 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gemini OpenAI ๅ
ผๅฎน API ๆๅก
|
| 3 |
+
|
| 4 |
+
ๅฏๅจ: python server.py
|
| 5 |
+
ๅๅฐ: http://localhost:8000/admin
|
| 6 |
+
API: http://localhost:8000/v1
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from fastapi import FastAPI, HTTPException, Header, Request
|
| 10 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 11 |
+
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse, JSONResponse
|
| 12 |
+
from pydantic import BaseModel
|
| 13 |
+
from typing import List, Dict, Any, Optional, Union
|
| 14 |
+
import uvicorn
|
| 15 |
+
import time
|
| 16 |
+
import uuid
|
| 17 |
+
import json
|
| 18 |
+
import os
|
| 19 |
+
import re
|
| 20 |
+
import httpx
|
| 21 |
+
import hashlib
|
| 22 |
+
import secrets
|
| 23 |
+
import asyncio
|
| 24 |
+
|
| 25 |
+
# ============ ้
็ฝฎ ============
|
| 26 |
+
API_KEY = os.getenv("API_KEY", "sk-geminixxxxx")
|
| 27 |
+
HOST = "0.0.0.0"
|
| 28 |
+
PORT = int(os.getenv("PORT", 7860))
|
| 29 |
+
CONFIG_FILE = "config_data.json"
|
| 30 |
+
# ๅๅฐ็ปๅฝ่ดฆๅทๅฏ็
|
| 31 |
+
ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "admin")
|
| 32 |
+
ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "admin123")
|
| 33 |
+
# Token ่ชๅจๅทๆฐ้
็ฝฎ
|
| 34 |
+
TOKEN_REFRESH_INTERVAL_MIN = 1800 # ๅทๆฐ้ด้ๆๅฐ็งๆฐ๏ผ้ป่ฎค 30 ๅ้๏ผ
|
| 35 |
+
TOKEN_REFRESH_INTERVAL_MAX = 3600 # ๅทๆฐ้ด้ๆๅคง็งๆฐ๏ผ้ป่ฎค 60 ๅ้๏ผ
|
| 36 |
+
TOKEN_AUTO_REFRESH = True # ๆฏๅฆๅฏ็จ่ชๅจๅทๆฐ
|
| 37 |
+
TOKEN_BACKGROUND_REFRESH = True # ๆฏๅฆๅฏ็จๅๅฐๅฎๆถๅทๆฐ๏ผ้ฒๆญข้ฟๆถ้ดไธ็จๅคฑๆ๏ผ
|
| 38 |
+
# ๅชไฝๆไปถๅค็ฝ่ฎฟ้ฎๅฐๅ
|
| 39 |
+
# ๅจ Hugging Face Spaces ไธญ๏ผๅฏไปฅ็ดๆฅ่ทๅ SPACE_ID ๆฅๆ้ URL
|
| 40 |
+
SPACE_ID = os.getenv("SPACE_ID")
|
| 41 |
+
if SPACE_ID:
|
| 42 |
+
MEDIA_BASE_URL = f"https://{SPACE_ID.replace('/', '-')}.hf.space"
|
| 43 |
+
else:
|
| 44 |
+
MEDIA_BASE_URL = os.getenv("MEDIA_BASE_URL", "http://127.0.0.1:7860")
|
| 45 |
+
# ==============================
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
import random
|
| 49 |
+
from datetime import datetime
|
| 50 |
+
|
| 51 |
+
# ๅๅฐๅทๆฐไปปๅกๆงๅถ
|
| 52 |
+
_background_refresh_task = None
|
| 53 |
+
_background_refresh_stop = False
|
| 54 |
+
|
| 55 |
+
app = FastAPI(title="Gemini OpenAI API", version="1.0.0")
|
| 56 |
+
|
| 57 |
+
app.add_middleware(
|
| 58 |
+
CORSMiddleware,
|
| 59 |
+
allow_origins=["*"],
|
| 60 |
+
allow_credentials=True,
|
| 61 |
+
allow_methods=["*"],
|
| 62 |
+
allow_headers=["*"],
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
# ้ๆๆไปถ่ทฏ็ฑ (็จไบ็คบไพๅพ็)
|
| 66 |
+
from fastapi.responses import FileResponse
|
| 67 |
+
|
| 68 |
+
# ็ๆ็ๅชไฝๆไปถ็ผๅญ็ฎๅฝ
|
| 69 |
+
MEDIA_CACHE_DIR = os.path.join(os.path.dirname(__file__), "media_cache")
|
| 70 |
+
os.makedirs(MEDIA_CACHE_DIR, exist_ok=True)
|
| 71 |
+
|
| 72 |
+
@app.get("/static/{filename}")
|
| 73 |
+
async def serve_static(filename: str):
|
| 74 |
+
"""ๆไพ้ๆๆไปถ๏ผ็คบไพๅพ็็ญ๏ผ"""
|
| 75 |
+
file_path = os.path.join(os.path.dirname(__file__), filename)
|
| 76 |
+
if os.path.exists(file_path):
|
| 77 |
+
return FileResponse(file_path)
|
| 78 |
+
raise HTTPException(status_code=404, detail="ๆไปถไธๅญๅจ")
|
| 79 |
+
|
| 80 |
+
@app.get("/media/{media_filename}")
|
| 81 |
+
async def serve_media(media_filename: str):
|
| 82 |
+
"""ๆไพ็ผๅญ็ๅชไฝๆไปถ"""
|
| 83 |
+
# ๅฎๅ
จๆฃๆฅ๏ผๅชๅ
่ฎธๅญๆฏๆฐๅญใไธๅ็บฟใ็นๅๅธธ่งๅ็ผ
|
| 84 |
+
import re
|
| 85 |
+
if not re.match(r'^[a-zA-Z0-9_-]+(\.(png|jpg|jpeg|gif|webp|mp4))?$', media_filename):
|
| 86 |
+
raise HTTPException(status_code=400, detail="ๆ ๆ็ๅชไฝๆไปถๅ")
|
| 87 |
+
|
| 88 |
+
# ็ดๆฅๆฅๆพๆไปถ๏ผๅธฆๅ็ผๅ๏ผ
|
| 89 |
+
file_path = os.path.join(MEDIA_CACHE_DIR, media_filename)
|
| 90 |
+
if os.path.exists(file_path):
|
| 91 |
+
return FileResponse(file_path)
|
| 92 |
+
|
| 93 |
+
# ๅ
ผๅฎนๆง็ๆฌ๏ผไธๅธฆๅ็ผๅ็่ฏทๆฑ๏ผๅฐ่ฏๆฅๆพๅน้
็ๆไปถ
|
| 94 |
+
media_id = media_filename.rsplit('.', 1)[0] if '.' in media_filename else media_filename
|
| 95 |
+
for ext in [".png", ".jpg", ".jpeg", ".gif", ".webp", ".mp4"]:
|
| 96 |
+
file_path = os.path.join(MEDIA_CACHE_DIR, media_id + ext)
|
| 97 |
+
if os.path.exists(file_path):
|
| 98 |
+
return FileResponse(file_path)
|
| 99 |
+
|
| 100 |
+
raise HTTPException(status_code=404, detail="ๅชไฝๆไปถไธๅญๅจ")
|
| 101 |
+
|
| 102 |
+
def cleanup_old_media(max_age_hours: int = 1):
|
| 103 |
+
"""ๆธ
็่ฟๆ็ๅชไฝ็ผๅญๆไปถ"""
|
| 104 |
+
import time
|
| 105 |
+
now = time.time()
|
| 106 |
+
max_age_seconds = max_age_hours * 3600
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
for filename in os.listdir(MEDIA_CACHE_DIR):
|
| 110 |
+
file_path = os.path.join(MEDIA_CACHE_DIR, filename)
|
| 111 |
+
if os.path.isfile(file_path):
|
| 112 |
+
file_age = now - os.path.getmtime(file_path)
|
| 113 |
+
if file_age > max_age_seconds:
|
| 114 |
+
os.remove(file_path)
|
| 115 |
+
except Exception:
|
| 116 |
+
pass
|
| 117 |
+
|
| 118 |
+
# ๅญๅจๆๆ็ session token
|
| 119 |
+
_admin_sessions = set()
|
| 120 |
+
|
| 121 |
+
def generate_session_token():
|
| 122 |
+
"""็ๆ้ๆบ session token"""
|
| 123 |
+
return secrets.token_hex(32)
|
| 124 |
+
|
| 125 |
+
def verify_admin_session(request: Request):
|
| 126 |
+
"""้ช่ฏ็ฎก็ๅ session"""
|
| 127 |
+
token = request.cookies.get("admin_session")
|
| 128 |
+
if not token or token not in _admin_sessions:
|
| 129 |
+
return False
|
| 130 |
+
return True
|
| 131 |
+
|
| 132 |
+
# ้ป่ฎคๅฏ็จๆจกๅๅ่กจ (Gemini 3 ๅฎ็ฝไธไธชๆจกๅ: ๅฟซ้/ๆ่/Pro)
|
| 133 |
+
DEFAULT_MODELS = ["gemini-3.0-flash", "gemini-3.0-flash-thinking", "gemini-3.0-pro"]
|
| 134 |
+
|
| 135 |
+
# ้ป่ฎคๆจกๅ ID (็จไบ่ฏทๆฑๅคด้ๆฉๆจกๅ)
|
| 136 |
+
DEFAULT_MODEL_IDS = {
|
| 137 |
+
"flash": "56fdd199312815e2",
|
| 138 |
+
"pro": "e6fa609c3fa255c0",
|
| 139 |
+
"thinking": "e051ce1aa80aa576",
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
# ้
็ฝฎๅญๅจ
|
| 143 |
+
_config = {
|
| 144 |
+
"SNLM0E": "",
|
| 145 |
+
"SECURE_1PSID": "",
|
| 146 |
+
"SECURE_1PSIDTS": "",
|
| 147 |
+
"SAPISID": "",
|
| 148 |
+
"SID": "",
|
| 149 |
+
"HSID": "",
|
| 150 |
+
"SSID": "",
|
| 151 |
+
"APISID": "",
|
| 152 |
+
"PUSH_ID": "",
|
| 153 |
+
"FULL_COOKIE": "", # ๅญๅจๅฎๆดcookieๅญ็ฌฆไธฒ
|
| 154 |
+
"MODELS": DEFAULT_MODELS.copy(), # ๅฏ็จๆจกๅๅ่กจ
|
| 155 |
+
"MODEL_IDS": DEFAULT_MODEL_IDS.copy(), # ๆจกๅ ID ๆ ๅฐ
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
# Cookie ๅญๆฎตๆ ๅฐ (ๆต่งๅจcookieๅ -> ้
็ฝฎๅญๆฎตๅ)
|
| 159 |
+
COOKIE_FIELD_MAP = {
|
| 160 |
+
"__Secure-1PSID": "SECURE_1PSID",
|
| 161 |
+
"__Secure-1PSIDTS": "SECURE_1PSIDTS",
|
| 162 |
+
"SAPISID": "SAPISID",
|
| 163 |
+
"__Secure-1PAPISID": "SAPISID", # ไนๆ ๅฐๅฐ SAPISID
|
| 164 |
+
"SID": "SID",
|
| 165 |
+
"HSID": "HSID",
|
| 166 |
+
"SSID": "SSID",
|
| 167 |
+
"APISID": "APISID",
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def parse_cookie_string(cookie_str: str) -> dict:
|
| 172 |
+
"""่งฃๆๅฎๆดcookieๅญ็ฌฆไธฒ๏ผๆๅๆ้ๅญๆฎต"""
|
| 173 |
+
result = {}
|
| 174 |
+
if not cookie_str:
|
| 175 |
+
return result
|
| 176 |
+
|
| 177 |
+
for item in cookie_str.split(";"):
|
| 178 |
+
item = item.strip()
|
| 179 |
+
if "=" in item:
|
| 180 |
+
eq_index = item.index("=")
|
| 181 |
+
key = item[:eq_index].strip()
|
| 182 |
+
value = item[eq_index + 1:].strip()
|
| 183 |
+
if key in COOKIE_FIELD_MAP:
|
| 184 |
+
result[COOKIE_FIELD_MAP[key]] = value
|
| 185 |
+
|
| 186 |
+
return result
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def fetch_tokens_from_page(cookies_str: str) -> dict:
|
| 190 |
+
"""ไป Gemini ้กต้ข่ชๅจ่ทๅ SNLM0EใPUSH_ID ๅๅฏ็จๆจกๅๅ่กจ"""
|
| 191 |
+
result = {"snlm0e": "", "push_id": "", "models": []}
|
| 192 |
+
try:
|
| 193 |
+
session = httpx.Client(
|
| 194 |
+
timeout=30.0,
|
| 195 |
+
follow_redirects=True,
|
| 196 |
+
headers={
|
| 197 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
| 198 |
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
| 199 |
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
| 200 |
+
}
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
# ่ฎพ็ฝฎ cookies
|
| 204 |
+
for item in cookies_str.split(";"):
|
| 205 |
+
item = item.strip()
|
| 206 |
+
if "=" in item:
|
| 207 |
+
key, value = item.split("=", 1)
|
| 208 |
+
session.cookies.set(key.strip(), value.strip(), domain=".google.com")
|
| 209 |
+
|
| 210 |
+
resp = session.get("https://gemini.google.com")
|
| 211 |
+
if resp.status_code != 200:
|
| 212 |
+
return result
|
| 213 |
+
|
| 214 |
+
html = resp.text
|
| 215 |
+
|
| 216 |
+
# ่ทๅ SNLM0E (AT Token)
|
| 217 |
+
snlm0e_patterns = [
|
| 218 |
+
r'"SNlM0e":"([^"]+)"',
|
| 219 |
+
r'SNlM0e["\s:]+["\']([^"\']+)["\']',
|
| 220 |
+
r'"at":"([^"]+)"',
|
| 221 |
+
]
|
| 222 |
+
for pattern in snlm0e_patterns:
|
| 223 |
+
match = re.search(pattern, html)
|
| 224 |
+
if match:
|
| 225 |
+
result["snlm0e"] = match.group(1)
|
| 226 |
+
break
|
| 227 |
+
|
| 228 |
+
# ่ทๅ PUSH_ID
|
| 229 |
+
push_id_patterns = [
|
| 230 |
+
r'"push[_-]?id["\s:]+["\'](feeds/[a-z0-9]+)["\']',
|
| 231 |
+
r'push[_-]?id["\s:=]+["\'](feeds/[a-z0-9]+)["\']',
|
| 232 |
+
r'feedName["\s:]+["\'](feeds/[a-z0-9]+)["\']',
|
| 233 |
+
r'clientId["\s:]+["\'](feeds/[a-z0-9]+)["\']',
|
| 234 |
+
r'(feeds/[a-z0-9]{14,})',
|
| 235 |
+
]
|
| 236 |
+
for pattern in push_id_patterns:
|
| 237 |
+
matches = re.findall(pattern, html, re.IGNORECASE)
|
| 238 |
+
if matches:
|
| 239 |
+
result["push_id"] = matches[0]
|
| 240 |
+
break
|
| 241 |
+
|
| 242 |
+
# ่ทๅๅฏ็จๆจกๅๅ่กจ (ไป้กต้ขไธญๆๅ gemini ๆจกๅ ID)
|
| 243 |
+
model_patterns = [
|
| 244 |
+
r'"(gemini-[a-z0-9\.\-]+)"', # ๅน้
"gemini-xxx" ๆ ผๅผ
|
| 245 |
+
r"'(gemini-[a-z0-9\.\-]+)'", # ๅน้
'gemini-xxx' ๆ ผๅผ
|
| 246 |
+
]
|
| 247 |
+
models_found = set()
|
| 248 |
+
for pattern in model_patterns:
|
| 249 |
+
matches = re.findall(pattern, html, re.IGNORECASE)
|
| 250 |
+
for m in matches:
|
| 251 |
+
# ่ฟๆปคๆๆ็ๆจกๅๅ็งฐ
|
| 252 |
+
if any(x in m.lower() for x in ['flash', 'pro', 'ultra', 'nano']):
|
| 253 |
+
models_found.add(m)
|
| 254 |
+
|
| 255 |
+
if models_found:
|
| 256 |
+
result["models"] = sorted(list(models_found))
|
| 257 |
+
|
| 258 |
+
# ่ทๅๆจกๅ ID (็จไบ x-goog-ext-525001261-jspb ่ฏทๆฑๅคด)
|
| 259 |
+
# ่ฟไบ ID ็จไบ้ๆฉไธๅ็ๆจกๅ็ๆฌ
|
| 260 |
+
model_id_pattern = r'\["([a-f0-9]{16})","gemini[^"]*(?:flash|pro|thinking)[^"]*"\]'
|
| 261 |
+
model_ids = re.findall(model_id_pattern, html, re.IGNORECASE)
|
| 262 |
+
if model_ids:
|
| 263 |
+
result["model_ids"] = list(set(model_ids))
|
| 264 |
+
|
| 265 |
+
#ๅค็จๆนๆก๏ผ็ดๆฅๆ็ดข 16 ไฝๅๅ
ญ่ฟๅถ ID๏ผๅจๆจกๅ้
็ฝฎ้่ฟ๏ผ
|
| 266 |
+
if not result.get("model_ids"):
|
| 267 |
+
# ๆ็ดข็ฑปไผผ "56fdd199312815e2" ็ๆจกๅผ
|
| 268 |
+
hex_id_pattern = r'"([a-f0-9]{16})"'
|
| 269 |
+
# ๅจๅ
ๅซ gemini ๆ model ็ไธไธๆไธญๆฅๆพ
|
| 270 |
+
context_pattern = r'.{0,100}(?:gemini|model|flash|pro|thinking).{0,100}'
|
| 271 |
+
contexts = re.findall(context_pattern, html, re.IGNORECASE)
|
| 272 |
+
hex_ids = set()
|
| 273 |
+
for ctx in contexts:
|
| 274 |
+
ids = re.findall(hex_id_pattern, ctx)
|
| 275 |
+
hex_ids.update(ids)
|
| 276 |
+
if hex_ids:
|
| 277 |
+
result["model_ids"] = list(hex_ids)
|
| 278 |
+
|
| 279 |
+
return result
|
| 280 |
+
except Exception:
|
| 281 |
+
return result
|
| 282 |
+
|
| 283 |
+
_client = None
|
| 284 |
+
_last_token_refresh = 0 # ไธๆฌก token ๅทๆฐๆถ้ด
|
| 285 |
+
_token_refresh_count = 0 # token ๅทๆฐๆฌกๆฐ็ป่ฎก
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
def try_refresh_tokens(force: bool = False) -> dict:
|
| 289 |
+
"""
|
| 290 |
+
ๅฐ่ฏๅทๆฐ token
|
| 291 |
+
|
| 292 |
+
Args:
|
| 293 |
+
force: ๆฏๅฆๅผบๅถๅทๆฐ๏ผๅฟฝ็ฅๆถ้ด้ด้
|
| 294 |
+
|
| 295 |
+
Returns:
|
| 296 |
+
dict: {"success": bool, "message": str, "snlm0e": str, "push_id": str}
|
| 297 |
+
"""
|
| 298 |
+
global _client, _last_token_refresh, _token_refresh_count, _config
|
| 299 |
+
|
| 300 |
+
result = {"success": False, "message": "", "snlm0e": "", "push_id": ""}
|
| 301 |
+
|
| 302 |
+
if not TOKEN_AUTO_REFRESH and not force:
|
| 303 |
+
result["message"] = "่ชๅจๅทๆฐๅทฒ็ฆ็จ"
|
| 304 |
+
return result
|
| 305 |
+
|
| 306 |
+
current_time = time.time()
|
| 307 |
+
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 308 |
+
|
| 309 |
+
# ๆฃๆฅๆฏๅฆ้่ฆๅทๆฐ๏ผ้ค้ๅผบๅถๅทๆฐ๏ผ
|
| 310 |
+
if not force and (current_time - _last_token_refresh) < TOKEN_REFRESH_INTERVAL_MIN:
|
| 311 |
+
result["message"] = f"่ท็ฆปไธๆฌกๅทๆฐไธ่ถณ {TOKEN_REFRESH_INTERVAL_MIN} ็ง"
|
| 312 |
+
return result
|
| 313 |
+
|
| 314 |
+
try:
|
| 315 |
+
# ๅฆๆ client ๅญๅจ๏ผไฝฟ็จ client ็ๅทๆฐๆนๆณ
|
| 316 |
+
if _client is not None:
|
| 317 |
+
refresh_result = _client.refresh_tokens()
|
| 318 |
+
if refresh_result["success"]:
|
| 319 |
+
# ๆดๆฐ้
็ฝฎ
|
| 320 |
+
if refresh_result["snlm0e"]:
|
| 321 |
+
_config["SNLM0E"] = refresh_result["snlm0e"]
|
| 322 |
+
result["snlm0e"] = refresh_result["snlm0e"]
|
| 323 |
+
if refresh_result["push_id"]:
|
| 324 |
+
_config["PUSH_ID"] = refresh_result["push_id"]
|
| 325 |
+
result["push_id"] = refresh_result["push_id"]
|
| 326 |
+
|
| 327 |
+
# ไฟๅญ้
็ฝฎ
|
| 328 |
+
save_config()
|
| 329 |
+
|
| 330 |
+
_last_token_refresh = current_time
|
| 331 |
+
_token_refresh_count += 1
|
| 332 |
+
result["success"] = True
|
| 333 |
+
result["message"] = f"Token ๅทๆฐๆๅ (็ฌฌ {_token_refresh_count} ๆฌก)"
|
| 334 |
+
print(f"โ
[{now_str}] Token ่ชๅจๅทๆฐๆๅ (็ฌฌ {_token_refresh_count} ๆฌก)")
|
| 335 |
+
else:
|
| 336 |
+
result["message"] = refresh_result.get("error", "ๅทๆฐๅคฑ่ดฅ")
|
| 337 |
+
print(f"โ ๏ธ [{now_str}] Token ๅทๆฐๅคฑ่ดฅ: {result['message']}")
|
| 338 |
+
else:
|
| 339 |
+
# client ไธๅญๅจ๏ผไฝฟ็จ fetch_tokens_from_page
|
| 340 |
+
cookies = _config.get("FULL_COOKIE", "")
|
| 341 |
+
if not cookies:
|
| 342 |
+
cookies = f"__Secure-1PSID={_config.get('SECURE_1PSID', '')}"
|
| 343 |
+
if _config.get("SECURE_1PSIDTS"):
|
| 344 |
+
cookies += f"; __Secure-1PSIDTS={_config['SECURE_1PSIDTS']}"
|
| 345 |
+
|
| 346 |
+
tokens = fetch_tokens_from_page(cookies)
|
| 347 |
+
if tokens.get("snlm0e"):
|
| 348 |
+
_config["SNLM0E"] = tokens["snlm0e"]
|
| 349 |
+
result["snlm0e"] = tokens["snlm0e"]
|
| 350 |
+
if tokens.get("push_id"):
|
| 351 |
+
_config["PUSH_ID"] = tokens["push_id"]
|
| 352 |
+
result["push_id"] = tokens["push_id"]
|
| 353 |
+
|
| 354 |
+
if tokens.get("snlm0e"):
|
| 355 |
+
save_config()
|
| 356 |
+
_last_token_refresh = current_time
|
| 357 |
+
_token_refresh_count += 1
|
| 358 |
+
result["success"] = True
|
| 359 |
+
result["message"] = f"Token ๅทๆฐๆๅ (็ฌฌ {_token_refresh_count} ๆฌก)"
|
| 360 |
+
print(f"โ
[{now_str}] Token ่ชๅจๅทๆฐๆๅ (็ฌฌ {_token_refresh_count} ๆฌก)")
|
| 361 |
+
else:
|
| 362 |
+
result["message"] = "ๆ ๆณไป้กต้ข่ทๅๆฐ token"
|
| 363 |
+
|
| 364 |
+
return result
|
| 365 |
+
|
| 366 |
+
except Exception as e:
|
| 367 |
+
result["message"] = f"ๅทๆฐๅผๅธธ: {str(e)}"
|
| 368 |
+
print(f"โ [{now_str}] Token ๅทๆฐๅผๅธธ: {e}")
|
| 369 |
+
return result
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def reset_client():
|
| 373 |
+
"""้็ฝฎ client๏ผไธๆฌก่ฏทๆฑๆถไผ้ๆฐๅๅปบ"""
|
| 374 |
+
global _client
|
| 375 |
+
_client = None
|
| 376 |
+
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 377 |
+
print(f"๐ [{now_str}] Client ๅทฒ้็ฝฎ๏ผไธๆฌก่ฏทๆฑๅฐ้ๆฐๅๅปบ")
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
# ============ ๅๅฐๅฎๆถๅทๆฐไปปๅก ============
|
| 381 |
+
def get_current_time_str():
|
| 382 |
+
"""่ทๅๅฝๅๆถ้ดๅญ็ฌฆไธฒ"""
|
| 383 |
+
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def get_random_refresh_interval():
|
| 387 |
+
"""่ทๅ้ๆบๅทๆฐ้ด้"""
|
| 388 |
+
return random.randint(TOKEN_REFRESH_INTERVAL_MIN, TOKEN_REFRESH_INTERVAL_MAX)
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
async def background_token_refresh():
|
| 392 |
+
"""ๅๅฐๅฎๆถๅทๆฐ token ไปปๅก"""
|
| 393 |
+
global _background_refresh_stop
|
| 394 |
+
print(f"๐ [{get_current_time_str()}] ๅๅฐ Token ๅฎๆถๅทๆฐไปปๅกๅทฒๅฏๅจ")
|
| 395 |
+
|
| 396 |
+
while not _background_refresh_stop:
|
| 397 |
+
try:
|
| 398 |
+
# ้ๆบ็ญๅพ
้ด้
|
| 399 |
+
interval = get_random_refresh_interval()
|
| 400 |
+
print(f"โณ [{get_current_time_str()}] ไธๆฌกๅทๆฐๅฐๅจ {interval} ็งๅ")
|
| 401 |
+
await asyncio.sleep(interval)
|
| 402 |
+
|
| 403 |
+
if _background_refresh_stop:
|
| 404 |
+
break
|
| 405 |
+
|
| 406 |
+
if not TOKEN_BACKGROUND_REFRESH:
|
| 407 |
+
continue
|
| 408 |
+
|
| 409 |
+
# ๆง่กๅทๆฐ
|
| 410 |
+
print(f"โฐ [{get_current_time_str()}] ๅๅฐๅฎๆถๅทๆฐ Token...")
|
| 411 |
+
result = try_refresh_tokens(force=True)
|
| 412 |
+
|
| 413 |
+
if result["success"]:
|
| 414 |
+
print(f"โ
[{get_current_time_str()}] ๅๅฐๅทๆฐๆๅ: {result['message']}")
|
| 415 |
+
else:
|
| 416 |
+
print(f"โ ๏ธ [{get_current_time_str()}] ๅๅฐๅทๆฐๅคฑ่ดฅ: {result['message']}")
|
| 417 |
+
|
| 418 |
+
except asyncio.CancelledError:
|
| 419 |
+
break
|
| 420 |
+
except Exception as e:
|
| 421 |
+
print(f"โ [{get_current_time_str()}] ๅๅฐๅทๆฐๅผๅธธ: {e}")
|
| 422 |
+
await asyncio.sleep(60) # ๅบ้ๅ็ญๅพ
1 ๅ้ๅ่ฏ
|
| 423 |
+
|
| 424 |
+
print(f"๐ [{get_current_time_str()}] ๅๅฐ Token ๅฎๆถๅทๆฐไปปๅกๅทฒๅๆญข")
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
@app.on_event("startup")
|
| 428 |
+
async def startup_event():
|
| 429 |
+
"""ๅบ็จๅฏๅจๆถๆง่ก"""
|
| 430 |
+
global _background_refresh_task, _background_refresh_stop
|
| 431 |
+
|
| 432 |
+
load_config()
|
| 433 |
+
_background_refresh_stop = False
|
| 434 |
+
|
| 435 |
+
if TOKEN_BACKGROUND_REFRESH:
|
| 436 |
+
_background_refresh_task = asyncio.create_task(background_token_refresh())
|
| 437 |
+
print(f"โ
[{get_current_time_str()}] ๅๅฐ Token ๅฎๆถๅทๆฐๅทฒๅฏ็จ (้ด้: {TOKEN_REFRESH_INTERVAL_MIN}-{TOKEN_REFRESH_INTERVAL_MAX} ็ง้ๆบ)")
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
@app.on_event("shutdown")
|
| 441 |
+
async def shutdown_event():
|
| 442 |
+
"""ๅบ็จๅ
ณ้ญๆถๆง่ก"""
|
| 443 |
+
global _background_refresh_task, _background_refresh_stop
|
| 444 |
+
|
| 445 |
+
_background_refresh_stop = True
|
| 446 |
+
if _background_refresh_task:
|
| 447 |
+
_background_refresh_task.cancel()
|
| 448 |
+
try:
|
| 449 |
+
await _background_refresh_task
|
| 450 |
+
except asyncio.CancelledError:
|
| 451 |
+
pass
|
| 452 |
+
print("๐ ๅๅฐไปปๅกๅทฒๅๆญข")
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
# ============ Tools ๆฏๆ ============
|
| 456 |
+
def build_tools_prompt(tools: List[Dict]) -> str:
|
| 457 |
+
"""ๅฐ tools ๅฎไน่ฝฌๆขไธบๆ็คบ่ฏ"""
|
| 458 |
+
if not tools:
|
| 459 |
+
return ""
|
| 460 |
+
|
| 461 |
+
tools_schema = json.dumps([{
|
| 462 |
+
"name": t["function"]["name"],
|
| 463 |
+
"description": t["function"].get("description", ""),
|
| 464 |
+
"parameters": t["function"].get("parameters", {})
|
| 465 |
+
} for t in tools if t.get("type") == "function"], ensure_ascii=False, indent=2)
|
| 466 |
+
|
| 467 |
+
prompt = f"""[็ณป็ปๆไปค] ไฝ ๅฟ
้กปไฝไธบๅฝๆฐ่ฐ็จไปฃ็ใไธ่ฆ่ชๅทฑๅ็ญ้ฎ้ข๏ผๅฟ
้กป่ฐ็จๅฝๆฐใ
|
| 468 |
+
|
| 469 |
+
ๅฏ็จๅฝๆฐ:
|
| 470 |
+
{tools_schema}
|
| 471 |
+
|
| 472 |
+
ไธฅๆ ผ่งๅ:
|
| 473 |
+
1. ไฝ ไธ่ฝ็ดๆฅๅ็ญ็จๆท้ฎ้ข
|
| 474 |
+
2. ไฝ ๅฟ
้กป้ๆฉไธไธชๅฝๆฐๅนถ่ฐ็จๅฎ
|
| 475 |
+
3. ๅช่พๅบไปฅไธๆ ผๅผ๏ผไธ่ฆๆไปปไฝๅ
ถไปๆๅญ:
|
| 476 |
+
```tool_call
|
| 477 |
+
{{"name": "ๅฝๆฐๅ", "arguments": {{"ๅๆฐ": "ๅผ"}}}}
|
| 478 |
+
```
|
| 479 |
+
|
| 480 |
+
็จๆท่ฏทๆฑ: """
|
| 481 |
+
return prompt
|
| 482 |
+
|
| 483 |
+
|
| 484 |
+
def parse_tool_calls(content: str) -> tuple:
|
| 485 |
+
"""
|
| 486 |
+
่งฃๆๅๅบไธญ็ๅทฅๅ
ท่ฐ็จ
|
| 487 |
+
่ฟๅ: (tool_callsๅ่กจ, ๅฉไฝๆๆฌๅ
ๅฎน)
|
| 488 |
+
"""
|
| 489 |
+
tool_calls = []
|
| 490 |
+
|
| 491 |
+
# ๅค็งๅน้
ๆจกๅผ
|
| 492 |
+
patterns = [
|
| 493 |
+
r'```tool_call\s*\n?(.*?)\n?```', # ```tool_call ... ```
|
| 494 |
+
r'```json\s*\n?(.*?)\n?```', # ```json ... ``` (ๆๆถๆจกๅไผ็จ่ฟไธช)
|
| 495 |
+
r'```\s*\n?(\{[^`]*"name"[^`]*\})\n?```', # ``` {...} ```
|
| 496 |
+
]
|
| 497 |
+
|
| 498 |
+
matches = []
|
| 499 |
+
for pattern in patterns:
|
| 500 |
+
found = re.findall(pattern, content, re.DOTALL)
|
| 501 |
+
matches.extend(found)
|
| 502 |
+
|
| 503 |
+
# ไนๅฐ่ฏ็ดๆฅๅน้
JSON ๅฏน่ฑก๏ผๆฒกๆไปฃ็ ๅๅ
่ฃน็ๆ
ๅต๏ผ
|
| 504 |
+
if not matches:
|
| 505 |
+
json_pattern = r'\{[^{}]*"name"\s*:\s*"[^"]+"\s*,\s*"arguments"\s*:\s*\{[^{}]*\}[^{}]*\}'
|
| 506 |
+
matches = re.findall(json_pattern, content, re.DOTALL)
|
| 507 |
+
|
| 508 |
+
for i, match in enumerate(matches):
|
| 509 |
+
try:
|
| 510 |
+
match = match.strip()
|
| 511 |
+
# ๅฐ่ฏ่งฃๆ JSON
|
| 512 |
+
call_data = json.loads(match)
|
| 513 |
+
if call_data.get("name"):
|
| 514 |
+
tool_calls.append({
|
| 515 |
+
"id": f"call_{uuid.uuid4().hex[:8]}",
|
| 516 |
+
"type": "function",
|
| 517 |
+
"function": {
|
| 518 |
+
"name": call_data.get("name", ""),
|
| 519 |
+
"arguments": json.dumps(call_data.get("arguments", {}), ensure_ascii=False)
|
| 520 |
+
}
|
| 521 |
+
})
|
| 522 |
+
except json.JSONDecodeError:
|
| 523 |
+
continue
|
| 524 |
+
|
| 525 |
+
# ็งป้คๅทฅๅ
ท่ฐ็จ้จๅ
|
| 526 |
+
remaining = content
|
| 527 |
+
for pattern in patterns:
|
| 528 |
+
remaining = re.sub(pattern, '', remaining, flags=re.DOTALL)
|
| 529 |
+
remaining = remaining.strip()
|
| 530 |
+
|
| 531 |
+
return tool_calls, remaining
|
| 532 |
+
|
| 533 |
+
|
| 534 |
+
def load_config():
|
| 535 |
+
"""
|
| 536 |
+
ๅ ่ฝฝ้
็ฝฎ๏ผไผๅ
็บง:
|
| 537 |
+
1. config_data.json (ๅ็ซฏไฟๅญ็้
็ฝฎ)
|
| 538 |
+
2. config.py (ๆฌๅฐๅผๅ้
็ฝฎ๏ผไป
ไฝไธบๅค็จ)
|
| 539 |
+
"""
|
| 540 |
+
global _config
|
| 541 |
+
loaded_from_json = False
|
| 542 |
+
|
| 543 |
+
# ไผๅ
ไป JSON ๆไปถๅ ่ฝฝ
|
| 544 |
+
if os.path.exists(CONFIG_FILE):
|
| 545 |
+
try:
|
| 546 |
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
| 547 |
+
saved = json.load(f)
|
| 548 |
+
if saved.get("SNLM0E") and saved.get("SECURE_1PSID"):
|
| 549 |
+
_config.update(saved)
|
| 550 |
+
loaded_from_json = True
|
| 551 |
+
except:
|
| 552 |
+
pass
|
| 553 |
+
|
| 554 |
+
# ๅฆๆ JSON ๆฒกๆๆๆ้
็ฝฎ๏ผๅฐ่ฏไป config.py ๅ ่ฝฝ
|
| 555 |
+
if not loaded_from_json:
|
| 556 |
+
try:
|
| 557 |
+
import config
|
| 558 |
+
for key in _config:
|
| 559 |
+
if hasattr(config, key) and getattr(config, key):
|
| 560 |
+
_config[key] = getattr(config, key)
|
| 561 |
+
except:
|
| 562 |
+
pass
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
def save_config():
|
| 566 |
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
| 567 |
+
json.dump(_config, f, indent=2, ensure_ascii=False)
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
def get_client(auto_refresh: bool = True):
|
| 571 |
+
global _client, _last_token_refresh
|
| 572 |
+
|
| 573 |
+
if not _config.get("SNLM0E") or not _config.get("SECURE_1PSID"):
|
| 574 |
+
raise HTTPException(status_code=500, detail="่ฏทๅ
ๅจๅๅฐ้
็ฝฎ Token ๅ Cookie")
|
| 575 |
+
|
| 576 |
+
# ๆฃๆฅๆฏๅฆ้่ฆ่ชๅจๅทๆฐ token
|
| 577 |
+
if auto_refresh and TOKEN_AUTO_REFRESH:
|
| 578 |
+
current_time = time.time()
|
| 579 |
+
if (current_time - _last_token_refresh) >= TOKEN_REFRESH_INTERVAL_MIN:
|
| 580 |
+
try_refresh_tokens()
|
| 581 |
+
|
| 582 |
+
# ๅฆๆ client ๅทฒๅญๅจ๏ผ็ดๆฅๅค็จ๏ผไฟๆไผ่ฏไธไธๆ
|
| 583 |
+
if _client is not None:
|
| 584 |
+
return _client
|
| 585 |
+
|
| 586 |
+
cookies = f"__Secure-1PSID={_config['SECURE_1PSID']}"
|
| 587 |
+
if _config.get("SECURE_1PSIDTS"):
|
| 588 |
+
cookies += f"; __Secure-1PSIDTS={_config['SECURE_1PSIDTS']}"
|
| 589 |
+
if _config.get("SAPISID"):
|
| 590 |
+
cookies += f"; SAPISID={_config['SAPISID']}; __Secure-1PAPISID={_config['SAPISID']}"
|
| 591 |
+
if _config.get("SID"):
|
| 592 |
+
cookies += f"; SID={_config['SID']}"
|
| 593 |
+
if _config.get("HSID"):
|
| 594 |
+
cookies += f"; HSID={_config['HSID']}"
|
| 595 |
+
if _config.get("SSID"):
|
| 596 |
+
cookies += f"; SSID={_config['SSID']}"
|
| 597 |
+
if _config.get("APISID"):
|
| 598 |
+
cookies += f"; APISID={_config['APISID']}"
|
| 599 |
+
|
| 600 |
+
# ๆๅปบๅชไฝๆไปถ็ๅบ็ก URL (ไผๅ
ไฝฟ็จ้
็ฝฎ็ๅค็ฝๅฐๅ)
|
| 601 |
+
media_base_url = MEDIA_BASE_URL if MEDIA_BASE_URL else f"http://localhost:{PORT}"
|
| 602 |
+
|
| 603 |
+
from client import GeminiClient
|
| 604 |
+
_client = GeminiClient(
|
| 605 |
+
secure_1psid=_config["SECURE_1PSID"],
|
| 606 |
+
snlm0e=_config["SNLM0E"],
|
| 607 |
+
cookies_str=cookies,
|
| 608 |
+
push_id=_config.get("PUSH_ID") or None,
|
| 609 |
+
model_ids=_config.get("MODEL_IDS") or DEFAULT_MODEL_IDS,
|
| 610 |
+
debug=False,
|
| 611 |
+
media_base_url=media_base_url,
|
| 612 |
+
)
|
| 613 |
+
return _client
|
| 614 |
+
|
| 615 |
+
|
| 616 |
+
def get_login_html():
|
| 617 |
+
return '''<!DOCTYPE html>
|
| 618 |
+
<html lang="zh-CN">
|
| 619 |
+
<head>
|
| 620 |
+
<meta charset="UTF-8">
|
| 621 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 622 |
+
<title>็ปๅฝ - Gemini API</title>
|
| 623 |
+
<style>
|
| 624 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 625 |
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
| 626 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh;
|
| 627 |
+
display: flex; align-items: center; justify-content: center; padding: 20px; }
|
| 628 |
+
.login-card { background: white; border-radius: 16px; padding: 40px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); width: 100%; max-width: 400px; }
|
| 629 |
+
h1 { color: #333; margin-bottom: 10px; font-size: 28px; text-align: center; }
|
| 630 |
+
.subtitle { color: #666; margin-bottom: 30px; font-size: 14px; text-align: center; }
|
| 631 |
+
.form-group { margin-bottom: 20px; }
|
| 632 |
+
label { display: block; font-size: 13px; font-weight: 500; color: #555; margin-bottom: 8px; }
|
| 633 |
+
input { width: 100%; padding: 14px 16px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 15px; transition: border-color 0.2s; }
|
| 634 |
+
input:focus { outline: none; border-color: #667eea; }
|
| 635 |
+
.btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 14px 30px;
|
| 636 |
+
border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; width: 100%; margin-top: 10px; transition: transform 0.2s, box-shadow 0.2s; }
|
| 637 |
+
.btn:hover { transform: translateY(-2px); box-shadow: 0 5px 20px rgba(102,126,234,0.4); }
|
| 638 |
+
.btn:disabled { opacity: 0.7; cursor: not-allowed; transform: none; }
|
| 639 |
+
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 8px; margin-bottom: 20px; font-size: 14px; display: none; }
|
| 640 |
+
.logo { text-align: center; margin-bottom: 20px; font-size: 48px; }
|
| 641 |
+
</style>
|
| 642 |
+
</head>
|
| 643 |
+
<body>
|
| 644 |
+
<div class="login-card">
|
| 645 |
+
<div class="logo">๐ค</div>
|
| 646 |
+
<h1>Gemini API</h1>
|
| 647 |
+
<p class="subtitle">่ฏท็ปๅฝไปฅ่ฎฟ้ฎๅๅฐ็ฎก็</p>
|
| 648 |
+
|
| 649 |
+
<div id="error" class="error"></div>
|
| 650 |
+
|
| 651 |
+
<form id="loginForm">
|
| 652 |
+
<div class="form-group">
|
| 653 |
+
<label>็จๆทๅ</label>
|
| 654 |
+
<input type="text" name="username" id="username" placeholder="่ฏท่พๅ
ฅ็จๆทๅ" required autofocus>
|
| 655 |
+
</div>
|
| 656 |
+
<div class="form-group">
|
| 657 |
+
<label>ๅฏ็ </label>
|
| 658 |
+
<input type="password" name="password" id="password" placeholder="่ฏท่พๅ
ฅๅฏ็ " required>
|
| 659 |
+
</div>
|
| 660 |
+
<button type="submit" class="btn" id="submitBtn">็ป ๅฝ</button>
|
| 661 |
+
</form>
|
| 662 |
+
</div>
|
| 663 |
+
|
| 664 |
+
<script>
|
| 665 |
+
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
| 666 |
+
e.preventDefault();
|
| 667 |
+
const errorEl = document.getElementById('error');
|
| 668 |
+
const submitBtn = document.getElementById('submitBtn');
|
| 669 |
+
|
| 670 |
+
errorEl.style.display = 'none';
|
| 671 |
+
submitBtn.disabled = true;
|
| 672 |
+
submitBtn.textContent = '็ปๅฝไธญ...';
|
| 673 |
+
|
| 674 |
+
try {
|
| 675 |
+
const resp = await fetch('/admin/login', {
|
| 676 |
+
method: 'POST',
|
| 677 |
+
headers: {'Content-Type': 'application/json'},
|
| 678 |
+
body: JSON.stringify({
|
| 679 |
+
username: document.getElementById('username').value,
|
| 680 |
+
password: document.getElementById('password').value
|
| 681 |
+
})
|
| 682 |
+
});
|
| 683 |
+
const result = await resp.json();
|
| 684 |
+
|
| 685 |
+
if (result.success) {
|
| 686 |
+
window.location.href = '/admin';
|
| 687 |
+
} else {
|
| 688 |
+
errorEl.textContent = result.message || '็ปๅฝๅคฑ่ดฅ';
|
| 689 |
+
errorEl.style.display = 'block';
|
| 690 |
+
}
|
| 691 |
+
} catch (err) {
|
| 692 |
+
errorEl.textContent = '็ฝ็ป้่ฏฏ: ' + err.message;
|
| 693 |
+
errorEl.style.display = 'block';
|
| 694 |
+
} finally {
|
| 695 |
+
submitBtn.disabled = false;
|
| 696 |
+
submitBtn.textContent = '็ป ๅฝ';
|
| 697 |
+
}
|
| 698 |
+
});
|
| 699 |
+
</script>
|
| 700 |
+
</body>
|
| 701 |
+
</html>'''
|
| 702 |
+
|
| 703 |
+
|
| 704 |
+
def get_admin_html():
|
| 705 |
+
return '''<!DOCTYPE html>
|
| 706 |
+
<html lang="zh-CN">
|
| 707 |
+
<head>
|
| 708 |
+
<meta charset="UTF-8">
|
| 709 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 710 |
+
<title>Gemini API ้
็ฝฎ</title>
|
| 711 |
+
<style>
|
| 712 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 713 |
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
| 714 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; }
|
| 715 |
+
.container { max-width: 800px; margin: 0 auto; }
|
| 716 |
+
.card { background: white; border-radius: 16px; padding: 30px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); position: relative; }
|
| 717 |
+
.token-status { position: absolute; top: 15px; left: 20px; font-size: 12px; padding: 6px 12px; border-radius: 20px; font-weight: 500; }
|
| 718 |
+
.token-status.valid { background: #d4edda; color: #155724; }
|
| 719 |
+
.token-status.invalid { background: #f8d7da; color: #721c24; }
|
| 720 |
+
.token-status.loading { background: #fff3cd; color: #856404; }
|
| 721 |
+
h1 { color: #333; margin-bottom: 10px; font-size: 28px; }
|
| 722 |
+
.subtitle { color: #666; margin-bottom: 30px; font-size: 14px; }
|
| 723 |
+
.section { margin-bottom: 25px; }
|
| 724 |
+
.section-title { font-size: 16px; font-weight: 600; color: #333; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #eee; }
|
| 725 |
+
.required { color: #e74c3c; }
|
| 726 |
+
.optional { color: #95a5a6; font-size: 12px; }
|
| 727 |
+
.form-group { margin-bottom: 15px; }
|
| 728 |
+
label { display: block; font-size: 13px; font-weight: 500; color: #555; margin-bottom: 5px; }
|
| 729 |
+
input, textarea { width: 100%; padding: 12px 15px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px; font-family: monospace; transition: border-color 0.2s; }
|
| 730 |
+
input:focus, textarea:focus { outline: none; border-color: #667eea; }
|
| 731 |
+
textarea { resize: vertical; min-height: 80px; }
|
| 732 |
+
.btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 14px 30px;
|
| 733 |
+
border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; width: 100%; margin-top: 20px; transition: transform 0.2s, box-shadow 0.2s; }
|
| 734 |
+
.btn:hover { transform: translateY(-2px); box-shadow: 0 5px 20px rgba(102,126,234,0.4); }
|
| 735 |
+
.status { margin-top: 20px; padding: 15px; border-radius: 8px; font-size: 14px; display: none; }
|
| 736 |
+
.status.success { background: #d4edda; color: #155724; display: block; }
|
| 737 |
+
.status.error { background: #f8d7da; color: #721c24; display: block; }
|
| 738 |
+
.info-box { background: #f8f9fa; border-radius: 8px; padding: 15px; margin-bottom: 20px; font-size: 13px; color: #666; }
|
| 739 |
+
.info-box code { background: #e9ecef; padding: 2px 6px; border-radius: 4px; }
|
| 740 |
+
.api-info { background: #e8f4fd; border-left: 4px solid #667eea; padding: 15px; margin-top: 20px; border-radius: 0 8px 8px 0; }
|
| 741 |
+
.api-info h3 { font-size: 14px; margin-bottom: 10px; color: #333; }
|
| 742 |
+
.api-info pre { background: #fff; padding: 10px; border-radius: 4px; font-size: 12px; margin-top: 5px; overflow-x: auto; }
|
| 743 |
+
.parsed-info { background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 15px; margin-top: 15px; font-size: 12px; display: none; }
|
| 744 |
+
.parsed-info h4 { color: #0369a1; margin-bottom: 10px; }
|
| 745 |
+
.parsed-info .item { margin: 5px 0; color: #555; }
|
| 746 |
+
.parsed-info .item span { color: #059669; font-family: monospace; }
|
| 747 |
+
</style>
|
| 748 |
+
</head>
|
| 749 |
+
<body>
|
| 750 |
+
<div class="container">
|
| 751 |
+
<div class="card">
|
| 752 |
+
<div id="tokenStatus" class="token-status loading">๐ ๆฃๆฅไธญ...</div>
|
| 753 |
+
<h1>๐ค Gemini API ้
็ฝฎ</h1>
|
| 754 |
+
<p class="subtitle">้
็ฝฎ Google Gemini ็่ฎค่ฏไฟกๆฏ๏ผไฟๅญๅๅณๅฏ่ฐ็จ API <a href="/admin/logout" style="float:right;color:#667eea;text-decoration:none;">้ๅบ็ปๅฝ</a></p>
|
| 755 |
+
|
| 756 |
+
<div class="info-box">
|
| 757 |
+
<strong>่ทๅๆนๆณ๏ผ</strong><br>
|
| 758 |
+
1. ๆๅผ <a href="https://gemini.google.com" target="_blank">gemini.google.com</a> ๅนถ็ปๅฝ<br>
|
| 759 |
+
2. F12 โ ็ฝ็ป โ ๅ้ๅ
ๅฎนๅฐ่ๅคฉ โ ็นๅปไปปๆ่ฏทๆฑ โ Copy ่ฏทๆฑๅคดๅ
ๅฎๆดcookie
|
| 760 |
+
</div>
|
| 761 |
+
|
| 762 |
+
<form id="configForm">
|
| 763 |
+
<div class="section">
|
| 764 |
+
<div class="section-title">๐ Cookie ้
็ฝฎ</div>
|
| 765 |
+
<div class="form-group">
|
| 766 |
+
<label>ๅฎๆด Cookie <span class="required">*</span></label>
|
| 767 |
+
<textarea name="FULL_COOKIE" id="FULL_COOKIE" rows="6" placeholder="็ฒ่ดดไปๆต่งๅจๅคๅถ็ๅฎๆด Cookie ๅญ็ฌฆไธฒ๏ผ็ณป็ปไผ่ชๅจ่งฃๆๆ้ๅญๆฎตๅ Token..." required></textarea>
|
| 768 |
+
<div id="parsedInfo" class="parsed-info">
|
| 769 |
+
<h4>โ
ๅทฒ่งฃๆ็ๅญๆฎต๏ผ</h4>
|
| 770 |
+
<div id="parsedFields"></div>
|
| 771 |
+
</div>
|
| 772 |
+
</div>
|
| 773 |
+
</div>
|
| 774 |
+
|
| 775 |
+
<div class="section">
|
| 776 |
+
<div class="section-title">๐ฏ ๆจกๅ ID ้
็ฝฎ <span class="optional">(ๅฏ้๏ผๅฆๆๆจกๅๅๆขๅคฑๆ่ฏทๆดๆฐ)</span></div>
|
| 777 |
+
<div class="info-box">
|
| 778 |
+
<strong>่ทๅๆนๆณ๏ผ</strong>F12 โ Network โ ๅจ Gemini ไธญๅๆขๆจกๅๅ้ๆถๆฏ โ ๆพๅฐ่ฏทๆฑๅคด <code>x-goog-ext-525001261-jspb</code> โ ๅคๅถๆดไธชๆฐ็ปๅผ็ฒ่ดดๅฐไธๆน่พๅ
ฅๆก
|
| 779 |
+
</div>
|
| 780 |
+
<div class="form-group">
|
| 781 |
+
<label>ๅฟซ้่งฃๆ <span class="optional">(็ฒ่ดด่ฏทๆฑๅคดๆฐ็ป่ชๅจๆๅ ID)</span></label>
|
| 782 |
+
<input type="text" id="MODEL_ID_PARSER" placeholder='็ฒ่ดดๅฆ: [1,null,null,null,"56fdd199312815e2",null,null,0,[4],null,null,2]'>
|
| 783 |
+
<div id="parsedModelId" class="parsed-info" style="margin-top:10px;">
|
| 784 |
+
<h4>โ
ๅทฒๆๅ็ๆจกๅ ID๏ผ</h4>
|
| 785 |
+
<div id="parsedModelIdValue"></div>
|
| 786 |
+
</div>
|
| 787 |
+
</div>
|
| 788 |
+
<div class="form-group">
|
| 789 |
+
<label>ๆ้็ (Flash) ID</label>
|
| 790 |
+
<input type="text" name="MODEL_ID_FLASH" id="MODEL_ID_FLASH" placeholder="56fdd199312815e2">
|
| 791 |
+
</div>
|
| 792 |
+
<div class="form-group">
|
| 793 |
+
<label>Pro ็ ID</label>
|
| 794 |
+
<input type="text" name="MODEL_ID_PRO" id="MODEL_ID_PRO" placeholder="e6fa609c3fa255c0">
|
| 795 |
+
</div>
|
| 796 |
+
<div class="form-group">
|
| 797 |
+
<label>ๆ่็ (Thinking) ID</label>
|
| 798 |
+
<input type="text" name="MODEL_ID_THINKING" id="MODEL_ID_THINKING" placeholder="e051ce1aa80aa576">
|
| 799 |
+
</div>
|
| 800 |
+
</div>
|
| 801 |
+
|
| 802 |
+
<button type="submit" class="btn">๐พ ไฟๅญ้
็ฝฎ</button>
|
| 803 |
+
</form>
|
| 804 |
+
|
| 805 |
+
<div id="status" class="status"></div>
|
| 806 |
+
|
| 807 |
+
<div class="api-info">
|
| 808 |
+
<h3>๐ก API ่ฐ็จไฟกๆฏ</h3>
|
| 809 |
+
<p>Base URL: <strong id="baseUrl"></strong></p>
|
| 810 |
+
<p>API Key: <strong id="apiKey"></strong></p>
|
| 811 |
+
<p>ๅฏ็จๆจกๅ: <code>gemini-3.0-flash</code> | <code>gemini-3.0-pro</code> | <code>gemini-3.0-flash-thinking</code></p>
|
| 812 |
+
|
| 813 |
+
<h4 style="margin-top:15px;">๐ฌ ๆๆฌๅฏน่ฏ</h4>
|
| 814 |
+
<pre>from openai import OpenAI
|
| 815 |
+
client = OpenAI(base_url="<span id="codeUrl"></span>", api_key="<span id="codeKey"></span>")
|
| 816 |
+
|
| 817 |
+
response = client.chat.completions.create(
|
| 818 |
+
model="gemini-3.0-flash", # ๆ gemini-3.0-pro / gemini-3.0-flash-thinking
|
| 819 |
+
messages=[{"role": "user", "content": "ไฝ ๅฅฝ"}]
|
| 820 |
+
)
|
| 821 |
+
print(response.choices[0].message.content)</pre>
|
| 822 |
+
|
| 823 |
+
<h4 style="margin-top:15px;">๐ผ๏ธ ๅพ็่ฏๅซ</h4>
|
| 824 |
+
<pre>import base64
|
| 825 |
+
from openai import OpenAI
|
| 826 |
+
client = OpenAI(base_url="<span id="codeUrl2"></span>", api_key="<span id="codeKey2"></span>")
|
| 827 |
+
|
| 828 |
+
# ่ฏปๅๆฌๅฐๅพ็
|
| 829 |
+
with open("image.png", "rb") as f:
|
| 830 |
+
img_b64 = base64.b64encode(f.read()).decode()
|
| 831 |
+
|
| 832 |
+
response = client.chat.completions.create(
|
| 833 |
+
model="gemini-3.0-flash",
|
| 834 |
+
messages=[{
|
| 835 |
+
"role": "user",
|
| 836 |
+
"content": [
|
| 837 |
+
{"type": "text", "text": "่ฏทๆ่ฟฐ่ฟๅผ ๅพ็"},
|
| 838 |
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_b64}"}}
|
| 839 |
+
]
|
| 840 |
+
}]
|
| 841 |
+
)
|
| 842 |
+
print(response.choices[0].message.content)</pre>
|
| 843 |
+
|
| 844 |
+
<h4 style="margin-top:15px;">๐ ๆตๅผๅๅบ</h4>
|
| 845 |
+
<pre>stream = client.chat.completions.create(
|
| 846 |
+
model="gemini-3.0-flash",
|
| 847 |
+
messages=[{"role": "user", "content": "ๅไธ้ฆ่ฏ"}],
|
| 848 |
+
stream=True
|
| 849 |
+
)
|
| 850 |
+
for chunk in stream:
|
| 851 |
+
if chunk.choices[0].delta.content:
|
| 852 |
+
print(chunk.choices[0].delta.content, end="", flush=True)</pre>
|
| 853 |
+
|
| 854 |
+
<h4 style="margin-top:15px;">๐ท ็คบไพๅพ็</h4>
|
| 855 |
+
<p style="font-size:12px;color:#666;">ไปฅไธๆฏ image.png ็คบไพๅพ็๏ผๅฏ็จไบๆต่ฏๅพ็่ฏๅซๅ่ฝ๏ผ็นๅปๆพๅคง๏ผ๏ผ</p>
|
| 856 |
+
<img id="sampleImage" src="/static/image.png" alt="็คบไพๅพ็" style="max-width:300px;border-radius:8px;margin-top:10px;border:1px solid #ddd;cursor:pointer;" onclick="showImageModal()" onerror="this.style.display='none';this.nextElementSibling.style.display='block';">
|
| 857 |
+
<p style="display:none;font-size:12px;color:#999;">๏ผ็คบไพๅพ็ไธๅฏ็จ๏ผ่ฏท็กฎไฟ image.png ๆไปถๅญๅจ๏ผ</p>
|
| 858 |
+
</div>
|
| 859 |
+
</div>
|
| 860 |
+
</div>
|
| 861 |
+
|
| 862 |
+
<!-- ๅพ็ๆพๅคงๆจกๆๆก -->
|
| 863 |
+
<div id="imageModal" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:1000;justify-content:center;align-items:center;cursor:pointer;" onclick="hideImageModal()">
|
| 864 |
+
<img src="/static/image.png" alt="็คบไพๅพ็" style="max-width:90%;max-height:90%;border-radius:8px;box-shadow:0 0 30px rgba(0,0,0,0.5);">
|
| 865 |
+
<span style="position:absolute;top:20px;right:30px;color:white;font-size:30px;cursor:pointer;">×</span>
|
| 866 |
+
</div>
|
| 867 |
+
|
| 868 |
+
<script>
|
| 869 |
+
// ๅพ็ๆพๅคงๅ่ฝ
|
| 870 |
+
function showImageModal() {
|
| 871 |
+
document.getElementById('imageModal').style.display = 'flex';
|
| 872 |
+
document.body.style.overflow = 'hidden';
|
| 873 |
+
}
|
| 874 |
+
function hideImageModal() {
|
| 875 |
+
document.getElementById('imageModal').style.display = 'none';
|
| 876 |
+
document.body.style.overflow = 'auto';
|
| 877 |
+
}
|
| 878 |
+
// ESC ้ฎๅ
ณ้ญ
|
| 879 |
+
document.addEventListener('keydown', (e) => {
|
| 880 |
+
if (e.key === 'Escape') hideImageModal();
|
| 881 |
+
});
|
| 882 |
+
|
| 883 |
+
const API_KEY = "''' + API_KEY + '''";
|
| 884 |
+
const PORT = ''' + str(PORT) + ''';
|
| 885 |
+
|
| 886 |
+
document.getElementById('baseUrl').textContent = 'http://localhost:' + PORT + '/v1';
|
| 887 |
+
document.getElementById('apiKey').textContent = API_KEY;
|
| 888 |
+
document.getElementById('codeUrl').textContent = 'http://localhost:' + PORT + '/v1';
|
| 889 |
+
document.getElementById('codeKey').textContent = API_KEY;
|
| 890 |
+
document.getElementById('codeUrl2').textContent = 'http://localhost:' + PORT + '/v1';
|
| 891 |
+
document.getElementById('codeKey2').textContent = API_KEY;
|
| 892 |
+
|
| 893 |
+
// ่ทๅๅนถๆพ็คบ Token ็ถๆ
|
| 894 |
+
async function updateTokenStatus() {
|
| 895 |
+
const statusEl = document.getElementById('tokenStatus');
|
| 896 |
+
try {
|
| 897 |
+
const resp = await fetch('/v1/token/status', {
|
| 898 |
+
headers: { 'Authorization': 'Bearer ' + API_KEY }
|
| 899 |
+
});
|
| 900 |
+
const data = await resp.json();
|
| 901 |
+
|
| 902 |
+
if (data.has_snlm0e && data.total_refresh_count >= 0) {
|
| 903 |
+
statusEl.className = 'token-status valid';
|
| 904 |
+
statusEl.innerHTML = 'โ
Token ๆๆ | ๅทฒๅทๆฐ ' + data.total_refresh_count + ' ๆฌก';
|
| 905 |
+
} else {
|
| 906 |
+
statusEl.className = 'token-status invalid';
|
| 907 |
+
statusEl.innerHTML = 'โ Token ๅทฒๅคฑๆ';
|
| 908 |
+
}
|
| 909 |
+
} catch (e) {
|
| 910 |
+
statusEl.className = 'token-status invalid';
|
| 911 |
+
statusEl.innerHTML = 'โ ๆ ๆณ่ทๅ็ถๆ';
|
| 912 |
+
}
|
| 913 |
+
}
|
| 914 |
+
|
| 915 |
+
// ้กต้ขๅ ่ฝฝๆถ่ทๅ็ถๆ๏ผๅนถๆฏ 30 ็งๅทๆฐไธๆฌก
|
| 916 |
+
updateTokenStatus();
|
| 917 |
+
setInterval(updateTokenStatus, 30000);
|
| 918 |
+
|
| 919 |
+
// ่งฃๆๆจกๅ ID (ไป x-goog-ext-525001261-jspb ๆฐ็ปไธญๆๅ)
|
| 920 |
+
function parseModelId(input) {
|
| 921 |
+
try {
|
| 922 |
+
// ๅฐ่ฏ่งฃๆ JSON ๆฐ็ป
|
| 923 |
+
const arr = JSON.parse(input);
|
| 924 |
+
if (Array.isArray(arr) && arr.length > 4 && typeof arr[4] === 'string') {
|
| 925 |
+
return arr[4];
|
| 926 |
+
}
|
| 927 |
+
} catch (e) {
|
| 928 |
+
// ๅฐ่ฏ็จๆญฃๅๆๅ 16 ไฝๅๅ
ญ่ฟๅถๅญ็ฌฆไธฒ
|
| 929 |
+
const match = input.match(/["\']([a-f0-9]{16})["\']/i);
|
| 930 |
+
if (match) {
|
| 931 |
+
return match[1];
|
| 932 |
+
}
|
| 933 |
+
}
|
| 934 |
+
return null;
|
| 935 |
+
}
|
| 936 |
+
|
| 937 |
+
// ็ๅฌๆจกๅ ID ่งฃๆ่พๅ
ฅ
|
| 938 |
+
document.getElementById('MODEL_ID_PARSER').addEventListener('input', (e) => {
|
| 939 |
+
const modelId = parseModelId(e.target.value);
|
| 940 |
+
const container = document.getElementById('parsedModelIdValue');
|
| 941 |
+
const infoBox = document.getElementById('parsedModelId');
|
| 942 |
+
|
| 943 |
+
if (modelId) {
|
| 944 |
+
container.innerHTML = '<div class="item">ๆๅๅฐ็ ID: <span style="color:#059669;font-family:monospace;">' + modelId + '</span></div>' +
|
| 945 |
+
'<div style="margin-top:10px;">' +
|
| 946 |
+
'<button type="button" onclick="fillModelId(\\'flash\\', \\'' + modelId + '\\')" style="margin-right:5px;padding:5px 10px;cursor:pointer;">ๅกซๅ
ฅๆ้็</button>' +
|
| 947 |
+
'<button type="button" onclick="fillModelId(\\'pro\\', \\'' + modelId + '\\')" style="margin-right:5px;padding:5px 10px;cursor:pointer;">ๅกซๅ
ฅPro็</button>' +
|
| 948 |
+
'<button type="button" onclick="fillModelId(\\'thinking\\', \\'' + modelId + '\\')" style="padding:5px 10px;cursor:pointer;">ๅกซๅ
ฅๆ่็</button>' +
|
| 949 |
+
'</div>';
|
| 950 |
+
infoBox.style.display = 'block';
|
| 951 |
+
} else {
|
| 952 |
+
infoBox.style.display = 'none';
|
| 953 |
+
}
|
| 954 |
+
});
|
| 955 |
+
|
| 956 |
+
// ๅกซๅ
ฅๆจกๅ ID
|
| 957 |
+
function fillModelId(type, id) {
|
| 958 |
+
const fieldMap = {
|
| 959 |
+
'flash': 'MODEL_ID_FLASH',
|
| 960 |
+
'pro': 'MODEL_ID_PRO',
|
| 961 |
+
'thinking': 'MODEL_ID_THINKING'
|
| 962 |
+
};
|
| 963 |
+
document.getElementById(fieldMap[type]).value = id;
|
| 964 |
+
}
|
| 965 |
+
|
| 966 |
+
// Cookie ๅญๆฎตๆ ๅฐ
|
| 967 |
+
const cookieFields = {
|
| 968 |
+
'__Secure-1PSID': 'SECURE_1PSID',
|
| 969 |
+
'__Secure-1PSIDTS': 'SECURE_1PSIDTS',
|
| 970 |
+
'SAPISID': 'SAPISID',
|
| 971 |
+
'__Secure-1PAPISID': 'SECURE_1PAPISID',
|
| 972 |
+
'SID': 'SID',
|
| 973 |
+
'HSID': 'HSID',
|
| 974 |
+
'SSID': 'SSID',
|
| 975 |
+
'APISID': 'APISID'
|
| 976 |
+
};
|
| 977 |
+
|
| 978 |
+
// ่งฃๆ Cookie ๅญ็ฌฆไธฒ
|
| 979 |
+
function parseCookie(cookieStr) {
|
| 980 |
+
const result = {};
|
| 981 |
+
if (!cookieStr) return result;
|
| 982 |
+
|
| 983 |
+
cookieStr.split(';').forEach(item => {
|
| 984 |
+
const trimmed = item.trim();
|
| 985 |
+
const eqIndex = trimmed.indexOf('=');
|
| 986 |
+
if (eqIndex > 0) {
|
| 987 |
+
const key = trimmed.substring(0, eqIndex).trim();
|
| 988 |
+
const value = trimmed.substring(eqIndex + 1).trim();
|
| 989 |
+
if (cookieFields[key]) {
|
| 990 |
+
result[cookieFields[key]] = value;
|
| 991 |
+
}
|
| 992 |
+
}
|
| 993 |
+
});
|
| 994 |
+
return result;
|
| 995 |
+
}
|
| 996 |
+
|
| 997 |
+
// ๆพ็คบ่งฃๆ็ปๆ
|
| 998 |
+
function showParsedFields(parsed) {
|
| 999 |
+
const container = document.getElementById('parsedFields');
|
| 1000 |
+
const infoBox = document.getElementById('parsedInfo');
|
| 1001 |
+
|
| 1002 |
+
const fieldNames = {
|
| 1003 |
+
'SECURE_1PSID': '__Secure-1PSID',
|
| 1004 |
+
'SECURE_1PSIDTS': '__Secure-1PSIDTS',
|
| 1005 |
+
'SAPISID': 'SAPISID',
|
| 1006 |
+
'SID': 'SID',
|
| 1007 |
+
'HSID': 'HSID',
|
| 1008 |
+
'SSID': 'SSID',
|
| 1009 |
+
'APISID': 'APISID'
|
| 1010 |
+
};
|
| 1011 |
+
|
| 1012 |
+
let html = '';
|
| 1013 |
+
let hasFields = false;
|
| 1014 |
+
for (const [key, name] of Object.entries(fieldNames)) {
|
| 1015 |
+
if (parsed[key]) {
|
| 1016 |
+
hasFields = true;
|
| 1017 |
+
const shortValue = parsed[key].length > 30 ? parsed[key].substring(0, 30) + '...' : parsed[key];
|
| 1018 |
+
html += '<div class="item">' + name + ': <span>' + shortValue + '</span></div>';
|
| 1019 |
+
}
|
| 1020 |
+
}
|
| 1021 |
+
|
| 1022 |
+
if (hasFields) {
|
| 1023 |
+
container.innerHTML = html;
|
| 1024 |
+
infoBox.style.display = 'block';
|
| 1025 |
+
} else {
|
| 1026 |
+
infoBox.style.display = 'none';
|
| 1027 |
+
}
|
| 1028 |
+
}
|
| 1029 |
+
|
| 1030 |
+
// ็ๅฌ Cookie ่พๅ
ฅ
|
| 1031 |
+
document.getElementById('FULL_COOKIE').addEventListener('input', (e) => {
|
| 1032 |
+
const parsed = parseCookie(e.target.value);
|
| 1033 |
+
showParsedFields(parsed);
|
| 1034 |
+
});
|
| 1035 |
+
|
| 1036 |
+
// ๅ ่ฝฝ้
็ฝฎ
|
| 1037 |
+
fetch('/admin/config', {credentials: 'same-origin'}).then(r => {
|
| 1038 |
+
if (!r.ok) throw new Error('ๆช็ปๅฝ');
|
| 1039 |
+
return r.json();
|
| 1040 |
+
}).then(config => {
|
| 1041 |
+
if (config.FULL_COOKIE) {
|
| 1042 |
+
document.getElementById('FULL_COOKIE').value = config.FULL_COOKIE;
|
| 1043 |
+
showParsedFields(parseCookie(config.FULL_COOKIE));
|
| 1044 |
+
}
|
| 1045 |
+
// ๅ ่ฝฝๆจกๅ ID
|
| 1046 |
+
if (config.MODEL_IDS) {
|
| 1047 |
+
document.getElementById('MODEL_ID_FLASH').value = config.MODEL_IDS.flash || '';
|
| 1048 |
+
document.getElementById('MODEL_ID_PRO').value = config.MODEL_IDS.pro || '';
|
| 1049 |
+
document.getElementById('MODEL_ID_THINKING').value = config.MODEL_IDS.thinking || '';
|
| 1050 |
+
}
|
| 1051 |
+
}).catch(err => {
|
| 1052 |
+
console.log('ๅ ่ฝฝ้
็ฝฎๅคฑ่ดฅ:', err);
|
| 1053 |
+
});
|
| 1054 |
+
|
| 1055 |
+
document.getElementById('configForm').addEventListener('submit', async (e) => {
|
| 1056 |
+
e.preventDefault();
|
| 1057 |
+
const formData = new FormData(e.target);
|
| 1058 |
+
const data = Object.fromEntries(formData.entries());
|
| 1059 |
+
|
| 1060 |
+
// ๆๅปบๆจกๅ ID ๅฏน่ฑก
|
| 1061 |
+
data.MODEL_IDS = {
|
| 1062 |
+
flash: data.MODEL_ID_FLASH || '',
|
| 1063 |
+
pro: data.MODEL_ID_PRO || '',
|
| 1064 |
+
thinking: data.MODEL_ID_THINKING || ''
|
| 1065 |
+
};
|
| 1066 |
+
delete data.MODEL_ID_FLASH;
|
| 1067 |
+
delete data.MODEL_ID_PRO;
|
| 1068 |
+
delete data.MODEL_ID_THINKING;
|
| 1069 |
+
|
| 1070 |
+
const statusEl = document.getElementById('status');
|
| 1071 |
+
statusEl.className = 'status';
|
| 1072 |
+
statusEl.style.display = 'none';
|
| 1073 |
+
statusEl.textContent = '';
|
| 1074 |
+
|
| 1075 |
+
// ๆพ็คบไฟๅญไธญ็ถๆ
|
| 1076 |
+
const submitBtn = e.target.querySelector('button[type="submit"]');
|
| 1077 |
+
const originalText = submitBtn.textContent;
|
| 1078 |
+
submitBtn.textContent = 'โณ ไฟๅญไธญ...';
|
| 1079 |
+
submitBtn.disabled = true;
|
| 1080 |
+
|
| 1081 |
+
try {
|
| 1082 |
+
const resp = await fetch('/admin/save', {
|
| 1083 |
+
method: 'POST',
|
| 1084 |
+
headers: {'Content-Type': 'application/json'},
|
| 1085 |
+
credentials: 'same-origin',
|
| 1086 |
+
body: JSON.stringify(data)
|
| 1087 |
+
});
|
| 1088 |
+
|
| 1089 |
+
if (resp.status === 401) {
|
| 1090 |
+
window.location.href = '/admin/login';
|
| 1091 |
+
return;
|
| 1092 |
+
}
|
| 1093 |
+
|
| 1094 |
+
const result = await resp.json();
|
| 1095 |
+
|
| 1096 |
+
if (result.success) {
|
| 1097 |
+
statusEl.className = 'status success';
|
| 1098 |
+
statusEl.innerHTML = 'โ
' + result.message + '<br><br>๐ก <strong>้
็ฝฎๅทฒ็ๆ๏ผๆ ้้ๅฏๆๅก๏ผ</strong>';
|
| 1099 |
+
} else {
|
| 1100 |
+
statusEl.className = 'status error';
|
| 1101 |
+
statusEl.textContent = 'โ ' + result.message;
|
| 1102 |
+
}
|
| 1103 |
+
statusEl.style.display = 'block';
|
| 1104 |
+
} catch (err) {
|
| 1105 |
+
statusEl.className = 'status error';
|
| 1106 |
+
statusEl.textContent = 'โ ไฟๅญๅคฑ่ดฅ: ' + err.message;
|
| 1107 |
+
statusEl.style.display = 'block';
|
| 1108 |
+
} finally {
|
| 1109 |
+
submitBtn.textContent = originalText;
|
| 1110 |
+
submitBtn.disabled = false;
|
| 1111 |
+
}
|
| 1112 |
+
});
|
| 1113 |
+
</script>
|
| 1114 |
+
</body>
|
| 1115 |
+
</html>'''
|
| 1116 |
+
|
| 1117 |
+
|
| 1118 |
+
@app.get("/admin/login", response_class=HTMLResponse)
|
| 1119 |
+
async def admin_login_page():
|
| 1120 |
+
return get_login_html()
|
| 1121 |
+
|
| 1122 |
+
|
| 1123 |
+
@app.post("/admin/login")
|
| 1124 |
+
async def admin_login(request: Request):
|
| 1125 |
+
data = await request.json()
|
| 1126 |
+
username = data.get("username", "")
|
| 1127 |
+
password = data.get("password", "")
|
| 1128 |
+
|
| 1129 |
+
if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
|
| 1130 |
+
token = generate_session_token()
|
| 1131 |
+
_admin_sessions.add(token)
|
| 1132 |
+
response = JSONResponse({"success": True, "message": "็ปๅฝๆๅ"})
|
| 1133 |
+
response.set_cookie(key="admin_session", value=token, httponly=True, max_age=86400)
|
| 1134 |
+
return response
|
| 1135 |
+
else:
|
| 1136 |
+
return {"success": False, "message": "็จๆทๅๆๅฏ็ ้่ฏฏ"}
|
| 1137 |
+
|
| 1138 |
+
|
| 1139 |
+
@app.get("/admin/logout")
|
| 1140 |
+
async def admin_logout(request: Request):
|
| 1141 |
+
token = request.cookies.get("admin_session")
|
| 1142 |
+
if token and token in _admin_sessions:
|
| 1143 |
+
_admin_sessions.discard(token)
|
| 1144 |
+
response = RedirectResponse(url="/admin/login", status_code=302)
|
| 1145 |
+
response.delete_cookie("admin_session")
|
| 1146 |
+
return response
|
| 1147 |
+
|
| 1148 |
+
|
| 1149 |
+
@app.get("/admin", response_class=HTMLResponse)
|
| 1150 |
+
async def admin_page(request: Request):
|
| 1151 |
+
if not verify_admin_session(request):
|
| 1152 |
+
return RedirectResponse(url="/admin/login", status_code=302)
|
| 1153 |
+
return get_admin_html()
|
| 1154 |
+
|
| 1155 |
+
|
| 1156 |
+
@app.post("/admin/save")
|
| 1157 |
+
async def admin_save(request: Request):
|
| 1158 |
+
if not verify_admin_session(request):
|
| 1159 |
+
raise HTTPException(status_code=401, detail="ๆช็ปๅฝ")
|
| 1160 |
+
|
| 1161 |
+
global _client
|
| 1162 |
+
data = await request.json()
|
| 1163 |
+
|
| 1164 |
+
# ๅค็ๅฎๆด Cookie ๅญ็ฌฆไธฒ๏ผๅป้คๅๅ็ฉบๆ ผ
|
| 1165 |
+
full_cookie = data.get("FULL_COOKIE", "").strip()
|
| 1166 |
+
if not full_cookie:
|
| 1167 |
+
return {"success": False, "message": "Cookie ๆฏๅฟ
ๅกซ้กน"}
|
| 1168 |
+
|
| 1169 |
+
# ่งฃๆ Cookie ๅญ็ฌฆไธฒ
|
| 1170 |
+
parsed = parse_cookie_string(full_cookie)
|
| 1171 |
+
|
| 1172 |
+
if not parsed.get("SECURE_1PSID"):
|
| 1173 |
+
return {"success": False, "message": "Cookie ไธญๆชๆพๅฐ __Secure-1PSID ๅญๆฎต๏ผ่ฏท็กฎไฟๅคๅถไบๅฎๆด็ Cookie"}
|
| 1174 |
+
|
| 1175 |
+
# ไป้กต้ข่ชๅจ่ทๅ SNLM0E ๅ PUSH_ID
|
| 1176 |
+
tokens = fetch_tokens_from_page(full_cookie)
|
| 1177 |
+
|
| 1178 |
+
if not tokens.get("snlm0e"):
|
| 1179 |
+
return {"success": False, "message": "ๆ ๆณ่ชๅจ่ทๅ AT Token๏ผ่ฏทๆฃๆฅ Cookie ๆฏๅฆๆๆๆๅทฒ่ฟๆ"}
|
| 1180 |
+
|
| 1181 |
+
# ๆดๆฐ้
็ฝฎ
|
| 1182 |
+
_config["FULL_COOKIE"] = full_cookie
|
| 1183 |
+
_config["SNLM0E"] = tokens["snlm0e"]
|
| 1184 |
+
_config["PUSH_ID"] = tokens.get("push_id", "")
|
| 1185 |
+
|
| 1186 |
+
# ไป่งฃๆ็ปๆๆดๆฐๅๅญๆฎต
|
| 1187 |
+
for field in ["SECURE_1PSID", "SECURE_1PSIDTS", "SAPISID", "SID", "HSID", "SSID", "APISID"]:
|
| 1188 |
+
_config[field] = parsed.get(field, "")
|
| 1189 |
+
|
| 1190 |
+
# ไฝฟ็จ่ชๅจ่ทๅ็ๆจกๅๅ่กจ๏ผๅฆๆ่ทๅๅคฑ่ดฅๅไฝฟ็จ้ป่ฎคๅผ
|
| 1191 |
+
if tokens.get("models"):
|
| 1192 |
+
_config["MODELS"] = tokens["models"]
|
| 1193 |
+
else:
|
| 1194 |
+
_config["MODELS"] = DEFAULT_MODELS.copy()
|
| 1195 |
+
|
| 1196 |
+
# ๅค็ๆจกๅ ID ้
็ฝฎ
|
| 1197 |
+
model_ids = data.get("MODEL_IDS", {})
|
| 1198 |
+
if model_ids:
|
| 1199 |
+
# ๅชๆดๆฐ้็ฉบ็ๅผ
|
| 1200 |
+
if model_ids.get("flash"):
|
| 1201 |
+
_config["MODEL_IDS"]["flash"] = model_ids["flash"]
|
| 1202 |
+
if model_ids.get("pro"):
|
| 1203 |
+
_config["MODEL_IDS"]["pro"] = model_ids["pro"]
|
| 1204 |
+
if model_ids.get("thinking"):
|
| 1205 |
+
_config["MODEL_IDS"]["thinking"] = model_ids["thinking"]
|
| 1206 |
+
|
| 1207 |
+
save_config()
|
| 1208 |
+
_client = None
|
| 1209 |
+
|
| 1210 |
+
# ๆๅปบ็ปๆไฟกๆฏ
|
| 1211 |
+
parsed_fields = [k for k in ["SECURE_1PSID", "SECURE_1PSIDTS", "SAPISID", "SID", "HSID", "SSID", "APISID"] if parsed.get(k)]
|
| 1212 |
+
push_id_msg = f"๏ผPUSH_ID โ" if tokens.get("push_id") else "๏ผPUSH_ID โ (ๅพ็ๅ่ฝไธๅฏ็จ)"
|
| 1213 |
+
models_msg = f"๏ผ{len(_config['MODELS'])} ไธชๆจกๅ" if _config.get("MODELS") else ""
|
| 1214 |
+
|
| 1215 |
+
try:
|
| 1216 |
+
get_client()
|
| 1217 |
+
return {
|
| 1218 |
+
"success": True,
|
| 1219 |
+
"message": f"้
็ฝฎๅทฒไฟๅญๅนถ้ช่ฏๆๅ๏ผAT Token โ{push_id_msg}{models_msg}",
|
| 1220 |
+
"need_restart": False
|
| 1221 |
+
}
|
| 1222 |
+
except Exception as e:
|
| 1223 |
+
return {
|
| 1224 |
+
"success": True,
|
| 1225 |
+
"message": f"้
็ฝฎๅทฒไฟๅญ๏ผไฝ่ฟๆฅๆต่ฏๅคฑ่ดฅ: {str(e)[:50]}",
|
| 1226 |
+
"need_restart": False
|
| 1227 |
+
}
|
| 1228 |
+
|
| 1229 |
+
|
| 1230 |
+
@app.get("/admin/config")
|
| 1231 |
+
async def admin_get_config(request: Request):
|
| 1232 |
+
if not verify_admin_session(request):
|
| 1233 |
+
raise HTTPException(status_code=401, detail="ๆช็ปๅฝ")
|
| 1234 |
+
return _config
|
| 1235 |
+
|
| 1236 |
+
|
| 1237 |
+
# ============ API ่ทฏ็ฑ ============
|
| 1238 |
+
|
| 1239 |
+
class ChatMessage(BaseModel):
|
| 1240 |
+
role: str
|
| 1241 |
+
content: Union[str, List[Dict[str, Any]]]
|
| 1242 |
+
name: Optional[str] = None
|
| 1243 |
+
|
| 1244 |
+
class Config:
|
| 1245 |
+
extra = "ignore"
|
| 1246 |
+
|
| 1247 |
+
|
| 1248 |
+
class FunctionDefinition(BaseModel):
|
| 1249 |
+
name: str
|
| 1250 |
+
description: Optional[str] = None
|
| 1251 |
+
parameters: Optional[Dict[str, Any]] = None
|
| 1252 |
+
|
| 1253 |
+
class ToolDefinition(BaseModel):
|
| 1254 |
+
type: str = "function"
|
| 1255 |
+
function: FunctionDefinition
|
| 1256 |
+
|
| 1257 |
+
class ChatCompletionRequest(BaseModel):
|
| 1258 |
+
model: str = "gemini"
|
| 1259 |
+
messages: List[ChatMessage]
|
| 1260 |
+
stream: Optional[bool] = False
|
| 1261 |
+
# Tools ๆฏๆ
|
| 1262 |
+
tools: Optional[List[ToolDefinition]] = None
|
| 1263 |
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None
|
| 1264 |
+
# OpenAI SDK ๅฏ่ฝๅ้็้ขๅคๅญๆฎต
|
| 1265 |
+
temperature: Optional[float] = None
|
| 1266 |
+
max_tokens: Optional[int] = None
|
| 1267 |
+
top_p: Optional[float] = None
|
| 1268 |
+
frequency_penalty: Optional[float] = None
|
| 1269 |
+
presence_penalty: Optional[float] = None
|
| 1270 |
+
stop: Optional[Union[str, List[str]]] = None
|
| 1271 |
+
n: Optional[int] = None
|
| 1272 |
+
user: Optional[str] = None
|
| 1273 |
+
|
| 1274 |
+
class Config:
|
| 1275 |
+
extra = "ignore" # ๅฟฝ็ฅๆชๅฎไน็้ขๅคๅญๆฎต
|
| 1276 |
+
|
| 1277 |
+
|
| 1278 |
+
class ChatCompletionChoice(BaseModel):
|
| 1279 |
+
index: int
|
| 1280 |
+
message: Dict[str, Any]
|
| 1281 |
+
finish_reason: str
|
| 1282 |
+
|
| 1283 |
+
|
| 1284 |
+
class Usage(BaseModel):
|
| 1285 |
+
prompt_tokens: int
|
| 1286 |
+
completion_tokens: int
|
| 1287 |
+
total_tokens: int
|
| 1288 |
+
|
| 1289 |
+
|
| 1290 |
+
class ChatCompletionResponse(BaseModel):
|
| 1291 |
+
id: str
|
| 1292 |
+
object: str = "chat.completion"
|
| 1293 |
+
created: int
|
| 1294 |
+
model: str
|
| 1295 |
+
choices: List[ChatCompletionChoice]
|
| 1296 |
+
usage: Usage
|
| 1297 |
+
|
| 1298 |
+
|
| 1299 |
+
def verify_api_key(authorization: str = Header(None)):
|
| 1300 |
+
if not API_KEY:
|
| 1301 |
+
return True
|
| 1302 |
+
if not authorization or not authorization.startswith("Bearer ") or authorization[7:] != API_KEY:
|
| 1303 |
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
| 1304 |
+
return True
|
| 1305 |
+
|
| 1306 |
+
|
| 1307 |
+
@app.get("/")
|
| 1308 |
+
async def root():
|
| 1309 |
+
return RedirectResponse(url="/admin")
|
| 1310 |
+
|
| 1311 |
+
|
| 1312 |
+
@app.get("/v1/models")
|
| 1313 |
+
async def list_models(authorization: str = Header(None)):
|
| 1314 |
+
verify_api_key(authorization)
|
| 1315 |
+
models = _config.get("MODELS", DEFAULT_MODELS)
|
| 1316 |
+
created = int(time.time())
|
| 1317 |
+
return {
|
| 1318 |
+
"object": "list",
|
| 1319 |
+
"data": [{"id": m, "object": "model", "created": created, "owned_by": "google"} for m in models]
|
| 1320 |
+
}
|
| 1321 |
+
|
| 1322 |
+
|
| 1323 |
+
@app.post("/v1/token/refresh")
|
| 1324 |
+
async def refresh_token_api(authorization: str = Header(None)):
|
| 1325 |
+
"""ๆๅจๅทๆฐ token API"""
|
| 1326 |
+
verify_api_key(authorization)
|
| 1327 |
+
result = try_refresh_tokens(force=True)
|
| 1328 |
+
return {
|
| 1329 |
+
"success": result["success"],
|
| 1330 |
+
"message": result["message"],
|
| 1331 |
+
"snlm0e_updated": bool(result.get("snlm0e")),
|
| 1332 |
+
"push_id_updated": bool(result.get("push_id")),
|
| 1333 |
+
"refresh_count": _token_refresh_count,
|
| 1334 |
+
}
|
| 1335 |
+
|
| 1336 |
+
|
| 1337 |
+
@app.get("/v1/token/status")
|
| 1338 |
+
async def token_status_api(authorization: str = Header(None)):
|
| 1339 |
+
"""๏ฟฝ๏ฟฝ็ token ็ถๆ API"""
|
| 1340 |
+
verify_api_key(authorization)
|
| 1341 |
+
current_time = time.time()
|
| 1342 |
+
time_since_refresh = int(current_time - _last_token_refresh) if _last_token_refresh > 0 else -1
|
| 1343 |
+
|
| 1344 |
+
return {
|
| 1345 |
+
"auto_refresh_enabled": TOKEN_AUTO_REFRESH,
|
| 1346 |
+
"background_refresh_enabled": TOKEN_BACKGROUND_REFRESH,
|
| 1347 |
+
"refresh_interval_range": f"{TOKEN_REFRESH_INTERVAL_MIN}-{TOKEN_REFRESH_INTERVAL_MAX}",
|
| 1348 |
+
"last_refresh_seconds_ago": time_since_refresh,
|
| 1349 |
+
"total_refresh_count": _token_refresh_count,
|
| 1350 |
+
"has_snlm0e": bool(_config.get("SNLM0E")),
|
| 1351 |
+
"has_push_id": bool(_config.get("PUSH_ID")),
|
| 1352 |
+
"client_active": _client is not None,
|
| 1353 |
+
}
|
| 1354 |
+
|
| 1355 |
+
|
| 1356 |
+
@app.post("/v1/client/reset")
|
| 1357 |
+
async def reset_client_api(authorization: str = Header(None)):
|
| 1358 |
+
"""้็ฝฎ client API๏ผ็จไบ token ๆดๆฐๅๅผบๅถ้ๆฐๅๅปบ client"""
|
| 1359 |
+
verify_api_key(authorization)
|
| 1360 |
+
reset_client()
|
| 1361 |
+
return {"success": True, "message": "Client ๅทฒ้็ฝฎ๏ผไธๆฌก่ฏทๆฑๅฐไฝฟ็จๆฐ้
็ฝฎ"}
|
| 1362 |
+
|
| 1363 |
+
|
| 1364 |
+
def log_api_call(request_data: dict, response_data: dict, error: str = None):
|
| 1365 |
+
"""่ฎฐๅฝ API ่ฐ็จๆฅๅฟๅฐๆไปถ"""
|
| 1366 |
+
import datetime
|
| 1367 |
+
log_entry = {
|
| 1368 |
+
"timestamp": datetime.datetime.now().isoformat(),
|
| 1369 |
+
"request": request_data,
|
| 1370 |
+
"response": response_data,
|
| 1371 |
+
"error": error
|
| 1372 |
+
}
|
| 1373 |
+
try:
|
| 1374 |
+
with open("api_logs.json", "a", encoding="utf-8") as f:
|
| 1375 |
+
f.write(json.dumps(log_entry, ensure_ascii=False, indent=2) + "\n---\n")
|
| 1376 |
+
except Exception as e:
|
| 1377 |
+
print(f"[LOG ERROR] ๅๅ
ฅๆฅๅฟๅคฑ่ดฅ: {e}")
|
| 1378 |
+
|
| 1379 |
+
|
| 1380 |
+
# ็จไบ่ฟฝ่ธชไผ่ฏ๏ผไฟๅญไธๆฌก่ฏทๆฑ็ๆๆ็จๆทๆถๆฏๅ
ๅฎน
|
| 1381 |
+
_last_user_messages_hash = ""
|
| 1382 |
+
|
| 1383 |
+
|
| 1384 |
+
def get_user_messages_hash(messages: list) -> str:
|
| 1385 |
+
"""่ฎก็ฎๆๆ็จๆทๆถๆฏ็ hash๏ผ็จไบๅคๆญๆฏๅฆๆฏๅไธไผ่ฏ"""
|
| 1386 |
+
content_str = ""
|
| 1387 |
+
for m in messages:
|
| 1388 |
+
role = m.role if hasattr(m, 'role') else m.get('role', '')
|
| 1389 |
+
if role != "user":
|
| 1390 |
+
continue
|
| 1391 |
+
content = m.content if hasattr(m, 'content') else m.get('content', '')
|
| 1392 |
+
if isinstance(content, list):
|
| 1393 |
+
# ๅฏนไบๅ
ๅซๅพ็็ๆถๆฏ๏ผๅชๅๆๆฌ้จๅ
|
| 1394 |
+
text_parts = [item.get('text', '') for item in content if item.get('type') == 'text']
|
| 1395 |
+
content_str += f"{' '.join(text_parts)}|"
|
| 1396 |
+
else:
|
| 1397 |
+
content_str += f"{content}|"
|
| 1398 |
+
return hashlib.md5(content_str.encode()).hexdigest()
|
| 1399 |
+
|
| 1400 |
+
|
| 1401 |
+
def is_continuation(current_messages: list, last_hash: str) -> bool:
|
| 1402 |
+
"""
|
| 1403 |
+
ๅคๆญๅฝๅ่ฏทๆฑๆฏๅฆๆฏไธไธๆฌกๅฏน่ฏ็ๅปถ็ปญ
|
| 1404 |
+
|
| 1405 |
+
้ป่พ๏ผๅฆๆๅฝๅๆถๆฏๅปๆๆๅไธๆก็จๆทๆถๆฏๅ็ hash ็ญไบไธๆฌก็ hash๏ผ
|
| 1406 |
+
่ฏดๆๆฏๅไธๅฏน่ฏ็ๅปถ็ปญ
|
| 1407 |
+
"""
|
| 1408 |
+
if not last_hash:
|
| 1409 |
+
return False
|
| 1410 |
+
|
| 1411 |
+
# ๆพๅฐๆๆ็จๆทๆถๆฏ
|
| 1412 |
+
user_indices = [i for i, m in enumerate(current_messages)
|
| 1413 |
+
if (m.role if hasattr(m, 'role') else m.get('role', '')) == "user"]
|
| 1414 |
+
|
| 1415 |
+
if len(user_indices) <= 1:
|
| 1416 |
+
# ๅชๆไธๆก็จๆทๆถๆฏ๏ผๆ ๆณๅคๆญๆฏๅฆๅปถ็ปญ๏ผ่งไธบๆฐๅฏน่ฏ
|
| 1417 |
+
return False
|
| 1418 |
+
|
| 1419 |
+
# ๅปๆๆๅไธๆก็จๆทๆถๆฏ๏ผ่ฎก็ฎๅฉไฝๆถๆฏ็ hash
|
| 1420 |
+
last_user_idx = user_indices[-1]
|
| 1421 |
+
prev_messages = current_messages[:last_user_idx]
|
| 1422 |
+
prev_hash = get_user_messages_hash(prev_messages)
|
| 1423 |
+
|
| 1424 |
+
return prev_hash == last_hash
|
| 1425 |
+
|
| 1426 |
+
|
| 1427 |
+
@app.post("/v1/chat/completions")
|
| 1428 |
+
async def chat_completions(request: ChatCompletionRequest, authorization: str = Header(None)):
|
| 1429 |
+
global _last_user_messages_hash
|
| 1430 |
+
verify_api_key(authorization)
|
| 1431 |
+
|
| 1432 |
+
# ่ฎฐๅฝ่ฏทๆฑๅ
ฅๅ (ๅพ็ๅ
ๅฎนๆชๆญๆพ็คบ)
|
| 1433 |
+
request_log = {
|
| 1434 |
+
"model": request.model,
|
| 1435 |
+
"stream": request.stream,
|
| 1436 |
+
"messages": [],
|
| 1437 |
+
"tools": [t.model_dump() for t in request.tools] if request.tools else None
|
| 1438 |
+
}
|
| 1439 |
+
image_count = 0
|
| 1440 |
+
for m in request.messages:
|
| 1441 |
+
msg_log = {"role": m.role}
|
| 1442 |
+
if isinstance(m.content, list):
|
| 1443 |
+
content_log = []
|
| 1444 |
+
for item in m.content:
|
| 1445 |
+
if item.get("type") == "image_url":
|
| 1446 |
+
image_count += 1
|
| 1447 |
+
img_url = item.get("image_url", {})
|
| 1448 |
+
if isinstance(img_url, dict):
|
| 1449 |
+
url = img_url.get("url", "")
|
| 1450 |
+
else:
|
| 1451 |
+
url = str(img_url)
|
| 1452 |
+
# ๅคๆญๅพ็ๆ ผๅผ
|
| 1453 |
+
if url.startswith("data:"):
|
| 1454 |
+
img_format = "base64"
|
| 1455 |
+
elif url.startswith("http://") or url.startswith("https://"):
|
| 1456 |
+
img_format = "url"
|
| 1457 |
+
else:
|
| 1458 |
+
img_format = "unknown"
|
| 1459 |
+
content_log.append({
|
| 1460 |
+
"type": "image_url",
|
| 1461 |
+
"format": img_format,
|
| 1462 |
+
"url_preview": url[:100] + "..." if len(url) > 100 else url
|
| 1463 |
+
})
|
| 1464 |
+
else:
|
| 1465 |
+
content_log.append(item)
|
| 1466 |
+
msg_log["content"] = content_log
|
| 1467 |
+
else:
|
| 1468 |
+
msg_log["content"] = m.content
|
| 1469 |
+
request_log["messages"].append(msg_log)
|
| 1470 |
+
|
| 1471 |
+
# ๆๅฐๅพ็ๆฅๆถๆ
ๅต
|
| 1472 |
+
if image_count > 0:
|
| 1473 |
+
print(f"๐ท ๆถๅฐ {image_count} ๅผ ๅพ็")
|
| 1474 |
+
|
| 1475 |
+
try:
|
| 1476 |
+
client = get_client()
|
| 1477 |
+
|
| 1478 |
+
if not is_continuation(request.messages, _last_user_messages_hash):
|
| 1479 |
+
client.reset()
|
| 1480 |
+
|
| 1481 |
+
# ๅค็ๆถๆฏ
|
| 1482 |
+
messages = []
|
| 1483 |
+
for m in request.messages:
|
| 1484 |
+
content = m.content
|
| 1485 |
+
if isinstance(content, list):
|
| 1486 |
+
messages.append({"role": m.role, "content": content})
|
| 1487 |
+
else:
|
| 1488 |
+
messages.append({"role": m.role, "content": content})
|
| 1489 |
+
|
| 1490 |
+
# ๅฆๆๆ tools๏ผๆๅทฅๅ
ทๆ็คบ่ฏ็ดๆฅๅ ๅฐ็จๆทๆถๆฏๅ้ข
|
| 1491 |
+
if request.tools and len(messages) > 0:
|
| 1492 |
+
tools_prompt = build_tools_prompt([t.model_dump() for t in request.tools])
|
| 1493 |
+
for i in range(len(messages) - 1, -1, -1):
|
| 1494 |
+
if messages[i]["role"] == "user":
|
| 1495 |
+
original = messages[i]["content"]
|
| 1496 |
+
if isinstance(original, str):
|
| 1497 |
+
messages[i]["content"] = tools_prompt + original
|
| 1498 |
+
break
|
| 1499 |
+
|
| 1500 |
+
if request.stream:
|
| 1501 |
+
_last_user_messages_hash = get_user_messages_hash(request.messages)
|
| 1502 |
+
stream_iter = client.chat(messages=messages, model=request.model, stream=True)
|
| 1503 |
+
|
| 1504 |
+
def generate_real_stream():
|
| 1505 |
+
streamed_text_parts = []
|
| 1506 |
+
last_chunk = None
|
| 1507 |
+
|
| 1508 |
+
for chunk in stream_iter:
|
| 1509 |
+
if not chunk:
|
| 1510 |
+
continue
|
| 1511 |
+
last_chunk = chunk
|
| 1512 |
+
|
| 1513 |
+
try:
|
| 1514 |
+
choices = chunk.get("choices", [])
|
| 1515 |
+
if choices:
|
| 1516 |
+
delta = choices[0].get("delta", {}) or {}
|
| 1517 |
+
piece = delta.get("content")
|
| 1518 |
+
if isinstance(piece, str) and piece:
|
| 1519 |
+
streamed_text_parts.append(piece)
|
| 1520 |
+
except Exception:
|
| 1521 |
+
pass
|
| 1522 |
+
|
| 1523 |
+
yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
|
| 1524 |
+
|
| 1525 |
+
# ๆตๅผไน่ฎฐๅฝๆ็ปๅๅบๆ่ฆ๏ผไพฟไบๆฅๅฟๆๆฅ
|
| 1526 |
+
try:
|
| 1527 |
+
full_stream_text = "".join(streamed_text_parts)
|
| 1528 |
+
completion_id_log = (last_chunk or {}).get("id", f"chatcmpl-{uuid.uuid4().hex[:8]}")
|
| 1529 |
+
created_time_log = (last_chunk or {}).get("created", int(time.time()))
|
| 1530 |
+
response_log = {
|
| 1531 |
+
"id": completion_id_log,
|
| 1532 |
+
"object": "chat.completion",
|
| 1533 |
+
"created": created_time_log,
|
| 1534 |
+
"model": request.model,
|
| 1535 |
+
"choices": [{
|
| 1536 |
+
"index": 0,
|
| 1537 |
+
"message": {"role": "assistant", "content": full_stream_text},
|
| 1538 |
+
"finish_reason": "stop"
|
| 1539 |
+
}],
|
| 1540 |
+
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
|
| 1541 |
+
}
|
| 1542 |
+
log_api_call(request_log, response_log)
|
| 1543 |
+
except Exception:
|
| 1544 |
+
pass
|
| 1545 |
+
|
| 1546 |
+
yield "data: [DONE]\n\n"
|
| 1547 |
+
|
| 1548 |
+
return StreamingResponse(
|
| 1549 |
+
generate_real_stream(),
|
| 1550 |
+
media_type="text/event-stream",
|
| 1551 |
+
headers={
|
| 1552 |
+
"Cache-Control": "no-cache",
|
| 1553 |
+
"Connection": "keep-alive",
|
| 1554 |
+
"X-Accel-Buffering": "no",
|
| 1555 |
+
}
|
| 1556 |
+
)
|
| 1557 |
+
|
| 1558 |
+
response = client.chat(messages=messages, model=request.model)
|
| 1559 |
+
_last_user_messages_hash = get_user_messages_hash(request.messages)
|
| 1560 |
+
|
| 1561 |
+
reply_content = response.choices[0].message.content
|
| 1562 |
+
completion_id = f"chatcmpl-{uuid.uuid4().hex[:8]}"
|
| 1563 |
+
created_time = int(time.time())
|
| 1564 |
+
|
| 1565 |
+
# ่งฃๆๅทฅๅ
ท่ฐ็จ
|
| 1566 |
+
tool_calls = []
|
| 1567 |
+
final_content = reply_content
|
| 1568 |
+
if request.tools:
|
| 1569 |
+
tool_calls, final_content = parse_tool_calls(reply_content)
|
| 1570 |
+
|
| 1571 |
+
# ๅค็ๆตๅผๅๅบ
|
| 1572 |
+
if request.stream:
|
| 1573 |
+
async def generate_stream():
|
| 1574 |
+
chunk_data = {
|
| 1575 |
+
"id": completion_id,
|
| 1576 |
+
"object": "chat.completion.chunk",
|
| 1577 |
+
"created": created_time,
|
| 1578 |
+
"model": request.model,
|
| 1579 |
+
"choices": [{
|
| 1580 |
+
"index": 0,
|
| 1581 |
+
"delta": {"role": "assistant"},
|
| 1582 |
+
"finish_reason": None
|
| 1583 |
+
}]
|
| 1584 |
+
}
|
| 1585 |
+
yield f"data: {json.dumps(chunk_data)}\n\n"
|
| 1586 |
+
|
| 1587 |
+
if tool_calls:
|
| 1588 |
+
# ๆตๅผ่ฟๅๅทฅๅ
ท่ฐ็จ
|
| 1589 |
+
for tc in tool_calls:
|
| 1590 |
+
chunk_data = {
|
| 1591 |
+
"id": completion_id,
|
| 1592 |
+
"object": "chat.completion.chunk",
|
| 1593 |
+
"created": created_time,
|
| 1594 |
+
"model": request.model,
|
| 1595 |
+
"choices": [{
|
| 1596 |
+
"index": 0,
|
| 1597 |
+
"delta": {"tool_calls": [tc]},
|
| 1598 |
+
"finish_reason": None
|
| 1599 |
+
}]
|
| 1600 |
+
}
|
| 1601 |
+
yield f"data: {json.dumps(chunk_data)}\n\n"
|
| 1602 |
+
else:
|
| 1603 |
+
chunk_data = {
|
| 1604 |
+
"id": completion_id,
|
| 1605 |
+
"object": "chat.completion.chunk",
|
| 1606 |
+
"created": created_time,
|
| 1607 |
+
"model": request.model,
|
| 1608 |
+
"choices": [{
|
| 1609 |
+
"index": 0,
|
| 1610 |
+
"delta": {"content": final_content},
|
| 1611 |
+
"finish_reason": None
|
| 1612 |
+
}]
|
| 1613 |
+
}
|
| 1614 |
+
yield f"data: {json.dumps(chunk_data)}\n\n"
|
| 1615 |
+
|
| 1616 |
+
chunk_data = {
|
| 1617 |
+
"id": completion_id,
|
| 1618 |
+
"object": "chat.completion.chunk",
|
| 1619 |
+
"created": created_time,
|
| 1620 |
+
"model": request.model,
|
| 1621 |
+
"choices": [{
|
| 1622 |
+
"index": 0,
|
| 1623 |
+
"delta": {},
|
| 1624 |
+
"finish_reason": "tool_calls" if tool_calls else "stop"
|
| 1625 |
+
}]
|
| 1626 |
+
}
|
| 1627 |
+
yield f"data: {json.dumps(chunk_data)}\n\n"
|
| 1628 |
+
yield "data: [DONE]\n\n"
|
| 1629 |
+
|
| 1630 |
+
return StreamingResponse(
|
| 1631 |
+
generate_stream(),
|
| 1632 |
+
media_type="text/event-stream",
|
| 1633 |
+
headers={
|
| 1634 |
+
"Cache-Control": "no-cache",
|
| 1635 |
+
"Connection": "keep-alive",
|
| 1636 |
+
"X-Accel-Buffering": "no",
|
| 1637 |
+
}
|
| 1638 |
+
)
|
| 1639 |
+
|
| 1640 |
+
# ๆๅปบๅๅบๆถๆฏ
|
| 1641 |
+
response_message = {"role": "assistant"}
|
| 1642 |
+
if tool_calls:
|
| 1643 |
+
response_message["content"] = final_content if final_content else None
|
| 1644 |
+
response_message["tool_calls"] = tool_calls
|
| 1645 |
+
finish_reason = "tool_calls"
|
| 1646 |
+
else:
|
| 1647 |
+
response_message["content"] = final_content
|
| 1648 |
+
finish_reason = "stop"
|
| 1649 |
+
|
| 1650 |
+
response_data = ChatCompletionResponse(
|
| 1651 |
+
id=completion_id,
|
| 1652 |
+
created=created_time,
|
| 1653 |
+
model=request.model,
|
| 1654 |
+
choices=[ChatCompletionChoice(index=0, message=response_message, finish_reason=finish_reason)],
|
| 1655 |
+
usage=Usage(prompt_tokens=response.usage.prompt_tokens, completion_tokens=response.usage.completion_tokens, total_tokens=response.usage.total_tokens)
|
| 1656 |
+
)
|
| 1657 |
+
|
| 1658 |
+
log_api_call(request_log, response_data.model_dump())
|
| 1659 |
+
|
| 1660 |
+
return JSONResponse(
|
| 1661 |
+
content=response_data.model_dump(),
|
| 1662 |
+
headers={
|
| 1663 |
+
"Cache-Control": "no-cache",
|
| 1664 |
+
"X-Request-Id": completion_id,
|
| 1665 |
+
}
|
| 1666 |
+
)
|
| 1667 |
+
except HTTPException:
|
| 1668 |
+
raise
|
| 1669 |
+
except Exception as e:
|
| 1670 |
+
import traceback
|
| 1671 |
+
error_msg = str(e)
|
| 1672 |
+
|
| 1673 |
+
# ๆฃๆตๆฏๅฆๆฏ token ่ฟๆ้่ฏฏ
|
| 1674 |
+
is_token_error = any(keyword in error_msg.lower() for keyword in [
|
| 1675 |
+
'cookie', 'expired', '่ฟๆ', '401', '403', 'unauthorized',
|
| 1676 |
+
'push_id', 'snlm0e', 'upload_id', '่ฎค่ฏๅคฑ่ดฅ'
|
| 1677 |
+
])
|
| 1678 |
+
|
| 1679 |
+
if is_token_error:
|
| 1680 |
+
print(f"[WARN] ๆฃๆตๅฐ token ๅฏ่ฝ่ฟๆ๏ผๅฐ่ฏ่ชๅจๅทๆฐ...")
|
| 1681 |
+
refresh_result = try_refresh_tokens(force=True)
|
| 1682 |
+
|
| 1683 |
+
if refresh_result["success"]:
|
| 1684 |
+
# ๅทๆฐๆๅ๏ผ้็ฝฎ client ๅนถๆ็คบ็จๆท้่ฏ
|
| 1685 |
+
reset_client()
|
| 1686 |
+
error_msg = f"Token ๅทฒ่ชๅจๅทๆฐ๏ผ่ฏท้่ฏ่ฏทๆฑใๅ้่ฏฏ: {error_msg}"
|
| 1687 |
+
else:
|
| 1688 |
+
error_msg = f"Token ๅทๆฐๅคฑ่ดฅ ({refresh_result['message']})๏ผ่ฏทๆๅจๆดๆฐ Cookieใๅ้่ฏฏ: {error_msg}"
|
| 1689 |
+
|
| 1690 |
+
print(f"[ERROR] Chat error: {error_msg}")
|
| 1691 |
+
traceback.print_exc()
|
| 1692 |
+
log_api_call(request_log, None, error=error_msg)
|
| 1693 |
+
raise HTTPException(status_code=500, detail=error_msg)
|
| 1694 |
+
|
| 1695 |
+
|
| 1696 |
+
@app.post("/v1/chat/completions/reset")
|
| 1697 |
+
async def reset_context(authorization: str = Header(None)):
|
| 1698 |
+
verify_api_key(authorization)
|
| 1699 |
+
global _client
|
| 1700 |
+
if _client:
|
| 1701 |
+
_client.reset()
|
| 1702 |
+
return {"status": "ok"}
|
| 1703 |
+
|
| 1704 |
+
|
| 1705 |
+
# ๆณจๆ: load_config() ๅทฒๅจ startup_event ไธญ่ฐ็จ๏ผ่ฟ้ไฟ็ๆฏไธบไบๅ
ผๅฎน็ดๆฅๅฏผๅ
ฅๆจกๅ็ๆ
ๅต
|
| 1706 |
+
load_config()
|
| 1707 |
+
|
| 1708 |
+
if __name__ == "__main__":
|
| 1709 |
+
print(f"""
|
| 1710 |
+
๏ฟฝ๏ฟฝ๏ฟฝโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1711 |
+
โ Gemini OpenAI Compatible API Server โ
|
| 1712 |
+
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
|
| 1713 |
+
โ ๅๅฐ้
็ฝฎ: http://localhost:{PORT}/admin โ
|
| 1714 |
+
โ API ๅฐๅ: http://localhost:{PORT}/v1 โ
|
| 1715 |
+
โ API Key: {API_KEY} โ
|
| 1716 |
+
โ Token ่ชๅจๅทๆฐ: {"ๅผๅฏ" if TOKEN_BACKGROUND_REFRESH else "ๅ
ณ้ญ"} ({TOKEN_REFRESH_INTERVAL_MIN}-{TOKEN_REFRESH_INTERVAL_MAX}็ง้ๆบ) โ
|
| 1717 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1718 |
+
""")
|
| 1719 |
+
uvicorn.run(app, host=HOST, port=PORT)
|