8e commited on
Commit
2a81db1
·
1 Parent(s): c9f4d90
Files changed (11) hide show
  1. Dockerfile +33 -0
  2. README.md +1 -0
  3. app/__init__.py +12 -0
  4. app/config.py +54 -0
  5. app/docs/img.png +0 -0
  6. app/routes.py +88 -0
  7. app/utils.py +519 -0
  8. main.py +6 -0
  9. recaptcha__zh_cn.js +9 -0
  10. requirements.txt +5 -0
  11. test.py +22 -0
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用基础镜像
2
+ FROM python:3.8-slim
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 复制应用程序代码到工作目录
8
+ #COPY . .
9
+
10
+ # 添加Google的签名密钥
11
+ RUN apt update && apt install -y unzip wget gnupg && wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add -
12
+
13
+ # 添加Google Chrome的仓库
14
+ RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
15
+
16
+ # 更新软件包列表并安装Google Chrome
17
+ RUN apt-get update && apt-get install -y google-chrome-stable \
18
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
19
+
20
+ RUN wget -O chromedriver.zip "https://storage.googleapis.com/chrome-for-testing-public/126.0.6478.62/linux64/chromedriver-linux64.zip" \
21
+ && unzip chromedriver.zip -d /usr/local/bin/ \
22
+ && mv /usr/local/bin/chromedriver-linux64/chromedriver /usr/local/bin/ \
23
+ && rm -rf /usr/local/bin/chromedriver-linux64 chromedriver.zip
24
+
25
+ COPY . .
26
+ # 安装 Python 依赖项
27
+ RUN pip install --no-cache-dir -r requirements.txt
28
+
29
+ # 暴露端口
30
+ EXPOSE 3000
31
+
32
+ # 启动应用程序
33
+ ENTRYPOINT ["python", "./main.py"]
README.md CHANGED
@@ -5,6 +5,7 @@ colorFrom: pink
5
  colorTo: purple
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
5
  colorTo: purple
6
  sdk: docker
7
  pinned: false
