anipy-cli 3.1.9__tar.gz → 3.1.11__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.
Potentially problematic release.
This version of anipy-cli might be problematic. Click here for more details.
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/PKG-INFO +2 -2
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/pyproject.toml +2 -2
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/__init__.py +1 -1
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/arg_parser.py +12 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/clis/binge_cli.py +11 -1
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/clis/default_cli.py +11 -1
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/clis/download_cli.py +11 -1
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/config.py +23 -15
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/prompts.py +123 -38
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/util.py +19 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/README.md +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/cli.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/clis/__init__.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/clis/base_cli.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/clis/history_cli.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/clis/mal_cli.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/clis/seasonal_cli.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/colors.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/discord.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/mal_proxy.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/menus/__init__.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/menus/base_menu.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/menus/mal_menu.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/menus/menu.py +0 -0
- {anipy_cli-3.1.9 → anipy_cli-3.1.11}/src/anipy_cli/menus/seasonal_menu.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anipy-cli
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.11
|
|
4
4
|
Summary: Watch and Download anime from the comfort of your Terminal
|
|
5
5
|
Home-page: https://sdaqo.github.io/anipy-cli
|
|
6
6
|
License: GPL-3.0
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Requires-Dist: anipy-api (>=3.1.
|
|
17
|
+
Requires-Dist: anipy-api (>=3.1.11,<4.0.0)
|
|
18
18
|
Requires-Dist: appdirs (>=1.4.4,<2.0.0)
|
|
19
19
|
Requires-Dist: inquirerpy (>=0.3.4,<0.4.0)
|
|
20
20
|
Requires-Dist: pypresence (>=4.3.0,<5.0.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "anipy-cli"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.11"
|
|
4
4
|
description = "Watch and Download anime from the comfort of your Terminal"
|
|
5
5
|
authors = ["sdaqo <sdaqo.dev@protonmail.com>"]
|
|
6
6
|
license = "GPL-3.0"
|
|
@@ -20,7 +20,7 @@ yaspin = "^3.0.2"
|
|
|
20
20
|
inquirerpy = "^0.3.4"
|
|
21
21
|
appdirs = "^1.4.4"
|
|
22
22
|
pypresence = "^4.3.0"
|
|
23
|
-
anipy-api = "^3.1.
|
|
23
|
+
anipy-api = "^3.1.11"
|
|
24
24
|
|
|
25
25
|
[tool.poetry.scripts]
|
|
26
26
|
anipy-cli = "anipy_cli.cli:run_cli"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__appname__ = "anipy-cli"
|
|
2
|
-
__version__ = "3.1.
|
|
2
|
+
__version__ = "3.1.11"
|
|
@@ -23,6 +23,7 @@ class CliArgs:
|
|
|
23
23
|
location: Optional[Path]
|
|
24
24
|
mal_password: Optional[str]
|
|
25
25
|
config: bool
|
|
26
|
+
seasonal_search: Optional[str]
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def parse_args(override_args: Optional[list[str]] = None) -> CliArgs:
|
|
@@ -107,6 +108,17 @@ def parse_args(override_args: Optional[list[str]] = None) -> CliArgs:
|
|
|
107
108
|
help="Provide a search term to Default, Download or Binge mode in this format: {query}:{episode range}:{dub/sub}. Examples: 'frieren:1-10:sub' or 'frieren:1:sub' or 'frieren:1-3 7-12:dub', this argument may be appended to any of the modes mentioned like so: 'anipy-cli (-D/B) -s <search>'",
|
|
108
109
|
)
|
|
109
110
|
|
|
111
|
+
options_group.add_argument(
|
|
112
|
+
"-ss",
|
|
113
|
+
"--seasonal-search",
|
|
114
|
+
required=False,
|
|
115
|
+
dest="seasonal_search",
|
|
116
|
+
nargs="?", # 1 or none possible args
|
|
117
|
+
default=None, # Used if flag is not present (added this line for clarity, because default is always None)
|
|
118
|
+
const=True, # Used if flag is present, but no value
|
|
119
|
+
help="Provide search parameters for seasons to Default, Download, or Binge mode in this format: {year}:{season}. You can only use part of the season name if you wish. Examples: '2024:win' or '2020:fa'",
|
|
120
|
+
)
|
|
121
|
+
|
|
110
122
|
options_group.add_argument(
|
|
111
123
|
"-q",
|
|
112
124
|
"--quality",
|
|
@@ -7,6 +7,7 @@ from anipy_cli.clis.base_cli import CliBase
|
|
|
7
7
|
from anipy_cli.colors import colors, cprint
|
|
8
8
|
from anipy_cli.config import Config
|
|
9
9
|
from anipy_cli.prompts import (
|
|
10
|
+
parse_seasonal_search,
|
|
10
11
|
pick_episode_range_prompt,
|
|
11
12
|
search_show_prompt,
|
|
12
13
|
lang_prompt,
|
|
@@ -34,6 +35,15 @@ class BingeCli(CliBase):
|
|
|
34
35
|
def print_header(self):
|
|
35
36
|
cprint(colors.GREEN, "***Binge Mode***")
|
|
36
37
|
|
|
38
|
+
def _get_anime_from_user(self):
|
|
39
|
+
if (ss := self.options.seasonal_search) is not None:
|
|
40
|
+
return parse_seasonal_search(
|
|
41
|
+
"binge",
|
|
42
|
+
ss,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return search_show_prompt("binge")
|
|
46
|
+
|
|
37
47
|
def take_input(self):
|
|
38
48
|
if self.options.search is not None:
|
|
39
49
|
self.anime, self.lang, self.episodes = parse_auto_search(
|
|
@@ -41,7 +51,7 @@ class BingeCli(CliBase):
|
|
|
41
51
|
)
|
|
42
52
|
return
|
|
43
53
|
|
|
44
|
-
anime =
|
|
54
|
+
anime = self._get_anime_from_user()
|
|
45
55
|
|
|
46
56
|
if anime is None:
|
|
47
57
|
sys.exit(0)
|
|
@@ -11,6 +11,7 @@ from anipy_cli.prompts import (
|
|
|
11
11
|
search_show_prompt,
|
|
12
12
|
lang_prompt,
|
|
13
13
|
parse_auto_search,
|
|
14
|
+
parse_seasonal_search,
|
|
14
15
|
)
|
|
15
16
|
from anipy_cli.util import (
|
|
16
17
|
DotSpinner,
|
|
@@ -42,6 +43,15 @@ class DefaultCli(CliBase):
|
|
|
42
43
|
def print_header(self):
|
|
43
44
|
pass
|
|
44
45
|
|
|
46
|
+
def _get_anime_from_user(self):
|
|
47
|
+
if (ss := self.options.seasonal_search) is not None:
|
|
48
|
+
return parse_seasonal_search(
|
|
49
|
+
"default",
|
|
50
|
+
ss,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return search_show_prompt("default")
|
|
54
|
+
|
|
45
55
|
def take_input(self):
|
|
46
56
|
if self.options.search is not None:
|
|
47
57
|
self.anime, self.lang, episodes = parse_auto_search(
|
|
@@ -50,7 +60,7 @@ class DefaultCli(CliBase):
|
|
|
50
60
|
self.epsiode = episodes[0]
|
|
51
61
|
return
|
|
52
62
|
|
|
53
|
-
anime =
|
|
63
|
+
anime = self._get_anime_from_user()
|
|
54
64
|
|
|
55
65
|
if anime is None:
|
|
56
66
|
return False
|
|
@@ -6,6 +6,7 @@ from anipy_cli.clis.base_cli import CliBase
|
|
|
6
6
|
from anipy_cli.colors import colors, cprint
|
|
7
7
|
from anipy_cli.config import Config
|
|
8
8
|
from anipy_cli.prompts import (
|
|
9
|
+
parse_seasonal_search,
|
|
9
10
|
pick_episode_range_prompt,
|
|
10
11
|
search_show_prompt,
|
|
11
12
|
lang_prompt,
|
|
@@ -39,6 +40,15 @@ class DownloadCli(CliBase):
|
|
|
39
40
|
cprint(colors.GREEN, "***Download Mode***")
|
|
40
41
|
cprint(colors.GREEN, "Downloads are stored in: ", colors.END, str(self.dl_path))
|
|
41
42
|
|
|
43
|
+
def _get_anime_from_user(self):
|
|
44
|
+
if (ss := self.options.seasonal_search) is not None:
|
|
45
|
+
return parse_seasonal_search(
|
|
46
|
+
"download",
|
|
47
|
+
ss,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return search_show_prompt("download")
|
|
51
|
+
|
|
42
52
|
def take_input(self):
|
|
43
53
|
if self.options.search is not None:
|
|
44
54
|
self.anime, self.lang, self.episodes = parse_auto_search(
|
|
@@ -46,7 +56,7 @@ class DownloadCli(CliBase):
|
|
|
46
56
|
)
|
|
47
57
|
return
|
|
48
58
|
|
|
49
|
-
anime =
|
|
59
|
+
anime = self._get_anime_from_user()
|
|
50
60
|
|
|
51
61
|
if anime is None:
|
|
52
62
|
return False
|
|
@@ -358,6 +358,12 @@ class Config:
|
|
|
358
358
|
"""If this is set to true you will not be prompted to search in season."""
|
|
359
359
|
return self._get_value("skip_season_search", False, bool)
|
|
360
360
|
|
|
361
|
+
@property
|
|
362
|
+
def assume_season_search(self) -> bool:
|
|
363
|
+
"""If this is set to true, the system will assume you want to search in season.
|
|
364
|
+
If skip_season_search is true, this will be ignored)"""
|
|
365
|
+
return self._get_value("assume_season_search", False, bool)
|
|
366
|
+
|
|
361
367
|
def _get_path_value(self, key: str, fallback: Path) -> Path:
|
|
362
368
|
path = self._get_value(key, fallback, str)
|
|
363
369
|
try:
|
|
@@ -385,21 +391,23 @@ class Config:
|
|
|
385
391
|
if attribute.startswith("_"):
|
|
386
392
|
continue
|
|
387
393
|
|
|
388
|
-
if isinstance(value, property):
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
)
|
|
394
|
+
if not isinstance(value, property):
|
|
395
|
+
continue
|
|
396
|
+
|
|
397
|
+
doc = inspect.getdoc(value)
|
|
398
|
+
if doc:
|
|
399
|
+
# Add docstrings
|
|
400
|
+
doc = Template(doc).safe_substitute(version=__version__)
|
|
401
|
+
doc = "\n".join([f"# {line}" for line in doc.split("\n")])
|
|
402
|
+
dump = dump + doc + "\n"
|
|
403
|
+
|
|
404
|
+
val = self.__getattribute__(attribute)
|
|
405
|
+
val = str(val) if isinstance(val, Path) else val
|
|
406
|
+
dump = (
|
|
407
|
+
dump
|
|
408
|
+
+ yaml.dump({attribute: val}, indent=4, default_flow_style=False)
|
|
409
|
+
+ "\n"
|
|
410
|
+
)
|
|
403
411
|
|
|
404
412
|
self._config_file.write_text(dump)
|
|
405
413
|
|
|
@@ -17,6 +17,7 @@ from anipy_cli.util import (
|
|
|
17
17
|
get_prefered_providers,
|
|
18
18
|
error,
|
|
19
19
|
parse_episode_ranges,
|
|
20
|
+
convert_letter_to_season,
|
|
20
21
|
)
|
|
21
22
|
from anipy_cli.colors import colors
|
|
22
23
|
from anipy_cli.config import Config
|
|
@@ -30,22 +31,9 @@ def search_show_prompt(
|
|
|
30
31
|
mode: str, skip_season_search: bool = False
|
|
31
32
|
) -> Optional["Anime"]:
|
|
32
33
|
if not (Config().skip_season_search or skip_season_search):
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
FilterCapabilities.SEASON
|
|
37
|
-
| FilterCapabilities.YEAR
|
|
38
|
-
| FilterCapabilities.NO_QUERY
|
|
39
|
-
):
|
|
40
|
-
season_provider = p
|
|
41
|
-
if season_provider is not None:
|
|
42
|
-
should_search = inquirer.confirm("Do you want to search in season?", default=False).execute() # type: ignore
|
|
43
|
-
if not should_search:
|
|
44
|
-
print(
|
|
45
|
-
"Hint: you can set `skip_season_search` to `true` in the config to skip this prompt!"
|
|
46
|
-
)
|
|
47
|
-
else:
|
|
48
|
-
return season_search_prompt(season_provider)
|
|
34
|
+
anime = season_search_pre_prompt(mode)
|
|
35
|
+
if anime is not None:
|
|
36
|
+
return anime
|
|
49
37
|
|
|
50
38
|
query = inquirer.text( # type: ignore
|
|
51
39
|
"Search Anime:",
|
|
@@ -88,41 +76,86 @@ def search_show_prompt(
|
|
|
88
76
|
return anime
|
|
89
77
|
|
|
90
78
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
79
|
+
def _get_season_provider(mode: str) -> Optional["BaseProvider"]:
|
|
80
|
+
season_provider = None
|
|
81
|
+
for p in get_prefered_providers(mode):
|
|
82
|
+
if p.FILTER_CAPS & (
|
|
83
|
+
FilterCapabilities.SEASON
|
|
84
|
+
| FilterCapabilities.YEAR
|
|
85
|
+
| FilterCapabilities.NO_QUERY
|
|
86
|
+
):
|
|
87
|
+
season_provider = p
|
|
88
|
+
return season_provider
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def season_search_pre_prompt(
|
|
92
|
+
mode: str, year: Optional[int] = None, season: Optional[str] = None
|
|
93
|
+
) -> Optional["Anime"]:
|
|
94
|
+
season_provider = _get_season_provider(mode)
|
|
95
|
+
assume_season_search = Config().assume_season_search
|
|
96
|
+
|
|
97
|
+
# If there is no proper season provider
|
|
98
|
+
if season_provider is None:
|
|
99
|
+
if not assume_season_search:
|
|
100
|
+
return
|
|
101
|
+
# If assume search was on, and there is no proper season provider
|
|
102
|
+
print(
|
|
103
|
+
f"`assume_season_search` was set to true, but the providers ({', '.join(Config().providers[mode])}) you have selected do not have seasonal capabilities"
|
|
104
|
+
)
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
if assume_season_search or (year and season):
|
|
108
|
+
return season_search_prompt(season_provider, year, season)
|
|
109
|
+
|
|
110
|
+
should_search = inquirer.confirm("Do you want to search in season?", default=False).execute() # type: ignore
|
|
111
|
+
|
|
112
|
+
if should_search:
|
|
113
|
+
return season_search_prompt(season_provider)
|
|
114
|
+
|
|
115
|
+
print(
|
|
116
|
+
"Hint: you can set `skip_season_search` to `true` in the config to skip this prompt!"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def season_search_prompt(
|
|
121
|
+
provider: "BaseProvider", year: Optional[int] = None, season: Optional[str] = None
|
|
122
|
+
) -> Optional["Anime"]:
|
|
123
|
+
if year is None:
|
|
124
|
+
curr_year = time.localtime().tm_year
|
|
125
|
+
year = inquirer.number( # type: ignore
|
|
126
|
+
message="Enter year:",
|
|
127
|
+
long_instruction="To skip this prompt press ctrl+z",
|
|
128
|
+
default=curr_year,
|
|
129
|
+
mandatory=False,
|
|
130
|
+
).execute()
|
|
98
131
|
|
|
99
132
|
if year is None:
|
|
100
133
|
return
|
|
101
134
|
|
|
102
|
-
season
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
135
|
+
if season is None:
|
|
136
|
+
season = inquirer.select( # type: ignore
|
|
137
|
+
message="Select Season:",
|
|
138
|
+
choices=["Winter", "Spring", "Summer", "Fall"],
|
|
139
|
+
instruction="The season selected by default is the current season.",
|
|
140
|
+
long_instruction="To skip this prompt press ctrl+z",
|
|
141
|
+
default=get_anime_season(time.localtime().tm_mon),
|
|
142
|
+
mandatory=False,
|
|
143
|
+
).execute()
|
|
110
144
|
|
|
111
145
|
if season is None:
|
|
112
146
|
return
|
|
113
147
|
|
|
114
|
-
|
|
148
|
+
discovered_anime = get_anime_by_season(provider, year, Season[season.upper()])
|
|
115
149
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
for r in provider.get_search(query="", filters=filters)
|
|
120
|
-
]
|
|
150
|
+
if not discovered_anime:
|
|
151
|
+
error(f"No anime found in {season} {year}")
|
|
152
|
+
return
|
|
121
153
|
|
|
122
154
|
anime = inquirer.fuzzy( # type: ignore
|
|
123
155
|
message="Select Show:",
|
|
124
156
|
choices=[
|
|
125
|
-
Choice(value=r, name=f"{n + 1}. {repr(r)}")
|
|
157
|
+
Choice(value=r, name=f"{n + 1}. {repr(r)}")
|
|
158
|
+
for n, r in enumerate(discovered_anime)
|
|
126
159
|
],
|
|
127
160
|
long_instruction=(
|
|
128
161
|
"\nS = Anime is available in sub\n"
|
|
@@ -136,6 +169,17 @@ def season_search_prompt(provider: "BaseProvider") -> Optional["Anime"]:
|
|
|
136
169
|
return anime
|
|
137
170
|
|
|
138
171
|
|
|
172
|
+
def get_anime_by_season(provider: "BaseProvider", year: int, season: Season):
|
|
173
|
+
with DotSpinner(
|
|
174
|
+
"Retrieving anime in ", colors.BLUE, f"{season.name} {year}", "..."
|
|
175
|
+
):
|
|
176
|
+
filters = Filters(year=year, season=season)
|
|
177
|
+
return [
|
|
178
|
+
Anime.from_search_result(provider, r)
|
|
179
|
+
for r in provider.get_search(query="", filters=filters)
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
|
|
139
183
|
def pick_episode_prompt(
|
|
140
184
|
anime: "Anime", lang: LanguageTypeEnum, instruction: str = ""
|
|
141
185
|
) -> Optional["Episode"]:
|
|
@@ -203,6 +247,47 @@ def lang_prompt(anime: "Anime") -> LanguageTypeEnum:
|
|
|
203
247
|
return next(iter(anime.languages))
|
|
204
248
|
|
|
205
249
|
|
|
250
|
+
def parse_seasonal_search(mode: str, passed: str | bool) -> Optional["Anime"]:
|
|
251
|
+
"""
|
|
252
|
+
Takes the mode we are in, as well as the search parameters.
|
|
253
|
+
Asks the user to choose an anime.
|
|
254
|
+
|
|
255
|
+
`Mode`: The provider to use.
|
|
256
|
+
`Passed`: The search terms passed (ex: `year:season`) or True,
|
|
257
|
+
if True we'll ask the user for this information
|
|
258
|
+
"""
|
|
259
|
+
if isinstance(passed, bool):
|
|
260
|
+
if not passed:
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
provider = _get_season_provider(mode)
|
|
264
|
+
|
|
265
|
+
if not provider:
|
|
266
|
+
error(
|
|
267
|
+
"No valid provider was found for season search in the current mode",
|
|
268
|
+
fatal=True,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return season_search_prompt(provider)
|
|
272
|
+
|
|
273
|
+
options = iter(passed.split(":"))
|
|
274
|
+
year = next(options, None)
|
|
275
|
+
season = next(options, None)
|
|
276
|
+
|
|
277
|
+
if (not year) or not year.isnumeric():
|
|
278
|
+
error("A year was either not provided, or was not a number", fatal=True)
|
|
279
|
+
|
|
280
|
+
if not season:
|
|
281
|
+
error("A season was not provided", fatal=True)
|
|
282
|
+
|
|
283
|
+
season = convert_letter_to_season(season)
|
|
284
|
+
|
|
285
|
+
if not season:
|
|
286
|
+
error("The given season was not a valid season", fatal=True)
|
|
287
|
+
|
|
288
|
+
return season_search_pre_prompt(mode, int(year), season)
|
|
289
|
+
|
|
290
|
+
|
|
206
291
|
def parse_auto_search(
|
|
207
292
|
mode: str, passed: str
|
|
208
293
|
) -> Tuple["Anime", LanguageTypeEnum, List["Episode"]]:
|
|
@@ -182,6 +182,25 @@ def get_anime_season(month):
|
|
|
182
182
|
return "Fall"
|
|
183
183
|
|
|
184
184
|
|
|
185
|
+
def convert_letter_to_season(letter: str) -> Optional[str]:
|
|
186
|
+
"""
|
|
187
|
+
Converts the beginning of the name of a season to that season name.
|
|
188
|
+
|
|
189
|
+
Ex:
|
|
190
|
+
```
|
|
191
|
+
win -> Winter
|
|
192
|
+
su -> Summer
|
|
193
|
+
sp -> Spring
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Returns None if the letter does not correspond to a season
|
|
197
|
+
"""
|
|
198
|
+
for season in ["Spring", "Summer", "Fall", "Winter"]:
|
|
199
|
+
if season.startswith(letter.capitalize()):
|
|
200
|
+
return season
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
|
|
185
204
|
def migrate_locallist(file: Path) -> LocalListData:
|
|
186
205
|
import json
|
|
187
206
|
import re
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|