webscout 8.3.6__py3-none-any.whl → 2025.10.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of webscout might be problematic. Click here for more details.
- webscout/AIauto.py +250 -250
- webscout/AIbase.py +379 -379
- webscout/AIutel.py +60 -58
- webscout/Bard.py +1012 -1012
- webscout/Bing_search.py +417 -417
- webscout/DWEBS.py +529 -529
- webscout/Extra/Act.md +309 -309
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/GitToolkit/gitapi/README.md +110 -110
- webscout/Extra/GitToolkit/gitapi/__init__.py +11 -11
- webscout/Extra/GitToolkit/gitapi/repository.py +195 -195
- webscout/Extra/GitToolkit/gitapi/user.py +96 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +61 -61
- webscout/Extra/YTToolkit/README.md +375 -375
- webscout/Extra/YTToolkit/YTdownloader.py +956 -956
- webscout/Extra/YTToolkit/__init__.py +2 -2
- webscout/Extra/YTToolkit/transcriber.py +475 -475
- webscout/Extra/YTToolkit/ytapi/README.md +44 -44
- webscout/Extra/YTToolkit/ytapi/__init__.py +6 -6
- webscout/Extra/YTToolkit/ytapi/channel.py +307 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +118 -118
- webscout/Extra/YTToolkit/ytapi/https.py +88 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +58 -58
- webscout/Extra/YTToolkit/ytapi/pool.py +7 -7
- webscout/Extra/YTToolkit/ytapi/query.py +39 -39
- webscout/Extra/YTToolkit/ytapi/stream.py +62 -62
- webscout/Extra/YTToolkit/ytapi/utils.py +62 -62
- webscout/Extra/YTToolkit/ytapi/video.py +232 -232
- webscout/Extra/autocoder/__init__.py +9 -9
- webscout/Extra/autocoder/autocoder.py +1105 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +332 -332
- webscout/Extra/gguf.md +429 -429
- webscout/Extra/gguf.py +1213 -1213
- webscout/Extra/tempmail/README.md +487 -487
- webscout/Extra/tempmail/__init__.py +27 -27
- webscout/Extra/tempmail/async_utils.py +140 -140
- webscout/Extra/tempmail/base.py +160 -160
- webscout/Extra/tempmail/cli.py +186 -186
- webscout/Extra/tempmail/emailnator.py +84 -84
- webscout/Extra/tempmail/mail_tm.py +360 -360
- webscout/Extra/tempmail/temp_mail_io.py +291 -291
- webscout/Extra/weather.md +281 -281
- webscout/Extra/weather.py +193 -193
- webscout/Litlogger/README.md +10 -10
- webscout/Litlogger/__init__.py +15 -15
- webscout/Litlogger/formats.py +13 -13
- webscout/Litlogger/handlers.py +121 -121
- webscout/Litlogger/levels.py +13 -13
- webscout/Litlogger/logger.py +134 -134
- webscout/Provider/AISEARCH/Perplexity.py +332 -332
- webscout/Provider/AISEARCH/README.md +279 -279
- webscout/Provider/AISEARCH/__init__.py +33 -11
- webscout/Provider/AISEARCH/felo_search.py +206 -206
- webscout/Provider/AISEARCH/genspark_search.py +323 -323
- webscout/Provider/AISEARCH/hika_search.py +185 -185
- webscout/Provider/AISEARCH/iask_search.py +410 -410
- webscout/Provider/AISEARCH/monica_search.py +219 -219
- webscout/Provider/AISEARCH/scira_search.py +316 -314
- webscout/Provider/AISEARCH/stellar_search.py +177 -177
- webscout/Provider/AISEARCH/webpilotai_search.py +255 -255
- webscout/Provider/Aitopia.py +314 -315
- webscout/Provider/Andi.py +3 -3
- webscout/Provider/Apriel.py +306 -0
- webscout/Provider/ChatGPTClone.py +236 -236
- webscout/Provider/ChatSandbox.py +343 -342
- webscout/Provider/Cloudflare.py +324 -324
- webscout/Provider/Cohere.py +208 -207
- webscout/Provider/Deepinfra.py +370 -369
- webscout/Provider/ExaAI.py +260 -260
- webscout/Provider/ExaChat.py +308 -387
- webscout/Provider/Flowith.py +221 -221
- webscout/Provider/GMI.py +293 -0
- webscout/Provider/Gemini.py +164 -162
- webscout/Provider/GeminiProxy.py +167 -166
- webscout/Provider/GithubChat.py +371 -370
- webscout/Provider/Groq.py +800 -800
- webscout/Provider/HeckAI.py +383 -379
- webscout/Provider/Jadve.py +282 -297
- webscout/Provider/K2Think.py +308 -0
- webscout/Provider/Koboldai.py +206 -384
- webscout/Provider/LambdaChat.py +423 -425
- webscout/Provider/Nemotron.py +244 -245
- webscout/Provider/Netwrck.py +248 -247
- webscout/Provider/OLLAMA.py +395 -394
- webscout/Provider/OPENAI/Cloudflare.py +394 -395
- webscout/Provider/OPENAI/FalconH1.py +452 -457
- webscout/Provider/OPENAI/FreeGemini.py +297 -299
- webscout/Provider/OPENAI/{monochat.py → K2Think.py} +432 -329
- webscout/Provider/OPENAI/NEMOTRON.py +241 -244
- webscout/Provider/OPENAI/PI.py +428 -427
- webscout/Provider/OPENAI/README.md +959 -959
- webscout/Provider/OPENAI/TogetherAI.py +345 -345
- webscout/Provider/OPENAI/TwoAI.py +466 -467
- webscout/Provider/OPENAI/__init__.py +33 -59
- webscout/Provider/OPENAI/ai4chat.py +313 -303
- webscout/Provider/OPENAI/base.py +249 -269
- webscout/Provider/OPENAI/chatglm.py +528 -0
- webscout/Provider/OPENAI/chatgpt.py +593 -588
- webscout/Provider/OPENAI/chatgptclone.py +521 -524
- webscout/Provider/OPENAI/chatsandbox.py +202 -177
- webscout/Provider/OPENAI/deepinfra.py +319 -315
- webscout/Provider/OPENAI/e2b.py +1665 -1665
- webscout/Provider/OPENAI/exaai.py +420 -420
- webscout/Provider/OPENAI/exachat.py +452 -452
- webscout/Provider/OPENAI/friendli.py +232 -232
- webscout/Provider/OPENAI/{refact.py → gmi.py} +324 -274
- webscout/Provider/OPENAI/groq.py +364 -364
- webscout/Provider/OPENAI/heckai.py +314 -311
- webscout/Provider/OPENAI/llmchatco.py +337 -337
- webscout/Provider/OPENAI/netwrck.py +355 -354
- webscout/Provider/OPENAI/oivscode.py +290 -290
- webscout/Provider/OPENAI/opkfc.py +518 -518
- webscout/Provider/OPENAI/pydantic_imports.py +1 -1
- webscout/Provider/OPENAI/scirachat.py +535 -529
- webscout/Provider/OPENAI/sonus.py +308 -308
- webscout/Provider/OPENAI/standardinput.py +442 -442
- webscout/Provider/OPENAI/textpollinations.py +340 -348
- webscout/Provider/OPENAI/toolbaz.py +419 -413
- webscout/Provider/OPENAI/typefully.py +362 -362
- webscout/Provider/OPENAI/utils.py +295 -295
- webscout/Provider/OPENAI/venice.py +436 -436
- webscout/Provider/OPENAI/wisecat.py +387 -387
- webscout/Provider/OPENAI/writecream.py +166 -166
- webscout/Provider/OPENAI/x0gpt.py +378 -378
- webscout/Provider/OPENAI/yep.py +389 -389
- webscout/Provider/OpenGPT.py +230 -230
- webscout/Provider/Openai.py +244 -496
- webscout/Provider/PI.py +405 -404
- webscout/Provider/Perplexitylabs.py +430 -431
- webscout/Provider/QwenLM.py +272 -254
- webscout/Provider/STT/__init__.py +32 -2
- webscout/Provider/{Llama3.py → Sambanova.py} +257 -258
- webscout/Provider/StandardInput.py +309 -309
- webscout/Provider/TTI/README.md +82 -82
- webscout/Provider/TTI/__init__.py +33 -12
- webscout/Provider/TTI/aiarta.py +413 -413
- webscout/Provider/TTI/base.py +136 -136
- webscout/Provider/TTI/bing.py +243 -243
- webscout/Provider/TTI/gpt1image.py +149 -149
- webscout/Provider/TTI/imagen.py +196 -196
- webscout/Provider/TTI/infip.py +211 -211
- webscout/Provider/TTI/magicstudio.py +232 -232
- webscout/Provider/TTI/monochat.py +219 -219
- webscout/Provider/TTI/piclumen.py +214 -214
- webscout/Provider/TTI/pixelmuse.py +232 -232
- webscout/Provider/TTI/pollinations.py +232 -232
- webscout/Provider/TTI/together.py +288 -288
- webscout/Provider/TTI/utils.py +12 -12
- webscout/Provider/TTI/venice.py +367 -367
- webscout/Provider/TTS/README.md +192 -192
- webscout/Provider/TTS/__init__.py +33 -10
- webscout/Provider/TTS/parler.py +110 -110
- webscout/Provider/TTS/streamElements.py +333 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TeachAnything.py +237 -236
- webscout/Provider/TextPollinationsAI.py +311 -318
- webscout/Provider/TogetherAI.py +356 -357
- webscout/Provider/TwoAI.py +313 -569
- webscout/Provider/TypliAI.py +312 -311
- webscout/Provider/UNFINISHED/ChatHub.py +208 -208
- webscout/Provider/UNFINISHED/ChutesAI.py +313 -313
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +294 -294
- webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +198 -198
- webscout/Provider/{Qodo.py → UNFINISHED/Qodo.py} +477 -477
- webscout/Provider/UNFINISHED/VercelAIGateway.py +338 -338
- webscout/Provider/{XenAI.py → UNFINISHED/XenAI.py} +324 -324
- webscout/Provider/UNFINISHED/Youchat.py +330 -330
- webscout/Provider/UNFINISHED/liner.py +334 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +262 -262
- webscout/Provider/UNFINISHED/puterjs.py +634 -634
- webscout/Provider/UNFINISHED/samurai.py +223 -223
- webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
- webscout/Provider/Venice.py +251 -250
- webscout/Provider/VercelAI.py +256 -255
- webscout/Provider/WiseCat.py +232 -231
- webscout/Provider/WrDoChat.py +367 -366
- webscout/Provider/__init__.py +33 -86
- webscout/Provider/ai4chat.py +174 -174
- webscout/Provider/akashgpt.py +331 -334
- webscout/Provider/cerebras.py +446 -340
- webscout/Provider/chatglm.py +394 -214
- webscout/Provider/cleeai.py +211 -212
- webscout/Provider/deepseek_assistant.py +1 -1
- webscout/Provider/elmo.py +282 -282
- webscout/Provider/geminiapi.py +208 -208
- webscout/Provider/granite.py +261 -261
- webscout/Provider/hermes.py +263 -265
- webscout/Provider/julius.py +223 -222
- webscout/Provider/learnfastai.py +309 -309
- webscout/Provider/llama3mitril.py +214 -214
- webscout/Provider/llmchat.py +243 -243
- webscout/Provider/llmchatco.py +290 -290
- webscout/Provider/meta.py +801 -801
- webscout/Provider/oivscode.py +309 -309
- webscout/Provider/scira_chat.py +384 -457
- webscout/Provider/searchchat.py +292 -291
- webscout/Provider/sonus.py +258 -258
- webscout/Provider/toolbaz.py +370 -364
- webscout/Provider/turboseek.py +274 -265
- webscout/Provider/typefully.py +208 -207
- webscout/Provider/x0gpt.py +1 -0
- webscout/Provider/yep.py +372 -371
- webscout/__init__.py +30 -31
- webscout/__main__.py +5 -5
- webscout/auth/api_key_manager.py +189 -189
- webscout/auth/config.py +175 -175
- webscout/auth/models.py +185 -185
- webscout/auth/routes.py +664 -664
- webscout/auth/simple_logger.py +236 -236
- webscout/cli.py +523 -523
- webscout/conversation.py +438 -438
- webscout/exceptions.py +361 -361
- webscout/litagent/Readme.md +298 -298
- webscout/litagent/__init__.py +28 -28
- webscout/litagent/agent.py +581 -581
- webscout/litagent/constants.py +59 -59
- webscout/litprinter/__init__.py +58 -58
- webscout/models.py +181 -181
- webscout/optimizers.py +419 -419
- webscout/prompt_manager.py +288 -288
- webscout/sanitize.py +1078 -1078
- webscout/scout/README.md +401 -401
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +6 -6
- webscout/scout/core/crawler.py +297 -297
- webscout/scout/core/scout.py +706 -706
- webscout/scout/core/search_result.py +95 -95
- webscout/scout/core/text_analyzer.py +62 -62
- webscout/scout/core/text_utils.py +277 -277
- webscout/scout/core/web_analyzer.py +51 -51
- webscout/scout/element.py +599 -599
- webscout/scout/parsers/__init__.py +69 -69
- webscout/scout/parsers/html5lib_parser.py +172 -172
- webscout/scout/parsers/html_parser.py +236 -236
- webscout/scout/parsers/lxml_parser.py +178 -178
- webscout/scout/utils.py +37 -37
- webscout/swiftcli/Readme.md +323 -323
- webscout/swiftcli/__init__.py +95 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +308 -308
- webscout/swiftcli/core/context.py +104 -104
- webscout/swiftcli/core/group.py +241 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +221 -221
- webscout/swiftcli/decorators/options.py +220 -220
- webscout/swiftcli/decorators/output.py +302 -302
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +135 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +59 -59
- webscout/swiftcli/utils/formatting.py +252 -252
- webscout/swiftcli/utils/parsing.py +267 -267
- webscout/update_checker.py +117 -117
- webscout/version.py +1 -1
- webscout/webscout_search.py +1183 -1183
- webscout/webscout_search_async.py +649 -649
- webscout/yep_search.py +346 -346
- webscout/zeroart/README.md +89 -89
- webscout/zeroart/__init__.py +134 -134
- webscout/zeroart/base.py +66 -66
- webscout/zeroart/effects.py +100 -100
- webscout/zeroart/fonts.py +1238 -1238
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/METADATA +937 -936
- webscout-2025.10.11.dist-info/RECORD +300 -0
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -793
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/GptOss.py +0 -207
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/Kimi.py +0 -445
- webscout/Provider/MCPCore.py +0 -322
- webscout/Provider/MiniMax.py +0 -207
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -1045
- webscout/Provider/OPENAI/MiniMax.py +0 -298
- webscout/Provider/OPENAI/Qwen3.py +0 -304
- webscout/Provider/OPENAI/autoproxy.py +0 -1067
- webscout/Provider/OPENAI/copilot.py +0 -321
- webscout/Provider/OPENAI/gptoss.py +0 -288
- webscout/Provider/OPENAI/kimi.py +0 -469
- webscout/Provider/OPENAI/mcpcore.py +0 -431
- webscout/Provider/OPENAI/multichat.py +0 -378
- webscout/Provider/OPENAI/qodo.py +0 -630
- webscout/Provider/OPENAI/xenai.py +0 -514
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/UNFINISHED/fetch_together_models.py +0 -90
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/copilot.py +0 -441
- webscout/Provider/freeaichat.py +0 -294
- webscout/Provider/koala.py +0 -182
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/monochat.py +0 -275
- webscout/Provider/multichat.py +0 -375
- webscout/Provider/scnet.py +0 -244
- webscout/Provider/talkai.py +0 -194
- webscout/tempid.py +0 -128
- webscout-8.3.6.dist-info/RECORD +0 -327
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/WHEEL +0 -0
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/entry_points.txt +0 -0
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/top_level.txt +0 -0
webscout/Provider/TTI/aiarta.py
CHANGED
|
@@ -1,413 +1,413 @@
|
|
|
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 (
|
|
17
|
-
ImageData,
|
|
18
|
-
ImageResponse
|
|
19
|
-
)
|
|
20
|
-
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
21
|
-
from io import BytesIO
|
|
22
|
-
import os
|
|
23
|
-
import tempfile
|
|
24
|
-
from webscout.litagent import LitAgent
|
|
25
|
-
import time
|
|
26
|
-
import json
|
|
27
|
-
import random
|
|
28
|
-
from pathlib import Path
|
|
29
|
-
|
|
30
|
-
try:
|
|
31
|
-
from PIL import Image
|
|
32
|
-
except ImportError:
|
|
33
|
-
Image = None
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class ModelNotFoundError(Exception):
|
|
37
|
-
pass
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class Images(BaseImages):
|
|
41
|
-
def __init__(self, client: "AIArta"):
|
|
42
|
-
self._client = client
|
|
43
|
-
|
|
44
|
-
def create(
|
|
45
|
-
self,
|
|
46
|
-
*,
|
|
47
|
-
model: str,
|
|
48
|
-
prompt: str,
|
|
49
|
-
n: int = 1,
|
|
50
|
-
size: str = "1024x1024",
|
|
51
|
-
response_format: str = "url",
|
|
52
|
-
user: Optional[str] = None,
|
|
53
|
-
style: str = "none",
|
|
54
|
-
aspect_ratio: str = "1:1",
|
|
55
|
-
timeout: int = 60,
|
|
56
|
-
image_format: str = "png",
|
|
57
|
-
**kwargs,
|
|
58
|
-
) -> ImageResponse:
|
|
59
|
-
"""
|
|
60
|
-
image_format: "png" or "jpeg"
|
|
61
|
-
"""
|
|
62
|
-
if Image is None:
|
|
63
|
-
raise ImportError("Pillow (PIL) is required for image format conversion.")
|
|
64
|
-
|
|
65
|
-
images = []
|
|
66
|
-
urls = []
|
|
67
|
-
agent = LitAgent()
|
|
68
|
-
|
|
69
|
-
def upload_file_with_retry(img_bytes, image_format, max_retries=3):
|
|
70
|
-
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
71
|
-
for attempt in range(max_retries):
|
|
72
|
-
tmp_path = None
|
|
73
|
-
try:
|
|
74
|
-
with tempfile.NamedTemporaryFile(
|
|
75
|
-
suffix=f".{ext}", delete=False
|
|
76
|
-
) as tmp:
|
|
77
|
-
tmp.write(img_bytes)
|
|
78
|
-
tmp.flush()
|
|
79
|
-
tmp_path = tmp.name
|
|
80
|
-
with open(tmp_path, "rb") as f:
|
|
81
|
-
files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
|
|
82
|
-
data = {"reqtype": "fileupload", "json": "true"}
|
|
83
|
-
headers = {"User-Agent": agent.random()}
|
|
84
|
-
if attempt > 0:
|
|
85
|
-
headers["Connection"] = "close"
|
|
86
|
-
resp = requests.post(
|
|
87
|
-
"https://catbox.moe/user/api.php",
|
|
88
|
-
files=files,
|
|
89
|
-
data=data,
|
|
90
|
-
headers=headers,
|
|
91
|
-
timeout=timeout,
|
|
92
|
-
)
|
|
93
|
-
if resp.status_code == 200 and resp.text.strip():
|
|
94
|
-
text = resp.text.strip()
|
|
95
|
-
if text.startswith("http"):
|
|
96
|
-
return text
|
|
97
|
-
try:
|
|
98
|
-
result = resp.json()
|
|
99
|
-
if "url" in result:
|
|
100
|
-
return result["url"]
|
|
101
|
-
except json.JSONDecodeError:
|
|
102
|
-
if "http" in text:
|
|
103
|
-
return text
|
|
104
|
-
except Exception:
|
|
105
|
-
if attempt < max_retries - 1:
|
|
106
|
-
time.sleep(1 * (attempt + 1))
|
|
107
|
-
finally:
|
|
108
|
-
if tmp_path and os.path.isfile(tmp_path):
|
|
109
|
-
try:
|
|
110
|
-
os.remove(tmp_path)
|
|
111
|
-
except Exception:
|
|
112
|
-
pass
|
|
113
|
-
return None
|
|
114
|
-
|
|
115
|
-
def upload_file_alternative(img_bytes, image_format):
|
|
116
|
-
try:
|
|
117
|
-
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
118
|
-
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
119
|
-
tmp.write(img_bytes)
|
|
120
|
-
tmp.flush()
|
|
121
|
-
tmp_path = tmp.name
|
|
122
|
-
try:
|
|
123
|
-
if not os.path.isfile(tmp_path):
|
|
124
|
-
return None
|
|
125
|
-
with open(tmp_path, "rb") as img_file:
|
|
126
|
-
files = {"file": img_file}
|
|
127
|
-
response = requests.post("https://0x0.st", files=files)
|
|
128
|
-
response.raise_for_status()
|
|
129
|
-
image_url = response.text.strip()
|
|
130
|
-
if not image_url.startswith("http"):
|
|
131
|
-
return None
|
|
132
|
-
return image_url
|
|
133
|
-
except Exception:
|
|
134
|
-
return None
|
|
135
|
-
finally:
|
|
136
|
-
try:
|
|
137
|
-
os.remove(tmp_path)
|
|
138
|
-
except Exception:
|
|
139
|
-
pass
|
|
140
|
-
except Exception:
|
|
141
|
-
return None
|
|
142
|
-
|
|
143
|
-
for _ in range(n):
|
|
144
|
-
# Step 1: Get Authentication Token
|
|
145
|
-
auth_data = self._client.read_and_refresh_token()
|
|
146
|
-
gen_headers = {
|
|
147
|
-
"Authorization": auth_data.get("idToken"),
|
|
148
|
-
}
|
|
149
|
-
if "content-type" in self._client.session.headers:
|
|
150
|
-
del self._client.session.headers["content-type"]
|
|
151
|
-
# Use the model name directly, not as 'style'
|
|
152
|
-
image_payload = {
|
|
153
|
-
"prompt": str(prompt),
|
|
154
|
-
"negative_prompt": str(
|
|
155
|
-
kwargs.get("negative_prompt", "blurry, deformed hands, ugly")
|
|
156
|
-
),
|
|
157
|
-
"style": str(model), # Use 'style' key for the model name
|
|
158
|
-
"images_num": str(1),
|
|
159
|
-
"cfg_scale": str(kwargs.get("guidance_scale", 7)),
|
|
160
|
-
"steps": str(kwargs.get("num_inference_steps", 30)),
|
|
161
|
-
"aspect_ratio": str(aspect_ratio),
|
|
162
|
-
}
|
|
163
|
-
# Remove 'model' from payload if present
|
|
164
|
-
if "model" in image_payload:
|
|
165
|
-
del image_payload["model"]
|
|
166
|
-
# Step 2: Generate Image (send as form data, not JSON)
|
|
167
|
-
image_response = self._client.session.post(
|
|
168
|
-
self._client.image_generation_url,
|
|
169
|
-
data=image_payload, # Use form data instead of JSON
|
|
170
|
-
headers=gen_headers,
|
|
171
|
-
timeout=timeout,
|
|
172
|
-
)
|
|
173
|
-
if image_response.status_code != 200:
|
|
174
|
-
raise RuntimeError(
|
|
175
|
-
f"AIArta API error {image_response.status_code}: {image_response.text}\nPayload: {image_payload}"
|
|
176
|
-
)
|
|
177
|
-
image_data = image_response.json()
|
|
178
|
-
record_id = image_data.get("record_id")
|
|
179
|
-
if not record_id:
|
|
180
|
-
raise RuntimeError(f"Failed to initiate image generation: {image_data}")
|
|
181
|
-
# Step 3: Check Generation Status
|
|
182
|
-
status_url = self._client.status_check_url.format(record_id=record_id)
|
|
183
|
-
while True:
|
|
184
|
-
status_response = self._client.session.get(
|
|
185
|
-
status_url,
|
|
186
|
-
headers=gen_headers,
|
|
187
|
-
timeout=timeout,
|
|
188
|
-
)
|
|
189
|
-
status_data = status_response.json()
|
|
190
|
-
status = status_data.get("status")
|
|
191
|
-
if status == "DONE":
|
|
192
|
-
image_urls = [
|
|
193
|
-
image["url"] for image in status_data.get("response", [])
|
|
194
|
-
]
|
|
195
|
-
if not image_urls:
|
|
196
|
-
raise RuntimeError("No image URLs returned from AIArta")
|
|
197
|
-
img_resp = self._client.session.get(
|
|
198
|
-
image_urls[0],
|
|
199
|
-
timeout=timeout,
|
|
200
|
-
)
|
|
201
|
-
img_resp.raise_for_status()
|
|
202
|
-
img_bytes = img_resp.content
|
|
203
|
-
# Convert to png or jpeg in memory
|
|
204
|
-
with BytesIO(img_bytes) as input_io:
|
|
205
|
-
with Image.open(input_io) as im:
|
|
206
|
-
out_io = BytesIO()
|
|
207
|
-
if image_format.lower() == "jpeg":
|
|
208
|
-
im = im.convert("RGB")
|
|
209
|
-
im.save(out_io, format="JPEG")
|
|
210
|
-
else:
|
|
211
|
-
im.save(out_io, format="PNG")
|
|
212
|
-
img_bytes = out_io.getvalue()
|
|
213
|
-
images.append(img_bytes)
|
|
214
|
-
if response_format == "url":
|
|
215
|
-
uploaded_url = upload_file_with_retry(img_bytes, image_format)
|
|
216
|
-
if not uploaded_url:
|
|
217
|
-
uploaded_url = upload_file_alternative(
|
|
218
|
-
img_bytes, image_format
|
|
219
|
-
)
|
|
220
|
-
if uploaded_url:
|
|
221
|
-
urls.append(uploaded_url)
|
|
222
|
-
else:
|
|
223
|
-
raise RuntimeError(
|
|
224
|
-
"Failed to upload image to catbox.moe using all available methods"
|
|
225
|
-
)
|
|
226
|
-
break
|
|
227
|
-
elif status in ("IN_QUEUE", "IN_PROGRESS"):
|
|
228
|
-
time.sleep(2)
|
|
229
|
-
else:
|
|
230
|
-
raise RuntimeError(f"Image generation failed with status: {status}")
|
|
231
|
-
|
|
232
|
-
result_data = []
|
|
233
|
-
if response_format == "url":
|
|
234
|
-
for url in urls:
|
|
235
|
-
result_data.append(ImageData(url=url))
|
|
236
|
-
elif response_format == "b64_json":
|
|
237
|
-
import base64
|
|
238
|
-
|
|
239
|
-
for img in images:
|
|
240
|
-
b64 = base64.b64encode(img).decode("utf-8")
|
|
241
|
-
result_data.append(ImageData(b64_json=b64))
|
|
242
|
-
else:
|
|
243
|
-
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
244
|
-
|
|
245
|
-
from time import time as _time
|
|
246
|
-
|
|
247
|
-
return ImageResponse(created=int(_time()), data=result_data)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
class AIArta(TTICompatibleProvider):
|
|
251
|
-
url = "https://ai-arta.com"
|
|
252
|
-
auth_url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
|
|
253
|
-
token_refresh_url = "https://securetoken.googleapis.com/v1/token?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
|
|
254
|
-
image_generation_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image"
|
|
255
|
-
status_check_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image/{record_id}/status"
|
|
256
|
-
AVAILABLE_MODELS = [
|
|
257
|
-
"Anything-xl",
|
|
258
|
-
"High GPT4o",
|
|
259
|
-
"On limbs black",
|
|
260
|
-
"F Dev",
|
|
261
|
-
"SDXL 1.0",
|
|
262
|
-
"Old School",
|
|
263
|
-
"Vincent Van Gogh",
|
|
264
|
-
"Cor-epica-xl",
|
|
265
|
-
"Professional",
|
|
266
|
-
"Cheyenne-xl",
|
|
267
|
-
"Chicano",
|
|
268
|
-
"SDXL L",
|
|
269
|
-
"Black Ink",
|
|
270
|
-
"Juggernaut-xl",
|
|
271
|
-
"Cinematic Art",
|
|
272
|
-
"Dreamshaper-xl",
|
|
273
|
-
"Fantasy Art",
|
|
274
|
-
"Neo-traditional",
|
|
275
|
-
"Realistic-stock-xl",
|
|
276
|
-
"Flame design",
|
|
277
|
-
"Japanese_2",
|
|
278
|
-
"Medieval",
|
|
279
|
-
"Surrealism",
|
|
280
|
-
"Dotwork",
|
|
281
|
-
"Graffiti",
|
|
282
|
-
"RevAnimated",
|
|
283
|
-
"On limbs color",
|
|
284
|
-
"Old school colored",
|
|
285
|
-
"GPT4o Ghibli",
|
|
286
|
-
"Low Poly",
|
|
287
|
-
"GPT4o",
|
|
288
|
-
"No Style",
|
|
289
|
-
"Anime",
|
|
290
|
-
"tattoo",
|
|
291
|
-
"Embroidery tattoo",
|
|
292
|
-
"Mini tattoo",
|
|
293
|
-
"Realistic tattoo",
|
|
294
|
-
"Playground-xl",
|
|
295
|
-
"Watercolor",
|
|
296
|
-
"F Pro",
|
|
297
|
-
"Kawaii",
|
|
298
|
-
"Photographic",
|
|
299
|
-
"Katayama-mix-xl",
|
|
300
|
-
"Death metal",
|
|
301
|
-
"New School",
|
|
302
|
-
"Pony-xl",
|
|
303
|
-
"Anima-pencil-xl",
|
|
304
|
-
"Flux",
|
|
305
|
-
"Biomech",
|
|
306
|
-
"Yamers-realistic-xl",
|
|
307
|
-
"Trash Polka",
|
|
308
|
-
"Red and Black",
|
|
309
|
-
]
|
|
310
|
-
|
|
311
|
-
def __init__(self):
|
|
312
|
-
self.session = requests.Session()
|
|
313
|
-
self.user_agent = LitAgent().random()
|
|
314
|
-
self.headers = {
|
|
315
|
-
"accept": "application/json",
|
|
316
|
-
"accept-language": "en-US,en;q=0.9",
|
|
317
|
-
"origin": "https://img-gen-prod.ai-arta.com",
|
|
318
|
-
"referer": "https://img-gen-prod.ai-arta.com/",
|
|
319
|
-
"user-agent": self.user_agent,
|
|
320
|
-
}
|
|
321
|
-
self.session.headers.update(self.headers)
|
|
322
|
-
self.images = Images(self)
|
|
323
|
-
|
|
324
|
-
def get_auth_file(self) -> str:
|
|
325
|
-
import tempfile
|
|
326
|
-
# Use a temp file in the system's temp directory, unique per class
|
|
327
|
-
filename = f"auth_{self.__class__.__name__}.json"
|
|
328
|
-
temp_dir = tempfile.gettempdir()
|
|
329
|
-
return os.path.join(temp_dir, filename)
|
|
330
|
-
|
|
331
|
-
def create_token(self, path: str) -> Dict[str, Any]:
|
|
332
|
-
auth_payload = {"clientType": "CLIENT_TYPE_ANDROID"}
|
|
333
|
-
proxies = self.session.proxies if self.session.proxies else None
|
|
334
|
-
auth_response = self.session.post(
|
|
335
|
-
self.auth_url,
|
|
336
|
-
json=auth_payload,
|
|
337
|
-
timeout=60,
|
|
338
|
-
proxies=proxies,
|
|
339
|
-
)
|
|
340
|
-
auth_data = auth_response.json()
|
|
341
|
-
auth_token = auth_data.get("idToken")
|
|
342
|
-
if not auth_token:
|
|
343
|
-
raise Exception("Failed to obtain authentication token.")
|
|
344
|
-
with open(path, "w") as f:
|
|
345
|
-
json.dump(auth_data, f)
|
|
346
|
-
return auth_data
|
|
347
|
-
|
|
348
|
-
def refresh_token(self, refresh_token: str) -> tuple[str, str]:
|
|
349
|
-
payload = {
|
|
350
|
-
"grant_type": "refresh_token",
|
|
351
|
-
"refresh_token": refresh_token,
|
|
352
|
-
}
|
|
353
|
-
response = self.session.post(
|
|
354
|
-
self.token_refresh_url,
|
|
355
|
-
data=payload,
|
|
356
|
-
timeout=60,
|
|
357
|
-
)
|
|
358
|
-
response_data = response.json()
|
|
359
|
-
return response_data.get("id_token"), response_data.get("refresh_token")
|
|
360
|
-
|
|
361
|
-
def read_and_refresh_token(self) -> Dict[str, Any]:
|
|
362
|
-
path = self.get_auth_file()
|
|
363
|
-
if os.path.isfile(path):
|
|
364
|
-
with open(path, "r") as f:
|
|
365
|
-
auth_data = json.load(f)
|
|
366
|
-
diff = time.time() - os.path.getmtime(path)
|
|
367
|
-
expires_in = int(auth_data.get("expiresIn", 3600))
|
|
368
|
-
if diff < expires_in:
|
|
369
|
-
if diff > expires_in / 2:
|
|
370
|
-
auth_data["idToken"], auth_data["refreshToken"] = (
|
|
371
|
-
self.refresh_token(auth_data.get("refreshToken"))
|
|
372
|
-
)
|
|
373
|
-
with open(path, "w") as f:
|
|
374
|
-
json.dump(auth_data, f)
|
|
375
|
-
return auth_data
|
|
376
|
-
return self.create_token(path)
|
|
377
|
-
|
|
378
|
-
def get_model(self, model: str) -> str:
|
|
379
|
-
if not model:
|
|
380
|
-
return self.default_model
|
|
381
|
-
if model in self.models:
|
|
382
|
-
return model
|
|
383
|
-
raise ModelNotFoundError(f"Model {model} not found")
|
|
384
|
-
|
|
385
|
-
@property
|
|
386
|
-
def models(self):
|
|
387
|
-
class _ModelList:
|
|
388
|
-
def list(inner_self):
|
|
389
|
-
return type(self).AVAILABLE_MODELS
|
|
390
|
-
return _ModelList()
|
|
391
|
-
|
|
392
|
-
@property
|
|
393
|
-
def models_list(self):
|
|
394
|
-
class _ModelList:
|
|
395
|
-
def list(inner_self):
|
|
396
|
-
return type(self).models
|
|
397
|
-
|
|
398
|
-
return _ModelList()
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
# Example usage:
|
|
402
|
-
if __name__ == "__main__":
|
|
403
|
-
from rich import print
|
|
404
|
-
|
|
405
|
-
client = AIArta()
|
|
406
|
-
response = client.images.create(
|
|
407
|
-
model="GPT4o",
|
|
408
|
-
prompt="Chitt Robot saying 'Hello World'",
|
|
409
|
-
response_format="url",
|
|
410
|
-
n=1,
|
|
411
|
-
timeout=3000,
|
|
412
|
-
)
|
|
413
|
-
print(response)
|
|
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 (
|
|
17
|
+
ImageData,
|
|
18
|
+
ImageResponse
|
|
19
|
+
)
|
|
20
|
+
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
21
|
+
from io import BytesIO
|
|
22
|
+
import os
|
|
23
|
+
import tempfile
|
|
24
|
+
from webscout.litagent import LitAgent
|
|
25
|
+
import time
|
|
26
|
+
import json
|
|
27
|
+
import random
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
from PIL import Image
|
|
32
|
+
except ImportError:
|
|
33
|
+
Image = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ModelNotFoundError(Exception):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Images(BaseImages):
|
|
41
|
+
def __init__(self, client: "AIArta"):
|
|
42
|
+
self._client = client
|
|
43
|
+
|
|
44
|
+
def create(
|
|
45
|
+
self,
|
|
46
|
+
*,
|
|
47
|
+
model: str,
|
|
48
|
+
prompt: str,
|
|
49
|
+
n: int = 1,
|
|
50
|
+
size: str = "1024x1024",
|
|
51
|
+
response_format: str = "url",
|
|
52
|
+
user: Optional[str] = None,
|
|
53
|
+
style: str = "none",
|
|
54
|
+
aspect_ratio: str = "1:1",
|
|
55
|
+
timeout: int = 60,
|
|
56
|
+
image_format: str = "png",
|
|
57
|
+
**kwargs,
|
|
58
|
+
) -> ImageResponse:
|
|
59
|
+
"""
|
|
60
|
+
image_format: "png" or "jpeg"
|
|
61
|
+
"""
|
|
62
|
+
if Image is None:
|
|
63
|
+
raise ImportError("Pillow (PIL) is required for image format conversion.")
|
|
64
|
+
|
|
65
|
+
images = []
|
|
66
|
+
urls = []
|
|
67
|
+
agent = LitAgent()
|
|
68
|
+
|
|
69
|
+
def upload_file_with_retry(img_bytes, image_format, max_retries=3):
|
|
70
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
71
|
+
for attempt in range(max_retries):
|
|
72
|
+
tmp_path = None
|
|
73
|
+
try:
|
|
74
|
+
with tempfile.NamedTemporaryFile(
|
|
75
|
+
suffix=f".{ext}", delete=False
|
|
76
|
+
) as tmp:
|
|
77
|
+
tmp.write(img_bytes)
|
|
78
|
+
tmp.flush()
|
|
79
|
+
tmp_path = tmp.name
|
|
80
|
+
with open(tmp_path, "rb") as f:
|
|
81
|
+
files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
|
|
82
|
+
data = {"reqtype": "fileupload", "json": "true"}
|
|
83
|
+
headers = {"User-Agent": agent.random()}
|
|
84
|
+
if attempt > 0:
|
|
85
|
+
headers["Connection"] = "close"
|
|
86
|
+
resp = requests.post(
|
|
87
|
+
"https://catbox.moe/user/api.php",
|
|
88
|
+
files=files,
|
|
89
|
+
data=data,
|
|
90
|
+
headers=headers,
|
|
91
|
+
timeout=timeout,
|
|
92
|
+
)
|
|
93
|
+
if resp.status_code == 200 and resp.text.strip():
|
|
94
|
+
text = resp.text.strip()
|
|
95
|
+
if text.startswith("http"):
|
|
96
|
+
return text
|
|
97
|
+
try:
|
|
98
|
+
result = resp.json()
|
|
99
|
+
if "url" in result:
|
|
100
|
+
return result["url"]
|
|
101
|
+
except json.JSONDecodeError:
|
|
102
|
+
if "http" in text:
|
|
103
|
+
return text
|
|
104
|
+
except Exception:
|
|
105
|
+
if attempt < max_retries - 1:
|
|
106
|
+
time.sleep(1 * (attempt + 1))
|
|
107
|
+
finally:
|
|
108
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
109
|
+
try:
|
|
110
|
+
os.remove(tmp_path)
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def upload_file_alternative(img_bytes, image_format):
|
|
116
|
+
try:
|
|
117
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
118
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
119
|
+
tmp.write(img_bytes)
|
|
120
|
+
tmp.flush()
|
|
121
|
+
tmp_path = tmp.name
|
|
122
|
+
try:
|
|
123
|
+
if not os.path.isfile(tmp_path):
|
|
124
|
+
return None
|
|
125
|
+
with open(tmp_path, "rb") as img_file:
|
|
126
|
+
files = {"file": img_file}
|
|
127
|
+
response = requests.post("https://0x0.st", files=files)
|
|
128
|
+
response.raise_for_status()
|
|
129
|
+
image_url = response.text.strip()
|
|
130
|
+
if not image_url.startswith("http"):
|
|
131
|
+
return None
|
|
132
|
+
return image_url
|
|
133
|
+
except Exception:
|
|
134
|
+
return None
|
|
135
|
+
finally:
|
|
136
|
+
try:
|
|
137
|
+
os.remove(tmp_path)
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
except Exception:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
for _ in range(n):
|
|
144
|
+
# Step 1: Get Authentication Token
|
|
145
|
+
auth_data = self._client.read_and_refresh_token()
|
|
146
|
+
gen_headers = {
|
|
147
|
+
"Authorization": auth_data.get("idToken"),
|
|
148
|
+
}
|
|
149
|
+
if "content-type" in self._client.session.headers:
|
|
150
|
+
del self._client.session.headers["content-type"]
|
|
151
|
+
# Use the model name directly, not as 'style'
|
|
152
|
+
image_payload = {
|
|
153
|
+
"prompt": str(prompt),
|
|
154
|
+
"negative_prompt": str(
|
|
155
|
+
kwargs.get("negative_prompt", "blurry, deformed hands, ugly")
|
|
156
|
+
),
|
|
157
|
+
"style": str(model), # Use 'style' key for the model name
|
|
158
|
+
"images_num": str(1),
|
|
159
|
+
"cfg_scale": str(kwargs.get("guidance_scale", 7)),
|
|
160
|
+
"steps": str(kwargs.get("num_inference_steps", 30)),
|
|
161
|
+
"aspect_ratio": str(aspect_ratio),
|
|
162
|
+
}
|
|
163
|
+
# Remove 'model' from payload if present
|
|
164
|
+
if "model" in image_payload:
|
|
165
|
+
del image_payload["model"]
|
|
166
|
+
# Step 2: Generate Image (send as form data, not JSON)
|
|
167
|
+
image_response = self._client.session.post(
|
|
168
|
+
self._client.image_generation_url,
|
|
169
|
+
data=image_payload, # Use form data instead of JSON
|
|
170
|
+
headers=gen_headers,
|
|
171
|
+
timeout=timeout,
|
|
172
|
+
)
|
|
173
|
+
if image_response.status_code != 200:
|
|
174
|
+
raise RuntimeError(
|
|
175
|
+
f"AIArta API error {image_response.status_code}: {image_response.text}\nPayload: {image_payload}"
|
|
176
|
+
)
|
|
177
|
+
image_data = image_response.json()
|
|
178
|
+
record_id = image_data.get("record_id")
|
|
179
|
+
if not record_id:
|
|
180
|
+
raise RuntimeError(f"Failed to initiate image generation: {image_data}")
|
|
181
|
+
# Step 3: Check Generation Status
|
|
182
|
+
status_url = self._client.status_check_url.format(record_id=record_id)
|
|
183
|
+
while True:
|
|
184
|
+
status_response = self._client.session.get(
|
|
185
|
+
status_url,
|
|
186
|
+
headers=gen_headers,
|
|
187
|
+
timeout=timeout,
|
|
188
|
+
)
|
|
189
|
+
status_data = status_response.json()
|
|
190
|
+
status = status_data.get("status")
|
|
191
|
+
if status == "DONE":
|
|
192
|
+
image_urls = [
|
|
193
|
+
image["url"] for image in status_data.get("response", [])
|
|
194
|
+
]
|
|
195
|
+
if not image_urls:
|
|
196
|
+
raise RuntimeError("No image URLs returned from AIArta")
|
|
197
|
+
img_resp = self._client.session.get(
|
|
198
|
+
image_urls[0],
|
|
199
|
+
timeout=timeout,
|
|
200
|
+
)
|
|
201
|
+
img_resp.raise_for_status()
|
|
202
|
+
img_bytes = img_resp.content
|
|
203
|
+
# Convert to png or jpeg in memory
|
|
204
|
+
with BytesIO(img_bytes) as input_io:
|
|
205
|
+
with Image.open(input_io) as im:
|
|
206
|
+
out_io = BytesIO()
|
|
207
|
+
if image_format.lower() == "jpeg":
|
|
208
|
+
im = im.convert("RGB")
|
|
209
|
+
im.save(out_io, format="JPEG")
|
|
210
|
+
else:
|
|
211
|
+
im.save(out_io, format="PNG")
|
|
212
|
+
img_bytes = out_io.getvalue()
|
|
213
|
+
images.append(img_bytes)
|
|
214
|
+
if response_format == "url":
|
|
215
|
+
uploaded_url = upload_file_with_retry(img_bytes, image_format)
|
|
216
|
+
if not uploaded_url:
|
|
217
|
+
uploaded_url = upload_file_alternative(
|
|
218
|
+
img_bytes, image_format
|
|
219
|
+
)
|
|
220
|
+
if uploaded_url:
|
|
221
|
+
urls.append(uploaded_url)
|
|
222
|
+
else:
|
|
223
|
+
raise RuntimeError(
|
|
224
|
+
"Failed to upload image to catbox.moe using all available methods"
|
|
225
|
+
)
|
|
226
|
+
break
|
|
227
|
+
elif status in ("IN_QUEUE", "IN_PROGRESS"):
|
|
228
|
+
time.sleep(2)
|
|
229
|
+
else:
|
|
230
|
+
raise RuntimeError(f"Image generation failed with status: {status}")
|
|
231
|
+
|
|
232
|
+
result_data = []
|
|
233
|
+
if response_format == "url":
|
|
234
|
+
for url in urls:
|
|
235
|
+
result_data.append(ImageData(url=url))
|
|
236
|
+
elif response_format == "b64_json":
|
|
237
|
+
import base64
|
|
238
|
+
|
|
239
|
+
for img in images:
|
|
240
|
+
b64 = base64.b64encode(img).decode("utf-8")
|
|
241
|
+
result_data.append(ImageData(b64_json=b64))
|
|
242
|
+
else:
|
|
243
|
+
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
244
|
+
|
|
245
|
+
from time import time as _time
|
|
246
|
+
|
|
247
|
+
return ImageResponse(created=int(_time()), data=result_data)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class AIArta(TTICompatibleProvider):
|
|
251
|
+
url = "https://ai-arta.com"
|
|
252
|
+
auth_url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
|
|
253
|
+
token_refresh_url = "https://securetoken.googleapis.com/v1/token?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
|
|
254
|
+
image_generation_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image"
|
|
255
|
+
status_check_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image/{record_id}/status"
|
|
256
|
+
AVAILABLE_MODELS = [
|
|
257
|
+
"Anything-xl",
|
|
258
|
+
"High GPT4o",
|
|
259
|
+
"On limbs black",
|
|
260
|
+
"F Dev",
|
|
261
|
+
"SDXL 1.0",
|
|
262
|
+
"Old School",
|
|
263
|
+
"Vincent Van Gogh",
|
|
264
|
+
"Cor-epica-xl",
|
|
265
|
+
"Professional",
|
|
266
|
+
"Cheyenne-xl",
|
|
267
|
+
"Chicano",
|
|
268
|
+
"SDXL L",
|
|
269
|
+
"Black Ink",
|
|
270
|
+
"Juggernaut-xl",
|
|
271
|
+
"Cinematic Art",
|
|
272
|
+
"Dreamshaper-xl",
|
|
273
|
+
"Fantasy Art",
|
|
274
|
+
"Neo-traditional",
|
|
275
|
+
"Realistic-stock-xl",
|
|
276
|
+
"Flame design",
|
|
277
|
+
"Japanese_2",
|
|
278
|
+
"Medieval",
|
|
279
|
+
"Surrealism",
|
|
280
|
+
"Dotwork",
|
|
281
|
+
"Graffiti",
|
|
282
|
+
"RevAnimated",
|
|
283
|
+
"On limbs color",
|
|
284
|
+
"Old school colored",
|
|
285
|
+
"GPT4o Ghibli",
|
|
286
|
+
"Low Poly",
|
|
287
|
+
"GPT4o",
|
|
288
|
+
"No Style",
|
|
289
|
+
"Anime",
|
|
290
|
+
"tattoo",
|
|
291
|
+
"Embroidery tattoo",
|
|
292
|
+
"Mini tattoo",
|
|
293
|
+
"Realistic tattoo",
|
|
294
|
+
"Playground-xl",
|
|
295
|
+
"Watercolor",
|
|
296
|
+
"F Pro",
|
|
297
|
+
"Kawaii",
|
|
298
|
+
"Photographic",
|
|
299
|
+
"Katayama-mix-xl",
|
|
300
|
+
"Death metal",
|
|
301
|
+
"New School",
|
|
302
|
+
"Pony-xl",
|
|
303
|
+
"Anima-pencil-xl",
|
|
304
|
+
"Flux",
|
|
305
|
+
"Biomech",
|
|
306
|
+
"Yamers-realistic-xl",
|
|
307
|
+
"Trash Polka",
|
|
308
|
+
"Red and Black",
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
def __init__(self):
|
|
312
|
+
self.session = requests.Session()
|
|
313
|
+
self.user_agent = LitAgent().random()
|
|
314
|
+
self.headers = {
|
|
315
|
+
"accept": "application/json",
|
|
316
|
+
"accept-language": "en-US,en;q=0.9",
|
|
317
|
+
"origin": "https://img-gen-prod.ai-arta.com",
|
|
318
|
+
"referer": "https://img-gen-prod.ai-arta.com/",
|
|
319
|
+
"user-agent": self.user_agent,
|
|
320
|
+
}
|
|
321
|
+
self.session.headers.update(self.headers)
|
|
322
|
+
self.images = Images(self)
|
|
323
|
+
|
|
324
|
+
def get_auth_file(self) -> str:
|
|
325
|
+
import tempfile
|
|
326
|
+
# Use a temp file in the system's temp directory, unique per class
|
|
327
|
+
filename = f"auth_{self.__class__.__name__}.json"
|
|
328
|
+
temp_dir = tempfile.gettempdir()
|
|
329
|
+
return os.path.join(temp_dir, filename)
|
|
330
|
+
|
|
331
|
+
def create_token(self, path: str) -> Dict[str, Any]:
|
|
332
|
+
auth_payload = {"clientType": "CLIENT_TYPE_ANDROID"}
|
|
333
|
+
proxies = self.session.proxies if self.session.proxies else None
|
|
334
|
+
auth_response = self.session.post(
|
|
335
|
+
self.auth_url,
|
|
336
|
+
json=auth_payload,
|
|
337
|
+
timeout=60,
|
|
338
|
+
proxies=proxies,
|
|
339
|
+
)
|
|
340
|
+
auth_data = auth_response.json()
|
|
341
|
+
auth_token = auth_data.get("idToken")
|
|
342
|
+
if not auth_token:
|
|
343
|
+
raise Exception("Failed to obtain authentication token.")
|
|
344
|
+
with open(path, "w") as f:
|
|
345
|
+
json.dump(auth_data, f)
|
|
346
|
+
return auth_data
|
|
347
|
+
|
|
348
|
+
def refresh_token(self, refresh_token: str) -> tuple[str, str]:
|
|
349
|
+
payload = {
|
|
350
|
+
"grant_type": "refresh_token",
|
|
351
|
+
"refresh_token": refresh_token,
|
|
352
|
+
}
|
|
353
|
+
response = self.session.post(
|
|
354
|
+
self.token_refresh_url,
|
|
355
|
+
data=payload,
|
|
356
|
+
timeout=60,
|
|
357
|
+
)
|
|
358
|
+
response_data = response.json()
|
|
359
|
+
return response_data.get("id_token"), response_data.get("refresh_token")
|
|
360
|
+
|
|
361
|
+
def read_and_refresh_token(self) -> Dict[str, Any]:
|
|
362
|
+
path = self.get_auth_file()
|
|
363
|
+
if os.path.isfile(path):
|
|
364
|
+
with open(path, "r") as f:
|
|
365
|
+
auth_data = json.load(f)
|
|
366
|
+
diff = time.time() - os.path.getmtime(path)
|
|
367
|
+
expires_in = int(auth_data.get("expiresIn", 3600))
|
|
368
|
+
if diff < expires_in:
|
|
369
|
+
if diff > expires_in / 2:
|
|
370
|
+
auth_data["idToken"], auth_data["refreshToken"] = (
|
|
371
|
+
self.refresh_token(auth_data.get("refreshToken"))
|
|
372
|
+
)
|
|
373
|
+
with open(path, "w") as f:
|
|
374
|
+
json.dump(auth_data, f)
|
|
375
|
+
return auth_data
|
|
376
|
+
return self.create_token(path)
|
|
377
|
+
|
|
378
|
+
def get_model(self, model: str) -> str:
|
|
379
|
+
if not model:
|
|
380
|
+
return self.default_model
|
|
381
|
+
if model in self.models:
|
|
382
|
+
return model
|
|
383
|
+
raise ModelNotFoundError(f"Model {model} not found")
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def models(self):
|
|
387
|
+
class _ModelList:
|
|
388
|
+
def list(inner_self):
|
|
389
|
+
return type(self).AVAILABLE_MODELS
|
|
390
|
+
return _ModelList()
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def models_list(self):
|
|
394
|
+
class _ModelList:
|
|
395
|
+
def list(inner_self):
|
|
396
|
+
return type(self).models
|
|
397
|
+
|
|
398
|
+
return _ModelList()
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# Example usage:
|
|
402
|
+
if __name__ == "__main__":
|
|
403
|
+
from rich import print
|
|
404
|
+
|
|
405
|
+
client = AIArta()
|
|
406
|
+
response = client.images.create(
|
|
407
|
+
model="GPT4o",
|
|
408
|
+
prompt="Chitt Robot saying 'Hello World'",
|
|
409
|
+
response_format="url",
|
|
410
|
+
n=1,
|
|
411
|
+
timeout=3000,
|
|
412
|
+
)
|
|
413
|
+
print(response)
|