webscout 8.3__py3-none-any.whl → 8.3.2__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.
Potentially problematic release.
This version of webscout might be problematic. Click here for more details.
- webscout/AIauto.py +4 -4
- webscout/AIbase.py +61 -1
- webscout/AIutel.py +46 -53
- webscout/Bing_search.py +418 -0
- webscout/Extra/YTToolkit/ytapi/patterns.py +45 -45
- webscout/Extra/YTToolkit/ytapi/stream.py +1 -1
- webscout/Extra/YTToolkit/ytapi/video.py +10 -10
- webscout/Extra/autocoder/autocoder_utiles.py +1 -1
- webscout/Extra/gguf.py +706 -177
- webscout/Litlogger/formats.py +9 -0
- webscout/Litlogger/handlers.py +18 -0
- webscout/Litlogger/logger.py +43 -1
- webscout/Provider/AISEARCH/genspark_search.py +7 -7
- webscout/Provider/AISEARCH/scira_search.py +3 -2
- webscout/Provider/GeminiProxy.py +140 -0
- webscout/Provider/LambdaChat.py +7 -1
- webscout/Provider/MCPCore.py +78 -75
- webscout/Provider/OPENAI/BLACKBOXAI.py +1046 -1017
- webscout/Provider/OPENAI/GeminiProxy.py +328 -0
- webscout/Provider/OPENAI/Qwen3.py +303 -303
- webscout/Provider/OPENAI/README.md +5 -0
- webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
- webscout/Provider/OPENAI/TogetherAI.py +355 -0
- webscout/Provider/OPENAI/__init__.py +16 -1
- webscout/Provider/OPENAI/autoproxy.py +332 -0
- webscout/Provider/OPENAI/base.py +101 -14
- webscout/Provider/OPENAI/chatgpt.py +15 -2
- webscout/Provider/OPENAI/chatgptclone.py +14 -3
- webscout/Provider/OPENAI/deepinfra.py +339 -328
- webscout/Provider/OPENAI/e2b.py +295 -74
- webscout/Provider/OPENAI/mcpcore.py +109 -70
- webscout/Provider/OPENAI/opkfc.py +18 -6
- webscout/Provider/OPENAI/scirachat.py +59 -50
- webscout/Provider/OPENAI/toolbaz.py +2 -10
- webscout/Provider/OPENAI/writecream.py +166 -166
- webscout/Provider/OPENAI/x0gpt.py +367 -367
- webscout/Provider/OPENAI/xenai.py +514 -0
- webscout/Provider/OPENAI/yep.py +389 -383
- webscout/Provider/STT/__init__.py +3 -0
- webscout/Provider/STT/base.py +281 -0
- webscout/Provider/STT/elevenlabs.py +265 -0
- webscout/Provider/TTI/__init__.py +4 -1
- webscout/Provider/TTI/aiarta.py +399 -365
- webscout/Provider/TTI/base.py +74 -2
- webscout/Provider/TTI/bing.py +231 -0
- webscout/Provider/TTI/fastflux.py +63 -30
- webscout/Provider/TTI/gpt1image.py +149 -0
- webscout/Provider/TTI/imagen.py +196 -0
- webscout/Provider/TTI/magicstudio.py +60 -29
- webscout/Provider/TTI/piclumen.py +43 -32
- webscout/Provider/TTI/pixelmuse.py +232 -225
- webscout/Provider/TTI/pollinations.py +43 -32
- webscout/Provider/TTI/together.py +287 -0
- webscout/Provider/TTI/utils.py +2 -1
- webscout/Provider/TTS/README.md +1 -0
- webscout/Provider/TTS/__init__.py +2 -1
- webscout/Provider/TTS/freetts.py +140 -0
- webscout/Provider/TTS/speechma.py +45 -39
- webscout/Provider/TogetherAI.py +366 -0
- webscout/Provider/UNFINISHED/ChutesAI.py +314 -0
- webscout/Provider/UNFINISHED/fetch_together_models.py +95 -0
- webscout/Provider/XenAI.py +324 -0
- webscout/Provider/__init__.py +8 -0
- webscout/Provider/deepseek_assistant.py +378 -0
- webscout/Provider/scira_chat.py +3 -2
- webscout/Provider/toolbaz.py +0 -1
- webscout/auth/__init__.py +44 -0
- webscout/auth/api_key_manager.py +189 -0
- webscout/auth/auth_system.py +100 -0
- webscout/auth/config.py +76 -0
- webscout/auth/database.py +400 -0
- webscout/auth/exceptions.py +67 -0
- webscout/auth/middleware.py +248 -0
- webscout/auth/models.py +130 -0
- webscout/auth/providers.py +257 -0
- webscout/auth/rate_limiter.py +254 -0
- webscout/auth/request_models.py +127 -0
- webscout/auth/request_processing.py +226 -0
- webscout/auth/routes.py +526 -0
- webscout/auth/schemas.py +103 -0
- webscout/auth/server.py +312 -0
- webscout/auth/static/favicon.svg +11 -0
- webscout/auth/swagger_ui.py +203 -0
- webscout/auth/templates/components/authentication.html +237 -0
- webscout/auth/templates/components/base.html +103 -0
- webscout/auth/templates/components/endpoints.html +750 -0
- webscout/auth/templates/components/examples.html +491 -0
- webscout/auth/templates/components/footer.html +75 -0
- webscout/auth/templates/components/header.html +27 -0
- webscout/auth/templates/components/models.html +286 -0
- webscout/auth/templates/components/navigation.html +70 -0
- webscout/auth/templates/static/api.js +455 -0
- webscout/auth/templates/static/icons.js +168 -0
- webscout/auth/templates/static/main.js +784 -0
- webscout/auth/templates/static/particles.js +201 -0
- webscout/auth/templates/static/styles.css +3353 -0
- webscout/auth/templates/static/ui.js +374 -0
- webscout/auth/templates/swagger_ui.html +170 -0
- webscout/client.py +49 -3
- webscout/litagent/Readme.md +12 -3
- webscout/litagent/agent.py +99 -62
- webscout/scout/core/scout.py +104 -26
- webscout/scout/element.py +139 -18
- webscout/swiftcli/core/cli.py +14 -3
- webscout/swiftcli/decorators/output.py +59 -9
- webscout/update_checker.py +31 -49
- webscout/version.py +1 -1
- webscout/webscout_search.py +4 -12
- webscout/webscout_search_async.py +3 -10
- webscout/yep_search.py +2 -11
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/METADATA +41 -11
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/RECORD +116 -68
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/entry_points.txt +1 -1
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/OPENAI/api.py +0 -1035
- webscout/Provider/TTI/artbit.py +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/WHEEL +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/top_level.txt +0 -0
webscout/Provider/TTI/base.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any, Optional
|
|
3
3
|
from .utils import ImageResponse
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
|
|
5
6
|
|
|
6
7
|
class BaseImages(ABC):
|
|
7
8
|
@abstractmethod
|
|
@@ -43,10 +44,81 @@ class BaseImages(ABC):
|
|
|
43
44
|
"""
|
|
44
45
|
raise NotImplementedError
|
|
45
46
|
|
|
47
|
+
# class ProxyAutoMeta(ABCMeta):
|
|
48
|
+
# """Metaclass providing seamless proxy injection for providers."""
|
|
49
|
+
|
|
50
|
+
# def __call__(cls, *args, **kwargs):
|
|
51
|
+
# # Determine if automatic proxying should be disabled
|
|
52
|
+
# disable_auto_proxy = kwargs.get('disable_auto_proxy', False) or getattr(cls, 'DISABLE_AUTO_PROXY', False)
|
|
53
|
+
|
|
54
|
+
# # Proxies may be supplied explicitly
|
|
55
|
+
# proxies = kwargs.get('proxies', None)
|
|
56
|
+
|
|
57
|
+
# # Otherwise try to fetch one automatically
|
|
58
|
+
# if proxies is None and not disable_auto_proxy:
|
|
59
|
+
# try:
|
|
60
|
+
# proxies = {"http": get_auto_proxy(), "https": get_auto_proxy()}
|
|
61
|
+
# except Exception as e:
|
|
62
|
+
# print(f"Failed to fetch auto-proxy: {e}")
|
|
63
|
+
# proxies = {}
|
|
64
|
+
# elif proxies is None:
|
|
65
|
+
# proxies = {}
|
|
66
|
+
|
|
67
|
+
# # No global monkeypatching, just set proxies on the instance
|
|
68
|
+
# instance = super().__call__(*args, **kwargs)
|
|
69
|
+
# instance.proxies = proxies
|
|
70
|
+
|
|
71
|
+
# # If proxies are set, patch any existing session-like attributes
|
|
72
|
+
# if proxies:
|
|
73
|
+
# for attr in dir(instance):
|
|
74
|
+
# obj = getattr(instance, attr)
|
|
75
|
+
# if isinstance(obj, requests.Session):
|
|
76
|
+
# obj.proxies.update(proxies)
|
|
77
|
+
# if CurlSession and isinstance(obj, CurlSession):
|
|
78
|
+
# try:
|
|
79
|
+
# obj.proxies.update(proxies)
|
|
80
|
+
# except (ValueError, KeyError, AttributeError):
|
|
81
|
+
# print("Failed to update proxies for CurlSession due to an expected error.")
|
|
82
|
+
# if CurlAsyncSession and isinstance(obj, CurlAsyncSession):
|
|
83
|
+
# try:
|
|
84
|
+
# obj.proxies.update(proxies)
|
|
85
|
+
# except (ValueError, KeyError, AttributeError):
|
|
86
|
+
# print("Failed to update proxies for CurlAsyncSession due to an expected error.")
|
|
87
|
+
|
|
88
|
+
# # Helper for backward compatibility
|
|
89
|
+
# def get_proxied_session():
|
|
90
|
+
# s = requests.Session()
|
|
91
|
+
# s.proxies.update(proxies)
|
|
92
|
+
# return s
|
|
93
|
+
|
|
94
|
+
# instance.get_proxied_session = get_proxied_session
|
|
95
|
+
|
|
96
|
+
# def get_proxied_curl_session(impersonate="chrome120", **kw):
|
|
97
|
+
# if CurlSession:
|
|
98
|
+
# return CurlSession(proxies=proxies, impersonate=impersonate, **kw)
|
|
99
|
+
# raise ImportError("curl_cffi is not installed")
|
|
100
|
+
|
|
101
|
+
# instance.get_proxied_curl_session = get_proxied_curl_session
|
|
102
|
+
|
|
103
|
+
# def get_proxied_curl_async_session(impersonate="chrome120", **kw):
|
|
104
|
+
# if CurlAsyncSession:
|
|
105
|
+
# return CurlAsyncSession(proxies=proxies, impersonate=impersonate, **kw)
|
|
106
|
+
# raise ImportError("curl_cffi is not installed")
|
|
107
|
+
|
|
108
|
+
# instance.get_proxied_curl_async_session = get_proxied_curl_async_session
|
|
109
|
+
|
|
110
|
+
# return instance
|
|
111
|
+
|
|
46
112
|
class TTICompatibleProvider(ABC):
|
|
47
113
|
"""
|
|
48
114
|
Abstract Base Class for TTI providers mimicking the OpenAI Python client structure.
|
|
49
115
|
Requires a nested 'images.create' structure.
|
|
116
|
+
All subclasses automatically get proxy support via ProxyAutoMeta.
|
|
117
|
+
|
|
118
|
+
Available proxy helpers:
|
|
119
|
+
- self.get_proxied_session() - returns a requests.Session with proxies
|
|
120
|
+
- self.get_proxied_curl_session() - returns a curl_cffi.Session with proxies
|
|
121
|
+
- self.get_proxied_curl_async_session() - returns a curl_cffi.AsyncSession with proxies
|
|
50
122
|
"""
|
|
51
123
|
images: BaseImages
|
|
52
124
|
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import time
|
|
3
|
+
import tempfile
|
|
4
|
+
import os
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from webscout.Provider.TTI.utils import ImageData, ImageResponse
|
|
7
|
+
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
from webscout.litagent import LitAgent
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from PIL import Image
|
|
13
|
+
except ImportError:
|
|
14
|
+
Image = None
|
|
15
|
+
|
|
16
|
+
class Images(BaseImages):
|
|
17
|
+
def __init__(self, client):
|
|
18
|
+
self._client = client
|
|
19
|
+
|
|
20
|
+
def create(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
model: str = "bing",
|
|
24
|
+
prompt: str,
|
|
25
|
+
n: int = 1,
|
|
26
|
+
size: str = "1024x1024",
|
|
27
|
+
response_format: str = "url",
|
|
28
|
+
user: Optional[str] = None,
|
|
29
|
+
style: str = "none",
|
|
30
|
+
aspect_ratio: str = "1:1",
|
|
31
|
+
timeout: int = 60,
|
|
32
|
+
image_format: str = "png",
|
|
33
|
+
seed: Optional[int] = None,
|
|
34
|
+
**kwargs
|
|
35
|
+
) -> ImageResponse:
|
|
36
|
+
if not prompt:
|
|
37
|
+
raise ValueError("Parameter 'prompt' is required")
|
|
38
|
+
if Image is None:
|
|
39
|
+
raise ImportError("Pillow (PIL) is required for image format conversion.")
|
|
40
|
+
agent = LitAgent()
|
|
41
|
+
session = self._client.session
|
|
42
|
+
headers = self._client.headers
|
|
43
|
+
images = []
|
|
44
|
+
urls = []
|
|
45
|
+
for _ in range(n):
|
|
46
|
+
data = {
|
|
47
|
+
"q": prompt,
|
|
48
|
+
"rt": "4",
|
|
49
|
+
"FORM": "GENCRE"
|
|
50
|
+
}
|
|
51
|
+
response = session.post(
|
|
52
|
+
"https://www.bing.com/images/create",
|
|
53
|
+
data=data,
|
|
54
|
+
headers=headers,
|
|
55
|
+
allow_redirects=False,
|
|
56
|
+
timeout=timeout
|
|
57
|
+
)
|
|
58
|
+
redirect_url = response.headers.get("Location")
|
|
59
|
+
if not redirect_url:
|
|
60
|
+
raise Exception("Failed to get redirect URL")
|
|
61
|
+
from urllib.parse import urlparse, parse_qs
|
|
62
|
+
query = urlparse(redirect_url).query
|
|
63
|
+
request_id = parse_qs(query).get("id", [None])[0]
|
|
64
|
+
if not request_id:
|
|
65
|
+
raise Exception("ID not found in URL")
|
|
66
|
+
polling_url = f"https://www.bing.com/images/create/async/results/{request_id}?q={requests.utils.quote(prompt)}"
|
|
67
|
+
attempts = 0
|
|
68
|
+
img_url = None
|
|
69
|
+
while attempts < 10:
|
|
70
|
+
time.sleep(3)
|
|
71
|
+
try:
|
|
72
|
+
poll_resp = session.get(polling_url, headers=headers, timeout=timeout)
|
|
73
|
+
from bs4 import BeautifulSoup
|
|
74
|
+
soup = BeautifulSoup(poll_resp.text, "html.parser")
|
|
75
|
+
imgs = [img["src"].split("?")[0] for img in soup.select(".img_cont .mimg") if img.get("src")]
|
|
76
|
+
if imgs:
|
|
77
|
+
img_url = imgs[0]
|
|
78
|
+
break
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
attempts += 1
|
|
82
|
+
if not img_url:
|
|
83
|
+
raise Exception("Failed to get images after polling.")
|
|
84
|
+
img_bytes = session.get(img_url, headers=headers, timeout=timeout).content
|
|
85
|
+
# Convert to png or jpeg in memory
|
|
86
|
+
with BytesIO(img_bytes) as input_io:
|
|
87
|
+
with Image.open(input_io) as im:
|
|
88
|
+
out_io = BytesIO()
|
|
89
|
+
if image_format.lower() == "jpeg":
|
|
90
|
+
im = im.convert("RGB")
|
|
91
|
+
im.save(out_io, format="JPEG")
|
|
92
|
+
else:
|
|
93
|
+
im.save(out_io, format="PNG")
|
|
94
|
+
img_bytes = out_io.getvalue()
|
|
95
|
+
images.append(img_bytes)
|
|
96
|
+
if response_format == "url":
|
|
97
|
+
def upload_file_with_retry(img_bytes, image_format, max_retries=3):
|
|
98
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
99
|
+
for attempt in range(max_retries):
|
|
100
|
+
tmp_path = None
|
|
101
|
+
try:
|
|
102
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
103
|
+
tmp.write(img_bytes)
|
|
104
|
+
tmp.flush()
|
|
105
|
+
tmp_path = tmp.name
|
|
106
|
+
with open(tmp_path, "rb") as f:
|
|
107
|
+
files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
|
|
108
|
+
data = {"reqtype": "fileupload", "json": "true"}
|
|
109
|
+
headers2 = {"User-Agent": agent.random()}
|
|
110
|
+
if attempt > 0:
|
|
111
|
+
headers2["Connection"] = "close"
|
|
112
|
+
resp2 = requests.post(
|
|
113
|
+
"https://catbox.moe/user/api.php",
|
|
114
|
+
files=files,
|
|
115
|
+
data=data,
|
|
116
|
+
headers=headers2,
|
|
117
|
+
timeout=timeout,
|
|
118
|
+
)
|
|
119
|
+
if resp2.status_code == 200 and resp2.text.strip():
|
|
120
|
+
text = resp2.text.strip()
|
|
121
|
+
if text.startswith("http"):
|
|
122
|
+
return text
|
|
123
|
+
try:
|
|
124
|
+
result = resp2.json()
|
|
125
|
+
if "url" in result:
|
|
126
|
+
return result["url"]
|
|
127
|
+
except Exception:
|
|
128
|
+
if "http" in text:
|
|
129
|
+
return text
|
|
130
|
+
except Exception:
|
|
131
|
+
if attempt < max_retries - 1:
|
|
132
|
+
time.sleep(1 * (attempt + 1))
|
|
133
|
+
finally:
|
|
134
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
135
|
+
try:
|
|
136
|
+
os.remove(tmp_path)
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
return None
|
|
140
|
+
def upload_file_alternative(img_bytes, image_format):
|
|
141
|
+
try:
|
|
142
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
143
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
144
|
+
tmp.write(img_bytes)
|
|
145
|
+
tmp.flush()
|
|
146
|
+
tmp_path = tmp.name
|
|
147
|
+
try:
|
|
148
|
+
if not os.path.isfile(tmp_path):
|
|
149
|
+
return None
|
|
150
|
+
with open(tmp_path, "rb") as img_file:
|
|
151
|
+
files = {"file": img_file}
|
|
152
|
+
alt_resp = requests.post("https://0x0.st", files=files)
|
|
153
|
+
alt_resp.raise_for_status()
|
|
154
|
+
image_url = alt_resp.text.strip()
|
|
155
|
+
if not image_url.startswith("http"):
|
|
156
|
+
return None
|
|
157
|
+
return image_url
|
|
158
|
+
except Exception:
|
|
159
|
+
return None
|
|
160
|
+
finally:
|
|
161
|
+
try:
|
|
162
|
+
os.remove(tmp_path)
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
except Exception:
|
|
166
|
+
return None
|
|
167
|
+
uploaded_url = upload_file_with_retry(img_bytes, image_format)
|
|
168
|
+
if not uploaded_url:
|
|
169
|
+
uploaded_url = upload_file_alternative(img_bytes, image_format)
|
|
170
|
+
if uploaded_url:
|
|
171
|
+
urls.append(uploaded_url)
|
|
172
|
+
else:
|
|
173
|
+
raise RuntimeError(
|
|
174
|
+
"Failed to upload image to catbox.moe using all available methods"
|
|
175
|
+
)
|
|
176
|
+
result_data = []
|
|
177
|
+
if response_format == "url":
|
|
178
|
+
for url in urls:
|
|
179
|
+
result_data.append(ImageData(url=url))
|
|
180
|
+
elif response_format == "b64_json":
|
|
181
|
+
import base64
|
|
182
|
+
for img in images:
|
|
183
|
+
b64 = base64.b64encode(img).decode("utf-8")
|
|
184
|
+
result_data.append(ImageData(b64_json=b64))
|
|
185
|
+
else:
|
|
186
|
+
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
187
|
+
from time import time as _time
|
|
188
|
+
return ImageResponse(created=int(_time()), data=result_data)
|
|
189
|
+
|
|
190
|
+
class BingImageAI(TTICompatibleProvider):
|
|
191
|
+
AVAILABLE_MODELS = ["bing"]
|
|
192
|
+
def __init__(self, cookie: Optional[str] = None):
|
|
193
|
+
self.session = requests.Session()
|
|
194
|
+
self.user_agent = LitAgent().random()
|
|
195
|
+
self.headers = {
|
|
196
|
+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
|
197
|
+
"accept-language": "id-ID,id;q=0.9",
|
|
198
|
+
"cache-control": "max-age=0",
|
|
199
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
200
|
+
"origin": "https://www.bing.com",
|
|
201
|
+
"referer": "https://www.bing.com/images/create?&wlexpsignin=1",
|
|
202
|
+
"sec-ch-ua": '"Chromium";v="131", "Not_A Brand";v="24", "Microsoft Edge Simulate";v="131", "Lemur";v="131"',
|
|
203
|
+
"sec-ch-ua-mobile": "?1",
|
|
204
|
+
"sec-fetch-site": "same-origin",
|
|
205
|
+
"sec-fetch-mode": "navigate",
|
|
206
|
+
"sec-fetch-dest": "document",
|
|
207
|
+
"upgrade-insecure-requests": "1",
|
|
208
|
+
"user-agent": self.user_agent,
|
|
209
|
+
}
|
|
210
|
+
self.session.headers.update(self.headers)
|
|
211
|
+
self.cookie = cookie
|
|
212
|
+
if cookie:
|
|
213
|
+
self.session.cookies.set("_U", cookie, domain="bing.com")
|
|
214
|
+
self.images = Images(self)
|
|
215
|
+
@property
|
|
216
|
+
def models(self):
|
|
217
|
+
class _ModelList:
|
|
218
|
+
def list(inner_self):
|
|
219
|
+
return type(self).AVAILABLE_MODELS
|
|
220
|
+
return _ModelList()
|
|
221
|
+
|
|
222
|
+
if __name__ == "__main__":
|
|
223
|
+
from rich import print
|
|
224
|
+
client = BingImageAI(cookie="1pkdvumH1SEjFkDjFymRYKouIRoXZlh_p5RTfAttx4DaaNOSDyz8qFP2M7LbZ93fbl4f6Xm8fTGwXHNDB648Gom5jfnTU_Iz-VH47l0HTYJDS1sItbBBS-sqSISFgXR62SoqnW5eX5MFht-j2uB1gZ4uDnpR_60fLRTCdW1SIRegDvnBm1TGhRiZsi6wUPyzwFg7-PsXAs3Fq9iV9m-0FEw")
|
|
225
|
+
response = client.images.create(
|
|
226
|
+
prompt="A cat riding a bicycle",
|
|
227
|
+
response_format="url",
|
|
228
|
+
n=4,
|
|
229
|
+
timeout=30
|
|
230
|
+
)
|
|
231
|
+
print(response)
|
|
@@ -2,8 +2,12 @@ import requests
|
|
|
2
2
|
import os
|
|
3
3
|
import tempfile
|
|
4
4
|
import time
|
|
5
|
+
import base64
|
|
5
6
|
from typing import Optional, List, Dict, Any
|
|
6
|
-
from webscout.Provider.TTI.utils import
|
|
7
|
+
from webscout.Provider.TTI.utils import (
|
|
8
|
+
ImageData,
|
|
9
|
+
ImageResponse
|
|
10
|
+
)
|
|
7
11
|
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
8
12
|
from io import BytesIO
|
|
9
13
|
from webscout.litagent import LitAgent
|
|
@@ -13,11 +17,13 @@ try:
|
|
|
13
17
|
except ImportError:
|
|
14
18
|
Image = None
|
|
15
19
|
|
|
20
|
+
|
|
16
21
|
class Images(BaseImages):
|
|
17
22
|
def __init__(self, client):
|
|
18
23
|
self._client = client
|
|
19
24
|
|
|
20
|
-
def create(
|
|
25
|
+
def create(
|
|
26
|
+
self,
|
|
21
27
|
model: str = "flux_1_schnell",
|
|
22
28
|
prompt: str = None,
|
|
23
29
|
n: int = 1,
|
|
@@ -29,7 +35,7 @@ class Images(BaseImages):
|
|
|
29
35
|
timeout: int = 60,
|
|
30
36
|
image_format: str = "png",
|
|
31
37
|
is_public: bool = False,
|
|
32
|
-
**kwargs
|
|
38
|
+
**kwargs,
|
|
33
39
|
) -> ImageResponse:
|
|
34
40
|
if not prompt:
|
|
35
41
|
raise ValueError("Prompt is required!")
|
|
@@ -41,15 +47,19 @@ class Images(BaseImages):
|
|
|
41
47
|
"prompt": prompt,
|
|
42
48
|
"model": model,
|
|
43
49
|
"size": size,
|
|
44
|
-
"isPublic": is_public
|
|
50
|
+
"isPublic": is_public,
|
|
45
51
|
}
|
|
46
52
|
for _ in range(n):
|
|
47
|
-
resp = self._client.session.post(
|
|
53
|
+
resp = self._client.session.post(
|
|
54
|
+
api_url,
|
|
55
|
+
json=payload,
|
|
56
|
+
timeout=timeout,
|
|
57
|
+
)
|
|
48
58
|
resp.raise_for_status()
|
|
49
59
|
result = resp.json()
|
|
50
|
-
if result and
|
|
51
|
-
image_data = result[
|
|
52
|
-
base64_data = image_data.split(
|
|
60
|
+
if result and "result" in result:
|
|
61
|
+
image_data = result["result"]
|
|
62
|
+
base64_data = image_data.split(",")[1]
|
|
53
63
|
img_bytes = base64.b64decode(base64_data)
|
|
54
64
|
# Convert to png or jpeg in memory if needed
|
|
55
65
|
if Image is not None:
|
|
@@ -64,37 +74,47 @@ class Images(BaseImages):
|
|
|
64
74
|
img_bytes = out_io.getvalue()
|
|
65
75
|
images.append(img_bytes)
|
|
66
76
|
if response_format == "url":
|
|
77
|
+
|
|
67
78
|
def upload_file_with_retry(img_bytes, image_format, max_retries=3):
|
|
68
79
|
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
69
80
|
for attempt in range(max_retries):
|
|
70
81
|
tmp_path = None
|
|
71
82
|
try:
|
|
72
|
-
with tempfile.NamedTemporaryFile(
|
|
83
|
+
with tempfile.NamedTemporaryFile(
|
|
84
|
+
suffix=f".{ext}", delete=False
|
|
85
|
+
) as tmp:
|
|
73
86
|
tmp.write(img_bytes)
|
|
74
87
|
tmp.flush()
|
|
75
88
|
tmp_path = tmp.name
|
|
76
|
-
with open(tmp_path,
|
|
89
|
+
with open(tmp_path, "rb") as f:
|
|
77
90
|
files = {
|
|
78
|
-
|
|
91
|
+
"fileToUpload": (
|
|
92
|
+
f"image.{ext}",
|
|
93
|
+
f,
|
|
94
|
+
f"image/{ext}",
|
|
95
|
+
)
|
|
79
96
|
}
|
|
80
|
-
data = {
|
|
81
|
-
|
|
82
|
-
'json': 'true'
|
|
83
|
-
}
|
|
84
|
-
headers = {'User-Agent': agent.random()}
|
|
97
|
+
data = {"reqtype": "fileupload", "json": "true"}
|
|
98
|
+
headers = {"User-Agent": agent.random()}
|
|
85
99
|
if attempt > 0:
|
|
86
|
-
headers[
|
|
87
|
-
resp = requests.post(
|
|
100
|
+
headers["Connection"] = "close"
|
|
101
|
+
resp = requests.post(
|
|
102
|
+
"https://catbox.moe/user/api.php",
|
|
103
|
+
files=files,
|
|
104
|
+
data=data,
|
|
105
|
+
headers=headers,
|
|
106
|
+
timeout=timeout,
|
|
107
|
+
)
|
|
88
108
|
if resp.status_code == 200 and resp.text.strip():
|
|
89
109
|
text = resp.text.strip()
|
|
90
|
-
if text.startswith(
|
|
110
|
+
if text.startswith("http"):
|
|
91
111
|
return text
|
|
92
112
|
try:
|
|
93
113
|
result = resp.json()
|
|
94
114
|
if "url" in result:
|
|
95
115
|
return result["url"]
|
|
96
116
|
except Exception:
|
|
97
|
-
if
|
|
117
|
+
if "http" in text:
|
|
98
118
|
return text
|
|
99
119
|
except Exception:
|
|
100
120
|
if attempt < max_retries - 1:
|
|
@@ -106,22 +126,27 @@ class Images(BaseImages):
|
|
|
106
126
|
except Exception:
|
|
107
127
|
pass
|
|
108
128
|
return None
|
|
129
|
+
|
|
109
130
|
def upload_file_alternative(img_bytes, image_format):
|
|
110
131
|
try:
|
|
111
132
|
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
112
|
-
with tempfile.NamedTemporaryFile(
|
|
133
|
+
with tempfile.NamedTemporaryFile(
|
|
134
|
+
suffix=f".{ext}", delete=False
|
|
135
|
+
) as tmp:
|
|
113
136
|
tmp.write(img_bytes)
|
|
114
137
|
tmp.flush()
|
|
115
138
|
tmp_path = tmp.name
|
|
116
139
|
try:
|
|
117
140
|
if not os.path.isfile(tmp_path):
|
|
118
141
|
return None
|
|
119
|
-
with open(tmp_path,
|
|
120
|
-
files = {
|
|
121
|
-
response = requests.post(
|
|
142
|
+
with open(tmp_path, "rb") as img_file:
|
|
143
|
+
files = {"file": img_file}
|
|
144
|
+
response = requests.post(
|
|
145
|
+
"https://0x0.st", files=files
|
|
146
|
+
)
|
|
122
147
|
response.raise_for_status()
|
|
123
148
|
image_url = response.text.strip()
|
|
124
|
-
if not image_url.startswith(
|
|
149
|
+
if not image_url.startswith("http"):
|
|
125
150
|
return None
|
|
126
151
|
return image_url
|
|
127
152
|
except Exception:
|
|
@@ -133,13 +158,16 @@ class Images(BaseImages):
|
|
|
133
158
|
pass
|
|
134
159
|
except Exception:
|
|
135
160
|
return None
|
|
161
|
+
|
|
136
162
|
uploaded_url = upload_file_with_retry(img_bytes, image_format)
|
|
137
163
|
if not uploaded_url:
|
|
138
164
|
uploaded_url = upload_file_alternative(img_bytes, image_format)
|
|
139
165
|
if uploaded_url:
|
|
140
166
|
urls.append(uploaded_url)
|
|
141
167
|
else:
|
|
142
|
-
raise RuntimeError(
|
|
168
|
+
raise RuntimeError(
|
|
169
|
+
"Failed to upload image to catbox.moe using all available methods"
|
|
170
|
+
)
|
|
143
171
|
else:
|
|
144
172
|
raise RuntimeError("No image data received from FastFlux API")
|
|
145
173
|
result_data = []
|
|
@@ -148,21 +176,22 @@ class Images(BaseImages):
|
|
|
148
176
|
result_data.append(ImageData(url=url))
|
|
149
177
|
elif response_format == "b64_json":
|
|
150
178
|
import base64
|
|
179
|
+
|
|
151
180
|
for img in images:
|
|
152
181
|
b64 = base64.b64encode(img).decode("utf-8")
|
|
153
182
|
result_data.append(ImageData(b64_json=b64))
|
|
154
183
|
else:
|
|
155
184
|
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
156
185
|
from time import time as _time
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
)
|
|
186
|
+
|
|
187
|
+
return ImageResponse(created=int(_time()), data=result_data)
|
|
188
|
+
|
|
161
189
|
|
|
162
190
|
class FastFluxAI(TTICompatibleProvider):
|
|
163
191
|
AVAILABLE_MODELS = [
|
|
164
192
|
"flux_1_schnell",
|
|
165
193
|
]
|
|
194
|
+
|
|
166
195
|
def __init__(self, api_key: str = None):
|
|
167
196
|
self.api_endpoint = "https://api.freeflux.ai/v1/images/generate"
|
|
168
197
|
self.session = requests.Session()
|
|
@@ -180,15 +209,19 @@ class FastFluxAI(TTICompatibleProvider):
|
|
|
180
209
|
self.headers["authorization"] = f"Bearer {self.api_key}"
|
|
181
210
|
self.session.headers.update(self.headers)
|
|
182
211
|
self.images = Images(self)
|
|
212
|
+
|
|
183
213
|
@property
|
|
184
214
|
def models(self):
|
|
185
215
|
class _ModelList:
|
|
186
216
|
def list(inner_self):
|
|
187
217
|
return type(self).AVAILABLE_MODELS
|
|
218
|
+
|
|
188
219
|
return _ModelList()
|
|
189
220
|
|
|
221
|
+
|
|
190
222
|
if __name__ == "__main__":
|
|
191
223
|
from rich import print
|
|
224
|
+
|
|
192
225
|
client = FastFluxAI()
|
|
193
226
|
response = client.images.create(
|
|
194
227
|
model="flux_1_schnell",
|