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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import random
|
|
3
|
+
import string
|
|
4
|
+
from typing import Optional, List, Dict, Any
|
|
5
|
+
from webscout.Provider.TTI.utils import (
|
|
6
|
+
ImageData,
|
|
7
|
+
ImageResponse
|
|
8
|
+
)
|
|
9
|
+
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
10
|
+
from io import BytesIO
|
|
11
|
+
import os
|
|
12
|
+
import tempfile
|
|
13
|
+
from webscout.litagent import LitAgent
|
|
14
|
+
import time
|
|
15
|
+
from requests.adapters import HTTPAdapter
|
|
16
|
+
from urllib3.util.retry import Retry
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Images(BaseImages):
|
|
20
|
+
def __init__(self, client):
|
|
21
|
+
self._client = client
|
|
22
|
+
self.base_url = "https://gpt1image.exomlapi.com"
|
|
23
|
+
# Create a session - it will automatically get proxies from the global monkey patch!
|
|
24
|
+
self.session = requests.Session()
|
|
25
|
+
self._setup_session_with_retries()
|
|
26
|
+
|
|
27
|
+
def _setup_session_with_retries(self):
|
|
28
|
+
"""Setup session with retry strategy and timeout configurations"""
|
|
29
|
+
# Configure retry strategy
|
|
30
|
+
retry_strategy = Retry(
|
|
31
|
+
total=3,
|
|
32
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
33
|
+
backoff_factor=1,
|
|
34
|
+
allowed_methods=["HEAD", "GET", "OPTIONS", "POST"],
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
38
|
+
self.session.mount("http://", adapter)
|
|
39
|
+
self.session.mount("https://", adapter)
|
|
40
|
+
|
|
41
|
+
# Set timeouts
|
|
42
|
+
# self.session.timeout = (10, 30) # (connect_timeout, read_timeout)
|
|
43
|
+
# Unlimited timeout: do not set session timeout here
|
|
44
|
+
|
|
45
|
+
def build_headers(self, extra: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
46
|
+
agent = LitAgent()
|
|
47
|
+
fp = agent.generate_fingerprint("chrome")
|
|
48
|
+
headers = {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"accept": fp["accept"],
|
|
51
|
+
"accept-language": fp["accept_language"],
|
|
52
|
+
"origin": self.base_url,
|
|
53
|
+
"referer": f"{self.base_url}/",
|
|
54
|
+
"user-agent": fp["user_agent"],
|
|
55
|
+
"sec-ch-ua": fp["sec_ch_ua"],
|
|
56
|
+
"sec-ch-ua-mobile": "?0",
|
|
57
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
58
|
+
"sec-fetch-dest": "empty",
|
|
59
|
+
"sec-fetch-mode": "cors",
|
|
60
|
+
"sec-fetch-site": "same-origin",
|
|
61
|
+
"x-forwarded-for": fp["x-forwarded-for"],
|
|
62
|
+
"x-real-ip": fp["x-real-ip"],
|
|
63
|
+
"x-request-id": fp["x-request-id"],
|
|
64
|
+
}
|
|
65
|
+
if extra:
|
|
66
|
+
headers.update(extra)
|
|
67
|
+
return headers
|
|
68
|
+
|
|
69
|
+
def create(
|
|
70
|
+
self,
|
|
71
|
+
model: str = None,
|
|
72
|
+
prompt: str = None,
|
|
73
|
+
n: int = 1,
|
|
74
|
+
size: str = "1024x1024",
|
|
75
|
+
response_format: str = "url",
|
|
76
|
+
user: Optional[str] = None,
|
|
77
|
+
style: str = None,
|
|
78
|
+
aspect_ratio: str = None,
|
|
79
|
+
timeout: int = 60,
|
|
80
|
+
image_format: str = "png",
|
|
81
|
+
enhance: bool = True,
|
|
82
|
+
**kwargs,
|
|
83
|
+
) -> ImageResponse:
|
|
84
|
+
if not prompt:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
"Describe the image you want to create (use the 'prompt' property)."
|
|
87
|
+
)
|
|
88
|
+
body = {
|
|
89
|
+
"prompt": prompt,
|
|
90
|
+
"n": n,
|
|
91
|
+
"size": size,
|
|
92
|
+
"is_enhance": enhance,
|
|
93
|
+
"response_format": response_format,
|
|
94
|
+
}
|
|
95
|
+
try:
|
|
96
|
+
# Use direct session.request instead of request_with_proxy_fallback
|
|
97
|
+
resp = self.session.request(
|
|
98
|
+
"post",
|
|
99
|
+
f"{self.base_url}/v1/images/generations",
|
|
100
|
+
json=body,
|
|
101
|
+
headers=self.build_headers(),
|
|
102
|
+
timeout=timeout,
|
|
103
|
+
)
|
|
104
|
+
data = resp.json()
|
|
105
|
+
if not data.get("data") or len(data["data"]) == 0:
|
|
106
|
+
error_info = (
|
|
107
|
+
f", server info: {data.get('error')}" if data.get("error") else ""
|
|
108
|
+
)
|
|
109
|
+
raise RuntimeError(
|
|
110
|
+
f"Failed to process image. No data found{error_info}."
|
|
111
|
+
)
|
|
112
|
+
result = data["data"]
|
|
113
|
+
result_data = []
|
|
114
|
+
for item in result:
|
|
115
|
+
if response_format == "url":
|
|
116
|
+
result_data.append(ImageData(url=item.get("url")))
|
|
117
|
+
else:
|
|
118
|
+
result_data.append(ImageData(b64_json=item.get("b64_json")))
|
|
119
|
+
return ImageResponse(data=result_data)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
raise RuntimeError(f"An error occurred: {str(e)}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class GPT1Image(TTICompatibleProvider):
|
|
125
|
+
AVAILABLE_MODELS = ["gpt1image"]
|
|
126
|
+
|
|
127
|
+
def __init__(self):
|
|
128
|
+
self.images = Images(self)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def models(self):
|
|
132
|
+
class _ModelList:
|
|
133
|
+
def list(inner_self):
|
|
134
|
+
return type(self).AVAILABLE_MODELS
|
|
135
|
+
|
|
136
|
+
return _ModelList()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
from rich import print
|
|
141
|
+
|
|
142
|
+
client = GPT1Image()
|
|
143
|
+
response = client.images.create(
|
|
144
|
+
prompt="A futuristic robot in a neon city",
|
|
145
|
+
response_format="url",
|
|
146
|
+
n=1,
|
|
147
|
+
timeout=None,
|
|
148
|
+
)
|
|
149
|
+
print(response)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import base64
|
|
3
|
+
from typing import Optional, List, Dict, Any
|
|
4
|
+
from webscout.Provider.TTI.utils import (
|
|
5
|
+
ImageData,
|
|
6
|
+
ImageResponse,
|
|
7
|
+
)
|
|
8
|
+
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
9
|
+
from webscout.litagent import LitAgent
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Images(BaseImages):
|
|
14
|
+
def __init__(self, client):
|
|
15
|
+
self._client = client
|
|
16
|
+
|
|
17
|
+
def create(
|
|
18
|
+
self,
|
|
19
|
+
*,
|
|
20
|
+
model: str,
|
|
21
|
+
prompt: str,
|
|
22
|
+
n: int = 1,
|
|
23
|
+
size: str = "1024x1024",
|
|
24
|
+
response_format: str = "url",
|
|
25
|
+
user: Optional[str] = None,
|
|
26
|
+
style: str = "none",
|
|
27
|
+
aspect_ratio: str = "1:1",
|
|
28
|
+
timeout: int = 60,
|
|
29
|
+
image_format: str = "png",
|
|
30
|
+
seed: Optional[int] = None,
|
|
31
|
+
**kwargs,
|
|
32
|
+
) -> ImageResponse:
|
|
33
|
+
"""
|
|
34
|
+
Create images using the Imagen API.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
model: The model to use (e.g., "imagen_3_5")
|
|
38
|
+
prompt: The text prompt for image generation
|
|
39
|
+
n: Number of images to generate
|
|
40
|
+
size: Image size (e.g., "1024x1024")
|
|
41
|
+
response_format: "url" or "b64_json"
|
|
42
|
+
timeout: Request timeout in seconds
|
|
43
|
+
**kwargs: Additional parameters
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
ImageResponse: The generated images
|
|
47
|
+
"""
|
|
48
|
+
if not prompt:
|
|
49
|
+
raise ValueError("Prompt is required!")
|
|
50
|
+
|
|
51
|
+
result_data = []
|
|
52
|
+
|
|
53
|
+
for _ in range(n):
|
|
54
|
+
# Prepare the request payload
|
|
55
|
+
payload = {
|
|
56
|
+
"prompt": prompt,
|
|
57
|
+
"model": model,
|
|
58
|
+
"size": size,
|
|
59
|
+
"response_format": "url", # Always request URL from API
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Make the API request
|
|
64
|
+
resp = self._client.session.request(
|
|
65
|
+
"post",
|
|
66
|
+
self._client.api_endpoint,
|
|
67
|
+
json=payload,
|
|
68
|
+
timeout=timeout,
|
|
69
|
+
)
|
|
70
|
+
resp.raise_for_status()
|
|
71
|
+
|
|
72
|
+
# Parse the response
|
|
73
|
+
result = resp.json()
|
|
74
|
+
|
|
75
|
+
if not result or "data" not in result:
|
|
76
|
+
raise RuntimeError("Invalid response from Imagen API")
|
|
77
|
+
|
|
78
|
+
# Process each image in the response
|
|
79
|
+
for item in result["data"]:
|
|
80
|
+
if response_format == "url":
|
|
81
|
+
if "url" in item and item["url"]:
|
|
82
|
+
result_data.append(ImageData(url=item["url"]))
|
|
83
|
+
else:
|
|
84
|
+
raise RuntimeError("No URL found in API response")
|
|
85
|
+
|
|
86
|
+
elif response_format == "b64_json":
|
|
87
|
+
if "url" in item and item["url"]:
|
|
88
|
+
# Download the image and convert to base64
|
|
89
|
+
img_resp = self._client.session.request(
|
|
90
|
+
"get",
|
|
91
|
+
item["url"],
|
|
92
|
+
timeout=timeout,
|
|
93
|
+
)
|
|
94
|
+
img_resp.raise_for_status()
|
|
95
|
+
img_bytes = img_resp.content
|
|
96
|
+
b64_string = base64.b64encode(img_bytes).decode("utf-8")
|
|
97
|
+
result_data.append(ImageData(b64_json=b64_string))
|
|
98
|
+
elif "b64_json" in item and item["b64_json"]:
|
|
99
|
+
result_data.append(ImageData(b64_json=item["b64_json"]))
|
|
100
|
+
else:
|
|
101
|
+
raise RuntimeError("No image data found in API response")
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
105
|
+
|
|
106
|
+
except requests.exceptions.RequestException as e:
|
|
107
|
+
raise RuntimeError(f"Failed to generate image with Imagen API: {e}")
|
|
108
|
+
except Exception as e:
|
|
109
|
+
raise RuntimeError(f"Error processing Imagen API response: {e}")
|
|
110
|
+
|
|
111
|
+
return ImageResponse(created=int(time.time()), data=result_data)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ImagenAI(TTICompatibleProvider):
|
|
115
|
+
"""
|
|
116
|
+
Imagen API provider for text-to-image generation.
|
|
117
|
+
|
|
118
|
+
This provider interfaces with the Imagen API at imagen.exomlapi.com
|
|
119
|
+
to generate images from text prompts.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
AVAILABLE_MODELS = ["imagen_3_5", "imagen_3"]
|
|
123
|
+
|
|
124
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
125
|
+
"""
|
|
126
|
+
Initialize the Imagen API client.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
api_key: Optional API key for authentication (if required)
|
|
130
|
+
"""
|
|
131
|
+
self.api_endpoint = "https://imagen.exomlapi.com/v1/images/generations"
|
|
132
|
+
self.session = requests.Session()
|
|
133
|
+
self.user_agent = LitAgent().random()
|
|
134
|
+
self.api_key = api_key
|
|
135
|
+
|
|
136
|
+
# Set up headers based on the provided request details
|
|
137
|
+
self.headers = {
|
|
138
|
+
"accept": "*/*",
|
|
139
|
+
"accept-encoding": "gzip, deflate, br, zstd",
|
|
140
|
+
"accept-language": "en-US,en;q=0.9,en-IN;q=0.8",
|
|
141
|
+
"content-type": "application/json",
|
|
142
|
+
"dnt": "1",
|
|
143
|
+
"origin": "https://imagen.exomlapi.com",
|
|
144
|
+
"referer": "https://imagen.exomlapi.com/",
|
|
145
|
+
"sec-ch-ua": '"Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"',
|
|
146
|
+
"sec-ch-ua-mobile": "?0",
|
|
147
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
148
|
+
"sec-fetch-dest": "empty",
|
|
149
|
+
"sec-fetch-mode": "cors",
|
|
150
|
+
"sec-fetch-site": "same-origin",
|
|
151
|
+
"sec-gpc": "1",
|
|
152
|
+
"user-agent": self.user_agent,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Add API key to headers if provided
|
|
156
|
+
if self.api_key:
|
|
157
|
+
self.headers["authorization"] = f"Bearer {self.api_key}"
|
|
158
|
+
|
|
159
|
+
self.session.headers.update(self.headers)
|
|
160
|
+
self.images = Images(self)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def models(self):
|
|
164
|
+
"""
|
|
165
|
+
Get available models for the Imagen API.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Object with list() method that returns available models
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
class _ModelList:
|
|
172
|
+
def list(inner_self):
|
|
173
|
+
return type(self).AVAILABLE_MODELS
|
|
174
|
+
|
|
175
|
+
return _ModelList()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
from rich import print
|
|
180
|
+
|
|
181
|
+
# Example usage
|
|
182
|
+
client = ImagenAI()
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
response = client.images.create(
|
|
186
|
+
model="imagen_3_5",
|
|
187
|
+
prompt="red car",
|
|
188
|
+
response_format="url",
|
|
189
|
+
n=1,
|
|
190
|
+
size="1024x1024",
|
|
191
|
+
timeout=30,
|
|
192
|
+
)
|
|
193
|
+
print("Generated image successfully:")
|
|
194
|
+
print(response)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print(f"Error: {e}")
|
|
@@ -4,7 +4,10 @@ import uuid
|
|
|
4
4
|
import time
|
|
5
5
|
import tempfile
|
|
6
6
|
from typing import Optional, List
|
|
7
|
-
from webscout.Provider.TTI.utils import
|
|
7
|
+
from webscout.Provider.TTI.utils import (
|
|
8
|
+
ImageData,
|
|
9
|
+
ImageResponse
|
|
10
|
+
)
|
|
8
11
|
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
9
12
|
from io import BytesIO
|
|
10
13
|
from webscout.litagent import LitAgent
|
|
@@ -14,11 +17,13 @@ try:
|
|
|
14
17
|
except ImportError:
|
|
15
18
|
Image = None
|
|
16
19
|
|
|
20
|
+
|
|
17
21
|
class Images(BaseImages):
|
|
18
22
|
def __init__(self, client):
|
|
19
23
|
self._client = client
|
|
20
24
|
|
|
21
|
-
def create(
|
|
25
|
+
def create(
|
|
26
|
+
self,
|
|
22
27
|
model: str = "magicstudio",
|
|
23
28
|
prompt: str = None,
|
|
24
29
|
n: int = 1,
|
|
@@ -29,7 +34,7 @@ class Images(BaseImages):
|
|
|
29
34
|
aspect_ratio: str = None,
|
|
30
35
|
timeout: int = 60,
|
|
31
36
|
image_format: str = "jpg",
|
|
32
|
-
**kwargs
|
|
37
|
+
**kwargs,
|
|
33
38
|
) -> ImageResponse:
|
|
34
39
|
if not prompt:
|
|
35
40
|
raise ValueError("Prompt is required!")
|
|
@@ -43,7 +48,7 @@ class Images(BaseImages):
|
|
|
43
48
|
"Origin": "https://magicstudio.com",
|
|
44
49
|
"Referer": "https://magicstudio.com/ai-art-generator/",
|
|
45
50
|
"DNT": "1",
|
|
46
|
-
"Sec-GPC": "1"
|
|
51
|
+
"Sec-GPC": "1",
|
|
47
52
|
}
|
|
48
53
|
session = requests.Session()
|
|
49
54
|
session.headers.update(headers)
|
|
@@ -57,7 +62,11 @@ class Images(BaseImages):
|
|
|
57
62
|
"user_is_subscribed": "false",
|
|
58
63
|
"client_id": uuid.uuid4().hex,
|
|
59
64
|
}
|
|
60
|
-
resp = session.post(
|
|
65
|
+
resp = session.post(
|
|
66
|
+
api_url,
|
|
67
|
+
data=form_data,
|
|
68
|
+
timeout=timeout,
|
|
69
|
+
)
|
|
61
70
|
resp.raise_for_status()
|
|
62
71
|
img_bytes = resp.content
|
|
63
72
|
# Convert to png or jpeg in memory if needed
|
|
@@ -65,7 +74,10 @@ class Images(BaseImages):
|
|
|
65
74
|
with BytesIO(img_bytes) as input_io:
|
|
66
75
|
with Image.open(input_io) as im:
|
|
67
76
|
out_io = BytesIO()
|
|
68
|
-
if
|
|
77
|
+
if (
|
|
78
|
+
image_format.lower() == "jpeg"
|
|
79
|
+
or image_format.lower() == "jpg"
|
|
80
|
+
):
|
|
69
81
|
im = im.convert("RGB")
|
|
70
82
|
im.save(out_io, format="JPEG")
|
|
71
83
|
else:
|
|
@@ -73,37 +85,43 @@ class Images(BaseImages):
|
|
|
73
85
|
img_bytes = out_io.getvalue()
|
|
74
86
|
images.append(img_bytes)
|
|
75
87
|
if response_format == "url":
|
|
88
|
+
|
|
76
89
|
def upload_file_with_retry(img_bytes, image_format, max_retries=3):
|
|
77
90
|
ext = "jpg" if image_format.lower() in ("jpeg", "jpg") else "png"
|
|
78
91
|
for attempt in range(max_retries):
|
|
79
92
|
tmp_path = None
|
|
80
93
|
try:
|
|
81
|
-
with tempfile.NamedTemporaryFile(
|
|
94
|
+
with tempfile.NamedTemporaryFile(
|
|
95
|
+
suffix=f".{ext}", delete=False
|
|
96
|
+
) as tmp:
|
|
82
97
|
tmp.write(img_bytes)
|
|
83
98
|
tmp.flush()
|
|
84
99
|
tmp_path = tmp.name
|
|
85
|
-
with open(tmp_path,
|
|
100
|
+
with open(tmp_path, "rb") as f:
|
|
86
101
|
files = {
|
|
87
|
-
|
|
102
|
+
"fileToUpload": (f"image.{ext}", f, f"image/{ext}")
|
|
88
103
|
}
|
|
89
|
-
data = {
|
|
90
|
-
|
|
91
|
-
'json': 'true'
|
|
92
|
-
}
|
|
93
|
-
headers = {'User-Agent': agent.random()}
|
|
104
|
+
data = {"reqtype": "fileupload", "json": "true"}
|
|
105
|
+
headers = {"User-Agent": agent.random()}
|
|
94
106
|
if attempt > 0:
|
|
95
|
-
headers[
|
|
96
|
-
resp = requests.post(
|
|
107
|
+
headers["Connection"] = "close"
|
|
108
|
+
resp = requests.post(
|
|
109
|
+
"https://catbox.moe/user/api.php",
|
|
110
|
+
files=files,
|
|
111
|
+
data=data,
|
|
112
|
+
headers=headers,
|
|
113
|
+
timeout=timeout,
|
|
114
|
+
)
|
|
97
115
|
if resp.status_code == 200 and resp.text.strip():
|
|
98
116
|
text = resp.text.strip()
|
|
99
|
-
if text.startswith(
|
|
117
|
+
if text.startswith("http"):
|
|
100
118
|
return text
|
|
101
119
|
try:
|
|
102
120
|
result = resp.json()
|
|
103
121
|
if "url" in result:
|
|
104
122
|
return result["url"]
|
|
105
123
|
except Exception:
|
|
106
|
-
if
|
|
124
|
+
if "http" in text:
|
|
107
125
|
return text
|
|
108
126
|
except Exception:
|
|
109
127
|
if attempt < max_retries - 1:
|
|
@@ -115,22 +133,27 @@ class Images(BaseImages):
|
|
|
115
133
|
except Exception:
|
|
116
134
|
pass
|
|
117
135
|
return None
|
|
136
|
+
|
|
118
137
|
def upload_file_alternative(img_bytes, image_format):
|
|
119
138
|
try:
|
|
120
|
-
ext =
|
|
121
|
-
|
|
139
|
+
ext = (
|
|
140
|
+
"jpg" if image_format.lower() in ("jpeg", "jpg") else "png"
|
|
141
|
+
)
|
|
142
|
+
with tempfile.NamedTemporaryFile(
|
|
143
|
+
suffix=f".{ext}", delete=False
|
|
144
|
+
) as tmp:
|
|
122
145
|
tmp.write(img_bytes)
|
|
123
146
|
tmp.flush()
|
|
124
147
|
tmp_path = tmp.name
|
|
125
148
|
try:
|
|
126
149
|
if not os.path.isfile(tmp_path):
|
|
127
150
|
return None
|
|
128
|
-
with open(tmp_path,
|
|
129
|
-
files = {
|
|
130
|
-
alt_resp = requests.post(
|
|
151
|
+
with open(tmp_path, "rb") as img_file:
|
|
152
|
+
files = {"file": img_file}
|
|
153
|
+
alt_resp = requests.post("https://0x0.st", files=files)
|
|
131
154
|
alt_resp.raise_for_status()
|
|
132
155
|
image_url = alt_resp.text.strip()
|
|
133
|
-
if not image_url.startswith(
|
|
156
|
+
if not image_url.startswith("http"):
|
|
134
157
|
return None
|
|
135
158
|
return image_url
|
|
136
159
|
except Exception:
|
|
@@ -142,32 +165,36 @@ class Images(BaseImages):
|
|
|
142
165
|
pass
|
|
143
166
|
except Exception:
|
|
144
167
|
return None
|
|
168
|
+
|
|
145
169
|
uploaded_url = upload_file_with_retry(img_bytes, image_format)
|
|
146
170
|
if not uploaded_url:
|
|
147
171
|
uploaded_url = upload_file_alternative(img_bytes, image_format)
|
|
148
172
|
if uploaded_url:
|
|
149
173
|
urls.append(uploaded_url)
|
|
150
174
|
else:
|
|
151
|
-
raise RuntimeError(
|
|
175
|
+
raise RuntimeError(
|
|
176
|
+
"Failed to upload image to catbox.moe using all available methods"
|
|
177
|
+
)
|
|
152
178
|
result_data = []
|
|
153
179
|
if response_format == "url":
|
|
154
180
|
for url in urls:
|
|
155
181
|
result_data.append(ImageData(url=url))
|
|
156
182
|
elif response_format == "b64_json":
|
|
157
183
|
import base64
|
|
184
|
+
|
|
158
185
|
for img in images:
|
|
159
186
|
b64 = base64.b64encode(img).decode("utf-8")
|
|
160
187
|
result_data.append(ImageData(b64_json=b64))
|
|
161
188
|
else:
|
|
162
189
|
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
163
190
|
from time import time as _time
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
191
|
+
|
|
192
|
+
return ImageResponse(created=int(_time()), data=result_data)
|
|
193
|
+
|
|
168
194
|
|
|
169
195
|
class MagicStudioAI(TTICompatibleProvider):
|
|
170
196
|
AVAILABLE_MODELS = ["magicstudio"]
|
|
197
|
+
|
|
171
198
|
def __init__(self):
|
|
172
199
|
self.api_endpoint = "https://ai-api.magicstudio.com/api/ai-art-generator"
|
|
173
200
|
self.session = requests.Session()
|
|
@@ -182,15 +209,19 @@ class MagicStudioAI(TTICompatibleProvider):
|
|
|
182
209
|
}
|
|
183
210
|
self.session.headers.update(self.headers)
|
|
184
211
|
self.images = Images(self)
|
|
212
|
+
|
|
185
213
|
@property
|
|
186
214
|
def models(self):
|
|
187
215
|
class _ModelList:
|
|
188
216
|
def list(inner_self):
|
|
189
217
|
return type(self).AVAILABLE_MODELS
|
|
218
|
+
|
|
190
219
|
return _ModelList()
|
|
191
220
|
|
|
221
|
+
|
|
192
222
|
if __name__ == "__main__":
|
|
193
223
|
from rich import print
|
|
224
|
+
|
|
194
225
|
client = MagicStudioAI()
|
|
195
226
|
response = client.images.create(
|
|
196
227
|
prompt="A cool cyberpunk city at night",
|