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/mal.py
DELETED
|
@@ -1,645 +0,0 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import datetime
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
|
-
import time
|
|
7
|
-
from copy import deepcopy
|
|
8
|
-
from multiprocessing import Pool
|
|
9
|
-
|
|
10
|
-
import requests
|
|
11
|
-
from requests.adapters import HTTPAdapter, Retry
|
|
12
|
-
|
|
13
|
-
from anipy_cli.url_handler import epHandler
|
|
14
|
-
from anipy_cli.seasonal import Seasonal
|
|
15
|
-
from anipy_cli.query import query
|
|
16
|
-
from anipy_cli.colors import colors, cprint
|
|
17
|
-
from anipy_cli.config import Config
|
|
18
|
-
from anipy_cli.misc import read_json, error, Entry
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _base64_decode(b64: str):
|
|
22
|
-
return base64.b64decode(b64).decode("ascii")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _base64_encode(string: str):
|
|
26
|
-
message_bytes = string.encode("ascii")
|
|
27
|
-
return base64.b64encode(message_bytes).decode("ascii")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class MAL:
|
|
31
|
-
"""
|
|
32
|
-
MyAnimeList API client
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def __init__(self, user=None, password=None):
|
|
36
|
-
# API information taken from here: https://github.com/SuperMarcus/myanimelist-api-specification
|
|
37
|
-
self.entry = Entry()
|
|
38
|
-
self.local_mal_list_json = None
|
|
39
|
-
|
|
40
|
-
self.anime_fields = [
|
|
41
|
-
"id",
|
|
42
|
-
"title",
|
|
43
|
-
"main_picture",
|
|
44
|
-
"alternative_titles",
|
|
45
|
-
"my_list_status{tags,is_rewatching,num_episodes_watched,score,status,updated_at}",
|
|
46
|
-
"start_season",
|
|
47
|
-
]
|
|
48
|
-
self.access_token_expire_time = None
|
|
49
|
-
self.access_token = None
|
|
50
|
-
self.refresh_token = os.environ.get("mal_api_refresh_token")
|
|
51
|
-
self.api_client_id = "6114d00ca681b7701d1e15fe11a4987e"
|
|
52
|
-
self.api_baseurl = "https://api.myanimelist.net/v2/"
|
|
53
|
-
self.mal_user = (
|
|
54
|
-
Config().mal_user
|
|
55
|
-
if Config().mal_user and Config().mal_user != ""
|
|
56
|
-
else (user if user else False)
|
|
57
|
-
)
|
|
58
|
-
self.mal_password = (
|
|
59
|
-
password
|
|
60
|
-
if password
|
|
61
|
-
else (
|
|
62
|
-
Config().mal_password
|
|
63
|
-
if Config().mal_password and Config().mal_password != ""
|
|
64
|
-
else False
|
|
65
|
-
)
|
|
66
|
-
)
|
|
67
|
-
self.anime_list = None
|
|
68
|
-
self.gogo_baseurl = Config().gogoanime_url
|
|
69
|
-
self.data = {
|
|
70
|
-
"client_id": self.api_client_id,
|
|
71
|
-
}
|
|
72
|
-
self.shows_failed_automap = set()
|
|
73
|
-
|
|
74
|
-
self.headers = {
|
|
75
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
76
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
|
|
77
|
-
"X-MAL-Client-ID": self.api_client_id,
|
|
78
|
-
}
|
|
79
|
-
self.session = requests.Session()
|
|
80
|
-
retry = Retry(connect=3, backoff_factor=0.5)
|
|
81
|
-
adapter = HTTPAdapter(max_retries=retry)
|
|
82
|
-
self.session.mount("http://", adapter)
|
|
83
|
-
self.session.mount("https://", adapter)
|
|
84
|
-
self.session.headers.update(self.headers)
|
|
85
|
-
self.read_save_data()
|
|
86
|
-
if self.mal_user:
|
|
87
|
-
if not self.auth():
|
|
88
|
-
error(
|
|
89
|
-
"Could not authenticate with MyAnimeList. Please check your credentials..."
|
|
90
|
-
)
|
|
91
|
-
self.get_anime_list()
|
|
92
|
-
if Config().auto_map_mal_to_gogo:
|
|
93
|
-
self.auto_map_all_without_map()
|
|
94
|
-
if Config().auto_sync_mal_to_seasonals:
|
|
95
|
-
self.sync_mal_with_seasonal()
|
|
96
|
-
|
|
97
|
-
def auto_map_all_without_map(self):
|
|
98
|
-
with Pool(processes=None) as pool:
|
|
99
|
-
mal_entries = self.local_mal_list_json["data"]
|
|
100
|
-
# apply the search function to each search value in parallel
|
|
101
|
-
results = pool.map(self.auto_map_gogo_mal, mal_entries, True)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
new_mal_entries = list()
|
|
105
|
-
for result in results:
|
|
106
|
-
if type(result) is dict:
|
|
107
|
-
new_mal_entries.append(result["mal_entry"])
|
|
108
|
-
if result["failed_to_map"] is True:
|
|
109
|
-
self.shows_failed_automap.add(result["mal_entry"]["node"]["title"])
|
|
110
|
-
else:
|
|
111
|
-
self.shows_failed_automap.discard(
|
|
112
|
-
result["mal_entry"]["node"]["title"]
|
|
113
|
-
)
|
|
114
|
-
self.local_mal_list_json["data"] = new_mal_entries
|
|
115
|
-
self.write_save_data()
|
|
116
|
-
|
|
117
|
-
return self.shows_failed_automap
|
|
118
|
-
|
|
119
|
-
def get_all_without_gogo_map(self):
|
|
120
|
-
shows_with_no_map = set()
|
|
121
|
-
for mal_entry in self.local_mal_list_json["data"]:
|
|
122
|
-
if "gogo_map" not in mal_entry or len(mal_entry["gogo_map"]) < 1:
|
|
123
|
-
shows_with_no_map.add(mal_entry["node"]["title"])
|
|
124
|
-
return shows_with_no_map
|
|
125
|
-
|
|
126
|
-
def _make_request(
|
|
127
|
-
self,
|
|
128
|
-
url: str,
|
|
129
|
-
method: str,
|
|
130
|
-
data: dict = None,
|
|
131
|
-
query_params: dict = None,
|
|
132
|
-
body: dict = None,
|
|
133
|
-
retry: int = 0,
|
|
134
|
-
is_auth: bool = False,
|
|
135
|
-
):
|
|
136
|
-
try:
|
|
137
|
-
response = self.session.request(
|
|
138
|
-
method, url, data=data, params=query_params, json=body
|
|
139
|
-
)
|
|
140
|
-
response.raise_for_status()
|
|
141
|
-
return response.json()
|
|
142
|
-
|
|
143
|
-
except requests.exceptions.RequestException as request_error:
|
|
144
|
-
if retry == 0 and not is_auth:
|
|
145
|
-
self.auth(retry=retry)
|
|
146
|
-
return self._make_request(
|
|
147
|
-
url,
|
|
148
|
-
method,
|
|
149
|
-
data=data,
|
|
150
|
-
query_params=query_params,
|
|
151
|
-
body=body,
|
|
152
|
-
retry=1,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
if "message" in request_error.response.json():
|
|
156
|
-
error(
|
|
157
|
-
"MyAnimeList Error: {}".format(
|
|
158
|
-
request_error.response.json()["message"]
|
|
159
|
-
)
|
|
160
|
-
)
|
|
161
|
-
if "hint" in request_error.response.json():
|
|
162
|
-
print(
|
|
163
|
-
"{}Hint:{} {}{}".format(
|
|
164
|
-
colors.BLUE,
|
|
165
|
-
colors.YELLOW,
|
|
166
|
-
request_error.response.json()["hint"],
|
|
167
|
-
colors.END,
|
|
168
|
-
)
|
|
169
|
-
)
|
|
170
|
-
sys.exit(1)
|
|
171
|
-
error("MyAnimeList - {}".format(request_error.response.json()))
|
|
172
|
-
return error
|
|
173
|
-
|
|
174
|
-
def auth(self, retry: int = 0):
|
|
175
|
-
data = self.data
|
|
176
|
-
time_now = datetime.datetime.now()
|
|
177
|
-
if (
|
|
178
|
-
self.access_token
|
|
179
|
-
and self.access_token_expire_time > time_now
|
|
180
|
-
and retry == 0
|
|
181
|
-
):
|
|
182
|
-
return True
|
|
183
|
-
|
|
184
|
-
elif self.mal_user:
|
|
185
|
-
print("MAL: Auth with user and password")
|
|
186
|
-
url = "https://api.myanimelist.net/v2/auth/token"
|
|
187
|
-
data.update(
|
|
188
|
-
{
|
|
189
|
-
"grant_type": "password",
|
|
190
|
-
"password": self.mal_password,
|
|
191
|
-
"username": self.mal_user,
|
|
192
|
-
}
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
else:
|
|
196
|
-
url = "https://myanimelist.net/v1/oauth2/token"
|
|
197
|
-
data.update({"client_id": self.api_client_id})
|
|
198
|
-
|
|
199
|
-
response = self._make_request(url, "post", data, is_auth=True)
|
|
200
|
-
if isinstance(response, dict):
|
|
201
|
-
if response["access_token"]:
|
|
202
|
-
self.access_token = response["access_token"]
|
|
203
|
-
self.session.headers.update(
|
|
204
|
-
{"Authorization": "Bearer " + self.access_token}
|
|
205
|
-
)
|
|
206
|
-
self.access_token_expire_time = time_now + datetime.timedelta(
|
|
207
|
-
0, int(response["expires_in"])
|
|
208
|
-
)
|
|
209
|
-
self.refresh_token = response["refresh_token"]
|
|
210
|
-
os.environ["mal_api_refresh_token"] = self.refresh_token
|
|
211
|
-
return True
|
|
212
|
-
|
|
213
|
-
else:
|
|
214
|
-
return None
|
|
215
|
-
|
|
216
|
-
else:
|
|
217
|
-
if retry == 0:
|
|
218
|
-
return self.auth(retry=1)
|
|
219
|
-
|
|
220
|
-
else:
|
|
221
|
-
return None
|
|
222
|
-
|
|
223
|
-
def get_anime_list(self, status_filter: str = None, automap: bool = False):
|
|
224
|
-
if status_filter is not None:
|
|
225
|
-
status_filter = status_filter.lower()
|
|
226
|
-
|
|
227
|
-
allowed_filters = [
|
|
228
|
-
"watching",
|
|
229
|
-
"completed",
|
|
230
|
-
"on_hold",
|
|
231
|
-
"dropped",
|
|
232
|
-
"plan_to_watch",
|
|
233
|
-
]
|
|
234
|
-
parameters = {}
|
|
235
|
-
if status_filter in allowed_filters:
|
|
236
|
-
parameters = {"status": status_filter}
|
|
237
|
-
|
|
238
|
-
url = f"{self.api_baseurl}users/@me/animelist"
|
|
239
|
-
anime_list = self.get_all_anime_pages(
|
|
240
|
-
url, limit=20, additional_params=parameters
|
|
241
|
-
)
|
|
242
|
-
if not isinstance(anime_list, dict):
|
|
243
|
-
return False
|
|
244
|
-
if not isinstance(anime_list, requests.exceptions.RequestException):
|
|
245
|
-
self.anime_list = deepcopy(anime_list)
|
|
246
|
-
self.write_mal_list()
|
|
247
|
-
return anime_list["data"]
|
|
248
|
-
|
|
249
|
-
else:
|
|
250
|
-
return None
|
|
251
|
-
|
|
252
|
-
def update_anime_list(self, anime_id: int, update_data: dict = None):
|
|
253
|
-
url = f"{self.api_baseurl}anime/{anime_id}/my_list_status"
|
|
254
|
-
allowed_update_data_keys = ["status", "num_watched_episodes", "tags"]
|
|
255
|
-
data = {}
|
|
256
|
-
for key, value in update_data.items():
|
|
257
|
-
if key in allowed_update_data_keys:
|
|
258
|
-
data[key] = value
|
|
259
|
-
if data:
|
|
260
|
-
self._make_request(url, "patch", data)
|
|
261
|
-
|
|
262
|
-
def get_seasonal_anime(
|
|
263
|
-
self, year: int, season: str, limit: int = 100, automap: bool = False
|
|
264
|
-
):
|
|
265
|
-
season = season.lower()
|
|
266
|
-
url = f"{self.api_baseurl}anime/season/{year}/{season}"
|
|
267
|
-
|
|
268
|
-
anime_season_list = self.get_all_anime_pages(url, limit)["data"]
|
|
269
|
-
|
|
270
|
-
return anime_season_list
|
|
271
|
-
|
|
272
|
-
def get_anime(self, query: str, limit: int = 2, automap: bool = True):
|
|
273
|
-
url = f"{self.api_baseurl}anime"
|
|
274
|
-
params = {
|
|
275
|
-
"q": query,
|
|
276
|
-
"limit": 10,
|
|
277
|
-
}
|
|
278
|
-
anime_found = self._make_request(url, "get", query_params=params)
|
|
279
|
-
if (
|
|
280
|
-
isinstance(anime_found, requests.exceptions.RequestException)
|
|
281
|
-
or not isinstance(anime_found, dict)
|
|
282
|
-
or len(anime_found["data"]) < 1
|
|
283
|
-
):
|
|
284
|
-
return []
|
|
285
|
-
|
|
286
|
-
return anime_found["data"]
|
|
287
|
-
|
|
288
|
-
def get_all_anime_pages(self, url, limit, additional_params: dict = None):
|
|
289
|
-
params = {
|
|
290
|
-
"fields": ",".join(self.anime_fields),
|
|
291
|
-
"limit": limit,
|
|
292
|
-
"offset": 0,
|
|
293
|
-
}
|
|
294
|
-
if additional_params:
|
|
295
|
-
params.update(additional_params)
|
|
296
|
-
next_page = True
|
|
297
|
-
offset = 0
|
|
298
|
-
anime_list = {"data": []}
|
|
299
|
-
|
|
300
|
-
while next_page:
|
|
301
|
-
response = self._make_request(url, "get", query_params=params)
|
|
302
|
-
if not "data" in response or not isinstance(response["data"], list):
|
|
303
|
-
continue
|
|
304
|
-
if (
|
|
305
|
-
response["paging"]
|
|
306
|
-
and "next" in response["paging"]
|
|
307
|
-
and response["paging"]["next"]
|
|
308
|
-
):
|
|
309
|
-
offset += limit
|
|
310
|
-
|
|
311
|
-
else:
|
|
312
|
-
next_page = False
|
|
313
|
-
|
|
314
|
-
anime_list["data"] += response["data"]
|
|
315
|
-
params["offset"] = offset
|
|
316
|
-
|
|
317
|
-
# sleep for 0.5 sec to limit rate
|
|
318
|
-
time.sleep(0.5)
|
|
319
|
-
return anime_list
|
|
320
|
-
|
|
321
|
-
def add_show(self, show_name, category_url, picked_ep):
|
|
322
|
-
search = self.get_anime(show_name, automap=True)
|
|
323
|
-
|
|
324
|
-
if isinstance(search, requests.exceptions.RequestException):
|
|
325
|
-
return None
|
|
326
|
-
|
|
327
|
-
else:
|
|
328
|
-
if search[0]["node"]["title"].lower() == show_name.lower().rstrip(
|
|
329
|
-
" (dub)"
|
|
330
|
-
).strip("(japanese dub)"):
|
|
331
|
-
print("Exact match")
|
|
332
|
-
self.update_anime_list(
|
|
333
|
-
search[0]["node"]["id"],
|
|
334
|
-
{
|
|
335
|
-
"status": "watching",
|
|
336
|
-
"num_watched_episodes": picked_ep,
|
|
337
|
-
"tags": "anipy-cli",
|
|
338
|
-
},
|
|
339
|
-
)
|
|
340
|
-
show = [
|
|
341
|
-
x
|
|
342
|
-
for x in self.local_mal_list_json["data"]
|
|
343
|
-
if x["node"]["title"] == search[0]["node"]["title"]
|
|
344
|
-
]
|
|
345
|
-
try:
|
|
346
|
-
new_item = self.make_gogo_map(category_url, show_name)
|
|
347
|
-
if "gogo_map" in show[0]:
|
|
348
|
-
self.update_gogo_map_list(show[0]["gogo_map"], new_item)
|
|
349
|
-
|
|
350
|
-
else:
|
|
351
|
-
new_map = {"gogo_map": []}
|
|
352
|
-
self.update_gogo_map_list(new_map["gogo_map"], new_item)
|
|
353
|
-
show[0].update(new_map)
|
|
354
|
-
except IndexError:
|
|
355
|
-
error("No show found for {}".format(search[0]["node"]["title"]))
|
|
356
|
-
|
|
357
|
-
self.write_mal_list()
|
|
358
|
-
else:
|
|
359
|
-
return search
|
|
360
|
-
|
|
361
|
-
def make_gogo_map(self, link_href, name):
|
|
362
|
-
link = (
|
|
363
|
-
link_href
|
|
364
|
-
if len(link_href.split("/category/")[0]) > 3
|
|
365
|
-
else Config().gogoanime_url + link_href
|
|
366
|
-
)
|
|
367
|
-
if "(dub)" in name.lower():
|
|
368
|
-
key = "dub"
|
|
369
|
-
|
|
370
|
-
else:
|
|
371
|
-
key = "sub"
|
|
372
|
-
|
|
373
|
-
return [{"name": name, "link": link, "type": key}]
|
|
374
|
-
|
|
375
|
-
def update_gogo_map_list(self, gogo_map, new_entry):
|
|
376
|
-
if len(gogo_map) > 0:
|
|
377
|
-
for index, item in enumerate(gogo_map):
|
|
378
|
-
if item["name"] == new_entry[0]["name"]:
|
|
379
|
-
if item.__eq__(new_entry):
|
|
380
|
-
return True
|
|
381
|
-
|
|
382
|
-
else:
|
|
383
|
-
gogo_map[index] = new_entry
|
|
384
|
-
return True
|
|
385
|
-
if isinstance(gogo_map, list):
|
|
386
|
-
gogo_map.extend(new_entry)
|
|
387
|
-
|
|
388
|
-
return True
|
|
389
|
-
|
|
390
|
-
def read_save_data(self):
|
|
391
|
-
try:
|
|
392
|
-
self.local_mal_list_json = read_json(Config().mal_local_user_list_path)
|
|
393
|
-
|
|
394
|
-
except json.decoder.JSONDecodeError:
|
|
395
|
-
pass
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
not isinstance(self.local_mal_list_json, dict)
|
|
399
|
-
or "data" not in self.local_mal_list_json
|
|
400
|
-
):
|
|
401
|
-
self.local_mal_list_json = {"data": []}
|
|
402
|
-
self.get_anime_list(automap=True)
|
|
403
|
-
|
|
404
|
-
def write_save_data(self):
|
|
405
|
-
try:
|
|
406
|
-
with Config().mal_local_user_list_path.open("w") as f:
|
|
407
|
-
json.dump(self.local_mal_list_json, f, indent=4)
|
|
408
|
-
|
|
409
|
-
except PermissionError:
|
|
410
|
-
error("Unable to write to local MAL-list file due permissions.")
|
|
411
|
-
sys.exit()
|
|
412
|
-
|
|
413
|
-
def write_mal_list(self):
|
|
414
|
-
for show_entry in self.anime_list["data"]:
|
|
415
|
-
if "data" not in self.local_mal_list_json:
|
|
416
|
-
self.local_mal_list_json = {"data": []}
|
|
417
|
-
show = [
|
|
418
|
-
x
|
|
419
|
-
for x in self.local_mal_list_json["data"]
|
|
420
|
-
if x["node"]["title"] == show_entry["node"]["title"]
|
|
421
|
-
]
|
|
422
|
-
if len(show) > 0:
|
|
423
|
-
update_dict_recursive(show[0]["node"], show_entry["node"])
|
|
424
|
-
|
|
425
|
-
else:
|
|
426
|
-
self.local_mal_list_json["data"].append(show_entry)
|
|
427
|
-
|
|
428
|
-
self.write_save_data()
|
|
429
|
-
|
|
430
|
-
def delete_mal_entry(self, anime_id):
|
|
431
|
-
show = [
|
|
432
|
-
x
|
|
433
|
-
for x in self.local_mal_list_json["data"]
|
|
434
|
-
if str(x["node"]["id"]) == str(anime_id)
|
|
435
|
-
]
|
|
436
|
-
show[0] = None
|
|
437
|
-
self.get_anime_list()
|
|
438
|
-
|
|
439
|
-
def del_show(self, anime_id):
|
|
440
|
-
url = f"{self.api_baseurl}anime/{anime_id}/my_list_status"
|
|
441
|
-
self._make_request(url, "delete")
|
|
442
|
-
self.delete_mal_entry(anime_id)
|
|
443
|
-
|
|
444
|
-
pass
|
|
445
|
-
|
|
446
|
-
def latest_eps(self, all_eps: bool = False):
|
|
447
|
-
"""
|
|
448
|
-
returns a dict like so:
|
|
449
|
-
{"name": {
|
|
450
|
-
"ep_list": [[ep, ep-link], [], ...],
|
|
451
|
-
"category_url": "https://"
|
|
452
|
-
},
|
|
453
|
-
"another anime": {
|
|
454
|
-
...
|
|
455
|
-
},
|
|
456
|
-
}
|
|
457
|
-
"""
|
|
458
|
-
|
|
459
|
-
self.read_save_data()
|
|
460
|
-
entries = [
|
|
461
|
-
i
|
|
462
|
-
for i in self.local_mal_list_json["data"]
|
|
463
|
-
if i["node"]["my_list_status"]["status"] in Config().mal_status_categories
|
|
464
|
-
]
|
|
465
|
-
|
|
466
|
-
latest_urls = {}
|
|
467
|
-
for i in entries:
|
|
468
|
-
if all_eps:
|
|
469
|
-
start_ep = 0
|
|
470
|
-
else:
|
|
471
|
-
start_ep = i["node"]["my_list_status"]["num_episodes_watched"]
|
|
472
|
-
|
|
473
|
-
if "gogo_map" not in i:
|
|
474
|
-
error(
|
|
475
|
-
'MAL entry "{}" is missing gogo-map, please re-add to MAL...'.format(
|
|
476
|
-
i["node"]["title"]
|
|
477
|
-
)
|
|
478
|
-
)
|
|
479
|
-
continue
|
|
480
|
-
|
|
481
|
-
for key, link in enumerate(i["gogo_map"]):
|
|
482
|
-
self.entry.category_url = link["link"]
|
|
483
|
-
ep_class = epHandler(self.entry)
|
|
484
|
-
latest = ep_class.get_latest()
|
|
485
|
-
eps_range = list(range(start_ep + 1, latest + 1))
|
|
486
|
-
ep_urls = []
|
|
487
|
-
for j in eps_range:
|
|
488
|
-
self.entry.ep = j
|
|
489
|
-
ep_class = epHandler(self.entry)
|
|
490
|
-
entry = ep_class.gen_eplink()
|
|
491
|
-
ep_urls.append([j, entry.ep_url])
|
|
492
|
-
|
|
493
|
-
latest_urls.update(
|
|
494
|
-
{
|
|
495
|
-
link["name"]: {
|
|
496
|
-
"ep_list": ep_urls,
|
|
497
|
-
"category_url": self.entry.category_url,
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
return latest_urls
|
|
503
|
-
|
|
504
|
-
def update_watched(self, gogo_show_name, ep):
|
|
505
|
-
show = []
|
|
506
|
-
for mal_entry in self.local_mal_list_json["data"]:
|
|
507
|
-
if "gogo_map" in mal_entry.keys():
|
|
508
|
-
for gogomap in mal_entry["gogo_map"]:
|
|
509
|
-
if gogo_show_name in gogomap["name"]:
|
|
510
|
-
show.append(mal_entry)
|
|
511
|
-
break
|
|
512
|
-
if len(show) > 0:
|
|
513
|
-
anime_id = show[0]["node"]["id"]
|
|
514
|
-
self.update_anime_list(anime_id, {"num_watched_episodes": ep})
|
|
515
|
-
|
|
516
|
-
def sync_mal_with_seasonal(self):
|
|
517
|
-
self.get_anime_list()
|
|
518
|
-
seasonal = Seasonal()
|
|
519
|
-
seasonal_list = seasonal.list_seasonals()
|
|
520
|
-
for mal_with_gogo_map in self.local_mal_list_json["data"]:
|
|
521
|
-
if (
|
|
522
|
-
mal_with_gogo_map["node"]["my_list_status"]["status"]
|
|
523
|
-
in Config().mal_status_categories
|
|
524
|
-
and "gogo_map" in mal_with_gogo_map
|
|
525
|
-
and len(mal_with_gogo_map["gogo_map"]) > 0
|
|
526
|
-
):
|
|
527
|
-
for anime_entry in mal_with_gogo_map["gogo_map"]:
|
|
528
|
-
print(
|
|
529
|
-
"{}Syncing {}{}".format(
|
|
530
|
-
colors.GREEN, anime_entry["name"], colors.END
|
|
531
|
-
)
|
|
532
|
-
)
|
|
533
|
-
if anime_entry["name"] in [x[0] for x in seasonal_list]:
|
|
534
|
-
seasonal.update_show(
|
|
535
|
-
anime_entry["name"],
|
|
536
|
-
anime_entry["link"],
|
|
537
|
-
mal_with_gogo_map["node"]["my_list_status"][
|
|
538
|
-
"num_episodes_watched"
|
|
539
|
-
],
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
else:
|
|
543
|
-
seasonal.add_show(
|
|
544
|
-
anime_entry["name"],
|
|
545
|
-
anime_entry["link"],
|
|
546
|
-
mal_with_gogo_map["node"]["my_list_status"][
|
|
547
|
-
"num_episodes_watched"
|
|
548
|
-
],
|
|
549
|
-
)
|
|
550
|
-
|
|
551
|
-
def sync_seasonals_with_mal(self):
|
|
552
|
-
seasonal = Seasonal()
|
|
553
|
-
seasonal_list = seasonal.export_seasonals()
|
|
554
|
-
for anime in seasonal_list:
|
|
555
|
-
print(f"{colors.GREEN}Syncing {anime[0]} into MAL{colors.END} ...")
|
|
556
|
-
self.add_show(anime[0], anime[1], anime[2])
|
|
557
|
-
print(f"{colors.GREEN}Done.{colors.END}")
|
|
558
|
-
|
|
559
|
-
def auto_map_gogo_mal(self, mal_entry, mp=False):
|
|
560
|
-
# Look issue 122
|
|
561
|
-
try:
|
|
562
|
-
if "gogo_map" in mal_entry and len(mal_entry["gogo_map"]) > 0:
|
|
563
|
-
return {"failed_to_map": False, "mal_entry": mal_entry}
|
|
564
|
-
failed_to_map = True
|
|
565
|
-
cprint(colors.GREEN, "Auto mapping: ", colors.BLUE, mal_entry["node"]["title"])
|
|
566
|
-
|
|
567
|
-
search_values = [
|
|
568
|
-
mal_entry["node"]["title"],
|
|
569
|
-
mal_entry["node"]["alternative_titles"]["en"],
|
|
570
|
-
] + mal_entry["node"]["alternative_titles"]["synonyms"]
|
|
571
|
-
|
|
572
|
-
found = {}
|
|
573
|
-
for search in search_values:
|
|
574
|
-
query_class = query(search, Entry)
|
|
575
|
-
query_class.get_pages()
|
|
576
|
-
found["search"] = query_class.get_links()
|
|
577
|
-
|
|
578
|
-
if found["search"] == 0:
|
|
579
|
-
self.shows_failed_automap.add(mal_entry["node"]["title"])
|
|
580
|
-
continue
|
|
581
|
-
if "gogo_map" not in mal_entry:
|
|
582
|
-
mal_entry["gogo_map"] = []
|
|
583
|
-
for i, anime in enumerate(found["search"][1]):
|
|
584
|
-
if any(
|
|
585
|
-
anime.lower().rstrip("(dub)").rstrip("(japanese dub)").strip(" ")
|
|
586
|
-
in show.lower()
|
|
587
|
-
for show in [search_values[0], search_values[1]]
|
|
588
|
-
):
|
|
589
|
-
gogo_map = mal_entry["gogo_map"]
|
|
590
|
-
current_map = self.make_gogo_map(found["search"][0][i], anime)
|
|
591
|
-
|
|
592
|
-
self.update_gogo_map_list(gogo_map, current_map)
|
|
593
|
-
failed_to_map = False
|
|
594
|
-
if not mp:
|
|
595
|
-
self.shows_failed_automap.discard(mal_entry["node"]["title"])
|
|
596
|
-
self.update_anime_list(
|
|
597
|
-
mal_entry["node"]["id"],
|
|
598
|
-
{
|
|
599
|
-
"num_watched_episodes": mal_entry["node"]["my_list_status"][
|
|
600
|
-
"num_episodes_watched"
|
|
601
|
-
],
|
|
602
|
-
"tags": "anipy-cli",
|
|
603
|
-
},
|
|
604
|
-
)
|
|
605
|
-
else:
|
|
606
|
-
if not mp:
|
|
607
|
-
self.shows_failed_automap.add(mal_entry["node"]["title"])
|
|
608
|
-
if mp:
|
|
609
|
-
self.write_save_data()
|
|
610
|
-
return {"failed_to_map": failed_to_map, "mal_entry": mal_entry}
|
|
611
|
-
except json.JSONDecodeError:
|
|
612
|
-
return {"failed_to_map": True, "mal_entry": mal_entry}
|
|
613
|
-
|
|
614
|
-
def manual_map_gogo_mal(self, mal_anime_name: str, gogo: dict):
|
|
615
|
-
mal_entry = [
|
|
616
|
-
x
|
|
617
|
-
for x in self.local_mal_list_json["data"]
|
|
618
|
-
if x["node"]["title"] == mal_anime_name
|
|
619
|
-
]
|
|
620
|
-
if len(mal_entry) > 0:
|
|
621
|
-
current_mal_entry = mal_entry[0]
|
|
622
|
-
|
|
623
|
-
else:
|
|
624
|
-
return False
|
|
625
|
-
|
|
626
|
-
if "gogo_map" not in current_mal_entry or not isinstance(
|
|
627
|
-
current_mal_entry["gogo_map"], list
|
|
628
|
-
):
|
|
629
|
-
current_mal_entry["gogo_map"] = []
|
|
630
|
-
|
|
631
|
-
self.update_gogo_map_list(
|
|
632
|
-
current_mal_entry["gogo_map"],
|
|
633
|
-
self.make_gogo_map(gogo["link"], gogo["name"]),
|
|
634
|
-
)
|
|
635
|
-
if mal_anime_name in self.shows_failed_automap:
|
|
636
|
-
self.shows_failed_automap.discard(mal_anime_name)
|
|
637
|
-
return True
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
def update_dict_recursive(dct, merge_dct):
|
|
641
|
-
for k, v in merge_dct.items():
|
|
642
|
-
if k in dct and isinstance(dct[k], dict) and isinstance(merge_dct[k], dict):
|
|
643
|
-
update_dict_recursive(dct[k], merge_dct[k])
|
|
644
|
-
else:
|
|
645
|
-
dct[k] = merge_dct[k]
|