mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-12 15:50:01 -08:00
Merge branch 'master' into hianime
This commit is contained in:
14
README.md
14
README.md
@@ -28,14 +28,20 @@
|
||||
<summary>
|
||||
<b>My Rice</b>
|
||||
</summary>
|
||||
|
||||
|
||||
**Anilist results menu:**
|
||||

|
||||
|
||||
**Episodes menu preview:**
|
||||

|
||||
|
||||
**Without preview images enabled:**
|
||||

|
||||
|
||||
**Desktop notifications + episodes menu without image preview:**
|
||||

|
||||
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -111,7 +117,7 @@ If you have any difficulty consult for help on the [discord channel](https://dis
|
||||

|
||||
|
||||
```bash
|
||||
nix profile install github:Benex254/fastanime
|
||||
nix profile install github:Benexl/fastanime
|
||||
```
|
||||
|
||||
### Installation using your favourite package manager
|
||||
@@ -189,7 +195,7 @@ Requirements:
|
||||
|
||||
To build from the source, follow these steps:
|
||||
|
||||
1. Clone the repository: `git clone https://github.com/FastAnime/FastAnime.git --depth 1`
|
||||
1. Clone the repository: `git clone https://github.com/Benexl/FastAnime.git --depth 1`
|
||||
2. Navigate into the folder: `cd FastAnime`
|
||||
3. Then build and Install the app:
|
||||
|
||||
@@ -330,7 +336,7 @@ fastanime --manga search -t <manga-title>
|
||||
|
||||
#### The anilist command :fire: :fire: :fire:
|
||||
|
||||
Stream, browse, and discover anime efficiently from the terminal using the [AniList API](https://github.com/AniList/ApiV2-GraphQL-Docs).
|
||||
Uses the [AniList API](https://github.com/AniList/ApiV2-GraphQL-Docs) to create a terminal anilist client which is then intergrated with the scraping capabilities of the project.
|
||||
|
||||
##### Running without any subcommand
|
||||
|
||||
|
||||
@@ -12,7 +12,11 @@ anime_normalizer_raw = {
|
||||
"Re:Zero kara Hajimeru Isekai Seikatsu Season 3": "Re:Zero kara Hajimeru Isekai Seikatsu 3rd Season",
|
||||
},
|
||||
"hianime": {"My Star": "Oshi no Ko"},
|
||||
"animepahe": {"Azumanga Daiou The Animation": "Azumanga Daioh"},
|
||||
"animepahe": {
|
||||
"Azumanga Daiou The Animation": "Azumanga Daioh",
|
||||
"Mairimashita! Iruma-kun 2nd Season": "Mairimashita! Iruma-kun 2",
|
||||
"Mairimashita! Iruma-kun 3rd Season": "Mairimashita! Iruma-kun 3"
|
||||
},
|
||||
"nyaa": {},
|
||||
"yugen": {},
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ class YtDLPDownloader:
|
||||
merge=False,
|
||||
clean=False,
|
||||
prompt=True,
|
||||
force_ffmpeg=False,
|
||||
hls_use_mpegts=False,
|
||||
):
|
||||
"""Helper function that downloads anime given url and path details
|
||||
|
||||
@@ -91,7 +93,23 @@ class YtDLPDownloader:
|
||||
vid_path = ""
|
||||
sub_path = ""
|
||||
for i, url in enumerate(urls):
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
options = ydl_opts
|
||||
if i == 0:
|
||||
if force_ffmpeg:
|
||||
options = options | {
|
||||
"external_downloader": {
|
||||
'default': 'ffmpeg'
|
||||
},
|
||||
"external_downloader_args": {
|
||||
"ffmpeg_i1": ["-v", "error", "-stats"],
|
||||
},
|
||||
}
|
||||
if hls_use_mpegts:
|
||||
options = options | {
|
||||
"hls_use_mpegts": hls_use_mpegts,
|
||||
}
|
||||
|
||||
with yt_dlp.YoutubeDL(options) as ydl:
|
||||
info = ydl.extract_info(url, download=True)
|
||||
if not info:
|
||||
continue
|
||||
|
||||
@@ -226,13 +226,18 @@ def run_cli(
|
||||
from .config import Config
|
||||
|
||||
ctx.obj = Config()
|
||||
if ctx.obj.check_for_updates and ctx.invoked_subcommand != "completions":
|
||||
# TODO: only check once per week or day
|
||||
# so as to not hit api limit
|
||||
# for now i have disabled it
|
||||
if ctx.obj.check_for_updates and ctx.invoked_subcommand != "completions" and False:
|
||||
from .app_updater import check_for_updates
|
||||
import sys
|
||||
|
||||
print("Checking for updates...")
|
||||
print("So you can enjoy the latest features and bug fixes")
|
||||
print("Checking for updates...", file=sys.stderr)
|
||||
print("So you can enjoy the latest features and bug fixes", file=sys.stderr)
|
||||
print(
|
||||
"You can disable this by setting check_for_updates to False in the config"
|
||||
"You can disable this by setting check_for_updates to False in the config",
|
||||
file=sys.stderr,
|
||||
)
|
||||
is_latest, github_release_data = check_for_updates()
|
||||
if not is_latest:
|
||||
|
||||
@@ -80,15 +80,13 @@ commands = {
|
||||
fastanime --log-file anilist notifier
|
||||
""",
|
||||
)
|
||||
@click.option("--resume", is_flag=True, help="Resume from the last session")
|
||||
@click.pass_context
|
||||
def anilist(ctx: click.Context):
|
||||
def anilist(ctx: click.Context, resume: bool):
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ....anilist import AniList
|
||||
from ....AnimeProvider import AnimeProvider
|
||||
from ...interfaces.anilist_interfaces import (
|
||||
fastanime_main_menu as anilist_interface,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...config import Config
|
||||
@@ -98,4 +96,33 @@ def anilist(ctx: click.Context):
|
||||
AniList.update_login_info(user, user["token"])
|
||||
if ctx.invoked_subcommand is None:
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
anilist_interface(ctx.obj, fastanime_runtime_state)
|
||||
if resume:
|
||||
from ...interfaces.anilist_interfaces import (
|
||||
anime_provider_search_results_menu,
|
||||
)
|
||||
|
||||
if not config.user_data["recent_anime"]:
|
||||
click.echo("No recent anime found", err=True, color=True)
|
||||
return
|
||||
fastanime_runtime_state.anilist_results_data = {
|
||||
"data": {"Page": {"media": config.user_data["recent_anime"]}}
|
||||
}
|
||||
|
||||
fastanime_runtime_state.selected_anime_anilist = config.user_data[
|
||||
"recent_anime"
|
||||
][0]
|
||||
fastanime_runtime_state.selected_anime_id_anilist = config.user_data[
|
||||
"recent_anime"
|
||||
][0]["id"]
|
||||
fastanime_runtime_state.selected_anime_title_anilist = (
|
||||
config.user_data["recent_anime"][0]["title"]["romaji"]
|
||||
or config.user_data["recent_anime"][0]["title"]["english"]
|
||||
)
|
||||
anime_provider_search_results_menu(config, fastanime_runtime_state)
|
||||
|
||||
else:
|
||||
from ...interfaces.anilist_interfaces import (
|
||||
fastanime_main_menu as anilist_interface,
|
||||
)
|
||||
|
||||
anilist_interface(ctx.obj, fastanime_runtime_state)
|
||||
|
||||
@@ -42,5 +42,12 @@ def completed(config: "Config", dump_json):
|
||||
from ...interfaces import anilist_interfaces
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = (
|
||||
lambda config, **kwargs: anilist_interfaces._handle_animelist(
|
||||
config, fastanime_runtime_state, "Completed", **kwargs
|
||||
)
|
||||
)
|
||||
fastanime_runtime_state.anilist_results_data = anime_list[1]
|
||||
anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state)
|
||||
|
||||
@@ -109,6 +109,16 @@ from .data import (
|
||||
help="Whether to prompt for anything instead just do the best thing",
|
||||
default=True,
|
||||
)
|
||||
@click.option(
|
||||
"--force-ffmpeg",
|
||||
is_flag=True,
|
||||
help="Force the use of FFmpeg for downloading (supports large variety of streams but slower)",
|
||||
)
|
||||
@click.option(
|
||||
"--hls-use-mpegts",
|
||||
is_flag=True,
|
||||
help="Use mpegts for hls streams (useful for some streams: see Docs) (this option forces --force-ffmpeg to be True)",
|
||||
)
|
||||
@click.option(
|
||||
"--max-results", "-M", type=int, help="The maximum number of results to show"
|
||||
)
|
||||
@@ -132,11 +142,15 @@ def download(
|
||||
clean,
|
||||
wait_time,
|
||||
prompt,
|
||||
force_ffmpeg,
|
||||
hls_use_mpegts,
|
||||
max_results,
|
||||
):
|
||||
from ....anilist import AniList
|
||||
from rich import print
|
||||
|
||||
force_ffmpeg |= hls_use_mpegts
|
||||
|
||||
success, anilist_search_results = AniList.search(
|
||||
query=title,
|
||||
sort=sort,
|
||||
@@ -367,6 +381,8 @@ def download(
|
||||
merge=merge,
|
||||
clean=clean,
|
||||
prompt=prompt,
|
||||
force_ffmpeg=force_ffmpeg,
|
||||
hls_use_mpegts=hls_use_mpegts,
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
@@ -42,5 +42,12 @@ def dropped(config: "Config", dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = (
|
||||
lambda config, **kwargs: anilist_interfaces._handle_animelist(
|
||||
config, fastanime_runtime_state, "Dropped", **kwargs
|
||||
)
|
||||
)
|
||||
fastanime_runtime_state.anilist_results_data = anime_list[1]
|
||||
anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state)
|
||||
|
||||
@@ -26,6 +26,9 @@ def favourites(config, dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = AniList.get_most_favourite
|
||||
fastanime_runtime_state.anilist_results_data = anime_data[1]
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
else:
|
||||
|
||||
@@ -41,6 +41,12 @@ def paused(config: "Config", dump_json):
|
||||
from ...interfaces import anilist_interfaces
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
anilist_config = FastAnimeRuntimeState()
|
||||
anilist_config.anilist_results_data = anime_list[1]
|
||||
anilist_interfaces.anilist_results_menu(config, anilist_config)
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = (
|
||||
lambda config, **kwargs: anilist_interfaces._handle_animelist(
|
||||
config, fastanime_runtime_state, "Paused", **kwargs
|
||||
)
|
||||
)
|
||||
fastanime_runtime_state.anilist_results_data = anime_list[1]
|
||||
anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state)
|
||||
|
||||
@@ -42,5 +42,12 @@ def planning(config: "Config", dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = (
|
||||
lambda config, **kwargs: anilist_interfaces._handle_animelist(
|
||||
config, fastanime_runtime_state, "Planned", **kwargs
|
||||
)
|
||||
)
|
||||
fastanime_runtime_state.anilist_results_data = anime_list[1]
|
||||
anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state)
|
||||
|
||||
@@ -25,6 +25,9 @@ def popular(config, dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = AniList.get_most_popular
|
||||
fastanime_runtime_state.anilist_results_data = anime_data[1]
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
else:
|
||||
|
||||
@@ -26,6 +26,11 @@ def recent(config, dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = (
|
||||
AniList.get_most_recently_updated
|
||||
)
|
||||
fastanime_runtime_state.anilist_results_data = anime_data[1]
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
else:
|
||||
|
||||
@@ -42,5 +42,12 @@ def rewatching(config: "Config", dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = (
|
||||
lambda config, **kwargs: anilist_interfaces._handle_animelist(
|
||||
config, fastanime_runtime_state, "Rewatching", **kwargs
|
||||
)
|
||||
)
|
||||
fastanime_runtime_state.anilist_results_data = anime_list[1]
|
||||
anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state)
|
||||
|
||||
@@ -25,6 +25,9 @@ def scores(config, dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = AniList.get_most_scored
|
||||
fastanime_runtime_state.anilist_results_data = anime_data[1]
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
else:
|
||||
|
||||
@@ -111,6 +111,22 @@ def search(
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = (
|
||||
lambda page=1, **kwargs: AniList.search(
|
||||
query=title,
|
||||
sort=sort,
|
||||
status_in=list(status),
|
||||
genre_in=list(genres),
|
||||
season=season,
|
||||
tag_in=list(tags),
|
||||
seasonYear=year,
|
||||
format_in=list(media_format),
|
||||
on_list=on_list,
|
||||
page=page,
|
||||
)
|
||||
)
|
||||
fastanime_runtime_state.anilist_results_data = search_results
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
else:
|
||||
|
||||
@@ -26,6 +26,9 @@ def trending(config, dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = AniList.get_trending
|
||||
fastanime_runtime_state.anilist_results_data = data
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
else:
|
||||
|
||||
@@ -25,6 +25,9 @@ def upcoming(config, dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = AniList.get_upcoming_anime
|
||||
fastanime_runtime_state.anilist_results_data = data
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
else:
|
||||
|
||||
@@ -42,5 +42,12 @@ def watching(config: "Config", dump_json):
|
||||
from ...utils.tools import FastAnimeRuntimeState
|
||||
|
||||
fastanime_runtime_state = FastAnimeRuntimeState()
|
||||
|
||||
fastanime_runtime_state.current_page = 1
|
||||
fastanime_runtime_state.current_data_loader = (
|
||||
lambda config, **kwargs: anilist_interfaces._handle_animelist(
|
||||
config, fastanime_runtime_state, "Watching", **kwargs
|
||||
)
|
||||
)
|
||||
fastanime_runtime_state.anilist_results_data = anime_list[1]
|
||||
anilist_interfaces.anilist_results_menu(config, fastanime_runtime_state)
|
||||
|
||||
@@ -114,6 +114,16 @@ if TYPE_CHECKING:
|
||||
help="Whether to prompt for anything instead just do the best thing",
|
||||
default=True,
|
||||
)
|
||||
@click.option(
|
||||
"--force-ffmpeg",
|
||||
is_flag=True,
|
||||
help="Force the use of FFmpeg for downloading (supports large variety of streams but slower)",
|
||||
)
|
||||
@click.option(
|
||||
"--hls-use-mpegts",
|
||||
is_flag=True,
|
||||
help="Use mpegts for hls streams (useful for some streams: see Docs) (this option forces --force-ffmpeg to be True)",
|
||||
)
|
||||
@click.pass_obj
|
||||
def download(
|
||||
config: "Config",
|
||||
@@ -127,6 +137,8 @@ def download(
|
||||
clean,
|
||||
wait_time,
|
||||
prompt,
|
||||
force_ffmpeg,
|
||||
hls_use_mpegts,
|
||||
):
|
||||
import time
|
||||
|
||||
@@ -146,6 +158,8 @@ def download(
|
||||
move_preferred_subtitle_lang_to_top,
|
||||
)
|
||||
|
||||
force_ffmpeg |= hls_use_mpegts
|
||||
|
||||
anime_provider = AnimeProvider(config.provider)
|
||||
anilist_anime_info = None
|
||||
|
||||
@@ -185,6 +199,8 @@ def download(
|
||||
clean,
|
||||
wait_time,
|
||||
prompt,
|
||||
force_ffmpeg,
|
||||
hls_use_mpegts,
|
||||
)
|
||||
return
|
||||
search_results = search_results["results"]
|
||||
@@ -236,6 +252,8 @@ def download(
|
||||
clean,
|
||||
wait_time,
|
||||
prompt,
|
||||
force_ffmpeg,
|
||||
hls_use_mpegts,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -369,6 +387,8 @@ def download(
|
||||
merge=merge,
|
||||
clean=clean,
|
||||
prompt=prompt,
|
||||
force_ffmpeg=force_ffmpeg,
|
||||
hls_use_mpegts=hls_use_mpegts,
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
@@ -51,6 +51,7 @@ class Config(object):
|
||||
"image_previews": "True" if S_PLATFORM != "win32" else "False",
|
||||
"normalize_titles": "True",
|
||||
"notification_duration": "2",
|
||||
"max_cache_lifetime": "03:00:00",
|
||||
"player": "mpv",
|
||||
"preferred_history": "local",
|
||||
"preferred_language": "english",
|
||||
@@ -128,6 +129,15 @@ class Config(object):
|
||||
self.notification_duration = self.configparser.getint(
|
||||
"general", "notification_duration"
|
||||
)
|
||||
self._max_cache_lifetime = self.configparser.get(
|
||||
"general", "max_cache_lifetime"
|
||||
)
|
||||
max_cache_lifetime = list(map(int, self._max_cache_lifetime.split(":")))
|
||||
self.max_cache_lifetime = (
|
||||
max_cache_lifetime[0] * 86400
|
||||
+ max_cache_lifetime[1] * 3600
|
||||
+ max_cache_lifetime[2] * 60
|
||||
)
|
||||
self.player = self.configparser.get("stream", "player")
|
||||
self.preferred_history = self.configparser.get("stream", "preferred_history")
|
||||
self.preferred_language = self.configparser.get("general", "preferred_language")
|
||||
@@ -396,6 +406,11 @@ force_forward_tracking = {self.force_forward_tracking}
|
||||
# from the cached_requests_db
|
||||
cache_requests = {self.cache_requests}
|
||||
|
||||
# the max lifetime for a cached request <days:hours:minutes>
|
||||
# defaults to 3days = 03:00:00
|
||||
# this is the time after which a cached request will be deleted (technically : )
|
||||
max_cache_lifetime = {self._max_cache_lifetime}
|
||||
|
||||
# whether to use a persistent store (basically a sqlitedb) for storing some data the provider requires
|
||||
# to enable a seamless experience [true/false]
|
||||
# this option exists primarily because i think it may help in the optimization
|
||||
|
||||
@@ -1421,7 +1421,7 @@ def anilist_results_menu(
|
||||
anime_data[title] = anime
|
||||
|
||||
# prompt for the anime of choice
|
||||
choices = [*anime_data.keys(), "Back"]
|
||||
choices = [*anime_data.keys(), "Next Page", "Previous Page", "Back"]
|
||||
if config.use_fzf:
|
||||
if config.preview:
|
||||
from .utils import get_fzf_anime_preview
|
||||
@@ -1460,6 +1460,43 @@ def anilist_results_menu(
|
||||
if selected_anime_title == "Back":
|
||||
fastanime_main_menu(config, fastanime_runtime_state)
|
||||
return
|
||||
if selected_anime_title == "Next Page":
|
||||
fastanime_runtime_state.current_page = page = (
|
||||
fastanime_runtime_state.current_page + 1
|
||||
)
|
||||
success, data = fastanime_runtime_state.current_data_loader(
|
||||
config=config, page=page
|
||||
)
|
||||
if success:
|
||||
fastanime_runtime_state.anilist_results_data = data
|
||||
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
else:
|
||||
print("Failed to get next page")
|
||||
print(data)
|
||||
input("Enter to continue...")
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
|
||||
return
|
||||
if selected_anime_title == "Previous Page":
|
||||
fastanime_runtime_state.current_page = page = (
|
||||
(fastanime_runtime_state.current_page - 1)
|
||||
if fastanime_runtime_state.current_page > 1
|
||||
else 1
|
||||
)
|
||||
success, data = fastanime_runtime_state.current_data_loader(
|
||||
config=config, page=page
|
||||
)
|
||||
if success:
|
||||
fastanime_runtime_state.anilist_results_data = data
|
||||
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
else:
|
||||
print("Failed to get previous page")
|
||||
print(data)
|
||||
input("Enter to continue...")
|
||||
anilist_results_menu(config, fastanime_runtime_state)
|
||||
return
|
||||
|
||||
selected_anime: "AnilistBaseMediaDataSchema" = anime_data[selected_anime_title]
|
||||
fastanime_runtime_state.selected_anime_anilist = selected_anime
|
||||
@@ -1474,8 +1511,11 @@ def anilist_results_menu(
|
||||
#
|
||||
# ---- FASTANIME MAIN MENU ----
|
||||
#
|
||||
def handle_animelist(
|
||||
config: "Config", fastanime_runtime_state: "FastAnimeRuntimeState", list_type: str
|
||||
def _handle_animelist(
|
||||
config: "Config",
|
||||
fastanime_runtime_state: "FastAnimeRuntimeState",
|
||||
list_type: str,
|
||||
page=1,
|
||||
):
|
||||
"""A helper function that handles user media lists
|
||||
|
||||
@@ -1508,13 +1548,13 @@ def handle_animelist(
|
||||
status = "DROPPED"
|
||||
case "Paused":
|
||||
status = "PAUSED"
|
||||
case "Repeating":
|
||||
case "Rewatching":
|
||||
status = "REPEATING"
|
||||
case _:
|
||||
return
|
||||
|
||||
# get the media list
|
||||
anime_list = AniList.get_anime_list(status)
|
||||
anime_list = AniList.get_anime_list(status, page=page)
|
||||
# handle null
|
||||
if not anime_list:
|
||||
print("Sth went wrong", anime_list)
|
||||
@@ -1545,6 +1585,56 @@ def handle_animelist(
|
||||
return anime_list
|
||||
|
||||
|
||||
def _anilist_search(config: "Config", page=1):
|
||||
"""A function that enables seaching of an anime
|
||||
|
||||
Returns:
|
||||
[TODO:return]
|
||||
"""
|
||||
# TODO: Add filters and other search features
|
||||
if config.use_rofi:
|
||||
search_term = str(Rofi.ask("Search for"))
|
||||
else:
|
||||
search_term = Prompt.ask("[cyan]Search for[/]")
|
||||
|
||||
return AniList.search(query=search_term, page=page)
|
||||
|
||||
|
||||
def _anilist_random(config: "Config", page=1):
|
||||
"""A function that generates random anilist ids enabling random discovery of anime
|
||||
|
||||
Returns:
|
||||
[TODO:return]
|
||||
"""
|
||||
random_anime = range(1, 15000)
|
||||
random_anime = random.sample(random_anime, k=50)
|
||||
|
||||
return AniList.search(id_in=list(random_anime))
|
||||
|
||||
|
||||
def _watch_history(config: "Config", page=1):
|
||||
"""Function that lets you see all the anime that has locally been saved to your watch history
|
||||
|
||||
Returns:
|
||||
[TODO:return]
|
||||
"""
|
||||
watch_history = list(map(int, config.watch_history.keys()))
|
||||
return AniList.search(id_in=watch_history, sort="TRENDING_DESC", page=page)
|
||||
|
||||
|
||||
def _recent(config: "Config", page=1):
|
||||
return (
|
||||
True,
|
||||
{"data": {"Page": {"media": config.user_data["recent_anime"]}}},
|
||||
)
|
||||
|
||||
|
||||
# WARNING: Will probably be depracated
|
||||
def _anime_list(config: "Config", page=1):
|
||||
anime_list = config.anime_list
|
||||
return AniList.search(id_in=anime_list, pages=page)
|
||||
|
||||
|
||||
def fastanime_main_menu(
|
||||
config: "Config", fastanime_runtime_state: "FastAnimeRuntimeState"
|
||||
):
|
||||
@@ -1555,52 +1645,7 @@ def fastanime_main_menu(
|
||||
fastanime_runtime_state: A query dict used to store data during navigation of the ui # initially this was very messy
|
||||
"""
|
||||
|
||||
def _anilist_search():
|
||||
"""A function that enables seaching of an anime
|
||||
|
||||
Returns:
|
||||
[TODO:return]
|
||||
"""
|
||||
# TODO: Add filters and other search features
|
||||
if config.use_rofi:
|
||||
search_term = str(Rofi.ask("Search for"))
|
||||
else:
|
||||
search_term = Prompt.ask("[cyan]Search for[/]")
|
||||
|
||||
return AniList.search(query=search_term)
|
||||
|
||||
def _anilist_random():
|
||||
"""A function that generates random anilist ids enabling random discovery of anime
|
||||
|
||||
Returns:
|
||||
[TODO:return]
|
||||
"""
|
||||
random_anime = range(1, 15000)
|
||||
random_anime = random.sample(random_anime, k=50)
|
||||
|
||||
return AniList.search(id_in=list(random_anime))
|
||||
|
||||
def _watch_history():
|
||||
"""Function that lets you see all the anime that has locally been saved to your watch history
|
||||
|
||||
Returns:
|
||||
[TODO:return]
|
||||
"""
|
||||
watch_history = list(map(int, config.watch_history.keys()))
|
||||
return AniList.search(id_in=watch_history, sort="TRENDING_DESC")
|
||||
|
||||
def _recent():
|
||||
return (
|
||||
True,
|
||||
{"data": {"Page": {"media": config.user_data["recent_anime"]}}},
|
||||
)
|
||||
|
||||
# WARNING: Will probably be depracated
|
||||
def _anime_list():
|
||||
anime_list = config.anime_list
|
||||
return AniList.search(id_in=anime_list)
|
||||
|
||||
def _edit_config():
|
||||
def _edit_config(*args, **kwargs):
|
||||
"""Helper function to edit your config when the ui is still running"""
|
||||
|
||||
from click import edit
|
||||
@@ -1625,23 +1670,35 @@ def fastanime_main_menu(
|
||||
options = {
|
||||
f"{'🔥 ' if icons else ''}Trending": AniList.get_trending,
|
||||
f"{'🎞️ ' if icons else ''}Recent": _recent,
|
||||
f"{'📺 ' if icons else ''}Watching": lambda media_list_type="Watching": handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type
|
||||
f"{'📺 ' if icons else ''}Watching": lambda config,
|
||||
media_list_type="Watching",
|
||||
page=1: _handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type, page=page
|
||||
),
|
||||
f"{'⏸ ' if icons else ''}Paused": lambda media_list_type="Paused": handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type
|
||||
f"{'⏸ ' if icons else ''}Paused": lambda config,
|
||||
media_list_type="Paused",
|
||||
page=1: _handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type, page=page
|
||||
),
|
||||
f"{'🚮 ' if icons else ''}Dropped": lambda media_list_type="Dropped": handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type
|
||||
f"{'🚮 ' if icons else ''}Dropped": lambda config,
|
||||
media_list_type="Dropped",
|
||||
page=1: _handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type, page=page
|
||||
),
|
||||
f"{'📑 ' if icons else ''}Planned": lambda media_list_type="Planned": handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type
|
||||
f"{'📑 ' if icons else ''}Planned": lambda config,
|
||||
media_list_type="Planned",
|
||||
page=1: _handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type, page=page
|
||||
),
|
||||
f"{'✅ ' if icons else ''}Completed": lambda media_list_type="Completed": handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type
|
||||
f"{'✅ ' if icons else ''}Completed": lambda config,
|
||||
media_list_type="Completed",
|
||||
page=1: _handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type, page=page
|
||||
),
|
||||
f"{'🔁 ' if icons else ''}Rewatching": lambda media_list_type="Repeating": handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type
|
||||
f"{'🔁 ' if icons else ''}Rewatching": lambda config,
|
||||
media_list_type="Rewatching",
|
||||
page=1: _handle_animelist(
|
||||
config, fastanime_runtime_state, media_list_type, page=page
|
||||
),
|
||||
f"{'🔔 ' if icons else ''}Recently Updated Anime": AniList.get_most_recently_updated,
|
||||
f"{'🔎 ' if icons else ''}Search": _anilist_search,
|
||||
@@ -1670,7 +1727,9 @@ def fastanime_main_menu(
|
||||
choices,
|
||||
"Select Action",
|
||||
)
|
||||
anilist_data = options[action]()
|
||||
fastanime_runtime_state.current_data_loader = options[action]
|
||||
fastanime_runtime_state.current_page = 1
|
||||
anilist_data = options[action](config=config)
|
||||
# anilist data is a (bool,data)
|
||||
# the bool indicated success
|
||||
if anilist_data[0]:
|
||||
|
||||
@@ -5,6 +5,7 @@ if TYPE_CHECKING:
|
||||
|
||||
from ...libs.anilist.types import AnilistBaseMediaDataSchema
|
||||
from ...libs.anime_provider.types import Anime, EpisodeStream, SearchResult, Server
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class FastAnimeRuntimeState(object):
|
||||
@@ -26,9 +27,11 @@ class FastAnimeRuntimeState(object):
|
||||
selected_anime_title_anilist: str
|
||||
# current_anilist_data: "AnilistDataSchema | AnilistMediaList"
|
||||
anilist_results_data: "Any"
|
||||
current_page: int
|
||||
current_data_loader: "Callable"
|
||||
|
||||
|
||||
def exit_app(exit_code=0, *args):
|
||||
def exit_app(exit_code=0, *args, **kwargs):
|
||||
import sys
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
@@ -139,9 +139,7 @@ class AniListApi:
|
||||
return self._make_authenticated_request(media_list_mutation, variables)
|
||||
|
||||
def get_anime_list(
|
||||
self,
|
||||
status: "AnilistMediaListStatus",
|
||||
type="ANIME",
|
||||
self, status: "AnilistMediaListStatus", type="ANIME", page=1, **kwargs
|
||||
) -> tuple[bool, "AnilistMediaLists"] | tuple[bool, None]:
|
||||
"""gets an anime list from your media list given the list status
|
||||
|
||||
@@ -151,7 +149,12 @@ class AniListApi:
|
||||
Returns:
|
||||
a media list
|
||||
"""
|
||||
variables = {"status": status, "userId": self.user_id, "type": type}
|
||||
variables = {
|
||||
"status": status,
|
||||
"userId": self.user_id,
|
||||
"type": type,
|
||||
"page": page,
|
||||
}
|
||||
return self._make_authenticated_request(media_list_query, variables)
|
||||
|
||||
def get_medialist_entry(
|
||||
@@ -351,46 +354,43 @@ class AniListApi:
|
||||
variables = {"id": id}
|
||||
return self.get_data(anime_query, variables)
|
||||
|
||||
def get_trending(self, type="ANIME", *_, **kwargs):
|
||||
def get_trending(self, type="ANIME", page=1, *_, **kwargs):
|
||||
"""
|
||||
Gets the currently trending anime
|
||||
"""
|
||||
variables = {"type": type}
|
||||
variables = {"type": type, "page": page}
|
||||
trending = self.get_data(trending_query, variables)
|
||||
return trending
|
||||
|
||||
def get_most_favourite(self, type="ANIME", *_, **kwargs):
|
||||
def get_most_favourite(self, type="ANIME", page=1, *_, **kwargs):
|
||||
"""
|
||||
Gets the most favoured anime on anilist
|
||||
"""
|
||||
variables = {"type": type}
|
||||
variables = {"type": type, "page": page}
|
||||
most_favourite = self.get_data(most_favourite_query, variables)
|
||||
return most_favourite
|
||||
|
||||
def get_most_scored(self, type="ANIME", *_, **kwargs):
|
||||
def get_most_scored(self, type="ANIME", page=1, *_, **kwargs):
|
||||
"""
|
||||
Gets most scored anime on anilist
|
||||
"""
|
||||
variables = {"type": type}
|
||||
variables = {"type": type, "page": page}
|
||||
most_scored = self.get_data(most_scored_query, variables)
|
||||
return most_scored
|
||||
|
||||
def get_most_recently_updated(self, type="ANIME", *_, **kwargs):
|
||||
def get_most_recently_updated(self, type="ANIME", page=1, *_, **kwargs):
|
||||
"""
|
||||
Gets most recently updated anime from anilist
|
||||
"""
|
||||
variables = {"type": type}
|
||||
variables = {"type": type, "page": page}
|
||||
most_recently_updated = self.get_data(most_recently_updated_query, variables)
|
||||
return most_recently_updated
|
||||
|
||||
def get_most_popular(
|
||||
self,
|
||||
type="ANIME",
|
||||
):
|
||||
def get_most_popular(self, type="ANIME", page=1, **kwargs):
|
||||
"""
|
||||
Gets most popular anime on anilist
|
||||
"""
|
||||
variables = {"type": type}
|
||||
variables = {"type": type, "page": page}
|
||||
most_popular = self.get_data(most_popular_query, variables)
|
||||
return most_popular
|
||||
|
||||
@@ -403,8 +403,8 @@ class AniListApi:
|
||||
return upcoming_anime
|
||||
|
||||
# NOTE: THe following methods will probably be scraped soon
|
||||
def get_recommended_anime_for(self, id: int, type="ANIME", *_, **kwargs):
|
||||
variables = {"type": type}
|
||||
def get_recommended_anime_for(self, id: int, type="ANIME", page=1, *_, **kwargs):
|
||||
variables = {"type": type, "page": page}
|
||||
recommended_anime = self.get_data(recommended_query, variables)
|
||||
return recommended_anime
|
||||
|
||||
|
||||
@@ -193,8 +193,8 @@ mutation (
|
||||
"""
|
||||
|
||||
media_list_query = """
|
||||
query ($userId: Int, $status: MediaListStatus, $type: MediaType) {
|
||||
Page {
|
||||
query ($userId: Int, $status: MediaListStatus, $type: MediaType, $page: Int) {
|
||||
Page(perPage: 15, page: $page) {
|
||||
pageInfo {
|
||||
currentPage
|
||||
total
|
||||
@@ -406,8 +406,8 @@ query($query:String,%s){
|
||||
)
|
||||
|
||||
trending_query = """
|
||||
query ($type: MediaType) {
|
||||
Page(perPage: 15) {
|
||||
query ($type: MediaType, $page: Int) {
|
||||
Page(perPage: 15, page: $page) {
|
||||
media(sort: TRENDING_DESC, type: $type, genre_not_in: ["hentai"]) {
|
||||
id
|
||||
idMal
|
||||
@@ -471,8 +471,8 @@ query ($type: MediaType) {
|
||||
|
||||
# mosts
|
||||
most_favourite_query = """
|
||||
query ($type: MediaType) {
|
||||
Page(perPage: 15) {
|
||||
query ($type: MediaType, $page: Int) {
|
||||
Page(perPage: 15, page: $page) {
|
||||
media(sort: FAVOURITES_DESC, type: $type, genre_not_in: ["hentai"]) {
|
||||
id
|
||||
idMal
|
||||
@@ -539,8 +539,8 @@ query ($type: MediaType) {
|
||||
"""
|
||||
|
||||
most_scored_query = """
|
||||
query ($type: MediaType) {
|
||||
Page(perPage: 15) {
|
||||
query ($type: MediaType, $page: Int) {
|
||||
Page(perPage: 15, page: $page) {
|
||||
media(sort: SCORE_DESC, type: $type, genre_not_in: ["hentai"]) {
|
||||
id
|
||||
idMal
|
||||
@@ -603,8 +603,8 @@ query ($type: MediaType) {
|
||||
"""
|
||||
|
||||
most_popular_query = """
|
||||
query ($type: MediaType) {
|
||||
Page(perPage: 15) {
|
||||
query ($type: MediaType, $page: Int) {
|
||||
Page(perPage: 15, page: $page) {
|
||||
media(sort: POPULARITY_DESC, type: $type, genre_not_in: ["hentai"]) {
|
||||
id
|
||||
idMal
|
||||
@@ -667,8 +667,8 @@ query ($type: MediaType) {
|
||||
"""
|
||||
|
||||
most_recently_updated_query = """
|
||||
query ($type: MediaType) {
|
||||
Page(perPage: 15) {
|
||||
query ($type: MediaType, $page: Int) {
|
||||
Page(perPage: 15, page: $page) {
|
||||
media(
|
||||
sort: UPDATED_AT_DESC
|
||||
type: $type
|
||||
@@ -738,8 +738,8 @@ query ($type: MediaType) {
|
||||
"""
|
||||
|
||||
recommended_query = """
|
||||
query ($type: MediaType) {
|
||||
Page(perPage: 15) {
|
||||
query ($type: MediaType, $page: Int) {
|
||||
Page(perPage: 15, page: $page) {
|
||||
media(type: $type, genre_not_in: ["hentai"]) {
|
||||
recommendations(sort: RATING_DESC) {
|
||||
nodes {
|
||||
@@ -838,8 +838,8 @@ query ($id: Int, $type: MediaType) {
|
||||
|
||||
|
||||
anime_relations_query = """
|
||||
query ($id: Int, $type: MediaType) {
|
||||
Page(perPage: 20) {
|
||||
query ($type: MediaType, $page: Int) {
|
||||
Page(perPage: 15, page: $page) {
|
||||
media(
|
||||
id: $id
|
||||
sort: POPULARITY_DESC
|
||||
@@ -924,7 +924,7 @@ query ($id: Int,$type:MediaType) {
|
||||
|
||||
upcoming_anime_query = """
|
||||
query ($page: Int, $type: MediaType) {
|
||||
Page(page: $page) {
|
||||
Page(perPage: 15, page: $page) {
|
||||
pageInfo {
|
||||
total
|
||||
perPage
|
||||
|
||||
@@ -19,7 +19,10 @@ class AnimeProvider:
|
||||
from ..common.requests_cacher import CachedRequestsSession
|
||||
|
||||
self.session = CachedRequestsSession(
|
||||
os.path.join(APP_CACHE_DIR, "cached_requests.db")
|
||||
os.path.join(APP_CACHE_DIR, "cached_requests.db"),
|
||||
max_lifetime=int(
|
||||
os.environ.get("FASTANIME_MAX_CACHE_LIFETIME", 259200)
|
||||
),
|
||||
)
|
||||
else:
|
||||
self.session = requests.session()
|
||||
|
||||
@@ -80,7 +80,8 @@ class CachedRequestsSession(requests.Session):
|
||||
response_headers TEXT,
|
||||
data BLOB,
|
||||
redirection_policy INT,
|
||||
cache_expiry INTEGER
|
||||
cache_expiry INTEGER,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)"""
|
||||
)
|
||||
|
||||
@@ -117,6 +118,8 @@ class CachedRequestsSession(requests.Session):
|
||||
url = ?
|
||||
AND redirection_policy = ?
|
||||
AND cache_expiry > ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(url, redirection_policy, int(time.time())),
|
||||
)
|
||||
@@ -162,8 +165,15 @@ class CachedRequestsSession(requests.Session):
|
||||
logger.debug("Caching the current request")
|
||||
cursor.execute(
|
||||
f"""
|
||||
INSERT INTO {self.table_name}
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO {self.table_name} (
|
||||
url,
|
||||
status_code,
|
||||
request_headers,
|
||||
response_headers,
|
||||
data,
|
||||
redirection_policy,
|
||||
cache_expiry
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
url,
|
||||
|
||||
Reference in New Issue
Block a user