webscout 8.2.8__py3-none-any.whl → 8.2.9__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 (184) hide show
  1. webscout/AIauto.py +32 -14
  2. webscout/AIbase.py +96 -37
  3. webscout/AIutel.py +491 -87
  4. webscout/Bard.py +441 -323
  5. webscout/Extra/GitToolkit/__init__.py +10 -10
  6. webscout/Extra/YTToolkit/ytapi/video.py +232 -232
  7. webscout/Litlogger/README.md +10 -0
  8. webscout/Litlogger/__init__.py +7 -59
  9. webscout/Litlogger/formats.py +4 -0
  10. webscout/Litlogger/handlers.py +103 -0
  11. webscout/Litlogger/levels.py +13 -0
  12. webscout/Litlogger/logger.py +92 -0
  13. webscout/Provider/AISEARCH/Perplexity.py +332 -358
  14. webscout/Provider/AISEARCH/felo_search.py +9 -35
  15. webscout/Provider/AISEARCH/genspark_search.py +30 -56
  16. webscout/Provider/AISEARCH/hika_search.py +4 -16
  17. webscout/Provider/AISEARCH/iask_search.py +410 -436
  18. webscout/Provider/AISEARCH/monica_search.py +4 -30
  19. webscout/Provider/AISEARCH/scira_search.py +6 -32
  20. webscout/Provider/AISEARCH/webpilotai_search.py +38 -64
  21. webscout/Provider/Blackboxai.py +153 -35
  22. webscout/Provider/Deepinfra.py +339 -339
  23. webscout/Provider/ExaChat.py +358 -358
  24. webscout/Provider/Gemini.py +169 -169
  25. webscout/Provider/GithubChat.py +1 -2
  26. webscout/Provider/Glider.py +3 -3
  27. webscout/Provider/HeckAI.py +171 -81
  28. webscout/Provider/OPENAI/BLACKBOXAI.py +766 -735
  29. webscout/Provider/OPENAI/Cloudflare.py +7 -7
  30. webscout/Provider/OPENAI/FreeGemini.py +6 -5
  31. webscout/Provider/OPENAI/NEMOTRON.py +8 -20
  32. webscout/Provider/OPENAI/Qwen3.py +283 -0
  33. webscout/Provider/OPENAI/README.md +952 -1253
  34. webscout/Provider/OPENAI/TwoAI.py +357 -0
  35. webscout/Provider/OPENAI/__init__.py +5 -1
  36. webscout/Provider/OPENAI/ai4chat.py +40 -40
  37. webscout/Provider/OPENAI/api.py +808 -649
  38. webscout/Provider/OPENAI/c4ai.py +3 -3
  39. webscout/Provider/OPENAI/chatgpt.py +555 -555
  40. webscout/Provider/OPENAI/chatgptclone.py +493 -487
  41. webscout/Provider/OPENAI/chatsandbox.py +4 -3
  42. webscout/Provider/OPENAI/copilot.py +242 -0
  43. webscout/Provider/OPENAI/deepinfra.py +5 -2
  44. webscout/Provider/OPENAI/e2b.py +63 -5
  45. webscout/Provider/OPENAI/exaai.py +416 -410
  46. webscout/Provider/OPENAI/exachat.py +444 -443
  47. webscout/Provider/OPENAI/freeaichat.py +2 -2
  48. webscout/Provider/OPENAI/glider.py +5 -2
  49. webscout/Provider/OPENAI/groq.py +5 -2
  50. webscout/Provider/OPENAI/heckai.py +308 -307
  51. webscout/Provider/OPENAI/mcpcore.py +8 -2
  52. webscout/Provider/OPENAI/multichat.py +4 -4
  53. webscout/Provider/OPENAI/netwrck.py +6 -5
  54. webscout/Provider/OPENAI/oivscode.py +287 -0
  55. webscout/Provider/OPENAI/opkfc.py +496 -496
  56. webscout/Provider/OPENAI/pydantic_imports.py +172 -0
  57. webscout/Provider/OPENAI/scirachat.py +15 -9
  58. webscout/Provider/OPENAI/sonus.py +304 -303
  59. webscout/Provider/OPENAI/standardinput.py +433 -433
  60. webscout/Provider/OPENAI/textpollinations.py +4 -4
  61. webscout/Provider/OPENAI/toolbaz.py +413 -413
  62. webscout/Provider/OPENAI/typefully.py +3 -3
  63. webscout/Provider/OPENAI/typegpt.py +11 -5
  64. webscout/Provider/OPENAI/uncovrAI.py +463 -462
  65. webscout/Provider/OPENAI/utils.py +90 -79
  66. webscout/Provider/OPENAI/venice.py +431 -425
  67. webscout/Provider/OPENAI/wisecat.py +387 -381
  68. webscout/Provider/OPENAI/writecream.py +3 -3
  69. webscout/Provider/OPENAI/x0gpt.py +365 -378
  70. webscout/Provider/OPENAI/yep.py +39 -13
  71. webscout/Provider/TTI/README.md +55 -101
  72. webscout/Provider/TTI/__init__.py +4 -9
  73. webscout/Provider/TTI/aiarta.py +365 -0
  74. webscout/Provider/TTI/artbit.py +0 -0
  75. webscout/Provider/TTI/base.py +64 -0
  76. webscout/Provider/TTI/fastflux.py +200 -0
  77. webscout/Provider/TTI/magicstudio.py +201 -0
  78. webscout/Provider/TTI/piclumen.py +203 -0
  79. webscout/Provider/TTI/pixelmuse.py +225 -0
  80. webscout/Provider/TTI/pollinations.py +221 -0
  81. webscout/Provider/TTI/utils.py +11 -0
  82. webscout/Provider/TTS/__init__.py +2 -1
  83. webscout/Provider/TTS/base.py +159 -159
  84. webscout/Provider/TTS/openai_fm.py +129 -0
  85. webscout/Provider/TextPollinationsAI.py +308 -308
  86. webscout/Provider/TwoAI.py +239 -44
  87. webscout/Provider/UNFINISHED/Youchat.py +330 -330
  88. webscout/Provider/UNFINISHED/puterjs.py +635 -0
  89. webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
  90. webscout/Provider/Writecream.py +246 -246
  91. webscout/Provider/__init__.py +2 -0
  92. webscout/Provider/ai4chat.py +33 -8
  93. webscout/Provider/koala.py +169 -169
  94. webscout/Provider/oivscode.py +309 -0
  95. webscout/Provider/samurai.py +3 -2
  96. webscout/Provider/typegpt.py +3 -3
  97. webscout/Provider/uncovr.py +368 -368
  98. webscout/client.py +70 -0
  99. webscout/litprinter/__init__.py +58 -58
  100. webscout/optimizers.py +419 -419
  101. webscout/scout/README.md +3 -1
  102. webscout/scout/core/crawler.py +134 -64
  103. webscout/scout/core/scout.py +148 -109
  104. webscout/scout/element.py +106 -88
  105. webscout/swiftcli/Readme.md +323 -323
  106. webscout/swiftcli/plugins/manager.py +9 -2
  107. webscout/version.py +1 -1
  108. webscout/zeroart/__init__.py +134 -134
  109. webscout/zeroart/effects.py +100 -100
  110. webscout/zeroart/fonts.py +1238 -1238
  111. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/METADATA +159 -35
  112. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/RECORD +116 -161
  113. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/WHEEL +1 -1
  114. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/entry_points.txt +1 -0
  115. webscout/Litlogger/Readme.md +0 -175
  116. webscout/Litlogger/core/__init__.py +0 -6
  117. webscout/Litlogger/core/level.py +0 -23
  118. webscout/Litlogger/core/logger.py +0 -165
  119. webscout/Litlogger/handlers/__init__.py +0 -12
  120. webscout/Litlogger/handlers/console.py +0 -33
  121. webscout/Litlogger/handlers/file.py +0 -143
  122. webscout/Litlogger/handlers/network.py +0 -173
  123. webscout/Litlogger/styles/__init__.py +0 -7
  124. webscout/Litlogger/styles/colors.py +0 -249
  125. webscout/Litlogger/styles/formats.py +0 -458
  126. webscout/Litlogger/styles/text.py +0 -87
  127. webscout/Litlogger/utils/__init__.py +0 -6
  128. webscout/Litlogger/utils/detectors.py +0 -153
  129. webscout/Litlogger/utils/formatters.py +0 -200
  130. webscout/Provider/TTI/AiForce/README.md +0 -159
  131. webscout/Provider/TTI/AiForce/__init__.py +0 -22
  132. webscout/Provider/TTI/AiForce/async_aiforce.py +0 -224
  133. webscout/Provider/TTI/AiForce/sync_aiforce.py +0 -245
  134. webscout/Provider/TTI/FreeAIPlayground/README.md +0 -99
  135. webscout/Provider/TTI/FreeAIPlayground/__init__.py +0 -9
  136. webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +0 -181
  137. webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +0 -180
  138. webscout/Provider/TTI/ImgSys/README.md +0 -174
  139. webscout/Provider/TTI/ImgSys/__init__.py +0 -23
  140. webscout/Provider/TTI/ImgSys/async_imgsys.py +0 -202
  141. webscout/Provider/TTI/ImgSys/sync_imgsys.py +0 -195
  142. webscout/Provider/TTI/MagicStudio/README.md +0 -101
  143. webscout/Provider/TTI/MagicStudio/__init__.py +0 -2
  144. webscout/Provider/TTI/MagicStudio/async_magicstudio.py +0 -111
  145. webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +0 -109
  146. webscout/Provider/TTI/Nexra/README.md +0 -155
  147. webscout/Provider/TTI/Nexra/__init__.py +0 -22
  148. webscout/Provider/TTI/Nexra/async_nexra.py +0 -286
  149. webscout/Provider/TTI/Nexra/sync_nexra.py +0 -258
  150. webscout/Provider/TTI/PollinationsAI/README.md +0 -146
  151. webscout/Provider/TTI/PollinationsAI/__init__.py +0 -23
  152. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +0 -311
  153. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +0 -265
  154. webscout/Provider/TTI/aiarta/README.md +0 -134
  155. webscout/Provider/TTI/aiarta/__init__.py +0 -2
  156. webscout/Provider/TTI/aiarta/async_aiarta.py +0 -482
  157. webscout/Provider/TTI/aiarta/sync_aiarta.py +0 -440
  158. webscout/Provider/TTI/artbit/README.md +0 -100
  159. webscout/Provider/TTI/artbit/__init__.py +0 -22
  160. webscout/Provider/TTI/artbit/async_artbit.py +0 -155
  161. webscout/Provider/TTI/artbit/sync_artbit.py +0 -148
  162. webscout/Provider/TTI/fastflux/README.md +0 -129
  163. webscout/Provider/TTI/fastflux/__init__.py +0 -22
  164. webscout/Provider/TTI/fastflux/async_fastflux.py +0 -261
  165. webscout/Provider/TTI/fastflux/sync_fastflux.py +0 -252
  166. webscout/Provider/TTI/huggingface/README.md +0 -114
  167. webscout/Provider/TTI/huggingface/__init__.py +0 -22
  168. webscout/Provider/TTI/huggingface/async_huggingface.py +0 -199
  169. webscout/Provider/TTI/huggingface/sync_huggingface.py +0 -195
  170. webscout/Provider/TTI/piclumen/README.md +0 -161
  171. webscout/Provider/TTI/piclumen/__init__.py +0 -23
  172. webscout/Provider/TTI/piclumen/async_piclumen.py +0 -268
  173. webscout/Provider/TTI/piclumen/sync_piclumen.py +0 -233
  174. webscout/Provider/TTI/pixelmuse/README.md +0 -79
  175. webscout/Provider/TTI/pixelmuse/__init__.py +0 -4
  176. webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +0 -249
  177. webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +0 -182
  178. webscout/Provider/TTI/talkai/README.md +0 -139
  179. webscout/Provider/TTI/talkai/__init__.py +0 -4
  180. webscout/Provider/TTI/talkai/async_talkai.py +0 -229
  181. webscout/Provider/TTI/talkai/sync_talkai.py +0 -207
  182. webscout/Provider/UNFINISHED/oivscode.py +0 -351
  183. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/licenses/LICENSE.md +0 -0
  184. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,64 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Dict, Optional, Any, Union, Generator