8
+ app_port: 3000
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app/__init__.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask
2
+ from flask_cors import CORS
3
+
4
+
5
+ def create_app():
6
+ app = Flask(__name__)
7
+ CORS(app)
8
+
9
+ with app.app_context():
10
+ from . import routes
11
+
12
+ return app
app/config.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import random
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ IGNORED_MODEL_NAMES = ["gpt-4", "gpt-3.5", "websearch", "dall-e-3", "gpt-4o"]
9
+ IMAGE_MODEL_NAMES = ["dalle3", "dalle-3", "dall-e-3"]
10
+ AUTH_TOKEN = os.getenv("AUTHORIZATION")
11
+ # G_TOKEN = os.getenv("G_TOKEN")
12
+ G_TOKEN = None
13
+ HISTORY_MSG_LIMIT = os.getenv("HISTORY_MSG_LIMIT", 0)
14
+ HTTP_PROXIES = os.getenv("HTTP_PROXIES")
15
+ HTTPS_PROXIES = os.getenv("HTTPS_PROXIES")
16
+ SOCKS_PROXIES = os.getenv("SOCKS_PROXIES")
17
+ RECAPTCHA_SECRET = os.getenv("RECAPTCHA_SECRET")
18
+ POPAI_BASE_URL = "https://www.popai.pro/"
19
+ log_level = os.getenv('LOG_LEVEL', 'INFO').upper()
20
+
21
+
22
+ def configure_logging():
23
+ extended_log_format = (
24
+ '%(asctime)s | %(levelname)s | %(name)s | '
25
+ '%(process)d | %(filename)s:%(lineno)d | %(funcName)s | %(message)s'
26
+ )
27
+ logging.basicConfig(level=log_level, format=extended_log_format)
28
+
29
+
30
+ def _get_proxies_from_env(env_var):
31
+ proxies = os.getenv(env_var, '')
32
+ return [proxy.strip() for proxy in proxies.split(',') if proxy.strip()]
33
+
34
+
35
+ class ProxyPool:
36
+ def __init__(self):
37
+ self.http_proxies = _get_proxies_from_env('HTTP_PROXIES')
38
+ self.https_proxies = _get_proxies_from_env('HTTPS_PROXIES')
39
+ self.socks_proxies = _get_proxies_from_env('SOCKS_PROXIES')
40
+
41
+ def get_random_proxy(self):
42
+ proxy = {}
43
+ if self.http_proxies:
44
+ proxy['http'] = random.choice(self.http_proxies)
45
+ if self.https_proxies:
46
+ proxy['https'] = random.choice(self.https_proxies)
47
+ if self.socks_proxies:
48
+ socks_proxy = random.choice(self.socks_proxies)
49
+ proxy['http'] = socks_proxy
50
+ proxy['https'] = socks_proxy
51
+
52
+ logging.info("proxy URL %s", proxy)
53
+
54
+ return proxy if proxy else None
app/docs/img.png ADDED
app/routes.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from datetime import datetime, timedelta
3
+
4
+ from flask import request, Response, current_app as app
5
+
6
+ from app.config import IGNORED_MODEL_NAMES, IMAGE_MODEL_NAMES, AUTH_TOKEN, HISTORY_MSG_LIMIT
7
+ from app.config import configure_logging
8
+ from app.utils import send_chat_message, fetch_channel_id, map_model_name, process_content, get_user_contents, \
9
+ generate_hash, get_next_auth_token, handle_error, get_request_parameters
10
+
11
+ configure_logging()
12
+ storage_map = {}
13
+
14
+
15
+ @app.route("/v1/chat/completions", methods=["GET", "POST", "OPTIONS"])
16
+ def onRequest():
17
+ try:
18
+ return fetch(request)
19
+ except Exception as e:
20
+ logging.error("An error occurred with chat : %s", e)
21
+ return handle_error(e)
22
+
23
+
24
+ @app.route('/v1/models')
25
+ def list_models():
26
+ return {
27
+ "object": "list",
28
+ "data": [{
29
+ "id": m,
30
+ "object": "model",
31
+ "created": int(datetime.now().timestamp()),
32
+ "owned_by": "popai"
33
+ } for m in IGNORED_MODEL_NAMES]
34
+ }
35
+
36
+
37
+ @app.route('/v1/images/generations', methods= ["post"])
38
+ def image():
39
+ try:
40
+ request.get_json()["model"] = IMAGE_MODEL_NAMES[0]
41
+ return fetch(request)
42
+ except Exception as e:
43
+ logging.error("An error occurred with image : %s", e)
44
+ return handle_error(e)
45
+
46
+
47
+ def get_channel_id(hash_value, token, model_name, content, template_id):
48
+ if hash_value in storage_map:
49
+ channel_id, expiry_time = storage_map[hash_value]
50
+ if expiry_time > datetime.now() and channel_id:
51
+ logging.info("Returning channel id from cache")
52
+ return channel_id
53
+ channel_id = fetch_channel_id(token, model_name, content, template_id)
54
+ expiry_time = datetime.now() + timedelta(days=1)
55
+ storage_map[hash_value] = (channel_id, expiry_time)
56
+ return channel_id
57
+
58
+
59
+ def fetch(req):
60
+ if req.method == "OPTIONS":
61
+ return handle_options_request()
62
+ token = get_next_auth_token(AUTH_TOKEN)
63
+ messages, model_name, prompt, user_stream = get_request_parameters(req.get_json())
64
+ model_to_use = map_model_name(model_name)
65
+ template_id = 2000000 if model_name in IMAGE_MODEL_NAMES else ''
66
+
67
+ if not messages and prompt:
68
+ final_user_content = prompt
69
+ first_user_message = final_user_content
70
+ image_url = None
71
+ elif messages:
72
+ last_message = messages[-1]
73
+ first_user_message, end_user_message, concatenated_messages = get_user_contents(messages, HISTORY_MSG_LIMIT)
74
+ final_user_content, image_url = process_content(last_message.get('content'))
75
+ final_user_content = concatenated_messages + '\n' + final_user_content if concatenated_messages else final_user_content
76
+ # channel_id = get_channel_id(hash_value, token, model_to_use, final_user_content, template_id)
77
+
78
+ hash_value = generate_hash(first_user_message, model_to_use, token)
79
+ channel_id = get_channel_id(hash_value, token, model_to_use, final_user_content, template_id)
80
+
81
+ if final_user_content is None:
82
+ return Response("No user message found", status=400)
83
+
84
+ return send_chat_message(req, token, channel_id, final_user_content, model_to_use, user_stream, image_url, model_name)
85
+
86
+
87
+ def handle_options_request():
88
+ return Response(status=204, headers={'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*'})
app/utils.py ADDED
@@ -0,0 +1,519 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import hashlib
3
+ import imghdr
4
+ import json
5
+ import logging
6
+ import os
7
+ import re
8
+ from collections import deque
9
+
10
+ import requests
11
+ from DrissionPage._configs.chromium_options import ChromiumOptions
12
+ from flask import Response, jsonify
13
+ from DrissionPage import ChromiumPage
14
+ from requests.exceptions import ProxyError
15
+
16
+ from app.config import configure_logging, IMAGE_MODEL_NAMES, ProxyPool, G_TOKEN, POPAI_BASE_URL
17
+
18
+ configure_logging()
19
+ proxy_pool = ProxyPool()
20
+ current_token_index = 0
21
+
22
+
23
+ def send_http_request(url, headers, data):
24
+ try:
25
+ response = requests.post(url, headers=headers, json=data)
26
+ response.raise_for_status()
27
+ return response
28
+ except requests.exceptions.RequestException as e:
29
+ logging.error("HTTP request error: %s", e)
30
+ raise
31
+
32
+
33
+ def get_env_variable(var_name):
34
+ return os.getenv(var_name)
35
+
36
+
37
+ def send_chat_message(req, auth_token, channel_id, final_user_content, model_name, user_stream, image_url,
38
+ user_model_name):
39
+ logging.info("Channel ID: %s", channel_id)
40
+ # logging.info("Final User Content: %s", final_user_content)
41
+ logging.info("Model Name: %s", model_name)
42
+ logging.info("Image URL: %s", image_url)
43
+ logging.info("User stream: %s", user_stream)
44
+ url = "https://api.popai.pro/api/v1/chat/send"
45
+ headers = {
46
+ "Accept": "text/event-stream",
47
+ "Accept-Encoding": "gzip, deflate, br, zstd",
48
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
49
+ "App-Name": "popai-web",
50
+ "Authorization": auth_token,
51
+ "Content-Type": "application/json",
52
+ "Device-Info": '{"web_id":"drBt-M9G_I9eKAgB8TdnY","baidu_id":"18f1fd3dc7749443876b69"}',
53
+ "Gtoken": G_TOKEN,
54
+ "Origin": "https://www.popai.pro",
55
+ "Priority": "u=1, i",
56
+ "Referer": "https://www.popai.pro/",
57
+ "Sec-Ch-Ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
58
+ "Sec-Ch-Ua-Mobile": "?0",
59
+ "Sec-Ch-Ua-Platform": "Windows",
60
+ "Sec-Fetch-Dest": "empty",
61
+ "Sec-Fetch-Mode": "cors",
62
+ "Sec-Fetch-Site": "same-site",
63
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
64
+ "Chrome/124.0.0.0 Safari/537.36",
65
+ "Cache-Control": "no-cache"
66
+ }
67
+
68
+ data = {
69
+ "isGetJson": True,
70
+ "version": "1.3.6",
71
+ "language": "zh-CN",
72
+ "channelId": channel_id,
73
+ "message": final_user_content,
74
+ "model": model_name,
75
+ "messageIds": [],
76
+ "imageUrls": image_url,
77
+ "improveId": None,
78
+ "richMessageId": None,
79
+ "isNewChat": False,
80
+ "action": None,
81
+ "isGeneratePpt": False,
82
+ "isSlidesChat": False,
83
+ "roleEnum": None,
84
+ "pptCoordinates": "",
85
+ "translateLanguage": None,
86
+ "docPromptTemplateId": None
87
+ }
88
+
89
+ max_retries = 3
90
+ for attempt in range(max_retries):
91
+ try:
92
+ logging.info("Using G_TOKEN: %s", headers["Gtoken"])
93
+ response = request_with_proxy_chat(url, headers, data, True)
94
+
95
+ # logging.info("Response headers: %s", response.headers)
96
+
97
+ # 检查响应头中的错误码
98
+ if response.headers.get('YJ-X-Content'):
99
+ raise Exception(f"Popai response error. Error: {response.headers.get('YJ-X-Content')}")
100
+
101
+ # 如果响应的内容类型是 'text/event-stream;charset=UTF-8'
102
+ if response.headers.get('Content-Type') == 'text/event-stream;charset=UTF-8':
103
+ if not user_stream:
104
+ return stream_2_json(response, model_name, user_model_name)
105
+ return stream_response(response, model_name)
106
+ else:
107
+ return stream_2_json(response, model_name, user_model_name)
108
+
109
+ except requests.exceptions.RequestException as e:
110
+ logging.error("send_chat_message error: %s", e)
111
+ if attempt == max_retries - 1:
112
+ return handle_error(e)
113
+
114
+ except Exception as e:
115
+ logging.error("send_chat_message error: %s", e)
116
+ try:
117
+ if "60001" in str(e):
118
+ logging.warning(f"Received 60001 error code on attempt {attempt + 1}. Retrying...")
119
+ if attempt == 1: # 第二次失败后更新 G_TOKEN
120
+ headers["Gtoken"] = updateGtoken() # 更新G_TOKEN后需要更新header
121
+ continue
122
+ if attempt == max_retries - 1:
123
+ return handle_error(e)
124
+ except Exception as e:
125
+ logging.error("Update_gtoken error: %s", e)
126
+ return handle_error(e)
127
+ return Exception(f"All attempts to send chat message failed.")
128
+
129
+
130
+ def stream_response(resp, model_name):
131
+ logging.info("Entering stream_response function")
132
+
133
+ def generate():
134
+ for message in handle_http_response(resp):
135
+ message_id = message.get("messageId", "")
136
+ objectid = message.get("chunkId", "")
137
+ content = message.get("content", "")
138
+ wrapped_chunk = {
139
+ "id": message_id,
140
+ "object": "chat.completion",
141
+ "created": 0,
142
+ "model": model_name,
143
+ "choices": [
144
+ {
145
+ "index": 0,
146
+ "delta": {
147
+ "role": "assistant",
148
+ "content": content
149
+ },
150
+ "finish_reason": "stop",
151
+ }
152
+ ],
153
+ "usage": {
154
+ "prompt_tokens": 13,
155
+ "completion_tokens": 7,
156
+ "total_tokens": 20
157
+ },
158
+ "system_fingerprint": None
159
+ }
160
+ event_data = f"data: {json.dumps(wrapped_chunk, ensure_ascii=False)}\n\n"
161
+ yield event_data.encode('utf-8')
162
+
163
+ logging.info("Exiting stream_response function")
164
+ return Response(generate(), mimetype='text/event-stream; charset=UTF-8')
165
+
166
+
167
+ def stream_2_json(resp, model_name, user_model_name):
168
+ logging.info("Entering stream_2_json function")
169
+
170
+ chunks = []
171
+ merged_content = ""
172
+ append_to_chunks = chunks.append
173
+ for message in handle_http_response(resp):
174
+ message_id = message.get("messageId", "")
175
+ objectid = message.get("chunkId", "")
176
+ content = message.get("content", "")
177
+ merged_content += content
178
+ if user_model_name in IMAGE_MODEL_NAMES:
179
+ # 如果 model_name 在 IMAGE_MODEL_NAMES 内,转换为包含 URL 的格式
180
+ wrapped_chunk = {
181
+ "created": 0,
182
+ "data": [
183
+ {"url": extract_url_from_content(merged_content)}
184
+ ]
185
+ }
186
+ else:
187
+ wrapped_chunk = {
188
+ "id": message_id,
189
+ "object": "chat.completion",
190
+ "created": 0,
191
+ "model": model_name,
192
+ "choices": [
193
+ {
194
+ "index": 0,
195
+ "message": {
196
+ "role": "assistant",
197
+ "content": merged_content
198
+ },
199
+ "finish_reason": "stop",
200
+ }
201
+ ],
202
+ "usage": {
203
+ "prompt_tokens": 13,
204
+ "completion_tokens": 7,
205
+ "total_tokens": 20
206
+ },
207
+ "system_fingerprint": None
208
+ }
209
+ append_to_chunks(wrapped_chunk)
210
+
211
+ logging.info("Exiting stream_2_json function")
212
+ if chunks:
213
+ return jsonify(chunks[-1])
214
+ else:
215
+ raise Exception("No data available")
216
+
217
+
218
+ def process_content(message):
219
+ text_array = []
220
+ image_url_array = []
221
+
222
+ if isinstance(message, str):
223
+ return message, image_url_array
224
+
225
+ if isinstance(message, list):
226
+ for msg in message:
227
+ content_type = msg.get("type")
228
+ if content_type == "text":
229
+ text_array.append(msg.get("text", ""))
230
+ elif content_type == "image_url":
231
+ url = msg.get("image_url", {}).get("url", "")
232
+ if is_base64_image(url):
233
+ url = upload_image_to_telegraph(url)
234
+ image_url_array.append(url)
235
+
236
+ return '\n'.join(text_array), image_url_array
237
+
238
+
239
+ def upload_image_to_telegraph(base64_string):
240
+ try:
241
+ if base64_string.startswith('data:image'):
242
+ base64_string = base64_string.split(',')[1]
243
+ image_data = base64.b64decode(base64_string)
244
+
245
+ image_type = imghdr.what(None, image_data)
246
+ if image_type is None:
247
+ raise ValueError("Invalid image data")
248
+
249
+ mime_type = f"image/{image_type}"
250
+ files = {'file': (f'image.{image_type}', image_data, mime_type)}
251
+ response = request_with_proxy_image('https://telegra.ph/upload', files=files)
252
+ response.raise_for_status()
253
+ json_response = response.json()
254
+ if isinstance(json_response, list) and 'src' in json_response[0]:
255
+ return 'https://telegra.ph' + json_response[0]['src']
256
+ else:
257
+ raise ValueError(f"Unexpected response format: {json_response}")
258
+
259
+ except requests.exceptions.RequestException as e:
260
+ raise Exception(f"Failed to upload image. Error: {e}")
261
+ except Exception as e:
262
+ raise Exception(f"Failed to upload image. An error occurred: {e}")
263
+
264
+
265
+ def is_base64_image(base64_string):
266
+ return base64_string.startswith('data:image')
267
+
268
+
269
+ def process_msg_content(content):
270
+ if isinstance(content, str):
271
+ return content
272
+ elif isinstance(content, list):
273
+ return ' '.join(item.get("text") for item in content if item.get("type") == "text")
274
+ return None
275
+
276
+
277
+ def get_user_contents(messages, limit):
278
+ limit = int(limit)
279
+ selected_messages = deque(maxlen=limit)
280
+ first_user_message = None
281
+
282
+ # 过滤并处理用户消息
283
+ for message in messages:
284
+ if message.get("role") == "user":
285
+ content = process_msg_content(message.get("content"))
286
+ if content:
287
+ selected_messages.append(content)
288
+ if first_user_message is None:
289
+ first_user_message = content
290
+
291
+ # 检查是否有足够的消息
292
+ if selected_messages:
293
+ end_user_message = selected_messages[-1]
294
+ else:
295
+ end_user_message = None
296
+
297
+ # 拼接消息内容
298
+ if selected_messages:
299
+ selected_messages.pop() # 移除最后一条数据
300
+
301
+ concatenated_messages = ' \n'.join(selected_messages)
302
+
303
+ return first_user_message, end_user_message, concatenated_messages
304
+
305
+
306
+ # def get_user_contents(messages, limit=3):
307
+ # user_messages = [str(message.get("content", '')) for message in messages if message.get("role") == "user"]
308
+ # end_message = user_messages[-1] if user_messages else None
309
+ # selected_messages = user_messages[-limit-1:-1] if len(user_messages) > limit else user_messages[:-1]
310
+ # concatenated_messages = ' '.join(selected_messages)
311
+ # return end_message, concatenated_messages
312
+
313
+ # def get_user_contents(messages, limit=3):
314
+ # contents = []
315
+ # user_content_added = False
316
+ # for message in messages:
317
+ # if message.get("role") == "user" and not user_content_added:
318
+ # contents.append(str(message.get("content", '')))
319
+ # user_content_added = True
320
+ # return contents
321
+
322
+
323
+ def fetch_channel_id(auth_token, model_name, content, template_id):
324
+ url = "https://api.popai.pro/api/v1/chat/getChannel"
325
+ headers = {
326
+ "Accept": "application/json",
327
+ "Accept-Encoding": "gzip, deflate, br, zstd",
328
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
329
+ "App-Name": "popai-web",
330
+ "Authorization": auth_token,
331
+ "Content-Type": "application/json",
332
+ "Device-Info": '{"web_id":"drBt-M9G_I9eKAgB8TdnY","baidu_id":"18f1fd3dc7749443876b69"}',
333
+ "Language": "en",
334
+ "Origin": "https://www.popai.pro",
335
+ "Referer": "https://www.popai.pro/",
336
+ "Pop-Url": "https://www.popai.pro/creation/All/Image",
337
+ "Sec-Ch-Ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
338
+ "Sec-Ch-Ua-Mobile": "?0",
339
+ "Sec-Ch-Ua-Platform": "Windows",
340
+ "Sec-Fetch-Dest": "empty",
341
+ "Sec-Fetch-Mode": "cors",
342
+ "Sec-Fetch-Site": "same-site",
343
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
344
+ }
345
+ data = {
346
+ "model": model_name,
347
+ "templateId": template_id,
348
+ "message": content,
349
+ "language": "English",
350
+ "fileType": None
351
+ }
352
+
353
+ try:
354
+ response = request_with_proxy_chat(url, headers, data, False)
355
+ response.raise_for_status()
356
+ response_data = response.json()
357
+ return response_data.get('data', {}).get('channelId')
358
+
359
+ except requests.exceptions.RequestException as e:
360
+ logging.error("fetch_channel_id error: %s", e)
361
+ raise Exception(f"Failed to fetch channel_id. Error: {e}") from e
362
+
363
+
364
+ def map_model_name(model_name):
365
+ model_mapping = {
366
+ "gpt-4": "GPT-4",
367
+ "dalle3": "GPT-4",
368
+ "dalle-3": "GPT-4",
369
+ "dall-e-3": "GPT-4",
370
+ "gpt-3.5": "Standard",
371
+ "websearch": "Web Search",
372
+ "internet": "Web Search",
373
+ "gpt-4o": "GPT-4o"
374
+ }
375
+ sorted_keys = sorted(model_mapping.keys(), key=len, reverse=True)
376
+ for key in sorted_keys:
377
+ if model_name.lower().startswith(key):
378
+ return model_mapping[key]
379
+ return "GPT-4"
380
+
381
+
382
+ def generate_hash(contents, model_name, token):
383
+ concatenated = ''.join(contents)
384
+ return token + model_name + hashlib.md5(concatenated.encode('utf-8')).hexdigest()
385
+
386
+
387
+ def handle_http_response(resp):
388
+ buffer = ""
389
+ json_object_counter = 0
390
+ for chunk in resp.iter_content(chunk_size=None):
391
+ buffer += chunk.decode('utf-8')
392
+ while "\n\n" in buffer:
393
+ json_object, buffer = buffer.split("\n\n", 1)
394
+ if json_object.startswith("data:"):
395
+ json_object = json_object[len("data:"):].strip()
396
+ json_object_counter += 1
397
+ if json_object_counter == 1:
398
+ continue
399
+ try:
400
+ chunk_json = json.loads(json_object)
401
+ except json.JSONDecodeError as e:
402
+ logging.error(f"Failed to parse JSON: {e}")
403
+ continue
404
+ for message in chunk_json:
405
+ yield message
406
+
407
+
408
+ def get_next_auth_token(tokens):
409
+ if not tokens:
410
+ raise ValueError("No tokens provided.")
411
+ auth_tokens = tokens.split(',')
412
+ global current_token_index
413
+ token = auth_tokens[current_token_index]
414
+ current_token_index = (current_token_index + 1) % len(auth_tokens)
415
+ logging.info("Using token: %s", token)
416
+ return token
417
+
418
+
419
+ def handle_error(e):
420
+ error_response = {
421
+ "error": {
422
+ "message": str(e),
423
+ "type": "popai_2_api_error"
424
+ }
425
+ }
426
+ return jsonify(error_response), 500
427
+
428
+
429
+ def get_request_parameters(body):
430
+ messages = body.get("messages", [])
431
+ model_name = body.get("model")
432
+ prompt = body.get("prompt", False)
433
+ stream = body.get("stream", False)
434
+ return messages, model_name, prompt, stream
435
+
436
+
437
+ def extract_url_from_content(content):
438
+ # 使用正则表达式从 Markdown 内容中提取 URL
439
+ match = re.search(r'\!\[.*?\]\((.*?)\)', content)
440
+ return match.group(1) if match else content
441
+
442
+
443
+ def request_with_proxy_image(url, files):
444
+ return request_with_proxy(url, None, None, False, files)
445
+
446
+
447
+ def request_with_proxy_chat(url, headers, data, stream):
448
+ return request_with_proxy(url, headers, data, stream, None)
449
+
450
+
451
+ def request_with_proxy(url, headers, data, stream, files):
452
+ try:
453
+ proxies = proxy_pool.get_random_proxy()
454
+ logging.info("Use proxy url %s", proxies)
455
+
456
+ if proxies:
457
+ response = requests.post(url, headers=headers, json=data, stream=stream, files=files, proxies=proxies)
458
+ else:
459
+ response = requests.post(url, headers=headers, json=data, stream=stream, files=files)
460
+ except ProxyError as e:
461
+ logging.error(f"Proxy error occurred: {e}")
462
+ raise Exception("Proxy error occurred")
463
+ return response
464
+
465
+
466
+ # def get_gtoken_back():
467
+ # gtoken = None
468
+ # try:
469
+ # with open('./recaptcha__zh_cn.js', 'r', encoding='utf-8', errors='ignore') as f:
470
+ # str_js = f.read()
471
+ #
472
+ # options = uc.ChromeOptions()
473
+ # # 无头模式
474
+ # # options.add_argument('--headless')
475
+ # options.add_argument('--disable-gpu')
476
+ # options.add_argument('--no-sandbox')
477
+ # options.add_argument('--disable-dev-shm-usage')
478
+ #
479
+ # driver = uc.Chrome(options=options)
480
+ # try:
481
+ # driver.get(POPAI_BASE_URL)
482
+ # # 设置最长等待时间s
483
+ # WebDriverWait(driver, 20).until(lambda d: d.execute_async_script(str_js))
484
+ # gtoken = driver.execute_async_script(str_js)
485
+ #
486
+ # with open('gtoken.txt', 'a', encoding='utf-8', errors='ignore') as f:
487
+ # f.write(gtoken)
488
+ # f.write('\n')
489
+ # finally:
490
+ # driver.quit() # 确保浏览器实例在异常情况下也能关闭
491
+ # except Exception as e:
492
+ # logging.error(f"An error occurred: {e}")
493
+ #
494
+ # return gtoken
495
+
496
+
497
+ def get_gtoken():
498
+ gtoken = ''
499
+ try:
500
+ co = ChromiumOptions()
501
+ co.headless()
502
+ co.set_argument('--headless=new')
503
+ co.set_argument('--no-sandbox')
504
+ page = ChromiumPage(co)
505
+ page.get('https://www.popai.pro')
506
+ gtoken = page.run_js_loaded('''
507
+ return window.grecaptcha.enterprise.execute("6LfP64kpAAAAAP_Jl8kdL0-09UKzowM87iddJqXA", {action: "LOGIN"});
508
+ ''')
509
+ except Exception as e:
510
+ logging.error(f"An error occurred: {e}")
511
+ return gtoken
512
+
513
+
514
+ def updateGtoken():
515
+ global G_TOKEN
516
+ G_TOKEN = get_gtoken()
517
+ logging.info("G_TOKEN updated successfully")
518
+ # logging.info("G_TOKEN: %s", G_TOKEN)
519
+ return G_TOKEN
main.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from app import create_app
2
+
3
+ app = create_app()
4
+
5
+ if __name__ == '__main__':
6
+ app.run(host='0.0.0.0', port=3000)
recaptcha__zh_cn.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ try {
2
+ var py_callback = arguments[arguments.length - 1];
3
+ const a = await window.grecaptcha.enterprise.execute("6LfP64kpAAAAAP_Jl8kdL0-09UKzowM87iddJqXA", {
4
+ action: "LOGIN"
5
+ });
6
+ py_callback(a)
7
+ } catch (a) {
8
+ py_callback("")
9
+ }
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Flask==3.0.3
2
+ Flask-Cors==4.0.0
3
+ requests==2.31.0
4
+ python-dotenv
5
+ DrissionPage
test.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ url = "http://127.0.0.1:3000/v1/chat/completions"
4
+ headers = {
5
+ "Authorization": "Bearer none",
6
+ "Content-Type": "application/json"
7
+ }
8
+ data = {
9
+ "model": "gpt-4o",
10
+ "messages": [
11
+ {
12
+ "role": "user",
13
+ "content": "收到请回复yessss"
14
+ }
15
+ ],
16
+ "stream": False
17
+ }
18
+
19
+ response = requests.get(url, headers=headers, json=data)
20
+
21
+ print(response.status_code)
22
+ print(response.json())