MeUtils 2025.6.9.11.35.52__py3-none-any.whl → 2025.6.13.10.18.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {MeUtils-2025.6.9.11.35.52.dist-info → MeUtils-2025.6.13.10.18.19.dist-info}/METADATA +261 -261
  2. {MeUtils-2025.6.9.11.35.52.dist-info → MeUtils-2025.6.13.10.18.19.dist-info}/RECORD +29 -24
  3. examples/_openaisdk/openai_chatfire_usa.py +32 -0
  4. examples/_openaisdk/openai_ppinfra.py +2 -1
  5. examples/_openaisdk/openai_zhipu.py +5 -2
  6. meutils/apis/fal/images.py +17 -15
  7. meutils/apis/google/chat.py +3 -3
  8. meutils/apis/images/generations.py +34 -0
  9. meutils/apis/jimeng/images.py +3 -2
  10. meutils/apis/oneapi/channel.py +31 -52
  11. meutils/apis/volcengine_apis/videos.py +146 -0
  12. meutils/data/VERSION +1 -1
  13. meutils/llm/check_utils.py +2 -2
  14. meutils/llm/openai_polling/chat.py +1 -0
  15. meutils/llm/openai_utils/adapters.py +3 -0
  16. meutils/llm/openai_utils/common.py +1 -1
  17. meutils/math_utils/__init__.py +11 -0
  18. meutils/math_utils/common.py +50 -0
  19. meutils/notice/feishu.py +5 -1
  20. meutils/schemas/image_types.py +53 -42
  21. meutils/schemas/oneapi/_types.py +39 -27
  22. meutils/schemas/oneapi/common.py +35 -0
  23. meutils/schemas/openai_types.py +2 -0
  24. meutils/str_utils/__init__.py +17 -4
  25. meutils/str_utils/regular_expression.py +16 -5
  26. {MeUtils-2025.6.9.11.35.52.dist-info → MeUtils-2025.6.13.10.18.19.dist-info}/LICENSE +0 -0
  27. {MeUtils-2025.6.9.11.35.52.dist-info → MeUtils-2025.6.13.10.18.19.dist-info}/WHEEL +0 -0
  28. {MeUtils-2025.6.9.11.35.52.dist-info → MeUtils-2025.6.13.10.18.19.dist-info}/entry_points.txt +0 -0
  29. {MeUtils-2025.6.9.11.35.52.dist-info → MeUtils-2025.6.13.10.18.19.dist-info}/top_level.txt +0 -0
@@ -218,7 +218,7 @@ if __name__ == '__main__':
218
218
 
219
219
  check_valid_token = partial(check_token_for_siliconflow, threshold=-1)
220
220
 
221
- arun(check_valid_token("sk-tythaoctwemlhkytdlmjcegtnufvhqgmwlncgmoxixdyqoxx"))
221
+ # arun(check_valid_token("sk-tythaoctwemlhkytdlmjcegtnufvhqgmwlncgmoxixdyqoxx"))
222
222
 
223
223
  pass
224
224
  # arun(check_valid_token("sk-LlB4W38z9kv5Wy1c3ceeu4PHeIWs6bbWsjr8Om31jYvsucRv", threshold=0.1))
@@ -248,4 +248,4 @@ if __name__ == '__main__':
248
248
 
249
249
  # arun(check_token_for_ppinfra("sk_F0kgPyCMTzmOH_-VCEJucOK8HIrbnLGYm_IWxBToHZQ"))
250
250
 
251
- # arun(check_token_for_volc(os.getenv("VOLC_API_KEY")))
251
+ arun(check_token_for_volc("07139a08-e360-44e2-ba31-07f379bf99ed"))
@@ -73,6 +73,7 @@ class Completions(object):
73
73
 
74
74
  elif "deepseek-r" in request.model:
75
75
  request.separate_reasoning = True # pp
76
+ """Error code: 403 - {'code': 403, 'reason': 'NOT_ENOUGH_BALANCE', 'message': 'not enough balance', 'metadata': {}}"""
76
77
  ###########################################################################
77
78
 
78
79
  data = to_openai_params(request)
