mikancli 0.1.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.
- mikancli-0.1.0/PKG-INFO +198 -0
- mikancli-0.1.0/README.md +175 -0
- mikancli-0.1.0/mikancli/__init__.py +7 -0
- mikancli-0.1.0/mikancli/__main__.py +5 -0
- mikancli-0.1.0/mikancli/cli/__init__.py +3 -0
- mikancli-0.1.0/mikancli/cli/entrypoint.py +215 -0
- mikancli-0.1.0/mikancli/cli/input_parsing.py +40 -0
- mikancli-0.1.0/mikancli/cli/prompts.py +145 -0
- mikancli-0.1.0/mikancli/cli/qbittorrent_flow.py +169 -0
- mikancli-0.1.0/mikancli/cli/save_path_flow.py +122 -0
- mikancli-0.1.0/mikancli/cli/search_flow.py +203 -0
- mikancli-0.1.0/mikancli/config.py +160 -0
- mikancli-0.1.0/mikancli/core/__init__.py +1 -0
- mikancli-0.1.0/mikancli/core/models.py +74 -0
- mikancli-0.1.0/mikancli/core/normalize.py +32 -0
- mikancli-0.1.0/mikancli/core/rules.py +52 -0
- mikancli-0.1.0/mikancli/display.py +89 -0
- mikancli-0.1.0/mikancli/integrations/__init__.py +1 -0
- mikancli-0.1.0/mikancli/integrations/mikan.py +72 -0
- mikancli-0.1.0/mikancli/integrations/mikan_parsers.py +282 -0
- mikancli-0.1.0/mikancli/integrations/mikan_urls.py +35 -0
- mikancli-0.1.0/mikancli/integrations/qbittorrent.py +156 -0
- mikancli-0.1.0/mikancli/integrations/qbittorrent_client.py +243 -0
- mikancli-0.1.0/mikancli.egg-info/PKG-INFO +198 -0
- mikancli-0.1.0/mikancli.egg-info/SOURCES.txt +36 -0
- mikancli-0.1.0/mikancli.egg-info/dependency_links.txt +1 -0
- mikancli-0.1.0/mikancli.egg-info/entry_points.txt +2 -0
- mikancli-0.1.0/mikancli.egg-info/requires.txt +1 -0
- mikancli-0.1.0/mikancli.egg-info/top_level.txt +1 -0
- mikancli-0.1.0/pyproject.toml +40 -0
- mikancli-0.1.0/setup.cfg +4 -0
- mikancli-0.1.0/tests/test_cli.py +739 -0
- mikancli-0.1.0/tests/test_config.py +279 -0
- mikancli-0.1.0/tests/test_mikan.py +143 -0
- mikancli-0.1.0/tests/test_normalize.py +15 -0
- mikancli-0.1.0/tests/test_prompts.py +221 -0
- mikancli-0.1.0/tests/test_qbittorrent.py +446 -0
- mikancli-0.1.0/tests/test_rules.py +44 -0
mikancli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mikancli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI foundation for building qBittorrent RSS rules from anime search input.
|
|
5
|
+
Author: metails
|
|
6
|
+
Project-URL: Repository, https://github.com/JianHua-Deng/MikanCli
|
|
7
|
+
Project-URL: Issues, https://github.com/JianHua-Deng/MikanCli/issues
|
|
8
|
+
Keywords: anime,rss,qbittorrent,cli,mikan
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: InquirerPy>=0.3
|
|
23
|
+
|
|
24
|
+
# MikanCli
|
|
25
|
+
|
|
26
|
+
MikanCli is a Python CLI for turning an anime search keyword into a qBittorrent RSS feed and download-rule setup workflow.
|
|
27
|
+
|
|
28
|
+
## Current increment
|
|
29
|
+
|
|
30
|
+
The repository currently contains the first small slice of the project:
|
|
31
|
+
|
|
32
|
+
- a Python CLI scaffold
|
|
33
|
+
- keyword normalization
|
|
34
|
+
- Mikan Bangumi search, subgroup discovery, and subgroup RSS feed resolution
|
|
35
|
+
- draft qBittorrent rule construction and interactive qBittorrent submission
|
|
36
|
+
- focused tests for the pure logic
|
|
37
|
+
|
|
38
|
+
This increment now searches Mikan, lets you select the Bangumi and subgroup,
|
|
39
|
+
resolves the subgroup-specific RSS feed, and prints that feed alongside the
|
|
40
|
+
draft rule details. In interactive search mode, MikanCli can now submit the
|
|
41
|
+
RSS feed and auto-download rule to qBittorrent after you confirm the draft.
|
|
42
|
+
After submission, it reads qBittorrent back to verify that the feed and rule
|
|
43
|
+
were created. qBittorrent WebUI setup verification is also available.
|
|
44
|
+
|
|
45
|
+
Internally, the project is split by responsibility:
|
|
46
|
+
|
|
47
|
+
- `mikancli/cli/` contains CLI entrypoint, prompts, and interactive navigation
|
|
48
|
+
- `mikancli/core/` contains models, normalization helpers, and rule-building logic
|
|
49
|
+
- `mikancli/integrations/` contains external service adapters such as Mikan
|
|
50
|
+
- `config.py` and `display.py` stay at the package root as shared support modules
|
|
51
|
+
|
|
52
|
+
## Install from source
|
|
53
|
+
|
|
54
|
+
Clone the repository, then install it from the project folder:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone <repo-url>
|
|
58
|
+
cd MikanCli
|
|
59
|
+
python -m pip install -e .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This installs MikanCli in editable mode, installs the dependencies declared in
|
|
63
|
+
`pyproject.toml`, and registers the `mikancli` command. After that, you can run
|
|
64
|
+
MikanCli from any terminal location:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
mikancli
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
You can also run the module directly from the project folder after installing
|
|
71
|
+
the local environment:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python -m mikancli
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Dependencies are installed by `pip` when the package is installed. MikanCli does
|
|
78
|
+
not install packages at runtime.
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
Search directly from the command line:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
mikancli "solo leveling" --include HEVC --exclude 720p
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
You can also run it with no extra arguments and let the script guide you:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
mikancli
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
To set up qBittorrent WebUI access for future increments:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
mikancli --setup-qbittorrent
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
When you run `mikancli` with no extra arguments, the first menu now lets you choose between:
|
|
101
|
+
|
|
102
|
+
- `Search anime`
|
|
103
|
+
- `Modify qBittorrent configurations`
|
|
104
|
+
|
|
105
|
+
If you choose `Search anime` and qBittorrent is not configured yet, MikanCli can still guide you into qBittorrent setup before continuing.
|
|
106
|
+
|
|
107
|
+
If `--save-path` is omitted, MikanCli first checks for a saved default in the
|
|
108
|
+
user config file. On Windows, the default config location is
|
|
109
|
+
`%APPDATA%\MikanCli\config.json`.
|
|
110
|
+
In a normal interactive run, the guided prompts now use `InquirerPy`, so list selections stay in place instead of printing a new block of text on every key press.
|
|
111
|
+
Every interactive menu now includes an `Exit MikanCli` option, and text prompts accept `exit` or `quit` to stop the tool cleanly.
|
|
112
|
+
The first search prompt now says that explicitly, so the quit path is visible before any lookup starts.
|
|
113
|
+
|
|
114
|
+
- use the saved default folder
|
|
115
|
+
- use the system Downloads folder
|
|
116
|
+
- browse for a folder
|
|
117
|
+
- type a folder path manually
|
|
118
|
+
|
|
119
|
+
If the chosen folder is not already the saved default, MikanCli asks whether it should be saved for future runs.
|
|
120
|
+
|
|
121
|
+
Current guided flow:
|
|
122
|
+
|
|
123
|
+
- ask for anime keyword if you did not type one
|
|
124
|
+
- search Mikan for matching Bangumi entries
|
|
125
|
+
- let you choose the correct Mikan entry when more than one match is found
|
|
126
|
+
- fetch subgroup entries from the selected Bangumi page
|
|
127
|
+
- let you choose the correct subgroup RSS feed when more than one subgroup is found
|
|
128
|
+
- show the subgroup RSS contents before confirmation
|
|
129
|
+
- let you confirm, go back to subgroup selection, or search again
|
|
130
|
+
- optionally ask for include words
|
|
131
|
+
- optionally ask for exclude words
|
|
132
|
+
- let you choose the base download folder from a menu
|
|
133
|
+
- ask for the content folder name inside that base folder, defaulting to the selected Bangumi title
|
|
134
|
+
- allow quitting cleanly from any interactive menu or prompt
|
|
135
|
+
- print the resolved Mikan page URL, subgroup, and subgroup RSS feed URL with the draft rule summary
|
|
136
|
+
- ask whether to submit the RSS feed and download rule to qBittorrent when WebUI access is configured
|
|
137
|
+
- verify submitted qBittorrent feeds and rules by reading them back from the WebUI API
|
|
138
|
+
|
|
139
|
+
## qBittorrent setup
|
|
140
|
+
|
|
141
|
+
Before MikanCli can talk to qBittorrent, do a one-time setup inside qBittorrent:
|
|
142
|
+
|
|
143
|
+
1. Open qBittorrent settings.
|
|
144
|
+
2. Enable WebUI / remote control.
|
|
145
|
+
3. Check the WebUI address, username, and password there.
|
|
146
|
+
|
|
147
|
+
Then run:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
mikancli --setup-qbittorrent
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Setup notes:
|
|
154
|
+
|
|
155
|
+
- if you press Enter for the URL, MikanCli uses `http://localhost:8080`
|
|
156
|
+
- you can enter `localhost:8080` and MikanCli will normalize it automatically
|
|
157
|
+
- username and password may be left blank for the first test if your local qBittorrent allows it
|
|
158
|
+
|
|
159
|
+
Troubleshooting:
|
|
160
|
+
|
|
161
|
+
- if MikanCli says it could not reach qBittorrent, WebUI may be disabled or using a different port
|
|
162
|
+
- if MikanCli says the credentials were rejected, re-check the WebUI username/password in qBittorrent settings
|
|
163
|
+
|
|
164
|
+
## Packaging and release
|
|
165
|
+
|
|
166
|
+
MikanCli is structured as an installable Python CLI package. The console command
|
|
167
|
+
is declared in `pyproject.toml`:
|
|
168
|
+
|
|
169
|
+
```toml
|
|
170
|
+
[project.scripts]
|
|
171
|
+
mikancli = "mikancli.cli.entrypoint:main"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Current local install flow:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
python -m pip install -e .
|
|
178
|
+
mikancli
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Planned public install flow after publishing to PyPI:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
python -m pip install mikancli
|
|
185
|
+
mikancli
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Recommended CLI-app install flow after publishing:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
pipx install mikancli
|
|
192
|
+
mikancli
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Remaining packaging work:
|
|
196
|
+
|
|
197
|
+
- add a release workflow for building and publishing distributions
|
|
198
|
+
- later consider a Windows executable for non-technical users
|
mikancli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# MikanCli
|
|
2
|
+
|
|
3
|
+
MikanCli is a Python CLI for turning an anime search keyword into a qBittorrent RSS feed and download-rule setup workflow.
|
|
4
|
+
|
|
5
|
+
## Current increment
|
|
6
|
+
|
|
7
|
+
The repository currently contains the first small slice of the project:
|
|
8
|
+
|
|
9
|
+
- a Python CLI scaffold
|
|
10
|
+
- keyword normalization
|
|
11
|
+
- Mikan Bangumi search, subgroup discovery, and subgroup RSS feed resolution
|
|
12
|
+
- draft qBittorrent rule construction and interactive qBittorrent submission
|
|
13
|
+
- focused tests for the pure logic
|
|
14
|
+
|
|
15
|
+
This increment now searches Mikan, lets you select the Bangumi and subgroup,
|
|
16
|
+
resolves the subgroup-specific RSS feed, and prints that feed alongside the
|
|
17
|
+
draft rule details. In interactive search mode, MikanCli can now submit the
|
|
18
|
+
RSS feed and auto-download rule to qBittorrent after you confirm the draft.
|
|
19
|
+
After submission, it reads qBittorrent back to verify that the feed and rule
|
|
20
|
+
were created. qBittorrent WebUI setup verification is also available.
|
|
21
|
+
|
|
22
|
+
Internally, the project is split by responsibility:
|
|
23
|
+
|
|
24
|
+
- `mikancli/cli/` contains CLI entrypoint, prompts, and interactive navigation
|
|
25
|
+
- `mikancli/core/` contains models, normalization helpers, and rule-building logic
|
|
26
|
+
- `mikancli/integrations/` contains external service adapters such as Mikan
|
|
27
|
+
- `config.py` and `display.py` stay at the package root as shared support modules
|
|
28
|
+
|
|
29
|
+
## Install from source
|
|
30
|
+
|
|
31
|
+
Clone the repository, then install it from the project folder:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone <repo-url>
|
|
35
|
+
cd MikanCli
|
|
36
|
+
python -m pip install -e .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This installs MikanCli in editable mode, installs the dependencies declared in
|
|
40
|
+
`pyproject.toml`, and registers the `mikancli` command. After that, you can run
|
|
41
|
+
MikanCli from any terminal location:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
mikancli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
You can also run the module directly from the project folder after installing
|
|
48
|
+
the local environment:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python -m mikancli
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Dependencies are installed by `pip` when the package is installed. MikanCli does
|
|
55
|
+
not install packages at runtime.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
Search directly from the command line:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
mikancli "solo leveling" --include HEVC --exclude 720p
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
You can also run it with no extra arguments and let the script guide you:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
mikancli
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
To set up qBittorrent WebUI access for future increments:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
mikancli --setup-qbittorrent
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
When you run `mikancli` with no extra arguments, the first menu now lets you choose between:
|
|
78
|
+
|
|
79
|
+
- `Search anime`
|
|
80
|
+
- `Modify qBittorrent configurations`
|
|
81
|
+
|
|
82
|
+
If you choose `Search anime` and qBittorrent is not configured yet, MikanCli can still guide you into qBittorrent setup before continuing.
|
|
83
|
+
|
|
84
|
+
If `--save-path` is omitted, MikanCli first checks for a saved default in the
|
|
85
|
+
user config file. On Windows, the default config location is
|
|
86
|
+
`%APPDATA%\MikanCli\config.json`.
|
|
87
|
+
In a normal interactive run, the guided prompts now use `InquirerPy`, so list selections stay in place instead of printing a new block of text on every key press.
|
|
88
|
+
Every interactive menu now includes an `Exit MikanCli` option, and text prompts accept `exit` or `quit` to stop the tool cleanly.
|
|
89
|
+
The first search prompt now says that explicitly, so the quit path is visible before any lookup starts.
|
|
90
|
+
|
|
91
|
+
- use the saved default folder
|
|
92
|
+
- use the system Downloads folder
|
|
93
|
+
- browse for a folder
|
|
94
|
+
- type a folder path manually
|
|
95
|
+
|
|
96
|
+
If the chosen folder is not already the saved default, MikanCli asks whether it should be saved for future runs.
|
|
97
|
+
|
|
98
|
+
Current guided flow:
|
|
99
|
+
|
|
100
|
+
- ask for anime keyword if you did not type one
|
|
101
|
+
- search Mikan for matching Bangumi entries
|
|
102
|
+
- let you choose the correct Mikan entry when more than one match is found
|
|
103
|
+
- fetch subgroup entries from the selected Bangumi page
|
|
104
|
+
- let you choose the correct subgroup RSS feed when more than one subgroup is found
|
|
105
|
+
- show the subgroup RSS contents before confirmation
|
|
106
|
+
- let you confirm, go back to subgroup selection, or search again
|
|
107
|
+
- optionally ask for include words
|
|
108
|
+
- optionally ask for exclude words
|
|
109
|
+
- let you choose the base download folder from a menu
|
|
110
|
+
- ask for the content folder name inside that base folder, defaulting to the selected Bangumi title
|
|
111
|
+
- allow quitting cleanly from any interactive menu or prompt
|
|
112
|
+
- print the resolved Mikan page URL, subgroup, and subgroup RSS feed URL with the draft rule summary
|
|
113
|
+
- ask whether to submit the RSS feed and download rule to qBittorrent when WebUI access is configured
|
|
114
|
+
- verify submitted qBittorrent feeds and rules by reading them back from the WebUI API
|
|
115
|
+
|
|
116
|
+
## qBittorrent setup
|
|
117
|
+
|
|
118
|
+
Before MikanCli can talk to qBittorrent, do a one-time setup inside qBittorrent:
|
|
119
|
+
|
|
120
|
+
1. Open qBittorrent settings.
|
|
121
|
+
2. Enable WebUI / remote control.
|
|
122
|
+
3. Check the WebUI address, username, and password there.
|
|
123
|
+
|
|
124
|
+
Then run:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
mikancli --setup-qbittorrent
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Setup notes:
|
|
131
|
+
|
|
132
|
+
- if you press Enter for the URL, MikanCli uses `http://localhost:8080`
|
|
133
|
+
- you can enter `localhost:8080` and MikanCli will normalize it automatically
|
|
134
|
+
- username and password may be left blank for the first test if your local qBittorrent allows it
|
|
135
|
+
|
|
136
|
+
Troubleshooting:
|
|
137
|
+
|
|
138
|
+
- if MikanCli says it could not reach qBittorrent, WebUI may be disabled or using a different port
|
|
139
|
+
- if MikanCli says the credentials were rejected, re-check the WebUI username/password in qBittorrent settings
|
|
140
|
+
|
|
141
|
+
## Packaging and release
|
|
142
|
+
|
|
143
|
+
MikanCli is structured as an installable Python CLI package. The console command
|
|
144
|
+
is declared in `pyproject.toml`:
|
|
145
|
+
|
|
146
|
+
```toml
|
|
147
|
+
[project.scripts]
|
|
148
|
+
mikancli = "mikancli.cli.entrypoint:main"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Current local install flow:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
python -m pip install -e .
|
|
155
|
+
mikancli
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Planned public install flow after publishing to PyPI:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
python -m pip install mikancli
|
|
162
|
+
mikancli
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Recommended CLI-app install flow after publishing:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pipx install mikancli
|
|
169
|
+
mikancli
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Remaining packaging work:
|
|
173
|
+
|
|
174
|
+
- add a release workflow for building and publishing distributions
|
|
175
|
+
- later consider a Windows executable for non-technical users
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from mikancli.cli.input_parsing import prompt_word_list
|
|
8
|
+
from mikancli.cli.prompts import ExitRequested, select_option
|
|
9
|
+
from mikancli.cli.qbittorrent_flow import (
|
|
10
|
+
QBITTORRENT_SUBMISSION_SKIPPED,
|
|
11
|
+
prompt_for_qbittorrent_setup_if_needed,
|
|
12
|
+
prompt_to_submit_rule_to_qbittorrent,
|
|
13
|
+
run_qbittorrent_configuration_flow,
|
|
14
|
+
setup_qbittorrent,
|
|
15
|
+
)
|
|
16
|
+
from mikancli.cli.save_path_flow import (
|
|
17
|
+
build_content_save_path,
|
|
18
|
+
prompt_for_content_folder_name,
|
|
19
|
+
resolve_save_path,
|
|
20
|
+
)
|
|
21
|
+
from mikancli.cli.search_flow import resolve_mikan_selection, run_interactive_selection
|
|
22
|
+
from mikancli.config import get_config_path, load_config
|
|
23
|
+
from mikancli.core.models import AppConfig, RuleDraft, SearchRequest
|
|
24
|
+
from mikancli.core.normalize import collapse_spaces
|
|
25
|
+
from mikancli.core.rules import build_rule_draft
|
|
26
|
+
from mikancli.display import print_text_summary
|
|
27
|
+
|
|
28
|
+
STARTUP_ACTION_SEARCH = "search"
|
|
29
|
+
STARTUP_ACTION_QBITTORRENT = "qbittorrent"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
33
|
+
"""Create the command-line parser for MikanCli flags and positional arguments. Returns a configured ArgumentParser; it does not parse arguments by itself."""
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
prog="mikancli",
|
|
36
|
+
description=(
|
|
37
|
+
"Search Mikan for an anime, inspect subgroup RSS contents, and preview "
|
|
38
|
+
"or submit qBittorrent RSS rule inputs."
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument("keyword", nargs="?", help="Anime title or search phrase.")
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--include",
|
|
44
|
+
action="append",
|
|
45
|
+
default=[],
|
|
46
|
+
help="Word that must appear in accepted releases. Repeat for multiple values.",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--exclude",
|
|
50
|
+
action="append",
|
|
51
|
+
default=[],
|
|
52
|
+
help="Word that must not appear in accepted releases. Repeat for multiple values.",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--save-path",
|
|
56
|
+
help="Optional save path to attach to the qBittorrent rule.",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--json",
|
|
60
|
+
action="store_true",
|
|
61
|
+
help="Print the draft as JSON.",
|
|
62
|
+
)
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--setup-qbittorrent",
|
|
65
|
+
action="store_true",
|
|
66
|
+
help="Configure and verify qBittorrent WebUI access.",
|
|
67
|
+
)
|
|
68
|
+
return parser
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _prompt_startup_action() -> str:
|
|
72
|
+
return select_option(
|
|
73
|
+
"Choose what you want to do",
|
|
74
|
+
[
|
|
75
|
+
(STARTUP_ACTION_SEARCH, "Search anime"),
|
|
76
|
+
(STARTUP_ACTION_QBITTORRENT, "Modify qBittorrent configurations"),
|
|
77
|
+
],
|
|
78
|
+
default=STARTUP_ACTION_SEARCH,
|
|
79
|
+
allow_exit=True,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def build_request_from_args(args: argparse.Namespace, *, config: AppConfig, config_path: Path) -> SearchRequest:
|
|
84
|
+
"""Convert parsed JSON-mode CLI arguments into a SearchRequest. Returns the cleaned request or raises ValueError when the required keyword is missing."""
|
|
85
|
+
|
|
86
|
+
if not args.keyword:
|
|
87
|
+
raise ValueError("keyword is required when using --json")
|
|
88
|
+
|
|
89
|
+
save_path = resolve_save_path(
|
|
90
|
+
args.save_path,
|
|
91
|
+
config,
|
|
92
|
+
prompt_for_default=False,
|
|
93
|
+
config_path=config_path,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return SearchRequest(
|
|
97
|
+
keyword=collapse_spaces(args.keyword),
|
|
98
|
+
include_words=tuple(args.include),
|
|
99
|
+
exclude_words=tuple(args.exclude),
|
|
100
|
+
save_path=save_path,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _build_interactive_draft(args: argparse.Namespace, *, config: AppConfig,config_path: Path) -> RuleDraft:
|
|
105
|
+
|
|
106
|
+
bangumi, subgroup = run_interactive_selection(initial_keyword=args.keyword)
|
|
107
|
+
|
|
108
|
+
include_words = tuple(args.include) or prompt_word_list(
|
|
109
|
+
"Enter include words separated by commas, or press Enter to skip: "
|
|
110
|
+
)
|
|
111
|
+
exclude_words = tuple(args.exclude) or prompt_word_list(
|
|
112
|
+
"Enter exclude words separated by commas, or press Enter to skip: "
|
|
113
|
+
)
|
|
114
|
+
save_path = resolve_save_path(
|
|
115
|
+
args.save_path,
|
|
116
|
+
config,
|
|
117
|
+
prompt_for_default=True,
|
|
118
|
+
config_path=config_path,
|
|
119
|
+
)
|
|
120
|
+
content_folder_name = prompt_for_content_folder_name(bangumi.title)
|
|
121
|
+
final_save_path = build_content_save_path(save_path, content_folder_name)
|
|
122
|
+
|
|
123
|
+
request = SearchRequest(
|
|
124
|
+
keyword=bangumi.title,
|
|
125
|
+
include_words=include_words,
|
|
126
|
+
exclude_words=exclude_words,
|
|
127
|
+
save_path=final_save_path,
|
|
128
|
+
)
|
|
129
|
+
return build_rule_draft(
|
|
130
|
+
request,
|
|
131
|
+
bangumi=bangumi,
|
|
132
|
+
subgroup=subgroup,
|
|
133
|
+
notes=("Review the draft before submitting it to qBittorrent.",),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def main(argv: list[str] | None = None) -> int:
|
|
138
|
+
|
|
139
|
+
parser = build_parser()
|
|
140
|
+
args = parser.parse_args(argv)
|
|
141
|
+
config_path = get_config_path()
|
|
142
|
+
config = load_config(config_path)
|
|
143
|
+
|
|
144
|
+
if args.setup_qbittorrent:
|
|
145
|
+
try:
|
|
146
|
+
return setup_qbittorrent(config, config_path)
|
|
147
|
+
except ExitRequested:
|
|
148
|
+
print("Exited MikanCli.")
|
|
149
|
+
return 0
|
|
150
|
+
|
|
151
|
+
if args.json:
|
|
152
|
+
try:
|
|
153
|
+
request = build_request_from_args(
|
|
154
|
+
args,
|
|
155
|
+
config=config,
|
|
156
|
+
config_path=config_path,
|
|
157
|
+
)
|
|
158
|
+
except ValueError as exc:
|
|
159
|
+
parser.error(str(exc))
|
|
160
|
+
|
|
161
|
+
bangumi, subgroup, lookup_notes = resolve_mikan_selection(request)
|
|
162
|
+
draft = build_rule_draft(
|
|
163
|
+
request,
|
|
164
|
+
bangumi=bangumi,
|
|
165
|
+
subgroup=subgroup,
|
|
166
|
+
notes=lookup_notes,
|
|
167
|
+
)
|
|
168
|
+
print(json.dumps(draft.to_dict(), ensure_ascii=False, indent=2))
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
has_startup_menu = args.keyword is None
|
|
172
|
+
|
|
173
|
+
while True:
|
|
174
|
+
try:
|
|
175
|
+
if has_startup_menu:
|
|
176
|
+
while True:
|
|
177
|
+
startup_action = _prompt_startup_action()
|
|
178
|
+
if startup_action == STARTUP_ACTION_QBITTORRENT:
|
|
179
|
+
setup_exit_code = run_qbittorrent_configuration_flow(config, config_path)
|
|
180
|
+
if setup_exit_code != 0:
|
|
181
|
+
return setup_exit_code
|
|
182
|
+
config = load_config(config_path)
|
|
183
|
+
continue
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
setup_exit_code = prompt_for_qbittorrent_setup_if_needed(
|
|
187
|
+
config,
|
|
188
|
+
config_path,
|
|
189
|
+
)
|
|
190
|
+
if setup_exit_code != 0:
|
|
191
|
+
return setup_exit_code
|
|
192
|
+
config = load_config(config_path)
|
|
193
|
+
draft = _build_interactive_draft(
|
|
194
|
+
args,
|
|
195
|
+
config=config,
|
|
196
|
+
config_path=config_path,
|
|
197
|
+
)
|
|
198
|
+
except ExitRequested:
|
|
199
|
+
print("Exited MikanCli.")
|
|
200
|
+
return 0
|
|
201
|
+
|
|
202
|
+
summary_exit_code = print_text_summary(draft)
|
|
203
|
+
if summary_exit_code != 0:
|
|
204
|
+
return summary_exit_code
|
|
205
|
+
try:
|
|
206
|
+
submission_exit_code = prompt_to_submit_rule_to_qbittorrent(config, draft)
|
|
207
|
+
except ExitRequested:
|
|
208
|
+
print("Exited MikanCli.")
|
|
209
|
+
return 0
|
|
210
|
+
|
|
211
|
+
if has_startup_menu and submission_exit_code == QBITTORRENT_SUBMISSION_SKIPPED:
|
|
212
|
+
config = load_config(config_path)
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
return 0 if submission_exit_code == QBITTORRENT_SUBMISSION_SKIPPED else submission_exit_code
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from mikancli.cli.prompts import prompt_text
|
|
4
|
+
from mikancli.core.normalize import collapse_spaces
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def prompt_required_text(prompt: str) -> str:
|
|
8
|
+
while True:
|
|
9
|
+
entered = collapse_spaces(prompt_text(prompt, allow_exit=True))
|
|
10
|
+
if entered:
|
|
11
|
+
return entered
|
|
12
|
+
print("A value is required.")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_word_list(value: str) -> tuple[str, ...]:
|
|
16
|
+
"""Parse a comma-separated word list while dropping blanks and duplicates. Example: parse_word_list("HEVC, 1080p, HEVC") returns ("HEVC", "1080p")."""
|
|
17
|
+
words: list[str] = []
|
|
18
|
+
seen: set[str] = set()
|
|
19
|
+
|
|
20
|
+
for raw_part in value.split(","):
|
|
21
|
+
cleaned = collapse_spaces(raw_part)
|
|
22
|
+
if not cleaned:
|
|
23
|
+
continue
|
|
24
|
+
|
|
25
|
+
marker = cleaned.casefold()
|
|
26
|
+
if marker in seen:
|
|
27
|
+
continue
|
|
28
|
+
|
|
29
|
+
seen.add(marker)
|
|
30
|
+
words.append(cleaned)
|
|
31
|
+
|
|
32
|
+
return tuple(words)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def prompt_word_list(prompt: str) -> tuple[str, ...]:
|
|
36
|
+
entered = collapse_spaces(prompt_text(prompt, allow_exit=True))
|
|
37
|
+
if not entered:
|
|
38
|
+
return ()
|
|
39
|
+
|
|
40
|
+
return parse_word_list(entered)
|