From 5abd25f633fed4d2534dfaf47d5af2958a06c114 Mon Sep 17 00:00:00 2001 From: Divyam Date: Thu, 16 Apr 2026 17:23:09 +0530 Subject: [PATCH] fix: update AllAnime decryption logic and search parsing - Implemented AES-256-GCM decryption for the new 'tobeparsed' field. - Switched search result parsing to python3 for better reliability. - Updated README.md with new dependencies (python3, pycryptodome). - Bumped version to 4.12.0. --- README.md | 5 ++++- ani-cli | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7fe0562c4..57f03436a 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,8 @@ cd .. && rm -rf ./ani-cli *To install (with Homebrew) the dependencies required on Mac OS, you can run:* ```sh -brew install curl grep aria2 ffmpeg git fzf yt-dlp && \ +brew install curl grep aria2 ffmpeg git fzf yt-dlp python3 && \ +pip3 install pycryptodome && \ brew install --cask iina ``` *Why iina and not mpv? Drop-in replacement for mpv for MacOS. Integrates well with OSX UI. Excellent support for M1. Open Source.* @@ -509,6 +510,8 @@ apk del grep sed curl fzf git aria2 ffmpeg ncurses - fzf - User interface - ani-skip (optional) - patch - Self updating +- python3 - Decryption +- pycryptodome (python library) - Decryption ### Ani-Skip diff --git a/ani-cli b/ani-cli index 43984cc6f..1fe1f8b2e 100755 --- a/ani-cli +++ b/ani-cli @@ -1,6 +1,6 @@ #!/bin/sh -version_number="4.11.0" +version_number="4.12.0" # UI @@ -214,7 +214,45 @@ get_episode_url() { #shellcheck disable=SC2016 episode_embed_gql='query ($showId: String!, $translationType: VaildTranslationTypeEnumType!, $episodeString: String!) { episode( showId: $showId translationType: $translationType episodeString: $episodeString ) { episodeString sourceUrls }}' - resp=$(curl -e "$allanime_refr" -s -H "Content-Type: application/json" -X POST "${allanime_api}/api" --data "{\"variables\":{\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"},\"query\":\"$episode_embed_gql\"}" -A "$agent" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":"--([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p') + payload="{\"variables\":{\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"},\"query\":\"$episode_embed_gql\"}" + resp_raw=$(curl -e "$allanime_refr" -s -H "Content-Type: application/json" -X POST "${allanime_api}/api" --data "$payload" -A "$agent") + + # Handle tobeparsed if present + if printf "%s" "$resp_raw" | grep -q "\"tobeparsed\""; then + tbp=$(printf "%s" "$resp_raw" | sed -nE 's/.*"tobeparsed":"([^"]*)".*/\1/p') + # AES-GCM decryption logic + # Key: sha256 of "SimtVaugFbGR2K7P" + # IV: first 12 bytes + # Tag: last 16 bytes + # Ciphertext: middle bytes + + # We use python3 for decryption as it's more reliable for AES-GCM than raw openssl in shell + resp=$(python3 -c " +import base64, hashlib, json, sys +from Crypto.Cipher import AES +tbp = sys.stdin.read().strip() +raw = base64.b64decode(tbp) +key = hashlib.sha256(\"P7K2RGbFgauVtmiS\"[::-1].encode()).digest() +iv, ciphertext, tag = raw[:12], raw[12:-16], raw[-16:] +cipher = AES.new(key, AES.MODE_GCM, nonce=iv) +try: + decrypted = cipher.decrypt_and_verify(ciphertext, tag).decode('utf-8') + data = json.loads(decrypted) + for source in data.get('episode', {}).get('sourceUrls', []): + url = source['sourceUrl'] + if url.startswith('--'): + url = url[2:] + print(f\"{source['sourceName']} :{url}\") +except Exception: + pass +" << EOF +$tbp +EOF +) + else + resp=$(printf "%s" "$resp_raw" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":"--([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p') + fi + # generate links into sequential files cache_dir="$(mktemp -d)" providers="1 2 3 4" @@ -238,8 +276,18 @@ search_anime() { #shellcheck disable=SC2016 search_gql='query( $search: SearchInput $limit: Int $page: Int $translationType: VaildTranslationTypeEnumType $countryOrigin: VaildCountryOriginEnumType ) { shows( search: $search limit: $limit page: $page translationType: $translationType countryOrigin: $countryOrigin ) { edges { _id name availableEpisodes __typename } }}' - curl -e "$allanime_refr" -s -H "Content-Type: application/json" -X POST "${allanime_api}/api" --data "{\"variables\":{\"search\":{\"allowAdult\":false,\"allowUnknown\":false,\"query\":\"$1\"},\"limit\":40,\"page\":1,\"translationType\":\"$mode\",\"countryOrigin\":\"ALL\"},\"query\":\"$search_gql\"}" -A "$agent" | sed 's|Show|\ -| g' | sed -nE "s|.*_id\":\"([^\"]*)\",\"name\":\"(.+)\",.*${mode}\":([1-9][^,]*).*|\1 \2 (\3 episodes)|p" | sed 's/\\"//g' + search_tmp=$(mktemp) + curl -e "$allanime_refr" -s -H "Content-Type: application/json" -X POST "${allanime_api}/api" --data "{\"variables\":{\"search\":{\"allowAdult\":false,\"allowUnknown\":false,\"query\":\"$1\"},\"limit\":40,\"page\":1,\"translationType\":\"$mode\",\"countryOrigin\":\"ALL\"},\"query\":\"$search_gql\"}" -A "$agent" -o "$search_tmp" + + python3 -c " +import json, sys +with open('$search_tmp', 'r') as f: + data = json.load(f) +mode = '$mode' +for edge in data.get('data', {}).get('shows', {}).get('edges', []): + print(f\"{edge['_id']}\t{edge['name']} ({edge['availableEpisodes'].get(mode, 0)} episodes)\") +" + rm "$search_tmp" } time_until_next_ep() {