# ani-cli Research > Source: > Version at time of research: 4.11.0 > Language: POSIX Shell (~500 lines) > License: GPL-3.0 ## Overview ani-cli is a CLI tool to browse and watch anime. It scrapes [allmanga.to](https://allmanga.to/) via a GraphQL API, decodes obfuscated video source URLs, and launches a media player (mpv, vlc, iina, etc.). The **entire codebase is a single file**: `ani-cli` (a POSIX shell script). ## Architecture / Code Sections The script is organized into these logical blocks: ### 1. UI Functions | Function | Purpose | |---|---| | `external_menu()` | Wraps rofi/dmenu for external menu support | | `launcher()` | Dispatches to fzf (default), rofi, or dmenu | | `nth()` | Core selection function -- pipes items through launcher, supports multi-select | ### 2. Scraping / API All API calls go to `https://api.allanime.day/api` as **GraphQL POST requests** with `Content-Type: application/json`. The referer is set to `https://allmanga.to`. #### GraphQL Queries **Search anime:** ```graphql 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 } } } ``` Variables: ```json { "search": { "allowAdult": false, "allowUnknown": false, "query": "" }, "limit": 40, "page": 1, "translationType": "sub", "countryOrigin": "ALL" } ``` **Get episodes list:** ```graphql query ($showId: String!) { show(_id: $showId) { _id availableEpisodesDetail } } ``` **Get episode embed URLs:** ```graphql query ( $showId: String! $translationType: VaildTranslationTypeEnumType! $episodeString: String! ) { episode( showId: $showId translationType: $translationType episodeString: $episodeString ) { episodeString sourceUrls } } ``` ### 3. Provider URL Decoding (Hex Cipher) The API returns obfuscated source URLs. The `provider_init()` function decodes them using a **hex-pair substitution cipher**. Each two-character hex code maps to an ASCII character: ``` 79 -> A, 7a -> B, 7b -> C, ... (uppercase letters) 59 -> a, 5a -> b, 5b -> c, ... (lowercase letters) 08 -> 0, 09 -> 1, 0a -> 2, ... (digits) 15 -> -, 16 -> ., 17 -> /, 02 -> :, etc. (symbols) ``` Full mapping (hex -> char): | Hex | Char | Hex | Char | Hex | Char | Hex | Char | |-----|------|-----|------|-----|------|-----|------| | 79 | A | 59 | a | 08 | 0 | 15 | - | | 7a | B | 5a | b | 09 | 1 | 16 | . | | 7b | C | 5b | c | 0a | 2 | 67 | _ | | 7c | D | 5c | d | 0b | 3 | 46 | ~ | | 7d | E | 5d | e | 0c | 4 | 02 | : | | 7e | F | 5e | f | 0d | 5 | 17 | / | | 7f | G | 5f | g | 0e | 6 | 07 | ? | | 70 | H | 50 | h | 0f | 7 | 1b | # | | 71 | I | 51 | i | 00 | 8 | 63 | [ | | 72 | J | 52 | j | 01 | 9 | 65 | ] | | 73 | K | 53 | k | | | 78 | @ | | 74 | L | 54 | l | | | 19 | ! | | 75 | M | 55 | m | | | 1c | $ | | 76 | N | 56 | n | | | 1e | & | | 77 | O | 57 | o | | | 10 | ( | | 68 | P | 48 | p | | | 11 | ) | | 69 | Q | 49 | q | | | 12 | * | | 6a | R | 4a | r | | | 13 | + | | 6b | S | 4b | s | | | 14 | , | | 6c | T | 4c | t | | | 03 | ; | | 6d | U | 4d | u | | | 05 | = | | 6e | V | 4e | v | | | 1d | % | | 6f | W | 4f | w | | | | | | 60 | X | 40 | x | | | | | | 61 | Y | 41 | y | | | | | | 62 | Z | 42 | z | | | | | After decoding, `/clock` is replaced with `/clock.json`. ### 4. Video Providers Four providers are tried in parallel: | # | Name | Regex Match | Format | |---|---|---|---| | 1 | wixmp (Default) | `/Default :/p` | m3u8 -> mp4 (multi-quality) | | 2 | youtube | `/Yt-mp4 :/p` | mp4 (single) | | 3 | sharepoint | `/S-mp4 :/p` | mp4 (single) | | 4 | hianime | `/Luf-Mp4 :/p` | m3u8 (multi-quality) | ### 5. Link Extraction (`get_links`) After fetching a provider URL, the response is parsed for video links. Three cases: 1. **wixmp (repackager.wixmp.com)** -- Extracts mp4 URLs with multiple quality options from a comma-separated URL pattern 2. **m3u8 (master.m3u8)** -- Parses the m3u8 playlist for stream variants at different resolutions. Also extracts subtitle URLs and referrer headers 3. **Direct links** -- Used as-is 4. **fast4speed.rsvp** -- YouTube-like links, passed to yt-dlp ### 6. Quality Selection (`select_quality`) - `best` -- first (highest resolution) link - `worst` -- last numeric resolution link - Specific (e.g., `1080`) -- grep for matching resolution - Falls back to `best` if specified quality not found - Sets `--referrer` and `--sub-file` flags for m3u8 streams ### 7. Playback (`play_episode`) Launches the selected player with the video URL. Supported players: | Player | Platform | How | |---|---|---| | mpv | Linux/Windows | `mpv` / `mpv.exe` (detached or attached) | | flatpak mpv | Linux | `flatpak run io.mpv.Mpv` | | vlc | Linux/Windows | `vlc` / `vlc.exe` | | iina | macOS | IINA app CLI | | android_mpv | Android | `am start` intent | | android_vlc | Android | `am start` intent | | syncplay | All | Syncplay wrapper | | download | All | `aria2c` (mp4) or `yt-dlp`/`ffmpeg` (m3u8) | | catt | All | Chromecast via catt | | iSH | iOS | VLC URL scheme | ### 8. History - Stored in `~/.local/state/ani-cli/ani-hsts` (or `$ANI_CLI_HIST_DIR`) - Format: `\t\t` - Updated after each episode play - Used by `--continue` to resume watching ### 9. Main Flow ``` 1. Parse CLI arguments 2. Check dependencies 3. Search: a. If --continue: read history, show unwatched, pick one b. Otherwise: prompt for search query, call search_anime(), pick result 4. Get episode list for selected anime 5. Pick episode (or use --episode flag) 6. play(): a. get_episode_url() -> fetch embed URLs -> decode providers -> fetch links -> select quality b. play_episode() -> launch player 7. Loop: next / replay / previous / select / change_quality / quit ``` ## Dependencies - `curl` -- HTTP requests - `sed`, `grep` -- text processing / parsing - `fzf` -- interactive selection (or rofi/dmenu) - `mpv` / `vlc` / `iina` -- video playback - `aria2c` -- download manager (for mp4 downloads) - `yt-dlp` -- m3u8 downloads - `ffmpeg` -- m3u8 downloads (fallback) - `ani-skip` -- optional, skip anime intros (uses aniskip API + mpv lua scripts) - `patch` -- self-updating ## Key Constants ``` User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0 Referer: https://allmanga.to API Base: https://api.allanime.day Link Base: allanime.day ```