3
+ from .utils import ImageResponse
4
+ import random
5
+
6
+ class BaseImages(ABC):
7
+ @abstractmethod
8
+ def create(
9
+ self,
10
+ *,
11
+ model: str,
12
+ prompt: str,
13
+ n: int = 1,
14
+ size: str = "1024x1024",
15
+ response_format: str = "url",
16
+ user: Optional[str] = None,
17
+ style: str = "none",
18
+ aspect_ratio: str = "1:1",
19
+ timeout: int = None,
20
+ image_format: str = "png",
21
+ seed: Optional[int] = None,
22
+ **kwargs
23
+ ) -> ImageResponse:
24
+ """
25
+ Abstract method to create images from a prompt.
26
+
27
+ Args:
28
+ model: The model to use for image generation.
29
+ prompt: The prompt for the image.
30
+ n: Number of images to generate.
31
+ size: Image size.
32
+ response_format: "url" or "b64_json".
33
+ user: Optional user identifier.
34
+ style: Optional style.
35
+ aspect_ratio: Optional aspect ratio.
36
+ timeout: Request timeout in seconds.
37
+ image_format: "png" or "jpeg" for output format.
38
+ seed: Optional random seed for reproducibility.
39
+ **kwargs: Additional provider-specific parameters.
40
+
41
+ Returns:
42
+ ImageResponse: The generated images.
43
+ """
44
+ raise NotImplementedError
45
+
46
+ class TTICompatibleProvider(ABC):
47
+ """
48
+ Abstract Base Class for TTI providers mimicking the OpenAI Python client structure.
49
+ Requires a nested 'images.create' structure.
50
+ """
51
+ images: BaseImages
52
+
53
+ @abstractmethod
54
+ def __init__(self, **kwargs: Any):
55
+ pass
56
+
57
+ @property
58
+ @abstractmethod
59
+ def models(self):
60
+ """
61
+ Property that returns an object with a .list() method returning available models.
62
+ Subclasses must implement this property.
63
+ """
64
+ pass
@@ -0,0 +1,200 @@
1
+ import requests
2
+ import os
3
+ import tempfile
4
+ import time
5
+ from typing import Optional, List, Dict, Any
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(self,
21
+ model: str = "flux_1_schnell",
22
+ prompt: str = None,
23
+ n: int = 1,
24
+ size: str = "1_1",
25
+ response_format: str = "url",
26
+ user: Optional[str] = None,
27
+ style: str = "none",
28
+ aspect_ratio: str = "1:1",
29
+ timeout: int = 60,
30
+ image_format: str = "png",
31
+ is_public: bool = False,
32
+ **kwargs
33
+ ) -> ImageResponse:
34
+ if not prompt:
35
+ raise ValueError("Prompt is required!")
36
+ agent = LitAgent()
37
+ images = []
38
+ urls = []
39
+ api_url = self._client.api_endpoint
40
+ payload = {
41
+ "prompt": prompt,
42
+ "model": model,
43
+ "size": size,
44
+ "isPublic": is_public
45
+ }
46
+ for _ in range(n):
47
+ resp = self._client.session.post(api_url, json=payload, timeout=timeout)
48
+ resp.raise_for_status()
49
+ result = resp.json()
50
+ if result and 'result' in result:
51
+ image_data = result['result']
52
+ base64_data = image_data.split(',')[1]
53
+ img_bytes = base64.b64decode(base64_data)
54
+ # Convert to png or jpeg in memory if needed
55
+ if Image is not None:
56
+ with BytesIO(img_bytes) as input_io:
57
+ with Image.open(input_io) as im:
58
+ out_io = BytesIO()
59
+ if image_format.lower() == "jpeg":
60
+ im = im.convert("RGB")
61
+ im.save(out_io, format="JPEG")
62
+ else:
63
+ im.save(out_io, format="PNG")
64
+ img_bytes = out_io.getvalue()
65
+ images.append(img_bytes)
66
+ if response_format == "url":
67
+ def upload_file_with_retry(img_bytes, image_format, max_retries=3):
68
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
69
+ for attempt in range(max_retries):
70
+ tmp_path = None
71
+ try:
72
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
73
+ tmp.write(img_bytes)
74
+ tmp.flush()
75
+ tmp_path = tmp.name
76
+ with open(tmp_path, 'rb') as f:
77
+ files = {
78
+ 'fileToUpload': (f'image.{ext}', f, f'image/{ext}')
79
+ }
80
+ data = {
81
+ 'reqtype': 'fileupload',
82
+ 'json': 'true'
83
+ }
84
+ headers = {'User-Agent': agent.random()}
85
+ if attempt > 0:
86
+ headers['Connection'] = 'close'
87
+ resp = requests.post("https://catbox.moe/user/api.php", files=files, data=data, headers=headers, timeout=timeout)
88
+ if resp.status_code == 200 and resp.text.strip():
89
+ text = resp.text.strip()
90
+ if text.startswith('http'):
91
+ return text
92
+ try:
93
+ result = resp.json()
94
+ if "url" in result:
95
+ return result["url"]
96
+ except Exception:
97
+ if 'http' in text:
98
+ return text
99
+ except Exception:
100
+ if attempt < max_retries - 1:
101
+ time.sleep(1 * (attempt + 1))
102
+ finally:
103
+ if tmp_path and os.path.isfile(tmp_path):
104
+ try:
105
+ os.remove(tmp_path)
106
+ except Exception:
107
+ pass
108
+ return None
109
+ def upload_file_alternative(img_bytes, image_format):
110
+ try:
111
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
112
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
113
+ tmp.write(img_bytes)
114
+ tmp.flush()
115
+ tmp_path = tmp.name
116
+ try:
117
+ if not os.path.isfile(tmp_path):
118
+ return None
119
+ with open(tmp_path, 'rb') as img_file:
120
+ files = {'file': img_file}
121
+ response = requests.post('https://0x0.st', files=files)
122
+ response.raise_for_status()
123
+ image_url = response.text.strip()
124
+ if not image_url.startswith('http'):
125
+ return None
126
+ return image_url
127
+ except Exception:
128
+ return None
129
+ finally:
130
+ try:
131
+ os.remove(tmp_path)
132
+ except Exception:
133
+ pass
134
+ except Exception:
135
+ return None
136
+ uploaded_url = upload_file_with_retry(img_bytes, image_format)
137
+ if not uploaded_url:
138
+ uploaded_url = upload_file_alternative(img_bytes, image_format)
139
+ if uploaded_url:
140
+ urls.append(uploaded_url)
141
+ else:
142
+ raise RuntimeError("Failed to upload image to catbox.moe using all available methods")
143
+ else:
144
+ raise RuntimeError("No image data received from FastFlux API")
145
+ result_data = []
146
+ if response_format == "url":
147
+ for url in urls:
148
+ result_data.append(ImageData(url=url))
149
+ elif response_format == "b64_json":
150
+ import base64
151
+ for img in images:
152
+ b64 = base64.b64encode(img).decode("utf-8")
153
+ result_data.append(ImageData(b64_json=b64))
154
+ else:
155
+ raise ValueError("response_format must be 'url' or 'b64_json'")
156
+ from time import time as _time
157
+ return ImageResponse(
158
+ created=int(_time()),
159
+ data=result_data
160
+ )
161
+
162
+ class FastFluxAI(TTICompatibleProvider):
163
+ AVAILABLE_MODELS = [
164
+ "flux_1_schnell",
165
+ ]
166
+ def __init__(self, api_key: str = None):
167
+ self.api_endpoint = "https://api.freeflux.ai/v1/images/generate"
168
+ self.session = requests.Session()
169
+ self.user_agent = LitAgent().random()
170
+ self.api_key = api_key
171
+ self.headers = {
172
+ "accept": "application/json, text/plain, */*",
173
+ "accept-language": "en-US,en;q=0.9",
174
+ "content-type": "application/json",
175
+ "origin": "https://freeflux.ai",
176
+ "referer": "https://freeflux.ai/",
177
+ "user-agent": self.user_agent,
178
+ }
179
+ if self.api_key:
180
+ self.headers["authorization"] = f"Bearer {self.api_key}"
181
+ self.session.headers.update(self.headers)
182
+ self.images = Images(self)
183
+ @property
184
+ def models(self):
185
+ class _ModelList:
186
+ def list(inner_self):
187
+ return type(self).AVAILABLE_MODELS
188
+ return _ModelList()
189
+
190
+ if __name__ == "__main__":
191
+ from rich import print
192
+ client = FastFluxAI()
193
+ response = client.images.create(
194
+ model="flux_1_schnell",
195
+ prompt="A cool cyberpunk city at night",
196
+ response_format="url",
197
+ n=2,
198
+ timeout=30,
199
+ )
200
+ print(response)
@@ -0,0 +1,201 @@
1
+ import requests
2
+ import os
3
+ import uuid
4
+ import time
5
+ import tempfile
6
+ from typing import Optional, List
7
+ from webscout.Provider.TTI.utils import ImageData, ImageResponse
8
+ from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
9
+ from io import BytesIO
10
+ from webscout.litagent import LitAgent
11
+
12
+ try:
13
+ from PIL import Image
14
+ except ImportError:
15
+ Image = None
16
+
17
+ class Images(BaseImages):
18
+ def __init__(self, client):
19
+ self._client = client
20
+
21
+ def create(self,
22
+ model: str = "magicstudio",
23
+ prompt: str = None,
24
+ n: int = 1,
25
+ size: str = None,
26
+ response_format: str = "url",
27
+ user: Optional[str] = None,
28
+ style: str = None,
29
+ aspect_ratio: str = None,
30
+ timeout: int = 60,
31
+ image_format: str = "jpg",
32
+ **kwargs
33
+ ) -> ImageResponse:
34
+ if not prompt:
35
+ raise ValueError("Prompt is required!")
36
+ agent = LitAgent()
37
+ images = []
38
+ urls = []
39
+ api_url = "https://ai-api.magicstudio.com/api/ai-art-generator"
40
+ headers = {
41
+ "Accept": "application/json, text/plain, */*",
42
+ "User-Agent": agent.random(),
43
+ "Origin": "https://magicstudio.com",
44
+ "Referer": "https://magicstudio.com/ai-art-generator/",
45
+ "DNT": "1",
46
+ "Sec-GPC": "1"
47
+ }
48
+ session = requests.Session()
49
+ session.headers.update(headers)
50
+ for _ in range(n):
51
+ form_data = {
52
+ "prompt": prompt,
53
+ "output_format": "bytes",
54
+ "user_profile_id": "null",
55
+ "anonymous_user_id": str(uuid.uuid4()),
56
+ "request_timestamp": time.time(),
57
+ "user_is_subscribed": "false",
58
+ "client_id": uuid.uuid4().hex,
59
+ }
60
+ resp = session.post(api_url, data=form_data, timeout=timeout)
61
+ resp.raise_for_status()
62
+ img_bytes = resp.content
63
+ # Convert to png or jpeg in memory if needed
64
+ if Image is not None:
65
+ with BytesIO(img_bytes) as input_io:
66
+ with Image.open(input_io) as im:
67
+ out_io = BytesIO()
68
+ if image_format.lower() == "jpeg" or image_format.lower() == "jpg":
69
+ im = im.convert("RGB")
70
+ im.save(out_io, format="JPEG")
71
+ else:
72
+ im.save(out_io, format="PNG")
73
+ img_bytes = out_io.getvalue()
74
+ images.append(img_bytes)
75
+ if response_format == "url":
76
+ def upload_file_with_retry(img_bytes, image_format, max_retries=3):
77
+ ext = "jpg" if image_format.lower() in ("jpeg", "jpg") else "png"
78
+ for attempt in range(max_retries):
79
+ tmp_path = None
80
+ try:
81
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
82
+ tmp.write(img_bytes)
83
+ tmp.flush()
84
+ tmp_path = tmp.name
85
+ with open(tmp_path, 'rb') as f:
86
+ files = {
87
+ 'fileToUpload': (f'image.{ext}', f, f'image/{ext}')
88
+ }
89
+ data = {
90
+ 'reqtype': 'fileupload',
91
+ 'json': 'true'
92
+ }
93
+ headers = {'User-Agent': agent.random()}
94
+ if attempt > 0:
95
+ headers['Connection'] = 'close'
96
+ resp = requests.post("https://catbox.moe/user/api.php", files=files, data=data, headers=headers, timeout=timeout)
97
+ if resp.status_code == 200 and resp.text.strip():
98
+ text = resp.text.strip()
99
+ if text.startswith('http'):
100
+ return text
101
+ try:
102
+ result = resp.json()
103
+ if "url" in result:
104
+ return result["url"]
105
+ except Exception:
106
+ if 'http' in text:
107
+ return text
108
+ except Exception:
109
+ if attempt < max_retries - 1:
110
+ time.sleep(1 * (attempt + 1))
111
+ finally:
112
+ if tmp_path and os.path.isfile(tmp_path):
113
+ try:
114
+ os.remove(tmp_path)
115
+ except Exception:
116
+ pass
117
+ return None
118
+ def upload_file_alternative(img_bytes, image_format):
119
+ try:
120
+ ext = "jpg" if image_format.lower() in ("jpeg", "jpg") else "png"
121
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
122
+ tmp.write(img_bytes)
123
+ tmp.flush()
124
+ tmp_path = tmp.name
125
+ try:
126
+ if not os.path.isfile(tmp_path):
127
+ return None
128
+ with open(tmp_path, 'rb') as img_file:
129
+ files = {'file': img_file}
130
+ alt_resp = requests.post('https://0x0.st', files=files)
131
+ alt_resp.raise_for_status()
132
+ image_url = alt_resp.text.strip()
133
+ if not image_url.startswith('http'):
134
+ return None
135
+ return image_url
136
+ except Exception:
137
+ return None
138
+ finally:
139
+ try:
140
+ os.remove(tmp_path)
141
+ except Exception:
142
+ pass
143
+ except Exception:
144
+ return None
145
+ uploaded_url = upload_file_with_retry(img_bytes, image_format)
146
+ if not uploaded_url:
147
+ uploaded_url = upload_file_alternative(img_bytes, image_format)
148
+ if uploaded_url:
149
+ urls.append(uploaded_url)
150
+ else:
151
+ raise RuntimeError("Failed to upload image to catbox.moe using all available methods")
152
+ result_data = []
153
+ if response_format == "url":
154
+ for url in urls:
155
+ result_data.append(ImageData(url=url))
156
+ elif response_format == "b64_json":
157
+ import base64
158
+ for img in images:
159
+ b64 = base64.b64encode(img).decode("utf-8")
160
+ result_data.append(ImageData(b64_json=b64))
161
+ else:
162
+ raise ValueError("response_format must be 'url' or 'b64_json'")
163
+ from time import time as _time
164
+ return ImageResponse(
165
+ created=int(_time()),
166
+ data=result_data
167
+ )
168
+
169
+ class MagicStudioAI(TTICompatibleProvider):
170
+ AVAILABLE_MODELS = ["magicstudio"]
171
+ def __init__(self):
172
+ self.api_endpoint = "https://ai-api.magicstudio.com/api/ai-art-generator"
173
+ self.session = requests.Session()
174
+ self.user_agent = LitAgent().random()
175
+ self.headers = {
176
+ "accept": "*/*",
177
+ "accept-language": "en-US,en;q=0.9",
178
+ "content-type": "application/json",
179
+ "origin": "https://magicstudio.com",
180
+ "referer": "https://magicstudio.com/ai-art-generator/",
181
+ "user-agent": self.user_agent,
182
+ }
183
+ self.session.headers.update(self.headers)
184
+ self.images = Images(self)
185
+ @property
186
+ def models(self):
187
+ class _ModelList:
188
+ def list(inner_self):
189
+ return type(self).AVAILABLE_MODELS
190
+ return _ModelList()
191
+
192
+ if __name__ == "__main__":
193
+ from rich import print
194
+ client = MagicStudioAI()
195
+ response = client.images.create(
196
+ prompt="A cool cyberpunk city at night",
197
+ response_format="url",
198
+ n=2,
199
+ timeout=30,
200
+ )
201
+ print(response)
@@ -0,0 +1,203 @@
1
+ import requests
2
+ from typing import Optional, List, Dict, Any
3
+ from webscout.Provider.TTI.utils import ImageData, ImageResponse
4
+ from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
5
+ from io import BytesIO
6
+ import os
7
+ import tempfile
8
+ from webscout.litagent import LitAgent
9
+ import time
10
+ import json
11
+
12
+ try:
13
+ from PIL import Image
14
+ except ImportError:
15
+ Image = None
16
+
17
+ class Images(BaseImages):
18
+ def __init__(self, client):
19
+ self._client = client
20
+
21
+ def create(self,
22
+ model: str,
23
+ prompt: str,
24
+ n: int = 1,
25
+ size: str = "1024x1024",
26
+ response_format: str = "url",
27
+ user: Optional[str] = None,
28
+ style: str = "none",
29
+ aspect_ratio: str = "1:1",
30
+ timeout: int = 60,
31
+ image_format: str = "jpeg",
32
+ **kwargs
33
+ ) -> ImageResponse:
34
+ """
35
+ image_format: "png" or "jpeg"
36
+ """
37
+ if Image is None:
38
+ raise ImportError("Pillow (PIL) is required for image format conversion.")
39
+
40
+ images = []
41
+ urls = []
42
+ agent = LitAgent()
43
+
44
+ def upload_file_with_retry(img_bytes, image_format, max_retries=3):
45
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
46
+ for attempt in range(max_retries):
47
+ tmp_path = None
48
+ try:
49
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
50
+ tmp.write(img_bytes)
51
+ tmp.flush()
52
+ tmp_path = tmp.name
53
+ with open(tmp_path, 'rb') as f:
54
+ files = {
55
+ 'fileToUpload': (f'image.{ext}', f, f'image/{ext}')
56
+ }
57
+ data = {
58
+ 'reqtype': 'fileupload',
59
+ 'json': 'true'
60
+ }
61
+ headers = {'User-Agent': agent.random()}
62
+ if attempt > 0:
63
+ headers['Connection'] = 'close'
64
+ resp = requests.post("https://catbox.moe/user/api.php", files=files, data=data, headers=headers, timeout=timeout)
65
+ if resp.status_code == 200 and resp.text.strip():
66
+ text = resp.text.strip()
67
+ if text.startswith('http'):
68
+ return text
69
+ try:
70
+ result = resp.json()
71
+ if "url" in result:
72
+ return result["url"]
73
+ except json.JSONDecodeError:
74
+ if 'http' in text:
75
+ return text
76
+ except Exception:
77
+ if attempt < max_retries - 1:
78
+ time.sleep(1 * (attempt + 1))
79
+ finally:
80
+ if tmp_path and os.path.isfile(tmp_path):
81
+ try:
82
+ os.remove(tmp_path)
83
+ except Exception:
84
+ pass
85
+ return None
86
+
87
+ def upload_file_alternative(img_bytes, image_format):
88
+ try:
89
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
90
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
91
+ tmp.write(img_bytes)
92
+ tmp.flush()
93
+ tmp_path = tmp.name
94
+ try:
95
+ if not os.path.isfile(tmp_path):
96
+ return None
97
+ with open(tmp_path, 'rb') as img_file:
98
+ files = {'file': img_file}
99
+ response = requests.post('https://0x0.st', files=files)
100
+ response.raise_for_status()
101
+ image_url = response.text.strip()
102
+ if not image_url.startswith('http'):
103
+ return None
104
+ return image_url
105
+ except Exception:
106
+ return None
107
+ finally:
108
+ try:
109
+ os.remove(tmp_path)
110
+ except Exception:
111
+ pass
112
+ except Exception:
113
+ return None
114
+
115
+ for _ in range(n):
116
+ payload = {"prompt": prompt}
117
+ resp = self._client.session.post(
118
+ self._client.api_endpoint,
119
+ json=payload,
120
+ timeout=timeout
121
+ )
122
+ resp.raise_for_status()
123
+ # Piclumen returns image/jpeg directly
124
+ if resp.headers.get('content-type') == 'image/jpeg':
125
+ img_bytes = resp.content
126
+ # Convert to png or jpeg in memory
127
+ with BytesIO(img_bytes) as input_io:
128
+ with Image.open(input_io) as im:
129
+ out_io = BytesIO()
130
+ if image_format.lower() == "jpeg":
131
+ im = im.convert("RGB")
132
+ im.save(out_io, format="JPEG")
133
+ else:
134
+ im.save(out_io, format="PNG")
135
+ img_bytes = out_io.getvalue()
136
+ images.append(img_bytes)
137
+ if response_format == "url":
138
+ uploaded_url = upload_file_with_retry(img_bytes, image_format)
139
+ if not uploaded_url:
140
+ uploaded_url = upload_file_alternative(img_bytes, image_format)
141
+ if uploaded_url:
142
+ urls.append(uploaded_url)
143
+ else:
144
+ raise RuntimeError("Failed to upload image to catbox.moe using all available methods")
145
+ else:
146
+ raise RuntimeError("No image data received from Piclumen")
147
+
148
+ result_data = []
149
+ if response_format == "url":
150
+ for url in urls:
151
+ result_data.append(ImageData(url=url))
152
+ elif response_format == "b64_json":
153
+ import base64
154
+ for img in images:
155
+ b64 = base64.b64encode(img).decode("utf-8")
156
+ result_data.append(ImageData(b64_json=b64))
157
+ else:
158
+ raise ValueError("response_format must be 'url' or 'b64_json'")
159
+
160
+ from time import time as _time
161
+ return ImageResponse(
162
+ created=int(_time()),
163
+ data=result_data
164
+ )
165
+
166
+ class PiclumenAI(TTICompatibleProvider):
167
+ AVAILABLE_MODELS = [
168
+ "piclumen-v1"
169
+ ]
170
+
171
+ def __init__(self):
172
+ self.api_endpoint = "https://s9.piclumen.art/comfy/api/generate-image"
173
+ self.session = requests.Session()
174
+ self.user_agent = LitAgent().random()
175
+ self.headers = {
176
+ "accept": "*/*",
177
+ "accept-language": "en-US,en;q=0.9",
178
+ "content-type": "application/json",
179
+ "origin": "https://www.piclumen.com",
180
+ "referer": "https://s9.piclumen.art/",
181
+ "user-agent": self.user_agent,
182
+ }
183
+ self.session.headers.update(self.headers)
184
+ self.images = Images(self)
185
+
186
+ @property
187
+ def models(self):
188
+ class _ModelList:
189
+ def list(inner_self):
190
+ return type(self).AVAILABLE_MODELS
191
+ return _ModelList()
192
+
193
+ if __name__ == "__main__":
194
+ from rich import print
195
+ client = PiclumenAI()
196
+ response = client.images.create(
197
+ model="piclumen-v1",
198
+ prompt="a futuristic city skyline at sunset",
199
+ response_format="url",
200
+ n=2,
201
+ timeout=30,
202
+ )
203
+ print(response)