diff --git a/fastanime/cli/commands/search.py b/fastanime/cli/commands/search.py index 889df34..52abb42 100644 --- a/fastanime/cli/commands/search.py +++ b/fastanime/cli/commands/search.py @@ -176,6 +176,7 @@ def search(config: Config, anime_titles: str, episode_range: str): stream_anime() return link = stream_link["link"] + stream_headers = server["headers"] episode_title = server["episode_title"] else: with Progress() as progress: @@ -204,16 +205,17 @@ def search(config: Config, anime_titles: str, episode_range: str): stream_anime() return link = stream_link["link"] + stream_headers = servers[server]["headers"] episode_title = servers[server]["episode_title"] print(f"[purple]Now Playing:[/] {search_result} Episode {episode}") if config.sync_play: from ..utils.syncplay import SyncPlayer - SyncPlayer(link, episode_title) + SyncPlayer(link, episode_title, headers=stream_headers) else: - run_mpv(link, episode_title) - except Exception as e: + run_mpv(link, episode_title, headers=stream_headers) + except IndexError as e: print(e) input("Enter to continue") stream_anime() diff --git a/fastanime/cli/interfaces/anilist_interfaces.py b/fastanime/cli/interfaces/anilist_interfaces.py index c7edcae..0ad79a3 100644 --- a/fastanime/cli/interfaces/anilist_interfaces.py +++ b/fastanime/cli/interfaces/anilist_interfaces.py @@ -117,7 +117,9 @@ def media_player_controls( from ..utils.syncplay import SyncPlayer stop_time, total_time = SyncPlayer( - current_episode_stream_link, selected_server["episode_title"] + current_episode_stream_link, + selected_server["episode_title"], + headers=selected_server["headers"], ) elif config.use_mpv_mod: from ..utils.player import player @@ -128,6 +130,7 @@ def media_player_controls( fastanime_runtime_state, config, selected_server["episode_title"], + headers=selected_server["headers"], ) # TODO: implement custom aniskip @@ -148,6 +151,7 @@ def media_player_controls( selected_server["episode_title"], start_time=start_time, custom_args=custom_args, + headers=selected_server["headers"], ) # either update the watch history to the next episode or current depending on progress @@ -509,7 +513,9 @@ def provider_anime_episode_servers_menu( from ..utils.syncplay import SyncPlayer stop_time, total_time = SyncPlayer( - current_stream_link, selected_server["episode_title"] + current_stream_link, + selected_server["episode_title"], + headers=selected_server["headers"], ) elif config.use_mpv_mod: from ..utils.player import player @@ -520,6 +526,7 @@ def provider_anime_episode_servers_menu( fastanime_runtime_state, config, selected_server["episode_title"], + headers=selected_server["headers"], ) # TODO: implement custom aniskip intergration @@ -543,6 +550,7 @@ def provider_anime_episode_servers_menu( selected_server["episode_title"], start_time=start_time, custom_args=custom_args, + headers=selected_server["headers"], ) print("Finished at: ", stop_time) diff --git a/fastanime/cli/utils/mpv.py b/fastanime/cli/utils/mpv.py index 2eafe41..11f8053 100644 --- a/fastanime/cli/utils/mpv.py +++ b/fastanime/cli/utils/mpv.py @@ -2,6 +2,8 @@ import re import shutil import subprocess +from fastanime.constants import S_PLATFORM + def stream_video(MPV, url, mpv_args, custom_args): process = subprocess.Popen( @@ -52,6 +54,7 @@ def run_mpv( start_time: str = "0", ytdl_format="", custom_args=[], + headers={}, ): # Determine if mpv is available MPV = shutil.which("mpv") @@ -61,7 +64,7 @@ def run_mpv( # Regex to check if the link is a YouTube URL youtube_regex = r"(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/.+" - if not MPV: + if not MPV and not S_PLATFORM == "win32": # Determine if the link is a YouTube URL if re.match(youtube_regex, link): # Android specific commands to launch mpv with a YouTube URL @@ -100,6 +103,11 @@ def run_mpv( else: # General mpv command with custom arguments mpv_args = [] + if headers: + mpv_headers = "--http-header-fields=" + for header_name, header_value in headers.items(): + mpv_headers += f"{header_name}:{header_value}," + mpv_args.append(mpv_headers) if start_time != "0": mpv_args.append(f"--start={start_time}") if title: diff --git a/fastanime/cli/utils/player.py b/fastanime/cli/utils/player.py index 93d8e94..f9622fb 100644 --- a/fastanime/cli/utils/player.py +++ b/fastanime/cli/utils/player.py @@ -151,6 +151,7 @@ class MpvPlayer(object): fastanime_runtime_state, config: "Config", title, + headers={}, ): self.anime_provider = anime_provider self.fastanime_runtime_state = fastanime_runtime_state @@ -174,6 +175,11 @@ class MpvPlayer(object): # mpv_player.cache = "yes" # mpv_player.cache_pause = "no" mpv_player.title = title + mpv_headers = "" + if headers: + for header_name, header_value in headers.items(): + mpv_headers += f"{header_name}:{header_value}," + mpv_player.http_header_fields = mpv_headers mpv_player.play(stream_link) diff --git a/fastanime/cli/utils/syncplay.py b/fastanime/cli/utils/syncplay.py index 956c722..cd51cc6 100644 --- a/fastanime/cli/utils/syncplay.py +++ b/fastanime/cli/utils/syncplay.py @@ -4,7 +4,7 @@ import subprocess from .tools import exit_app -def SyncPlayer(url: str, anime_title=None, *args): +def SyncPlayer(url: str, anime_title=None, headers={}, *args): # TODO: handle m3u8 multi quality streams # # check for SyncPlay @@ -14,6 +14,12 @@ def SyncPlayer(url: str, anime_title=None, *args): exit_app(1) return "0", "0" # start SyncPlayer + mpv_args = [] + if headers: + mpv_headers = "--http-header-fields=" + for header_name, header_value in headers.items(): + mpv_headers += f"{header_name}:{header_value}," + mpv_args.append(mpv_headers) if not anime_title: subprocess.run( [ @@ -23,7 +29,13 @@ def SyncPlayer(url: str, anime_title=None, *args): ) else: subprocess.run( - [SYNCPLAY_EXECUTABLE, url, "--", f"--force-media-title={anime_title}"] + [ + SYNCPLAY_EXECUTABLE, + url, + "--", + f"--force-media-title={anime_title}", + *mpv_args, + ] ) # for compatability diff --git a/fastanime/libs/anime_provider/__init__.py b/fastanime/libs/anime_provider/__init__.py index f124f6f..9e8f1e8 100644 --- a/fastanime/libs/anime_provider/__init__.py +++ b/fastanime/libs/anime_provider/__init__.py @@ -9,4 +9,5 @@ SERVERS_AVAILABLE = [ "weTransfer", "wixmp", "kwik", + "Yt", ] diff --git a/fastanime/libs/anime_provider/allanime/api.py b/fastanime/libs/anime_provider/allanime/api.py index 4fae6e9..b71b631 100644 --- a/fastanime/libs/anime_provider/allanime/api.py +++ b/fastanime/libs/anime_provider/allanime/api.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING from requests.exceptions import Timeout from ...anime_provider.base_provider import AnimeProvider -from ..utils import decode_hex_string, give_random_quality +from ..utils import give_random_quality, one_digit_symmetric_xor from .constants import ( ALLANIME_API_ENDPOINT, ALLANIME_BASE, @@ -205,23 +205,45 @@ class AllAnimeAPI(AnimeProvider): # filter the working streams no need to get all since the others are mostly hsl # TODO: should i just get all the servers and handle the hsl?? if embed.get("sourceName", "") not in ( - "Sak", - "Kir", - "S-mp4", - "Luf-mp4", - "Default", + # priorities based on death note + "Sak", # 7 + "S-mp4", # 7.9 + "Luf-mp4", # 7.7 + "Default", # 8.5 + "Yt-mp4", # 7.9 + "Kir", # NA + # "Vid-mp4" # 4 + # "Ok", # 3.5 + # "Ss-Hls", # 5.5 + # "Mp4", # 4 ): continue url = embed.get("sourceUrl") - + # if not url: continue if url.startswith("--"): url = url[2:] + url = one_digit_symmetric_xor(56, url) + + if "tools.fast4speed.rsvp" in url: + yield { + "server": "Yt", + "episode_title": f'{anime["title"]}; Episode {episode_number}', + "headers": {"Referer": f"https://{ALLANIME_BASE}/"}, + "links": [ + { + "link": url, + "quality": "1080", + } + ], + } # pyright:ignore + continue # get the stream url for an episode of the defined source names - parsed_url = decode_hex_string(url) - embed_url = f"https://{ALLANIME_BASE}{parsed_url.replace('clock', 'clock.json')}" + embed_url = ( + f"https://{ALLANIME_BASE}{url.replace('clock', 'clock.json')}" + ) resp = self.session.get( embed_url, headers={ @@ -230,12 +252,14 @@ class AllAnimeAPI(AnimeProvider): }, timeout=10, ) + if resp.status_code == 200: match embed["sourceName"]: case "Luf-mp4": logger.debug("allanime:Found streams from gogoanime") yield { "server": "gogoanime", + "headers": {}, "episode_title": ( allanime_episode["notes"] or f'{anime["title"]}' ) @@ -246,6 +270,7 @@ class AllAnimeAPI(AnimeProvider): logger.debug("allanime:Found streams from wetransfer") yield { "server": "wetransfer", + "headers": {}, "episode_title": ( allanime_episode["notes"] or f'{anime["title"]}' ) @@ -256,6 +281,7 @@ class AllAnimeAPI(AnimeProvider): logger.debug("allanime:Found streams from sharepoint") yield { "server": "sharepoint", + "headers": {}, "episode_title": ( allanime_episode["notes"] or f'{anime["title"]}' ) @@ -266,6 +292,7 @@ class AllAnimeAPI(AnimeProvider): logger.debug("allanime:Found streams from dropbox") yield { "server": "dropbox", + "headers": {}, "episode_title": ( allanime_episode["notes"] or f'{anime["title"]}' ) @@ -276,20 +303,22 @@ class AllAnimeAPI(AnimeProvider): logger.debug("allanime:Found streams from wixmp") yield { "server": "wixmp", + "headers": {}, "episode_title": ( allanime_episode["notes"] or f'{anime["title"]}' ) + f"; Episode {episode_number}", "links": give_random_quality(resp.json()["links"]), } # pyright:ignore + except Timeout: logger.error( "Timeout has been exceeded this could mean allanime is down or you have lost internet connection" ) - return [] + except Exception as e: logger.error(f"FA(Allanime): {e}") - return [] + except Exception as e: logger.error(f"FA(Allanime): {e}") return [] @@ -301,7 +330,7 @@ if __name__ == "__main__": import subprocess import sys - from InquirerPy import inquirer, validator + from InquirerPy import inquirer, validator # pyright:ignore anime = input("Enter the anime name: ") translation = input("Enter the translation type: ") diff --git a/fastanime/libs/anime_provider/animepahe/api.py b/fastanime/libs/anime_provider/animepahe/api.py index 5ee154b..ec4a91b 100644 --- a/fastanime/libs/anime_provider/animepahe/api.py +++ b/fastanime/libs/anime_provider/animepahe/api.py @@ -183,7 +183,12 @@ class AnimePaheApi(AnimeProvider): episode["title"] or f"{anime['title']}; Episode {episode['episode']}" ) # get all links - streams = {"server": "kwik", "links": [], "episode_title": episode_title} + streams = { + "server": "kwik", + "links": [], + "episode_title": episode_title, + "headers": {}, + } for res_dict in res_dicts: # get embed url embed_url = res_dict["data-src"] diff --git a/fastanime/libs/anime_provider/types.py b/fastanime/libs/anime_provider/types.py index 8de39a6..ed613df 100644 --- a/fastanime/libs/anime_provider/types.py +++ b/fastanime/libs/anime_provider/types.py @@ -60,12 +60,12 @@ class EpisodeStream(TypedDict): hls: bool | None mp4: bool | None priority: int | None - headers: dict | None quality: Literal["360", "720", "1080", "unknown"] translation_type: Literal["dub", "sub"] class Server(TypedDict): + headers: dict server: str episode_title: str links: list[EpisodeStream] diff --git a/fastanime/libs/anime_provider/utils.py b/fastanime/libs/anime_provider/utils.py index 5e6f9bb..001764d 100644 --- a/fastanime/libs/anime_provider/utils.py +++ b/fastanime/libs/anime_provider/utils.py @@ -44,6 +44,14 @@ def give_random_quality(links: list[dict]): ] +def one_digit_symmetric_xor(password: int, target: str): + def genexp(): + for segment in bytearray.fromhex(target): + yield segment ^ password + + return bytes(genexp()).decode("utf-8") + + def decode_hex_string(hex_string): """some of the sources encrypt the urls into hex codes this function decrypts the urls