novelai-python 0.7.10__tar.gz → 0.7.12__tar.gz

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 (82) hide show
  1. {novelai_python-0.7.10 → novelai_python-0.7.12}/PKG-INFO +14 -2
  2. {novelai_python-0.7.10 → novelai_python-0.7.12}/README.md +12 -0
  3. {novelai_python-0.7.10 → novelai_python-0.7.12}/pyproject.toml +2 -2
  4. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/credential/ApiToken.py +3 -8
  5. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/credential/JwtToken.py +3 -8
  6. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/credential/UserAuth.py +2 -7
  7. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/_enum.py +24 -19
  8. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/augment_image/__init__.py +0 -23
  9. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate/__init__.py +0 -17
  10. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate_image/__init__.py +19 -33
  11. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate_image/params.py +72 -47
  12. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate_image/schema.py +2 -2
  13. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate_image/suggest_tags.py +2 -21
  14. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate_stream.py +0 -1
  15. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate_voice/__init__.py +0 -22
  16. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/upscale.py +0 -23
  17. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/schema.py +0 -4
  18. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/user/information.py +0 -12
  19. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/user/login.py +0 -4
  20. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/user/subscription.py +0 -19
  21. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/utils/encode.py +1 -1
  22. {novelai_python-0.7.10 → novelai_python-0.7.12}/LICENSE +0 -0
  23. {novelai_python-0.7.10 → novelai_python-0.7.12}/NOTICE.MD +0 -0
  24. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/__init__.py +0 -0
  25. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_enum.py +0 -0
  26. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_exceptions.py +0 -0
  27. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/__init__.py +0 -0
  28. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/ai/__init__.py +0 -0
  29. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/ai/generate.py +0 -0
  30. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/ai/generate_image.py +0 -0
  31. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/ai/generate_stream.py +0 -0
  32. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/ai/generate_voice.py +0 -0
  33. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/ai/upscale.py +0 -0
  34. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/schema.py +0 -0
  35. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/user/__init__.py +0 -0
  36. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/user/information.py +0 -0
  37. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/user/login.py +0 -0
  38. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/_response/user/subscription.py +0 -0
  39. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/credential/__init__.py +0 -0
  40. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/credential/_base.py +0 -0
  41. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/__init__.py +0 -0
  42. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/LICENSE +0 -0
  43. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/__init__.py +0 -0
  44. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/_const.py +0 -0
  45. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/_cost.py +0 -0
  46. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/augment_image/_enum.py +0 -0
  47. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate/_const.py +0 -0
  48. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate/_enum.py +0 -0
  49. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate/_schema.py +0 -0
  50. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate/themes.json +0 -0
  51. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate_image/tokenizer.py +0 -0
  52. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/ai/generate_voice/_enum.py +0 -0
  53. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/sdk/user/__init__.py +0 -0
  54. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/server.py +0 -0
  55. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tokenizer/__init__.py +0 -0
  56. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tokenizer/clip_simple_tokenizer.py +0 -0
  57. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tokenizer/nerdstash_v1.model +0 -0
  58. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tokenizer/nerdstash_v2.model +0 -0
  59. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tokenizer/novelai.model +0 -0
  60. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tokenizer/novelai_v2.model +0 -0
  61. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/__init__.py +0 -0
  62. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/image_metadata/__init__.py +0 -0
  63. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/image_metadata/bch_utils.py +0 -0
  64. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/image_metadata/lsb_extractor.py +0 -0
  65. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/image_metadata/lsb_injector.py +0 -0
  66. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/paint_mask/__init__.py +0 -0
  67. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/random_prompt/__init__.py +0 -0
  68. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/random_prompt/generate_scene_composition.py +0 -0
  69. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/random_prompt/generate_scene_tags.py +0 -0
  70. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/tool/random_prompt/generate_tags.py +0 -0
  71. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/utils/__init__.py +0 -0
  72. {novelai_python-0.7.10 → novelai_python-0.7.12}/src/novelai_python/utils/useful.py +0 -0
  73. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/__init__.py +0 -0
  74. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/test_generate_voice.py +0 -0
  75. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/test_random_prompt.py +0 -0
  76. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/test_server.py +0 -0
  77. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/test_server_run.py +0 -0
  78. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/test_tokenizer.py +0 -0
  79. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/test_upscale.py +0 -0
  80. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/test_user_information.py +0 -0
  81. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/test_user_login.py +0 -0
  82. {novelai_python-0.7.10 → novelai_python-0.7.12}/tests/test_user_subscription.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: novelai-python
3
- Version: 0.7.10
3
+ Version: 0.7.12
4
4
  Summary: NovelAI Python Binding With Pydantic
5
5
  Author-Email: sudoskys <coldlando@hotmail.com>
6
6
  License: Apache License 2.0
@@ -14,7 +14,7 @@ Requires-Dist: pydantic>=2.9.0
14
14
  Requires-Dist: httpx>=0.26.0
15
15
  Requires-Dist: shortuuid>=1.0.11
16
16
  Requires-Dist: Pillow>=10.2.0
17
- Requires-Dist: curl-cffi>=0.9.0
17
+ Requires-Dist: curl-cffi>=0.11.3
18
18
  Requires-Dist: fastapi>=0.109.0
19
19
  Requires-Dist: uvicorn[standard]>=0.27.0.post1
20
20
  Requires-Dist: numpy>=1.24.4
