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.
Files changed (67) hide show
  1. anipy_cli/__init__.py +2 -20
  2. anipy_cli/anilist_proxy.py +229 -0
  3. anipy_cli/arg_parser.py +109 -21
  4. anipy_cli/cli.py +98 -0
  5. anipy_cli/clis/__init__.py +17 -0
  6. anipy_cli/clis/anilist_cli.py +62 -0
  7. anipy_cli/clis/base_cli.py +34 -0
  8. anipy_cli/clis/binge_cli.py +96 -0
  9. anipy_cli/clis/default_cli.py +115 -0
  10. anipy_cli/clis/download_cli.py +85 -0
  11. anipy_cli/clis/history_cli.py +96 -0
  12. anipy_cli/clis/mal_cli.py +71 -0
  13. anipy_cli/{cli/clis → clis}/seasonal_cli.py +9 -6
  14. anipy_cli/colors.py +14 -8
  15. anipy_cli/config.py +387 -90
  16. anipy_cli/discord.py +34 -0
  17. anipy_cli/download_component.py +194 -0
  18. anipy_cli/logger.py +200 -0
  19. anipy_cli/mal_proxy.py +228 -0
  20. anipy_cli/menus/__init__.py +6 -0
  21. anipy_cli/menus/anilist_menu.py +671 -0
  22. anipy_cli/{cli/menus → menus}/base_menu.py +9 -14
  23. anipy_cli/menus/mal_menu.py +657 -0
  24. anipy_cli/menus/menu.py +265 -0
  25. anipy_cli/menus/seasonal_menu.py +270 -0
  26. anipy_cli/prompts.py +387 -0
  27. anipy_cli/util.py +268 -0
  28. anipy_cli-3.8.2.dist-info/METADATA +71 -0
  29. anipy_cli-3.8.2.dist-info/RECORD +31 -0
  30. {anipy_cli-2.7.17.dist-info → anipy_cli-3.8.2.dist-info}/WHEEL +1 -2
  31. anipy_cli-3.8.2.dist-info/entry_points.txt +3 -0
  32. anipy_cli/cli/__init__.py +0 -1
  33. anipy_cli/cli/cli.py +0 -37
  34. anipy_cli/cli/clis/__init__.py +0 -6
  35. anipy_cli/cli/clis/base_cli.py +0 -43
  36. anipy_cli/cli/clis/binge_cli.py +0 -54
  37. anipy_cli/cli/clis/default_cli.py +0 -46
  38. anipy_cli/cli/clis/download_cli.py +0 -92
  39. anipy_cli/cli/clis/history_cli.py +0 -64
  40. anipy_cli/cli/clis/mal_cli.py +0 -27
  41. anipy_cli/cli/menus/__init__.py +0 -3
  42. anipy_cli/cli/menus/mal_menu.py +0 -411
  43. anipy_cli/cli/menus/menu.py +0 -102
  44. anipy_cli/cli/menus/seasonal_menu.py +0 -174
  45. anipy_cli/cli/util.py +0 -118
  46. anipy_cli/download.py +0 -454
  47. anipy_cli/history.py +0 -83
  48. anipy_cli/mal.py +0 -645
  49. anipy_cli/misc.py +0 -227
  50. anipy_cli/player/__init__.py +0 -1
  51. anipy_cli/player/player.py +0 -33
  52. anipy_cli/player/players/__init__.py +0 -3
  53. anipy_cli/player/players/base.py +0 -106
  54. anipy_cli/player/players/mpv.py +0 -19
  55. anipy_cli/player/players/mpv_contrl.py +0 -37
  56. anipy_cli/player/players/syncplay.py +0 -19
  57. anipy_cli/player/players/vlc.py +0 -18
  58. anipy_cli/query.py +0 -92
  59. anipy_cli/run_anipy_cli.py +0 -14
  60. anipy_cli/seasonal.py +0 -106
  61. anipy_cli/url_handler.py +0 -442
  62. anipy_cli/version.py +0 -1
  63. anipy_cli-2.7.17.dist-info/LICENSE +0 -674
  64. anipy_cli-2.7.17.dist-info/METADATA +0 -159
  65. anipy_cli-2.7.17.dist-info/RECORD +0 -43
  66. anipy_cli-2.7.17.dist-info/entry_points.txt +0 -2
  67. anipy_cli-2.7.17.dist-info/top_level.txt +0 -1
