Skip to content

Commit

Permalink
Merge pull request #191 from DragonOfShuu/master
Browse files Browse the repository at this point in the history
Season Search Flag + Config Option
  • Loading branch information
sdaqo authored Aug 7, 2024
2 parents 614dfed + 01a38f0 commit dbcf1a2
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 58 deletions.
8 changes: 7 additions & 1 deletion api/src/anipy_api/player/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ def get_player(
if Path(player.name).stem == "mpv-controlled":
return MpvControllable(play_callback=play_callback)

player_dict = {"mpv": Mpv, "mpvnet": Mpv, "vlc": Vlc, "syncplay": Syncplay, "iina": Iina}
player_dict = {
"mpv": Mpv,
"mpvnet": Mpv,
"vlc": Vlc,
"syncplay": Syncplay,
"iina": Iina,
}

player_class = player_dict.get(Path(player.name).stem, None)

Expand Down
12 changes: 12 additions & 0 deletions cli/src/anipy_cli/arg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class CliArgs:
location: Optional[Path]
mal_password: Optional[str]
config: bool
seasonal_search: Optional[str]


def parse_args(override_args: Optional[list[str]] = None) -> CliArgs:
Expand Down Expand Up @@ -107,6 +108,17 @@ def parse_args(override_args: Optional[list[str]] = None) -> CliArgs:
help="Provide a search term to Default, Download or Binge mode in this format: {query}:{episode range}:{dub/sub}. Examples: 'frieren:1-10:sub' or 'frieren:1:sub' or 'frieren:1-3 7-12:dub', this argument may be appended to any of the modes mentioned like so: 'anipy-cli (-D/B) -s <search>'",
)

options_group.add_argument(
"-ss",
"--seasonal-search",
required=False,
dest="seasonal_search",
nargs="?", # 1 or none possible args
default=None, # Used if flag is not present (added this line for clarity, because default is always None)
const=True, # Used if flag is present, but no value
help="Provide search parameters for seasons to Default, Download, or Binge mode in this format: {year}:{season}. You can only use part of the season name if you wish. Examples: '2024:win' or '2020:fa'",
)

