webscout 8.3.1__py3-none-any.whl → 8.3.3__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.

Files changed (114) hide show
  1. webscout/AIutel.py +180 -78
  2. webscout/Bing_search.py +417 -0
  3. webscout/Extra/gguf.py +706 -177
  4. webscout/Provider/AISEARCH/__init__.py +1 -0
  5. webscout/Provider/AISEARCH/genspark_search.py +7 -7
  6. webscout/Provider/AISEARCH/stellar_search.py +132 -0
  7. webscout/Provider/ExaChat.py +84 -58
  8. webscout/Provider/GeminiProxy.py +140 -0
  9. webscout/Provider/HeckAI.py +85 -80
  10. webscout/Provider/Jadve.py +56 -50
  11. webscout/Provider/MCPCore.py +78 -75
  12. webscout/Provider/MiniMax.py +207 -0
  13. webscout/Provider/Nemotron.py +41 -13
  14. webscout/Provider/Netwrck.py +34 -51
  15. webscout/Provider/OPENAI/BLACKBOXAI.py +0 -4
  16. webscout/Provider/OPENAI/GeminiProxy.py +328 -0
  17. webscout/Provider/OPENAI/MiniMax.py +298 -0
  18. webscout/Provider/OPENAI/README.md +32 -29
  19. webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
  20. webscout/Provider/OPENAI/TogetherAI.py +4 -17
  21. webscout/Provider/OPENAI/__init__.py +17 -1
  22. webscout/Provider/OPENAI/autoproxy.py +1067 -39
  23. webscout/Provider/OPENAI/base.py +17 -76
  24. webscout/Provider/OPENAI/deepinfra.py +42 -108
  25. webscout/Provider/OPENAI/e2b.py +0 -1
  26. webscout/Provider/OPENAI/flowith.py +179 -166
  27. webscout/Provider/OPENAI/friendli.py +233 -0
  28. webscout/Provider/OPENAI/mcpcore.py +109 -70
  29. webscout/Provider/OPENAI/monochat.py +329 -0
  30. webscout/Provider/OPENAI/pydantic_imports.py +1 -172
  31. webscout/Provider/OPENAI/scirachat.py +59 -51
  32. webscout/Provider/OPENAI/toolbaz.py +3 -9
  33. webscout/Provider/OPENAI/typegpt.py +1 -1
  34. webscout/Provider/OPENAI/utils.py +19 -42
  35. webscout/Provider/OPENAI/x0gpt.py +14 -2
  36. webscout/Provider/OPENAI/xenai.py +514 -0
  37. webscout/Provider/OPENAI/yep.py +8 -2
  38. webscout/Provider/OpenGPT.py +54 -32
  39. webscout/Provider/PI.py +58 -84
  40. webscout/Provider/StandardInput.py +32 -13
  41. webscout/Provider/TTI/README.md +9 -9
  42. webscout/Provider/TTI/__init__.py +3 -1
  43. webscout/Provider/TTI/aiarta.py +92 -78
  44. webscout/Provider/TTI/bing.py +231 -0
  45. webscout/Provider/TTI/infip.py +212 -0
  46. webscout/Provider/TTI/monochat.py +220 -0
  47. webscout/Provider/TTS/speechma.py +45 -39
  48. webscout/Provider/TeachAnything.py +11 -3
  49. webscout/Provider/TextPollinationsAI.py +78 -70
  50. webscout/Provider/TogetherAI.py +350 -0
  51. webscout/Provider/Venice.py +37 -46
  52. webscout/Provider/VercelAI.py +27 -24
  53. webscout/Provider/WiseCat.py +35 -35
  54. webscout/Provider/WrDoChat.py +22 -26
  55. webscout/Provider/WritingMate.py +26 -22
  56. webscout/Provider/XenAI.py +324 -0
  57. webscout/Provider/__init__.py +10 -5
  58. webscout/Provider/deepseek_assistant.py +378 -0
  59. webscout/Provider/granite.py +48 -57
  60. webscout/Provider/koala.py +51 -39
  61. webscout/Provider/learnfastai.py +49 -64
  62. webscout/Provider/llmchat.py +79 -93
  63. webscout/Provider/llmchatco.py +63 -78
  64. webscout/Provider/multichat.py +51 -40
  65. webscout/Provider/oivscode.py +1 -1
  66. webscout/Provider/scira_chat.py +159 -96
  67. webscout/Provider/scnet.py +13 -13
  68. webscout/Provider/searchchat.py +13 -13
  69. webscout/Provider/sonus.py +12 -11
  70. webscout/Provider/toolbaz.py +25 -8
  71. webscout/Provider/turboseek.py +41 -42
  72. webscout/Provider/typefully.py +27 -12
  73. webscout/Provider/typegpt.py +41 -46
  74. webscout/Provider/uncovr.py +55 -90
  75. webscout/Provider/x0gpt.py +33 -17
  76. webscout/Provider/yep.py +79 -96
  77. webscout/auth/__init__.py +55 -0
  78. webscout/auth/api_key_manager.py +189 -0
  79. webscout/auth/auth_system.py +100 -0
  80. webscout/auth/config.py +76 -0
  81. webscout/auth/database.py +400 -0
  82. webscout/auth/exceptions.py +67 -0
  83. webscout/auth/middleware.py +248 -0
  84. webscout/auth/models.py +130 -0
  85. webscout/auth/providers.py +279 -0
  86. webscout/auth/rate_limiter.py +254 -0
  87. webscout/auth/request_models.py +127 -0
  88. webscout/auth/request_processing.py +226 -0
  89. webscout/auth/routes.py +550 -0
  90. webscout/auth/schemas.py +103 -0
  91. webscout/auth/server.py +367 -0
  92. webscout/client.py +121 -70
  93. webscout/litagent/Readme.md +68 -55
  94. webscout/litagent/agent.py +99 -9
  95. webscout/scout/core/scout.py +104 -26
  96. webscout/scout/element.py +139 -18
  97. webscout/swiftcli/core/cli.py +14 -3
  98. webscout/swiftcli/decorators/output.py +59 -9
  99. webscout/update_checker.py +31 -49
  100. webscout/version.py +1 -1
  101. webscout/webscout_search.py +4 -12
  102. webscout/webscout_search_async.py +3 -10
  103. webscout/yep_search.py +2 -11
  104. {webscout-8.3.1.dist-info → webscout-8.3.3.dist-info}/METADATA +141 -99
  105. {webscout-8.3.1.dist-info → webscout-8.3.3.dist-info}/RECORD +109 -83
  106. {webscout-8.3.1.dist-info → webscout-8.3.3.dist-info}/entry_points.txt +1 -1
  107. webscout/Provider/HF_space/__init__.py +0 -0
  108. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  109. webscout/Provider/OPENAI/api.py +0 -1320
  110. webscout/Provider/TTI/fastflux.py +0 -233
  111. webscout/Provider/Writecream.py +0 -246
  112. {webscout-8.3.1.dist-info → webscout-8.3.3.dist-info}/WHEEL +0 -0
  113. {webscout-8.3.1.dist-info → webscout-8.3.3.dist-info}/licenses/LICENSE.md +0 -0
  114. {webscout-8.3.1.dist-info → webscout-8.3.3.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -0,0 +1,212 @@
1
+ """InfipAI TTI-Compatible Provider - Generate images with Infip AI! 🎨
2
+
3
+ This module provides access to Infip's image generation API through a unified interface.
4
+ Supports img3, img4, and uncen models with various aspect ratios and customization options.
5
+
6
+ Example Usage:
7
+ from webscout.Provider.TTI.infip import InfipAI
8
+
9
+ # Initialize the provider
10
+ client = InfipAI()
11
+
12
+ # Generate an image
13
+ response = client.images.create(
14
+ model="img3",
15
+ prompt="A beautiful sunset over mountains",
16
+ n=1,
17
+ aspect_ratio="IMAGE_ASPECT_RATIO_LANDSCAPE",
18
+ seed=42
19
+ )
20
+
21
+ # Get the image URL
22
+ image_url = response.data[0].url
23
+ print(f"Generated image: {image_url}")
24
+
25
+ Available Models:
26
+ - img3: High-quality image generation
27
+ - img4: Enhanced image generation model
28
+ - uncen: Uncensored image generation model
29
+
30
+ Supported Aspect Ratios:
31
+ - IMAGE_ASPECT_RATIO_LANDSCAPE: 16:9 landscape
32
+ - IMAGE_ASPECT_RATIO_PORTRAIT: 9:16 portrait
33
+ - IMAGE_ASPECT_RATIO_SQUARE: 1:1 square
34
+ """
35
+
36
+ import requests
37
+ from typing import Optional
38
+ from webscout.Provider.TTI.utils import (
39
+ ImageData,
40
+ ImageResponse
41
+ )
42
+ from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
43
+ from webscout.litagent import LitAgent
44
+ import time
45
+
46
+
47
+ class Images(BaseImages):
48
+ def __init__(self, client):
49
+ self._client = client
50
+
51
+ def create(
52
+ self,
53
+ *,
54
+ model: str,
55
+ prompt: str,
56
+ n: int = 1,
57
+ size: str = "1024x1024",
58
+ response_format: str = "url",
59
+ user: Optional[str] = None,
60
+ style: str = "none",
61
+ aspect_ratio: str = "IMAGE_ASPECT_RATIO_LANDSCAPE",
62
+ timeout: int = 60,
63
+ image_format: str = "png",
64
+ seed: Optional[int] = None,
65
+ **kwargs,
66
+ ) -> ImageResponse:
67
+ """
68
+ Create images using Infip AI API.
69
+
70
+ Args:
71
+ model: The model to use ("img3", "img4", or "uncen")
72
+ prompt: Text description of the image to generate
73
+ n: Number of images to generate (default: 1)
74
+ size: Image size (ignored, aspect_ratio is used instead)
75
+ response_format: "url" or "b64_json" (default: "url")
76
+ user: Optional user identifier (ignored)
77
+ style: Optional style (ignored)
78
+ aspect_ratio: Image aspect ratio ("IMAGE_ASPECT_RATIO_LANDSCAPE",
79
+ "IMAGE_ASPECT_RATIO_PORTRAIT", "IMAGE_ASPECT_RATIO_SQUARE")
80
+ timeout: Request timeout in seconds (default: 60)
81
+ image_format: Image format "png" or "jpeg" (ignored by API)
82
+ seed: Random seed for reproducibility (default: 0 for random)
83
+ **kwargs: Additional parameters
84
+
85
+ Returns:
86
+ ImageResponse: The generated images
87
+
88
+ Raises:
89
+ ValueError: If model is not supported
90
+ RuntimeError: If image generation fails
91
+ """
92
+ if model not in self._client.AVAILABLE_MODELS:
93
+ raise ValueError(f"Model '{model}' not supported. Available models: {self._client.AVAILABLE_MODELS}")
94
+
95
+ # Validate aspect ratio
96
+ valid_ratios = [
97
+ "IMAGE_ASPECT_RATIO_LANDSCAPE",
98
+ "IMAGE_ASPECT_RATIO_PORTRAIT",
99
+ "IMAGE_ASPECT_RATIO_SQUARE"
100
+ ]
101
+ if aspect_ratio not in valid_ratios:
102
+ aspect_ratio = "IMAGE_ASPECT_RATIO_LANDSCAPE"
103
+
104
+ # Prepare request payload
105
+ payload = {
106
+ "prompt": prompt,
107
+ "num_images": n,
108
+ "seed": seed if seed is not None else 0,
109
+ "aspect_ratio": aspect_ratio,
110
+ "models": model
111
+ }
112
+
113
+ try:
114
+ # Make API request
115
+ response = self._client.session.post(
116
+ self._client.api_endpoint,
117
+ json=payload,
118
+ timeout=timeout
119
+ )
120
+ response.raise_for_status()
121
+
122
+ # Parse response
123
+ result = response.json()
124
+
125
+ if "images" not in result or not result["images"]:
126
+ raise RuntimeError("No images returned from Infip API")
127
+
128
+ # Process response based on format
129
+ result_data = []
130
+
131
+ if response_format == "url":
132
+ for image_url in result["images"]:
133
+ result_data.append(ImageData(url=image_url))
134
+ elif response_format == "b64_json":
135
+ # For b64_json format, we need to download and encode the images
136
+ import base64
137
+ for image_url in result["images"]:
138
+ try:
139
+ img_response = self._client.session.get(image_url, timeout=timeout)
140
+ img_response.raise_for_status()
141
+ b64_data = base64.b64encode(img_response.content).decode('utf-8')
142
+ result_data.append(ImageData(b64_json=b64_data))
143
+ except Exception as e:
144
+ raise RuntimeError(f"Failed to download image for base64 encoding: {e}")
145
+ else:
146
+ raise ValueError("response_format must be 'url' or 'b64_json'")
147
+
148
+ return ImageResponse(created=int(time.time()), data=result_data)
149
+
150
+ except requests.RequestException as e:
151
+ raise RuntimeError(f"Failed to generate image with Infip API: {e}")
152
+ except Exception as e:
153
+ raise RuntimeError(f"Unexpected error during image generation: {e}")
154
+
155
+
156
+ class InfipAI(TTICompatibleProvider):
157
+ """
158
+ Infip AI provider for text-to-image generation.
159
+
160
+ This provider interfaces with the Infip API to generate images from text prompts.
161
+ It supports multiple models and aspect ratios for flexible image creation.
162
+ """
163
+
164
+ AVAILABLE_MODELS = ["img3", "img4", "uncen"]
165
+
166
+ def __init__(self, **kwargs):
167
+ """
168
+ Initialize the Infip AI provider.
169
+
170
+ Args:
171
+ **kwargs: Additional configuration options
172
+ """
173
+ self.api_endpoint = "https://api.infip.pro/generate"
174
+ self.session = requests.Session()
175
+
176
+ # Set up headers with user agent
177
+ agent = LitAgent()
178
+ self.headers = {
179
+ "accept": "application/json",
180
+ "Content-Type": "application/json",
181
+ "User-Agent": agent.random()
182
+ }
183
+ self.session.headers.update(self.headers)
184
+
185
+ # Initialize the images interface
186
+ self.images = Images(self)
187
+
188
+ @property
189
+ def models(self):
190
+ """
191
+ Get available models for the provider.
192
+
193
+ Returns:
194
+ Object with list() method that returns available model names
195
+ """
196
+ class ModelList:
197
+ def list(self):
198
+ return InfipAI.AVAILABLE_MODELS
199
+
200
+ return ModelList()
201
+
202
+
203
+ if __name__ == "__main__":
204
+ client = InfipAI()
205
+ response = client.images.create(
206
+ model="img3",
207
+ prompt="A beautiful sunset over mountains",
208
+ n=1,
209
+ aspect_ratio="IMAGE_ASPECT_RATIO_LANDSCAPE",
210
+ seed=42
211
+ )
212
+ print(response.data[0].url)
@@ -0,0 +1,220 @@
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
+ from requests.adapters import HTTPAdapter
11
+ from urllib3.util.retry import Retry
12
+ import os
13
+ import tempfile
14
+ import time
15
+ import json
16
+ from io import BytesIO
17
+
18
+ try:
19
+ from PIL import Image
20
+ except ImportError:
21
+ Image = None
22
+
23
+
24
+ class Images(BaseImages):
25
+ def __init__(self, client):
26
+ self._client = client
27
+
28
+ def create(
29
+ self,
30
+ *,
31
+ model: str,
32
+ prompt: str,
33
+ n: int = 1,
34
+ size: str = "1024x1024",
35
+ response_format: str = "b64_json",
36
+ user: Optional[str] = None,
37
+ style: str = None,
38
+ aspect_ratio: str = None,
39
+ timeout: int = 60,
40
+ image_format: str = "png",
41
+ **kwargs,
42
+ ) -> ImageResponse:
43
+ if not prompt:
44
+ raise ValueError(
45
+ "Describe the image you want to create (use the 'prompt' property)."
46
+ )
47
+ # Only one image is supported by MonoChat API, but keep n for compatibility
48
+ body = {
49
+ "prompt": prompt,
50
+ "model": model
51
+ }
52
+ session = self._client.session
53
+ headers = self._client.headers
54
+ images = []
55
+ urls = []
56
+
57
+ def upload_file_with_retry(img_bytes, image_format, max_retries=3):
58
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
59
+ for attempt in range(max_retries):
60
+ tmp_path = None
61
+ try:
62
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
63
+ tmp.write(img_bytes)
64
+ tmp.flush()
65
+ tmp_path = tmp.name
66
+ with open(tmp_path, "rb") as f:
67
+ files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
68
+ data = {"reqtype": "fileupload", "json": "true"}
69
+ headers = {"User-Agent": LitAgent().random()}
70
+ if attempt > 0:
71
+ headers["Connection"] = "close"
72
+ resp = requests.post(
73
+ "https://catbox.moe/user/api.php",
74
+ files=files,
75
+ data=data,
76
+ headers=headers,
77
+ timeout=timeout,
78
+ )
79
+ if resp.status_code == 200 and resp.text.strip():
80
+ text = resp.text.strip()
81
+ if text.startswith("http"):
82
+ return text
83
+ try:
84
+ result = resp.json()
85
+ if "url" in result:
86
+ return result["url"]
87
+ except json.JSONDecodeError:
88
+ if "http" in text:
89
+ return text
90
+ except Exception:
91
+ if attempt < max_retries - 1:
92
+ time.sleep(1 * (attempt + 1))
93
+ finally:
94
+ if tmp_path and os.path.isfile(tmp_path):
95
+ try:
96
+ os.remove(tmp_path)
97
+ except Exception:
98
+ pass
99
+ return None
100
+
101
+ def upload_file_alternative(img_bytes, image_format):
102
+ try:
103
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
104
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
105
+ tmp.write(img_bytes)
106
+ tmp.flush()
107
+ tmp_path = tmp.name
108
+ try:
109
+ if not os.path.isfile(tmp_path):
110
+ return None
111
+ with open(tmp_path, "rb") as img_file:
112
+ files = {"file": img_file}
113
+ response = requests.post("https://0x0.st", files=files)
114
+ response.raise_for_status()
115
+ image_url = response.text.strip()
116
+ if not image_url.startswith("http"):
117
+ return None
118
+ return image_url
119
+ except Exception:
120
+ return None
121
+ finally:
122
+ try:
123
+ os.remove(tmp_path)
124
+ except Exception:
125
+ pass
126
+ except Exception:
127
+ return None
128
+
129
+ try:
130
+ resp = session.post(
131
+ f"{self._client.api_endpoint}/image",
132
+ json=body,
133
+ headers=headers,
134
+ timeout=timeout,
135
+ )
136
+ resp.raise_for_status()
137
+ data = resp.json()
138
+ if not data.get("image"):
139
+ raise RuntimeError("Failed to process image. No image data found.")
140
+ # Always decode the base64 image
141
+ image_bytes = base64.b64decode(data.get("image"))
142
+ if response_format == "b64_json":
143
+ result_data = [ImageData(b64_json=data.get("image"))]
144
+ elif response_format == "url":
145
+ if Image is None:
146
+ raise ImportError("Pillow (PIL) is required for image format conversion.")
147
+ # Convert to png or jpeg in memory
148
+ with BytesIO(image_bytes) as input_io:
149
+ with Image.open(input_io) as im:
150
+ out_io = BytesIO()
151
+ if image_format.lower() == "jpeg":
152
+ im = im.convert("RGB")
153
+ im.save(out_io, format="JPEG")
154
+ else:
155
+ im.save(out_io, format="PNG")
156
+ img_bytes = out_io.getvalue()
157
+ # Try primary upload method with retries
158
+ uploaded_url = upload_file_with_retry(img_bytes, image_format)
159
+ # If primary method fails, try alternative
160
+ if not uploaded_url:
161
+ uploaded_url = upload_file_alternative(img_bytes, image_format)
162
+ if uploaded_url:
163
+ result_data = [ImageData(url=uploaded_url)]
164
+ else:
165
+ raise RuntimeError("Failed to upload image to catbox.moe using all available methods")
166
+ else:
167
+ raise ValueError("response_format must be 'url' or 'b64_json'")
168
+ from time import time as _time
169
+ return ImageResponse(created=int(_time()), data=result_data)
170
+ except Exception as e:
171
+ raise RuntimeError(f"An error occurred: {str(e)}")
172
+
173
+
174
+ class MonoChatAI(TTICompatibleProvider):
175
+ AVAILABLE_MODELS = ["nextlm-image-1", "gpt-image-1", "dall-e-3", "dall-e-2"]
176
+
177
+ def __init__(self):
178
+ self.api_endpoint = "https://www.chatwithmono.xyz/api"
179
+ self.session = requests.Session()
180
+ self._setup_session_with_retries()
181
+ self.user_agent = LitAgent().random()
182
+ self.headers = {
183
+ "accept": "*/*",
184
+ "content-type": "application/json",
185
+ "origin": "https://www.chatwithmono.xyz",
186
+ "referer": "https://www.chatwithmono.xyz/",
187
+ "user-agent": self.user_agent,
188
+ }
189
+ self.session.headers.update(self.headers)
190
+ self.images = Images(self)
191
+
192
+ def _setup_session_with_retries(self):
193
+ retry_strategy = Retry(
194
+ total=3,
195
+ status_forcelist=[429, 500, 502, 503, 504],
196
+ backoff_factor=1,
197
+ allowed_methods=["HEAD", "GET", "OPTIONS", "POST"],
198
+ )
199
+ adapter = HTTPAdapter(max_retries=retry_strategy)
200
+ self.session.mount("http://", adapter)
201
+ self.session.mount("https://", adapter)
202
+
203
+ @property
204
+ def models(self):
205
+ class _ModelList:
206
+ def list(inner_self):
207
+ return type(self).AVAILABLE_MODELS
208
+ return _ModelList()
209
+
210
+
211
+ if __name__ == "__main__":
212
+ from rich import print
213
+ client = MonoChatAI()
214
+ response = client.images.create(
215
+ model="dall-e-3",
216
+ prompt="A red car on a sunny day",
217
+ response_format="url",
218
+ timeout=60000,
219
+ )
220
+ print(response)