shindan-cli 2.1.1__tar.gz → 2.2.0__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shindan-cli
3
- Version: 2.1.1
3
+ Version: 2.2.0
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>
@@ -10,25 +10,25 @@ Keywords: cli,shindanmaker
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Programming Language :: Python :: 3 :: Only
13
- Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
18
  Classifier: Typing :: Typed
19
- Requires-Python: <4,>=3.9
19
+ Requires-Python: <4,>=3.10
20
20
  Requires-Dist: beautifulsoup4<5,>=4.12.3
21
21
  Requires-Dist: cloudscraper<2,>=1.2.71
22
- Requires-Dist: lxml<6,>=5.3
22
+ Requires-Dist: lxml<7,>=5.3
23
23
  Requires-Dist: requests<3,>=2.32.3
24
24
  Description-Content-Type: text/markdown
25
25
 
26
26
  # shindan-cli
27
27
 
28
- [![Test](
29
- <https://github.com/eggplants/shindan-cli/actions/workflows/test.yml/badge.svg>
28
+ [![CI](
29
+ <https://github.com/eggplants/shindan-cli/actions/workflows/ci.yml/badge.svg>
30
30
  )](
31
- <https://github.com/eggplants/shindan-cli/actions/workflows/test.yml>
31
+ <https://github.com/eggplants/shindan-cli/actions/workflows/ci.yml>
32
32
  ) [![Release Package](
33
33
  <https://github.com/eggplants/shindan-cli/actions/workflows/release.yml/badge.svg>
34
34
  )](
@@ -43,16 +43,6 @@ Description-Content-Type: text/markdown
43
43
  <https://pypi.org/project/shindan-cli>
44
44
  )
45
45
 
46
- [![Maintainability](
47
- <https://qlty.sh/badges/9e2de2a6-51c0-4797-bc60-da4a2ff28737/maintainability.svg>
48
- )](
49
- <https://qlty.sh/gh/eggplants/projects/shindan-cli>
50
- ) [![Code Coverage](
51
- <https://qlty.sh/badges/9e2de2a6-51c0-4797-bc60-da4a2ff28737/test_coverage.svg>
52
- )](
53
- <https://qlty.sh/gh/eggplants/projects/shindan-cli>
54
- )
55
-
56
46
  ShindanMaker (診断メーカー, <https://shindanmaker.com>) CLI + Library
57
47
 
58
48
  ## Install
@@ -1,9 +1,9 @@
1
1
  # shindan-cli
2
2
 
3
- [![Test](
4
- <https://github.com/eggplants/shindan-cli/actions/workflows/test.yml/badge.svg>
3
+ [![CI](
4
+ <https://github.com/eggplants/shindan-cli/actions/workflows/ci.yml/badge.svg>
5
5
  )](
6
- <https://github.com/eggplants/shindan-cli/actions/workflows/test.yml>
6
+ <https://github.com/eggplants/shindan-cli/actions/workflows/ci.yml>
7
7
  ) [![Release Package](
8
8
  <https://github.com/eggplants/shindan-cli/actions/workflows/release.yml/badge.svg>
9
9
  )](
@@ -18,16 +18,6 @@
18
18
  <https://pypi.org/project/shindan-cli>
19
19
  )
20
20
 
21
- [![Maintainability](
22
- <https://qlty.sh/badges/9e2de2a6-51c0-4797-bc60-da4a2ff28737/maintainability.svg>
23
- )](
24
- <https://qlty.sh/gh/eggplants/projects/shindan-cli>
25
- ) [![Code Coverage](
26
- <https://qlty.sh/badges/9e2de2a6-51c0-4797-bc60-da4a2ff28737/test_coverage.svg>
27
- )](
28
- <https://qlty.sh/gh/eggplants/projects/shindan-cli>
29
- )
30
-
31
21
  ShindanMaker (診断メーカー, <https://shindanmaker.com>) CLI + Library
32
22
 
33
23
  ## Install
@@ -13,23 +13,23 @@ keywords = [
13
13
  ]
14
14
  license = { text = "MIT" }
15
15
  authors = [ { name = "eggplants", email = "w10776e8w@yahoo.co.jp" } ]
16
- requires-python = ">=3.9,<4"
16
+ requires-python = ">=3.10,<4"
17
17
  classifiers = [
18
18
  "Development Status :: 3 - Alpha",
19
19
  "License :: OSI Approved :: MIT License",
20
20
  "Programming Language :: Python :: 3 :: Only",
21
- "Programming Language :: Python :: 3.9",
22
21
  "Programming Language :: Python :: 3.10",
23
22
  "Programming Language :: Python :: 3.11",
24
23
  "Programming Language :: Python :: 3.12",
25
24
  "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
26
  "Typing :: Typed",
27
27
  ]
28
28
  dynamic = [ "version" ]
29
29
  dependencies = [
30
30
  "beautifulsoup4>=4.12.3,<5",
31
31
  "cloudscraper>=1.2.71,<2",
32
- "lxml>=5.3,<6",
32
+ "lxml>=5.3,<7",
33
33
  "requests>=2.32.3,<3",
34
34
  ]
35
35
 
@@ -38,20 +38,17 @@ scripts.shindan = "shindan_cli.main:main"
38
38
 
39
39
  [dependency-groups]
40
40
  dev = [
41
- "lxml-stubs>=0.4,<0.6",
42
- "mypy>=1.13,<2",
43
- "pre-commit>=4.0.1,<5",
44
- "pytest>=7.2.2,<9",
45
- "pytest-cov>=4,<7",
46
- "taskipy>=1.10.3,<2",
47
- "types-beautifulsoup4>=4.11.6.7,<5",
48
- "types-requests>=2.28.11.14,<3",
41
+ "pymarkdownlnt>=0.9.33",
42
+ "pyproject-fmt>=2.11.1",
43
+ "pytest>=9.0.2",
44
+ "pytest-asyncio>=1.3",
45
+ "pytest-cov>=7",
46
+ ]
47
+ docs = [
48
+ "pdoc>=16",
49
49
  ]
50
- docs = [ "pdoc" ]
51
50
 
52
51
  [tool.hatch.version]
53
- # tool.hatch.version.source is not listed in official schema.
54
- # https://github.com/ninoseki/uv-dynamic-versioning/issues/21
55
52
  source = "uv-dynamic-versioning"
56
53
 
57
54
  [tool.hatch.build.targets.sdist]
@@ -60,42 +57,30 @@ include = [ "shindan_cli" ]
60
57
  [tool.hatch.build.targets.wheel]
61
58
  include = [ "shindan_cli" ]
62
59
 
60
+ [tool.uv]
61
+ preview = true
62
+ default-groups = [
63
+ "dev",
64
+ "docs",
65
+ ]
66
+ exclude-newer = "P7D"
67
+
63
68
  [tool.ruff]
64
69
  line-length = 120
65
70
  format.indent-style = "space"
66
71
  format.quote-style = "double"
67
- lint.select = [
68
- "ALL",
69
- ]
72
+ lint.select = [ "ALL" ]
70
73
  lint.ignore = [
71
- "D203", # 1 blank line required before class docstring
72
- "D213", # Multi-line docstring summary should start at the second line
74
+ "COM812",
75
+ "D203", # 1 blank line required before class docstring
76
+ "D213", # Multi-line docstring summary should start at the second line
73
77
  ]
74
78
  lint.per-file-ignores."tests/*.py" = [
75
79
  "D",
76
80
  "S101", # Use of assert detected
77
81
  ]
78
82
 
79
- [tool.mypy]
80
- pretty = true
81
- python_version = "3.9"
82
- show_error_codes = true
83
- strict = true
84
-
85
- [tool.uv]
86
- default-groups = [
87
- "dev",
88
- "docs",
89
- ]
90
-
91
83
  [tool.uv-dynamic-versioning]
92
84
  vcs = "git"
93
85
  style = "semver"
94
-
95
- [tool.taskipy.tasks]
96
- lint = "pre-commit run -a"
97
- profile = "python -m cProfile"
98
- setup = "poetry install && pre-commit install -t pre-commit -t pre-push"
99
- test = "pytest --cov=shindan_cli --cov-report=term"
100
- "test:ci" = "task test --cov-report=xml:cov.xml"
101
- gendoc = "pdoc shindan_cli -o ./docs --docformat google"
86
+ fallback-version = "0.0.0"
@@ -0,0 +1,66 @@
1
+ """Constants for the shindan CLI."""
2
+
3
+ from typing import Literal, TypedDict
4
+
5
+
6
+ class AIParams(TypedDict):
7
+ _token: str
8
+ randname: str
9
+ type: Literal["ai"]
10
+ shindan_token: str
11
+ encrypted_exec_key: str
12
+
13
+
14
+ class BranchParams(TypedDict):
15
+ _token: str
16
+ randname: str
17
+ hiddenName: str
18
+ type: Literal["branch"]
19
+ shindan_token: str
20
+ rbr: str
21
+
22
+
23
+ class CheckParams(TypedDict):
24
+ _token: str
25
+ randname: str
26
+ hiddenName: str
27
+ type: Literal["check"]
28
+ shindan_token: str
29
+ # input-check-choice[choice_id]: str
30
+
31
+
32
+ class NameParams(TypedDict):
33
+ _token: str
34
+ randname: str
35
+ hiddenName: str
36
+ type: Literal["name"]
37
+ shindan_token: str
38
+
39
+
40
+ class TargetKeysByType(TypedDict):
41
+ ai: AIParams
42
+ branch: BranchParams
43
+ check: CheckParams
44
+ name: NameParams
45
+
46
+
47
+ TARGET_KEYS_BY_TYPE = {
48
+ "ai": AIParams.__annotations__.keys(),
49
+ "branch": BranchParams.__annotations__.keys(),
50
+ "check": CheckParams.__annotations__.keys(),
51
+ "name": NameParams.__annotations__.keys(),
52
+ }
53
+
54
+ BASE_URL = "https://shindanmaker.com"
55
+
56
+ HEADERS = {
57
+ "User-Agent": (
58
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36(KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
59
+ ),
60
+ }
61
+
62
+ __all__ = (
63
+ "BASE_URL",
64
+ "HEADERS",
65
+ "TARGET_KEYS_BY_TYPE",
66
+ )
@@ -7,14 +7,14 @@ from typing import TYPE_CHECKING, Union
7
7
 
8
8
  from bs4 import BeautifulSoup, Tag
9
9
 
10
- from .constants import BASE_URL, HEADERS
10
+ from .constants import BASE_URL, HEADERS, AIParams, BranchParams, CheckParams, NameParams
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from requests import Session
14
14
 
15
15
  from .models import ShindanResult, UserInputs
16
16
 
17
- Params = dict[str, Union[str, list[str], None]]
17
+ Params = Union[AIParams, BranchParams, CheckParams, NameParams]
18
18
 
19
19
 
20
20
  def __get_result(
@@ -50,7 +50,7 @@ def __get_result(
50
50
 
51
51
  def get_result_by_ai(
52
52
  session: Session,
53
- params: Params,
53
+ params: AIParams,
54
54
  *,
55
55
  user_inputs: UserInputs,
56
56
  hashtag: str | None,
@@ -98,6 +98,7 @@ def get_result_by_ai(
98
98
  "form_values": user_inputs,
99
99
  "shindan_token": params["_token"],
100
100
  "ai_result_request_times": 0,
101
+ "encrypted_exec_key": params["encrypted_exec_key"],
101
102
  },
102
103
  headers=HEADERS,
103
104
  )
@@ -113,7 +114,7 @@ def get_result_by_ai(
113
114
 
114
115
  def get_result_by_branch(
115
116
  session: Session,
116
- params: Params,
117
+ params: BranchParams,
117
118
  *,
118
119
  shindan_url: str,
119
120
  ) -> ShindanResult:
@@ -133,7 +134,7 @@ def get_result_by_branch(
133
134
 
134
135
  def get_result_by_check(
135
136
  session: Session,
136
- params: Params,
137
+ params: CheckParams,
137
138
  *,
138
139
  user_choices: dict[int, str],
139
140
  shindan_url: str,
@@ -151,7 +152,7 @@ def get_result_by_check(
151
152
 
152
153
  """
153
154
  for choice_id, answer_id in user_choices.items():
154
- params[f"input-check-choice[{choice_id}]"] = answer_id
155
+ params[f"input-check-choice[{choice_id}]"] = answer_id # type: ignore[literal-required]
155
156
 
156
157
  return __get_result(session, params, is_renewal=True, shindan_url=shindan_url)
157
158
 
@@ -4,10 +4,12 @@ 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
8
+
7
9
  if TYPE_CHECKING:
8
10
  from bs4 import BeautifulSoup
9
11
 
10
- from .models import QuestionBranch, QuestionChoice, UserInput, UserInputs
12
+ from .models import UserInputs
11
13
 
12
14
 
13
15
  def get_user_inputs(
@@ -28,19 +30,16 @@ def get_user_inputs(
28
30
  form_labels = source.select("form#shindanForm > div.px-3 > div > span")
29
31
  for idx, question in enumerate([*form_labels, *range(10 - len(form_labels))]):
30
32
  if isinstance(question, int):
31
- user_inputs[f"user_input_{idx + 1}"] = {"q": "", "a": None}
33
+ user_inputs[f"user_input_{idx + 1}"] = UserInput({"q": "", "a": None})
32
34
  continue
33
35
  question_text = question.text
34
36
  if question_text == "あなたの名前":
35
- user_inputs[f"user_input_{idx + 1}"] = {
36
- "q": question_text,
37
- "a": shindan_name,
38
- }
37
+ user_inputs[f"user_input_{idx + 1}"] = UserInput({"q": question_text, "a": shindan_name})
39
38
  continue
40
39
  answer_text = None
41
40
  while not answer_text:
42
41
  answer_text = input(f"[{question_text}]: ").strip()
43
- user_inputs["user_input_{idx + 1}"] = {"q": question_text, "a": answer_text}
42
+ user_inputs[f"user_input_{idx + 1}"] = UserInput({"q": question_text, "a": answer_text})
44
43
  return user_inputs
45
44
 
46
45
 
@@ -60,17 +59,21 @@ def get_rbr(source: BeautifulSoup) -> str:
60
59
  question = branch.select_one("p.shindan_branch_question")
61
60
  if not isinstance(branch_id, str) or not question:
62
61
  continue
63
- question_branches[branch_id.replace("shindan_branch_", "")] = {
64
- "question": question.text.strip(),
65
- "choices": [
66
- {
67
- "label": choice.text,
68
- "next_branch_id": next_branch_id,
69
- }
70
- for choice in branch.select("button")
71
- if isinstance(next_branch_id := choice.get("data-next_branch"), str)
72
- ],
73
- }
62
+ question_branches[branch_id.replace("shindan_branch_", "")] = QuestionBranch(
63
+ {
64
+ "question": question.text.strip(),
65
+ "choices": [
66
+ QuestionBranchChoice(
67
+ {
68
+ "label": choice.text,
69
+ "next_branch_id": next_branch_id,
70
+ }
71
+ )
72
+ for choice in branch.select("button")
73
+ if isinstance(next_branch_id := choice.get("data-next_branch"), str)
74
+ ],
75
+ }
76
+ )
74
77
 
75
78
  goal_branches: dict[str, str] = {}
76
79
  for branch in source.select("div[id^='shindan_branch_'][data-kind='1']"):
@@ -115,14 +118,16 @@ def get_choices(source: BeautifulSoup) -> dict[int, str]:
115
118
  question_text = question.select_one("div > p > span")
116
119
  if not isinstance(order_id, str) or not question_text:
117
120
  continue
118
- question_choices[int(order_id)] = {
119
- "question": question_text.text.strip(),
120
- "choices": [
121
- {"label": choice.text.strip(), "choice_id": choice_key}
122
- for choice in question.select("button.list-group-item")
123
- if isinstance(choice_key := choice.get("data-choice_key"), str)
124
- ],
125
- }
121
+ question_choices[int(order_id)] = QuestionChoice(
122
+ {
123
+ "question": question_text.text.strip(),
124
+ "choices": [
125
+ QuestionChoiceChoice({"label": choice.text.strip(), "choice_id": choice_key})
126
+ for choice in question.select("button.list-group-item")
127
+ if isinstance(choice_key := choice.get("data-choice_key"), str)
128
+ ],
129
+ }
130
+ )
126
131
  current_question_id = 1
127
132
  total_questions = len(question_choices)
128
133
  answers: dict[int, str] = {}
@@ -4,13 +4,15 @@ from __future__ import annotations
4
4
 
5
5
  import random
6
6
  import time
7
+ from typing import cast
7
8
 
8
9
  import cloudscraper # type: ignore[unused-ignore,import-not-found,import-untyped]
9
10
  from bs4 import BeautifulSoup
10
- from requests import codes
11
+ from requests.sessions import Session
11
12
 
12
13
  from .constants import BASE_URL, HEADERS, TARGET_KEYS_BY_TYPE
13
14
  from .get_results import (
15
+ Params,
14
16
  get_result_by_ai,
15
17
  get_result_by_branch,
16
18
  get_result_by_check,
@@ -53,9 +55,10 @@ def shindan(
53
55
  shindan_url = f"{BASE_URL}/{page_id}"
54
56
 
55
57
  session = cloudscraper.create_scraper()
58
+ assert isinstance(session, Session)
56
59
 
57
60
  shindan_page = session.get(shindan_url, headers=HEADERS)
58
- if shindan_page.status_code != codes.ok:
61
+ if shindan_page.status_code != 200:
59
62
  raise ShindanError(shindan_page.status_code)
60
63
 
61
64
  source = BeautifulSoup(shindan_page.text, features="lxml")
@@ -65,18 +68,21 @@ def shindan(
65
68
  msg = "Cannot detect shindan type!"
66
69
  raise ValueError(msg)
67
70
  shindan_type = str(shindan_type_input.get("value"))
68
- params = {
69
- key: key_input.get("value")
70
- for key in TARGET_KEYS_BY_TYPE[shindan_type]
71
- if (key_input := source.select_one(f'input[name="{key}"]'))
72
- }
71
+ params = cast(
72
+ "Params",
73
+ {
74
+ key: key_input.get("value")
75
+ for key in TARGET_KEYS_BY_TYPE[shindan_type]
76
+ if (key_input := source.select_one(f'input[name="{key}"]'))
77
+ },
78
+ )
73
79
  # overwrite randname (old: shindanName)
74
80
  params["randname"] = shindan_name
75
81
 
76
82
  if wait:
77
83
  time.sleep(random.uniform(2, 5)) # noqa: S311
78
84
 
79
- if shindan_type == "ai":
85
+ if params["type"] == "ai":
80
86
  hashtag_title = source.select_one("h1#shindanTitle")
81
87
  if not hashtag_title or not isinstance(
82
88
  hashtag := hashtag_title.get("data-shindan_hashtag"),
@@ -90,21 +96,21 @@ def shindan(
90
96
  hashtag=hashtag,
91
97
  shindan_url=shindan_url,
92
98
  )
93
- if shindan_type == "branch":
99
+ if params["type"] == "branch":
94
100
  params["rbr"] = get_rbr(source)
95
101
  return get_result_by_branch(
96
102
  session,
97
103
  params,
98
104
  shindan_url=shindan_url,
99
105
  )
100
- if shindan_type == "check":
106
+ if params["type"] == "check":
101
107
  return get_result_by_check(
102
108
  session,
103
109
  params,
104
110
  user_choices=get_choices(source),
105
111
  shindan_url=shindan_url,
106
112
  )
107
- if shindan_type == "name":
113
+ if params["type"] == "name":
108
114
  return get_result_by_name(
109
115
  session,
110
116
  params,
@@ -1,23 +0,0 @@
1
- """Constants for the shindan CLI."""
2
-
3
- TARGET_KEYS_BY_TYPE = {
4
- "ai": ("_token", "randname", "type", "shindan_token"),
5
- "branch": ("_token", "randname", "hiddenName", "type", "shindan_token", "rbr"),
6
- "check": ("_token", "randname", "hiddenName", "type", "shindan_token"),
7
- "name": ("_token", "randname", "hiddenName", "type", "shindan_token"),
8
- }
9
-
10
- BASE_URL = "https://shindanmaker.com"
11
-
12
- HEADERS = {
13
- "User-Agent": (
14
- "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
15
- "(KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
16
- ),
17
- }
18
-
19
- __all__ = (
20
- "BASE_URL",
21
- "HEADERS",
22
- "TARGET_KEYS_BY_TYPE",
23
- )
File without changes
File without changes