anipy-cli 2.7.31__py3-none-any.whl → 3.0.0.dev0__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.

Files changed (62) hide show
  1. anipy_cli/__init__.py +2 -20
  2. anipy_cli/arg_parser.py +30 -20
  3. anipy_cli/cli.py +66 -0
  4. anipy_cli/clis/__init__.py +15 -0
  5. anipy_cli/clis/base_cli.py +32 -0
  6. anipy_cli/clis/binge_cli.py +83 -0
  7. anipy_cli/clis/default_cli.py +104 -0
  8. anipy_cli/clis/download_cli.py +111 -0
  9. anipy_cli/clis/history_cli.py +93 -0
  10. anipy_cli/clis/mal_cli.py +71 -0
  11. anipy_cli/{cli/clis → clis}/seasonal_cli.py +9 -6
  12. anipy_cli/colors.py +4 -4
  13. anipy_cli/config.py +308 -87
  14. anipy_cli/discord.py +34 -0
  15. anipy_cli/mal_proxy.py +216 -0
  16. anipy_cli/menus/__init__.py +5 -0
  17. anipy_cli/{cli/menus → menus}/base_menu.py +8 -12
  18. anipy_cli/menus/mal_menu.py +660 -0
  19. anipy_cli/menus/menu.py +194 -0
  20. anipy_cli/menus/seasonal_menu.py +263 -0
  21. anipy_cli/prompts.py +231 -0
  22. anipy_cli/util.py +262 -0
  23. anipy_cli-3.0.0.dev0.dist-info/METADATA +67 -0
  24. anipy_cli-3.0.0.dev0.dist-info/RECORD +26 -0
  25. {anipy_cli-2.7.31.dist-info → anipy_cli-3.0.0.dev0.dist-info}/WHEEL +1 -2
  26. anipy_cli-3.0.0.dev0.dist-info/entry_points.txt +3 -0
  27. anipy_cli/cli/__init__.py +0 -1
  28. anipy_cli/cli/cli.py +0 -37
  29. anipy_cli/cli/clis/__init__.py +0 -6
  30. anipy_cli/cli/clis/base_cli.py +0 -43
  31. anipy_cli/cli/clis/binge_cli.py +0 -54
  32. anipy_cli/cli/clis/default_cli.py +0 -46
  33. anipy_cli/cli/clis/download_cli.py +0 -92
  34. anipy_cli/cli/clis/history_cli.py +0 -64
  35. anipy_cli/cli/clis/mal_cli.py +0 -27
  36. anipy_cli/cli/menus/__init__.py +0 -3
  37. anipy_cli/cli/menus/mal_menu.py +0 -411
  38. anipy_cli/cli/menus/menu.py +0 -108
  39. anipy_cli/cli/menus/seasonal_menu.py +0 -177
  40. anipy_cli/cli/util.py +0 -125
  41. anipy_cli/download.py +0 -467
  42. anipy_cli/history.py +0 -83
  43. anipy_cli/mal.py +0 -651
  44. anipy_cli/misc.py +0 -227
  45. anipy_cli/player/__init__.py +0 -1
  46. anipy_cli/player/player.py +0 -35
  47. anipy_cli/player/players/__init__.py +0 -3
  48. anipy_cli/player/players/base.py +0 -107
  49. anipy_cli/player/players/mpv.py +0 -19
  50. anipy_cli/player/players/mpv_control.py +0 -37
  51. anipy_cli/player/players/syncplay.py +0 -19
  52. anipy_cli/player/players/vlc.py +0 -18
  53. anipy_cli/query.py +0 -100
  54. anipy_cli/run_anipy_cli.py +0 -14
  55. anipy_cli/seasonal.py +0 -112
  56. anipy_cli/url_handler.py +0 -470
  57. anipy_cli/version.py +0 -1
  58. anipy_cli-2.7.31.dist-info/LICENSE +0 -674
  59. anipy_cli-2.7.31.dist-info/METADATA +0 -162
  60. anipy_cli-2.7.31.dist-info/RECORD +0 -43
  61. anipy_cli-2.7.31.dist-info/entry_points.txt +0 -2
  62. anipy_cli-2.7.31.dist-info/top_level.txt +0 -1
