anipy-cli 2.7.17__py3-none-any.whl → 3.8.2__py3-none-any.whl
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.
- anipy_cli/__init__.py +2 -20
- anipy_cli/anilist_proxy.py +229 -0
- anipy_cli/arg_parser.py +109 -21
- anipy_cli/cli.py +98 -0
- anipy_cli/clis/__init__.py +17 -0
- anipy_cli/clis/anilist_cli.py +62 -0
- anipy_cli/clis/base_cli.py +34 -0
- anipy_cli/clis/binge_cli.py +96 -0
- anipy_cli/clis/default_cli.py +115 -0
- anipy_cli/clis/download_cli.py +85 -0
- anipy_cli/clis/history_cli.py +96 -0
- anipy_cli/clis/mal_cli.py +71 -0
- anipy_cli/{cli/clis → clis}/seasonal_cli.py +9 -6
- anipy_cli/colors.py +14 -8
- anipy_cli/config.py +387 -90
- anipy_cli/discord.py +34 -0
- anipy_cli/download_component.py +194 -0
- anipy_cli/logger.py +200 -0
- anipy_cli/mal_proxy.py +228 -0
- anipy_cli/menus/__init__.py +6 -0
- anipy_cli/menus/anilist_menu.py +671 -0
- anipy_cli/{cli/menus → menus}/base_menu.py +9 -14
- anipy_cli/menus/mal_menu.py +657 -0
- anipy_cli/menus/menu.py +265 -0
- anipy_cli/menus/seasonal_menu.py +270 -0
- anipy_cli/prompts.py +387 -0
- anipy_cli/util.py +268 -0
- anipy_cli-3.8.2.dist-info/METADATA +71 -0
- anipy_cli-3.8.2.dist-info/RECORD +31 -0
- {anipy_cli-2.7.17.dist-info → anipy_cli-3.8.2.dist-info}/WHEEL +1 -2
- anipy_cli-3.8.2.dist-info/entry_points.txt +3 -0
- anipy_cli/cli/__init__.py +0 -1
- anipy_cli/cli/cli.py +0 -37
- anipy_cli/cli/clis/__init__.py +0 -6
- anipy_cli/cli/clis/base_cli.py +0 -43
- anipy_cli/cli/clis/binge_cli.py +0 -54
- anipy_cli/cli/clis/default_cli.py +0 -46
- anipy_cli/cli/clis/download_cli.py +0 -92
- anipy_cli/cli/clis/history_cli.py +0 -64
- anipy_cli/cli/clis/mal_cli.py +0 -27
- anipy_cli/cli/menus/__init__.py +0 -3
- anipy_cli/cli/menus/mal_menu.py +0 -411
- anipy_cli/cli/menus/menu.py +0 -102
- anipy_cli/cli/menus/seasonal_menu.py +0 -174
- anipy_cli/cli/util.py +0 -118
- anipy_cli/download.py +0 -454
- anipy_cli/history.py +0 -83
- anipy_cli/mal.py +0 -645
- anipy_cli/misc.py +0 -227
- anipy_cli/player/__init__.py +0 -1
- anipy_cli/player/player.py +0 -33
- anipy_cli/player/players/__init__.py +0 -3
- anipy_cli/player/players/base.py +0 -106
- anipy_cli/player/players/mpv.py +0 -19
- anipy_cli/player/players/mpv_contrl.py +0 -37
- anipy_cli/player/players/syncplay.py +0 -19
- anipy_cli/player/players/vlc.py +0 -18
- anipy_cli/query.py +0 -92
- anipy_cli/run_anipy_cli.py +0 -14
- anipy_cli/seasonal.py +0 -106
- anipy_cli/url_handler.py +0 -442
- anipy_cli/version.py +0 -1
- anipy_cli-2.7.17.dist-info/LICENSE +0 -674
- anipy_cli-2.7.17.dist-info/METADATA +0 -159
- anipy_cli-2.7.17.dist-info/RECORD +0 -43
- anipy_cli-2.7.17.dist-info/entry_points.txt +0 -2
- anipy_cli-2.7.17.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from anipy_api.locallist import LocalList
|
|
5
|
+
|
|
6
|
+
from anipy_cli.clis.base_cli import CliBase
|
|
7
|
+
from anipy_cli.colors import colors, cprint
|
|
8
|
+
from anipy_cli.config import Config
|
|
9
|
+
from anipy_cli.prompts import (
|
|
10
|
+
parse_seasonal_search,
|
|
11
|
+
pick_episode_range_prompt,
|
|
12
|
+
search_show_prompt,
|
|
13
|
+
lang_prompt,
|
|
14
|
+
parse_auto_search,
|
|
15
|
+
)
|
|
16
|
+
from anipy_cli.util import DotSpinner, get_configured_player, migrate_locallist, error
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from anipy_cli.arg_parser import CliArgs
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BingeCli(CliBase):
|
|
23
|
+
def __init__(self, options: "CliArgs"):
|
|
24
|
+
super().__init__(options)
|
|
25
|
+
|
|
26
|
+
self.player = get_configured_player(self.options.optional_player)
|
|
27
|
+
self.history_list = LocalList(
|
|
28
|
+
Config()._history_file_path, migrate_cb=migrate_locallist
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
self.anime = None
|
|
32
|
+
self.episodes = None
|
|
33
|
+
self.lang = None
|
|
34
|
+
|
|
35
|
+
def print_header(self):
|
|
36
|
+
cprint(colors.GREEN, "***Binge Mode***")
|
|
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
|
+
|
|
47
|
+
def take_input(self):
|
|
48
|
+
if self.options.search is not None:
|
|
49
|
+
self.anime, self.lang, self.episodes = parse_auto_search(
|
|
50
|
+
"binge", self.options.search
|
|
51
|
+
)
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
anime = self._get_anime_from_user()
|
|
55
|
+
|
|
56
|
+
if anime is None:
|
|
57
|
+
sys.exit(0)
|
|
58
|
+
|
|
59
|
+
self.lang = lang_prompt(anime)
|
|
60
|
+
|
|
61
|
+
episodes = pick_episode_range_prompt(anime, self.lang)
|
|
62
|
+
|
|
63
|
+
self.anime = anime
|
|
64
|
+
self.episodes = episodes
|
|
65
|
+
|
|
66
|
+
def process(self): ...
|
|
67
|
+
|
|
68
|
+
def show(self):
|
|
69
|
+
assert self.episodes is not None
|
|
70
|
+
assert self.anime is not None
|
|
71
|
+
assert self.lang is not None
|
|
72
|
+
|
|
73
|
+
for e in self.episodes:
|
|
74
|
+
with DotSpinner(
|
|
75
|
+
"Extracting streams for ",
|
|
76
|
+
colors.BLUE,
|
|
77
|
+
f"{self.anime.name} ({self.lang})",
|
|
78
|
+
colors.END,
|
|
79
|
+
" Episode ",
|
|
80
|
+
e,
|
|
81
|
+
"...",
|
|
82
|
+
) as s:
|
|
83
|
+
stream = self.anime.get_video(
|
|
84
|
+
e, self.lang, preferred_quality=self.options.quality
|
|
85
|
+
)
|
|
86
|
+
if stream is None:
|
|
87
|
+
error("Could not find stream for requested episode, skipping")
|
|
88
|
+
continue
|
|
89
|
+
s.ok("✔")
|
|
90
|
+
|
|
91
|
+
self.history_list.update(self.anime, episode=e, language=self.lang)
|
|
92
|
+
self.player.play_title(self.anime, stream)
|
|
93
|
+
self.player.wait()
|
|
94
|
+
|
|
95
|
+
def post(self):
|
|
96
|
+
self.player.kill_player()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional
|
|
2
|
+
|
|
3
|
+
from anipy_api.locallist import LocalList
|
|
4
|
+
|
|
5
|
+
from anipy_cli.clis.base_cli import CliBase
|
|
6
|
+
from anipy_cli.colors import colors
|
|
7
|
+
from anipy_cli.config import Config
|
|
8
|
+
from anipy_cli.menus import Menu
|
|
9
|
+
from anipy_cli.prompts import (
|
|
10
|
+
lang_prompt,
|
|
11
|
+
parse_auto_search,
|
|
12
|
+
parse_seasonal_search,
|
|
13
|
+
pick_episode_prompt,
|
|
14
|
+
search_show_prompt,
|
|
15
|
+
)
|
|
16
|
+
from anipy_cli.util import DotSpinner, error, get_configured_player, migrate_locallist
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from anipy_api.anime import Anime
|
|
20
|
+
from anipy_api.provider import Episode, LanguageTypeEnum, ProviderStream
|
|
21
|
+
|
|
22
|
+
from anipy_cli.arg_parser import CliArgs
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# TODO: Add Resume feature
|
|
26
|
+
class DefaultCli(CliBase):
|
|
27
|
+
def __init__(self, options: "CliArgs"):
|
|
28
|
+
super().__init__(options)
|
|
29
|
+
|
|
30
|
+
self.player = get_configured_player(self.options.optional_player)
|
|
31
|
+
self.history_list = LocalList(
|
|
32
|
+
Config()._history_file_path, migrate_cb=migrate_locallist
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
self.anime: Optional["Anime"] = None
|
|
36
|
+
self.epsiode: Optional["Episode"] = None
|
|
37
|
+
self.stream: Optional["ProviderStream"] = None
|
|
38
|
+
self.lang: Optional["LanguageTypeEnum"] = None
|
|
39
|
+
|
|
40
|
+
def print_header(self):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def _get_anime_from_user(self):
|
|
44
|
+
if (ss := self.options.seasonal_search) is not None:
|
|
45
|
+
return parse_seasonal_search(
|
|
46
|
+
"default",
|
|
47
|
+
ss,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return search_show_prompt("default")
|
|
51
|
+
|
|
52
|
+
def take_input(self):
|
|
53
|
+
if self.options.search is not None:
|
|
54
|
+
self.anime, self.lang, episodes = parse_auto_search(
|
|
55
|
+
"default", self.options.search
|
|
56
|
+
)
|
|
57
|
+
self.epsiode = episodes[0]
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
anime = self._get_anime_from_user()
|
|
61
|
+
|
|
62
|
+
if anime is None:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
self.lang = lang_prompt(anime)
|
|
66
|
+
|
|
67
|
+
episode = pick_episode_prompt(anime, self.lang)
|
|
68
|
+
|
|
69
|
+
if episode is None:
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
self.anime = anime
|
|
73
|
+
self.epsiode = episode
|
|
74
|
+
|
|
75
|
+
def process(self):
|
|
76
|
+
assert self.anime is not None
|
|
77
|
+
assert self.epsiode is not None
|
|
78
|
+
assert self.lang is not None
|
|
79
|
+
|
|
80
|
+
with DotSpinner(
|
|
81
|
+
"Extracting streams for ",
|
|
82
|
+
colors.BLUE,
|
|
83
|
+
f"{self.anime.name} ({self.lang})",
|
|
84
|
+
" Episode ",
|
|
85
|
+
self.epsiode,
|
|
86
|
+
"...",
|
|
87
|
+
):
|
|
88
|
+
self.stream = self.anime.get_video(
|
|
89
|
+
self.epsiode, self.lang, preferred_quality=self.options.quality
|
|
90
|
+
)
|
|
91
|
+
if not self.stream:
|
|
92
|
+
error(
|
|
93
|
+
f"Could not find any streams for {self.anime.name} ({self.lang}) Episode {self.epsiode}",
|
|
94
|
+
fatal=True,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def show(self):
|
|
98
|
+
assert self.anime is not None
|
|
99
|
+
assert self.stream is not None
|
|
100
|
+
|
|
101
|
+
self.history_list.update(
|
|
102
|
+
self.anime, episode=self.epsiode, language=self.stream.language
|
|
103
|
+
)
|
|
104
|
+
self.player.play_title(self.anime, self.stream)
|
|
105
|
+
|
|
106
|
+
def post(self):
|
|
107
|
+
assert self.anime is not None
|
|
108
|
+
assert self.stream is not None
|
|
109
|
+
|
|
110
|
+
Menu(
|
|
111
|
+
options=self.options,
|
|
112
|
+
anime=self.anime,
|
|
113
|
+
stream=self.stream,
|
|
114
|
+
player=self.player,
|
|
115
|
+
).run()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional, List
|
|
2
|
+
|
|
3
|
+
from anipy_cli.download_component import DownloadComponent
|
|
4
|
+
|
|
5
|
+
from anipy_cli.clis.base_cli import CliBase
|
|
6
|
+
from anipy_cli.colors import colors, cprint
|
|
7
|
+
from anipy_cli.config import Config
|
|
8
|
+
from anipy_cli.prompts import (
|
|
9
|
+
parse_seasonal_search,
|
|
10
|
+
pick_episode_range_prompt,
|
|
11
|
+
search_show_prompt,
|
|
12
|
+
lang_prompt,
|
|
13
|
+
parse_auto_search,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from anipy_api.anime import Anime
|
|
18
|
+
from anipy_api.provider import Episode, LanguageTypeEnum
|
|
19
|
+
|
|
20
|
+
from anipy_cli.arg_parser import CliArgs
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DownloadCli(CliBase):
|
|
24
|
+
def __init__(self, options: "CliArgs"):
|
|
25
|
+
super().__init__(options)
|
|
26
|
+
|
|
27
|
+
self.anime: Optional["Anime"] = None
|
|
28
|
+
self.episodes: Optional[List["Episode"]] = None
|
|
29
|
+
self.lang: Optional["LanguageTypeEnum"] = None
|
|
30
|
+
|
|
31
|
+
self.dl_path = Config().download_folder_path
|
|
32
|
+
if options.location:
|
|
33
|
+
self.dl_path = options.location
|
|
34
|
+
|
|
35
|
+
def print_header(self):
|
|
36
|
+
cprint(colors.GREEN, "***Download Mode***")
|
|
37
|
+
cprint(colors.GREEN, "Downloads are stored in: ", colors.END, str(self.dl_path))
|
|
38
|
+
|
|
39
|
+
def _get_anime_from_user(self):
|
|
40
|
+
if (ss := self.options.seasonal_search) is not None:
|
|
41
|
+
return parse_seasonal_search(
|
|
42
|
+
"download",
|
|
43
|
+
ss,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return search_show_prompt("download")
|
|
47
|
+
|
|
48
|
+
def take_input(self):
|
|
49
|
+
if self.options.search is not None:
|
|
50
|
+
self.anime, self.lang, self.episodes = parse_auto_search(
|
|
51
|
+
"download", self.options.search
|
|
52
|
+
)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
anime = self._get_anime_from_user()
|
|
56
|
+
|
|
57
|
+
if anime is None:
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
self.lang = lang_prompt(anime)
|
|
61
|
+
|
|
62
|
+
episodes = pick_episode_range_prompt(anime, self.lang)
|
|
63
|
+
|
|
64
|
+
self.anime = anime
|
|
65
|
+
self.episodes = episodes
|
|
66
|
+
|
|
67
|
+
def process(self):
|
|
68
|
+
assert self.episodes is not None
|
|
69
|
+
assert self.anime is not None
|
|
70
|
+
assert self.lang is not None
|
|
71
|
+
|
|
72
|
+
errors = DownloadComponent(
|
|
73
|
+
self.options, self.dl_path, "download"
|
|
74
|
+
).download_anime(
|
|
75
|
+
[(self.anime, self.lang, self.episodes)],
|
|
76
|
+
only_skip_ep_on_err=True,
|
|
77
|
+
sub_only=self.options.subtitles,
|
|
78
|
+
)
|
|
79
|
+
DownloadComponent.serve_download_errors(errors, only_skip_ep_on_err=True)
|
|
80
|
+
|
|
81
|
+
def show(self):
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
def post(self):
|
|
85
|
+
pass
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional
|
|
2
|
+
|
|
3
|
+
from InquirerPy.base.control import Choice
|
|
4
|
+
from anipy_api.anime import Anime
|
|
5
|
+
from anipy_api.locallist import LocalList, LocalListEntry
|
|
6
|
+
from InquirerPy import inquirer
|
|
7
|
+
|
|
8
|
+
from anipy_cli.clis.base_cli import CliBase
|
|
9
|
+
from anipy_cli.colors import colors
|
|
10
|
+
from anipy_cli.config import Config
|
|
11
|
+
from anipy_cli.menus import Menu
|
|
12
|
+
from anipy_cli.util import DotSpinner, get_configured_player, migrate_locallist
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from anipy_api.provider import ProviderStream
|
|
16
|
+
|
|
17
|
+
from anipy_cli.arg_parser import CliArgs
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class HistoryCli(CliBase):
|
|
21
|
+
def __init__(self, options: "CliArgs"):
|
|
22
|
+
super().__init__(options)
|
|
23
|
+
|
|
24
|
+
self.player = get_configured_player(self.options.optional_player)
|
|
25
|
+
self.history_list = LocalList(
|
|
26
|
+
Config()._history_file_path, migrate_cb=migrate_locallist
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
self.anime: Optional[Anime] = None
|
|
30
|
+
self.history_entry: Optional["LocalListEntry"] = None
|
|
31
|
+
self.stream: Optional["ProviderStream"] = None
|
|
32
|
+
|
|
33
|
+
def print_header(self):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def take_input(self):
|
|
37
|
+
history = self.history_list.get_all()
|
|
38
|
+
history.sort(key=lambda h: h.timestamp, reverse=True)
|
|
39
|
+
|
|
40
|
+
if not history:
|
|
41
|
+
print("You have no History, exiting")
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
entry = inquirer.fuzzy( # type: ignore
|
|
45
|
+
message="Select History Entry:",
|
|
46
|
+
choices=[
|
|
47
|
+
Choice(value=h, name=f"{n + 1}. {repr(h)}")
|
|
48
|
+
for n, h in enumerate(history)
|
|
49
|
+
],
|
|
50
|
+
long_instruction="To cancel this prompt press ctrl+z",
|
|
51
|
+
mandatory=False,
|
|
52
|
+
).execute()
|
|
53
|
+
|
|
54
|
+
if entry is None:
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
self.history_entry = LocalListEntry.from_dict(entry)
|
|
58
|
+
self.anime = Anime.from_local_list_entry(self.history_entry)
|
|
59
|
+
|
|
60
|
+
def process(self):
|
|
61
|
+
assert self.anime is not None
|
|
62
|
+
assert self.history_entry is not None
|
|
63
|
+
|
|
64
|
+
with DotSpinner(
|
|
65
|
+
"Extracting streams for ",
|
|
66
|
+
colors.BLUE,
|
|
67
|
+
self.history_entry,
|
|
68
|
+
"...",
|
|
69
|
+
):
|
|
70
|
+
self.stream = self.anime.get_video(
|
|
71
|
+
self.history_entry.episode,
|
|
72
|
+
self.history_entry.language,
|
|
73
|
+
preferred_quality=self.options.quality,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def show(self):
|
|
77
|
+
assert self.anime is not None
|
|
78
|
+
assert self.stream is not None
|
|
79
|
+
|
|
80
|
+
self.player.play_title(self.anime, self.stream)
|
|
81
|
+
self.history_list.update(
|
|
82
|
+
self.anime,
|
|
83
|
+
episode=self.stream.episode,
|
|
84
|
+
language=self.stream.language,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def post(self):
|
|
88
|
+
assert self.anime is not None
|
|
89
|
+
assert self.stream is not None
|
|
90
|
+
|
|
91
|
+
Menu(
|
|
92
|
+
options=self.options,
|
|
93
|
+
anime=self.anime,
|
|
94
|
+
stream=self.stream,
|
|
95
|
+
player=self.player,
|
|
96
|
+
).run()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from anipy_api.error import MyAnimeListError
|
|
4
|
+
from anipy_api.mal import MyAnimeList
|
|
5
|
+
from InquirerPy import inquirer
|
|
6
|
+
|
|
7
|
+
from anipy_cli.clis.base_cli import CliBase
|
|
8
|
+
from anipy_cli.config import Config
|
|
9
|
+
from anipy_cli.menus import MALMenu
|
|
10
|
+
from anipy_cli.util import DotSpinner, error
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from anipy_cli.arg_parser import CliArgs
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MalCli(CliBase):
|
|
17
|
+
def __init__(self, options: "CliArgs"):
|
|
18
|
+
super().__init__(options)
|
|
19
|
+
self.user = ""
|
|
20
|
+
self.password = ""
|
|
21
|
+
self.mal = None
|
|
22
|
+
|
|
23
|
+
def print_header(self):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def take_input(self):
|
|
27
|
+
config = Config()
|
|
28
|
+
self.user = self.options.mal_user or config.mal_user
|
|
29
|
+
self.password = self.options.mal_password or config.mal_password
|
|
30
|
+
|
|
31
|
+
if not self.user:
|
|
32
|
+
self.user = inquirer.text( # type: ignore
|
|
33
|
+
"Your MyAnimeList Username:",
|
|
34
|
+
validate=lambda x: len(x) > 1,
|
|
35
|
+
invalid_message="You must enter a username!",
|
|
36
|
+
long_instruction="Hint: You can save your username and password in the config!",
|
|
37
|
+
).execute()
|
|
38
|
+
|
|
39
|
+
if not self.password:
|
|
40
|
+
self.password = inquirer.secret( # type: ignore
|
|
41
|
+
"Your MyAnimeList Password:",
|
|
42
|
+
transformer=lambda _: "[hidden]",
|
|
43
|
+
validate=lambda x: len(x) > 1,
|
|
44
|
+
invalid_message="You must enter a password!",
|
|
45
|
+
long_instruction="Hint: You can also pass the password via the `--mal-password` option!",
|
|
46
|
+
).execute()
|
|
47
|
+
|
|
48
|
+
def process(self):
|
|
49
|
+
try:
|
|
50
|
+
with DotSpinner("Logging into MyAnimeList..."):
|
|
51
|
+
self.mal = MyAnimeList.from_password_grant(self.user, self.password)
|
|
52
|
+
except MyAnimeListError as e:
|
|
53
|
+
error(
|
|
54
|
+
f"{str(e)}\nCannot login to MyAnimeList, it is likely your credentials are wrong",
|
|
55
|
+
fatal=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def show(self):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def post(self):
|
|
62
|
+
assert self.mal is not None
|
|
63
|
+
|
|
64
|
+
menu = MALMenu(mal=self.mal, options=self.options)
|
|
65
|
+
|
|
66
|
+
if self.options.auto_update:
|
|
67
|
+
menu.download()
|
|
68
|
+
elif self.options.mal_sync_seasonals:
|
|
69
|
+
menu.sync_mal_seasonls()
|
|
70
|
+
else:
|
|
71
|
+
menu.run()
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
from
|
|
2
|
-
from anipy_cli.
|
|
3
|
-
from anipy_cli.
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
from anipy_cli.menus import SeasonalMenu
|
|
3
|
+
from anipy_cli.clis.base_cli import CliBase
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from anipy_cli.arg_parser import CliArgs
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
class SeasonalCli(CliBase):
|
|
7
|
-
def __init__(self, options: CliArgs
|
|
8
|
-
super().__init__(options
|
|
10
|
+
def __init__(self, options: "CliArgs"):
|
|
11
|
+
super().__init__(options)
|
|
9
12
|
|
|
10
13
|
def print_header(self):
|
|
11
14
|
pass
|
|
@@ -20,7 +23,7 @@ class SeasonalCli(CliBase):
|
|
|
20
23
|
pass
|
|
21
24
|
|
|
22
25
|
def post(self):
|
|
23
|
-
menu = SeasonalMenu(self.options
|
|
26
|
+
menu = SeasonalMenu(self.options)
|
|
24
27
|
|
|
25
28
|
if self.options.auto_update:
|
|
26
29
|
menu.download_latest()
|
anipy_cli/colors.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class colors: # noqa: N801
|
|
5
|
+
"""Just a class for colors."""
|
|
5
6
|
|
|
6
7
|
GREEN = "\033[92m"
|
|
7
8
|
ERROR = "\033[93m"
|
|
@@ -11,23 +12,28 @@ class colors:
|
|
|
11
12
|
CYAN = "\u001b[36m"
|
|
12
13
|
RED = "\u001b[31m"
|
|
13
14
|
END = "\x1b[0m"
|
|
15
|
+
BOLD = "\033[1m"
|
|
16
|
+
UNDERLINE = "\033[4m"
|
|
17
|
+
RESET = "\033[0m"
|
|
14
18
|
|
|
15
19
|
|
|
16
|
-
def color(*values, sep: str = "") -> str:
|
|
20
|
+
def color(*values: Any, sep: str = "") -> str:
|
|
17
21
|
"""Decorate a string with color codes.
|
|
22
|
+
|
|
18
23
|
Basically just ensures that the color doesn't "leak"
|
|
19
24
|
from the text.
|
|
20
|
-
format: color(color1, text1, color2, text2...)
|
|
25
|
+
format: color(color1, text1, color2, text2...)
|
|
26
|
+
"""
|
|
21
27
|
return sep.join(map(str, values)) + colors.END
|
|
22
28
|
|
|
23
29
|
|
|
24
|
-
def cinput(*prompt, input_color: str = "") -> str:
|
|
30
|
+
def cinput(*prompt: Any, input_color: str = "") -> str:
|
|
25
31
|
"""An input function that handles coloring input."""
|
|
26
32
|
inp = input(color(*prompt) + input_color)
|
|
27
33
|
print(colors.END, end="")
|
|
28
34
|
return inp
|
|
29
35
|
|
|
30
36
|
|
|
31
|
-
def cprint(*values, sep: str = "", **kwargs) -> None:
|
|
37
|
+
def cprint(*values: Any, sep: str = "", **kwargs: Any) -> None:
|
|
32
38
|
"""Prints colored text."""
|
|
33
39
|
print(color(*values, sep=sep), **kwargs)
|