shindan-cli 2.2.0__tar.gz → 2.2.1__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.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shindan-cli
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: ShindanMaker (https://shindanmaker.com) CLI
5
5
  Project-URL: Repository, https://github.com/eggplants/shindan-cli
6
6
  Author-email: eggplants <w10776e8w@yahoo.co.jp>
7
- License: MIT
7
+ License-Expression: MIT
8
8
  License-File: LICENSE
9
9
  Keywords: cli,shindanmaker
10
10
  Classifier: Development Status :: 3 - Alpha
@@ -1,6 +1,5 @@
1
1
  [build-system]
2
2
  build-backend = "hatchling.build"
3
-
4
3
  requires = [ "hatchling", "uv-dynamic-versioning" ]
5
4
 
6
5
  [project]
@@ -11,7 +10,7 @@ keywords = [
11
10
  "cli",
12
11
  "shindanmaker",
13
12
  ]
14
- license = { text = "MIT" }
13
+ license = "MIT"
15
14
  authors = [ { name = "eggplants", email = "w10776e8w@yahoo.co.jp" } ]
16
15
  requires-python = ">=3.10,<4"
17
16
  classifiers = [
@@ -32,7 +31,6 @@ dependencies = [
32
31
  "lxml>=5.3,<7",
33
32
  "requests>=2.32.3,<3",
34
33
  ]
35
-
36
34
  urls.Repository = "https://github.com/eggplants/shindan-cli"
37
35
  scripts.shindan = "shindan_cli.main:main"
38
36
 
@@ -48,22 +46,19 @@ docs = [
48
46
  "pdoc>=16",
49
47
  ]
50
48
 
51
- [tool.hatch.version]
52
- source = "uv-dynamic-versioning"
53
-
54
- [tool.hatch.build.targets.sdist]
55
- include = [ "shindan_cli" ]
56
-
57
- [tool.hatch.build.targets.wheel]
58
- include = [ "shindan_cli" ]
49
+ [tool.hatch]
50
+ build.targets.sdist.include = [ "shindan_cli" ]
51
+ build.targets.wheel.include = [ "shindan_cli" ]
52
+ version.source = "uv-dynamic-versioning"
59
53
 
60
54
  [tool.uv]
61
- preview = true
55
+ python-downloads = "never"
62
56
  default-groups = [
63
57
  "dev",
64
58
  "docs",
65
59
  ]
66
60
  exclude-newer = "P7D"
61
+ preview = true
67
62
 
68
63
  [tool.ruff]
69
64
  line-length = 120
@@ -7,34 +7,26 @@ class AIParams(TypedDict):
7
7
  _token: str
8
8
  randname: str
9
9
  type: Literal["ai"]
10
- shindan_token: str
11
10
  encrypted_exec_key: str
12
11
 
13
12
 
14
13
  class BranchParams(TypedDict):
15
14
  _token: str
16
15
  randname: str
17
- hiddenName: str
18
16
  type: Literal["branch"]
19
- shindan_token: str
20
17
  rbr: str
21
18
 
22
19
 
23
20
  class CheckParams(TypedDict):
24
21
  _token: str
25
22
  randname: str
26
- hiddenName: str
27
23
  type: Literal["check"]
28
- shindan_token: str
29
- # input-check-choice[choice_id]: str
30
24
 
31
25
 
32
26
  class NameParams(TypedDict):
33
27
  _token: str
34
28
  randname: str
35
- hiddenName: str
36
29
  type: Literal["name"]
37
- shindan_token: str
38
30
 
39
31
 
40
32
  class TargetKeysByType(TypedDict):
@@ -3,18 +3,22 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import re
6
- from typing import TYPE_CHECKING, Union
6
+ import time
7
+ from typing import TYPE_CHECKING, cast
7
8
 
8
9
  from bs4 import BeautifulSoup, Tag
9
10
 
10
- from .constants import BASE_URL, HEADERS, AIParams, BranchParams, CheckParams, NameParams
11
+ from .constants import HEADERS, AIParams, BranchParams, CheckParams, NameParams
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from requests import Session
14
15
 
15
16
  from .models import ShindanResult, UserInputs
16
17
 
17
- Params = Union[AIParams, BranchParams, CheckParams, NameParams]
18
+ Params = AIParams | BranchParams | CheckParams | NameParams
19
+
20
+ _MAX_RETRIES = 3
21
+ _RETRY_WAIT = 4.0
18
22
 
19
23
 
20
24
  def __get_result(
@@ -24,15 +28,23 @@ def __get_result(
24
28
  is_renewal: bool = False,
25
29
  shindan_url: str,
26
30
  ) -> ShindanResult:
27
- result_page = session.post(
28
- shindan_url + ("/r" if is_renewal else ""),
29
- data=params,
30
- headers=HEADERS,
31
- )
32
- soup = BeautifulSoup(result_page.text, features="lxml")
33
- result_tag = soup.find(id="share-copytext-shindanresult-textarea")
31
+ result_tag: Tag | None = None
32
+
33
+ for attempt in range(_MAX_RETRIES):
34
+ if attempt > 0:
35
+ time.sleep(_RETRY_WAIT)
36
+ result_page = session.post(
37
+ shindan_url + ("/r" if is_renewal else ""),
38
+ data=params,
39
+ headers=HEADERS,
40
+ )
41
+ soup = BeautifulSoup(result_page.text, features="lxml")
42
+ found = soup.find(id="share-copytext-shindanresult-textarea")
43
+ if isinstance(found, Tag) and found.text:
44
+ result_tag = found
45
+ break
34
46
 
35
- if not isinstance(result_tag, Tag) or not result_tag.text:
47
+ if result_tag is None:
36
48
  msg = f"Could not find a tag contains the result, returns: {result_tag}"
37
49
  raise TypeError(msg)
38
50
 
@@ -54,6 +66,7 @@ def get_result_by_ai(
54
66
  *,
55
67
  user_inputs: UserInputs,
56
68
  hashtag: str | None,
69
+ csrf_token: str,
57
70
  shindan_url: str,
58
71
  ) -> ShindanResult:
59
72
  """Get result by AI type shindan.
@@ -63,34 +76,14 @@ def get_result_by_ai(
63
76
  params (Params): input parameters fetched from shindan page
64
77
  user_inputs (UserInputs): user inputs
65
78
  hashtag (str | None): hashtag
79
+ csrf_token (str): CSRF token extracted from the shindan page
66
80
  shindan_url (str): shindan url
67
81
 
68
82
  Returns:
69
83
  ShindanResult: the returned result from <https://shindanmaker.com>
70
84
 
71
85
  """
72
- res = session.post(
73
- shindan_url,
74
- data=params,
75
- headers=HEADERS,
76
- )
77
- meta = BeautifulSoup(
78
- res.text,
79
- features="lxml",
80
- ).select_one('meta[name="csrf-token"]')
81
- if not res.ok or not meta or not isinstance(csrf_token := meta.get("content"), str):
82
- msg = f"Failed to get the csrf token. ({res.status_code})"
83
- raise ValueError(msg)
84
- HEADERS.update({"x-csrf-token": csrf_token})
85
- if not res.ok:
86
- res = session.post(
87
- f"{BASE_URL}/ai_life_update",
88
- data={"ai_life": 3},
89
- headers=HEADERS,
90
- )
91
- if not res.ok:
92
- msg = f"Failed to update AI life. ({res.status_code})"
93
- raise ValueError(msg)
86
+ ai_headers = {**HEADERS, "x-csrf-token": csrf_token}
94
87
 
95
88
  result_sse = session.post(
96
89
  f"{shindan_url}/ai_result",
@@ -100,7 +93,7 @@ def get_result_by_ai(
100
93
  "ai_result_request_times": 0,
101
94
  "encrypted_exec_key": params["encrypted_exec_key"],
102
95
  },
103
- headers=HEADERS,
96
+ headers=ai_headers,
104
97
  )
105
98
  gpt_results = "".join(
106
99
  re.findall(r'"content":"([^"]+)', result_sse.text),
@@ -151,8 +144,9 @@ def get_result_by_check(
151
144
  ShindanResult: the returned result from <https://shindanmaker.com>
152
145
 
153
146
  """
147
+ check_params = cast("dict[str, str]", params)
154
148
  for choice_id, answer_id in user_choices.items():
155
- params[f"input-check-choice[{choice_id}]"] = answer_id # type: ignore[literal-required]
149
+ check_params[f"input-check-choice[{choice_id}]"] = answer_id
156
150
 
157
151
  return __get_result(session, params, is_renewal=True, shindan_url=shindan_url)
158
152
 
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import TYPE_CHECKING
6
6
 
7
- from shindan_cli.models import QuestionBranchChoice, QuestionChoiceChoice, UserInput, QuestionBranch, QuestionChoice
7
+ from shindan_cli.models import QuestionBranch, QuestionBranchChoice, QuestionChoice, QuestionChoiceChoice, UserInput
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from bs4 import BeautifulSoup
@@ -27,7 +27,7 @@ def get_user_inputs(
27
27
 
28
28
  """
29
29
  user_inputs: dict[str, UserInput] = {}
30
- form_labels = source.select("form#shindanForm > div.px-3 > div > span")
30
+ form_labels = source.select("form#shindanForm label")
31
31
  for idx, question in enumerate([*form_labels, *range(10 - len(form_labels))]):
32
32
  if isinstance(question, int):
33
33
  user_inputs[f"user_input_{idx + 1}"] = UserInput({"q": "", "a": None})
@@ -55,10 +55,10 @@ def shindan(
55
55
  shindan_url = f"{BASE_URL}/{page_id}"
56
56
 
57
57
  session = cloudscraper.create_scraper()
58
- assert isinstance(session, Session)
58
+ assert isinstance(session, Session) # noqa: S101
59
59
 
60
60
  shindan_page = session.get(shindan_url, headers=HEADERS)
61
- if shindan_page.status_code != 200:
61
+ if shindan_page.status_code != 200: # noqa: PLR2004
62
62
  raise ShindanError(shindan_page.status_code)
63
63
 
64
64
  source = BeautifulSoup(shindan_page.text, features="lxml")
@@ -89,11 +89,16 @@ def shindan(
89
89
  str,
90
90
  ):
91
91
  hashtag = None
92
+ csrf_meta = source.select_one('meta[name="csrf-token"]')
93
+ if not csrf_meta or not isinstance(csrf_token := csrf_meta.get("content"), str):
94
+ msg = "Could not find CSRF token on the shindan page."
95
+ raise ShindanError(msg)
92
96
  return get_result_by_ai(
93
97
  session,
94
98
  params,
95
99
  user_inputs=get_user_inputs(source, shindan_name),
96
100
  hashtag=hashtag,
101
+ csrf_token=csrf_token,
97
102
  shindan_url=shindan_url,
98
103
  )
99
104
  if params["type"] == "branch":
File without changes
File without changes
File without changes