anipy_cli/mal_proxy.py ADDED
@@ -0,0 +1,216 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Dict, List, Optional, Set
3
+
4
+ from anipy_api.anime import Anime
5
+ from anipy_api.mal import (
6
+ MALAnime,
7
+ MALMyListStatus,
8
+ MALMyListStatusEnum,
9
+ MyAnimeList,
10
+ MyAnimeListAdapter,
11
+ )
12
+ from anipy_api.provider import LanguageTypeEnum, list_providers
13
+ from dataclasses_json import DataClassJsonMixin, config
14
+ from InquirerPy import inquirer
15
+
16
+ from anipy_cli.config import Config
17
+ from anipy_cli.util import error, get_prefered_providers
18
+
19
+
20
+ @dataclass
21
+ class ProviderMapping(DataClassJsonMixin):
22
+ provider: str = field(metadata=config(field_name="pv"))
23
+ name: str = field(metadata=config(field_name="na"))
24
+ identifier: str = field(metadata=config(field_name="id"))
25
+ languages: Set[LanguageTypeEnum] = field(metadata=config(field_name="la"))
26
+
27
+
28
+ @dataclass
29
+ class MALProviderMapping(DataClassJsonMixin):
30
+ mal_anime: MALAnime
31
+ mappings: Dict[str, ProviderMapping]
32
+
33
+
34
+ @dataclass
35
+ class MALLocalList(DataClassJsonMixin):
36
+ mappings: Dict[int, MALProviderMapping]
37
+
38
+ def write(self, user_id: int):
39
+ config = Config()
40
+ local_list = config._mal_local_user_list_path.with_stem(
41
+ f"{config._mal_local_user_list_path.stem}_{user_id}"
42
+ )
43
+ local_list.write_text(self.to_json())
44
+
45
+ @staticmethod
46
+ def read(user_id: int) -> "MALLocalList":
47
+ config = Config()
48
+ local_list = config._mal_local_user_list_path.with_stem(
49
+ f"{config._mal_local_user_list_path.stem}_{user_id}"
50
+ )
51
+
52
+ if not local_list.is_file():
53
+ local_list.parent.mkdir(exist_ok=True, parents=True)
54
+ local_list.touch()
55
+ mylist = MALLocalList({})
56
+ mylist.write(user_id)
57
+ return mylist
58
+
59
+ try:
60
+ mylist: MALLocalList = MALLocalList.from_json(local_list.read_text())
61
+ except KeyError:
62
+ choice = inquirer.confirm(
63
+ message=f"Your local MyAnimeList ({str(local_list)}) is not in the correct format, should it be deleted?",
64
+ default=False,
65
+ ).execute()
66
+ if choice:
67
+ local_list.unlink()
68
+ return MALLocalList.read(user_id)
69
+ else:
70
+ error("could not read your MyAnimeList", fatal=True)
71
+
72
+ return mylist
73
+
74
+
75
+ class MyAnimeListProxy:
76
+ def __init__(self, mal: MyAnimeList):
77
+ self.mal = mal
78
+ self.user_id = mal.get_user().id
79
+ self.local_list = MALLocalList.read(self.user_id)
80
+
81
+ def _cache_list(self, mylist: List[MALAnime]):
82
+ config = Config()
83
+ for e in mylist:
84
+ if self.local_list.mappings.get(e.id, None):
85
+ if e.my_list_status and config.mal_ignore_tag in e.my_list_status.tags:
86
+ self.local_list.mappings.pop(e.id)
87
+ else:
88
+ self.local_list.mappings[e.id].mal_anime = e
89
+ else:
90
+ self.local_list.mappings[e.id] = MALProviderMapping(e, {})
91
+
92
+ self.local_list.write(self.user_id)
93
+
94
+ def _write_mapping(self, mal_anime: MALAnime, mapping: Anime):
95
+ self._cache_list([mal_anime])
96
+
97
+ self.local_list.mappings[mal_anime.id].mappings[
98
+ f"{mapping.provider.NAME}:{mapping.identifier}"
99
+ ] = ProviderMapping(
100
+ mapping.provider.NAME, mapping.name, mapping.identifier, mapping.languages
101
+ )
102
+
103
+ self.local_list.write(self.user_id)
104
+
105
+ def get_list(
106
+ self, status_catagories: Optional[Set[MALMyListStatusEnum]] = None
107
+ ) -> List[MALAnime]:
108
+ config = Config()
109
+ mylist: List[MALAnime] = []
110
+
111
+ catagories = (
112
+ status_catagories
113
+ if status_catagories is not None
114
+ else set(
115
+ [MALMyListStatusEnum[s.upper()] for s in config.mal_status_categories]
116
+ )
117
+ )
118
+
119
+ for c in catagories:
120
+ mylist.extend(
121
+ filter(
122
+ lambda e: (
123
+ config.mal_ignore_tag not in e.my_list_status.tags
124
+ if e.my_list_status
125
+ else True
126
+ ),
127
+ self.mal.get_anime_list(c),
128
+ )
129
+ )
130
+
131
+ self._cache_list(mylist)
132
+ filtered_list = filter(
133
+ lambda x: (
134
+ x.my_list_status.status in catagories if x.my_list_status else False
135
+ ),
136
+ mylist,
137
+ )
138
+ return list(filtered_list)
139
+
140
+ def update_show(
141
+ self,
142
+ anime: MALAnime,
143
+ status: Optional[MALMyListStatusEnum] = None,
144
+ episode: Optional[int] = None,
145
+ tags: Set[str] = set(),
146
+ ) -> MALMyListStatus:
147
+ config = Config()
148
+ tags |= set(config.mal_tags)
149
+ result = self.mal.update_anime_list(
150
+ anime.id, status=status, watched_episodes=episode, tags=list(tags)
151
+ )
152
+ anime.my_list_status = result
153
+ self._cache_list([anime])
154
+ return result
155
+
156
+ def delete_show(self, anime: MALAnime) -> None:
157
+ self.local_list.mappings.pop(anime.id)
158
+ self.local_list.write(self.user_id)
159
+
160
+ self.mal.remove_from_anime_list(anime.id)
161
+
162
+ def map_from_mal(
163
+ self, anime: MALAnime, mapping: Optional[Anime] = None
164
+ ) -> Optional[Anime]:
165
+ if mapping is not None:
166
+ self._write_mapping(anime, mapping)
167
+ return mapping
168
+
169
+ if self.local_list.mappings[anime.id].mappings:
170
+ map = list(self.local_list.mappings[anime.id].mappings.values())[0]
171
+ provider = next(filter(lambda x: x.NAME == map.provider, list_providers()))
172
+ return Anime(provider(), map.name, map.identifier, map.languages)
173
+
174
+ config = Config()
175
+ result = None
176
+ for p in get_prefered_providers("mal"):
177
+ adapter = MyAnimeListAdapter(self.mal, p)
178
+ result = adapter.from_myanimelist(
179
+ anime,
180
+ config.mal_mapping_min_similarity,
181
+ config.mal_mapping_use_filters,
182
+ config.mal_mapping_use_alternatives,
183
+ )
184
+
185
+ if result is not None:
186
+ break
187
+
188
+ if result:
189
+ self._write_mapping(anime, result)
190
+
191
+ return result
192
+
193
+ def map_from_provider(
194
+ self, anime: Anime, mapping: Optional[MALAnime] = None
195
+ ) -> Optional[MALAnime]:
196
+ if mapping is not None:
197
+ self._write_mapping(mapping, anime)
198
+ return mapping
199
+
200
+ for _, m in self.local_list.mappings.items():
201
+ existing = m.mappings.get(f"{anime.provider.NAME}:{anime.identifier}", None)
202
+ if existing:
203
+ return m.mal_anime
204
+
205
+ config = Config()
206
+ adapter = MyAnimeListAdapter(self.mal, anime.provider)
207
+ result = adapter.from_provider(
208
+ anime,
209
+ config.mal_mapping_min_similarity,
210
+ config.mal_mapping_use_alternatives,
211
+ )
212
+
213
+ if result:
214
+ self._write_mapping(result, anime)
215
+
216
+ return result
@@ -0,0 +1,5 @@
1
+ from anipy_cli.menus.menu import Menu
2
+ from anipy_cli.menus.mal_menu import MALMenu
3
+ from anipy_cli.menus.seasonal_menu import SeasonalMenu
4
+
5
+ __all__ = ["Menu", "MALMenu", "SeasonalMenu"]
@@ -1,10 +1,10 @@
1
- import sys
1
+ import os
2
+ from abc import ABC, abstractmethod
2
3
  from dataclasses import dataclass
3
4
  from typing import Callable, List
4
- from abc import ABC, abstractmethod
5
5
 
6
- from anipy_cli.misc import error, clear_console
7
- from anipy_cli.colors import colors, color
6
+ from anipy_cli.colors import color, colors
7
+ from anipy_cli.util import error
8
8
 
9
9
 
10
10
  @dataclass(frozen=True)
@@ -20,12 +20,10 @@ class MenuOption:
20
20
  class MenuBase(ABC):
21
21
  @property
22
22
  @abstractmethod
23
- def menu_options(self) -> List[MenuOption]:
24
- pass
23
+ def menu_options(self) -> List[MenuOption]: ...
25
24
 
26
25
  @abstractmethod
27
- def print_header(self):
28
- pass
26
+ def print_header(self): ...
29
27
 
30
28
  def run(self):
31
29
  self.print_options()
@@ -44,10 +42,8 @@ class MenuBase(ABC):
44
42
 
45
43
  def print_options(self, clear_screen=True):
46
44
  if clear_screen:
47
- clear_console()
45
+ os.system("cls" if os.name == "nt" else "clear")
46
+
48
47
  self.print_header()
49
48
  for op in self.menu_options:
50
49
  print(op)
51
-
52
- def quit(self):
53
- sys.exit()