anipy_cli/cli/util.py DELETED
@@ -1,118 +0,0 @@
1
- from anipy_cli.colors import cprint, colors, cinput
2
- from anipy_cli.misc import Entry, search_in_season_on_gogo, print_names
3
- from anipy_cli.url_handler import epHandler, videourl
4
- from anipy_cli.mal import MAL
5
- from anipy_cli.seasonal import Seasonal
6
- from anipy_cli.player import PlayerBaseType
7
-
8
-
9
- def binge(ep_list, quality, player: PlayerBaseType, mode="", mal_class: MAL = None):
10
- """
11
- TODO: bruh what is this, let this accept a list of Entry
12
- Accepts ep_list like so:
13
- {"name" {'ep_urls': [], 'eps': [], 'category_url': }, "next_anime"...}
14
- """
15
- cprint(colors.RED, "To quit press CTRL+C")
16
- try:
17
- for i in ep_list:
18
- print(i)
19
- show_entry = Entry()
20
- show_entry.show_name = i
21
- show_entry.category_url = ep_list[i]["category_url"]
22
- show_entry.latest_ep = epHandler(show_entry).get_latest()
23
- for url, ep in zip(ep_list[i]["ep_urls"], ep_list[i]["eps"]):
24
- show_entry.ep = ep
25
- show_entry.embed_url = ""
26
- show_entry.ep_url = url
27
- cprint(
28
- colors.GREEN,
29
- "Fetching links for: ",
30
- colors.END,
31
- show_entry.show_name,
32
- colors.RED,
33
- f""" | EP: {
34
- show_entry.ep
35
- }/{
36
- show_entry.latest_ep
37
- }""",
38
- )
39
-
40
- url_class = videourl(show_entry, quality)
41
- url_class.stream_url()
42
- show_entry = url_class.get_entry()
43
- player.play_title(show_entry)
44
- player.wait()
45
-
46
- if mode == "seasonal":
47
- Seasonal().update_show(
48
- show_entry.show_name, show_entry.category_url, show_entry.ep
49
- )
50
- elif mode == "mal":
51
- mal_class.update_watched(show_entry.show_name, show_entry.ep)
52
-
53
- except KeyboardInterrupt:
54
- player.kill_player()
55
-
56
-
57
- def get_season_searches(gogo=True):
58
- searches = []
59
- selected = []
60
- season_year = None
61
- season_name = None
62
- while not season_year:
63
- try:
64
- season_year = int(cinput(colors.CYAN, "Season Year: "))
65
- except ValueError:
66
- print("Please enter a valid year.\n")
67
-
68
- while not season_name:
69
- season_name_input = cinput(
70
- colors.CYAN, "Season Name (spring|summer|fall|winter): "
71
- )
72
- if season_name_input.lower() in ["spring", "summer", "fall", "winter"]:
73
- season_name = season_name_input
74
-
75
- else:
76
- cprint(colors.YELLOW, "Please enter a valid season name.\n")
77
-
78
- if gogo:
79
- anime_in_season = search_in_season_on_gogo(season_year, season_name)
80
-
81
- else:
82
- anime_in_season = MAL().get_seasonal_anime(season_year, season_name)
83
-
84
- cprint("Anime found in {} {} Season: ".format(season_year, season_name))
85
- cprint(
86
- colors.CYAN,
87
- "Anime found in ",
88
- colors.GREEN,
89
- season_year,
90
- colors.CYAN,
91
- " ",
92
- colors.YELLOW,
93
- season_name,
94
- colors.CYAN,
95
- " Season: ",
96
- )
97
- anime_names = []
98
- for anime in anime_in_season:
99
- if gogo:
100
- anime_names.append(anime["name"])
101
-
102
- else:
103
- anime_names.append(anime["node"]["title"])
104
-
105
- print_names(anime_names)
106
- selection = cinput(colors.CYAN, "Selection: (e.g. 1, 1 3 or 1-3) \n>> ")
107
- if selection.__contains__("-"):
108
- selection_range = selection.strip(" ").split("-")
109
- for i in range(int(selection_range[0]) - 1, int(selection_range[1]) - 1, 1):
110
- selected.append(i)
111
-
112
- else:
113
- for i in selection.lstrip(" ").rstrip(" ").split(" "):
114
- selected.append(int(i) - 1)
115
-
116
- for value in selected:
117
- searches.append(anime_in_season[int(value)])
118
- return searches
anipy_cli/download.py DELETED
@@ -1,454 +0,0 @@
1
- import re
2
- import urllib
3
- from pathlib import Path
4
-
5
- import m3u8
6
- import requests
7
- import shutil
8
- import sys
9
-
10
- from tqdm import tqdm
11
- from requests.adapters import HTTPAdapter, Retry
12
- from concurrent.futures import ThreadPoolExecutor
13
- from better_ffmpeg_progress import FfmpegProcess
14
- from moviepy.editor import ffmpeg_tools
15
-
16
- from anipy_cli.misc import response_err, error, keyboard_inter
17
- from anipy_cli.colors import colors, color, cprint
18
- from anipy_cli.config import Config
19
-
20
-
21
- class download:
22
- """
23
- Download Class.
24
- A entry with all fields is required.
25
- """
26
-
27
- def __init__(self, entry, quality, ffmpeg=False, dl_path: Path = None) -> None:
28
- try:
29
- self.quality = int(quality)
30
- except ValueError:
31
- self.quality = quality
32
- self.is_audio = None
33
- self.content_audio_media = None
34
- self._m3u8_content = None
35
- self.session = None
36
- self.entry = entry
37
- self.ffmpeg = ffmpeg
38
- self.dl_path = dl_path
39
- if dl_path is None:
40
- self.dl_path = Config().download_folder_path
41
- self.headers = {
42
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
43
- "referer": self.entry.embed_url,
44
- }
45
-
46
- def download(self):
47
- show_name = self._get_valid_pathname(self.entry.show_name)
48
- show_name.strip()
49
- self.show_folder = self.dl_path / f"{show_name}"
50
-
51
- if Config().download_remove_dub_from_folder_name:
52
- if show_name.endswith(" (Dub)"):
53
- self.show_folder = self.dl_path / f"{show_name[:-6]}"
54
- print(self.show_folder)
55
-
56
- self.dl_path.mkdir(exist_ok=True, parents=True)
57
- self.show_folder.mkdir(exist_ok=True)
58
- self.session = requests.Session()
59
- retry = Retry(connect=3, backoff_factor=0.5)
60
- adapter = HTTPAdapter(max_retries=retry)
61
- self.session.mount("http://", adapter)
62
- self.session.mount("https://", adapter)
63
- self.session.headers.update(self.headers)
64
-
65
- fname = self._get_fname()
66
- dl_path = self.show_folder / fname
67
-
68
- if dl_path.is_file():
69
- print("-" * 20)
70
- cprint(
71
- colors.GREEN,
72
- "Skipping Already Existing: ",
73
- colors.RED,
74
- f"{self.entry.show_name} EP: {self.entry.ep} - {self.entry.quality}",
75
- )
76
- return dl_path
77
-
78
- print("-" * 20)
79
- cprint(
80
- colors.CYAN,
81
- "Downloading: ",
82
- colors.RED,
83
- f"{self.entry.show_name} EP: {self.entry.ep} - {self.entry.quality}",
84
- )
85
-
86
- if "m3u8" in self.entry.stream_url:
87
- cprint(colors.CYAN, "Type: ", colors.RED, "m3u8")
88
- if self.ffmpeg or Config().ffmpeg_hls:
89
- cprint(colors.CYAN, "Downloader: ", colors.RED, "ffmpeg")
90
- self.ffmpeg_dl()
91
- return dl_path
92
-
93
- cprint(colors.CYAN, "Downloader:", colors.RED, "internal")
94
- self.multithread_m3u8_dl()
95
- elif "mp4" in self.entry.stream_url:
96
- cprint(colors.CYAN, "Type: ", colors.RED, "mp4")
97
- self.mp4_dl(self.entry.stream_url)
98
-
99
- return dl_path
100
-
101
- def ffmpeg_dl(self):
102
- Config().user_files_path.mkdir(exist_ok=True, parents=True)
103
- Config().ffmpeg_log_path.mkdir(exist_ok=True, parents=True)
104
- fname = self._get_fname()
105
-
106
- dl_path = self.show_folder / fname
107
-
108
- ffmpeg_process = FfmpegProcess(
109
- [
110
- "ffmpeg",
111
- "-headers",
112
- f"referer:{self.entry.embed_url}",
113
- "-i",
114
- self.entry.stream_url,
115
- "-vcodec",
116
- "copy",
117
- "-acodec",
118
- "copy",
119
- "-scodec",
120
- "mov_text",
121
- "-c",
122
- "copy",
123
- str(dl_path),
124
- ]
125
- )
126
-
127
- try:
128
- ffmpeg_process.run(
129
- ffmpeg_output_file=str(
130
- Config().ffmpeg_log_path / fname.replace("mp4", "log")
131
- )
132
- )
133
- cprint(colors.CYAN, "Download finished.")
134
- except KeyboardInterrupt:
135
- error("interrupted deleting partially downloaded file")
136
- fname.unlink()
137
-
138
- def ffmpeg_merge(self, input_file, audio_input_file):
139
- Config().user_files_path.mkdir(exist_ok=True, parents=True)
140
- Config().ffmpeg_log_path.mkdir(exist_ok=True, parents=True)
141
- fname = self._get_fname()
142
-
143
- dl_path = self.show_folder / fname
144
-
145
- merged_video_ts = self.merge_ts_files(input_file)
146
- merged_audio_ts = None
147
- if audio_input_file:
148
- merged_audio_ts = self.merge_ts_files(audio_input_file, "_audio")
149
-
150
- try:
151
- cprint(colors.CYAN, "Merging Parts using Movie.py...")
152
- if audio_input_file:
153
- ffmpeg_tools.ffmpeg_merge_video_audio(
154
- merged_video_ts,
155
- merged_audio_ts,
156
- str(dl_path),
157
- vcodec="copy",
158
- acodec="copy",
159
- ffmpeg_output=False,
160
- logger="bar",
161
- )
162
- else:
163
- ffmpeg_tools.ffmpeg_merge_video_audio(
164
- merged_video_ts,
165
- merged_video_ts,
166
- str(dl_path),
167
- vcodec="copy",
168
- acodec="copy",
169
- ffmpeg_output=False,
170
- logger="bar",
171
- )
172
-
173
- cprint(colors.CYAN, "Merge finished.")
174
- except KeyboardInterrupt:
175
- error("interrupted deleting partially downloaded file")
176
- fname.unlink()
177
-
178
- def merge_ts_files(self, input_file, suffix=""):
179
- filename = f"{self.temp_folder}/{self.entry.show_name}_{self.entry.ep}_merged{suffix}.ts"
180
- # Parse playlist for filenames with ending .ts and put them into the list ts_filenames
181
- with open(input_file, "r") as playlist:
182
- ts_filenames = [
183
- line.rstrip() + f"{suffix}"
184
- for line in playlist
185
- if not line.lstrip().startswith("#")
186
- ]
187
- # open one ts_file from the list after another and append them to merged.ts
188
- with open(filename, "wb") as merged:
189
- for ts_file in ts_filenames:
190
- with open(ts_file, "rb") as mergefile:
191
- shutil.copyfileobj(mergefile, merged)
192
- return filename
193
-
194
- def mp4_dl(self, dl_link):
195
- """
196
-
197
- :param dl_link:
198
- :type dl_link:
199
- :return:
200
- :rtype:
201
- """
202
- r = self.session.get(dl_link, headers=self.headers, stream=True)
203
- response_err(r, dl_link)
204
- total = int(r.headers.get("content-length", 0))
205
- fname = self.show_folder / self._get_fname()
206
- try:
207
- with fname.open("wb") as out_file, tqdm(
208
- desc=self.entry.show_name,
209
- total=total,
210
- unit="iB",
211
- unit_scale=True,
212
- unit_divisor=1024,
213
- ) as bar:
214
- for data in r.iter_content(chunk_size=1024):
215
- size = out_file.write(data)
216
- bar.update(size)
217
- except KeyboardInterrupt:
218
- error("interrupted deleting partially downloaded file")
219
- fname.unlink()
220
-
221
- cprint(colors.CYAN, "Download finished.")
222
-
223
- def download_ts(self, m3u8_segments, retry=0):
224
- self.counter += 1
225
- audio_suffix = ""
226
- uri = urllib.parse.urljoin(m3u8_segments.base_uri, m3u8_segments.uri)
227
- if not self._is_url(uri):
228
- input(f"uri: {uri} is not an uri")
229
- return
230
-
231
- if self.is_audio:
232
- audio_suffix = "audio"
233
- filename = self._get_filename(uri, self.temp_folder, audio_suffix)
234
- headers = self.headers
235
- retry_count = 0
236
- while not Path(filename).is_file() and retry_count < 20:
237
- cprint(
238
- colors.CYAN,
239
- f"Downloading {audio_suffix} Part: {self.counter}/{self.segment_count}",
240
- end="",
241
- )
242
- print("\r", end="")
243
-
244
- try:
245
- with self.session.get(
246
- uri, timeout=10, headers=headers, stream=False
247
- ) as response:
248
- if response.status_code == 416:
249
- return
250
-
251
- response.raise_for_status()
252
-
253
- with open(filename, "wb") as fout:
254
- fout.write(response.content)
255
-
256
- except Exception as e:
257
- exit(e.__str__())
258
- retry_count += 1
259
-
260
- def multithread_m3u8_dl(self):
261
- """
262
- Multithread download
263
- function for m3u8 links.
264
- - Creates show and temp folder
265
- - Starts ThreadPoolExecutor instance
266
- and downloads all ts links
267
- - Merges ts files
268
- - Deletes temp folder
269
-
270
- :return:
271
- :rtype:
272
- """
273
-
274
- self.temp_folder = self.show_folder / f"{self.entry.ep}_temp"
275
- self.temp_folder.mkdir(exist_ok=True)
276
- self.counter = 0
277
-
278
- self._m3u8_content = self._download_m3u8(
279
- self.entry.stream_url, 10, self.headers
280
- )
281
-
282
- assert self._m3u8_content.is_variant is False
283
-
284
- try:
285
- if self.content_audio_media and not self.content_audio_media.is_variant:
286
- self.segment_count = len(self.content_audio_media.segments)
287
- self.is_audio = True
288
- with ThreadPoolExecutor(12) as pool_audio:
289
- pool_audio.map(self.download_ts, self.content_audio_media.segments)
290
- self.is_audio = False
291
- self.counter = 0
292
- self.segment_count = len(self._m3u8_content.segments)
293
- print("\n")
294
- with ThreadPoolExecutor(12) as pool_video:
295
- pool_video.map(self.download_ts, self._m3u8_content.segments)
296
- except KeyboardInterrupt:
297
- shutil.rmtree(self.temp_folder)
298
- keyboard_inter()
299
- exit()
300
-
301
- input_file = self._dump_m3u8(self._m3u8_content)
302
- audio_input_file = None
303
- if self.content_audio_media and not self.content_audio_media.is_variant:
304
- self.is_audio = True
305
- audio_input_file = self._dump_m3u8(self.content_audio_media)
306
-
307
- cprint("\n", colors.CYAN, "Parts Downloaded")
308
- try:
309
- self.ffmpeg_merge(input_file, audio_input_file)
310
- except FileNotFoundError:
311
- # This restarts the download if a file is missing
312
- error("Missing a download part, restarting download")
313
- return self.multithread_m3u8_dl()
314
-
315
- cprint("\n", colors.CYAN, "Parts Merged")
316
- shutil.rmtree(self.temp_folder)
317
-
318
- def _download_m3u8(self, uri, timeout, headers, is_audio=False):
319
- if self._is_url(uri):
320
- resp = self.session.get(uri, timeout=timeout, headers=self.headers)
321
- resp.raise_for_status()
322
- raw_content = resp.content.decode(resp.encoding or "utf-8")
323
- base_uri = urllib.parse.urljoin(uri, ".")
324
- else:
325
- with open(uri) as fin:
326
- raw_content = fin.read()
327
- base_uri = Path(uri)
328
- content = m3u8.M3U8(raw_content, base_uri=base_uri)
329
- if content.is_variant:
330
- if self.content_audio_media is not None:
331
- content.add_media(media=self.content_audio_media)
332
-
333
- # sort
334
- content.playlists.sort(key=lambda x: x.stream_info.bandwidth, reverse=True)
335
-
336
- selected_index = 0
337
- if self.quality == "worst":
338
- selected_index = len(content.playlists) - 1
339
-
340
- for index, playlist in enumerate(content.playlists):
341
- cprint(
342
- colors.GREEN,
343
- "Playlist Index: ",
344
- colors.RED,
345
- index,
346
- "\n",
347
- colors.GREEN,
348
- "Resolution at this index: ",
349
- colors.RED,
350
- playlist.stream_info.resolution,
351
- "\n\n",
352
- )
353
-
354
- if self.quality in playlist.stream_info.resolution:
355
- selected_index = index
356
-
357
- try:
358
- for media in content.media:
359
- if content.playlists[selected_index].stream_info.audio in str(
360
- media
361
- ):
362
- self.content_audio_media = media
363
-
364
- chosen_uri = content.playlists[selected_index].uri
365
- if not self._is_url(chosen_uri):
366
- chosen_uri = urllib.parse.urljoin(content.base_uri, chosen_uri)
367
- if self.content_audio_media is not None:
368
- media_uri = self.content_audio_media.uri
369
- self.content_audio_media = self._download_m3u8(
370
- media_uri, timeout, headers, True
371
- )
372
- cprint(
373
- colors.GREEN,
374
- "Quality for Download:",
375
- colors.YELLOW,
376
- content.playlists[selected_index].stream_info.resolution,
377
- )
378
- return self._download_m3u8(chosen_uri, timeout, headers)
379
-
380
- except (ValueError, IndexError):
381
- exit("Failed to get stream for chosen quality")
382
-
383
- else:
384
- self._download_key(content)
385
-
386
- return content
387
-
388
- def _dump_m3u8(self, content):
389
- audio_suffix = ""
390
- for index, segment in enumerate(content.segments):
391
- content.segments[index].uri = self._get_filename(
392
- segment.uri, self.temp_folder
393
- )
394
-
395
- if self.is_audio:
396
- audio_suffix = "_audio"
397
- filename = self._get_filename(f"master{audio_suffix}.m3u8", self.temp_folder)
398
- content.dump(filename)
399
- return filename
400
-
401
- def _download_key(self, content):
402
- for key in content.keys:
403
- if key:
404
- uri = key.absolute_uri
405
- filename = self._get_filename(uri, self.temp_folder)
406
-
407
- with self.session.get(
408
- uri, timeout=10, headers=self.headers
409
- ) as response:
410
- response.raise_for_status()
411
- with open(filename, "wb") as fout:
412
- fout.write(response.content)
413
-
414
- key.uri = filename.__str__().replace(
415
- "\\", "/"
416
- ) # ffmpeg error when using \\ in windows
417
-
418
- def _get_fname(self) -> str:
419
- """
420
- This function returns what the filename for the outputed video should be.
421
-
422
- It finds this by using data in self.entry and the Config.
423
-
424
- Returns a string which should be the filename.
425
- """
426
-
427
- show_name = self._get_valid_pathname(self.entry.show_name)
428
-
429
- return Config().download_name_format.format(
430
- show_name=show_name,
431
- episode_number=self.entry.ep,
432
- quality=self.entry.quality,
433
- )
434
-
435
- @staticmethod
436
- def _get_valid_pathname(name):
437
- WIN_INVALID_CHARS = ["\\", "/", ":", "*", "?", "<", ">", "|", '"']
438
-
439
- if sys.platform == "win32":
440
- name = "".join(["" if x in WIN_INVALID_CHARS else x for x in name])
441
-
442
- return name
443
-
444
- @staticmethod
445
- def _is_url(uri):
446
- return re.match(r"https?://", uri) is not None
447
-
448
- @staticmethod
449
- def _get_filename(uri, directory, suffix=""):
450
- if suffix:
451
- suffix = f"_{suffix}"
452
- basename = urllib.parse.urlparse(uri).path.split("/")[-1]
453
- filename = Path("{}/{}{}".format(directory, basename, suffix)).__str__()
454
- return filename
anipy_cli/history.py DELETED
@@ -1,83 +0,0 @@
1
- import json
2
- import sys
3
-
4
- from anipy_cli.misc import error, read_json
5
- from anipy_cli.config import Config
6
-
7
-
8
- class history:
9
- """
10
- Class for history.
11
- Following entry fields are required
12
- for writing to history file:
13
- - show_name
14
- - category_url
15
- - ep_url
16
- - ep
17
- """
18
-
19
- def __init__(self, entry) -> None:
20
- self.entry = entry
21
-
22
- def read_save_data(self):
23
- self.json = read_json(Config().history_file_path)
24
-
25
- return self.json
26
-
27
- def check_duplicate(self):
28
- """
29
- Check if show is already in
30
- history file.
31
- """
32
- for i in self.json:
33
- if i == self.entry.show_name:
34
- self.dup = True
35
- return 1
36
-
37
- self.dup = False
38
-
39
- def prepend_json(self):
40
- """Moves data to the top of a json file"""
41
- new_data = self.json[self.entry.show_name]
42
- self.json.pop(self.entry.show_name)
43
- new_data = {self.entry.show_name: (new_data)}
44
- self.json = {**new_data, **self.json}
45
-
46
- def update_hist(self):
47
- self.json[self.entry.show_name]["ep"] = self.entry.ep
48
- self.json[self.entry.show_name]["ep-link"] = self.entry.ep_url
49
-
50
- def write_hist(self):
51
- """
52
- Write json that looks something like this
53
- {"some-anime":
54
- {
55
- "ep": 1,
56
- "ep-link": "https://ep-link",
57
- "category_url": "https://"
58
- }
59
- "another-anime": ...}
60
- """
61
-
62
- self.read_save_data()
63
- self.check_duplicate()
64
- if self.dup:
65
- self.update_hist()
66
- else:
67
- add_data = {
68
- self.entry.show_name: {
69
- "ep": self.entry.ep,
70
- "ep-link": self.entry.ep_url,
71
- "category-link": self.entry.category_url,
72
- }
73
- }
74
- self.json.update(add_data)
75
-
76
- self.prepend_json()
77
-
78
- try:
79
- with Config().history_file_path.open("w") as f:
80
- json.dump(self.json, f, indent=4)
81
- except PermissionError:
82
- error("Unable to write to history file due permissions.")
83
- sys.exit()