anipy-cli 2.7.31__py3-none-any.whl → 3.0.0__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.
Potentially problematic release.
This version of anipy-cli might be problematic. Click here for more details.
- anipy_cli/__init__.py +2 -20
- anipy_cli/arg_parser.py +30 -20
- anipy_cli/cli.py +66 -0
- anipy_cli/clis/__init__.py +15 -0
- anipy_cli/clis/base_cli.py +32 -0
- anipy_cli/clis/binge_cli.py +83 -0
- anipy_cli/clis/default_cli.py +104 -0
- anipy_cli/clis/download_cli.py +111 -0
- anipy_cli/clis/history_cli.py +93 -0
- anipy_cli/clis/mal_cli.py +71 -0
- anipy_cli/{cli/clis → clis}/seasonal_cli.py +9 -6
- anipy_cli/colors.py +4 -4
- anipy_cli/config.py +308 -87
- anipy_cli/discord.py +34 -0
- anipy_cli/mal_proxy.py +216 -0
- anipy_cli/menus/__init__.py +5 -0
- anipy_cli/{cli/menus → menus}/base_menu.py +8 -12
- anipy_cli/menus/mal_menu.py +660 -0
- anipy_cli/menus/menu.py +194 -0
- anipy_cli/menus/seasonal_menu.py +263 -0
- anipy_cli/prompts.py +231 -0
- anipy_cli/util.py +262 -0
- anipy_cli-3.0.0.dist-info/METADATA +67 -0
- anipy_cli-3.0.0.dist-info/RECORD +26 -0
- {anipy_cli-2.7.31.dist-info → anipy_cli-3.0.0.dist-info}/WHEEL +1 -2
- anipy_cli-3.0.0.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 -108
- anipy_cli/cli/menus/seasonal_menu.py +0 -177
- anipy_cli/cli/util.py +0 -125
- anipy_cli/download.py +0 -467
- anipy_cli/history.py +0 -83
- anipy_cli/mal.py +0 -651
- anipy_cli/misc.py +0 -227
- anipy_cli/player/__init__.py +0 -1
- anipy_cli/player/player.py +0 -35
- anipy_cli/player/players/__init__.py +0 -3
- anipy_cli/player/players/base.py +0 -107
- anipy_cli/player/players/mpv.py +0 -19
- anipy_cli/player/players/mpv_control.py +0 -37
- anipy_cli/player/players/syncplay.py +0 -19
- anipy_cli/player/players/vlc.py +0 -18
- anipy_cli/query.py +0 -100
- anipy_cli/run_anipy_cli.py +0 -14
- anipy_cli/seasonal.py +0 -112
- anipy_cli/url_handler.py +0 -470
- anipy_cli/version.py +0 -1
- anipy_cli-2.7.31.dist-info/LICENSE +0 -674
- anipy_cli-2.7.31.dist-info/METADATA +0 -162
- anipy_cli-2.7.31.dist-info/RECORD +0 -43
- anipy_cli-2.7.31.dist-info/entry_points.txt +0 -2
- anipy_cli-2.7.31.dist-info/top_level.txt +0 -1
anipy_cli/util.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import (
|
|
5
|
+
TYPE_CHECKING,
|
|
6
|
+
Iterator,
|
|
7
|
+
List,
|
|
8
|
+
Literal,
|
|
9
|
+
NoReturn,
|
|
10
|
+
Optional,
|
|
11
|
+
Union,
|
|
12
|
+
overload,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from anipy_api.anime import Anime
|
|
16
|
+
from anipy_api.download import Downloader
|
|
17
|
+
from anipy_api.player import get_player
|
|
18
|
+
from anipy_api.provider import (
|
|
19
|
+
LanguageTypeEnum,
|
|
20
|
+
list_providers,
|
|
21
|
+
get_provider,
|
|
22
|
+
)
|
|
23
|
+
from anipy_api.locallist import LocalListData, LocalListEntry
|
|
24
|
+
from anipy_api.error import LangTypeNotAvailableError
|
|
25
|
+
from InquirerPy import inquirer
|
|
26
|
+
from yaspin.core import Yaspin
|
|
27
|
+
from yaspin.spinners import Spinners
|
|
28
|
+
|
|
29
|
+
from anipy_cli.colors import color, colors
|
|
30
|
+
from anipy_cli.config import Config
|
|
31
|
+
from anipy_cli.discord import DiscordPresence
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from anipy_api.player import PlayerBase
|
|
35
|
+
from anipy_api.provider import BaseProvider, Episode, ProviderStream
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DotSpinner(Yaspin):
|
|
39
|
+
def __init__(self, *text_and_colors, **spinner_args):
|
|
40
|
+
super().__init__(
|
|
41
|
+
text=color(*text_and_colors),
|
|
42
|
+
color="cyan",
|
|
43
|
+
spinner=Spinners.dots,
|
|
44
|
+
**spinner_args,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def __enter__(self) -> "DotSpinner":
|
|
48
|
+
self.start()
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def set_text(self, *text_and_colors):
|
|
52
|
+
self.text = color(*text_and_colors)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@overload
|
|
56
|
+
def error(error: str, fatal: Literal[True]) -> NoReturn: ...
|
|
57
|
+
@overload
|
|
58
|
+
def error(error: str, fatal: Literal[False] = ...) -> None: ...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def error(error: str, fatal: bool = False) -> Union[NoReturn, None]:
|
|
62
|
+
if not fatal:
|
|
63
|
+
sys.stderr.write(
|
|
64
|
+
color(colors.RED, "anipy-cli: error: ", colors.END, f"{error}\n")
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
sys.stderr.write(
|
|
68
|
+
color(
|
|
69
|
+
colors.RED,
|
|
70
|
+
"anipy-cli: fatal error: ",
|
|
71
|
+
colors.END,
|
|
72
|
+
f"{error}, exiting\n",
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_prefered_providers(mode: str) -> Iterator["BaseProvider"]:
|
|
79
|
+
config = Config()
|
|
80
|
+
preferred_providers = config.providers[mode]
|
|
81
|
+
|
|
82
|
+
if not preferred_providers:
|
|
83
|
+
error(
|
|
84
|
+
f"you have no providers set for {mode} mode, look into your config",
|
|
85
|
+
fatal=True,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
for i in list_providers():
|
|
89
|
+
if i.NAME in preferred_providers:
|
|
90
|
+
url_override = config.provider_urls.get(i.NAME, None)
|
|
91
|
+
yield i(url_override)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_download_path(
|
|
95
|
+
anime: "Anime",
|
|
96
|
+
stream: "ProviderStream",
|
|
97
|
+
parent_directory: Optional[Path] = None,
|
|
98
|
+
) -> Path:
|
|
99
|
+
config = Config()
|
|
100
|
+
download_folder = parent_directory or config.download_folder_path
|
|
101
|
+
|
|
102
|
+
anime_name = Downloader._get_valid_pathname(anime.name)
|
|
103
|
+
filename = config.download_name_format.format(
|
|
104
|
+
show_name=anime_name,
|
|
105
|
+
episode_number=str(stream.episode).zfill(2),
|
|
106
|
+
quality=stream.resolution,
|
|
107
|
+
provider=anime.provider.NAME,
|
|
108
|
+
type=stream.language,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
filename = Downloader._get_valid_pathname(filename) # type: ignore
|
|
112
|
+
|
|
113
|
+
return download_folder / anime_name / filename
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def parse_episode_ranges(ranges: str, episodes: List["Episode"]) -> List["Episode"]:
|
|
117
|
+
picked = set()
|
|
118
|
+
for r in ranges.split():
|
|
119
|
+
numbers = r.split("-")
|
|
120
|
+
if numbers[0] > numbers[-1]:
|
|
121
|
+
error(f"invalid range: {r}")
|
|
122
|
+
continue
|
|
123
|
+
# return pick_episode_range_prompt(anime, dub)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
picked = picked | set(
|
|
127
|
+
episodes[
|
|
128
|
+
episodes.index(parsenum(numbers[0])) : episodes.index(
|
|
129
|
+
parsenum(numbers[-1])
|
|
130
|
+
)
|
|
131
|
+
+ 1
|
|
132
|
+
]
|
|
133
|
+
)
|
|
134
|
+
except ValueError:
|
|
135
|
+
error(f"range `{r}` is not contained in episodes {episodes}")
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
return sorted(picked)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def parsenum(n: str):
|
|
142
|
+
try:
|
|
143
|
+
return int(n)
|
|
144
|
+
except ValueError:
|
|
145
|
+
return float(n)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def find_closest(episodes: List["Episode"], target: int) -> "Episode":
|
|
149
|
+
left, right = 0, len(episodes) - 1
|
|
150
|
+
while left < right:
|
|
151
|
+
if abs(episodes[left] - target) <= abs(episodes[right] - target):
|
|
152
|
+
right -= 1
|
|
153
|
+
else:
|
|
154
|
+
left += 1
|
|
155
|
+
|
|
156
|
+
return episodes[left]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_configured_player(player_override: Optional[str] = None) -> "PlayerBase":
|
|
160
|
+
config = Config()
|
|
161
|
+
player = Path(player_override or config.player_path)
|
|
162
|
+
if config.dc_presence:
|
|
163
|
+
# If the cache size is 0, it means that DiscordPresence was
|
|
164
|
+
# not intialized once in the run_cli function and therefore we
|
|
165
|
+
# can assume that it failed to initialize beacuse of some error.
|
|
166
|
+
if DiscordPresence.cache_info().currsize > 0:
|
|
167
|
+
discord_cb = DiscordPresence().dc_presence_callback
|
|
168
|
+
else:
|
|
169
|
+
discord_cb = None
|
|
170
|
+
else:
|
|
171
|
+
discord_cb = None
|
|
172
|
+
|
|
173
|
+
if "mpv" in player.stem:
|
|
174
|
+
args = config.mpv_commandline_options
|
|
175
|
+
elif "vlc" in player.stem:
|
|
176
|
+
args = config.vlc_commandline_options
|
|
177
|
+
else:
|
|
178
|
+
args = []
|
|
179
|
+
|
|
180
|
+
return get_player(player, args, discord_cb)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_anime_season(month):
|
|
184
|
+
if 1 <= month <= 3:
|
|
185
|
+
return "Winter"
|
|
186
|
+
elif 4 <= month <= 6:
|
|
187
|
+
return "Spring"
|
|
188
|
+
elif 7 <= month <= 9:
|
|
189
|
+
return "Summer"
|
|
190
|
+
else:
|
|
191
|
+
return "Fall"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def migrate_locallist(file: Path) -> LocalListData:
|
|
195
|
+
import json
|
|
196
|
+
import re
|
|
197
|
+
|
|
198
|
+
error(
|
|
199
|
+
f"{file} is in an unsuported format, trying to migrate the old gogoanime entries..."
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
old_data = json.load(file.open("r"))
|
|
203
|
+
new_list = LocalListData({})
|
|
204
|
+
gogo = get_provider(
|
|
205
|
+
"gogoanime", base_url_override=Config().provider_urls.get("gogoanime", None)
|
|
206
|
+
)
|
|
207
|
+
assert gogo is not None
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
for k, v in old_data.items():
|
|
211
|
+
name = k
|
|
212
|
+
name = re.sub(r"\s?\((dub|japanese\sdub)\)", "", name, flags=re.IGNORECASE)
|
|
213
|
+
identifier = Path(v.get("category_url", v["category-link"])).name
|
|
214
|
+
is_dub = identifier.endswith("-dub") or identifier.endswith("-japanese-dub")
|
|
215
|
+
identifier = identifier.removesuffix("-dub").removesuffix("-japanese-dub")
|
|
216
|
+
episode = v["ep"]
|
|
217
|
+
unique_id = f"gogoanime:{identifier}"
|
|
218
|
+
|
|
219
|
+
langs = set()
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
gogo.get_episodes(identifier, lang=LanguageTypeEnum.DUB)
|
|
223
|
+
langs.add(LanguageTypeEnum.DUB)
|
|
224
|
+
gogo.get_episodes(identifier, lang=LanguageTypeEnum.SUB)
|
|
225
|
+
langs.add(LanguageTypeEnum.SUB)
|
|
226
|
+
except LangTypeNotAvailableError:
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
if not langs:
|
|
230
|
+
error(f"> failed to migrate {name}, as it was not found in gogoanime")
|
|
231
|
+
|
|
232
|
+
if is_dub and LanguageTypeEnum.DUB not in langs:
|
|
233
|
+
error(
|
|
234
|
+
f"> failed to migrate {name}, as it was configured as dub but"
|
|
235
|
+
f"{gogo.BASE_URL}/category/{identifier}-dub or {gogo.BASE_URL}/category/{identifier}-japanese-dub was not found!"
|
|
236
|
+
)
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
new_entry = LocalListEntry(
|
|
240
|
+
provider="gogoanmie",
|
|
241
|
+
name=name,
|
|
242
|
+
identifier=identifier,
|
|
243
|
+
episode=episode,
|
|
244
|
+
language=LanguageTypeEnum.DUB if is_dub else LanguageTypeEnum.SUB,
|
|
245
|
+
languages=langs,
|
|
246
|
+
timestamp=int(time.time()),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
new_list.data[unique_id] = new_entry
|
|
250
|
+
|
|
251
|
+
new_list.write(file)
|
|
252
|
+
return new_list
|
|
253
|
+
except KeyError:
|
|
254
|
+
choice = inquirer.confirm( # type: ignore
|
|
255
|
+
message=f"Can not migrate {file}, should it be delted?",
|
|
256
|
+
default=False,
|
|
257
|
+
).execute()
|
|
258
|
+
if choice:
|
|
259
|
+
file.unlink()
|
|
260
|
+
return new_list
|
|
261
|
+
else:
|
|
262
|
+
error("could not read {file}", fatal=True)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: anipy-cli
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Watch and Download anime from the comfort of your Terminal
|
|
5
|
+
Home-page: https://sdaqo.github.io/anipy-cli
|
|
6
|
+
License: GPL-3.0
|
|
7
|
+
Keywords: anime,cli
|
|
8
|
+
Author: sdaqo
|
|
9
|
+
Author-email: sdaqo.dev@protonmail.com
|
|
10
|
+
Requires-Python: >=3.9,<4.0
|
|
11
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Dist: anipy-api (>=3.0.0,<4.0.0)
|
|
18
|
+
Requires-Dist: appdirs (>=1.4.4,<2.0.0)
|
|
19
|
+
Requires-Dist: inquirerpy (>=0.3.4,<0.4.0)
|
|
20
|
+
Requires-Dist: pypresence (>=4.3.0,<5.0.0)
|
|
21
|
+
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
22
|
+
Requires-Dist: yaspin (>=3.0.2,<4.0.0)
|
|
23
|
+
Project-URL: Bug Tracker, https://github.com/sdaqo/anipy-cli/issues
|
|
24
|
+
Project-URL: Documentation, https://sdaqo.github.io/anipy-cli/getting-started-cli
|
|
25
|
+
Project-URL: Repository, https://github.com/sdaqo/anipy-cli
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# ANIPY-CLI
|
|
29
|
+
**Anime from the comfort of your Terminal**
|
|
30
|
+
|
|
31
|
+
<p align="center"><img src="https://github.com/sdaqo/anipy-cli/assets/63876564/1dafa5fb-4273-4dc1-a7ab-2664dd668fc9" /> </p>
|
|
32
|
+
|
|
33
|
+
https://user-images.githubusercontent.com/63876564/162056019-ed0e7a60-78f6-4a2c-bc73-9be5dc2a4f07.mp4
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## What even is this?
|
|
37
|
+
A Little tool written in python to watch and download anime from the terminal (the better way to watch anime)
|
|
38
|
+
This project's main aim is to create a enjoyable anime watching and downloading experience, directly in the terminal - your favorite place.
|
|
39
|
+
|
|
40
|
+
Since the version 3 rewrite this project is split into api and frontend this makes it easy to integrate this into your project!
|
|
41
|
+
|
|
42
|
+
## You are just here for the client?
|
|
43
|
+
As one wise man once said:
|
|
44
|
+
> I DONT GIVE A FUCK ABOUT THE FUCKING CODE! i just want to download this stupid fucking application and use it.
|
|
45
|
+
>
|
|
46
|
+
> WHY IS THERE CODE??? MAKE A FUCKING .EXE FILE AND GIVE IT TO ME. these dumbfucks think that everyone is a developer and understands code. well i am not and i don't understand it. I only know to download and install applications. SO WHY THE FUCK IS THERE CODE? make an EXE file and give it to me. STUPID FUCKING SMELLY NERDS
|
|
47
|
+
|
|
48
|
+
<sub>Please do not take this seriously this is some stupid copypasta</sub>
|
|
49
|
+
|
|
50
|
+
We do not have a .exe but we have pipx: `pipx install anipy-cli`
|
|
51
|
+
|
|
52
|
+
Check out [Getting Started - CLI](https://sdaqo.github.io/anipy-cli/getting-started-cli) for better instructions and advice!
|
|
53
|
+
|
|
54
|
+
## You want to use the api for your project?
|
|
55
|
+
Feel free to - please check out [Getting Started - API](https://sdaqo.github.io/anipy-cli/getting-started-api) for instructions
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## :heart: Credits!
|
|
59
|
+
|
|
60
|
+
#### Heavily inspired by https://github.com/pystardust/ani-cli/
|
|
61
|
+
|
|
62
|
+
#### All contributors for contributing
|
|
63
|
+
|
|
64
|
+
<a href="https://github.com/sdaqo/anipy-cli/graphs/contributors">
|
|
65
|
+
<img src="https://contrib.rocks/image?repo=sdaqo/anipy-cli" alt="anipy-cli contributors" title="anipy-cli contributors" width="800"/>
|
|
66
|
+
</a>
|
|
67
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
anipy_cli/__init__.py,sha256=yDw4V_yk0FWij9eqDtQZMc2vSieuAKhqSbCoROXlGGI,48
|
|
2
|
+
anipy_cli/arg_parser.py,sha256=r2yxTYqKdhKvbzj8Pg00_4lmhK8mguxAVbg9G2vM-II,5299
|
|
3
|
+
anipy_cli/cli.py,sha256=Ah5BE-u_SjcuzU5dQ5cSHtUloVRApCWwTK5i49yQtH8,1922
|
|
4
|
+
anipy_cli/clis/__init__.py,sha256=Y00uiPWiMvvRImxJMvfLA55BOkMUOrrx5vJUNvquNsY,411
|
|
5
|
+
anipy_cli/clis/base_cli.py,sha256=CC8L9gKhzWpduHHdHyvuh004YbP1LUTCx3woiwV08fc,609
|
|
6
|
+
anipy_cli/clis/binge_cli.py,sha256=eSvdOo_BRSb8-Xp48ZDcelqUBlDPdyQqu8OV0vc4szo,2281
|
|
7
|
+
anipy_cli/clis/default_cli.py,sha256=lBjPAhdhIQRkvjHDsmkxrgv_T-_70YpCt6QR14g0e1o,2815
|
|
8
|
+
anipy_cli/clis/download_cli.py,sha256=MSNiLpkotdRY4SeHEmxIcSb6thNmx__ABpfCyailKz0,3197
|
|
9
|
+
anipy_cli/clis/history_cli.py,sha256=dwihSahyzXKMWhRQs3O7VuZqrd6vEeBTDy-pHdaJZU0,2676
|
|
10
|
+
anipy_cli/clis/mal_cli.py,sha256=StPRHTA3GrmcKEZXHPVYeeaZi_DJdI_DFGq-SApYjbM,2260
|
|
11
|
+
anipy_cli/clis/seasonal_cli.py,sha256=GV2TQNm9UotG1cxfYbrFFgg7Jmy8SFa7w_GlFtPdRVE,616
|
|
12
|
+
anipy_cli/colors.py,sha256=voXC7z1Fs9tHg4zzNTNMIrt9k-EVgJ3_xEf5KiW2xgo,916
|
|
13
|
+
anipy_cli/config.py,sha256=Q0RqI_aX5V5I35qT0mG7YLuxzVbMUuf3331RdSUk7E0,15404
|
|
14
|
+
anipy_cli/discord.py,sha256=c6mdqnEdblzZBYs3cGP66oDeS4ySm59OfTRP-R-Duls,1160
|
|
15
|
+
anipy_cli/mal_proxy.py,sha256=wIsku2_dl8vKD2K99L63OlzA3L5fl-VmyeiXC9VrxI4,6981
|
|
16
|
+
anipy_cli/menus/__init__.py,sha256=aIzbphxAW-QGfZwR1DIegFZuTJp1O3tSUnai0f0f4lY,185
|
|
17
|
+
anipy_cli/menus/base_menu.py,sha256=g5b9Z7SpvCxcq_vqObcPzxLwcXeGPltLgSwa0sEzyfk,1140
|
|
18
|
+
anipy_cli/menus/mal_menu.py,sha256=PJKZtvO-X-_AUIQ4NuOxLcK28EpW2AcOrIKe3N8IiIY,24091
|
|
19
|
+
anipy_cli/menus/menu.py,sha256=raHBeNQ5NNXOrTr1pCoE44FlOXWrqa1f1_kk1Rk3bH4,6179
|
|
20
|
+
anipy_cli/menus/seasonal_menu.py,sha256=VBmeXanJb-vS5TXiK79KgtJ5vPW87gIOdpN_EijAG_U,9097
|
|
21
|
+
anipy_cli/prompts.py,sha256=5h4FI0TJ8YwvVmRtfl-NBnEzPWUc7UMqgSnWuWfEVrI,6989
|
|
22
|
+
anipy_cli/util.py,sha256=FiWEfz-tQVIFMOOlUZY26fArhEPRJNhtfF0U1sT3T7Q,7700
|
|
23
|
+
anipy_cli-3.0.0.dist-info/METADATA,sha256=r9ZDgd0No0LycnTVyIGC_h67oSfbsWuYrVmp3M7Z12Y,3084
|
|
24
|
+
anipy_cli-3.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
25
|
+
anipy_cli-3.0.0.dist-info/entry_points.txt,sha256=86iXpcm_ECFndrt0JAI2mqYfXC2Ar7mGi0iOaxCrNP0,51
|
|
26
|
+
anipy_cli-3.0.0.dist-info/RECORD,,
|
anipy_cli/cli/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from anipy_cli.cli.cli import run_cli
|
anipy_cli/cli/cli.py
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from anipy_cli.misc import error, dc_presence_connect
|
|
2
|
-
from anipy_cli.arg_parser import parse_args
|
|
3
|
-
from anipy_cli.config import Config
|
|
4
|
-
from anipy_cli.colors import cprint, colors
|
|
5
|
-
from anipy_cli.cli.clis import *
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def run_cli() -> None:
|
|
9
|
-
args = parse_args()
|
|
10
|
-
|
|
11
|
-
rpc_client = None
|
|
12
|
-
if Config().dc_presence:
|
|
13
|
-
rpc_client = dc_presence_connect()
|
|
14
|
-
|
|
15
|
-
if args.config:
|
|
16
|
-
print(Config()._config_file)
|
|
17
|
-
return
|
|
18
|
-
elif args.delete:
|
|
19
|
-
try:
|
|
20
|
-
Config().history_file_path.unlink()
|
|
21
|
-
cprint(colors.RED, "Done")
|
|
22
|
-
except FileNotFoundError:
|
|
23
|
-
error("no history file found")
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
clis_dict = {
|
|
27
|
-
args.download: DownloadCli,
|
|
28
|
-
args.binge: BingeCli,
|
|
29
|
-
args.seasonal: SeasonalCli,
|
|
30
|
-
args.history: HistoryCli,
|
|
31
|
-
args.mal: MalCli,
|
|
32
|
-
args.auto_update: SeasonalCli,
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
cli_class = clis_dict.get(True, DefaultCli)
|
|
36
|
-
|
|
37
|
-
cli_class(options=args, rpc_client=rpc_client).run()
|
anipy_cli/cli/clis/__init__.py
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
from anipy_cli.cli.clis.default_cli import DefaultCli
|
|
2
|
-
from anipy_cli.cli.clis.history_cli import HistoryCli
|
|
3
|
-
from anipy_cli.cli.clis.mal_cli import MalCli
|
|
4
|
-
from anipy_cli.cli.clis.seasonal_cli import SeasonalCli
|
|
5
|
-
from anipy_cli.cli.clis.binge_cli import BingeCli
|
|
6
|
-
from anipy_cli.cli.clis.download_cli import DownloadCli
|
anipy_cli/cli/clis/base_cli.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
|
|
4
|
-
from anipy_cli.arg_parser import CliArgs
|
|
5
|
-
from anipy_cli.misc import error
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class CliBase(ABC):
|
|
9
|
-
def __init__(self, options: CliArgs, rpc_client=None):
|
|
10
|
-
self.options = options
|
|
11
|
-
self.rpc_client = rpc_client
|
|
12
|
-
|
|
13
|
-
@abstractmethod
|
|
14
|
-
def print_header(self):
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
@abstractmethod
|
|
18
|
-
def take_input(self):
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
@abstractmethod
|
|
22
|
-
def process(self):
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
@abstractmethod
|
|
26
|
-
def show(self):
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
@abstractmethod
|
|
30
|
-
def post(self):
|
|
31
|
-
pass
|
|
32
|
-
|
|
33
|
-
def run(self):
|
|
34
|
-
self.print_header()
|
|
35
|
-
self.take_input()
|
|
36
|
-
self.process()
|
|
37
|
-
self.show()
|
|
38
|
-
self.post()
|
|
39
|
-
|
|
40
|
-
@staticmethod
|
|
41
|
-
def exit(error_str: str):
|
|
42
|
-
error(error_str)
|
|
43
|
-
sys.exit()
|
anipy_cli/cli/clis/binge_cli.py
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
from anipy_cli.colors import cprint, colors
|
|
2
|
-
from anipy_cli.misc import Entry
|
|
3
|
-
from anipy_cli.query import query
|
|
4
|
-
from anipy_cli.url_handler import epHandler
|
|
5
|
-
from anipy_cli.player import get_player
|
|
6
|
-
from anipy_cli.arg_parser import CliArgs
|
|
7
|
-
from anipy_cli.cli.util import binge
|
|
8
|
-
from anipy_cli.cli.clis.base_cli import CliBase
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class BingeCli(CliBase):
|
|
12
|
-
def __init__(self, options: CliArgs, rpc_client=None):
|
|
13
|
-
super().__init__(options, rpc_client)
|
|
14
|
-
self.entry = Entry()
|
|
15
|
-
self.ep_list = []
|
|
16
|
-
self.binge_list = {}
|
|
17
|
-
self.player = get_player(self.rpc_client, self.options.optional_player)
|
|
18
|
-
|
|
19
|
-
def print_header(self):
|
|
20
|
-
cprint(colors.GREEN, "***Binge Mode***")
|
|
21
|
-
|
|
22
|
-
def take_input(self):
|
|
23
|
-
inp = input("Search: ")
|
|
24
|
-
user_query = query(inp, self.entry)
|
|
25
|
-
|
|
26
|
-
if user_query.get_links() == 0:
|
|
27
|
-
self.exit("no search results")
|
|
28
|
-
|
|
29
|
-
self.entry = user_query.pick_show()
|
|
30
|
-
self.ep_list = epHandler(self.entry).pick_range()
|
|
31
|
-
|
|
32
|
-
def process(self):
|
|
33
|
-
ep_urls = []
|
|
34
|
-
for i in self.ep_list:
|
|
35
|
-
ent = Entry()
|
|
36
|
-
ent.ep = int(i)
|
|
37
|
-
ent.category_url = self.entry.category_url
|
|
38
|
-
ep_class = epHandler(ent)
|
|
39
|
-
ent = ep_class.gen_eplink()
|
|
40
|
-
ep_urls.append(ent.ep_url)
|
|
41
|
-
|
|
42
|
-
self.binge_list = {
|
|
43
|
-
self.entry.show_name: {
|
|
44
|
-
"ep_urls": ep_urls,
|
|
45
|
-
"eps": self.ep_list,
|
|
46
|
-
"category_url": self.entry.category_url,
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
def show(self):
|
|
51
|
-
binge(self.binge_list, self.options.quality, self.player)
|
|
52
|
-
|
|
53
|
-
def post(self):
|
|
54
|
-
self.player.kill_player()
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
|
-
from anipy_cli.misc import Entry
|
|
4
|
-
from anipy_cli.query import query
|
|
5
|
-
from anipy_cli.url_handler import epHandler, videourl
|
|
6
|
-
from anipy_cli.player import get_player
|
|
7
|
-
from anipy_cli.arg_parser import CliArgs
|
|
8
|
-
from anipy_cli.cli.menus import Menu
|
|
9
|
-
from anipy_cli.cli.clis.base_cli import CliBase
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class DefaultCli(CliBase):
|
|
13
|
-
def __init__(self, options: CliArgs, rpc_client=None):
|
|
14
|
-
super().__init__(options, rpc_client)
|
|
15
|
-
|
|
16
|
-
self.entry = Entry()
|
|
17
|
-
self.player = get_player(self.rpc_client, self.options.optional_player)
|
|
18
|
-
|
|
19
|
-
def print_header(self):
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
def take_input(self):
|
|
23
|
-
inp = input("Search: ")
|
|
24
|
-
user_query = query(inp, self.entry)
|
|
25
|
-
|
|
26
|
-
if user_query.get_links() == 0:
|
|
27
|
-
self.exit("no search results")
|
|
28
|
-
|
|
29
|
-
self.entry = user_query.pick_show()
|
|
30
|
-
self.entry = epHandler(self.entry).pick_ep()
|
|
31
|
-
|
|
32
|
-
def process(self):
|
|
33
|
-
url_parser = videourl(self.entry, self.options.quality)
|
|
34
|
-
url_parser.stream_url()
|
|
35
|
-
self.entry = url_parser.get_entry()
|
|
36
|
-
|
|
37
|
-
def show(self):
|
|
38
|
-
self.player.play_title(self.entry)
|
|
39
|
-
|
|
40
|
-
def post(self):
|
|
41
|
-
Menu(
|
|
42
|
-
options=self.options,
|
|
43
|
-
entry=self.entry,
|
|
44
|
-
player=self.player,
|
|
45
|
-
rpc_client=self.rpc_client,
|
|
46
|
-
).run()
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
from copy import deepcopy
|
|
2
|
-
|
|
3
|
-
from anipy_cli.arg_parser import CliArgs
|
|
4
|
-
from anipy_cli.config import Config
|
|
5
|
-
from anipy_cli.colors import cprint, colors
|
|
6
|
-
from anipy_cli.misc import Entry, parsenum
|
|
7
|
-
from anipy_cli.query import query
|
|
8
|
-
from anipy_cli.url_handler import videourl, epHandler
|
|
9
|
-
from anipy_cli.download import download
|
|
10
|
-
from anipy_cli.cli.util import get_season_searches
|
|
11
|
-
from anipy_cli.cli.clis.base_cli import CliBase
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class DownloadCli(CliBase):
|
|
15
|
-
def __init__(self, options: CliArgs, rpc_client=None):
|
|
16
|
-
super().__init__(options, rpc_client)
|
|
17
|
-
|
|
18
|
-
self.entry = Entry()
|
|
19
|
-
self.show_entries = []
|
|
20
|
-
self.dl_path = Config().download_folder_path
|
|
21
|
-
if options.location:
|
|
22
|
-
self.dl_path = options.location
|
|
23
|
-
|
|
24
|
-
def print_header(self):
|
|
25
|
-
cprint(colors.GREEN, "***Download Mode***")
|
|
26
|
-
cprint(colors.GREEN, "Downloads are stored in: ", colors.END, str(self.dl_path))
|
|
27
|
-
|
|
28
|
-
def take_input(self):
|
|
29
|
-
is_season_search = False
|
|
30
|
-
|
|
31
|
-
searches = []
|
|
32
|
-
if (
|
|
33
|
-
not self.options.no_season_search
|
|
34
|
-
and input("Search MyAnimeList for anime in Season? (y|n): \n>> ") == "y"
|
|
35
|
-
):
|
|
36
|
-
searches = get_season_searches()
|
|
37
|
-
|
|
38
|
-
else:
|
|
39
|
-
another = "y"
|
|
40
|
-
while another == "y":
|
|
41
|
-
searches.append(input("Search: "))
|
|
42
|
-
another = input("Add another search: (y|n)\n")
|
|
43
|
-
|
|
44
|
-
for search in searches:
|
|
45
|
-
links = 0
|
|
46
|
-
query_class = None
|
|
47
|
-
if isinstance(search, dict):
|
|
48
|
-
is_season_search = True
|
|
49
|
-
links = [search["category_url"]]
|
|
50
|
-
|
|
51
|
-
else:
|
|
52
|
-
print("\nCurrent: ", search)
|
|
53
|
-
query_class = query(search, self.entry)
|
|
54
|
-
query_class.get_pages()
|
|
55
|
-
links = query_class.get_links()
|
|
56
|
-
|
|
57
|
-
if links == 0:
|
|
58
|
-
self.exit("no search results")
|
|
59
|
-
|
|
60
|
-
if is_season_search:
|
|
61
|
-
self.entry = Entry()
|
|
62
|
-
self.entry.show_name = search["name"]
|
|
63
|
-
self.entry.category_url = search["category_url"]
|
|
64
|
-
|
|
65
|
-
else:
|
|
66
|
-
self.entry = query_class.pick_show()
|
|
67
|
-
|
|
68
|
-
ep_class = epHandler(self.entry)
|
|
69
|
-
ep_list = ep_class.pick_range()
|
|
70
|
-
self.show_entries.append(
|
|
71
|
-
{"show_entry": deepcopy(self.entry), "ep_list": deepcopy(ep_list)}
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
def process(self):
|
|
75
|
-
for ent in self.show_entries:
|
|
76
|
-
entry = ent["show_entry"]
|
|
77
|
-
ep_list = ent["ep_list"]
|
|
78
|
-
for i in ep_list:
|
|
79
|
-
entry.ep = parsenum(i)
|
|
80
|
-
entry.embed_url = ""
|
|
81
|
-
ep_class = epHandler(entry)
|
|
82
|
-
entry = ep_class.gen_eplink()
|
|
83
|
-
url_class = videourl(entry, self.options.quality)
|
|
84
|
-
url_class.stream_url()
|
|
85
|
-
entry = url_class.get_entry()
|
|
86
|
-
download(entry, self.options.quality, self.options.ffmpeg).download()
|
|
87
|
-
|
|
88
|
-
def show(self):
|
|
89
|
-
pass
|
|
90
|
-
|
|
91
|
-
def post(self):
|
|
92
|
-
pass
|