options_group.add_argument(
"-q",
"--quality",
Expand Down
12 changes: 11 additions & 1 deletion cli/src/anipy_cli/clis/binge_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from anipy_cli.colors import colors, cprint
from anipy_cli.config import Config
from anipy_cli.prompts import (
parse_seasonal_search,
pick_episode_range_prompt,
search_show_prompt,
lang_prompt,
Expand Down Expand Up @@ -34,14 +35,23 @@ def __init__(self, options: "CliArgs"):
def print_header(self):
cprint(colors.GREEN, "***Binge Mode***")

def _get_anime_from_user(self):
if (ss := self.options.seasonal_search) is not None:
return parse_seasonal_search(
"binge",
ss,
)

return search_show_prompt("binge")

def take_input(self):
if self.options.search is not None:
self.anime, self.lang, self.episodes = parse_auto_search(
"binge", self.options.search
)
return

anime = search_show_prompt("binge")
anime = self._get_anime_from_user()

if anime is None:
sys.exit(0)
Expand Down
12 changes: 11 additions & 1 deletion cli/src/anipy_cli/clis/default_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
search_show_prompt,
lang_prompt,
parse_auto_search,
parse_seasonal_search,
)
from anipy_cli.util import (
DotSpinner,
Expand Down Expand Up @@ -42,6 +43,15 @@ def __init__(self, options: "CliArgs"):
def print_header(self):
pass

def _get_anime_from_user(self):
if (ss := self.options.seasonal_search) is not None:
return parse_seasonal_search(
"default",
ss,
)

return search_show_prompt("default")

def take_input(self):
if self.options.search is not None:
self.anime, self.lang, episodes = parse_auto_search(
Expand All @@ -50,7 +60,7 @@ def take_input(self):
self.epsiode = episodes[0]
return

anime = search_show_prompt("default")
anime = self._get_anime_from_user()

if anime is None:
return False
Expand Down
12 changes: 11 additions & 1 deletion cli/src/anipy_cli/clis/download_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from anipy_cli.colors import colors, cprint
from anipy_cli.config import Config
from anipy_cli.prompts import (
parse_seasonal_search,
pick_episode_range_prompt,
search_show_prompt,
lang_prompt,
Expand Down Expand Up @@ -39,14 +40,23 @@ def print_header(self):
cprint(colors.GREEN, "***Download Mode***")
cprint(colors.GREEN, "Downloads are stored in: ", colors.END, str(self.dl_path))

def _get_anime_from_user(self):
if (ss := self.options.seasonal_search) is not None:
return parse_seasonal_search(
"download",
ss,
)

return search_show_prompt("download")

def take_input(self):
if self.options.search is not None:
self.anime, self.lang, self.episodes = parse_auto_search(
"download", self.options.search
)
return

anime = search_show_prompt("download")
anime = self._get_anime_from_user()

if anime is None:
return False
Expand Down
38 changes: 23 additions & 15 deletions cli/src/anipy_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,12 @@ def skip_season_search(self) -> bool:
"""If this is set to true you will not be prompted to search in season."""
return self._get_value("skip_season_search", False, bool)

@property
def assume_season_search(self) -> bool:
"""If this is set to true, the system will assume you want to search in season.
If skip_season_search is true, this will be ignored)"""
return self._get_value("assume_season_search", False, bool)

def _get_path_value(self, key: str, fallback: Path) -> Path:
path = self._get_value(key, fallback, str)
try:
Expand Down Expand Up @@ -385,21 +391,23 @@ def _create_config(self):
if attribute.startswith("_"):
continue

if isinstance(value, property):
doc = inspect.getdoc(value)
if doc:
# Add docstrings
doc = Template(doc).safe_substitute(version=__version__)
doc = "\n".join([f"# {line}" for line in doc.split("\n")])
dump = dump + doc + "\n"

val = self.__getattribute__(attribute)
val = str(val) if isinstance(val, Path) else val
dump = (
dump
+ yaml.dump({attribute: val}, indent=4, default_flow_style=False)
+ "\n"
)
if not isinstance(value, property):
continue

doc = inspect.getdoc(value)
if doc:
# Add docstrings
doc = Template(doc).safe_substitute(version=__version__)
doc = "\n".join([f"# {line}" for line in doc.split("\n")])
dump = dump + doc + "\n"

val = self.__getattribute__(attribute)
val = str(val) if isinstance(val, Path) else val
dump = (
dump
+ yaml.dump({attribute: val}, indent=4, default_flow_style=False)
+ "\n"
)

self._config_file.write_text(dump)

Expand Down
159 changes: 120 additions & 39 deletions cli/src/anipy_cli/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
get_prefered_providers,
error,
parse_episode_ranges,
convert_letter_to_season,
)
from anipy_cli.colors import colors
from anipy_cli.config import Config
Expand All @@ -30,22 +31,9 @@ def search_show_prompt(
mode: str, skip_season_search: bool = False
) -> Optional["Anime"]:
if not (Config().skip_season_search or skip_season_search):
season_provider = None
for p in get_prefered_providers(mode):
if p.FILTER_CAPS & (
FilterCapabilities.SEASON
| FilterCapabilities.YEAR
| FilterCapabilities.NO_QUERY
):
season_provider = p
if season_provider is not None:
should_search = inquirer.confirm("Do you want to search in season?", default=False).execute() # type: ignore
if not should_search:
print(
"Hint: you can set `skip_season_search` to `true` in the config to skip this prompt!"
)
else:
return season_search_prompt(season_provider)
anime = season_search_pre_prompt(mode)
if anime is not None:
return anime

query = inquirer.text( # type: ignore
"Search Anime:",
Expand Down Expand Up @@ -88,41 +76,82 @@ def search_show_prompt(
return anime


def season_search_prompt(provider: "BaseProvider") -> Optional["Anime"]:
year = inquirer.number( # type: ignore
message="Enter year:",
long_instruction="To skip this prompt press ctrl+z",
default=time.localtime().tm_year,
mandatory=False,
).execute()
def _get_season_provider(mode: str) -> Optional["BaseProvider"]:
season_provider = None
for p in get_prefered_providers(mode):
if p.FILTER_CAPS & (
FilterCapabilities.SEASON
| FilterCapabilities.YEAR
| FilterCapabilities.NO_QUERY
):
season_provider = p
return season_provider


def season_search_pre_prompt(
mode: str, year: Optional[int] = None, season: Optional[str] = None
) -> Optional["Anime"]:
season_provider = _get_season_provider(mode)
assume_season_search = Config().assume_season_search

# If there is no proper season provider
if season_provider is None:
if not assume_season_search:
return
# If assume search was on, and there is no proper season provider
print(
f"`assume_season_search` was set to true, but the providers ({", ".join(Config().providers[mode])}) you have selected do not have seasonal capabilities"
)
return

if assume_season_search or (year and season):
return season_search_prompt(season_provider, year, season)

should_search = inquirer.confirm("Do you want to search in season?", default=False).execute() # type: ignore

if should_search:
return season_search_prompt(season_provider)

print(
"Hint: you can set `skip_season_search` to `true` in the config to skip this prompt!"
)


def season_search_prompt(
provider: "BaseProvider", year: Optional[int] = None, season: Optional[str] = None
) -> Optional["Anime"]:
if year is None:
curr_year = time.localtime().tm_year
year = inquirer.number( # type: ignore
message="Enter year:",
long_instruction="To skip this prompt press ctrl+z",
default=curr_year,
mandatory=False,
).execute()

if year is None:
return

season = inquirer.select( # type: ignore
message="Select Season:",
choices=["Winter", "Spring", "Summer", "Fall"],
instruction="The season selected by default is the current season.",
long_instruction="To skip this prompt press ctrl+z",
default=get_anime_season(time.localtime().tm_mon),
mandatory=False,
).execute()
if season is None:
season = inquirer.select( # type: ignore
message="Select Season:",
choices=["Winter", "Spring", "Summer", "Fall"],
instruction="The season selected by default is the current season.",
long_instruction="To skip this prompt press ctrl+z",
default=get_anime_season(time.localtime().tm_mon),
mandatory=False,
).execute()

if season is None:
return

season = Season[season.upper()]

filters = Filters(year=year, season=season)
results = [
Anime.from_search_result(provider, r)
for r in provider.get_search(query="", filters=filters)
]
discovered_anime = get_anime_by_season(provider, year, Season[season.upper()])

anime = inquirer.fuzzy( # type: ignore
message="Select Show:",
choices=[
Choice(value=r, name=f"{n + 1}. {repr(r)}") for n, r in enumerate(results)
Choice(value=r, name=f"{n + 1}. {repr(r)}")
for n, r in enumerate(discovered_anime)
],
long_instruction=(
"\nS = Anime is available in sub\n"
Expand All @@ -136,6 +165,17 @@ def season_search_prompt(provider: "BaseProvider") -> Optional["Anime"]:
return anime


def get_anime_by_season(provider: "BaseProvider", year: int, season: Season):
with DotSpinner(
"Retrieving anime in ", colors.BLUE, f"{season.name} {year}", "..."
):
filters = Filters(year=year, season=season)
return [
Anime.from_search_result(provider, r)
for r in provider.get_search(query="", filters=filters)
]


def pick_episode_prompt(
anime: "Anime", lang: LanguageTypeEnum, instruction: str = ""
) -> Optional["Episode"]:
Expand Down Expand Up @@ -203,6 +243,47 @@ def lang_prompt(anime: "Anime") -> LanguageTypeEnum:
return next(iter(anime.languages))


def parse_seasonal_search(mode: str, passed: str | bool) -> Optional["Anime"]:
"""
Takes the mode we are in, as well as the search parameters.
Asks the user to choose an anime.
`Mode`: The provider to use.
`Passed`: The search terms passed (ex: `year:season`) or True,
if True we'll ask the user for this information
"""
if isinstance(passed, bool):
if not passed:
return

provider = _get_season_provider(mode)

if not provider:
error(
"No valid provider was found for season search in the current mode",
fatal=True,
)

return season_search_prompt(provider)

options = iter(passed.split(":"))
year = next(options, None)
season = next(options, None)

if (not year) or not year.isnumeric():
error("A year was either not provided, or was not a number", fatal=True)

if not season:
error("A season was not provided", fatal=True)

season = convert_letter_to_season(season)

if not season:
error("The given season was not a valid season", fatal=True)

return season_search_pre_prompt(mode, int(year), season)


def parse_auto_search(
mode: str, passed: str
) -> Tuple["Anime", LanguageTypeEnum, List["Episode"]]:
Expand Down
Loading

0 comments on commit dbcf1a2

Please sign in to comment.