webscout 8.2.7__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 (281) hide show
  1. webscout/AIauto.py +33 -15
  2. webscout/AIbase.py +96 -37
  3. webscout/AIutel.py +703 -250
  4. webscout/Bard.py +441 -323
  5. webscout/Extra/Act.md +309 -0
  6. webscout/Extra/GitToolkit/__init__.py +10 -0
  7. webscout/Extra/GitToolkit/gitapi/README.md +110 -0
  8. webscout/Extra/GitToolkit/gitapi/__init__.py +12 -0
  9. webscout/Extra/GitToolkit/gitapi/repository.py +195 -0
  10. webscout/Extra/GitToolkit/gitapi/user.py +96 -0
  11. webscout/Extra/GitToolkit/gitapi/utils.py +62 -0
  12. webscout/Extra/YTToolkit/README.md +375 -0
  13. webscout/Extra/YTToolkit/YTdownloader.py +957 -0
  14. webscout/Extra/YTToolkit/__init__.py +3 -0
  15. webscout/Extra/YTToolkit/transcriber.py +476 -0
  16. webscout/Extra/YTToolkit/ytapi/README.md +44 -0
  17. webscout/Extra/YTToolkit/ytapi/__init__.py +6 -0
  18. webscout/Extra/YTToolkit/ytapi/channel.py +307 -0
  19. webscout/Extra/YTToolkit/ytapi/errors.py +13 -0
  20. webscout/Extra/YTToolkit/ytapi/extras.py +118 -0
  21. webscout/Extra/YTToolkit/ytapi/https.py +88 -0
  22. webscout/Extra/YTToolkit/ytapi/patterns.py +61 -0
  23. webscout/Extra/YTToolkit/ytapi/playlist.py +59 -0
  24. webscout/Extra/YTToolkit/ytapi/pool.py +8 -0
  25. webscout/Extra/YTToolkit/ytapi/query.py +40 -0
  26. webscout/Extra/YTToolkit/ytapi/stream.py +63 -0
  27. webscout/Extra/YTToolkit/ytapi/utils.py +62 -0
  28. webscout/Extra/YTToolkit/ytapi/video.py +232 -0
  29. webscout/Extra/__init__.py +7 -0
  30. webscout/Extra/autocoder/__init__.py +9 -0
  31. webscout/Extra/autocoder/autocoder.py +1105 -0
  32. webscout/Extra/autocoder/autocoder_utiles.py +332 -0
  33. webscout/Extra/gguf.md +430 -0
  34. webscout/Extra/gguf.py +684 -0
  35. webscout/Extra/tempmail/README.md +488 -0
  36. webscout/Extra/tempmail/__init__.py +28 -0
  37. webscout/Extra/tempmail/async_utils.py +141 -0
  38. webscout/Extra/tempmail/base.py +161 -0
  39. webscout/Extra/tempmail/cli.py +187 -0
  40. webscout/Extra/tempmail/emailnator.py +84 -0
  41. webscout/Extra/tempmail/mail_tm.py +361 -0
  42. webscout/Extra/tempmail/temp_mail_io.py +292 -0
  43. webscout/Extra/weather.md +281 -0
  44. webscout/Extra/weather.py +194 -0
  45. webscout/Extra/weather_ascii.py +76 -0
  46. webscout/Litlogger/README.md +10 -0
  47. webscout/Litlogger/__init__.py +15 -0
  48. webscout/Litlogger/formats.py +4 -0
  49. webscout/Litlogger/handlers.py +103 -0
  50. webscout/Litlogger/levels.py +13 -0
  51. webscout/Litlogger/logger.py +92 -0
  52. webscout/Provider/AI21.py +177 -0
  53. webscout/Provider/AISEARCH/DeepFind.py +254 -0
  54. webscout/Provider/AISEARCH/Perplexity.py +333 -0
  55. webscout/Provider/AISEARCH/README.md +279 -0
  56. webscout/Provider/AISEARCH/__init__.py +9 -0
  57. webscout/Provider/AISEARCH/felo_search.py +202 -0
  58. webscout/Provider/AISEARCH/genspark_search.py +324 -0
  59. webscout/Provider/AISEARCH/hika_search.py +186 -0
  60. webscout/Provider/AISEARCH/iask_search.py +410 -0
  61. webscout/Provider/AISEARCH/monica_search.py +220 -0
  62. webscout/Provider/AISEARCH/scira_search.py +298 -0
  63. webscout/Provider/AISEARCH/webpilotai_search.py +255 -0
  64. webscout/Provider/Aitopia.py +316 -0
  65. webscout/Provider/AllenAI.py +440 -0
  66. webscout/Provider/Andi.py +228 -0
  67. webscout/Provider/Blackboxai.py +791 -0
  68. webscout/Provider/ChatGPTClone.py +237 -0
  69. webscout/Provider/ChatGPTGratis.py +194 -0
  70. webscout/Provider/ChatSandbox.py +342 -0
  71. webscout/Provider/Cloudflare.py +324 -0
  72. webscout/Provider/Cohere.py +208 -0
  73. webscout/Provider/Deepinfra.py +340 -0
  74. webscout/Provider/ExaAI.py +261 -0
  75. webscout/Provider/ExaChat.py +358 -0
  76. webscout/Provider/Flowith.py +217 -0
  77. webscout/Provider/FreeGemini.py +250 -0
  78. webscout/Provider/Gemini.py +169 -0
  79. webscout/Provider/GithubChat.py +369 -0
  80. webscout/Provider/GizAI.py +295 -0
  81. webscout/Provider/Glider.py +225 -0
  82. webscout/Provider/Groq.py +801 -0
  83. webscout/Provider/HF_space/__init__.py +0 -0
  84. webscout/Provider/HF_space/qwen_qwen2.py +206 -0
  85. webscout/Provider/HeckAI.py +375 -0
  86. webscout/Provider/HuggingFaceChat.py +469 -0
  87. webscout/Provider/Hunyuan.py +283 -0
  88. webscout/Provider/Jadve.py +291 -0
  89. webscout/Provider/Koboldai.py +384 -0
  90. webscout/Provider/LambdaChat.py +411 -0
  91. webscout/Provider/Llama3.py +259 -0
  92. webscout/Provider/MCPCore.py +315 -0
  93. webscout/Provider/Marcus.py +198 -0
  94. webscout/Provider/Nemotron.py +218 -0
  95. webscout/Provider/Netwrck.py +270 -0
  96. webscout/Provider/OLLAMA.py +396 -0
  97. webscout/Provider/OPENAI/BLACKBOXAI.py +766 -0
  98. webscout/Provider/OPENAI/Cloudflare.py +378 -0
  99. webscout/Provider/OPENAI/FreeGemini.py +283 -0
  100. webscout/Provider/OPENAI/NEMOTRON.py +232 -0
  101. webscout/Provider/OPENAI/Qwen3.py +283 -0
  102. webscout/Provider/OPENAI/README.md +952 -0
  103. webscout/Provider/OPENAI/TwoAI.py +357 -0
  104. webscout/Provider/OPENAI/__init__.py +40 -0
  105. webscout/Provider/OPENAI/ai4chat.py +293 -0
  106. webscout/Provider/OPENAI/api.py +969 -0
  107. webscout/Provider/OPENAI/base.py +249 -0
  108. webscout/Provider/OPENAI/c4ai.py +373 -0
  109. webscout/Provider/OPENAI/chatgpt.py +556 -0
  110. webscout/Provider/OPENAI/chatgptclone.py +494 -0
  111. webscout/Provider/OPENAI/chatsandbox.py +173 -0
  112. webscout/Provider/OPENAI/copilot.py +242 -0
  113. webscout/Provider/OPENAI/deepinfra.py +322 -0
  114. webscout/Provider/OPENAI/e2b.py +1414 -0
  115. webscout/Provider/OPENAI/exaai.py +417 -0
  116. webscout/Provider/OPENAI/exachat.py +444 -0
  117. webscout/Provider/OPENAI/flowith.py +162 -0
  118. webscout/Provider/OPENAI/freeaichat.py +359 -0
  119. webscout/Provider/OPENAI/glider.py +326 -0
  120. webscout/Provider/OPENAI/groq.py +364 -0
  121. webscout/Provider/OPENAI/heckai.py +308 -0
  122. webscout/Provider/OPENAI/llmchatco.py +335 -0
  123. webscout/Provider/OPENAI/mcpcore.py +389 -0
  124. webscout/Provider/OPENAI/multichat.py +376 -0
  125. webscout/Provider/OPENAI/netwrck.py +357 -0
  126. webscout/Provider/OPENAI/oivscode.py +287 -0
  127. webscout/Provider/OPENAI/opkfc.py +496 -0
  128. webscout/Provider/OPENAI/pydantic_imports.py +172 -0
  129. webscout/Provider/OPENAI/scirachat.py +477 -0
  130. webscout/Provider/OPENAI/sonus.py +304 -0
  131. webscout/Provider/OPENAI/standardinput.py +433 -0
  132. webscout/Provider/OPENAI/textpollinations.py +339 -0
  133. webscout/Provider/OPENAI/toolbaz.py +413 -0
  134. webscout/Provider/OPENAI/typefully.py +355 -0
  135. webscout/Provider/OPENAI/typegpt.py +364 -0
  136. webscout/Provider/OPENAI/uncovrAI.py +463 -0
  137. webscout/Provider/OPENAI/utils.py +318 -0
  138. webscout/Provider/OPENAI/venice.py +431 -0
  139. webscout/Provider/OPENAI/wisecat.py +387 -0
  140. webscout/Provider/OPENAI/writecream.py +163 -0
  141. webscout/Provider/OPENAI/x0gpt.py +365 -0
  142. webscout/Provider/OPENAI/yep.py +382 -0
  143. webscout/Provider/OpenGPT.py +209 -0
  144. webscout/Provider/Openai.py +496 -0
  145. webscout/Provider/PI.py +429 -0
  146. webscout/Provider/Perplexitylabs.py +415 -0
  147. webscout/Provider/QwenLM.py +254 -0
  148. webscout/Provider/Reka.py +214 -0
  149. webscout/Provider/StandardInput.py +290 -0
  150. webscout/Provider/TTI/README.md +82 -0
  151. webscout/Provider/TTI/__init__.py +7 -0
  152. webscout/Provider/TTI/aiarta.py +365 -0
  153. webscout/Provider/TTI/artbit.py +0 -0
  154. webscout/Provider/TTI/base.py +64 -0
  155. webscout/Provider/TTI/fastflux.py +200 -0
  156. webscout/Provider/TTI/magicstudio.py +201 -0
  157. webscout/Provider/TTI/piclumen.py +203 -0
  158. webscout/Provider/TTI/pixelmuse.py +225 -0
  159. webscout/Provider/TTI/pollinations.py +221 -0
  160. webscout/Provider/TTI/utils.py +11 -0
  161. webscout/Provider/TTS/README.md +192 -0
  162. webscout/Provider/TTS/__init__.py +10 -0
  163. webscout/Provider/TTS/base.py +159 -0
  164. webscout/Provider/TTS/deepgram.py +156 -0
  165. webscout/Provider/TTS/elevenlabs.py +111 -0
  166. webscout/Provider/TTS/gesserit.py +128 -0
  167. webscout/Provider/TTS/murfai.py +113 -0
  168. webscout/Provider/TTS/openai_fm.py +129 -0
  169. webscout/Provider/TTS/parler.py +111 -0
  170. webscout/Provider/TTS/speechma.py +580 -0
  171. webscout/Provider/TTS/sthir.py +94 -0
  172. webscout/Provider/TTS/streamElements.py +333 -0
  173. webscout/Provider/TTS/utils.py +280 -0
  174. webscout/Provider/TeachAnything.py +229 -0
  175. webscout/Provider/TextPollinationsAI.py +308 -0
  176. webscout/Provider/TwoAI.py +475 -0
  177. webscout/Provider/TypliAI.py +305 -0
  178. webscout/Provider/UNFINISHED/ChatHub.py +209 -0
  179. webscout/Provider/UNFINISHED/Youchat.py +330 -0
  180. webscout/Provider/UNFINISHED/liner_api_request.py +263 -0
  181. webscout/Provider/UNFINISHED/puterjs.py +635 -0
  182. webscout/Provider/UNFINISHED/test_lmarena.py +119 -0
  183. webscout/Provider/Venice.py +258 -0
  184. webscout/Provider/VercelAI.py +253 -0
  185. webscout/Provider/WiseCat.py +233 -0
  186. webscout/Provider/WrDoChat.py +370 -0
  187. webscout/Provider/Writecream.py +246 -0
  188. webscout/Provider/WritingMate.py +269 -0
  189. webscout/Provider/__init__.py +174 -0
  190. webscout/Provider/ai4chat.py +174 -0
  191. webscout/Provider/akashgpt.py +335 -0
  192. webscout/Provider/asksteve.py +220 -0
  193. webscout/Provider/cerebras.py +290 -0
  194. webscout/Provider/chatglm.py +215 -0
  195. webscout/Provider/cleeai.py +213 -0
  196. webscout/Provider/copilot.py +425 -0
  197. webscout/Provider/elmo.py +283 -0
  198. webscout/Provider/freeaichat.py +285 -0
  199. webscout/Provider/geminiapi.py +208 -0
  200. webscout/Provider/granite.py +235 -0
  201. webscout/Provider/hermes.py +266 -0
  202. webscout/Provider/julius.py +223 -0
  203. webscout/Provider/koala.py +170 -0
  204. webscout/Provider/learnfastai.py +325 -0
  205. webscout/Provider/llama3mitril.py +215 -0
  206. webscout/Provider/llmchat.py +258 -0
  207. webscout/Provider/llmchatco.py +306 -0
  208. webscout/Provider/lmarena.py +198 -0
  209. webscout/Provider/meta.py +801 -0
  210. webscout/Provider/multichat.py +364 -0
  211. webscout/Provider/oivscode.py +309 -0
  212. webscout/Provider/samurai.py +224 -0
  213. webscout/Provider/scira_chat.py +299 -0
  214. webscout/Provider/scnet.py +243 -0
  215. webscout/Provider/searchchat.py +292 -0
  216. webscout/Provider/sonus.py +258 -0
  217. webscout/Provider/talkai.py +194 -0
  218. webscout/Provider/toolbaz.py +353 -0
  219. webscout/Provider/turboseek.py +266 -0
  220. webscout/Provider/typefully.py +202 -0
  221. webscout/Provider/typegpt.py +289 -0
  222. webscout/Provider/uncovr.py +368 -0
  223. webscout/Provider/x0gpt.py +299 -0
  224. webscout/Provider/yep.py +389 -0
  225. webscout/__init__.py +4 -2
  226. webscout/cli.py +3 -28
  227. webscout/client.py +70 -0
  228. webscout/conversation.py +35 -35
  229. webscout/litagent/Readme.md +276 -0
  230. webscout/litagent/__init__.py +29 -0
  231. webscout/litagent/agent.py +455 -0
  232. webscout/litagent/constants.py +60 -0
  233. webscout/litprinter/__init__.py +59 -0
  234. webscout/optimizers.py +419 -419
  235. webscout/scout/README.md +404 -0
  236. webscout/scout/__init__.py +8 -0
  237. webscout/scout/core/__init__.py +7 -0
  238. webscout/scout/core/crawler.py +210 -0
  239. webscout/scout/core/scout.py +607 -0
  240. webscout/scout/core/search_result.py +96 -0
  241. webscout/scout/core/text_analyzer.py +63 -0
  242. webscout/scout/core/text_utils.py +277 -0
  243. webscout/scout/core/web_analyzer.py +52 -0
  244. webscout/scout/element.py +478 -0
  245. webscout/scout/parsers/__init__.py +69 -0
  246. webscout/scout/parsers/html5lib_parser.py +172 -0
  247. webscout/scout/parsers/html_parser.py +236 -0
  248. webscout/scout/parsers/lxml_parser.py +178 -0
  249. webscout/scout/utils.py +37 -0
  250. webscout/swiftcli/Readme.md +323 -0
  251. webscout/swiftcli/__init__.py +95 -0
  252. webscout/swiftcli/core/__init__.py +7 -0
  253. webscout/swiftcli/core/cli.py +297 -0
  254. webscout/swiftcli/core/context.py +104 -0
  255. webscout/swiftcli/core/group.py +241 -0
  256. webscout/swiftcli/decorators/__init__.py +28 -0
  257. webscout/swiftcli/decorators/command.py +221 -0
  258. webscout/swiftcli/decorators/options.py +220 -0
  259. webscout/swiftcli/decorators/output.py +252 -0
  260. webscout/swiftcli/exceptions.py +21 -0
  261. webscout/swiftcli/plugins/__init__.py +9 -0
  262. webscout/swiftcli/plugins/base.py +135 -0
  263. webscout/swiftcli/plugins/manager.py +269 -0
  264. webscout/swiftcli/utils/__init__.py +59 -0
  265. webscout/swiftcli/utils/formatting.py +252 -0
  266. webscout/swiftcli/utils/parsing.py +267 -0
  267. webscout/version.py +1 -1
  268. webscout/webscout_search.py +2 -182
  269. webscout/webscout_search_async.py +1 -179
  270. webscout/zeroart/README.md +89 -0
  271. webscout/zeroart/__init__.py +135 -0
  272. webscout/zeroart/base.py +66 -0
  273. webscout/zeroart/effects.py +101 -0
  274. webscout/zeroart/fonts.py +1239 -0
  275. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/METADATA +262 -83
  276. webscout-8.2.9.dist-info/RECORD +289 -0
  277. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/WHEEL +1 -1
  278. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/entry_points.txt +1 -0
  279. webscout-8.2.7.dist-info/RECORD +0 -26
  280. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/licenses/LICENSE.md +0 -0
  281. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,365 @@