@@ -43,6 +43,18 @@ Description-Content-Type: text/markdown
43
43
  [![PyPI version](https://badge.fury.io/py/novelai-python.svg)](https://badge.fury.io/py/novelai-python)
44
44
  [![Downloads](https://pepy.tech/badge/novelai_python)](https://pepy.tech/project/novelai_python)
45
45
 
46
+
47
+ >**Declaration** (2025/06/04)
48
+ >
49
+ >**I have nothing to do with NovelAI.**
50
+ >
51
+ >Due to various considerations, such as NovelAI's confusing project architecture and "can't learn anything from it", this project will be in a *non-active* maintenance state. If you have any questions, please *raise an issue*.
52
+ >
53
+ >This project is designed to be a sample project for me to write Python SDK. It turns out that Pydantic is very powerful.
54
+ >
55
+ >*I will invest more time in open source projects that are more beneficial to society.*
56
+ >
57
+
46
58
  ✨ NovelAI api python sdk with Pydantic, modern and user-friendly.
47
59
 
48
60
  The goal of this repository is to use Pydantic to build legitimate requests to access
@@ -5,6 +5,18 @@
5
5
  [![PyPI version](https://badge.fury.io/py/novelai-python.svg)](https://badge.fury.io/py/novelai-python)
6
6
  [![Downloads](https://pepy.tech/badge/novelai_python)](https://pepy.tech/project/novelai_python)
7
7
 
8
+
9
+ >**Declaration** (2025/06/04)
10
+ >
11
+ >**I have nothing to do with NovelAI.**
12
+ >
13
+ >Due to various considerations, such as NovelAI's confusing project architecture and "can't learn anything from it", this project will be in a *non-active* maintenance state. If you have any questions, please *raise an issue*.
14
+ >
15
+ >This project is designed to be a sample project for me to write Python SDK. It turns out that Pydantic is very powerful.
16
+ >
17
+ >*I will invest more time in open source projects that are more beneficial to society.*
18
+ >
19
+
8
20
  ✨ NovelAI api python sdk with Pydantic, modern and user-friendly.
9
21
 
10
22
  The goal of this repository is to use Pydantic to build legitimate requests to access
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "novelai-python"
3
- version = "0.7.10"
3
+ version = "0.7.12"
4
4
  description = "NovelAI Python Binding With Pydantic"
5
5
  authors = [
6
6
  { name = "sudoskys", email = "coldlando@hotmail.com" },
@@ -13,7 +13,7 @@ dependencies = [
13
13
  "httpx>=0.26.0",
14
14
  "shortuuid>=1.0.11",
15
15
  "Pillow>=10.2.0",
16
- "curl-cffi>=0.9.0",
16
+ "curl-cffi>=0.11.3",
17
17
  "fastapi>=0.109.0",
18
18
  "uvicorn[standard]>=0.27.0.post1",
19
19
  "numpy>=1.24.4",
@@ -10,7 +10,7 @@ from curl_cffi.requests import AsyncSession
10
10
  from loguru import logger
11
11
  from pydantic import SecretStr, Field, field_validator
12
12
 
13
- from ._base import CredentialBase, FAKE_UA
13
+ from ._base import CredentialBase
14
14
 
15
15
 
16
16
  class ApiCredential(CredentialBase):
@@ -22,13 +22,8 @@ class ApiCredential(CredentialBase):
22
22
 
23
23
  async def get_session(self, timeout: int = 180, update_headers: dict = None):
24
24
  headers = {
25
- "Accept": "*/*",
26
- "Accept-Encoding": "gzip, deflate, br",
27
- "User-Agent": FAKE_UA.edge,
28
25
  "Authorization": f"Bearer {self.api_token.get_secret_value()}",
29
26
  "Content-Type": "application/json",
30
- "Origin": "https://novelai.net",
31
- "Referer": "https://novelai.net/",
32
27
  "x-correlation-id": self.x_correlation_id,
33
28
  "x-initiated-at": f"{arrow.utcnow().isoformat()}Z",
34
29
  }
@@ -37,10 +32,10 @@ class ApiCredential(CredentialBase):
37
32
  assert isinstance(update_headers, dict), "update_headers must be a dict"
38
33
  headers.update(update_headers)
39
34
 
40
- return AsyncSession(timeout=timeout, headers=headers, impersonate="edge101")
35
+ return AsyncSession(timeout=timeout, headers=headers, impersonate="chrome136")
41
36
 
42
37
  @field_validator('api_token')
43
38
  def check_api_token(cls, v: SecretStr):
44
39
  if not v.get_secret_value().startswith("pst"):
45
40
  logger.warning("api token should start with `pst-`")
46
- return v
41
+ return v
@@ -10,7 +10,7 @@ from curl_cffi.requests import AsyncSession
10
10
  from loguru import logger
11
11
  from pydantic import SecretStr, Field, field_validator
12
12
 
13
- from ._base import CredentialBase, FAKE_UA
13
+ from ._base import CredentialBase
14
14
 
15
15
 
16
16
  class JwtCredential(CredentialBase):
@@ -22,13 +22,8 @@ class JwtCredential(CredentialBase):
22
22
 
23
23
  async def get_session(self, timeout: int = 180, update_headers: dict = None):
24
24
  headers = {
25
- "Accept": "*/*",
26
- "User-Agent": FAKE_UA.edge,
27
- "Accept-Encoding": "gzip, deflate, br",
28
25
  "Authorization": f"Bearer {self.jwt_token.get_secret_value()}",
29
26
  "Content-Type": "application/json",
30
- "Origin": "https://novelai.net",
31
- "Referer": "https://novelai.net/",
32
27
  "x-correlation-id": self.x_correlation_id,
33
28
  "x-initiated-at": f"{arrow.utcnow().isoformat()}Z",
34
29
  }
@@ -37,10 +32,10 @@ class JwtCredential(CredentialBase):
37
32
  assert isinstance(update_headers, dict), "update_headers must be a dict"
38
33
  headers.update(update_headers)
39
34
 
40
- return AsyncSession(timeout=timeout, headers=headers, impersonate="edge101")
35
+ return AsyncSession(timeout=timeout, headers=headers, impersonate="chrome136")
41
36
 
42
37
  @field_validator('jwt_token')
43
38
  def check_jwt_token(cls, v: SecretStr):
44
39
  if not v.get_secret_value().startswith("ey"):
45
40
  logger.warning("jwt_token should start with ey")
46
- return v
41
+ return v
@@ -10,7 +10,7 @@ import shortuuid
10
10
  from curl_cffi.requests import AsyncSession
11
11
  from pydantic import SecretStr, Field
12
12
 
13
- from ._base import CredentialBase, FAKE_UA
13
+ from ._base import CredentialBase
14
14
 
15
15
 
16
16
  class LoginCredential(CredentialBase):
@@ -25,13 +25,8 @@ class LoginCredential(CredentialBase):
25
25
 
26
26
  async def get_session(self, timeout: int = 180, update_headers: dict = None):
27
27
  headers = {
28
- "Accept": "*/*",
29
- "User-Agent": FAKE_UA.edge,
30
- "Accept-Encoding": "gzip, deflate, br",
31
28
  "Authorization": "Bearer ",
32
29
  "Content-Type": "application/json",
33
- "Origin": "https://novelai.net",
34
- "Referer": "https://novelai.net/",
35
30
  "x-correlation-id": self.x_correlation_id,
36
31
  "x-initiated-at": f"{arrow.utcnow().isoformat()}Z",
37
32
  }
@@ -49,4 +44,4 @@ class LoginCredential(CredentialBase):
49
44
  if update_headers:
50
45
  headers.update(update_headers)
51
46
 
52
- return AsyncSession(timeout=timeout, headers=headers, impersonate="edge101")
47
+ return AsyncSession(timeout=timeout, headers=headers, impersonate="chrome136")
@@ -217,12 +217,17 @@ class SupportCondition:
217
217
  img2imgInpainting: bool
218
218
 
219
219
 
220
- def get_supported_params(model: Model):
220
+ def get_supported_params(model: ModelTypeAlias):
221
221
  """
222
222
  Get supported parameters for a given model
223
223
  :param model: Model
224
224
  :return: SupportCondition
225
225
  """
226
+ if isinstance(model, str):
227
+ try:
228
+ model = Model(model)
229
+ except ValueError:
230
+ pass
226
231
  if model in [
227
232
  Model.STABLE_DIFFUSION,
228
233
  Model.NAI_DIFFUSION,
@@ -459,7 +464,7 @@ def get_modifiers(model: Model) -> Modifier:
459
464
  ]:
460
465
  return Modifier(
461
466
  qualityTags="",
462
- suffix=", location, very aesthetic, masterpiece, no text"
467
+ suffix=", very aesthetic, masterpiece, no text"
463
468
  )
464
469
  if model in [
465
470
  Model.NAI_DIFFUSION_4_5_CURATED,
@@ -656,12 +661,12 @@ def get_uc_preset(model: ModelTypeAlias) -> List[UcPrompt]:
656
661
  UcPrompt(
657
662
  category="heavy",
658
663
  name="lowQualityPlusBadAnatomy",
659
- text="lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
664
+ text="nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
660
665
  ),
661
666
  UcPrompt(
662
667
  category="light",
663
668
  name="lowQuality",
664
- text="lowres, text, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
669
+ text="nsfw, lowres, text, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
665
670
  ),
666
671
  UcPrompt(
667
672
  category="none",
@@ -677,12 +682,12 @@ def get_uc_preset(model: ModelTypeAlias) -> List[UcPrompt]:
677
682
  UcPrompt(
678
683
  category="light",
679
684
  name="lowQuality",
680
- text="worst quality, low quality, what has science done, what, nightmare fuel, eldritch horror, where is your god now, why"
685
+ text="nsfw, worst quality, low quality, what has science done, what, nightmare fuel, eldritch horror, where is your god now, why"
681
686
  ),
682
687
  UcPrompt(
683
688
  category="heavy",
684
689
  name="badAnatomy",
685
- text="{worst quality}, low quality, distracting watermark, [nightmare fuel], {{unfinished}}, deformed, outline, pattern, simple background"
690
+ text="nsfw, {worst quality}, low quality, distracting watermark, [nightmare fuel], {{unfinished}}, deformed, outline, pattern, simple background"
686
691
  ),
687
692
  UcPrompt(
688
693
  category="none",
@@ -697,12 +702,12 @@ def get_uc_preset(model: ModelTypeAlias) -> List[UcPrompt]:
697
702
  UcPrompt(
698
703
  category="heavy",
699
704
  name="heavy",
700
- text="lowres, bad, text, error, missing, extra, fewer, cropped, jpeg artifacts, worst quality, bad quality, watermark, displeasing, unfinished, chromatic aberration, scan, scan artifacts"
705
+ text="nsfw, lowres, bad, text, error, missing, extra, fewer, cropped, jpeg artifacts, worst quality, bad quality, watermark, displeasing, unfinished, chromatic aberration, scan, scan artifacts"
701
706
  ),
702
707
  UcPrompt(
703
708
  category="light",
704
709
  name="light",
705
- text="lowres, jpeg artifacts, worst quality, watermark, blurry, very displeasing"
710
+ text="nsfw, lowres, jpeg artifacts, worst quality, watermark, blurry, very displeasing"
706
711
  ),
707
712
  UcPrompt(
708
713
  category="none",
@@ -718,17 +723,17 @@ def get_uc_preset(model: ModelTypeAlias) -> List[UcPrompt]:
718
723
  UcPrompt(
719
724
  category="heavy",
720
725
  name="heavy",
721
- text="lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]"
726
+ text="nsfw, lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]"
722
727
  ),
723
728
  UcPrompt(
724
729
  category="light",
725
730
  name="light",
726
- text="lowres, jpeg artifacts, worst quality, watermark, blurry, very displeasing"
731
+ text="nsfw, lowres, jpeg artifacts, worst quality, watermark, blurry, very displeasing"
727
732
  ),
728
733
  UcPrompt(
729
734
  category="human",
730
735
  name="humanFocus",
731
- text="lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract], bad anatomy, bad hands, @_@, mismatched pupils, heart-shaped pupils, glowing eyes"
736
+ text="nsfw, lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract], bad anatomy, bad hands, @_@, mismatched pupils, heart-shaped pupils, glowing eyes"
732
737
  ),
733
738
  UcPrompt(
734
739
  category="none",
@@ -744,12 +749,12 @@ def get_uc_preset(model: ModelTypeAlias) -> List[UcPrompt]:
744
749
  UcPrompt(
745
750
  category="heavy",
746
751
  name="heavy",
747
- text="{{worst quality}}, [displeasing], {unusual pupils}, guide lines, {{unfinished}}, {bad}, url, artist name, {{tall image}}, mosaic, {sketch page}, comic panel, impact (font), [dated], {logo}, ych, {what}, {where is your god now}, {distorted text}, repeated text, {floating head}, {1994}, {widescreen}, absolutely everyone, sequence, {compression artifacts}, hard translated, {cropped}, {commissioner name}, unknown text, high contrast"
752
+ text="nsfw, {{worst quality}}, [displeasing], {unusual pupils}, guide lines, {{unfinished}}, {bad}, url, artist name, {{tall image}}, mosaic, {sketch page}, comic panel, impact (font), [dated], {logo}, ych, {what}, {where is your god now}, {distorted text}, repeated text, {floating head}, {1994}, {widescreen}, absolutely everyone, sequence, {compression artifacts}, hard translated, {cropped}, {commissioner name}, unknown text, high contrast"
748
753
  ),
749
754
  UcPrompt(
750
755
  category="light",
751
756
  name="light",
752
- text="{worst quality}, guide lines, unfinished, bad, url, tall image, widescreen, compression artifacts, unknown text"
757
+ text="nsfw, {worst quality}, guide lines, unfinished, bad, url, tall image, widescreen, compression artifacts, unknown text"
753
758
  ),
754
759
  UcPrompt(
755
760
  category="none",
@@ -786,12 +791,12 @@ def get_uc_preset(model: ModelTypeAlias) -> List[UcPrompt]:
786
791
  UcPrompt(
787
792
  category="heavy",
788
793
  name="heavy",
789
- text="blurry, lowres, error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, multiple views, logo, too many watermarks, white blank page, blank page"
794
+ text="nsfw, blurry, lowres, error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, multiple views, logo, too many watermarks, white blank page, blank page"
790
795
  ),
791
796
  UcPrompt(
792
797
  category="light",
793
798
  name="light",
794
- text="blurry, lowres, error, worst quality, bad quality, jpeg artifacts, very displeasing, white blank page, blank page"
799
+ text="nsfw, blurry, lowres, error, worst quality, bad quality, jpeg artifacts, very displeasing, white blank page, blank page"
795
800
  ),
796
801
  UcPrompt(
797
802
  category="none",
@@ -834,22 +839,22 @@ def get_uc_preset(model: ModelTypeAlias) -> List[UcPrompt]:
834
839
  UcPrompt(
835
840
  category="heavy",
836
841
  name="heavy",
837
- text="lowres, artistic error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, dithering, halftone, screentone, multiple views, logo, too many watermarks, negative space, blank page"
842
+ text="nsfw, lowres, artistic error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, dithering, halftone, screentone, multiple views, logo, too many watermarks, negative space, blank page"
838
843
  ),
839
844
  UcPrompt(
840
845
  category="light",
841
846
  name="light",
842
- text="lowres, artistic error, scan artifacts, worst quality, bad quality, jpeg artifacts, multiple views, very displeasing, too many watermarks, negative space, blank page"
847
+ text="nsfw, lowres, artistic error, scan artifacts, worst quality, bad quality, jpeg artifacts, multiple views, very displeasing, too many watermarks, negative space, blank page"
843
848
  ),
844
849
  UcPrompt(
845
850
  category="furry",
846
851
  name="furryFocus",
847
- text="{worst quality}, distracting watermark, unfinished, bad quality, {widescreen}, upscale, {sequence}, {{grandfathered content}}, blurred foreground, chromatic aberration, sketch, everyone, [sketch background], simple, [flat colors], ych (character), outline, multiple scenes, [[horror (theme)]], comic"
852
+ text="nsfw, {worst quality}, distracting watermark, unfinished, bad quality, {widescreen}, upscale, {sequence}, {{grandfathered content}}, blurred foreground, chromatic aberration, sketch, everyone, [sketch background], simple, [flat colors], ych (character), outline, multiple scenes, [[horror (theme)]], comic"
848
853
  ),
849
854
  UcPrompt(
850
855
  category="human",
851
856
  name="humanFocus",
852
- text="lowres, artistic error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, dithering, halftone, screentone, multiple views, logo, too many watermarks, negative space, blank page, @_@, mismatched pupils, glowing eyes, bad anatomy"
857
+ text="nsfw, lowres, artistic error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, dithering, halftone, screentone, multiple views, logo, too many watermarks, negative space, blank page, @_@, mismatched pupils, glowing eyes, bad anatomy"
853
858
  ),
854
859
  UcPrompt(
855
860
  category="none",
@@ -174,28 +174,6 @@ class AugmentImageInfer(ApiBaseModel):
174
174
  prompt=prompt,
175
175
  )
176
176
 
177
- async def necessary_headers(self, request_data) -> dict:
178
- """
179
- :param request_data:
180
- :return:
181
- """
182
- return {
183
- "Host": urlparse(self.endpoint).netloc,
184
- "Accept": "*/*",
185
- "Accept-Encoding": "gzip, deflate, br",
186
- "Referer": "https://novelai.net/",
187
- "Content-Type": "application/json",
188
- "Origin": "https://novelai.net",
189
- "Content-Length": str(len(json.dumps(request_data).encode("utf-8"))),
190
- "Connection": "keep-alive",
191
- "Sec-Fetch-Dest": "empty",
192
- "Sec-Fetch-Mode": "cors",
193
- "Sec-Fetch-Site": "same-site",
194
- "Pragma": "no-cache",
195
- "Cache-Control": "no-cache",
196
- 'priority': "u=1, i"
197
- }
198
-
199
177
  @retry(
200
178
  wait=wait_random(min=1, max=3),
201
179
  stop=stop_after_attempt(3),
@@ -216,7 +194,6 @@ class AugmentImageInfer(ApiBaseModel):
216
194
  # Prepare request data
217
195
  request_data = self.model_dump(mode="json", exclude_none=True)
218
196
  async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
219
- sess.headers.update(await self.necessary_headers(request_data))
220
197
  if override_headers:
221
198
  sess.headers.clear()
222
199
  sess.headers.update(override_headers)
@@ -65,22 +65,6 @@ class LLM(ApiBaseModel):
65
65
  def base_url(self):
66
66
  return f"{self.endpoint.strip('/')}/ai/generate"
67
67
 
68
- async def necessary_headers(self, request_data) -> dict:
69
- """
70
- :param request_data: dict
71
- :return: dict
72
- """
73
- return {
74
- "Host": urlparse(self.endpoint).netloc,
75
- "accept": "*/*",
76
- "accept-language": "zh-CN,zh;q=0.9",
77
- "cache-control": "no-cache",
78
- "content-type": "application/json",
79
- "pragma": "no-cache",
80
- "Referer": "https://novelai.net/",
81
- "Referrer-Policy": "strict-origin-when-cross-origin"
82
- }
83
-
84
68
  @model_validator(mode="after")
85
69
  def normalize_model(self):
86
70
  if self.model in [
@@ -361,7 +345,6 @@ class LLM(ApiBaseModel):
361
345
  }
362
346
  async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
363
347
  # Header
364
- sess.headers.update(await self.necessary_headers(request_data))
365
348
  if override_headers:
366
349
  sess.headers.clear()
367
350
  sess.headers.update(override_headers)
@@ -9,7 +9,6 @@ from copy import deepcopy
9
9
  from enum import Enum
10
10
  from io import BytesIO
11
11
  from typing import Optional, Union, List
12
- from urllib.parse import urlparse
13
12
  from zipfile import ZipFile, BadZipFile
14
13
 
15
14
  import curl_cffi
@@ -61,9 +60,9 @@ class GenerateImageInfer(ApiBaseModel):
61
60
  self._mutual_exclusion = bool(value)
62
61
  return self
63
62
 
64
- action: Union[str, Action] = Field(Action.GENERATE, description="Mode for img generate")
65
63
  input: str = "1girl, best quality, amazing quality, very aesthetic, absurdres"
66
64
  model: ModelTypeAlias = "nai-diffusion-3"
65
+ action: Union[str, Action] = Field(Action.GENERATE, description="Mode for img generate")
67
66
  parameters: Union[Params]
68
67
  model_config = ConfigDict(extra="ignore")
69
68
 
@@ -158,7 +157,14 @@ class GenerateImageInfer(ApiBaseModel):
158
157
  for key in input_prompt:
159
158
  if key in uc_prompt:
160
159
  uc_prompt.pop(key)
161
- self.parameters.negative_prompt = ",".join(uc_prompt.values())
160
+ self.parameters.negative_prompt = ",".join(uc_prompt.values()).strip()
161
+
162
+ # Instantly remove nsfw if input contains it
163
+ elif "nsfw" in self.input and "nsfw" in self.parameters.negative_prompt:
164
+ uc_prompt = {x.strip(): x for x in self.parameters.negative_prompt.split(",")}
165
+ uc_prompt.pop("nsfw", None)
166
+
167
+ self.parameters.negative_prompt = ",".join(uc_prompt.values()).strip()
162
168
 
163
169
  @model_validator(mode="after")
164
170
  def _build_nai4_prompt(self):
@@ -188,9 +194,9 @@ class GenerateImageInfer(ApiBaseModel):
188
194
  if not get_supported_params(self.model).vibetransfer:
189
195
  if self.parameters.reference_image_multiple:
190
196
  logger.warning("Vibe transfer is not supported for this model.")
191
- self.parameters.reference_image_multiple = []
192
- self.parameters.reference_information_extracted_multiple = []
193
- self.parameters.reference_strength_multiple = []
197
+ self.parameters.reference_image_multiple = None # Auto exclude None values
198
+ self.parameters.reference_information_extracted_multiple = None # Auto exclude None values
199
+ self.parameters.reference_strength_multiple = None # Auto exclude None values
194
200
 
195
201
  if not get_supported_params(self.model).controlnet:
196
202
  if self.parameters.controlnet_condition is not None:
@@ -211,6 +217,9 @@ class GenerateImageInfer(ApiBaseModel):
211
217
  """
212
218
  if self.parameters.characterPrompts:
213
219
  self.parameters.use_coords = any([c.center != PositionMap.AUTO for c in self.parameters.characterPrompts])
220
+
221
+ if get_supported_params(self.model).v4Prompts and self.parameters.v4_prompt is not None:
222
+ self.parameters.v4_prompt.use_coords = self.parameters.use_coords
214
223
  # Mix Prompt Warning
215
224
  if self.input.count('|') > 6:
216
225
  logger.warning("Maximum prompt mixes exceeded. Extra will be ignored.")
@@ -489,12 +498,12 @@ class GenerateImageInfer(ApiBaseModel):
489
498
  def build_img2img(
490
499
  prompt: str,
491
500
  *,
501
+ model: Union[Model, str] = Model.NAI_DIFFUSION_4_5_FULL,
492
502
  image: Union[bytes, str],
493
503
  strength: float = None,
494
504
  noise: float = None,
495
505
  seed: int = None,
496
506
  extra_noise_seed: int = None,
497
- model: Union[Model, str] = Model.NAI_DIFFUSION_4_5_CURATED,
498
507
  negative_prompt: str = None,
499
508
  ucPreset: UCPresetTypeAlias = None,
500
509
  steps: int = None,
@@ -523,7 +532,7 @@ class GenerateImageInfer(ApiBaseModel):
523
532
  :param reference_strength_multiple:
524
533
  :param reference_image_multiple:
525
534
  :param prompt: Given prompt.
526
- :param model: Model for generation.
535
+ :param model: Model for generation, which NOT end with 'inpainting'.
527
536
  :param negative_prompt: The things you don't want to see in the image.
528
537
  :param ucPreset: The negative prompt preset.
529
538
  :param steps: The steps for generation.
@@ -592,10 +601,10 @@ class GenerateImageInfer(ApiBaseModel):
592
601
  def build_infill(
593
602
  prompt: str,
594
603
  *,
604
+ model: Union[Model, str],
595
605
  image: Union[bytes, str],
596
606
  mask: Union[bytes, str],
597
607
  strength: float = None,
598
- model: Union[Model, str] = Model.NAI_DIFFUSION_4_5_CURATED,
599
608
  negative_prompt: str = None,
600
609
  ucPreset: UCPresetTypeAlias = None,
601
610
  steps: int = None,
@@ -645,7 +654,7 @@ class GenerateImageInfer(ApiBaseModel):
645
654
 
646
655
  # Update only explicitly set parameters
647
656
  params.image = image # image is required for infill
648
- params.mask = mask # mask is required for infill
657
+ params.mask = mask # mask is required for infill
649
658
  if strength is not None:
650
659
  params.strength = strength
651
660
  if negative_prompt is not None:
@@ -686,28 +695,6 @@ class GenerateImageInfer(ApiBaseModel):
686
695
  parameters=params
687
696
  )
688
697
 
689
- async def necessary_headers(self, request_data) -> dict:
690
- """
691
- :param request_data:
692
- :return:
693
- """
694
- return {
695
- "Host": urlparse(self.endpoint).netloc,
696
- "Accept": "*/*",
697
-
698
- "Accept-Encoding": "gzip, deflate, br",
699
- "Referer": "https://novelai.net/",
700
- "Content-Type": "application/json",
701
- "Origin": "https://novelai.net",
702
- "Content-Length": str(len(json.dumps(request_data).encode("utf-8"))),
703
- "Connection": "keep-alive",
704
- "Sec-Fetch-Dest": "empty",
705
- "Sec-Fetch-Mode": "cors",
706
- "Sec-Fetch-Site": "same-site",
707
- "Pragma": "no-cache",
708
- "Cache-Control": "no-cache",
709
- }
710
-
711
698
  @retry(
712
699
  wait=wait_random(min=1, max=3),
713
700
  stop=stop_after_attempt(3),
@@ -738,7 +725,6 @@ class GenerateImageInfer(ApiBaseModel):
738
725
  # Prepare request data
739
726
  request_data = self.model_dump(mode="json", exclude_none=True)
740
727
  async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
741
- sess.headers.update(await self.necessary_headers(request_data))
742
728
  if override_headers:
743
729
  sess.headers.clear()
744
730
  sess.headers.update(override_headers)
@@ -1,13 +1,14 @@
1
1
  import base64
2
2
  import random
3
3
  from io import BytesIO
4
- from typing import Optional, List, Union, Tuple
4
+ from collections import OrderedDict
5
+ from typing import Optional, List, Set, Union, Tuple
5
6
 
6
7
  import cv2
7
8
  import numpy as np
8
9
  from PIL import Image
9
10
  from loguru import logger
10
- from pydantic import BaseModel, Field, model_validator, field_validator
11
+ from pydantic import BaseModel, Field, model_serializer, model_validator, field_validator
11
12
 
12
13
  from novelai_python.sdk.ai._enum import Sampler, UCPresetTypeAlias, NoiseSchedule, ImageBytesTypeAlias, ControlNetModel, \
13
14
  Model
@@ -20,6 +21,8 @@ def is_multiple_of_01(num, precision=1e-10):
20
21
 
21
22
 
22
23
  class Params(BaseModel):
24
+ params_version: int = 3
25
+ """Params Version For Request"""
23
26
  width: int = Field(832, ge=64, le=49152)
24
27
  """Width For Image"""
25
28
  height: int = Field(1216, ge=64, le=49152)
@@ -32,42 +35,68 @@ class Params(BaseModel):
32
35
  """Steps"""
33
36
  n_samples: int = Field(1, ge=1, le=8)
34
37
  """Number of samples"""
35
- strength: Optional[float] = Field(0.7, ge=0.01, le=0.99, multiple_of=0.01)
36
- """Strength for img2img"""
37
- noise: Optional[float] = Field(0, ge=0, le=0.99, multiple_of=0.01)
38
- """Noise for img2img"""
39
38
  ucPreset: UCPresetTypeAlias = Field(None, ge=0)
40
39
  """The Negative Prompt Preset, Bigger or equal to 0"""
41
40
  qualityToggle: bool = True
42
41
  """Whether to add the quality prompt"""
43
- sm: Optional[bool] = False
44
- # TODO: find out the usage
45
- sm_dyn: Optional[bool] = False
46
- # TODO: find out the usage
42
+ autoSmea: Optional[bool] = False
47
43
  dynamic_thresholding: bool = False
48
44
  """Decrisp:Reduce artifacts caused by high prompt guidance values"""
49
45
  controlnet_strength: float = Field(1.0, ge=0.1, le=2, multiple_of=0.1)
50
46
  """ControlNet Strength"""
51
47
  legacy: bool = False
52
48
  """Legacy Mode"""
49
+ add_original_image: Optional[bool] = Field(False, description="Overlay Original Image")
50
+ """
51
+ Overlay Original Image.Prevents the existing image from changing,
52
+ but can introduce seams along the edge of the mask.
53
+ """
53
54
  cfg_rescale: Optional[float] = Field(0, ge=0, le=1, multiple_of=0.02)
54
55
  """Prompt Guidance Rescale"""
55
56
  noise_schedule: Optional[NoiseSchedule] = None
56
57
  """Noise Schedule"""
57
58
  legacy_v3_extend: Optional[bool] = False
58
59
  """Legacy V3 Extend"""
59
- mask: ImageBytesTypeAlias = None
60
- """Mask for Inpainting"""
60
+ skip_cfg_above_sigma: Optional[int] = None
61
+ """Variety Boost, a new feature to improve the diversity of samples."""
62
+ use_coords: bool = Field(False, description="Use Coordinates")
63
+ """Use Coordinates"""
64
+ legacy_uc: bool = False
65
+ normalize_reference_strength_multiple: bool = True
66
+ inpaintImg2ImgStrength: Optional[float] = Field(None, ge=0, le=1, multiple_of=0.01)
67
+ """For SameFeel"""
61
68
  seed: int = Field(
62
69
  default_factory=lambda: random.randint(0, 4294967295 - 7),
63
70
  gt=0,
64
71
  le=4294967295 - 7,
65
72
  )
66
73
  """Seed"""
67
- image: ImageBytesTypeAlias = None
68
- """Image for img2img"""
74
+ characterPrompts: List[Character] = Field(default_factory=list)
75
+ """Character Prompts"""
76
+ v4_prompt: Optional[V4Prompt] = Field(None, description="V4 Prompt")
77
+ """V4 Prompt"""
78
+ v4_negative_prompt: Optional[V4NegativePrompt] = Field(None, description="V4 Negative Prompt")
79
+ """V4 Negative Prompt"""
69
80
  negative_prompt: Optional[str] = ''
70
81
  """Negative Prompt"""
82
+ deliberate_euler_ancestral_bug: Optional[bool] = Field(False, description="Deliberate Euler Ancestral Bug")
83
+ """Deliberate Euler Ancestral Bug"""
84
+ prefer_brownian: bool = Field(True, description="Prefer Brownian")
85
+ """Prefer Brownian"""
86
+
87
+ # Other fields
88
+ strength: Optional[float] = Field(0.7, ge=0.01, le=0.99, multiple_of=0.01)
89
+ """Strength for img2img"""
90
+ noise: Optional[float] = Field(0, ge=0, le=0.99, multiple_of=0.01)
91
+ """Noise for img2img"""
92
+ sm: Optional[bool] = False
93
+ # TODO: find out the usage
94
+ sm_dyn: Optional[bool] = False
95
+ # TODO: find out the usage
96
+ mask: ImageBytesTypeAlias = None
97
+ """Mask for Inpainting"""
98
+ image: ImageBytesTypeAlias = None
99
+ """Image for img2img"""
71
100
  reference_image_multiple: Optional[List[Union[str, bytes]]] = Field(default_factory=list)
72
101
  """Reference Image For Vibe Mode"""
73
102
  reference_information_extracted_multiple: Optional[List[float]] = Field(default_factory=list)
@@ -79,48 +108,44 @@ class Params(BaseModel):
79
108
  """Reference Strength For Vibe Mode"""
80
109
  extra_noise_seed: Optional[int] = Field(None, gt=0, le=4294967295 - 7)
81
110
  """Extra Noise Seed"""
82
-
83
- # ====Version3 ====
84
- params_version: int = 3
85
- """Params Version For Request"""
86
- add_original_image: Optional[bool] = Field(False, description="Overlay Original Image")
87
- """
88
- Overlay Original Image.Prevents the existing image from changing,
89
- but can introduce seams along the edge of the mask.
90
- """
91
- use_coords: bool = Field(False, description="Use Coordinates")
92
- """Use Coordinates"""
93
- characterPrompts: List[Character] = Field(default_factory=list)
94
- """Character Prompts"""
95
-
96
111
  color_correct: Optional[bool] = None
97
112
  """For Inpaint"""
98
- inpaintImg2ImgStrength: Optional[float] = Field(None, ge=0, le=1, multiple_of=0.01)
99
- """For SameFeel"""
100
-
101
- v4_prompt: Optional[V4Prompt] = Field(None, description="V4 Prompt")
102
- """V4 Prompt"""
103
- v4_negative_prompt: Optional[V4NegativePrompt] = Field(None, description="V4 Negative Prompt")
104
- """V4 Negative Prompt"""
105
-
106
- deliberate_euler_ancestral_bug: Optional[bool] = Field(False, description="Deliberate Euler Ancestral Bug")
107
- """Deliberate Euler Ancestral Bug"""
108
- prefer_brownian: bool = Field(True, description="Prefer Brownian")
109
- """Prefer Brownian"""
110
-
111
- # ======== V1 ========
112
113
  controlnet_condition: Optional[str] = None
113
114
  """ControlNet Condition"""
114
115
  controlnet_model: Optional[ControlNetModel] = None
115
116
  """ControlNet Model"""
116
- skip_cfg_above_sigma: Optional[int] = None
117
- """Variety Boost, a new feature to improve the diversity of samples."""
118
117
  uncond_scale: Optional[float] = Field(None, ge=0, le=1.5, multiple_of=0.05)
119
118
  """Undesired Content Strength"""
120
119
 
121
- autoSmea: Optional[bool] = False
122
- normalize_reference_strength_multiple: bool = True
123
- legacy_uc: bool = False
120
+ # region Custom serializer
121
+ __strong_values__: Set[str] = {
122
+ "skip_cfg_above_sigma", # See models before Anime V3
123
+ }
124
+ """Settings with none value to strongly include"""
125
+
126
+ @model_serializer(mode="wrap")
127
+ def _serialize(self, handler):
128
+ """
129
+ Custom serializer to force include specific fields even when they are None
130
+ """
131
+ data = handler(self)
132
+
133
+ # Force include strong fields and ensure proper ordering
134
+ # Create ordered dict following JavaScript field order
135
+ ordered_fields = list(self.__class__.model_fields.keys())
136
+ ordered_data = OrderedDict()
137
+
138
+ for field_name in ordered_fields:
139
+ if field_name in data:
140
+ # Field exists in data, include it
141
+ ordered_data[field_name] = data[field_name]
142
+ elif field_name in self.__strong_values__:
143
+ # Since we iterate from `self.__class__.model_fields`,、
144
+ # the field is guaranteed to exist. The previous safety check was redundant.
145
+ ordered_data[field_name] = getattr(self, field_name, None)
146
+
147
+ return ordered_data
148
+ # endregion
124
149
 
125
150
  @model_validator(mode="after")
126
151
  def v_character(self):
@@ -84,14 +84,14 @@ class Caption(BaseModel):
84
84
 
85
85
  class V4Prompt(BaseModel):
86
86
  caption: Caption = Field(default_factory=Caption, description="Caption")
87
- use_coords: bool = Field(True, description="Use Coordinates")
87
+ use_coords: bool = Field(False, description="Use Coordinates")
88
88
  use_order: bool = Field(True, description="Use Order")
89
89
 
90
90
  @classmethod
91
91
  def build_from_character_prompts(cls,
92
92
  base_caption: str,
93
93
  character_prompts: List[Character],
94
- use_coords: bool = True,
94
+ use_coords: bool = False,
95
95
  use_order: bool = True
96
96
  ) -> "V4Prompt":
97
97
  """
@@ -37,25 +37,6 @@ class SuggestTags(ApiBaseModel):
37
37
  def base_url(self):
38
38
  return f"{self.endpoint.strip('/')}/ai/generate-image/suggest-tags"
39
39
 
40
- async def necessary_headers(self, request_data) -> dict:
41
- return {
42
- "Host": urlparse(self.endpoint).netloc,
43
- "Accept": "*/*",
44
-
45
- "Accept-Encoding": "gzip, deflate, br",
46
- "Access-Control-Allow-Origin": "*",
47
- "Referer": "https://novelai.net/",
48
- "Content-Type": "application/json",
49
- "Origin": "https://novelai.net",
50
- "Connection": "keep-alive",
51
- "Sec-Fetch-Dest": "empty",
52
- "Sec-Fetch-Mode": "cors",
53
- "Sec-Fetch-Site": "same-site",
54
- "Pragma": "no-cache",
55
- "Cache-Control": "no-cache",
56
- "TE": "trailers",
57
- }
58
-
59
40
  async def request(self,
60
41
  session: Union[AsyncSession, CredentialBase],
61
42
  *,
@@ -70,9 +51,9 @@ class SuggestTags(ApiBaseModel):
70
51
  # Data Build
71
52
  request_data = self.model_dump(mode="json", exclude_none=True)
72
53
  if isinstance(session, AsyncSession):
73
- session.headers.update(await self.necessary_headers(request_data))
54
+ pass
74
55
  elif isinstance(session, CredentialBase):
75
- session = await session.get_session(update_headers=await self.necessary_headers(request_data))
56
+ pass
76
57
  # Header
77
58
  if override_headers:
78
59
  session.headers.clear()
@@ -87,7 +87,6 @@ class LLMStream(LLM):
87
87
  }
88
88
  async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
89
89
  # Header
90
- sess.headers.update(await self.necessary_headers(request_data))
91
90
  if override_headers:
92
91
  sess.headers.clear()
93
92
  sess.headers.update(override_headers)
@@ -57,27 +57,6 @@ class VoiceGenerate(ApiBaseModel):
57
57
  def endpoint(self, value):
58
58
  self._endpoint = value
59
59
 
60
- async def necessary_headers(self, request_data) -> dict:
61
- """
62
- :param request_data: dict
63
- :return: dict
64
- "Sec-Ch-Ua": '"Edge";v="123", "Not:A-Brand";v="8"',
65
- "Sec-Ch-Ua-Mobile": "?0",
66
- "Sec-Ch-Ua-Platform": '"Windows"',
67
- "Sec-Fetch-Dest": "empty",
68
- "Sec-Fetch-Mode": "cors",
69
- "Sec-Fetch-Site": "same-site",
70
- """
71
- return {
72
- "Host": urlparse(self.endpoint).netloc,
73
- "Accept": "*/*",
74
- "Accept-Language": "zh-CN,zh;q=0.9",
75
- "Cache-Control": "no-cache",
76
- "Origin": "https://novelai.net",
77
- "Pragma": "no-cache",
78
- "Referer": "https://novelai.net/",
79
- }
80
-
81
60
  @classmethod
82
61
  def build(cls,
83
62
  text: str,
@@ -133,7 +112,6 @@ class VoiceGenerate(ApiBaseModel):
133
112
  request_data = self.model_dump(mode="json", exclude_none=True)
134
113
  async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
135
114
  # Header
136
- sess.headers.update(await self.necessary_headers(request_data))
137
115
  if override_headers:
138
116
  sess.headers.clear()
139
117
  sess.headers.update(override_headers)
@@ -66,28 +66,6 @@ class Upscale(ApiBaseModel):
66
66
  def endpoint(self, value):
67
67
  self._endpoint = value
68
68
 
69
- async def necessary_headers(self, request_data) -> dict:
70
- """
71
- :param request_data:
72
- :return:
73
- """
74
- return {
75
- "Host": urlparse(self.endpoint).netloc,
76
- "Accept": "*/*",
77
-
78
- "Accept-Encoding": "gzip, deflate, br",
79
- "Referer": "https://novelai.net/",
80
- "Content-Type": "application/json",
81
- "Origin": "https://novelai.net",
82
- "Content-Length": str(len(json.dumps(request_data).encode("utf-8"))),
83
- "Connection": "keep-alive",
84
- "Sec-Fetch-Dest": "empty",
85
- "Sec-Fetch-Mode": "cors",
86
- "Sec-Fetch-Site": "same-site",
87
- "Pragma": "no-cache",
88
- "Cache-Control": "no-cache",
89
- }
90
-
91
69
  @retry(
92
70
  wait=wait_random(min=1, max=3),
93
71
  stop=stop_after_attempt(3),
@@ -108,7 +86,6 @@ class Upscale(ApiBaseModel):
108
86
  # Prepare request data
109
87
  request_data = self.model_dump(mode="json", exclude_none=True)
110
88
  async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
111
- sess.headers.update(await self.necessary_headers(request_data))
112
89
  if override_headers:
113
90
  sess.headers.clear()
114
91
  sess.headers.update(override_headers)
@@ -31,10 +31,6 @@ class ApiBaseModel(BaseModel, ABC):
31
31
  def endpoint(self, value):
32
32
  self._endpoint = value
33
33
 
34
- @abstractmethod
35
- async def necessary_headers(self, request_data) -> dict:
36
- raise NotImplementedError()
37
-
38
34
  @staticmethod
39
35
  def ensure_session_has_post_method(session):
40
36
  if not hasattr(session, "post"):
@@ -33,17 +33,6 @@ class Information(ApiBaseModel):
33
33
  def endpoint(self, value):
34
34
  self._endpoint = value
35
35
 
36
- async def necessary_headers(self, request_data) -> dict:
37
- return {
38
- "Host": urlparse(self.endpoint).netloc,
39
- "Accept": "*/*",
40
-
41
- "Accept-Encoding": "gzip, deflate, br",
42
- "Referer": "https://novelai.net/",
43
- "Content-Type": "application/json",
44
- "Origin": "https://novelai.net"
45
- }
46
-
47
36
  async def request(self,
48
37
  session: Union[AsyncSession, CredentialBase],
49
38
  *,
@@ -58,7 +47,6 @@ class Information(ApiBaseModel):
58
47
  request_data = {}
59
48
  async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
60
49
  # Header
61
- sess.headers.update(await self.necessary_headers(request_data))
62
50
  if override_headers:
63
51
  sess.headers.clear()
64
52
  sess.headers.update(override_headers)
@@ -53,9 +53,6 @@ class Login(ApiBaseModel):
53
53
  """
54
54
  return cls(key=encode_access_key(user_name, password))
55
55
 
56
- async def necessary_headers(self, request_data) -> dict:
57
- return {}
58
-
59
56
  async def request(self,
60
57
  session: Union[AsyncSession, CredentialBase] = None,
61
58
  *,
@@ -70,7 +67,6 @@ class Login(ApiBaseModel):
70
67
 
71
68
  async with session if isinstance(session, AsyncSession) else self.session as sess:
72
69
  # Header
73
- sess.headers.update(await self.necessary_headers(request_data))
74
70
  if override_headers:
75
71
  sess.headers.clear()
76
72
  sess.headers.update(override_headers)
@@ -33,24 +33,6 @@ class Subscription(ApiBaseModel):
33
33
  def endpoint(self, value):
34
34
  self._endpoint = value
35
35
 
36
- async def necessary_headers(self, request_data) -> dict:
37
- return {
38
- "Host": urlparse(self.endpoint).netloc,
39
- "Accept": "*/*",
40
-
41
- "Accept-Encoding": "gzip, deflate, br",
42
- "Referer": "https://novelai.net/",
43
- "Content-Type": "application/json",
44
- "Origin": "https://novelai.net",
45
- "Connection": "keep-alive",
46
- "Sec-Fetch-Dest": "empty",
47
- "Sec-Fetch-Mode": "cors",
48
- "Sec-Fetch-Site": "same-site",
49
- "Pragma": "no-cache",
50
- "Cache-Control": "no-cache",
51
- "TE": "trailers",
52
- }
53
-
54
36
  async def request(self,
55
37
  session: Union[AsyncSession, CredentialBase],
56
38
  *,
@@ -66,7 +48,6 @@ class Subscription(ApiBaseModel):
66
48
  request_data = {}
67
49
  async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
68
50
  # Header
69
- sess.headers.update(await self.necessary_headers(request_data))
70
51
  if override_headers:
71
52
  sess.headers.clear()
72
53
  sess.headers.update(override_headers)
@@ -16,7 +16,7 @@ def encode_access_key(username: str, password: str) -> str:
16
16
  :param password: str (plaintext)
17
17
  :return: str
18
18
  """
19
- pre_salt = f"{password[:6]}{username}novelai_data_access_key"
19
+ pre_salt = f"{password[:6]}{username.lower()}novelai_data_access_key"
20
20
 
21
21
  blake = blake2b(digest_size=16)
22
22
  blake.update(pre_salt.encode())
File without changes