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
anipy_cli/config.py
CHANGED
|
@@ -1,165 +1,474 @@
|
|
|
1
|
-
import yaml
|
|
2
1
|
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
import os
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from
|
|
5
|
-
from
|
|
5
|
+
from string import Template
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple, Type
|
|
6
7
|
|
|
8
|
+
import yaml
|
|
9
|
+
from appdirs import user_config_dir, user_data_dir
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
pass
|
|
11
|
+
from anipy_cli import __appname__, __version__
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class Config:
|
|
13
15
|
def __init__(self):
|
|
14
16
|
self._config_file, self._yaml_conf = Config._read_config()
|
|
15
|
-
|
|
17
|
+
|
|
16
18
|
if not self._yaml_conf:
|
|
17
19
|
self._yaml_conf = {}
|
|
20
|
+
self._create_config() # Create config file
|
|
18
21
|
|
|
19
22
|
@property
|
|
20
|
-
def
|
|
21
|
-
|
|
23
|
+
def user_files_path(self) -> Path:
|
|
24
|
+
"""Path to user files, this includes history, seasonals files and more.
|
|
25
|
+
|
|
26
|
+
You may use `~` or environment vars in your path.
|
|
27
|
+
"""
|
|
22
28
|
|
|
23
|
-
@property
|
|
24
|
-
def download_folder_path(self):
|
|
25
29
|
return self._get_path_value(
|
|
26
|
-
"
|
|
30
|
+
"user_files_path", Path(user_data_dir(__appname__, appauthor=False))
|
|
27
31
|
)
|
|
28
32
|
|
|
29
33
|
@property
|
|
30
|
-
def
|
|
34
|
+
def _history_file_path(self) -> Path:
|
|
35
|
+
return self.user_files_path / "history.json"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def _seasonal_file_path(self) -> Path:
|
|
39
|
+
return self.user_files_path / "seasonals.json"
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def _mal_local_user_list_path(self) -> Path:
|
|
43
|
+
return self.user_files_path / "mal_list.json"
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def _anilist_local_user_list_path(self) -> Path:
|
|
47
|
+
return self.user_files_path / "anilist_list.json"
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def download_folder_path(self) -> Path:
|
|
51
|
+
"""Path to your download folder/directory.
|
|
52
|
+
|
|
53
|
+
You may use `~` or environment vars in your path.
|
|
54
|
+
"""
|
|
31
55
|
return self._get_path_value(
|
|
32
|
-
"
|
|
56
|
+
"download_folder_path", self.user_files_path / "download"
|
|
33
57
|
)
|
|
34
58
|
|
|
35
59
|
@property
|
|
36
|
-
def
|
|
60
|
+
def seasonals_dl_path(self) -> Path:
|
|
61
|
+
"""Path to your seasonal downloads directory.
|
|
62
|
+
|
|
63
|
+
You may use `~` or environment vars in your path.
|
|
64
|
+
"""
|
|
37
65
|
return self._get_path_value(
|
|
38
|
-
"
|
|
66
|
+
"seasonals_dl_path", self.download_folder_path / "seasonals"
|
|
39
67
|
)
|
|
40
68
|
|
|
41
69
|
@property
|
|
42
|
-
def
|
|
43
|
-
|
|
70
|
+
def providers(self) -> Dict[str, List[str]]:
|
|
71
|
+
"""A list of pairs defining which providers will search for anime
|
|
72
|
+
in different parts of the program. Configurable areas are as follows:
|
|
73
|
+
default (and history), download (-D), seasonal (-S), binge (-B), anilist (-A)
|
|
74
|
+
and mal (-M) The example will show you how it is done! Please note that for seasonal
|
|
75
|
+
search always the first provider that supports it is used.
|
|
76
|
+
|
|
77
|
+
For an updated list of providers look here: https://sdaqo.github.io/anipy-cli/availabilty
|
|
78
|
+
|
|
79
|
+
Supported providers (as of $version): allanime, animekai (animekai is not functional for now), native (filesystem provider, set root in provider_urls config)
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
providers:
|
|
83
|
+
default: ["provider1"] # used in default mode and for the history
|
|
84
|
+
download: ["provider2"]
|
|
85
|
+
seasonal: ["provider3"]
|
|
86
|
+
binge: ["provider4"]
|
|
87
|
+
mal: ["provider2", "provider3"]
|
|
88
|
+
anilist: ["provider1"]
|
|
89
|
+
"""
|
|
90
|
+
defaults = {
|
|
91
|
+
"default": ["allanime"],
|
|
92
|
+
"download": ["allanime"],
|
|
93
|
+
"seasonal": ["allanime"],
|
|
94
|
+
"binge": ["allanime"],
|
|
95
|
+
"mal": ["allanime"],
|
|
96
|
+
"anilist": ["allanime"],
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
value = self._get_value("providers", defaults, dict)
|
|
100
|
+
|
|
101
|
+
# Merge Dicts
|
|
102
|
+
defaults.update(value)
|
|
103
|
+
return defaults
|
|
44
104
|
|
|
45
105
|
@property
|
|
46
|
-
def
|
|
47
|
-
|
|
106
|
+
def provider_urls(self) -> Dict[str, str]:
|
|
107
|
+
"""A list of pairs to override the default urls that providers use.
|
|
48
108
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
109
|
+
Note: the native provider accepts a path instead of a url, it defaults to ~/Videos
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
provider_urls:
|
|
113
|
+
gogoanime: "https://gogoanime3.co"
|
|
114
|
+
native: "~/Videos"
|
|
115
|
+
provider_urls: {} # do not override any urls
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
return self._get_value("provider_urls", {}, dict)
|
|
52
119
|
|
|
53
120
|
@property
|
|
54
|
-
def player_path(self):
|
|
55
|
-
|
|
121
|
+
def player_path(self) -> Path:
|
|
122
|
+
"""
|
|
123
|
+
Path to your video player.
|
|
124
|
+
For a list of supported players look here: https://sdaqo.github.io/anipy-cli/availabilty
|
|
125
|
+
|
|
126
|
+
Supported players (as of $version): mpv, vlc, syncplay, mpvnet, mpv-controlled
|
|
127
|
+
|
|
128
|
+
Info for mpv-controlled:
|
|
129
|
+
Reuse the mpv window instead of closing and reopening.
|
|
130
|
+
This uses python-mpv, which uses libmpv, on linux this is (normally) preinstalled
|
|
131
|
+
with mpv, on windows you have to get the mpv-2.dll file from here:
|
|
132
|
+
https://sourceforge.net/projects/mpv-player-windows/files/libmpv/
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
player_path: /usr/bin/syncplay # full path
|
|
136
|
+
player_path: syncplay # if in PATH this also works
|
|
137
|
+
player_path: C:\\\\Programms\\mpv\\mpv.exe # on windows path with .exe
|
|
138
|
+
player_path: mpv-controlled # recycle your mpv windows!
|
|
139
|
+
"""
|
|
140
|
+
return self._get_path_value("player_path", Path("mpv"))
|
|
56
141
|
|
|
57
142
|
@property
|
|
58
|
-
def mpv_commandline_options(self):
|
|
143
|
+
def mpv_commandline_options(self) -> List[str]:
|
|
144
|
+
"""Extra commandline arguments for mpv and derivative.
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
mpv_commandline_options: ["--keep-open=no", "--fs=yes"]
|
|
148
|
+
"""
|
|
59
149
|
return self._get_value("mpv_commandline_options", ["--keep-open=no"], list)
|
|
60
150
|
|
|
61
151
|
@property
|
|
62
|
-
def vlc_commandline_options(self):
|
|
152
|
+
def vlc_commandline_options(self) -> List[str]:
|
|
153
|
+
"""Extra commandline arguments for vlc.
|
|
154
|
+
|
|
155
|
+
Examples:
|
|
156
|
+
vlc_commandline_options: ["--fullscreen"]
|
|
157
|
+
"""
|
|
63
158
|
return self._get_value("vlc_commandline_options", [], list)
|
|
64
159
|
|
|
65
160
|
@property
|
|
66
|
-
def
|
|
161
|
+
def iina_commandline_options(self) -> List[str]:
|
|
162
|
+
"""Extra commandline arguments for iina.
|
|
163
|
+
|
|
164
|
+
Examples:
|
|
165
|
+
iina_commandline_options: ["--mpv-fullscreen"]
|
|
166
|
+
"""
|
|
167
|
+
return self._get_value("iina_commandline_options", [], list)
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def reuse_mpv_window(self) -> bool:
|
|
171
|
+
"""DEPRECATED This option was deprecated in 3.0.0, please use `mpv-
|
|
172
|
+
controlled` in the `player_path` instead!
|
|
173
|
+
|
|
174
|
+
Reuse the mpv window instead of closing and reopening. This uses
|
|
175
|
+
python-mpv, which uses libmpv, on linux this is (normally)
|
|
176
|
+
preinstalled with mpv, on windows you have to get the mpv-2.dll
|
|
177
|
+
file from here:
|
|
178
|
+
https://sourceforge.net/projects/mpv-player-windows/files/libmpv/
|
|
179
|
+
"""
|
|
67
180
|
return self._get_value("reuse_mpv_window", False, bool)
|
|
68
181
|
|
|
69
182
|
@property
|
|
70
|
-
def ffmpeg_hls(self):
|
|
183
|
+
def ffmpeg_hls(self) -> bool:
|
|
184
|
+
"""Always use ffmpeg to download m3u8 playlists instead of the internal
|
|
185
|
+
downloader.
|
|
186
|
+
|
|
187
|
+
To temporarily enable this use the `--ffmpeg` command line flag.
|
|
188
|
+
"""
|
|
71
189
|
return self._get_value("ffmpeg_hls", False, bool)
|
|
72
190
|
|
|
73
191
|
@property
|
|
74
|
-
def
|
|
75
|
-
|
|
192
|
+
def remux_to(self) -> Optional[str]:
|
|
193
|
+
"""
|
|
194
|
+
Remux resulting download to a specific container using ffmpeg.
|
|
195
|
+
You can use about any conatainer supported by ffmpeg: `.your-container`.
|
|
196
|
+
|
|
197
|
+
Examples:
|
|
198
|
+
remux_to: .mkv # remux all downloads to .mkv container
|
|
199
|
+
remux_to .mp4 # downloads with ffmpeg default to a .mp4 container,
|
|
200
|
+
with this option the internal downloader's downloads also get remuxed
|
|
201
|
+
remux_to: null or remux_to: "" # do not remux
|
|
202
|
+
"""
|
|
203
|
+
return self._get_value("remux_to", None, str)
|
|
76
204
|
|
|
77
205
|
@property
|
|
78
|
-
def download_name_format(self):
|
|
79
|
-
|
|
80
|
-
|
|
206
|
+
def download_name_format(self) -> str:
|
|
207
|
+
"""
|
|
208
|
+
Specify the name format of a download, available fields are:
|
|
209
|
+
show_name: name of the show/anime
|
|
210
|
+
episode_number: number of the episode
|
|
211
|
+
quality: quality/resolution of the video
|
|
212
|
+
provider: provider used to download
|
|
213
|
+
type: this field is populated with `dub` if the episode is in dub format or `sub` otherwise
|
|
214
|
+
|
|
215
|
+
The fields should be set in curly braces i.e. `{field_name}`.
|
|
216
|
+
Do not add a suffix (e.g. '.mp4') here, if you want to change this
|
|
217
|
+
look at the `remux_to` config option.
|
|
218
|
+
|
|
219
|
+
You have to at least use episode_number in the format or else, while downloading,
|
|
220
|
+
perceding episodes of the same series will be skipped because the file name will be the same.
|
|
221
|
+
|
|
222
|
+
Examples:
|
|
223
|
+
download_name_format: "[{provider}] {show_name} E{episode_number} [{type}][{quality}p]"
|
|
224
|
+
download_name_format: "{show_name}_{episode_number}"
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
# Remove suffix for past 3.0.0 versions
|
|
229
|
+
value = self._get_value(
|
|
230
|
+
"download_name_format", "{show_name}_{episode_number}", str
|
|
81
231
|
)
|
|
232
|
+
return str(Path(value).with_suffix(""))
|
|
82
233
|
|
|
83
234
|
@property
|
|
84
|
-
def
|
|
85
|
-
|
|
235
|
+
def post_download_scripts(self) -> Dict[str, List[str]]:
|
|
236
|
+
"""With this option you can define scripts that run after a file
|
|
237
|
+
has been downloaded. As with the 'providers' option, you can configure
|
|
238
|
+
different behaviour, depending on which part of anipy-cli the download occurs.
|
|
239
|
+
Configurable areas are as follows: default (and history), download (-D), seasonal (-S),
|
|
240
|
+
anilist (-A) and mal (-M). The example will show you how it is done! Please note that
|
|
241
|
+
if you define several scripts for one area, they will run in the order you put them in the list.
|
|
242
|
+
You can also define a timeout (in seconds), after which a script will be terminated,
|
|
243
|
+
if set to null there will be no timeout and any script will run forever.
|
|
244
|
+
|
|
245
|
+
A "script" is a path to an executable file which accepts following parameters (in this order):
|
|
246
|
+
1. Path to the file
|
|
247
|
+
2. Name of series
|
|
248
|
+
3. Episode
|
|
249
|
+
4. Provider
|
|
250
|
+
5. Quality
|
|
251
|
+
6. Language profile
|
|
252
|
+
|
|
253
|
+
Examples:
|
|
254
|
+
post_download_scripts:
|
|
255
|
+
default: [] # used in default mode and for the history
|
|
256
|
+
download: ["/scripts/send_notification.sh", "/scripts/move_and_rename.sh"]
|
|
257
|
+
seasonal: ["link_to_jellyfin.bat", "jellyfin_library_update.exe"] # All executable files should work, including windows specific
|
|
258
|
+
mal: ["hard_link_to_shoko"]
|
|
259
|
+
timeout: 60 # terminate any script after running for 60 seconds
|
|
260
|
+
"""
|
|
261
|
+
defaults = {
|
|
262
|
+
"default": [],
|
|
263
|
+
"download": [],
|
|
264
|
+
"seasonal": [],
|
|
265
|
+
"mal": [],
|
|
266
|
+
"anilist": [],
|
|
267
|
+
"timeout": None,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
value = self._get_value("post_download_scripts", defaults, dict)
|
|
271
|
+
|
|
272
|
+
# Merge Dicts
|
|
273
|
+
defaults.update(value)
|
|
274
|
+
return defaults
|
|
86
275
|
|
|
87
276
|
@property
|
|
88
|
-
def dc_presence(self):
|
|
277
|
+
def dc_presence(self) -> bool:
|
|
278
|
+
"""Activate discord presence, only works with discord open."""
|
|
89
279
|
return self._get_value("dc_presence", False, bool)
|
|
90
280
|
|
|
91
281
|
@property
|
|
92
|
-
def auto_open_dl_defaultcli(self):
|
|
93
|
-
|
|
282
|
+
def auto_open_dl_defaultcli(self) -> bool:
|
|
283
|
+
"""This automatically opens the downloaded file if downloaded through
|
|
284
|
+
the `d` option in the default cli."""
|
|
285
|
+
return self._get_value("auto_open_dl_defaultcli", True, bool)
|
|
94
286
|
|
|
95
287
|
@property
|
|
96
|
-
def
|
|
97
|
-
|
|
288
|
+
def mal_user(self) -> str:
|
|
289
|
+
"""Your MyAnimeList username for MAL mode."""
|
|
290
|
+
return self._get_value("mal_user", "", str)
|
|
98
291
|
|
|
99
292
|
@property
|
|
100
|
-
def
|
|
101
|
-
|
|
293
|
+
def anilist_token(self) -> str:
|
|
294
|
+
"""Your AniList access token for AniList mode."""
|
|
295
|
+
return self._get_value("anilist_token", "", str)
|
|
102
296
|
|
|
103
297
|
@property
|
|
104
|
-
def mal_password(self):
|
|
298
|
+
def mal_password(self) -> str:
|
|
299
|
+
"""Your MyAnimeList password for MAL mode.
|
|
300
|
+
|
|
301
|
+
The password may also be passed via the `--mal-password <pwd>`
|
|
302
|
+
commandline option.
|
|
303
|
+
"""
|
|
105
304
|
return self._get_value("mal_password", "", str)
|
|
106
305
|
|
|
107
306
|
@property
|
|
108
|
-
def
|
|
307
|
+
def tracker_ignore_tag(self) -> str:
|
|
308
|
+
"""All anime in your MyAnimeList with this tag will be ignored by
|
|
309
|
+
anipy-cli.
|
|
310
|
+
|
|
311
|
+
Examples:
|
|
312
|
+
tracker_ignore_tag: ignore # all anime with ignore tag will be ignored
|
|
313
|
+
tracker_ignore_tag: "" # no anime will be ignored
|
|
314
|
+
"""
|
|
315
|
+
return self._get_value("tracker_ignore_tag", "ignore", str)
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def tracker_dub_tag(self) -> str:
|
|
319
|
+
"""All anime in your Anime Tracker with this tag will be switched over to
|
|
320
|
+
dub in tracker mode, if the dub is available. If you do not specify a tag,
|
|
321
|
+
anipy-cli will use `preferred_type` to choose dub or sub in tracker mode.
|
|
322
|
+
|
|
323
|
+
Examples:
|
|
324
|
+
tracker_dub_tag: dub # all anime with this tag will be switched to dub
|
|
325
|
+
tracker_dub_tag: "" # no anime will be switched to dub, except you have preferred_type on dub
|
|
326
|
+
"""
|
|
327
|
+
return self._get_value("tracker_dub_tag", "dub", str)
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def tracker_tags(self) -> List[str]:
|
|
331
|
+
"""Custom tags to tag all anime in your Anime Tracker that are
|
|
332
|
+
altered/added by anipy-cli.
|
|
333
|
+
|
|
334
|
+
Examples:
|
|
335
|
+
tracker_tags: ["anipy-cli"] # tag all anime with anipy-cli
|
|
336
|
+
tracker_tags: ["anipy-cli", "important"] # tag all anime with anipy-cli and important
|
|
337
|
+
tracker_tags: null or tracker_tags: [] # Do not tag the anime
|
|
338
|
+
"""
|
|
339
|
+
return self._get_value("tracker_tags", [], list)
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def tracker_status_categories(self) -> List[str]:
|
|
343
|
+
"""Status categories of your Anime Tracker that anipy-cli uses for
|
|
344
|
+
downloading/watching new episodes listing anime in your list and stuff
|
|
345
|
+
like that. Normally the watching catagory should be enough as you would
|
|
346
|
+
normally put anime you currently watch in the watching catagory.
|
|
347
|
+
|
|
348
|
+
Valid values are: watching, completed, on_hold, dropped, plan_to_watch
|
|
349
|
+
"""
|
|
350
|
+
return self._get_value("tracker_status_categories", ["watching"], list)
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def tracker_mapping_min_similarity(self) -> float:
|
|
354
|
+
"""
|
|
355
|
+
The minumum similarity between titles when mapping anime in tracker mode.
|
|
356
|
+
This is a decimal number from 0 - 1, 1 meaning 100% match and 0 meaning all characters are different.
|
|
357
|
+
If the similarity of a map is below the threshold you will be prompted for a manual map.
|
|
358
|
+
|
|
359
|
+
So in summary:
|
|
360
|
+
higher number: more exact matching, but more manual mapping
|
|
361
|
+
lower number: less exact matching, but less manual mapping
|
|
362
|
+
|
|
363
|
+
If you are interested, the algorithm being used here is this: https://en.wikipedia.org/wiki/Levenshtein_distance
|
|
364
|
+
"""
|
|
365
|
+
return self._get_value("tracker_mapping_min_similarity", 0.8, float)
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def tracker_mapping_use_alternatives(self) -> bool:
|
|
369
|
+
"""Check alternative names when mapping anime.
|
|
370
|
+
|
|
371
|
+
If turned on this will slow down mapping but provide better
|
|
372
|
+
chances of finding a match.
|
|
373
|
+
"""
|
|
374
|
+
return self._get_value("tracker_mapping_use_alternatives", True, bool)
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def tracker_mapping_use_filters(self) -> bool:
|
|
378
|
+
"""Use filters (e.g. year, season etc.) of providers to narrow down the
|
|
379
|
+
results, this will lead to more accurate mapping, but provide wrong
|
|
380
|
+
results if the filters of the provider do not work properly or if anime
|
|
381
|
+
are not correctly marked with the correct data."""
|
|
382
|
+
return self._get_value("tracker_mapping_use_filters", True, bool)
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def auto_sync_mal_to_seasonals(self) -> bool:
|
|
386
|
+
"""DEPRECATED This option was deprecated in 3.0.0, please consider
|
|
387
|
+
using the `--mal-sync-seasonals` cli option in compination with `-M`
|
|
388
|
+
instead.
|
|
389
|
+
|
|
390
|
+
Automatically sync MyAnimeList to Seasonals list.
|
|
391
|
+
"""
|
|
109
392
|
return self._get_value("auto_sync_mal_to_seasonals", False, bool)
|
|
110
393
|
|
|
111
394
|
@property
|
|
112
|
-
def auto_map_mal_to_gogo(self):
|
|
395
|
+
def auto_map_mal_to_gogo(self) -> bool:
|
|
113
396
|
return self._get_value("auto_map_mal_to_gogo", False, bool)
|
|
114
397
|
|
|
115
398
|
@property
|
|
116
|
-
def
|
|
117
|
-
|
|
399
|
+
def preferred_type(self) -> Optional[str]:
|
|
400
|
+
"""Specify which anime types (dub or sub) you prefer. If this is
|
|
401
|
+
specified, you will not be asked to switch to dub anymore. You can
|
|
402
|
+
however always switch to either in the menu.
|
|
403
|
+
|
|
404
|
+
Examples:
|
|
405
|
+
preferred_type: sub
|
|
406
|
+
preferred_type: dub
|
|
407
|
+
preferred_type: null or preferred_type: "" # always ask
|
|
408
|
+
"""
|
|
409
|
+
return self._get_value("preferred_type", None, str)
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def skip_season_search(self) -> bool:
|
|
413
|
+
"""If this is set to true you will not be prompted to search in season."""
|
|
414
|
+
return self._get_value("skip_season_search", False, bool)
|
|
118
415
|
|
|
119
416
|
@property
|
|
120
|
-
def
|
|
121
|
-
|
|
417
|
+
def assume_season_search(self) -> bool:
|
|
418
|
+
"""If this is set to true, the system will assume you want to search in season.
|
|
419
|
+
If skip_season_search is true, this will be ignored)"""
|
|
420
|
+
return self._get_value("assume_season_search", False, bool)
|
|
122
421
|
|
|
123
422
|
def _get_path_value(self, key: str, fallback: Path) -> Path:
|
|
124
423
|
path = self._get_value(key, fallback, str)
|
|
125
424
|
try:
|
|
126
|
-
|
|
127
|
-
|
|
425
|
+
# os.path.expanduser is equivalent to Path().expanduser()
|
|
426
|
+
# But because pathlib doesn't have expandvars(), we resort
|
|
427
|
+
# to using the os module inside the Path constructor
|
|
428
|
+
return Path(os.path.expandvars(path)).expanduser()
|
|
429
|
+
except RuntimeError:
|
|
128
430
|
return fallback
|
|
129
431
|
|
|
130
|
-
def _get_value(self, key: str, fallback,
|
|
432
|
+
def _get_value(self, key: str, fallback: Any, _type: Type) -> Any:
|
|
131
433
|
value = self._yaml_conf.get(key, fallback)
|
|
132
|
-
if isinstance(value,
|
|
434
|
+
if isinstance(value, _type):
|
|
133
435
|
return value
|
|
134
436
|
|
|
135
437
|
return fallback
|
|
136
438
|
|
|
137
439
|
def _create_config(self):
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
440
|
+
self._get_config_path().mkdir(exist_ok=True, parents=True)
|
|
441
|
+
self._config_file.touch()
|
|
442
|
+
|
|
443
|
+
dump = ""
|
|
444
|
+
# generate config based on attrs and default values of config class
|
|
445
|
+
for attribute, value in Config.__dict__.items():
|
|
446
|
+
if attribute.startswith("_"):
|
|
447
|
+
continue
|
|
448
|
+
|
|
449
|
+
if not isinstance(value, property):
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
doc = inspect.getdoc(value)
|
|
453
|
+
if doc:
|
|
454
|
+
# Add docstrings
|
|
455
|
+
doc = Template(doc).safe_substitute(version=__version__)
|
|
456
|
+
doc = "\n".join([f"# {line}" for line in doc.split("\n")])
|
|
457
|
+
dump = dump + doc + "\n"
|
|
458
|
+
|
|
459
|
+
val = self.__getattribute__(attribute)
|
|
460
|
+
val = str(val) if isinstance(val, Path) else val
|
|
461
|
+
dump = (
|
|
462
|
+
dump
|
|
463
|
+
+ yaml.dump({attribute: val}, indent=4, default_flow_style=False)
|
|
464
|
+
+ "\n"
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
self._config_file.write_text(dump)
|
|
468
|
+
|
|
160
469
|
@staticmethod
|
|
161
470
|
@functools.lru_cache
|
|
162
|
-
def _read_config():
|
|
471
|
+
def _read_config() -> Tuple[Path, dict[str, Any]]:
|
|
163
472
|
config_file = Config._get_config_path() / "config.yaml"
|
|
164
473
|
try:
|
|
165
474
|
with config_file.open("r") as conf:
|
|
@@ -167,21 +476,9 @@ class Config:
|
|
|
167
476
|
except FileNotFoundError:
|
|
168
477
|
# There is no config file, create one
|
|
169
478
|
yaml_conf = {}
|
|
170
|
-
|
|
171
|
-
return config_file, yaml_conf
|
|
172
479
|
|
|
480
|
+
return config_file, yaml_conf
|
|
173
481
|
|
|
174
482
|
@staticmethod
|
|
175
483
|
def _get_config_path() -> Path:
|
|
176
|
-
|
|
177
|
-
windows_path = Path().home() / "AppData" / "Local" / "anipy-cli"
|
|
178
|
-
macos_path = Path().home() / ".config" / "anipy-cli"
|
|
179
|
-
|
|
180
|
-
if platform == "linux":
|
|
181
|
-
return linux_path
|
|
182
|
-
elif platform == "darwin":
|
|
183
|
-
return macos_path
|
|
184
|
-
elif platform == "win32":
|
|
185
|
-
return windows_path
|
|
186
|
-
else:
|
|
187
|
-
raise SysNotFoundError(platform)
|
|
484
|
+
return Path(user_config_dir(__appname__, appauthor=False))
|
anipy_cli/discord.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import functools
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from pypresence import Presence
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from anipy_api.provider import ProviderStream
|
|
9
|
+
from anipy_api.anime import Anime
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@functools.lru_cache(maxsize=None)
|
|
13
|
+
class DiscordPresence(object):
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.rpc_client = Presence(966365883691855942)
|
|
16
|
+
self.rpc_client.connect()
|
|
17
|
+
|
|
18
|
+
def dc_presence_callback(self, anime: "Anime", stream: "ProviderStream"):
|
|
19
|
+
anime_info = anime.get_info()
|
|
20
|
+
self.rpc_client.update(
|
|
21
|
+
details=f"Watching {anime.name} via anipy-cli",
|
|
22
|
+
state=f"Episode {stream.episode}/{anime.get_episodes(stream.language)[-1]}",
|
|
23
|
+
large_image=anime_info.image or "",
|
|
24
|
+
small_image="https://raw.githubusercontent.com/sdaqo/anipy-cli/master/docs/assets/anipy-logo-dark-compact.png",
|
|
25
|
+
large_text=anime.name,
|
|
26
|
+
small_text="anipy-cli",
|
|
27
|
+
start=int(time.time()),
|
|
28
|
+
buttons=[
|
|
29
|
+
{
|
|
30
|
+
"label": "Check out anipy-cli",
|
|
31
|
+
"url": "https://github.com/sdaqo/anipy-cli",
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
)
|