shindan-cli 2.1.1__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.
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/PKG-INFO +8 -18
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/README.md +3 -13
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/pyproject.toml +29 -49
- shindan_cli-2.2.1/shindan_cli/constants.py +58 -0
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/shindan_cli/get_results.py +33 -38
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/shindan_cli/interactive.py +32 -27
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/shindan_cli/shindan.py +22 -11
- shindan_cli-2.1.1/shindan_cli/constants.py +0 -23
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/.gitignore +0 -0
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/LICENSE +0 -0
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/shindan_cli/__init__.py +0 -0
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/shindan_cli/main.py +0 -0
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/shindan_cli/models.py +0 -0
- {shindan_cli-2.1.1 → shindan_cli-2.2.1}/shindan_cli/py.typed +0 -0
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shindan-cli
|
|
3
|
-
Version: 2.
|
|
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
|
|
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.
|
|
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<
|
|
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
|
-
[](
|
|
31
|
-
<https://github.com/eggplants/shindan-cli/actions/workflows/
|
|
31
|
+
<https://github.com/eggplants/shindan-cli/actions/workflows/ci.yml>
|
|
32
32
|
) [](
|
|
@@ -43,16 +43,6 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
<https://pypi.org/project/shindan-cli>
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
-
[](
|
|
49
|
-
<https://qlty.sh/gh/eggplants/projects/shindan-cli>
|
|
50
|
-
) [](
|
|
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
|
-
[](
|
|
6
|
-
<https://github.com/eggplants/shindan-cli/actions/workflows/
|
|
6
|
+
<https://github.com/eggplants/shindan-cli/actions/workflows/ci.yml>
|
|
7
7
|
) [](
|
|
@@ -18,16 +18,6 @@
|
|
|
18
18
|
<https://pypi.org/project/shindan-cli>
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
-
[](
|
|
24
|
-
<https://qlty.sh/gh/eggplants/projects/shindan-cli>
|
|
25
|
-
) [](
|
|
28
|
-
<https://qlty.sh/gh/eggplants/projects/shindan-cli>
|
|
29
|
-
)
|
|
30
|
-
|
|
31
21
|
ShindanMaker (診断メーカー, <https://shindanmaker.com>) CLI + Library
|
|
32
22
|
|
|
33
23
|
## Install
|
|
@@ -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,91 +10,72 @@ keywords = [
|
|
|
11
10
|
"cli",
|
|
12
11
|
"shindanmaker",
|
|
13
12
|
]
|
|
14
|
-
license =
|
|
13
|
+
license = "MIT"
|
|
15
14
|
authors = [ { name = "eggplants", email = "w10776e8w@yahoo.co.jp" } ]
|
|
16
|
-
requires-python = ">=3.
|
|
15
|
+
requires-python = ">=3.10,<4"
|
|
17
16
|
classifiers = [
|
|
18
17
|
"Development Status :: 3 - Alpha",
|
|
19
18
|
"License :: OSI Approved :: MIT License",
|
|
20
19
|
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
-
"Programming Language :: Python :: 3.9",
|
|
22
20
|
"Programming Language :: Python :: 3.10",
|
|
23
21
|
"Programming Language :: Python :: 3.11",
|
|
24
22
|
"Programming Language :: Python :: 3.12",
|
|
25
23
|
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
26
25
|
"Typing :: Typed",
|
|
27
26
|
]
|
|
28
27
|
dynamic = [ "version" ]
|
|
29
28
|
dependencies = [
|
|
30
29
|
"beautifulsoup4>=4.12.3,<5",
|
|
31
30
|
"cloudscraper>=1.2.71,<2",
|
|
32
|
-
"lxml>=5.3,<
|
|
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
|
|
|
39
37
|
[dependency-groups]
|
|
40
38
|
dev = [
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"pytest>=
|
|
45
|
-
"pytest-cov>=
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
39
|
+
"pymarkdownlnt>=0.9.33",
|
|
40
|
+
"pyproject-fmt>=2.11.1",
|
|
41
|
+
"pytest>=9.0.2",
|
|
42
|
+
"pytest-asyncio>=1.3",
|
|
43
|
+
"pytest-cov>=7",
|
|
44
|
+
]
|
|
45
|
+
docs = [
|
|
46
|
+
"pdoc>=16",
|
|
49
47
|
]
|
|
50
|
-
docs = [ "pdoc" ]
|
|
51
|
-
|
|
52
|
-
[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
|
-
source = "uv-dynamic-versioning"
|
|
56
48
|
|
|
57
|
-
[tool.hatch
|
|
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
|
-
[tool.
|
|
61
|
-
|
|
54
|
+
[tool.uv]
|
|
55
|
+
python-downloads = "never"
|
|
56
|
+
default-groups = [
|
|
57
|
+
"dev",
|
|
58
|
+
"docs",
|
|
59
|
+
]
|
|
60
|
+
exclude-newer = "P7D"
|
|
61
|
+
preview = true
|
|
62
62
|
|
|
63
63
|
[tool.ruff]
|
|
64
64
|
line-length = 120
|
|
65
65
|
format.indent-style = "space"
|
|
66
66
|
format.quote-style = "double"
|
|
67
|
-
lint.select = [
|
|
68
|
-
"ALL",
|
|
69
|
-
]
|
|
67
|
+
lint.select = [ "ALL" ]
|
|
70
68
|
lint.ignore = [
|
|
71
|
-
"
|
|
72
|
-
"
|
|
69
|
+
"COM812",
|
|
70
|
+
"D203", # 1 blank line required before class docstring
|
|
71
|
+
"D213", # Multi-line docstring summary should start at the second line
|
|
73
72
|
]
|
|
74
73
|
lint.per-file-ignores."tests/*.py" = [
|
|
75
74
|
"D",
|
|
76
75
|
"S101", # Use of assert detected
|
|
77
76
|
]
|
|
78
77
|
|
|
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
78
|
[tool.uv-dynamic-versioning]
|
|
92
79
|
vcs = "git"
|
|
93
80
|
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"
|
|
81
|
+
fallback-version = "0.0.0"
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
encrypted_exec_key: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BranchParams(TypedDict):
|
|
14
|
+
_token: str
|
|
15
|
+
randname: str
|
|
16
|
+
type: Literal["branch"]
|
|
17
|
+
rbr: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CheckParams(TypedDict):
|
|
21
|
+
_token: str
|
|
22
|
+
randname: str
|
|
23
|
+
type: Literal["check"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NameParams(TypedDict):
|
|
27
|
+
_token: str
|
|
28
|
+
randname: str
|
|
29
|
+
type: Literal["name"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TargetKeysByType(TypedDict):
|
|
33
|
+
ai: AIParams
|
|
34
|
+
branch: BranchParams
|
|
35
|
+
check: CheckParams
|
|
36
|
+
name: NameParams
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
TARGET_KEYS_BY_TYPE = {
|
|
40
|
+
"ai": AIParams.__annotations__.keys(),
|
|
41
|
+
"branch": BranchParams.__annotations__.keys(),
|
|
42
|
+
"check": CheckParams.__annotations__.keys(),
|
|
43
|
+
"name": NameParams.__annotations__.keys(),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
BASE_URL = "https://shindanmaker.com"
|
|
47
|
+
|
|
48
|
+
HEADERS = {
|
|
49
|
+
"User-Agent": (
|
|
50
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36(KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
|
|
51
|
+
),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
__all__ = (
|
|
55
|
+
"BASE_URL",
|
|
56
|
+
"HEADERS",
|
|
57
|
+
"TARGET_KEYS_BY_TYPE",
|
|
58
|
+
)
|
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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
|
|
|
@@ -50,10 +62,11 @@ def __get_result(
|
|
|
50
62
|
|
|
51
63
|
def get_result_by_ai(
|
|
52
64
|
session: Session,
|
|
53
|
-
params:
|
|
65
|
+
params: AIParams,
|
|
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
|
-
|
|
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",
|
|
@@ -98,8 +91,9 @@ def get_result_by_ai(
|
|
|
98
91
|
"form_values": user_inputs,
|
|
99
92
|
"shindan_token": params["_token"],
|
|
100
93
|
"ai_result_request_times": 0,
|
|
94
|
+
"encrypted_exec_key": params["encrypted_exec_key"],
|
|
101
95
|
},
|
|
102
|
-
headers=
|
|
96
|
+
headers=ai_headers,
|
|
103
97
|
)
|
|
104
98
|
gpt_results = "".join(
|
|
105
99
|
re.findall(r'"content":"([^"]+)', result_sse.text),
|
|
@@ -113,7 +107,7 @@ def get_result_by_ai(
|
|
|
113
107
|
|
|
114
108
|
def get_result_by_branch(
|
|
115
109
|
session: Session,
|
|
116
|
-
params:
|
|
110
|
+
params: BranchParams,
|
|
117
111
|
*,
|
|
118
112
|
shindan_url: str,
|
|
119
113
|
) -> ShindanResult:
|
|
@@ -133,7 +127,7 @@ def get_result_by_branch(
|
|
|
133
127
|
|
|
134
128
|
def get_result_by_check(
|
|
135
129
|
session: Session,
|
|
136
|
-
params:
|
|
130
|
+
params: CheckParams,
|
|
137
131
|
*,
|
|
138
132
|
user_choices: dict[int, str],
|
|
139
133
|
shindan_url: str,
|
|
@@ -150,8 +144,9 @@ def get_result_by_check(
|
|
|
150
144
|
ShindanResult: the returned result from <https://shindanmaker.com>
|
|
151
145
|
|
|
152
146
|
"""
|
|
147
|
+
check_params = cast("dict[str, str]", params)
|
|
153
148
|
for choice_id, answer_id in user_choices.items():
|
|
154
|
-
|
|
149
|
+
check_params[f"input-check-choice[{choice_id}]"] = answer_id
|
|
155
150
|
|
|
156
151
|
return __get_result(session, params, is_renewal=True, shindan_url=shindan_url)
|
|
157
152
|
|
|
@@ -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 QuestionBranch, QuestionBranchChoice, QuestionChoice, QuestionChoiceChoice, UserInput
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from bs4 import BeautifulSoup
|
|
9
11
|
|
|
10
|
-
from .models import
|
|
12
|
+
from .models import UserInputs
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
def get_user_inputs(
|
|
@@ -25,22 +27,19 @@ def get_user_inputs(
|
|
|
25
27
|
|
|
26
28
|
"""
|
|
27
29
|
user_inputs: dict[str, UserInput] = {}
|
|
28
|
-
form_labels = source.select("form#shindanForm
|
|
30
|
+
form_labels = source.select("form#shindanForm label")
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
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) # noqa: S101
|
|
56
59
|
|
|
57
60
|
shindan_page = session.get(shindan_url, headers=HEADERS)
|
|
58
|
-
if shindan_page.status_code !=
|
|
61
|
+
if shindan_page.status_code != 200: # noqa: PLR2004
|
|
59
62
|
raise ShindanError(shindan_page.status_code)
|
|
60
63
|
|
|
61
64
|
source = BeautifulSoup(shindan_page.text, features="lxml")
|
|
@@ -65,46 +68,54 @@ 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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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"),
|
|
83
89
|
str,
|
|
84
90
|
):
|
|
85
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)
|
|
86
96
|
return get_result_by_ai(
|
|
87
97
|
session,
|
|
88
98
|
params,
|
|
89
99
|
user_inputs=get_user_inputs(source, shindan_name),
|
|
90
100
|
hashtag=hashtag,
|
|
101
|
+
csrf_token=csrf_token,
|
|
91
102
|
shindan_url=shindan_url,
|
|
92
103
|
)
|
|
93
|
-
if
|
|
104
|
+
if params["type"] == "branch":
|
|
94
105
|
params["rbr"] = get_rbr(source)
|
|
95
106
|
return get_result_by_branch(
|
|
96
107
|
session,
|
|
97
108
|
params,
|
|
98
109
|
shindan_url=shindan_url,
|
|
99
110
|
)
|
|
100
|
-
if
|
|
111
|
+
if params["type"] == "check":
|
|
101
112
|
return get_result_by_check(
|
|
102
113
|
session,
|
|
103
114
|
params,
|
|
104
115
|
user_choices=get_choices(source),
|
|
105
116
|
shindan_url=shindan_url,
|
|
106
117
|
)
|
|
107
|
-
if
|
|
118
|
+
if params["type"] == "name":
|
|
108
119
|
return get_result_by_name(
|
|
109
120
|
session,
|
|
110
121
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|