1
+ """AIArtaImager TTI-Compatible Provider - Generate stunning AI art with AI Arta! 🎨
2
+
3
+ Examples:
4
+ >>> from webscout.Provider.TTI.aiarta import AIArta
5
+ >>> client = AIArta()
6
+ >>> response = client.images.create(
7
+ ... model="flux",
8
+ ... prompt="A cool cyberpunk city at night",
9
+ ... n=1
10
+ ... )
11
+ >>> print(response)
12
+ """
13
+
14
+ import requests
15
+ from typing import Optional, List, Dict, Any
16
+ from webscout.Provider.TTI.utils import ImageData, ImageResponse
17
+ from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
18
+ from io import BytesIO
19
+ import os
20
+ import tempfile
21
+ from webscout.litagent import LitAgent
22
+ import time
23
+ import json
24
+
25
+ try:
26
+ from PIL import Image
27
+ except ImportError:
28
+ Image = None
29
+
30
+ class Images(BaseImages):
31
+ def __init__(self, client: 'AIArta'):
32
+ self._client = client
33
+
34
+ def create(
35
+ self,
36
+ *,
37
+ model: str,
38
+ prompt: str,
39
+ n: int = 1,
40
+ size: str = "1024x1024",
41
+ response_format: str = "url",
42
+ user: Optional[str] = None,
43
+ style: str = "none",
44
+ aspect_ratio: str = "1:1",
45
+ timeout: int = 60,
46
+ image_format: str = "png",
47
+ **kwargs
48
+ ) -> ImageResponse:
49
+ """
50
+ image_format: "png" or "jpeg"
51
+ """
52
+ if Image is None:
53
+ raise ImportError("Pillow (PIL) is required for image format conversion.")
54
+
55
+ images = []
56
+ urls = []
57
+ agent = LitAgent()
58
+
59
+ def upload_file_with_retry(img_bytes, image_format, max_retries=3):
60
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
61
+ for attempt in range(max_retries):
62
+ tmp_path = None
63
+ try:
64
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
65
+ tmp.write(img_bytes)
66
+ tmp.flush()
67
+ tmp_path = tmp.name
68
+ with open(tmp_path, 'rb') as f:
69
+ files = {
70
+ 'fileToUpload': (f'image.{ext}', f, f'image/{ext}')
71
+ }
72
+ data = {
73
+ 'reqtype': 'fileupload',
74
+ 'json': 'true'
75
+ }
76
+ headers = {'User-Agent': agent.random()}
77
+ if attempt > 0:
78
+ headers['Connection'] = 'close'
79
+ resp = requests.post("https://catbox.moe/user/api.php", files=files, data=data, headers=headers, timeout=timeout)
80
+ if resp.status_code == 200 and resp.text.strip():
81
+ text = resp.text.strip()
82
+ if text.startswith('http'):
83
+ return text
84
+ try:
85
+ result = resp.json()
86
+ if "url" in result:
87
+ return result["url"]
88
+ except json.JSONDecodeError:
89
+ if 'http' in text:
90
+ return text
91
+ except Exception:
92
+ if attempt < max_retries - 1:
93
+ time.sleep(1 * (attempt + 1))
94
+ finally:
95
+ if tmp_path and os.path.isfile(tmp_path):
96
+ try:
97
+ os.remove(tmp_path)
98
+ except Exception:
99
+ pass
100
+ return None
101
+
102
+ def upload_file_alternative(img_bytes, image_format):
103
+ try:
104
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
105
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
106
+ tmp.write(img_bytes)
107
+ tmp.flush()
108
+ tmp_path = tmp.name
109
+ try:
110
+ if not os.path.isfile(tmp_path):
111
+ return None
112
+ with open(tmp_path, 'rb') as img_file:
113
+ files = {'file': img_file}
114
+ response = requests.post('https://0x0.st', files=files)
115
+ response.raise_for_status()
116
+ image_url = response.text.strip()
117
+ if not image_url.startswith('http'):
118
+ return None
119
+ return image_url
120
+ except Exception:
121
+ return None
122
+ finally:
123
+ try:
124
+ os.remove(tmp_path)
125
+ except Exception:
126
+ pass
127
+ except Exception:
128
+ return None
129
+
130
+ for _ in range(n):
131
+ # Step 1: Get Authentication Token
132
+ auth_data = self._client.read_and_refresh_token()
133
+ gen_headers = {
134
+ "Authorization": auth_data.get("idToken"),
135
+ }
136
+ # Remove content-type header for form data
137
+ if "content-type" in self._client.session.headers:
138
+ del self._client.session.headers["content-type"]
139
+ # get_model now returns the proper style name from model_aliases
140
+ style_value = self._client.get_model(model)
141
+ image_payload = {
142
+ "prompt": str(prompt),
143
+ "negative_prompt": str(kwargs.get("negative_prompt", "blurry, deformed hands, ugly")),
144
+ "style": str(style_value),
145
+ "images_num": str(1), # Generate one image at a time in the loop
146
+ "cfg_scale": str(kwargs.get("guidance_scale", 7)),
147
+ "steps": str(kwargs.get("num_inference_steps", 30)),
148
+ "aspect_ratio": str(aspect_ratio),
149
+ }
150
+ # Step 2: Generate Image (send as form data, not JSON)
151
+ image_response = self._client.session.post(
152
+ self._client.image_generation_url,
153
+ data=image_payload, # Use form data instead of JSON
154
+ headers=gen_headers,
155
+ timeout=timeout
156
+ )
157
+ if image_response.status_code != 200:
158
+ raise RuntimeError(f"AIArta API error {image_response.status_code}: {image_response.text}\nPayload: {image_payload}")
159
+ image_data = image_response.json()
160
+ record_id = image_data.get("record_id")
161
+ if not record_id:
162
+ raise RuntimeError(f"Failed to initiate image generation: {image_data}")
163
+ # Step 3: Check Generation Status
164
+ status_url = self._client.status_check_url.format(record_id=record_id)
165
+ while True:
166
+ status_response = self._client.session.get(
167
+ status_url,
168
+ headers=gen_headers,
169
+ timeout=timeout
170
+ )
171
+ status_data = status_response.json()
172
+ status = status_data.get("status")
173
+ if status == "DONE":
174
+ image_urls = [image["url"] for image in status_data.get("response", [])]
175
+ if not image_urls:
176
+ raise RuntimeError("No image URLs returned from AIArta")
177
+ img_resp = self._client.session.get(image_urls[0], timeout=timeout)
178
+ img_resp.raise_for_status()
179
+ img_bytes = img_resp.content
180
+ # Convert to png or jpeg in memory
181
+ with BytesIO(img_bytes) as input_io:
182
+ with Image.open(input_io) as im:
183
+ out_io = BytesIO()
184
+ if image_format.lower() == "jpeg":
185
+ im = im.convert("RGB")
186
+ im.save(out_io, format="JPEG")
187
+ else:
188
+ im.save(out_io, format="PNG")
189
+ img_bytes = out_io.getvalue()
190
+ images.append(img_bytes)
191
+ if response_format == "url":
192
+ uploaded_url = upload_file_with_retry(img_bytes, image_format)
193
+ if not uploaded_url:
194
+ uploaded_url = upload_file_alternative(img_bytes, image_format)
195
+ if uploaded_url:
196
+ urls.append(uploaded_url)
197
+ else:
198
+ raise RuntimeError("Failed to upload image to catbox.moe using all available methods")
199
+ break
200
+ elif status in ("IN_QUEUE", "IN_PROGRESS"):
201
+ time.sleep(2)
202
+ else:
203
+ raise RuntimeError(f"Image generation failed with status: {status}")
204
+
205
+ result_data = []
206
+ if response_format == "url":
207
+ for url in urls:
208
+ result_data.append(ImageData(url=url))
209
+ elif response_format == "b64_json":
210
+ import base64
211
+ for img in images:
212
+ b64 = base64.b64encode(img).decode("utf-8")
213
+ result_data.append(ImageData(b64_json=b64))
214
+ else:
215
+ raise ValueError("response_format must be 'url' or 'b64_json'")
216
+
217
+ from time import time as _time
218
+ return ImageResponse(
219
+ created=int(_time()),
220
+ data=result_data
221
+ )
222
+
223
+ class AIArta(TTICompatibleProvider):
224
+ # Model aliases mapping from lowercase keys to proper API style names
225
+ model_aliases = {
226
+ "flux": "Flux",
227
+ "medieval": "Medieval",
228
+ "vincent_van_gogh": "Vincent Van Gogh",
229
+ "f_dev": "F Dev",
230
+ "low_poly": "Low Poly",
231
+ "dreamshaper_xl": "Dreamshaper-xl",
232
+ "anima_pencil_xl": "Anima-pencil-xl",
233
+ "biomech": "Biomech",
234
+ "trash_polka": "Trash Polka",
235
+ "no_style": "No Style",
236
+ "cheyenne_xl": "Cheyenne-xl",
237
+ "chicano": "Chicano",
238
+ "embroidery_tattoo": "Embroidery tattoo",
239
+ "red_and_black": "Red and Black",
240
+ "fantasy_art": "Fantasy Art",
241
+ "watercolor": "Watercolor",
242
+ "dotwork": "Dotwork",
243
+ "old_school_colored": "Old school colored",
244
+ "realistic_tattoo": "Realistic tattoo",
245
+ "japanese_2": "Japanese_2",
246
+ "realistic_stock_xl": "Realistic-stock-xl",
247
+ "f_pro": "F Pro",
248
+ "revanimated": "RevAnimated",
249
+ "katayama_mix_xl": "Katayama-mix-xl",
250
+ "sdxl_l": "SDXL L",
251
+ "cor_epica_xl": "Cor-epica-xl",
252
+ "anime_tattoo": "Anime tattoo",
253
+ "new_school": "New School",
254
+ "death_metal": "Death metal",
255
+ "old_school": "Old School",
256
+ "juggernaut_xl": "Juggernaut-xl",
257
+ "photographic": "Photographic",
258
+ "sdxl_1_0": "SDXL 1.0",
259
+ "graffiti": "Graffiti",
260
+ "mini_tattoo": "Mini tattoo",
261
+ "surrealism": "Surrealism",
262
+ "neo_traditional": "Neo-traditional",
263
+ "on_limbs_black": "On limbs black",
264
+ "yamers_realistic_xl": "Yamers-realistic-xl",
265
+ "pony_xl": "Pony-xl",
266
+ "playground_xl": "Playground-xl",
267
+ "anything_xl": "Anything-xl",
268
+ "flame_design": "Flame design",
269
+ "kawaii": "Kawaii",
270
+ "cinematic_art": "Cinematic Art",
271
+ "professional": "Professional",
272
+ "black_ink": "Black Ink"
273
+ }
274
+
275
+ AVAILABLE_MODELS = list(model_aliases.keys())
276
+ default_model = "Flux"
277
+ default_image_model = default_model
278
+
279
+ def __init__(self):
280
+ self.image_generation_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image"
281
+ self.status_check_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image/{record_id}/status"
282
+ self.auth_url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
283
+ self.token_refresh_url = "https://securetoken.googleapis.com/v1/token?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
284
+ self.session = requests.Session()
285
+ self.user_agent = LitAgent().random()
286
+ self.headers = {
287
+ "accept": "application/json",
288
+ "accept-language": "en-US,en;q=0.9",
289
+ "origin": "https://img-gen-prod.ai-arta.com",
290
+ "referer": "https://img-gen-prod.ai-arta.com/",
291
+ "user-agent": self.user_agent,
292
+ }
293
+ self.session.headers.update(self.headers)
294
+ self.images = Images(self)
295
+
296
+ def get_auth_file(self) -> str:
297
+ path = os.path.join(os.path.expanduser("~"), ".ai_arta_cookies")
298
+ if not os.path.exists(path):
299
+ os.makedirs(path)
300
+ filename = f"auth_{self.__class__.__name__}.json"
301
+ return os.path.join(path, filename)
302
+
303
+ def create_token(self, path: str) -> Dict[str, Any]:
304
+ auth_payload = {"clientType": "CLIENT_TYPE_ANDROID"}
305
+ proxies = self.session.proxies if self.session.proxies else None
306
+ auth_response = self.session.post(self.auth_url, json=auth_payload, timeout=60, proxies=proxies)
307
+ auth_data = auth_response.json()
308
+ auth_token = auth_data.get("idToken")
309
+ if not auth_token:
310
+ raise Exception("Failed to obtain authentication token.")
311
+ with open(path, 'w') as f:
312
+ json.dump(auth_data, f)
313
+ return auth_data
314
+
315
+ def refresh_token(self, refresh_token: str) -> tuple[str, str]:
316
+ payload = {
317
+ "grant_type": "refresh_token",
318
+ "refresh_token": refresh_token,
319
+ }
320
+ response = self.session.post(self.token_refresh_url, data=payload, timeout=60)
321
+ response_data = response.json()
322
+ return response_data.get("id_token"), response_data.get("refresh_token")
323
+
324
+ def read_and_refresh_token(self) -> Dict[str, Any]:
325
+ path = self.get_auth_file()
326
+ if os.path.isfile(path):
327
+ with open(path, 'r') as f:
328
+ auth_data = json.load(f)
329
+ diff = time.time() - os.path.getmtime(path)
330
+ expires_in = int(auth_data.get("expiresIn", 3600))
331
+ if diff < expires_in:
332
+ if diff > expires_in / 2:
333
+ auth_data["idToken"], auth_data["refreshToken"] = self.refresh_token(
334
+ auth_data.get("refreshToken")
335
+ )
336
+ with open(path, 'w') as f:
337
+ json.dump(auth_data, f)
338
+ return auth_data
339
+ return self.create_token(path)
340
+
341
+ def get_model(self, model_name: str) -> str:
342
+ # Convert to lowercase for lookup
343
+ model_key = model_name.lower()
344
+ # Return the proper style name from model_aliases, or the original if not found
345
+ return self.model_aliases.get(model_key, model_name)
346
+
347
+ @property
348
+ def models(self):
349
+ class _ModelList:
350
+ def list(inner_self):
351
+ return type(self).AVAILABLE_MODELS
352
+ return _ModelList()
353
+
354
+ # Example usage:
355
+ if __name__ == "__main__":
356
+ from rich import print
357
+ client = AIArta()
358
+ response = client.images.create(
359
+ model="flux",
360
+ prompt="a white siamese cat",
361
+ response_format="url",
362
+ n=2,
363
+ timeout=30,
364
+ )
365
+ print(response)
File without changes
@@ -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)