mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-14 08:30:47 -08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be7f464073 | ||
|
|
c7f8f168f5 | ||
|
|
ba59fbdcb0 | ||
|
|
9f54fa4998 | ||
|
|
3c9688b32c | ||
|
|
1f046447bb |
23
README.md
23
README.md
@@ -68,7 +68,7 @@ Heavily inspired by [animdl](https://github.com/justfoolingaround/animdl), [jerr
|
|||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
>
|
>
|
||||||
> This project currently scrapes allanime, aniwatch and animepahe. The site is in the public domain and can be accessed by any one with a browser.
|
> This project currently scrapes allanime, hianime and animepahe, nyaa. The site is in the public domain and can be accessed by any one with a browser.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -182,6 +182,7 @@ The only required external dependency, unless you won't be streaming, is [MPV](h
|
|||||||
|
|
||||||
**Other external dependencies that will just make your experience better:**
|
**Other external dependencies that will just make your experience better:**
|
||||||
|
|
||||||
|
- [webtorrent-cli](https://github.com/webtorrent/webtorrent-cli) used when the provider is nyaa
|
||||||
- [ffmpeg](https://www.ffmpeg.org/) is required to be in your path environment variables to properly download [hls](https://www.cloudflare.com/en-gb/learning/video/what-is-http-live-streaming/) streams.
|
- [ffmpeg](https://www.ffmpeg.org/) is required to be in your path environment variables to properly download [hls](https://www.cloudflare.com/en-gb/learning/video/what-is-http-live-streaming/) streams.
|
||||||
- [fzf](https://github.com/junegunn/fzf) 🔥 which is used as a better alternative to the ui.
|
- [fzf](https://github.com/junegunn/fzf) 🔥 which is used as a better alternative to the ui.
|
||||||
- [rofi](https://github.com/davatorium/rofi) 🔥 which is used as another alternative ui + the the desktop entry ui
|
- [rofi](https://github.com/davatorium/rofi) 🔥 which is used as another alternative ui + the the desktop entry ui
|
||||||
@@ -196,7 +197,7 @@ The only required external dependency, unless you won't be streaming, is [MPV](h
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The project offers a featureful command-line interface and MPV interface through the use of python-mpv.
|
The project offers a featureful command-line interface and MPV interface through the use of python-mpv.
|
||||||
The project also offers subs in different languages thanks to aniwatch provider.
|
The project also offers subs in different languages thanks to hianime provider.
|
||||||
|
|
||||||
### The Commandline interface :fire:
|
### The Commandline interface :fire:
|
||||||
|
|
||||||
@@ -240,7 +241,7 @@ Available options for the fastanime include:
|
|||||||
- `--default` use the default ui
|
- `--default` use the default ui
|
||||||
- `--preview` show a preview when using fzf
|
- `--preview` show a preview when using fzf
|
||||||
- `--no-preview` dont show a preview when using fzf
|
- `--no-preview` dont show a preview when using fzf
|
||||||
- `--format <yt-dlp format string>` or `-f <yt-dlp format string>` set the format of anime downloaded and streamed based on [yt-dlp format](https://github.com/yt-dlp/yt-dlp#format-selection). Works when `--server gogoanime` or on providers that provide multi quality streams eg aniwatch
|
- `--format <yt-dlp format string>` or `-f <yt-dlp format string>` set the format of anime downloaded and streamed based on [yt-dlp format](https://github.com/yt-dlp/yt-dlp#format-selection). Works when `--server gogoanime` or on providers that provide multi quality streams eg hianime
|
||||||
- `--icons/--no-icons` toggle the visibility of the icons
|
- `--icons/--no-icons` toggle the visibility of the icons
|
||||||
- `--skip/--no-skip` whether to skip the opening and ending theme songs.
|
- `--skip/--no-skip` whether to skip the opening and ending theme songs.
|
||||||
- `--rofi` use rofi for the ui
|
- `--rofi` use rofi for the ui
|
||||||
@@ -251,9 +252,9 @@ Available options for the fastanime include:
|
|||||||
- `--log-file` allow logging to a file
|
- `--log-file` allow logging to a file
|
||||||
- `--rich-traceback` allow rich traceback
|
- `--rich-traceback` allow rich traceback
|
||||||
- `--use-mpv-mod/--use-default-player` whether to use python-mpv
|
- `--use-mpv-mod/--use-default-player` whether to use python-mpv
|
||||||
- `--provider <allanime/animepahe>` anime site of choice to scrape from
|
- `--provider <allanime/animepahe/hianime/nyaa>` anime site of choice to scrape from
|
||||||
- `--sync-play` or `-sp` use syncplay for streaming anime so you can watch with your friends
|
- `--sync-play` or `-sp` use syncplay for streaming anime so you can watch with your friends
|
||||||
- `--sub-lang <en/or any other common shortform for country>` regex is used to determine the appropriate. Only works when provider is aniwatch.
|
- `--sub-lang <en/or any other common shortform for country>` regex is used to determine the appropriate. Only works when provider is hianime.
|
||||||
- `--normalize-titles/--no-normalize-titles` whether to normalize provider titles
|
- `--normalize-titles/--no-normalize-titles` whether to normalize provider titles
|
||||||
- `--manga` toggle experimental manga mode
|
- `--manga` toggle experimental manga mode
|
||||||
|
|
||||||
@@ -431,7 +432,7 @@ fastanime download -t <anime-title> -r ':<episodes-end>'
|
|||||||
# remember python indexing starts at 0
|
# remember python indexing starts at 0
|
||||||
fastanime download -t <anime-title> -r '<episode-1>:<episode>'
|
fastanime download -t <anime-title> -r '<episode-1>:<episode>'
|
||||||
|
|
||||||
# merge subtitles with ffmpeg to mkv format; aniwatch tends to give subs as separate files
|
# merge subtitles with ffmpeg to mkv format; hianime tends to give subs as separate files
|
||||||
# and dont prompt for anything
|
# and dont prompt for anything
|
||||||
# eg existing file in destination instead remove
|
# eg existing file in destination instead remove
|
||||||
# and clean
|
# and clean
|
||||||
@@ -717,10 +718,10 @@ quality = 1080
|
|||||||
# this also applies to episode titles
|
# this also applies to episode titles
|
||||||
normalize_titles = True
|
normalize_titles = True
|
||||||
|
|
||||||
# can be [allanime, animepahe, aniwatch]
|
# can be [allanime, animepahe, hianime]
|
||||||
# allanime is the most realible
|
# allanime is the most realible
|
||||||
# animepahe provides different links to streams of different quality so a quality can be selected reliably with --quality option
|
# animepahe provides different links to streams of different quality so a quality can be selected reliably with --quality option
|
||||||
# aniwatch which is now hianime usually provides subs in different languuages and its servers are generally faster
|
# hianime which is now hianime usually provides subs in different languuages and its servers are generally faster
|
||||||
provider = allanime
|
provider = allanime
|
||||||
|
|
||||||
# Display language [english, romaji]
|
# Display language [english, romaji]
|
||||||
@@ -772,7 +773,7 @@ notification_duration = 2
|
|||||||
|
|
||||||
# used when the provider gives subs of different languages
|
# used when the provider gives subs of different languages
|
||||||
# currently its the case for:
|
# currently its the case for:
|
||||||
# aniwatch
|
# hianime
|
||||||
# the values for this option are the short names for countries
|
# the values for this option are the short names for countries
|
||||||
# regex is used to determine what you selected
|
# regex is used to determine what you selected
|
||||||
sub_lang = eng
|
sub_lang = eng
|
||||||
@@ -799,7 +800,7 @@ translation_type = sub
|
|||||||
# what server to use for a particular provider
|
# what server to use for a particular provider
|
||||||
# allanime: [dropbox, sharepoint, wetransfer, gogoanime, wixmp]
|
# allanime: [dropbox, sharepoint, wetransfer, gogoanime, wixmp]
|
||||||
# animepahe: [kwik]
|
# animepahe: [kwik]
|
||||||
# aniwatch: [HD1, HD2, StreamSB, StreamTape]
|
# hianime: [HD1, HD2, StreamSB, StreamTape]
|
||||||
# 'top' can also be used as a value for this option
|
# 'top' can also be used as a value for this option
|
||||||
# 'top' will cause fastanime to auto select the first server it sees
|
# 'top' will cause fastanime to auto select the first server it sees
|
||||||
# this saves on resources and is faster since not all servers are being fetched
|
# this saves on resources and is faster since not all servers are being fetched
|
||||||
@@ -860,7 +861,7 @@ force_window = immediate
|
|||||||
# only works for downloaded anime if:
|
# only works for downloaded anime if:
|
||||||
# provider=allanime, server=gogoanime
|
# provider=allanime, server=gogoanime
|
||||||
# provider=allanime, server=wixmp
|
# provider=allanime, server=wixmp
|
||||||
# provider=aniwatch
|
# provider=hianime
|
||||||
# this is because they provider a m3u8 file that contans multiple quality streams
|
# this is because they provider a m3u8 file that contans multiple quality streams
|
||||||
format = best[height<=1080]/bestvideo[height<=1080]+bestaudio/best
|
format = best[height<=1080]/bestvideo[height<=1080]+bestaudio/best
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ anime_normalizer_raw = {
|
|||||||
},
|
},
|
||||||
"hianime": {"My Star": "Oshi no Ko"},
|
"hianime": {"My Star": "Oshi no Ko"},
|
||||||
"animepahe": {"Azumanga Daiou The Animation": "Azumanga Daioh"},
|
"animepahe": {"Azumanga Daiou The Animation": "Azumanga Daioh"},
|
||||||
|
"nyaa": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,25 @@ class YtDLPDownloader:
|
|||||||
"""
|
"""
|
||||||
anime_title = sanitize_filename(anime_title)
|
anime_title = sanitize_filename(anime_title)
|
||||||
episode_title = sanitize_filename(episode_title)
|
episode_title = sanitize_filename(episode_title)
|
||||||
|
if url.endswith(".torrent"):
|
||||||
|
WEBTORRENT_CLI = shutil.which("webtorrent")
|
||||||
|
if not WEBTORRENT_CLI:
|
||||||
|
import time
|
||||||
|
|
||||||
|
print(
|
||||||
|
"webtorrent cli is not installed which is required for downloading and streaming from nyaa\nplease install it or use another provider"
|
||||||
|
)
|
||||||
|
time.sleep(120)
|
||||||
|
return
|
||||||
|
cmd = [
|
||||||
|
WEBTORRENT_CLI,
|
||||||
|
"download",
|
||||||
|
url,
|
||||||
|
"--out",
|
||||||
|
os.path.join(download_dir, anime_title, episode_title),
|
||||||
|
]
|
||||||
|
subprocess.run(cmd)
|
||||||
|
return
|
||||||
ydl_opts = {
|
ydl_opts = {
|
||||||
# Specify the output path and template
|
# Specify the output path and template
|
||||||
"http_headers": headers,
|
"http_headers": headers,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ if sys.version_info < (3, 10):
|
|||||||
) # noqa: F541
|
) # noqa: F541
|
||||||
|
|
||||||
|
|
||||||
__version__ = "v2.5.5"
|
__version__ = "v2.5.6"
|
||||||
|
|
||||||
APP_NAME = "FastAnime"
|
APP_NAME = "FastAnime"
|
||||||
AUTHOR = "Benex254"
|
AUTHOR = "Benex254"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ if TYPE_CHECKING:
|
|||||||
# remember python indexing starts at 0
|
# remember python indexing starts at 0
|
||||||
fastanime download -t <anime-title> -r '<episode-1>:<episode>'
|
fastanime download -t <anime-title> -r '<episode-1>:<episode>'
|
||||||
\b
|
\b
|
||||||
# merge subtitles with ffmpeg to mkv format; aniwatch tends to give subs as separate files
|
# merge subtitles with ffmpeg to mkv format; hianime tends to give subs as separate files
|
||||||
# and dont prompt for anything
|
# and dont prompt for anything
|
||||||
# eg existing file in destination instead remove
|
# eg existing file in destination instead remove
|
||||||
# and clean
|
# and clean
|
||||||
|
|||||||
@@ -295,10 +295,10 @@ quality = {self.quality}
|
|||||||
# this also applies to episode titles
|
# this also applies to episode titles
|
||||||
normalize_titles = {self.normalize_titles}
|
normalize_titles = {self.normalize_titles}
|
||||||
|
|
||||||
# can be [allanime, animepahe, aniwatch]
|
# can be [allanime, animepahe, hianime]
|
||||||
# allanime is the most realible
|
# allanime is the most realible
|
||||||
# animepahe provides different links to streams of different quality so a quality can be selected reliably with --quality option
|
# animepahe provides different links to streams of different quality so a quality can be selected reliably with --quality option
|
||||||
# aniwatch which is now hianime usually provides subs in different languuages and its servers are generally faster
|
# hianime which is now hianime usually provides subs in different languuages and its servers are generally faster
|
||||||
provider = {self.provider}
|
provider = {self.provider}
|
||||||
|
|
||||||
# Display language [english, romaji]
|
# Display language [english, romaji]
|
||||||
@@ -350,7 +350,7 @@ notification_duration = {self.notification_duration}
|
|||||||
|
|
||||||
# used when the provider gives subs of different languages
|
# used when the provider gives subs of different languages
|
||||||
# currently its the case for:
|
# currently its the case for:
|
||||||
# aniwatch
|
# hianime
|
||||||
# the values for this option are the short names for countries
|
# the values for this option are the short names for countries
|
||||||
# regex is used to determine what you selected
|
# regex is used to determine what you selected
|
||||||
sub_lang = {self.sub_lang}
|
sub_lang = {self.sub_lang}
|
||||||
@@ -388,7 +388,7 @@ translation_type = {self.translation_type}
|
|||||||
# what server to use for a particular provider
|
# what server to use for a particular provider
|
||||||
# allanime: [dropbox, sharepoint, wetransfer, gogoanime, wixmp]
|
# allanime: [dropbox, sharepoint, wetransfer, gogoanime, wixmp]
|
||||||
# animepahe: [kwik]
|
# animepahe: [kwik]
|
||||||
# aniwatch: [HD1, HD2, StreamSB, StreamTape]
|
# hianime: [HD1, HD2, StreamSB, StreamTape]
|
||||||
# 'top' can also be used as a value for this option
|
# 'top' can also be used as a value for this option
|
||||||
# 'top' will cause fastanime to auto select the first server it sees
|
# 'top' will cause fastanime to auto select the first server it sees
|
||||||
# this saves on resources and is faster since not all servers are being fetched
|
# this saves on resources and is faster since not all servers are being fetched
|
||||||
@@ -450,7 +450,7 @@ force_window = immediate
|
|||||||
# only works for downloaded anime if:
|
# only works for downloaded anime if:
|
||||||
# provider=allanime, server=gogoanime
|
# provider=allanime, server=gogoanime
|
||||||
# provider=allanime, server=wixmp
|
# provider=allanime, server=wixmp
|
||||||
# provider=aniwatch
|
# provider=hianime
|
||||||
# this is because they provider a m3u8 file that contans multiple quality streams
|
# this is because they provider a m3u8 file that contans multiple quality streams
|
||||||
format = {self.format}
|
format = {self.format}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,19 @@ def run_mpv(
|
|||||||
# Regex to check if the link is a YouTube URL
|
# Regex to check if the link is a YouTube URL
|
||||||
youtube_regex = r"(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/.+"
|
youtube_regex = r"(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/.+"
|
||||||
|
|
||||||
|
if link.endswith(".torrent"):
|
||||||
|
WEBTORRENT_CLI = shutil.which("webtorrent")
|
||||||
|
if not WEBTORRENT_CLI:
|
||||||
|
import time
|
||||||
|
|
||||||
|
print(
|
||||||
|
"webtorrent cli is not installed which is required for downloading and streaming from nyaa\nplease install it or use another provider"
|
||||||
|
)
|
||||||
|
time.sleep(120)
|
||||||
|
return "0", "0"
|
||||||
|
cmd = [WEBTORRENT_CLI, link, f"--{player}"]
|
||||||
|
subprocess.run(cmd)
|
||||||
|
return "0", "0"
|
||||||
if player == "vlc":
|
if player == "vlc":
|
||||||
VLC = shutil.which("vlc")
|
VLC = shutil.which("vlc")
|
||||||
if not VLC and not S_PLATFORM == "win32":
|
if not VLC and not S_PLATFORM == "win32":
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
from .allanime.constants import SERVERS_AVAILABLE as ALLANIME_SERVERS
|
from .allanime.constants import SERVERS_AVAILABLE as ALLANIME_SERVERS
|
||||||
from .animepahe.constants import SERVERS_AVAILABLE as ANIMEPAHESERVERS
|
from .animepahe.constants import SERVERS_AVAILABLE as ANIMEPAHE_SERVERS
|
||||||
from .hianime.constants import SERVERS_AVAILABLE as ANIWATCHSERVERS
|
from .hianime.constants import SERVERS_AVAILABLE as HIANIME_SERVERS
|
||||||
|
|
||||||
anime_sources = {
|
anime_sources = {
|
||||||
"allanime": "api.AllAnimeAPI",
|
"allanime": "api.AllAnimeAPI",
|
||||||
"animepahe": "api.AnimePaheApi",
|
"animepahe": "api.AnimePaheApi",
|
||||||
"hianime": "api.HiAnimeApi",
|
"hianime": "api.HiAnimeApi",
|
||||||
|
"nyaa": "api.NyaaApi",
|
||||||
}
|
}
|
||||||
SERVERS_AVAILABLE = [*ALLANIME_SERVERS, *ANIMEPAHESERVERS, *ANIWATCHSERVERS]
|
SERVERS_AVAILABLE = [*ALLANIME_SERVERS, *ANIMEPAHE_SERVERS, *HIANIME_SERVERS]
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from ..base_provider import AnimeProvider
|
|||||||
from ..decorators import debug_provider
|
from ..decorators import debug_provider
|
||||||
from ..utils import give_random_quality
|
from ..utils import give_random_quality
|
||||||
from .constants import SERVERS_AVAILABLE
|
from .constants import SERVERS_AVAILABLE
|
||||||
from .types import AniWatchStream
|
from .types import HiAnimeStream
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class ParseAnchorAndImgTag(HTMLParser):
|
|||||||
class HiAnimeApi(AnimeProvider):
|
class HiAnimeApi(AnimeProvider):
|
||||||
# HEADERS = {"Referer": "https://hianime.to/home"}
|
# HEADERS = {"Referer": "https://hianime.to/home"}
|
||||||
|
|
||||||
@debug_provider("ANIWATCH")
|
@debug_provider("HIANIME")
|
||||||
def search_for_anime(self, anime_title: str, *args):
|
def search_for_anime(self, anime_title: str, *args):
|
||||||
query = quote_plus(anime_title)
|
query = quote_plus(anime_title)
|
||||||
url = f"https://hianime.to/search?keyword={query}"
|
url = f"https://hianime.to/search?keyword={query}"
|
||||||
@@ -88,20 +88,20 @@ class HiAnimeApi(AnimeProvider):
|
|||||||
self.search_results = results
|
self.search_results = results
|
||||||
return {"pageInfo": {}, "results": results}
|
return {"pageInfo": {}, "results": results}
|
||||||
|
|
||||||
@debug_provider("ANIWATCH")
|
@debug_provider("HIANIME")
|
||||||
def get_anime(self, aniwatch_id, *args):
|
def get_anime(self, hianime_id, *args):
|
||||||
anime_result = {}
|
anime_result = {}
|
||||||
for anime in self.search_results:
|
for anime in self.search_results:
|
||||||
if anime["id"] == aniwatch_id:
|
if anime["id"] == hianime_id:
|
||||||
anime_result = anime
|
anime_result = anime
|
||||||
break
|
break
|
||||||
anime_url = f"https://hianime.to/ajax/v2/episode/list/{aniwatch_id}"
|
anime_url = f"https://hianime.to/ajax/v2/episode/list/{hianime_id}"
|
||||||
response = self.session.get(anime_url, timeout=10)
|
response = self.session.get(anime_url, timeout=10)
|
||||||
if response.ok:
|
if response.ok:
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
aniwatch_anime_page = response_json["html"]
|
hianime_anime_page = response_json["html"]
|
||||||
episodes_info_container_html = get_element_html_by_class(
|
episodes_info_container_html = get_element_html_by_class(
|
||||||
"ss-list", aniwatch_anime_page
|
"ss-list", hianime_anime_page
|
||||||
)
|
)
|
||||||
episodes_info_html_list = get_elements_html_by_class(
|
episodes_info_html_list = get_elements_html_by_class(
|
||||||
"ep-item", episodes_info_container_html
|
"ep-item", episodes_info_container_html
|
||||||
@@ -127,7 +127,7 @@ class HiAnimeApi(AnimeProvider):
|
|||||||
for episode in episodes_info_dicts
|
for episode in episodes_info_dicts
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
"id": aniwatch_id,
|
"id": hianime_id,
|
||||||
"availableEpisodesDetail": {
|
"availableEpisodesDetail": {
|
||||||
"dub": episodes,
|
"dub": episodes,
|
||||||
"sub": episodes,
|
"sub": episodes,
|
||||||
@@ -138,7 +138,7 @@ class HiAnimeApi(AnimeProvider):
|
|||||||
"episodes_info": self.episodes_info,
|
"episodes_info": self.episodes_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
@debug_provider("ANIWATCH")
|
@debug_provider("HIANIME")
|
||||||
def get_episode_streams(
|
def get_episode_streams(
|
||||||
self, anime_id, anime_title, episode, translation_type, *args
|
self, anime_id, anime_title, episode, translation_type, *args
|
||||||
):
|
):
|
||||||
@@ -166,7 +166,7 @@ class HiAnimeApi(AnimeProvider):
|
|||||||
"server-item", servers_containers_html[0]
|
"server-item", servers_containers_html[0]
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("AniWatch: sub not found")
|
logger.warning("HiAnime: sub not found")
|
||||||
servers_html_sub = None
|
servers_html_sub = None
|
||||||
|
|
||||||
# dub servers
|
# dub servers
|
||||||
@@ -175,7 +175,7 @@ class HiAnimeApi(AnimeProvider):
|
|||||||
"server-item", servers_containers_html[1]
|
"server-item", servers_containers_html[1]
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("AniWatch: dub not found")
|
logger.warning("HiAnime: dub not found")
|
||||||
servers_html_dub = None
|
servers_html_dub = None
|
||||||
|
|
||||||
if translation_type == "dub":
|
if translation_type == "dub":
|
||||||
@@ -185,7 +185,7 @@ class HiAnimeApi(AnimeProvider):
|
|||||||
if not servers_html:
|
if not servers_html:
|
||||||
return
|
return
|
||||||
|
|
||||||
@debug_provider("ANIWATCH")
|
@debug_provider("HIANIME")
|
||||||
def _get_server(server_name, server_html):
|
def _get_server(server_name, server_html):
|
||||||
# keys: [ data-type: translation_type, data-id: embed_id, data-server-id: server_id ]
|
# keys: [ data-type: translation_type, data-id: embed_id, data-server-id: server_id ]
|
||||||
servers_info = extract_attributes(server_html)
|
servers_info = extract_attributes(server_html)
|
||||||
@@ -205,7 +205,7 @@ class HiAnimeApi(AnimeProvider):
|
|||||||
link_to_streams = f"https://{provider_domain}/embed-{embed_type}/ajax/e-{episode_number}/getSources?id={source_id}"
|
link_to_streams = f"https://{provider_domain}/embed-{embed_type}/ajax/e-{episode_number}/getSources?id={source_id}"
|
||||||
link_to_streams_response = self.session.get(link_to_streams)
|
link_to_streams_response = self.session.get(link_to_streams)
|
||||||
if link_to_streams_response.ok:
|
if link_to_streams_response.ok:
|
||||||
juicy_streams_json: "AniWatchStream" = (
|
juicy_streams_json: "HiAnimeStream" = (
|
||||||
link_to_streams_response.json()
|
link_to_streams_response.json()
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
from typing import Literal, TypedDict
|
from typing import Literal, TypedDict
|
||||||
|
|
||||||
|
|
||||||
class AniWatchSkipTime(TypedDict):
|
class HiAnimeSkipTime(TypedDict):
|
||||||
start: int
|
start: int
|
||||||
end: int
|
end: int
|
||||||
|
|
||||||
|
|
||||||
class AniWatchSource(TypedDict):
|
class HiAnimeSource(TypedDict):
|
||||||
file: str
|
file: str
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
|
|
||||||
class AniWatchTrack(TypedDict):
|
class HiAnimeTrack(TypedDict):
|
||||||
file: str
|
file: str
|
||||||
label: str
|
label: str
|
||||||
kind: Literal["captions", "thumbnails", "audio"]
|
kind: Literal["captions", "thumbnails", "audio"]
|
||||||
|
|
||||||
|
|
||||||
class AniWatchStream(TypedDict):
|
class HiAnimeStream(TypedDict):
|
||||||
sources: list[AniWatchSource]
|
sources: list[HiAnimeSource]
|
||||||
tracks: list[AniWatchTrack]
|
tracks: list[HiAnimeTrack]
|
||||||
encrypted: bool
|
encrypted: bool
|
||||||
intro: AniWatchSkipTime
|
intro: HiAnimeSkipTime
|
||||||
outro: AniWatchSkipTime
|
outro: HiAnimeSkipTime
|
||||||
server: int
|
server: int
|
||||||
|
|||||||
344
fastanime/libs/anime_provider/nyaa/api.py
Normal file
344
fastanime/libs/anime_provider/nyaa/api.py
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
from yt_dlp.utils import (
|
||||||
|
extract_attributes,
|
||||||
|
get_element_html_by_attribute,
|
||||||
|
get_element_html_by_class,
|
||||||
|
get_element_text_and_html_by_tag,
|
||||||
|
get_elements_html_by_class,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ...common.mini_anilist import search_for_anime_with_anilist
|
||||||
|
from ..base_provider import AnimeProvider
|
||||||
|
from ..decorators import debug_provider
|
||||||
|
from ..types import SearchResults
|
||||||
|
from .constants import NYAA_ENDPOINT
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
EXTRACT_USEFUL_INFO_PATTERN_1 = re.compile(
|
||||||
|
r"\[(\w+)\] (.+) - (\d+) [\[\(](\d+)p[\]\)].*"
|
||||||
|
)
|
||||||
|
|
||||||
|
EXTRACT_USEFUL_INFO_PATTERN_2 = re.compile(
|
||||||
|
r"\[(\w+)\] (.+)E(\d+) [\[\(]?(\d+)p.*[\]\)]?.*"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NyaaApi(AnimeProvider):
|
||||||
|
search_results: SearchResults
|
||||||
|
|
||||||
|
@debug_provider("NYAA")
|
||||||
|
def search_for_anime(self, user_query: str, *args, **_):
|
||||||
|
self.search_results = search_for_anime_with_anilist(
|
||||||
|
user_query, True
|
||||||
|
) # pyright: ignore
|
||||||
|
self.user_query = user_query
|
||||||
|
return self.search_results
|
||||||
|
|
||||||
|
@debug_provider("NYAA")
|
||||||
|
def get_anime(self, anilist_id: str, *_):
|
||||||
|
for anime in self.search_results["results"]:
|
||||||
|
if anime["id"] == anilist_id:
|
||||||
|
self.titles = [anime["title"], *anime["otherTitles"], self.user_query]
|
||||||
|
return {
|
||||||
|
"id": anime["id"],
|
||||||
|
"title": anime["title"],
|
||||||
|
"poster": anime["poster"],
|
||||||
|
"availableEpisodesDetail": {
|
||||||
|
"dub": anime["availableEpisodes"],
|
||||||
|
"sub": anime["availableEpisodes"],
|
||||||
|
"raw": anime["availableEpisodes"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@debug_provider("NYAA")
|
||||||
|
def get_episode_streams(
|
||||||
|
self,
|
||||||
|
anime_id: str,
|
||||||
|
anime_title: str,
|
||||||
|
episode_number: str,
|
||||||
|
translation_type: str,
|
||||||
|
trusted_only=bool(int(os.environ.get("FA_NYAA_TRUSTED_ONLY", "0"))),
|
||||||
|
allow_dangerous=bool(int(os.environ.get("FA_NYAA_ALLOW_DANGEROUS", "0"))),
|
||||||
|
sort_by="seeders",
|
||||||
|
*args,
|
||||||
|
):
|
||||||
|
logger.debug(f"Searching nyaa for query: '{anime_title} {episode_number}'")
|
||||||
|
servers = {}
|
||||||
|
|
||||||
|
torrents_table = ""
|
||||||
|
for title in self.titles:
|
||||||
|
try:
|
||||||
|
url_arguments: dict[str, str] = {
|
||||||
|
"c": "1_2", # Language (English)
|
||||||
|
"q": f"{title} {'0' if len(episode_number)==1 else ''}{episode_number}", # Search Query
|
||||||
|
}
|
||||||
|
# url_arguments["q"] = anime_title
|
||||||
|
|
||||||
|
# if trusted_only:
|
||||||
|
# url_arguments["f"] = "2" # Trusted uploaders only
|
||||||
|
|
||||||
|
# What to sort torrents by
|
||||||
|
if sort_by == "seeders":
|
||||||
|
url_arguments["s"] = "seeders"
|
||||||
|
elif sort_by == "date":
|
||||||
|
url_arguments["s"] = "id"
|
||||||
|
elif sort_by == "size":
|
||||||
|
url_arguments["s"] = "size"
|
||||||
|
elif sort_by == "comments":
|
||||||
|
url_arguments["s"] = "comments"
|
||||||
|
|
||||||
|
logger.debug(f"URL Arguments: {url_arguments}")
|
||||||
|
|
||||||
|
response = self.session.get(NYAA_ENDPOINT, params=url_arguments)
|
||||||
|
if not response.ok:
|
||||||
|
logger.error(f"[NYAA]: {response.text}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
torrents_table = get_element_text_and_html_by_tag(
|
||||||
|
"table", response.text
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[NYAA]: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not torrents_table:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for anime_torrent in get_elements_html_by_class(
|
||||||
|
"success", torrents_table[1]
|
||||||
|
):
|
||||||
|
td_title = get_element_html_by_attribute(
|
||||||
|
"colspan", "2", anime_torrent
|
||||||
|
)
|
||||||
|
if not td_title:
|
||||||
|
continue
|
||||||
|
title_anchor_tag = get_element_text_and_html_by_tag("a", td_title)
|
||||||
|
|
||||||
|
if not title_anchor_tag:
|
||||||
|
continue
|
||||||
|
title_anchor_tag_attrs = extract_attributes(title_anchor_tag[1])
|
||||||
|
if not title_anchor_tag_attrs:
|
||||||
|
continue
|
||||||
|
if "class" in title_anchor_tag_attrs:
|
||||||
|
td_title = td_title.replace(title_anchor_tag[1], "")
|
||||||
|
title_anchor_tag = get_element_text_and_html_by_tag(
|
||||||
|
"a", td_title
|
||||||
|
)
|
||||||
|
|
||||||
|
if not title_anchor_tag:
|
||||||
|
continue
|
||||||
|
title_anchor_tag_attrs = extract_attributes(title_anchor_tag[1])
|
||||||
|
if not title_anchor_tag_attrs:
|
||||||
|
continue
|
||||||
|
anime_title_info = title_anchor_tag_attrs["title"]
|
||||||
|
if not anime_title_info:
|
||||||
|
continue
|
||||||
|
match = EXTRACT_USEFUL_INFO_PATTERN_1.search(
|
||||||
|
anime_title_info.strip()
|
||||||
|
)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
server = match[1]
|
||||||
|
match[2]
|
||||||
|
_episode_number = match[3]
|
||||||
|
quality = match[4]
|
||||||
|
if float(episode_number) != float(_episode_number):
|
||||||
|
continue
|
||||||
|
|
||||||
|
links_td = get_element_html_by_class("text-center", anime_torrent)
|
||||||
|
if not links_td:
|
||||||
|
continue
|
||||||
|
torrent_anchor_tag = get_element_text_and_html_by_tag("a", links_td)
|
||||||
|
if not torrent_anchor_tag:
|
||||||
|
continue
|
||||||
|
torrent_anchor_tag_atrrs = extract_attributes(torrent_anchor_tag[1])
|
||||||
|
if not torrent_anchor_tag_atrrs:
|
||||||
|
continue
|
||||||
|
torrent_file_url = (
|
||||||
|
f'{NYAA_ENDPOINT}{torrent_anchor_tag_atrrs["href"]}'
|
||||||
|
)
|
||||||
|
if server in servers:
|
||||||
|
link = {
|
||||||
|
"translation_type": "sub",
|
||||||
|
"link": torrent_file_url,
|
||||||
|
"quality": quality,
|
||||||
|
}
|
||||||
|
if link not in servers[server]["links"]:
|
||||||
|
servers[server]["links"].append(link)
|
||||||
|
else:
|
||||||
|
servers[server] = {
|
||||||
|
"server": server,
|
||||||
|
"headers": {},
|
||||||
|
"episode_title": f"{anime_title}; Episode {episode_number}",
|
||||||
|
"subtitles": [],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"translation_type": "sub",
|
||||||
|
"link": torrent_file_url,
|
||||||
|
"quality": quality,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
for anime_torrent in get_elements_html_by_class(
|
||||||
|
"default", torrents_table[1]
|
||||||
|
):
|
||||||
|
td_title = get_element_html_by_attribute(
|
||||||
|
"colspan", "2", anime_torrent
|
||||||
|
)
|
||||||
|
if not td_title:
|
||||||
|
continue
|
||||||
|
title_anchor_tag = get_element_text_and_html_by_tag("a", td_title)
|
||||||
|
|
||||||
|
if not title_anchor_tag:
|
||||||
|
continue
|
||||||
|
title_anchor_tag_attrs = extract_attributes(title_anchor_tag[1])
|
||||||
|
if not title_anchor_tag_attrs:
|
||||||
|
continue
|
||||||
|
if "class" in title_anchor_tag_attrs:
|
||||||
|
td_title = td_title.replace(title_anchor_tag[1], "")
|
||||||
|
title_anchor_tag = get_element_text_and_html_by_tag(
|
||||||
|
"a", td_title
|
||||||
|
)
|
||||||
|
|
||||||
|
if not title_anchor_tag:
|
||||||
|
continue
|
||||||
|
title_anchor_tag_attrs = extract_attributes(title_anchor_tag[1])
|
||||||
|
if not title_anchor_tag_attrs:
|
||||||
|
continue
|
||||||
|
anime_title_info = title_anchor_tag_attrs["title"]
|
||||||
|
if not anime_title_info:
|
||||||
|
continue
|
||||||
|
match = EXTRACT_USEFUL_INFO_PATTERN_2.search(
|
||||||
|
anime_title_info.strip()
|
||||||
|
)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
server = match[1]
|
||||||
|
match[2]
|
||||||
|
_episode_number = match[3]
|
||||||
|
quality = match[4]
|
||||||
|
if float(episode_number) != float(_episode_number):
|
||||||
|
continue
|
||||||
|
|
||||||
|
links_td = get_element_html_by_class("text-center", anime_torrent)
|
||||||
|
if not links_td:
|
||||||
|
continue
|
||||||
|
torrent_anchor_tag = get_element_text_and_html_by_tag("a", links_td)
|
||||||
|
if not torrent_anchor_tag:
|
||||||
|
continue
|
||||||
|
torrent_anchor_tag_atrrs = extract_attributes(torrent_anchor_tag[1])
|
||||||
|
if not torrent_anchor_tag_atrrs:
|
||||||
|
continue
|
||||||
|
torrent_file_url = (
|
||||||
|
f'{NYAA_ENDPOINT}{torrent_anchor_tag_atrrs["href"]}'
|
||||||
|
)
|
||||||
|
if server in servers:
|
||||||
|
link = {
|
||||||
|
"translation_type": "sub",
|
||||||
|
"link": torrent_file_url,
|
||||||
|
"quality": quality,
|
||||||
|
}
|
||||||
|
if link not in servers[server]["links"]:
|
||||||
|
servers[server]["links"].append(link)
|
||||||
|
else:
|
||||||
|
servers[server] = {
|
||||||
|
"server": server,
|
||||||
|
"headers": {},
|
||||||
|
"episode_title": f"{anime_title}; Episode {episode_number}",
|
||||||
|
"subtitles": [],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"translation_type": "sub",
|
||||||
|
"link": torrent_file_url,
|
||||||
|
"quality": quality,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
if not allow_dangerous:
|
||||||
|
break
|
||||||
|
for anime_torrent in get_elements_html_by_class(
|
||||||
|
"danger", torrents_table[1]
|
||||||
|
):
|
||||||
|
td_title = get_element_html_by_attribute(
|
||||||
|
"colspan", "2", anime_torrent
|
||||||
|
)
|
||||||
|
if not td_title:
|
||||||
|
continue
|
||||||
|
title_anchor_tag = get_element_text_and_html_by_tag("a", td_title)
|
||||||
|
|
||||||
|
if not title_anchor_tag:
|
||||||
|
continue
|
||||||
|
title_anchor_tag_attrs = extract_attributes(title_anchor_tag[1])
|
||||||
|
if not title_anchor_tag_attrs:
|
||||||
|
continue
|
||||||
|
if "class" in title_anchor_tag_attrs:
|
||||||
|
td_title = td_title.replace(title_anchor_tag[1], "")
|
||||||
|
title_anchor_tag = get_element_text_and_html_by_tag(
|
||||||
|
"a", td_title
|
||||||
|
)
|
||||||
|
|
||||||
|
if not title_anchor_tag:
|
||||||
|
continue
|
||||||
|
title_anchor_tag_attrs = extract_attributes(title_anchor_tag[1])
|
||||||
|
if not title_anchor_tag_attrs:
|
||||||
|
continue
|
||||||
|
anime_title_info = title_anchor_tag_attrs["title"]
|
||||||
|
if not anime_title_info:
|
||||||
|
continue
|
||||||
|
match = EXTRACT_USEFUL_INFO_PATTERN_2.search(
|
||||||
|
anime_title_info.strip()
|
||||||
|
)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
server = match[1]
|
||||||
|
match[2]
|
||||||
|
_episode_number = match[3]
|
||||||
|
quality = match[4]
|
||||||
|
if float(episode_number) != float(_episode_number):
|
||||||
|
continue
|
||||||
|
|
||||||
|
links_td = get_element_html_by_class("text-center", anime_torrent)
|
||||||
|
if not links_td:
|
||||||
|
continue
|
||||||
|
torrent_anchor_tag = get_element_text_and_html_by_tag("a", links_td)
|
||||||
|
if not torrent_anchor_tag:
|
||||||
|
continue
|
||||||
|
torrent_anchor_tag_atrrs = extract_attributes(torrent_anchor_tag[1])
|
||||||
|
if not torrent_anchor_tag_atrrs:
|
||||||
|
continue
|
||||||
|
torrent_file_url = (
|
||||||
|
f'{NYAA_ENDPOINT}{torrent_anchor_tag_atrrs["href"]}'
|
||||||
|
)
|
||||||
|
if server in servers:
|
||||||
|
link = {
|
||||||
|
"translation_type": "sub",
|
||||||
|
"link": torrent_file_url,
|
||||||
|
"quality": quality,
|
||||||
|
}
|
||||||
|
if link not in servers[server]["links"]:
|
||||||
|
servers[server]["links"].append(link)
|
||||||
|
else:
|
||||||
|
servers[server] = {
|
||||||
|
"server": server,
|
||||||
|
"headers": {},
|
||||||
|
"episode_title": f"{anime_title}; Episode {episode_number}",
|
||||||
|
"subtitles": [],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"translation_type": "sub",
|
||||||
|
"link": torrent_file_url,
|
||||||
|
"quality": quality,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[NYAA]: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
for server in servers:
|
||||||
|
yield servers[server]
|
||||||
1
fastanime/libs/anime_provider/nyaa/constants.py
Normal file
1
fastanime/libs/anime_provider/nyaa/constants.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
NYAA_ENDPOINT = "https://nyaa.si"
|
||||||
126
fastanime/libs/anime_provider/nyaa/utils.py
Normal file
126
fastanime/libs/anime_provider/nyaa/utils.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import libtorrent # pyright: ignore
|
||||||
|
from rich import print
|
||||||
|
from rich.progress import (
|
||||||
|
BarColumn,
|
||||||
|
DownloadColumn,
|
||||||
|
Progress,
|
||||||
|
TextColumn,
|
||||||
|
TimeRemainingColumn,
|
||||||
|
TransferSpeedColumn,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger("nyaa")
|
||||||
|
|
||||||
|
|
||||||
|
def download_torrent(
|
||||||
|
filename: str,
|
||||||
|
result_filename: str | None = None,
|
||||||
|
show_progress: bool = True,
|
||||||
|
base_path: str = "Anime",
|
||||||
|
) -> str:
|
||||||
|
session = libtorrent.session({"listen_interfaces": "0.0.0.0:6881"})
|
||||||
|
logger.debug("Started libtorrent session")
|
||||||
|
|
||||||
|
base_path = os.path.expanduser(base_path)
|
||||||
|
logger.debug(f"Downloading output to: '{base_path}'")
|
||||||
|
|
||||||
|
info = libtorrent.torrent_info(filename)
|
||||||
|
|
||||||
|
logger.debug("Started downloading torrent")
|
||||||
|
handle: libtorrent.torrent_handle = session.add_torrent(
|
||||||
|
{"ti": info, "save_path": base_path}
|
||||||
|
)
|
||||||
|
|
||||||
|
status: libtorrent.session_status = handle.status()
|
||||||
|
|
||||||
|
progress_bar = Progress(
|
||||||
|
"[progress.description]{task.description}",
|
||||||
|
BarColumn(bar_width=None),
|
||||||
|
"[progress.percentage]{task.percentage:>3.1f}%",
|
||||||
|
"•",
|
||||||
|
DownloadColumn(),
|
||||||
|
"•",
|
||||||
|
TransferSpeedColumn(),
|
||||||
|
"•",
|
||||||
|
TimeRemainingColumn(),
|
||||||
|
"•",
|
||||||
|
TextColumn("[green]Peers: {task.fields[peers]}[/green]"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if show_progress:
|
||||||
|
with progress_bar:
|
||||||
|
download_task = progress_bar.add_task(
|
||||||
|
"downloading",
|
||||||
|
filename=status.name,
|
||||||
|
total=status.total_wanted,
|
||||||
|
peers=0,
|
||||||
|
start=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
while not status.total_done:
|
||||||
|
# Checking files
|
||||||
|
status = handle.status()
|
||||||
|
description = "[bold yellow]Checking files[/bold yellow]"
|
||||||
|
progress_bar.update(
|
||||||
|
download_task,
|
||||||
|
completed=status.total_done,
|
||||||
|
peers=status.num_peers,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Started download
|
||||||
|
progress_bar.start_task(download_task)
|
||||||
|
description = f"[bold blue]Downloading[/bold blue] [bold yellow]{result_filename}[/bold yellow]"
|
||||||
|
|
||||||
|
while not status.is_seeding:
|
||||||
|
status = handle.status()
|
||||||
|
|
||||||
|
progress_bar.update(
|
||||||
|
download_task,
|
||||||
|
completed=status.total_done,
|
||||||
|
peers=status.num_peers,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
|
||||||
|
alerts = session.pop_alerts()
|
||||||
|
|
||||||
|
alert: libtorrent.alert
|
||||||
|
for alert in alerts:
|
||||||
|
if (
|
||||||
|
alert.category()
|
||||||
|
& libtorrent.alert.category_t.error_notification
|
||||||
|
):
|
||||||
|
logger.debug(f"[Alert] {alert}")
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
progress_bar.update(
|
||||||
|
download_task,
|
||||||
|
description=f"[bold blue]Finished Downloading[/bold blue] [bold green]{result_filename}[/bold green]",
|
||||||
|
completed=status.total_wanted,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result_filename:
|
||||||
|
old_name = f"{base_path}/{status.name}"
|
||||||
|
new_name = f"{base_path}/{result_filename}"
|
||||||
|
|
||||||
|
os.rename(old_name, new_name)
|
||||||
|
|
||||||
|
logger.debug(f"Finished torrent download, renamed '{old_name}' to '{new_name}'")
|
||||||
|
|
||||||
|
return new_name
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("You need to pass in the .torrent file path.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
download_torrent(sys.argv[1])
|
||||||
@@ -19,6 +19,7 @@ class PageInfo(TypedDict):
|
|||||||
class SearchResult(TypedDict):
|
class SearchResult(TypedDict):
|
||||||
id: str
|
id: str
|
||||||
title: str
|
title: str
|
||||||
|
otherTitles: list[str]
|
||||||
availableEpisodes: list[str]
|
availableEpisodes: list[str]
|
||||||
type: str
|
type: str
|
||||||
score: int
|
score: int
|
||||||
|
|||||||
@@ -96,16 +96,16 @@ def search_for_manga_with_anilist(manga_title: str):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def search_for_anime_with_anilist(anime_title: str):
|
def search_for_anime_with_anilist(anime_title: str, prefer_eng_titles=False):
|
||||||
query = """
|
query = """
|
||||||
query ($query: String) {
|
query ($query: String) {
|
||||||
Page(perPage: 50) {
|
Page(perPage: 50) {
|
||||||
pageInfo {
|
pageInfo {
|
||||||
total
|
total
|
||||||
currentPage
|
currentPage
|
||||||
hasNextPage
|
hasNextPage
|
||||||
}
|
}
|
||||||
media(search: $query, type: ANIME,genre_not_in: ["hentai"]) {
|
media(search: $query, type: ANIME, genre_not_in: ["hentai"]) {
|
||||||
id
|
id
|
||||||
idMal
|
idMal
|
||||||
title {
|
title {
|
||||||
@@ -114,14 +114,18 @@ def search_for_anime_with_anilist(anime_title: str):
|
|||||||
}
|
}
|
||||||
episodes
|
episodes
|
||||||
status
|
status
|
||||||
|
synonyms
|
||||||
nextAiringEpisode {
|
nextAiringEpisode {
|
||||||
timeUntilAiring
|
timeUntilAiring
|
||||||
airingAt
|
airingAt
|
||||||
episode
|
episode
|
||||||
}
|
}
|
||||||
|
coverImage {
|
||||||
|
large
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
response = post(
|
response = post(
|
||||||
ANILIST_ENDPOINT,
|
ANILIST_ENDPOINT,
|
||||||
@@ -134,11 +138,37 @@ def search_for_anime_with_anilist(anime_title: str):
|
|||||||
"pageInfo": anilist_data["data"]["Page"]["pageInfo"],
|
"pageInfo": anilist_data["data"]["Page"]["pageInfo"],
|
||||||
"results": [
|
"results": [
|
||||||
{
|
{
|
||||||
"id": anime_result["id"],
|
"id": str(anime_result["id"]),
|
||||||
"title": anime_result["title"]["romaji"]
|
"title": (
|
||||||
or anime_result["title"]["english"],
|
(
|
||||||
|
anime_result["title"]["english"]
|
||||||
|
or anime_result["title"]["romaji"]
|
||||||
|
)
|
||||||
|
if prefer_eng_titles
|
||||||
|
else (
|
||||||
|
anime_result["title"]["romaji"]
|
||||||
|
or anime_result["title"]["english"]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"otherTitles": [
|
||||||
|
(
|
||||||
|
(
|
||||||
|
anime_result["title"]["romaji"]
|
||||||
|
or anime_result["title"]["english"]
|
||||||
|
)
|
||||||
|
if prefer_eng_titles
|
||||||
|
else (
|
||||||
|
anime_result["title"]["english"]
|
||||||
|
or anime_result["title"]["romaji"]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
*(anime_result["synonyms"] or []),
|
||||||
|
],
|
||||||
"type": "anime",
|
"type": "anime",
|
||||||
|
"poster": anime_result["coverImage"]["large"],
|
||||||
"availableEpisodes": list(
|
"availableEpisodes": list(
|
||||||
|
map(
|
||||||
|
str,
|
||||||
range(
|
range(
|
||||||
1,
|
1,
|
||||||
(
|
(
|
||||||
@@ -151,6 +181,7 @@ def search_for_anime_with_anilist(anime_title: str):
|
|||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
31
poetry.lock
generated
31
poetry.lock
generated
@@ -523,7 +523,7 @@ files = [
|
|||||||
name = "inquirerpy"
|
name = "inquirerpy"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
description = "Python port of Inquirer.js (A collection of common interactive command-line user interfaces)"
|
description = "Python port of Inquirer.js (A collection of common interactive command-line user interfaces)"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=3.7,<4.0"
|
python-versions = ">=3.7,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "InquirerPy-0.3.4-py3-none-any.whl", hash = "sha256:c65fdfbac1fa00e3ee4fb10679f4d3ed7a012abf4833910e63c295827fe2a7d4"},
|
{file = "InquirerPy-0.3.4-py3-none-any.whl", hash = "sha256:c65fdfbac1fa00e3ee4fb10679f4d3ed7a012abf4833910e63c295827fe2a7d4"},
|
||||||
@@ -551,11 +551,21 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
colors = ["colorama (>=0.4.6)"]
|
colors = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lbry-libtorrent"
|
||||||
|
version = "1.2.4"
|
||||||
|
description = "Python bindings for libtorrent-rasterbar (lbry fork for packaging)"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "lbry_libtorrent-1.2.4-py3-none-any.whl", hash = "sha256:dedfdbc83c0243676e4e7e709cb47d9da142fa39e2fae18513c9b310baf222b4"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown-it-py"
|
name = "markdown-it-py"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
description = "Python port of markdown-it. Markdown parsing, done right!"
|
description = "Python port of markdown-it. Markdown parsing, done right!"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
|
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
|
||||||
@@ -579,7 +589,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
|||||||
name = "mdurl"
|
name = "mdurl"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
description = "Markdown URL utilities"
|
description = "Markdown URL utilities"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||||
@@ -660,7 +670,7 @@ files = [
|
|||||||
name = "pfzy"
|
name = "pfzy"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
description = "Python port of the fzy fuzzy string matching algorithm"
|
description = "Python port of the fzy fuzzy string matching algorithm"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=3.7,<4.0"
|
python-versions = ">=3.7,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "pfzy-0.3.4-py3-none-any.whl", hash = "sha256:5f50d5b2b3207fa72e7ec0ef08372ef652685470974a107d0d4999fc5a903a96"},
|
{file = "pfzy-0.3.4-py3-none-any.whl", hash = "sha256:5f50d5b2b3207fa72e7ec0ef08372ef652685470974a107d0d4999fc5a903a96"},
|
||||||
@@ -740,7 +750,7 @@ virtualenv = ">=20.10.0"
|
|||||||
name = "prompt-toolkit"
|
name = "prompt-toolkit"
|
||||||
version = "3.0.47"
|
version = "3.0.47"
|
||||||
description = "Library for building powerful interactive command lines in Python"
|
description = "Library for building powerful interactive command lines in Python"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=3.7.0"
|
python-versions = ">=3.7.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"},
|
{file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"},
|
||||||
@@ -817,7 +827,7 @@ files = [
|
|||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.18.0"
|
version = "2.18.0"
|
||||||
description = "Pygments is a syntax highlighting package written in Python."
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
|
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
|
||||||
@@ -1093,7 +1103,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
|||||||
name = "rich"
|
name = "rich"
|
||||||
version = "13.8.1"
|
version = "13.8.1"
|
||||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=3.7.0"
|
python-versions = ">=3.7.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"},
|
{file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"},
|
||||||
@@ -1237,7 +1247,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
|
|||||||
name = "wcwidth"
|
name = "wcwidth"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
description = "Measures the displayed width of unicode strings in a terminal"
|
description = "Measures the displayed width of unicode strings in a terminal"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
|
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
|
||||||
@@ -1371,12 +1381,11 @@ static-analysis = ["autopep8 (>=2.0,<3.0)", "ruff (>=0.5.0,<0.6.0)"]
|
|||||||
test = ["pytest (>=8.1,<9.0)"]
|
test = ["pytest (>=8.1,<9.0)"]
|
||||||
|
|
||||||
[extras]
|
[extras]
|
||||||
cli = ["click", "inquirerpy", "rich"]
|
full = ["mpv", "plyer"]
|
||||||
full = ["click", "inquirerpy", "mpv", "plyer", "rich"]
|
|
||||||
mpv = ["mpv"]
|
mpv = ["mpv"]
|
||||||
notifications = ["plyer"]
|
notifications = ["plyer"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "aa6445db170dfcb5ed647d78bf5969696025eb935107c1a5ff2812666216f67c"
|
content-hash = "94eb46eebad30c13907af11f4c87b6d6670b733bcc5af502294b20e5de3e1d20"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "fastanime"
|
name = "fastanime"
|
||||||
version = "2.5.5.dev1"
|
version = "2.5.6.dev1"
|
||||||
description = "A browser anime site experience from the terminal"
|
description = "A browser anime site experience from the terminal"
|
||||||
authors = ["Benextempest <benextempest@gmail.com>"]
|
authors = ["Benextempest <benextempest@gmail.com>"]
|
||||||
license = "UNLICENSE"
|
license = "UNLICENSE"
|
||||||
@@ -17,7 +17,7 @@ inquirerpy = { version = "^0.3.4", optional = false }
|
|||||||
mpv = { version = "^1.0.7", optional = true }
|
mpv = { version = "^1.0.7", optional = true }
|
||||||
plyer = { version = "^2.1.0", optional = true }
|
plyer = { version = "^2.1.0", optional = true }
|
||||||
|
|
||||||
|
lbry-libtorrent = "^1.2.4"
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
full = ["plyer", "mpv"]
|
full = ["plyer", "mpv"]
|
||||||
# cli = ["rich", "click", "inquirerpy"]
|
# cli = ["rich", "click", "inquirerpy"]
|
||||||
|
|||||||
Reference in New Issue
Block a user