anime-downloader/anime_downloader/watch.py

226 lines
8.7 KiB
Python
Raw Normal View History

2020-08-03 10:01:30 -07:00
from anime_downloader import config
2020-10-15 13:05:34 -07:00
from anime_downloader.sites import get_anime_class, ALL_ANIME_SITES
2018-05-31 04:04:47 -07:00
import os
import sys
import json
2018-05-31 04:04:47 -07:00
import logging
import click
2018-06-03 05:43:19 -07:00
import warnings
2018-06-03 10:30:26 -07:00
from time import time
2018-06-03 05:43:19 -07:00
logger = logging.getLogger(__name__)
2018-06-03 05:43:19 -07:00
# Don't warn if not using fuzzywuzzy[speedup]
with warnings.catch_warnings():
warnings.simplefilter('ignore')
from fuzzywuzzy import process
2018-05-31 04:04:47 -07:00
class Watcher:
WATCH_FILE = os.path.join(config.APP_DIR, 'watch.json')
def __init__(self):
self.sorted = None
2018-05-31 04:04:47 -07:00
pass
def new(self, url):
AnimeInfo = self._get_anime_info_class(url)
2018-06-03 10:30:26 -07:00
anime = AnimeInfo(url, timestamp=time())
2018-05-31 04:04:47 -07:00
self._append_to_watch_file(anime)
logger.info('Added {:.50} to watch list.'.format(anime.title))
return anime
2018-05-31 04:04:47 -07:00
def list(self, filt=None):
2018-05-31 04:04:47 -07:00
animes = self._read_from_watch_file()
if filt in [None, 'all']:
animes = self._sorting_for_list(animes)
self.sorted = True
click.echo('{:>5} | {:^35} | {:^8} | {} | {:^10}'.format(
'SlNo', 'Name', 'Eps', 'Score', 'Status'
2018-06-02 12:11:43 -07:00
))
click.echo('-' * 65)
fmt_str = '{:5} | {:35.35} | {:3}/{:<3} | {:^5} | {}'
if not filt in [None, 'all']:
animes = [i for i in animes if i.watch_status == filt]
2018-05-31 04:04:47 -07:00
for idx, anime in enumerate(animes):
meta = anime.meta
click.echo(click.style(fmt_str.format(idx + 1,
anime.title,
*anime.progress(),
anime.score,
anime.watch_status), fg=anime.colours))
2018-05-31 04:04:47 -07:00
def anime_list(self):
return self._read_from_watch_file()
2018-05-31 04:04:47 -07:00
def get(self, anime_name):
animes = self._read_from_watch_file()
if self.sorted == True:
animes = self._sorting_for_list(animes)
2018-06-03 05:43:19 -07:00
if isinstance(anime_name, int):
return animes[anime_name]
2018-06-02 13:04:39 -07:00
match = process.extractOne(anime_name, animes, score_cutoff=40)
if match:
2018-06-03 10:30:26 -07:00
anime = match[0]
logger.debug('Anime: {!r}, episodes_done: {}'.format(
2018-07-06 11:13:10 -07:00
anime, anime.episodes_done))
2018-06-03 10:30:26 -07:00
if (time() - anime._timestamp) > 4 * 24 * 60 * 60:
2018-06-05 14:11:43 -07:00
anime = self.update_anime(anime)
2018-06-03 10:30:26 -07:00
return anime
2018-05-31 04:04:47 -07:00
2018-06-05 14:11:43 -07:00
def update_anime(self, anime):
if not hasattr(anime, 'colours'):
colours = {
2020-10-14 21:31:01 -07:00
'watching': 'cyan',
'completed': 'green',
'dropped': 'red',
'planned': 'yellow',
'hold': 'white'
}
anime.colours = colours.get(anime.watch_status, 'yellow')
if not hasattr(anime, 'meta') or not anime.meta.get('Status') or \
anime.meta['Status'].lower() == 'airing':
logger.info('Updating anime {}'.format(anime.title))
AnimeInfo = self._get_anime_info_class(anime.url)
newanime = AnimeInfo(anime.url, episodes_done=anime.episodes_done,
2018-06-18 12:07:31 -07:00
timestamp=time())
newanime.title = anime.title
self.update(newanime)
return newanime
2018-07-05 08:40:40 -07:00
return anime
2018-06-05 14:11:43 -07:00
2018-06-03 05:43:19 -07:00
def add(self, anime):
self._append_to_watch_file(anime)
2018-06-05 14:11:43 -07:00
def remove(self, anime):
anime_name = anime.title
2018-06-02 12:11:43 -07:00
animes = self._read_from_watch_file()
animes = [anime for anime in animes if anime.title != anime_name]
self._write_to_watch_file(animes)
def update(self, changed_anime):
animes = self._read_from_watch_file()
animes = [anime for anime in animes
if anime.title != changed_anime.title]
animes = [changed_anime] + animes
self._write_to_watch_file(animes)
2018-05-31 04:04:47 -07:00
def _append_to_watch_file(self, anime):
if not os.path.exists(self.WATCH_FILE):
2018-06-02 12:11:43 -07:00
self._write_to_watch_file([anime])
2018-05-31 04:04:47 -07:00
return
data = self._read_from_watch_file()
2018-06-02 12:11:43 -07:00
data = [anime] + data
2018-06-02 12:11:43 -07:00
self._write_to_watch_file(data)
2018-05-31 04:04:47 -07:00
def _write_to_watch_file(self, animes, MAL_import=False):
if not MAL_import:
animes = [anime.__dict__ for anime in animes]
with open(self.WATCH_FILE, 'w') as watch_file:
json.dump(animes, watch_file)
2018-05-31 04:04:47 -07:00
def _import_from_MAL(self, PATH):
import xml.etree.ElementTree as ET # Standard Library import, conditional as it only needs to be imported for this line
root = ET.parse(PATH).getroot()
list_to_dict = []
values = {'Plan to Watch': {'planned': 'yellow'},
'Completed': {'completed': 'green'},
'Watching': {'watching': 'cyan'},
'Dropped': {'dropped': 'red'},
'On-Hold': {'hold': 'white'}
}
for type_tag in root.findall('anime'):
mal_watched_episodes = type_tag.find('my_watched_episodes').text
mal_score = type_tag.find('my_score').text
mal_watch_status = type_tag.find('my_status').text
colour = str(list(values[mal_watch_status].values())[0])
mal_watch_status = str(list(values[mal_watch_status].keys())[0])
mal_title = type_tag.find('series_title').text
mal_episodes = type_tag.find('series_episodes').text
2020-10-21 15:08:11 -07:00
mal_ID = type_tag.find('series_animedb_id').text
2020-10-15 13:23:05 -07:00
#We have to initialise some values for when we add anime from MAL. Now, we do this instead of letting the user choose the provider
#On first run, this is so the user doesn't have to manually do hundreds of entries. The values initialise to one of the sites we already have
#But with a broken link, the provider needs to be set manually for a series by using the set command in the list.
list_to_dict.append({
"episodes_done": int(mal_watched_episodes),
"_timestamp": time(),
"score": int(mal_score),
"watch_status": mal_watch_status,
"colours": colour,
2020-10-21 15:08:11 -07:00
'mal_ID': int(mal_ID),
2020-10-15 13:23:05 -07:00
"url": ALL_ANIME_SITES[0][1],
"_fallback_qualities": ["720p", "480p", "360p"],
"quality": "720p",
"title": mal_title,
2020-10-15 13:23:05 -07:00
"_episode_urls": [[1, "https://notarealwebsite.illusion/"]],
"_len": int(mal_episodes)
})
self._write_to_watch_file(list_to_dict, MAL_import=True)
2020-10-15 13:23:05 -07:00
logger.warn("MAL List has been imported, please initialise the sites by using the 'set' command on a list entry!")
2018-05-31 04:04:47 -07:00
def _read_from_watch_file(self):
if not os.path.exists(self.WATCH_FILE):
logger.error('Add something to watch list first.')
2018-06-03 10:30:26 -07:00
sys.exit(1)
2018-05-31 04:04:47 -07:00
with open(self.WATCH_FILE, 'r') as watch_file:
data = json.load(watch_file)
ret = []
for anime_dict in data:
# For backwards compatibility
if '_episodeIds' in anime_dict:
anime_dict['_episode_urls'] = anime_dict['_episodeIds']
AnimeInfo = self._get_anime_info_class(anime_dict['url'])
anime = AnimeInfo(_skip_online_data=True)
anime.__dict__ = anime_dict
ret.append(anime)
return ret
2018-05-31 04:04:47 -07:00
def _sorting_for_list(self, animes):
status_index = ['watching', 'completed', 'dropped', 'planned', 'hold', 'all']
animes = sorted(animes, key=lambda x: status_index.index(x.watch_status))
return animes
def _get_anime_info_class(self, url):
cls = get_anime_class(url)
if not cls:
logger.warn(f"The url: {url} is no longer supported. The provider needs to be set manually upon selection.")
"""
2020-10-15 13:05:34 -07:00
Provides some level of backcompatability when watch lists have providers that have been removed. They are then warned via logger that they will
have to change providers using the set function when an anime is selected in the list.
"""
2020-10-15 13:05:34 -07:00
url = ALL_ANIME_SITES[0][1]
cls = get_anime_class(url)
2018-05-31 04:04:47 -07:00
# TODO: Maybe this is better off as a mixin
class AnimeInfo(cls, sitename=cls.sitename):
def __init__(self, *args, **kwargs):
self.episodes_done = kwargs.pop('episodes_done', 0)
self._timestamp = kwargs.pop('timestamp', 0)
2020-10-21 15:08:11 -07:00
# Initial values needed for MAL which can't be got yet from just a simple addition to the watch list.
self.score = 0
self.watch_status = 'watching'
self.colours = 'blue'
2020-10-21 15:08:11 -07:00
self.mal_ID = 0
super(cls, self).__init__(*args, **kwargs)
def progress(self):
return (self.episodes_done, len(self))
2018-05-31 04:04:47 -07:00
return AnimeInfo