@@ -22,7 +22,10 @@ async def stream_to_nostream(
22
22
  async def chat_for_image(
23
23
  generate: Callable,
24
24
  request: CompletionRequest,
25
+ api_key: Optional[str] = None,
25
26
  ):
27
+ generate = partial(generate, api_key=api_key)
28
+
26
29
  if not request.stream or request.last_user_content.startswith( # 跳过nextchat
27
30
  (
28
31
  "hi",
@@ -178,7 +178,7 @@ async def ppu_flow(
178
178
  else: # 不计费
179
179
  yield
180
180
 
181
-
181
+ # 按量计费
182
182
  def create_chat_completion(
183
183
  completion: Union[str, Iterable[str]],
184
184
  redirect_model: str = '',
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # @Project : AI. @by PyCharm
4
+ # @File : __init__.py
5
+ # @Time : 2025/6/10 09:11
6
+ # @Author : betterme
7
+ # @WeChat : meutils
8
+ # @Software : PyCharm
9
+ # @Description :
10
+
11
+ from meutils.math_utils.common import *
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # @Project : AI. @by PyCharm
4
+ # @File : common
5
+ # @Time : 2025/6/10 09:11
6
+ # @Author : betterme
7
+ # @WeChat : meutils
8
+ # @Software : PyCharm
9
+ # @Description :
10
+
11
+
12
+ import math
13
+
14
+
15
+ def calculate_min_resolution(w, h):
16
+ """
17
+ 计算给定宽高比的最小像素公约数分辨率(宽高互质)
18
+
19
+ 参数:
20
+ aspect_ratio (str): 宽高比字符串,例如"16:9"
21
+
22
+ 返回:
23
+ tuple: (宽, 高) 的元组,整数类型
24
+ """
25
+ # 分割字符串并转换为整数
26
+ w, h = map(int, (w, h))
27
+
28
+ # 计算最大公约数
29
+ gcd_val = math.gcd(w, h)
30
+
31
+ # 化简为互质的整数比
32
+ width = w // gcd_val
33
+ height = h // gcd_val
34
+
35
+ return width, height
36
+
37
+
38
+ def size2aspect_ratio(size):
39
+ if 'x' in size:
40
+ w, h = size.split('x')
41
+ w, h = calculate_min_resolution(w, h)
42
+ return f"{w}:{h}" # aspect_ratio
43
+
44
+ elif ':' in size:
45
+ return size
46
+
47
+
48
+ if __name__ == '__main__':
49
+ print(size2aspect_ratio("1920x1080"))
50
+ print(size2aspect_ratio("1920:1080"))
meutils/notice/feishu.py CHANGED
@@ -22,6 +22,8 @@ AUDIO = "https://open.feishu.cn/open-apis/bot/v2/hook/80c2a700-adfa-4b9b-8e3f-00
22
22
  FILES = "https://open.feishu.cn/open-apis/bot/v2/hook/075fb2fa-a559-4a7e-89ac-3ab9934ff15c"
23
23
  KLING = "https://open.feishu.cn/open-apis/bot/v2/hook/e9a850c2-d171-4637-b976-ee93f7654c40"
24
24
 
25
+ VOLC = "https://open.feishu.cn/open-apis/bot/v2/hook/d487ce4f-3c2b-44db-a5b4-7ee4c5e03b4f"
26
+
25
27
 
26
28
  @background_task
27
29
  def send_message(
@@ -119,13 +121,15 @@ send_message_for_http = partial(send_message, url=http_feishu_url)
119
121
  try_catch_feishu_url = "https://open.feishu.cn/open-apis/bot/v2/hook/887fe4d3-8bcd-4cfb-bac9-62f776091ca2"
120
122
  send_message_for_try_catch = partial(send_message, url=try_catch_feishu_url)
121
123
 
124
+ send_message_for_volc = partial(send_message, url=VOLC)
125
+
122
126
  if __name__ == '__main__':
123
127
  # send_message("xxx", title=None)
124
128
  send_message(None, title=None)
125
129
 
126
130
  # send_message_for_images("xxxxxxxx", title=None)
127
131
 
128
- send_message_for_try_catch("xxxxxxxx")
132
+ send_message_for_volc("xxxxxxxx")
129
133
  # @catch(task_name='这是一个任务名')
130
134
  # def f():
131
135
  # time.sleep(3)
@@ -9,7 +9,8 @@
9
9
  # @Description : todo: 通用比例适配
10
10
 
11
11
  from meutils.pipe import *
12
- from meutils.str_utils.regular_expression import parse_url
12
+ from meutils.str_utils import parse_url
13
+ from meutils.math_utils import size2aspect_ratio
13
14
 
14
15
  from pydantic import constr
15
16
  from openai.types import ImagesResponse as _ImagesResponse, Image
@@ -124,11 +125,7 @@ class ImageRequest(BaseModel): # openai
124
125
  self.size = ASPECT_RATIOS.get(self.aspect_ratio, '1024x1024')
125
126
 
126
127
  elif self.size: # 适配尺寸
127
- if ':' in self.size:
128
- self.aspect_ratio = self.size
129
-
130
- else:
131
- self.aspect_ratio = ASPECT_RATIOS.get(self.size, '1:1')
128
+ self.aspect_ratio = size2aspect_ratio(self.size)
132
129
 
133
130
  self.size = self.size if 'x' in self.size else '512x512'
134
131
 
@@ -138,7 +135,7 @@ class ImageRequest(BaseModel): # openai
138
135
  if len(prompts) == 2:
139
136
  return prompts
140
137
  else:
141
- return prompts + [' ']
138
+ return prompts + [' '] # 只有 单url
142
139
 
143
140
  elif "http" in self.prompt and (images := parse_url(self.prompt)):
144
141
  return images[0], self.prompt.replace(images[0], "")
@@ -146,6 +143,11 @@ class ImageRequest(BaseModel): # openai
146
143
  else:
147
144
  return None, self.prompt
148
145
 
146
+ def prompt2aspect_ratio(self, aspect_ratios): # 提示词优先级最高:从支持的比例中选择匹配
147
+ for aspect_ratio in aspect_ratios:
148
+ if aspect_ratio in self.prompt:
149
+ return aspect_ratio
150
+
149
151
  class Config:
150
152
  extra = "allow"
151
153
 
@@ -162,6 +164,40 @@ class ImageRequest(BaseModel): # openai
162
164
  }
163
165
 
164
166
 
167
+ class ImageEditRequest(BaseModel):
168
+ model: Union[str, Literal["dall-e-2", "dall-e-3", "gpt-image-1"]]
169
+
170
+ prompt: str
171
+ image: Any # 图片
172
+
173
+ mask: Optional[Any] = None # 图片
174
+ background: Optional[Literal["transparent", "opaque", "auto"]] = None
175
+
176
+ n: Optional[int] = 1
177
+ quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] = None
178
+ size: Optional[
179
+ Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"]]] = "1024x1024"
180
+ response_format: Optional[Literal["url", "b64_json"]] = None
181
+
182
+ aspect_ratio: Optional[str] = None
183
+
184
+ user: Optional[str] = None
185
+
186
+ def __init__(self, /, **data: Any):
187
+ super().__init__(**data)
188
+ if not isinstance(self.image, list):
189
+ self.image = [self.image]
190
+
191
+ if self.aspect_ratio: # 适配比例
192
+ self.size = ASPECT_RATIOS.get(self.aspect_ratio, '1024x1024')
193
+
194
+ elif self.size: # 适配尺寸
195
+
196
+ self.aspect_ratio = size2aspect_ratio(self.size)
197
+
198
+ self.size = self.size if 'x' in self.size else '512x512'
199
+
200
+
165
201
  class FalImageRequest(ImageRequest):
166
202
  prompt: str
167
203
  seed: Optional[int] = None
@@ -525,40 +561,6 @@ class ImageProcess(BaseModel):
525
561
  # extra = "allow"
526
562
 
527
563
 
528
- class ImageEditRequest(BaseModel):
529
- model: Union[str, Literal["dall-e-2", "dall-e-3", "gpt-image-1"]]
530
-
531
- prompt: str
532
- image: Any # 图片
533
-
534
- mask: Optional[Any] = None # 图片
535
- background: Optional[Literal["transparent", "opaque", "auto"]] = None
536
-
537
- n: Optional[int] = 1
538
- quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] = None
539
- size: Optional[
540
- Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"]]] = "1024x1024"
541
- response_format: Optional[Literal["url", "b64_json"]] = None
542
-
543
- aspect_ratio: Optional[str] = None
544
-
545
- user: Optional[str] = None
546
-
547
- def __init__(self, /, **data: Any):
548
- super().__init__(**data)
549
- if not isinstance(self.image, list):
550
- self.image = [self.image]
551
-
552
- if self.aspect_ratio: # 适配比例
553
- self.size = ASPECT_RATIOS.get(self.aspect_ratio, '1024x1024')
554
-
555
- elif self.size: # 适配尺寸
556
- if ':' in self.size:
557
- self.aspect_ratio = self.size
558
-
559
- self.size = self.size if 'x' in self.size else '512x512'
560
-
561
-
562
564
  if __name__ == '__main__':
563
565
  # print(ASPECT_RATIOS.items())
564
566
 
@@ -599,9 +601,18 @@ if __name__ == '__main__':
599
601
 
600
602
  data = {
601
603
  "model": "flux-kontext-pro",
602
- "prompt": "a cat",
604
+ "prompt": "a cat and a dog",
603
605
  "size": "512x1024",
606
+ # "aspect_ratio": "16:9"
607
+ }
608
+
609
+ dd = {
610
+ "model": "flux-kontext-pro",
611
+ "prompt": "a cat and a dog",
612
+ "size": "5121x1024",
613
+ # "aspect_ratio": "16:9"
604
614
  }
605
615
 
606
616
  r = ImageRequest(**data)
607
617
 
618
+ print(ImagesResponse())
@@ -38,52 +38,64 @@ class ModelInfo(BaseModel):
38
38
 
39
39
 
40
40
  class ChannelInfo(BaseModel):
41
+ id: Optional[int] = None # 不存在就新建
42
+ type: int = 1 # 枚举值 openai
43
+
44
+ name: str = ''
41
45
  tag: str = ''
46
+ group: str = 'default'
47
+
48
+ base_url: str = ''
49
+ key: str
50
+ models: str = 'MODEL'
42
51
 
43
- id: Optional[int] = None
44
- type: int = 1 # 枚举值 openai
45
- key: Optional[str] = None
46
52
  access_token: str = ''
47
53
  openai_organization: str = ''
48
54
  test_model: str = ''
49
- status: int = '1'
50
- name: str = 'fal-flux'
51
- weight: int = '0'
52
- created_time: int = '1749175121'
53
- test_time: int = '0'
54
- response_time: int = '0'
55
- base_url: Optional[str] = None
55
+ status: int = 1
56
+ weight: int = 0
57
+ created_time: int = Field(default_factory=lambda: int(time.time()))
58
+ test_time: int = 0
59
+ response_time: int = 0
56
60
  other: str = ''
57
- balance: int = '0'
58
- balance_updated_time: int = '0'
61
+ balance: int = 0
62
+ balance_updated_time: int = 0
59
63
 
60
- models: str
61
- group: str = 'default'
62
-
63
- used_quota: Optional[int] = None
64
- upstream_user_quota: int = '0'
64
+ used_quota: int = 0
65
+ upstream_user_quota: int = 0
65
66
 
66
- model_mapping: Optional[str] = None # json
67
+ model_mapping: Union[str, dict] = "" # json
67
68
 
68
69
  headers: str = '' # json
69
70
  status_code_mapping: str = ''
70
- priority: int = '1'
71
- auto_ban: int = '1'
72
- empty_response_retry: int = '0'
73
- not_use_key: int = '0'
71
+ priority: int = 0
72
+ auto_ban: int = 1
73
+ empty_response_retry: int = 0
74
+ not_use_key: int = 0
74
75
  remark: str = ''
75
- mj_relax_limit: int = '99'
76
- mj_fast_limit: int = '99'
77
- mj_turbo_limit: int = '99'
76
+ mj_relax_limit: int = 99
77
+ mj_fast_limit: int = 99
78
+ mj_turbo_limit: int = 99
78
79
  other_info: str = ''
79
- channel_ratio: int = '1'
80
- error_return_429: int = '0'
80
+ channel_ratio: int = 1
81
+ error_return_429: int = 0
81
82
  setting: str = ''
82
83
 
83
84
  """参数覆盖"""
84
85
  param_override: str = '' # json
85
86
  is_tools: bool = False
86
87
 
88
+ def __init__(self, /, **data: Any):
89
+ super().__init__(**data)
90
+ self.name = self.name or self.base_url or "NAME"
91
+ self.tag = self.tag or self.base_url or "TAG"
92
+
93
+ if self.base_url:
94
+ self.type = 8
95
+
96
+ if isinstance(self.model_mapping, dict):
97
+ self.model_mapping = json.dumps(self.model_mapping)
98
+
87
99
 
88
100
  # https://oss.ffire.cc/images/qw.jpeg?x-oss-process=image/format,jpg/resize,w_512
89
101
  if __name__ == '__main__':
@@ -92,6 +92,19 @@ MODEL_PRICE = {
92
92
  "sora-1:1-480p-5s": 1.2,
93
93
  "dall-e-3": 0.03,
94
94
 
95
+ # rag
96
+ "qwen3-reranker-0.6b": 0.0011,
97
+ "qwen3-reranker-4b": 0.0011,
98
+ "qwen3-reranker-8b": 0.0011,
99
+
100
+ "qwen3-embedding-0.6b": 0.0011,
101
+ "qwen3-embedding-4b": 0.0011,
102
+ "qwen3-embedding-8b": 0.0011,
103
+
104
+ # 视频
105
+ "api-videos-3d": 0.01,
106
+ "api-videos-3d-1.5": 0.01,
107
+
95
108
  # 智能体
96
109
  "ppt": 0.1,
97
110
  "ppt-islide": 0.1,
@@ -667,6 +680,10 @@ MODEL_RATIO = {
667
680
  "meta-deepresearch": 2,
668
681
 
669
682
  # 豆包
683
+ "doubao-seed-1-6-flash-250615": 0.075,
684
+ "doubao-seed-1-6-250615": 0.4,
685
+ "doubao-seed-1-6-thinking-250615": 0.4,
686
+
670
687
  "doubao-1-5-ui-tars-250428": 1.75,
671
688
  "ui-tars-72b": 1.75,
672
689
  "doubao-1-5-pro-32k": 0.4,
@@ -725,6 +742,11 @@ MODEL_RATIO = {
725
742
  "hunyuan-standard-256k": 60,
726
743
 
727
744
  # 百度文心
745
+ "ernie-4.5-turbo-vl-32k": 0.45,
746
+ "ernie-4.5-turbo-128k": 0.12,
747
+ "ernie-x1-turbo-32k": 0.15,
748
+ "ernie-x1-32k-preview": 0.3,
749
+
728
750
  "ERNIE-Speed-8K": 0.2858,
729
751
  "ERNIE-Speed-128K": 0.2858,
730
752
 
@@ -912,6 +934,8 @@ MODEL_RATIO = {
912
934
  "o3": 5,
913
935
  "o3-2025-04-16": 5,
914
936
 
937
+ "o3-pro": 10,
938
+
915
939
  # 硅基
916
940
  "llama-3.1-8b-instruct": 0.01,
917
941
  "meta-llama/Meta-Llama-3.1-8B-Instruct": 0.01,
@@ -1023,6 +1047,7 @@ COMPLETION_RATIO = {
1023
1047
 
1024
1048
  "o3": 4,
1025
1049
  "o3-2025-04-16": 4,
1050
+ "o3-pro": 4,
1026
1051
 
1027
1052
  "gpt-4o-realtime-preview": 4,
1028
1053
  "gpt-4o-realtime-preview-2024-10-01": 4,
@@ -1095,6 +1120,11 @@ COMPLETION_RATIO = {
1095
1120
  "ERNIE-4.0-Turbo-8K": 3,
1096
1121
  "ERNIE-4.0-8K": 3,
1097
1122
 
1123
+ "ernie-4.5-turbo-vl-32k": 4,
1124
+ "ernie-4.5-turbo-128k": 4,
1125
+ "ernie-x1-turbo-32k": 4,
1126
+ "ernie-x1-32k-preview": 4,
1127
+
1098
1128
  "gemini-all": 5,
1099
1129
  "gemini-1.5-pro-001": 4,
1100
1130
  "gemini-1.5-pro-002": 4,
@@ -1193,6 +1223,11 @@ COMPLETION_RATIO = {
1193
1223
  "deepseek-ai/deepseek-vl2": 4,
1194
1224
 
1195
1225
  # 豆包
1226
+ "doubao-seed-1-6-flash-250615": 10,
1227
+ # doubao-seed-1-6-flash-250615,doubao-seed-1-6-250615,doubao-seed-1-6-thinking-250615
1228
+ "doubao-seed-1-6-250615": 10,
1229
+ "doubao-seed-1-6-thinking-250615": 10,
1230
+
1196
1231
  "doubao-1-5-ui-tars-250428": 3.43,
1197
1232
  "ui-tars-72b": 4,
1198
1233
 
@@ -627,3 +627,5 @@ if __name__ == '__main__':
627
627
  # )
628
628
  #
629
629
  # print(chat_completion_chunk)
630
+
631
+ print(ImageRequest(prompt='xx'))
@@ -6,8 +6,10 @@
6
6
  # @Author : yuanjie
7
7
  # @WeChat : meutils
8
8
  # @Software : PyCharm
9
- # @Description :
9
+ # @Description :
10
+ import re
10
11
 
12
+ import httpx
11
13
 
12
14
  from meutils.pipe import *
13
15
  # from meutils.str_utils.translater import translater
@@ -231,13 +233,13 @@ def validate_url(url):
231
233
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
232
234
  }
233
235
 
234
- response = requests.head(url, timeout=5, allow_redirects=True, headers=headers)
236
+ # response = requests.head(url, timeout=5, allow_redirects=False, headers=headers)
237
+ response = requests.head(url, timeout=5, headers=headers)
235
238
  if response.status_code >= 400:
236
239
  logger.error(f"URL 返回错误状态码: {response.status_code}")
237
240
 
238
241
  return False
239
242
 
240
- logger.error("URL 有效")
241
243
  return True
242
244
  except requests.exceptions.RequestException as e:
243
245
  logger.error(f"连接错误: {str(e)}")
@@ -277,4 +279,15 @@ if __name__ == '__main__':
277
279
 
278
280
  url = 'https://fal.ai/models/fal-ai/flux-pro/kontext/requests/de5f28be-2ca8-4bd4-8c42-c7fc32969801?output=0'
279
281
  url = "https://5b0988e595225.cdn.sohucs.com/images/20190814/5ebb727f502545718c4a06f199cd848b.jpeg"
280
- print(validate_url([url] * 3))
282
+ # url = "https://filesystem.site/cdn/20250609/1XPdqIyhHiOJ8SC68W4ZQGBrf7XRZD.png"
283
+
284
+ # url = "https://filesystem.site/cdn/20250609/1QUKzDHRQedraO15CXnUc22aBjvqEN.png"
285
+
286
+ # url = "https://photog.art/api/oss/R2yh8N"
287
+
288
+ url = "https://p3-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/1fe07cca46224208bfbed8c0f3c50ed8.png~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1780112531&x-signature=e7q1NOMjqCHvMz%2FC3dVAEVisAh4%3D&x-wf-file_name=9748f6214970f744fe7fd7a3699cfa2.png"
289
+
290
+ # print(validate_url([url] * 3))
291
+ print(validate_url(url))
292
+
293
+ print(re.findall("http", url*2))
@@ -8,6 +8,7 @@
8
8
  # @Software : PyCharm
9
9
  # @Description :
10
10
  import json
11
+ import re
11
12
 
12
13
  from meutils.pipe import *
13
14
  from urllib.parse import unquote, unquote_plus
@@ -66,6 +67,9 @@ def get_parse_and_index(text, pattern):
66
67
 
67
68
  @lru_cache()
68
69
  def parse_url(text: str, for_image=False, fn: Optional[Callable] = None):
70
+ if text.strip().startswith("http") and len(re.findall("http", text)) == 1: # http开头且是单链接
71
+ return text.split(maxsplit=1)[:1]
72
+
69
73
  fn = fn or (lambda x: x.removesuffix(")"))
70
74
 
71
75
  # url_pattern = r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
@@ -97,9 +101,7 @@ def parse_url(text: str, for_image=False, fn: Optional[Callable] = None):
97
101
 
98
102
  valid_urls = []
99
103
  for url in urls:
100
- url = url.strip(r"\n")
101
- if fn:
102
- url = fn(url) # lambda x: x.removesuffix(")")
104
+ url = fn(url.strip(r"\n"))
103
105
 
104
106
  valid_urls.append(url)
105
107
 
@@ -171,7 +173,16 @@ if __name__ == '__main__':
171
173
 
172
174
  # print(parse_url(text, True))
173
175
  text = """
174
- https://p3-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/1fe07cca46224208bfbed8c0f3c50ed8.png~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1780112531&x-signature=e7q1NOMjqCHvMz%2FC3dVAEVisAh4%3D&x-wf-file_name=9748f6214970f744fe7fd7a3699cfa2.png \nA young woman holding a lipstick tube with a black body and gold decorative rings, featuring a nude or light brown lipstick bullet. The lipstick product faces the camera, positioned slightly below her face. In the background, a close-up of lips coated with the same nude or light brown shade, creating a natural and soft effect.
176
+ https://p26-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/f13171faeed2447b8b9c301ba912f25c.jpg~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1779880356&x-signature=AJop4%2FM8VjCUfjqiEzUugprc0CI%3D&x-wf-file_name=B0DCGKG71N.MAIN.jpg
177
+
178
+ 还有这种url,两个.jpg的也能兼容么
175
179
  """
176
- print(parse_url(text, for_image=True))
180
+ print(parse_url(text))
181
+
177
182
 
183
+ # print(parse_url(text, for_image=False))
184
+
185
+ # text = """https://photog.art/api/oss/R2yh8N Convert this portrait into a straight-on,front-facing ID-style headshot."""
186
+ # print(parse_url(text))
187
+ #
188
+ # valid_urls = parse_url(text, for_image=True)