Files
anishio/ani-cli.md
T
2026-04-16 09:46:22 +02:00

6.5 KiB

ani-cli Research

Source: https://github.com/pystardust/ani-cli 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 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:

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:

{
  "search": { "allowAdult": false, "allowUnknown": false, "query": "<search term>" },
  "limit": 40,
  "page": 1,
  "translationType": "sub",
  "countryOrigin": "ALL"
}

Get episodes list:

query ($showId: String!) {
  show(_id: $showId) {
    _id
    availableEpisodesDetail
  }
}

Get episode embed URLs:

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)

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: <episode_no>\t<show_id>\t<title>
  • 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