diff --git a/dotfiles/.config/rofi/config.rasi b/dotfiles/.config/rofi/config.rasi
new file mode 100644
index 0000000..1f84900
--- /dev/null
+++ b/dotfiles/.config/rofi/config.rasi
@@ -0,0 +1,205 @@
+@import "~/.themes/Serpensortia/rofi/theme.rasi"
+
+configuration {
+ modi: "drun,run,window";
+ show-icons: true;
+ display-drun: "Applications";
+ display-run: "Run Command";
+ display-window: "Switch Window";
+}
+window {
+ font: "Fira Sans 11";
+ border-color: @border;
+ background-color: @background-alt;
+ border: @main-border;
+ padding: @main-padding;
+ border-radius: @main-radius;
+ spacing: @main-spacing;
+
+ anchor: north;
+ location: center;
+ y-offset: -15.5em;
+
+
+ children: [ inputbar, message, listview ];
+}
+
+wrapper-mode-switcher {
+ orientation: horizontal;
+
+ expand: false;
+ spacing: 0;
+ children: [ mode-switcher ];
+}
+
+mode-switcher {
+ border: 0px;
+ spacing: 0px;
+ expand: true;
+}
+
+button {
+ padding: @element-spacing;
+ border: @button-border;
+ border-color: @separator;
+}
+button selected.normal {
+ text-color: @on-selected;
+ background-color: @list-bg;
+
+ border: @button-selected-border;
+ border-color: @separator;
+ border-radius: @button-selected-radius;
+}
+
+sidebar {
+ expand: false;
+}
+
+message {
+ text-color: @foreground-alt;
+ background-color: @message-bg;
+ border-color: @border-alt;
+ border: @main-border;
+ border-radius: @element-radius;
+ padding: @element-padding;
+ margin: @message-margin;
+ expand: false;
+}
+
+listview {
+ spacing: @element-spacing;
+ scrollbar: false;
+ padding: @main-padding;
+ background-color: @list-bg;
+
+ expand: true;
+ border: @list-border;
+ border-color: @separator;
+ border-radius: @list-radius;
+}
+element {
+ border: @element-border;
+ border-color: transparent;
+ padding: @element-padding;
+}
+element-text {
+ background-color: inherit;
+ text-color: inherit;
+}
+element.normal.normal {
+ background-color: @normal-background;
+ text-color: @normal-foreground;
+}
+element.normal.urgent {
+ background-color: @urgent-background;
+ text-color: @urgent-foreground;
+}
+element.normal.active {
+ background-color: @active-background;
+ text-color: @active-foreground;
+}
+element.selected.normal {
+ border: @element-border;
+ border-color: @border-alt;
+ background-color: @selected-normal-background;
+ text-color: @selected-normal-foreground;
+}
+element.selected.urgent {
+ border: @element-border;
+ border-color: @border-alt;
+ background-color: @selected-urgent-background;
+ text-color: @selected-urgent-foreground;
+}
+element.selected.active {
+ border: @element-border;
+ border-color: @border-alt;
+ background-color: @selected-active-background;
+ text-color: @selected-active-foreground;
+}
+element.alternate.normal {
+ background-color: @alternate-normal-background;
+ text-color: @alternate-normal-foreground;
+}
+element.alternate.urgent {
+ background-color: @alternate-urgent-background;
+ text-color: @alternate-urgent-foreground;
+}
+element.alternate.active {
+ background-color: @alternate-active-background;
+ text-color: @alternate-active-foreground;
+}
+scrollbar {
+ width: 4px ;
+ border: 0;
+ handle-width: 8px ;
+ padding: 0;
+}
+sidebar {
+ border: @sidebar-border;
+ border-color: @separator;
+}
+inputbar {
+ text-color: @normal-foreground;
+ padding: @input-padding;
+ spacing: 8px;
+ orientation: vertical;
+}
+prompt {
+ enabled: true;
+ padding: @element-padding;
+ background-color: transparent;
+ text-color: @foreground;
+ font: "Fira Sans Bold 13";
+}
+case-indicator {
+ text-color: @normal-foreground;
+}
+
+wrapper {
+ orientation: horizontal;
+ text-color: @foreground-alt;
+ background-color: @background-bar;
+ border-color: @border-alt;
+
+ border: @main-border;
+ border-radius: @element-radius;
+ padding: @element-padding;
+ children: [ icon-k, entry, icon-paste];
+ spacing: @main-padding;
+}
+button-paste {
+ expand: false;
+ str: "gtk-paste";
+ size: 24;
+ vertical-align: 0.5;
+ action: "kb-cancel";
+}
+icon-paste {
+ expand: false;
+ filename: "gtk-paste";
+ size: 24;
+ vertical-align: 0.5;
+ action: "kb-primary-paste";
+}
+icon-k {
+ expand: false;
+ filename: "input-keyboard";
+ size: 24;
+ vertical-align: 0.5;
+
+}
+entry {
+ vertical-align: 0.5;
+}
+inputbar {
+ children: [ prompt, wrapper ];
+}
+
+error-message {
+ background-color: @error-bg;
+ border-color: @error-border;
+ border-radius: @main-radius;
+ border: @main-border;
+ padding: @main-padding;
+}
diff --git a/dotfiles/.local/bin/anime b/dotfiles/.local/bin/anime
new file mode 100755
index 0000000..212b969
--- /dev/null
+++ b/dotfiles/.local/bin/anime
@@ -0,0 +1,579 @@
+#!/bin/sh
+
+version_number="4.10.4"
+
+# UI
+
+external_menu() {
+ rofi "$1" -sort -dmenu -i -width 1500 -p "$2" "$3"
+}
+
+launcher() {
+ [ "$use_external_menu" = "0" ] && [ -z "$1" ] && set -- "+m" "$2"
+ [ "$use_external_menu" = "0" ] && fzf "$1" --reverse --cycle --prompt "$2"
+ [ "$use_external_menu" = "1" ] && external_menu "$1" "$2" "$external_menu_args"
+}
+
+nth() {
+ stdin=$(cat -)
+ [ -z "$stdin" ] && return 1
+ line_count="$(printf "%s\n" "$stdin" | wc -l | tr -d "[:space:]")"
+ [ "$line_count" -eq 1 ] && printf "%s" "$stdin" | cut -f2,3 && return 0
+ prompt="$1"
+ multi_flag=""
+ [ $# -ne 1 ] && shift && multi_flag="$1"
+ line=$(printf "%s" "$stdin" | cut -f1,3 | tr '\t' ' ' | launcher "$multi_flag" "$prompt" | cut -d " " -f 1)
+ line_start=$(printf "%s" "$line" | head -n1)
+ line_end=$(printf "%s" "$line" | tail -n1)
+ [ -n "$line" ] || exit 1
+ if [ "$line_start" = "$line_end" ]; then
+ printf "%s" "$stdin" | grep -E '^'"${line}"'($|[[:space:]])' | cut -f2,3 || exit 1
+ else
+ printf "%s" "$stdin" | sed -n '/^'"${line_start}"'$/,/^'"${line_end}$"'/p' || exit 1
+ fi
+}
+
+die() {
+ printf "\33[2K\r\033[1;31m%s\033[0m\n" "$*" >&2
+ exit 1
+}
+
+help_info() {
+ printf "
+ Usage:
+ %s [options] [query]
+ %s [query] [options]
+ %s [options] [query] [options]
+
+ Options:
+ -c, --continue
+ Continue watching from history
+ -d, --download
+ Download the video instead of playing it
+ -D, --delete
+ Delete history
+ -l, --logview
+ Show logs
+ -s, --syncplay
+ Use Syncplay to watch with friends
+ -S, --select-nth
+ Select nth entry
+ -q, --quality
+ Specify the video quality
+ -v, --vlc
+ Use VLC to play the video
+ -V, --version
+ Show the version of the script
+ -h, --help
+ Show this help message and exit
+ -e, --episode, -r, --range
+ Specify the number of episodes to watch
+ --dub
+ Play dubbed version
+ --rofi
+ Use rofi instead of fzf for the interactive menu
+ --skip
+ Use ani-skip to skip the intro of the episode (mpv only)
+ --no-detach
+ Don't detach the player (useful for in-terminal playback, mpv only)
+ --exit-after-play
+ Exit the player, and return the player exit code (useful for non interactive scenarios, mpv only)
+ --skip-title
+ Use given title as ani-skip query
+ -N, --nextep-countdown
+ Display a countdown to the next episode
+ -U, --update
+ Update the script
+ Some example usages:
+ %s -q 720p banana fish
+ %s --skip --skip-title \"one piece\" -S 2 one piece
+ %s -d -e 2 cyberpunk edgerunners
+ %s --vlc cyberpunk edgerunners -q 1080p -e 4
+ %s blue lock -e 5-6
+ %s -e \"5 6\" blue lock
+ \n" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}"
+ exit 0
+}
+
+version_info() {
+ printf "%s\n" "$version_number"
+ exit 0
+}
+
+update_script() {
+ update="$(curl -s -A "$agent" "https://raw.githubusercontent.com/pystardust/ani-cli/master/ani-cli")" || die "Connection error"
+ update="$(printf '%s\n' "$update" | diff -u "$0" -)"
+ if [ -z "$update" ]; then
+ printf "Script is up to date :)\n"
+ else
+ if printf '%s\n' "$update" | patch "$0" -; then
+ printf "Script has been updated\n"
+ else
+ die "Can't update for some reason!"
+ fi
+ fi
+ exit 0
+}
+
+# checks if dependencies are present
+dep_ch() {
+ for dep; do
+ command -v "${dep%% *}" >/dev/null || die "Program \"${dep%% *}\" not found. Please install it."
+ done
+}
+
+where_iina() {
+ [ -e "/Applications/IINA.app/Contents/MacOS/iina-cli" ] && echo "/Applications/IINA.app/Contents/MacOS/iina-cli" && return 0
+ printf "%s" "iina" && return 0
+}
+
+where_mpv() {
+ command -v "flatpak" >/dev/null && flatpak info io.mpv.Mpv >/dev/null 2>&1 && printf "%s" "flatpak_mpv" && return 0
+ printf "%s" "mpv" && return 0
+}
+
+# SCRAPING
+
+# extract the video links from response of embed urls, extract mp4 links form m3u8 lists
+get_links() {
+ response="$(curl -e "$allanime_refr" -s "https://${allanime_base}$*" -A "$agent")"
+ episode_link="$(printf '%s' "$response" | sed 's|},{|\
+|g' | sed -nE 's|.*link":"([^"]*)".*"resolutionStr":"([^"]*)".*|\2 >\1|p;s|.*hls","url":"([^"]*)".*"hardsub_lang":"en-US".*|\1|p')"
+
+ case "$episode_link" in
+ *repackager.wixmp.com*)
+ extract_link=$(printf "%s" "$episode_link" | cut -d'>' -f2 | sed 's|repackager.wixmp.com/||g;s|\.urlset.*||g')
+ for j in $(printf "%s" "$episode_link" | sed -nE 's|.*/,([^/]*),/mp4.*|\1|p' | sed 's|,|\
+|g'); do
+ printf "%s >%s\n" "$j" "$extract_link" | sed "s|,[^/]*|${j}|g"
+ done | sort -nr
+ ;;
+ *master.m3u8*)
+ m3u8_refr=$(printf '%s' "$response" | sed -nE 's|.*Referer":"([^"]*)".*|\1|p') && printf '%s\n' "m3u8_refr >$m3u8_refr" >"$cache_dir/m3u8_refr"
+ extract_link=$(printf "%s" "$episode_link" | head -1 | cut -d'>' -f2)
+ relative_link=$(printf "%s" "$extract_link" | sed 's|[^/]*$||')
+ m3u8_streams="$(curl -e "$m3u8_refr" -s "$extract_link" -A "$agent")"
+ printf "%s" "$m3u8_streams" | grep -q "EXTM3U" && printf "%s" "$m3u8_streams" | sed 's|^#EXT-X-STREAM.*x||g; s|,.*|p|g; /^#/d; $!N; s|\n| >|;/EXT-X-I-FRAME/d' |
+ sed "s|>|cc>${relative_link}|g" | sort -nr
+ printf '%s' "$response" | sed -nE 's|.*"subtitles":\[\{"lang":"en","label":"English","default":"default","src":"([^"]*)".*|subtitle >\1|p' >"$cache_dir/suburl"
+ ;;
+ *) [ -n "$episode_link" ] && printf "%s\n" "$episode_link" ;;
+ esac
+
+ printf "%s" "$*" | grep -q "tools.fast4speed.rsvp" && printf "%s\n" "Yt >$*"
+ printf "\033[1;32m%s\033[0m Links Fetched\n" "$provider_name" 1>&2
+}
+
+# initialises provider_name and provider_id. First argument is the provider name, 2nd is the regex that matches that provider's link
+provider_init() {
+ provider_name=$1
+ provider_id=$(printf "%s" "$resp" | sed -n "$2" | head -1 | cut -d':' -f2 | sed 's/../&\
+/g' | sed 's/^79$/A/g;s/^7a$/B/g;s/^7b$/C/g;s/^7c$/D/g;s/^7d$/E/g;s/^7e$/F/g;s/^7f$/G/g;s/^70$/H/g;s/^71$/I/g;s/^72$/J/g;s/^73$/K/g;s/^74$/L/g;s/^75$/M/g;s/^76$/N/g;s/^77$/O/g;s/^68$/P/g;s/^69$/Q/g;s/^6a$/R/g;s/^6b$/S/g;s/^6c$/T/g;s/^6d$/U/g;s/^6e$/V/g;s/^6f$/W/g;s/^60$/X/g;s/^61$/Y/g;s/^62$/Z/g;s/^59$/a/g;s/^5a$/b/g;s/^5b$/c/g;s/^5c$/d/g;s/^5d$/e/g;s/^5e$/f/g;s/^5f$/g/g;s/^50$/h/g;s/^51$/i/g;s/^52$/j/g;s/^53$/k/g;s/^54$/l/g;s/^55$/m/g;s/^56$/n/g;s/^57$/o/g;s/^48$/p/g;s/^49$/q/g;s/^4a$/r/g;s/^4b$/s/g;s/^4c$/t/g;s/^4d$/u/g;s/^4e$/v/g;s/^4f$/w/g;s/^40$/x/g;s/^41$/y/g;s/^42$/z/g;s/^08$/0/g;s/^09$/1/g;s/^0a$/2/g;s/^0b$/3/g;s/^0c$/4/g;s/^0d$/5/g;s/^0e$/6/g;s/^0f$/7/g;s/^00$/8/g;s/^01$/9/g;s/^15$/-/g;s/^16$/./g;s/^67$/_/g;s/^46$/~/g;s/^02$/:/g;s/^17$/\//g;s/^07$/?/g;s/^1b$/#/g;s/^63$/\[/g;s/^65$/\]/g;s/^78$/@/g;s/^19$/!/g;s/^1c$/$/g;s/^1e$/&/g;s/^10$/\(/g;s/^11$/\)/g;s/^12$/*/g;s/^13$/+/g;s/^14$/,/g;s/^03$/;/g;s/^05$/=/g;s/^1d$/%/g' | tr -d '\n' | sed "s/\/clock/\/clock\.json/")
+}
+
+# generates links based on given provider
+generate_link() {
+ case $1 in
+ 1) provider_init "wixmp" "/Default :/p" ;; # wixmp(default)(m3u8)(multi) -> (mp4)(multi)
+ 2) provider_init "youtube" "/Yt-mp4 :/p" ;; # youtube(mp4)(single)
+ 3) provider_init "sharepoint" "/S-mp4 :/p" ;; # sharepoint(mp4)(single)
+ *) provider_init "hianime" "/Luf-Mp4 :/p" ;; # hianime(m3u8)(multi)
+ esac
+ [ -n "$provider_id" ] && get_links "$provider_id"
+}
+
+select_quality() {
+ # removing urls which have soft subs to avoid playing on android, iSH and vlc (m3u8 streams don't get correct referrer)
+ printf '%s' "$player_function" | cut -f1 -d" " | grep -qE '(android|iSH|vlc)' && links=$(printf '%s' "$links" | sed '/cc>/d;/subtitle >/d;/m3u8_refr >/d')
+ printf '%s' "$player_function" | cut -f1 -d" " | grep -qE '(android|iSH)' && links=$(printf '%s' "$links" | sed '/Yt >/d')
+ case "$1" in
+ best) result=$(printf "%s" "$links" | head -n1) ;;
+ worst) result=$(printf "%s" "$links" | grep -E '^[0-9]{3,4}' | tail -n1) ;;
+ *) result=$(printf "%s" "$links" | grep -m 1 "$1") ;;
+ esac
+ [ -z "$result" ] && printf "Specified quality not found, defaulting to best\n" 1>&2 && result=$(printf "%s" "$links" | head -n1)
+
+ # add refr,sub flags for m3u8 and refr flag for yt
+ printf '%s' "$result" | grep -q "cc>" && subtitle="$(printf '%s' "$links" | sed -nE 's|subtitle >(.*)|\1|p')" &&
+ [ -n "$subtitle" ] && subs_flag="--sub-file=$subtitle"
+ printf '%s' "$result" | grep -q "cc>" && m3u8_refr="$(printf '%s' "$links" | sed -nE 's|m3u8_refr >(.*)|\1|p')" && refr_flag="--referrer=$m3u8_refr"
+ printf "%s" "$result" | grep -q "tools.fast4speed.rsvp" && refr_flag="--referrer=$allanime_refr"
+
+ ! (printf '%s' "$result" | grep -qE "(cc>|tools.fast4speed.rsvp)") && unset refr_flag
+ ! (printf '%s' "$result" | grep -q "cc>") && unset subs_flag
+ episode=$(printf "%s" "$result" | cut -d'>' -f2)
+}
+
+# gets embed urls, collects direct links into provider files, selects one with desired quality into $episode
+get_episode_url() {
+ # get the embed urls of the selected episode
+ #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 -G "${allanime_api}/api" --data-urlencode "variables={\"showId\":\"$id\",\"translationType\":\"$mode\",\"episodeString\":\"$ep_no\"}" --data-urlencode "query=$episode_embed_gql" -A "$agent" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":"--([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p')
+ # generate links into sequential files
+ cache_dir="$(mktemp -d)"
+ providers="1 2 3 4"
+ for provider in $providers; do
+ generate_link "$provider" >"$cache_dir"/"$provider" &
+ done
+ wait
+ # select the link with matching quality
+ links=$(cat "$cache_dir"/* | sort -g -r -s)
+ rm -r "$cache_dir"
+ select_quality "$quality"
+ if printf "%s" "$ep_list" | grep -q "^$ep_no$"; then
+ [ -z "$episode" ] && die "Episode is released, but no valid sources!"
+ else
+ [ -z "$episode" ] && die "Episode not released!"
+ fi
+}
+
+# search the query and give results
+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 -G "${allanime_api}/api" --data-urlencode "variables={\"search\":{\"allowAdult\":false,\"allowUnknown\":false,\"query\":\"$1\"},\"limit\":40,\"page\":1,\"translationType\":\"$mode\",\"countryOrigin\":\"ALL\"}" --data-urlencode "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'
+}
+
+time_until_next_ep() {
+ animeschedule="https://animeschedule.net"
+ query="$(printf "%s\n" "$*" | tr ' ' '+')"
+ curl -s -G "$animeschedule/api/v3/anime" --data "q=${query}" | sed 's|"id"|\n|g' | sed -nE 's|.*,"route":"([^"]*)","premier.*|\1|p' | while read -r anime; do
+ data=$(curl -s "$animeschedule/anime/$anime" | sed '1,/"anime-header-list-buttons-wrapper"/d' | sed -nE 's|.*countdown-time-raw" datetime="([^"]*)">.*|Next Raw Release: \1|p;s|.*countdown-time" datetime="([^"]*)">.*|Next Sub Release: \1|p;s|.*english-title">([^<]*)<.*|English Title: \1|p;s|.*main-title".*>([^<]*)<.*|Japanese Title: \1|p')
+ status="Ongoing"
+ color="33"
+ printf "%s\n" "$data"
+ ! (printf "%s\n" "$data" | grep -q "Next Raw Release:") && status="Finished" && color="32"
+ printf "Status: \033[1;%sm%s\033[0m\n---\n" "$color" "$status"
+ done
+ exit 0
+}
+
+# get the episodes list of the selected anime
+episodes_list() {
+ #shellcheck disable=SC2016
+ episodes_list_gql='query ($showId: String!) { show( _id: $showId ) { _id availableEpisodesDetail }}'
+
+ curl -e "$allanime_refr" -s -G "${allanime_api}/api" --data-urlencode "variables={\"showId\":\"$*\"}" --data-urlencode "query=$episodes_list_gql" -A "$agent" | sed -nE "s|.*$mode\":\[([0-9.\",]*)\].*|\1|p" | sed 's|,|\
+|g; s|"||g' | sort -n -k 1
+}
+
+# PLAYING
+
+process_hist_entry() {
+ ep_list=$(episodes_list "$id")
+ latest_ep=$(printf "%s\n" "$ep_list" | tail -n1)
+ title=$(printf "%s\n" "$title" | sed "s|[0-9]\+ episodes|${latest_ep} episodes|")
+ ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null
+ [ -n "$ep_no" ] && printf "%s\t%s - episode %s\n" "$id" "$title" "$ep_no"
+}
+
+update_history() {
+ if grep -q -- "$id" "$histfile"; then
+ sed -E "s|^[^ ]+ ${id} [^ ]+$|${ep_no} ${id} ${title}|" "$histfile" >"${histfile}.new"
+ else
+ cp "$histfile" "${histfile}.new"
+ printf "%s\t%s\t%s\n" "$ep_no" "$id" "$title" >>"${histfile}.new"
+ fi
+ mv "${histfile}.new" "$histfile"
+}
+
+download() {
+ # download subtitle if it's set
+ [ -n "$subtitle" ] && curl -s "$subtitle" -o "$download_dir/$2.vtt"
+ case $1 in
+ *m3u8*)
+ if command -v "yt-dlp" >/dev/null; then
+ yt-dlp --referer "$m3u8_refr" "$1" --no-skip-unavailable-fragments --fragment-retries infinite -N 16 -o "$download_dir/$2.mp4"
+ else
+ ffmpeg -extension_picky 0 -referer "$m3u8_refr" -loglevel error -stats -i "$1" -c copy "$download_dir/$2.mp4"
+ fi
+ # embed subs into downloads
+ # [ -e "$download_dir/$2.vtt" ] && ffmpeg -i "$download_dir/$2.mp4" -i "$download_dir/$2.vtt" -c copy -c:s mov_text "$download_dir/$2.bak.mp4" && mv "$download_dir/$2.bak.mp4" "$download_dir/$2.mp4"
+ ;;
+ *)
+ # shellcheck disable=SC2086
+ aria2c --referer="$allanime_refr" --enable-rpc=false --check-certificate=false --continue $iSH_DownFix --summary-interval=0 -x 16 -s 16 "$1" --dir="$download_dir" -o "$2.mp4" --download-result=hide
+ ;;
+ esac
+}
+
+play_episode() {
+ [ "$log_episode" = 1 ] && [ "$player_function" != "debug" ] && [ "$player_function" != "download" ] && command -v logger >/dev/null && logger -t ani-cli "${allanime_title}${ep_no}"
+ [ "$skip_intro" = 1 ] && skip_flag="$(ani-skip -q "$mal_id" -e "$ep_no")"
+ [ -z "$episode" ] && get_episode_url
+ # shellcheck disable=SC2086
+ case "$player_function" in
+ debug)
+ printf "All links:\n%s\nSelected link:\n" "$links"
+ printf "%s\n" "$episode"
+ ;;
+ mpv*)
+ if [ "$no_detach" = 0 ]; then
+ nohup $player_function $skip_flag --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" $subs_flag $refr_flag >/dev/null 2>&1 &
+ else
+ $player_function $skip_flag $subs_flag $refr_flag --force-media-title="${allanime_title}Episode ${ep_no}" "$episode"
+ mpv_exitcode=$?
+ [ "$exit_after_play" = 1 ] && [ -z "$range" ] && exit "$mpv_exitcode"
+ fi
+ ;;
+ android_mpv) nohup am start --user 0 -a android.intent.action.VIEW -d "$episode" -n is.xyz.mpv/.MPVActivity >/dev/null 2>&1 & ;;
+ android_vlc) nohup am start --user 0 -a android.intent.action.VIEW -d "$episode" -n org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity -e "title" "${allanime_title}Episode ${ep_no}" >/dev/null 2>&1 & ;;
+ *iina*)
+ [ -n "$subs_flag" ] && subs_flag="--mpv-${subs_flag#--}"
+ [ -n "$refr_flag" ] && refr_flag="--mpv-${refr_flag#--}"
+ if pgrep -f "IINA" >/dev/null 2>&1; then
+ # omit --keep-running when an IINA instance exists to prevent hanging
+ nohup $player_function --no-stdin --mpv-force-media-title="${allanime_title}Episode ${ep_no}" $subs_flag $refr_flag "$episode" >/dev/null 2>&1 &
+ else
+ nohup $player_function --no-stdin --keep-running --mpv-force-media-title="${allanime_title}Episode ${ep_no}" $subs_flag $refr_flag "$episode" >/dev/null 2>&1 &
+ fi
+ ;;
+ flatpak_mpv) flatpak run io.mpv.Mpv --force-media-title="${allanime_title}Episode ${ep_no}" "$episode" $subs_flag $refr_flag >/dev/null 2>&1 & ;;
+ vlc*) nohup $player_function --http-referrer="${allanime_refr}" --play-and-exit --meta-title="${allanime_title}Episode ${ep_no}" "$episode" >/dev/null 2>&1 & ;;
+ *yncpla*) nohup $player_function "$episode" -- --force-media-title="${allanime_title}Episode ${ep_no}" $subs_flag $refr_flag >/dev/null 2>&1 & ;;
+ download) "$player_function" "$episode" "${allanime_title}Episode ${ep_no}" "$subtitle" ;;
+ catt) nohup catt cast "$episode" -s "$subtitle" >/dev/null 2>&1 & ;;
+ iSH)
+ printf "\e]8;;vlc://%s\a~~~~~~~~~~~~~~~~~~~~\n~ Tap to open VLC ~\n~~~~~~~~~~~~~~~~~~~~\e]8;;\a\n" "$episode"
+ sleep 5
+ ;;
+ *) nohup $player_function "$episode" >/dev/null 2>&1 & ;;
+ esac
+ replay="$episode"
+ unset episode
+ update_history
+ [ "$use_external_menu" = "1" ] && wait
+}
+
+play() {
+ start=$(printf "%s" "$ep_no" | grep -Eo '^(-1|[0-9]+(\.[0-9]+)?)')
+ end=$(printf "%s" "$ep_no" | grep -Eo '(-1|[0-9]+(\.[0-9]+)?)$')
+ [ "$start" = "-1" ] && ep_no=$(printf "%s" "$ep_list" | tail -n1) && unset start
+ [ -z "$end" ] || [ "$end" = "$start" ] && unset start end
+ [ "$end" = "-1" ] && end=$(printf "%s" "$ep_list" | tail -n1)
+ line_count=$(printf "%s\n" "$ep_no" | wc -l | tr -d "[:space:]")
+ if [ "$line_count" != 1 ] || [ -n "$start" ]; then
+ [ -z "$start" ] && start=$(printf "%s\n" "$ep_no" | head -n1)
+ [ -z "$end" ] && end=$(printf "%s\n" "$ep_no" | tail -n1)
+ range=$(printf "%s\n" "$ep_list" | sed -nE "/^${start}\$/,/^${end}\$/p")
+ [ -z "$range" ] && die "Invalid range!"
+ for i in $range; do
+ tput clear
+ ep_no=$i
+ printf "\33[2K\r\033[1;34mPlaying episode %s...\033[0m\n" "$ep_no"
+ [ "$i" = "$end" ] && unset range
+ play_episode
+ done
+ else
+ play_episode
+ fi
+ # moves up to stored position and deletes to end
+ [ "$player_function" != "debug" ] && [ "$player_function" != "download" ] && tput rc && tput ed
+}
+
+# MAIN
+
+# setup
+agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0"
+allanime_refr="https://allmanga.to"
+allanime_base="allanime.day"
+allanime_api="https://api.${allanime_base}"
+mode="${ANI_CLI_MODE:-dub}"
+download_dir="${ANI_CLI_DOWNLOAD_DIR:-.}"
+log_episode="${ANI_CLI_LOG:-1}"
+quality="${ANI_CLI_QUALITY:-best}"
+case "$(uname -a | cut -d " " -f 1,3-)" in
+ *Darwin*) player_function="${ANI_CLI_PLAYER:-$(where_iina)}" ;; # mac OS
+ *ndroid*) player_function="${ANI_CLI_PLAYER:-android_mpv}" ;; # Android OS (termux)
+ *MINGW* | *WSL2*) player_function="${ANI_CLI_PLAYER:-mpv.exe}" ;; # Windows OS
+ *ish*) player_function="${ANI_CLI_PLAYER:-iSH}" ;; # iOS (iSH)
+ *) player_function="${ANI_CLI_PLAYER:-$(where_mpv)}" ;; # Linux OS
+esac
+
+no_detach="${ANI_CLI_NO_DETACH:-0}"
+exit_after_play="${ANI_CLI_EXIT_AFTER_PLAY:-0}"
+use_external_menu="${ANI_CLI_EXTERNAL_MENU:-0}"
+external_menu_normal_window="${ANI_CLI_EXTERNAL_MENU_NORMAL_WINDOW:-0}"
+skip_intro="${ANI_CLI_SKIP_INTRO:-0}"
+# shellcheck disable=SC2154
+skip_title="$ANI_CLI_SKIP_TITLE"
+[ -t 0 ] || use_external_menu=1
+hist_dir="${ANI_CLI_HIST_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/ani-cli}"
+[ ! -d "$hist_dir" ] && mkdir -p "$hist_dir"
+histfile="$hist_dir/ani-hsts"
+[ ! -f "$histfile" ] && : >"$histfile"
+search="${ANI_CLI_DEFAULT_SOURCE:-scrape}"
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -v | --vlc)
+ case "$(uname -a | cut -d " " -f 1,3-)" in
+ *ndroid*) player_function="android_vlc" ;;
+ MINGW* | *WSL2*) player_function="vlc.exe" ;;
+ *ish*) player_function="iSH" ;;
+ *) player_function="vlc" ;;
+ esac
+ ;;
+ -s | --syncplay)
+ case "$(uname -s)" in
+ Darwin*) player_function="/Applications/Syncplay.app/Contents/MacOS/syncplay" ;;
+ MINGW* | *Msys)
+ export PATH="$PATH":"/c/Program Files (x86)/Syncplay/"
+ player_function="syncplay.exe"
+ ;;
+ *) player_function="syncplay" ;;
+ esac
+ ;;
+ -q | --quality)
+ [ $# -lt 2 ] && die "missing argument!"
+ quality="$2"
+ shift
+ ;;
+ -S | --select-nth)
+ [ $# -lt 2 ] && die "missing argument!"
+ index="$2"
+ shift
+ ;;
+ -c | --continue) search=history ;;
+ -d | --download)
+ [ "$player_function" = "iSH" ] && iSH_DownFix="--async-dns=false"
+ player_function=download
+ ;;
+ -D | --delete)
+ : >"$histfile"
+ exit 0
+ ;;
+ -l | --logview)
+ case "$(uname -s)" in
+ Darwin*) log show --predicate 'process == "logger"' ;;
+ Linux*) journalctl -t ani-cli ;;
+ *) die "Logger not implemented for your platform" ;;
+ esac
+ exit 0
+ ;;
+ -V | --version) version_info ;;
+ -h | --help) help_info ;;
+ -e | --episode | -r | --range)
+ [ $# -lt 2 ] && die "missing argument!"
+ ep_no="$2"
+ shift
+ ;;
+ --dub) mode="dub" ;;
+ --no-detach) no_detach=1 ;;
+ --exit-after-play) exit_after_play=1 && no_detach=1 ;;
+ --rofi) use_external_menu=1 ;;
+ --skip) skip_intro=1 ;;
+ --skip-title)
+ [ $# -lt 2 ] && die "missing argument!"
+ skip_title="$2"
+ shift
+ ;;
+ -N | --nextep-countdown) search=nextep ;;
+ -U | --update) update_script ;;
+ *) query="$(printf "%s" "$query $1" | sed "s|^ ||;s| |+|g")" ;;
+ esac
+ shift
+done
+[ "$use_external_menu" = "0" ] && multi_selection_flag="${ANI_CLI_MULTI_SELECTION:-"-m"}"
+[ "$use_external_menu" = "1" ] && multi_selection_flag="${ANI_CLI_MULTI_SELECTION:-"-multi-select"}"
+[ "$external_menu_normal_window" = "1" ] && external_menu_args="-normal-window"
+printf "\33[2K\r\033[1;34mChecking dependencies...\033[0m\n"
+dep_ch "curl" "sed" "grep" || true
+[ "$skip_intro" = 1 ] && (dep_ch "ani-skip" || true)
+dep_ch "fzf" || true
+case "$player_function" in
+ debug) ;;
+ download) dep_ch "ffmpeg" "aria2c" ;;
+ android*) printf "\33[2K\rChecking of players on Android is disabled\n" ;;
+ *iSH*) printf "\33[2K\rChecking of players on iOS is disabled\n" ;;
+ flatpak_mpv) true ;; # handled out of band in where_mpv
+ *) dep_ch "$player_function" ;;
+esac
+
+# searching
+case "$search" in
+ history)
+ anime_list=$(while read -r ep_no id title; do process_hist_entry & done <"$histfile")
+ wait
+ [ -z "$anime_list" ] && die "No unwatched series in history!"
+ [ -z "${index##*[!0-9]*}" ] && id=$(printf "%s" "$anime_list" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select anime: " | cut -f1)
+ [ -z "${index##*[!0-9]*}" ] || id=$(printf "%s" "$anime_list" | sed -n "${index}p" | cut -f1)
+ [ -z "$id" ] && exit 1
+ title=$(printf "%s" "$anime_list" | grep "$id" | cut -f2 | sed 's/ - episode.*//')
+ ep_list=$(episodes_list "$id")
+ ep_no=$(printf "%s" "$anime_list" | grep "$id" | cut -f2 | sed -nE 's/.*- episode (.+)$/\1/p')
+ allanime_title="$(printf "%s" "$title" | cut -d'(' -f1 | tr -d '[:punct:]')"
+ ;;
+ *)
+ if [ "$use_external_menu" = "0" ]; then
+ while [ -z "$query" ]; do
+ printf "\33[2K\r\033[1;36mSearch anime: \033[0m" && read -r query
+ done
+ else
+ [ -z "$query" ] && query=$(printf "" | external_menu "" "Search anime: " "$external_menu_args")
+ [ -z "$query" ] && exit 1
+ fi
+ # for checking new releases by specifying anime name
+ [ "$search" = "nextep" ] && time_until_next_ep "$query"
+
+ query=$(printf "%s" "$query" | sed "s| |+|g")
+ anime_list=$(search_anime "$query")
+ [ -z "$anime_list" ] && die "No results found!"
+ [ "$index" -eq "$index" ] 2>/dev/null && result=$(printf "%s" "$anime_list" | sed -n "${index}p")
+ [ -z "$index" ] && result=$(printf "%s" "$anime_list" | nl -w 2 | sed 's/^[[:space:]]//' | nth "Select anime: ")
+ [ -z "$result" ] && exit 1
+ title=$(printf "%s" "$result" | cut -f2)
+ allanime_title="$(printf "%s" "$title" | cut -d'(' -f1 | tr -d '[:punct:]')"
+ id=$(printf "%s" "$result" | cut -f1)
+ ep_list=$(episodes_list "$id")
+ [ -z "$ep_no" ] && ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$multi_selection_flag")
+ [ -z "$ep_no" ] && exit 1
+ ;;
+esac
+[ "$skip_intro" = 1 ] && mal_id="$(ani-skip -q "${skip_title:-${title}}")"
+
+# moves the cursor up one line and clears that line
+tput cuu1 && tput el
+# stores the position of cursor
+tput sc
+
+# playback & loop
+play
+[ "$player_function" = "download" ] || [ "$player_function" = "debug" ] && exit 0
+
+while cmd=$(printf "next\nreplay\nprevious\nselect\nchange_quality\nquit" | nth "Playing episode $ep_no of $title... "); do
+ case "$cmd" in
+ next) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{n;p;}") 2>/dev/null ;;
+ replay) episode="$replay" ;;
+ previous) ep_no=$(printf "%s" "$ep_list" | sed -n "/^${ep_no}$/{g;1!p;};h") 2>/dev/null ;;
+ select) ep_no=$(printf "%s" "$ep_list" | nth "Select episode: " "$multi_selection_flag") ;;
+ change_quality)
+ new_quality="$(printf "%s" "$links" | launcher | cut -d\> -f1)"
+ select_quality "$new_quality"
+ ;;
+ *) exit 0 ;;
+ esac
+ [ -z "$ep_no" ] && die "Out of range"
+ play
+done
+
+# ani-cli
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# Project repository: https://github.com/pystardust/ani-cli
\ No newline at end of file
diff --git a/dotfiles/.themes/Serpensortia/intellij/Serpensortia.jar b/dotfiles/.themes/Serpensortia/intellij/Serpensortia.jar
index 386e945..1e1e2db 100644
Binary files a/dotfiles/.themes/Serpensortia/intellij/Serpensortia.jar and b/dotfiles/.themes/Serpensortia/intellij/Serpensortia.jar differ
diff --git a/dotfiles/.themes/Serpensortia/rofi/theme.rasi b/dotfiles/.themes/Serpensortia/rofi/theme.rasi
new file mode 100644
index 0000000..1cb4330
--- /dev/null
+++ b/dotfiles/.themes/Serpensortia/rofi/theme.rasi
@@ -0,0 +1,62 @@
+* {
+ /* Colors */
+ background: rgba ( 8, 12, 8, 80 % );
+ background-alt: rgba ( 19, 29, 19, 80 % );
+ background-bar: rgba ( 66, 74, 66, 80 % );
+ foreground: #c0c0c0;
+ foreground-alt: #c0c0c0;
+ accent: #1c7a44;
+ border: #1c7a44;
+ border-alt: #1c7a44;
+ selected: rgba ( 66, 74, 66, 80 % );
+ urgent: #dc143c;
+ on-selected: #c0c0c0;
+ separator: #0b110b;
+ list-bg: rgba ( 8, 12, 8, 80 % );
+ message-bg: rgba ( 19, 29, 19, 80 % );
+ error-bg: #dc143c;
+ error-border: #0b110b;
+
+ /* Standard Rofi variables for legacy support or internal use */
+ normal-foreground: @foreground;
+ normal-background: transparent;
+ selected-normal-foreground: @on-selected;
+ selected-normal-background: @selected;
+
+ urgent-foreground: @urgent;
+ urgent-background: transparent;
+ selected-urgent-foreground: @foreground;
+ selected-urgent-background: @urgent;
+
+ active-foreground: @accent;
+ active-background: transparent;
+ selected-active-foreground: @on-selected;
+ selected-active-background: @selected;
+
+ alternate-normal-foreground: @foreground;
+ alternate-normal-background: transparent;
+ alternate-urgent-foreground: @urgent;
+ alternate-urgent-background: transparent;
+ alternate-active-foreground: @accent;
+ alternate-active-background: transparent;
+
+ /* Spacing, Padding, Margin */
+ main-spacing: 1px;
+ main-padding: 4px 8px;
+ element-spacing: 1px;
+ element-padding: 1px 2px;
+ input-padding: 2px 4px;
+ message-margin: 1px;
+
+ /* Borders and Radius */
+ main-border: 1px;
+ main-radius: 16px;
+ element-border: 1px;
+ element-radius: 4px;
+ button-border: 1px;
+ button-selected-border: 1px;
+ button-selected-radius: 4px;
+ list-border: 1px;
+ list-radius: 4px;
+ sidebar-border: 1px;
+}
diff --git a/sithego/main.go b/sithego/main.go
index 01f3978..47ca249 100644
--- a/sithego/main.go
+++ b/sithego/main.go
@@ -6,6 +6,7 @@ import (
"Sithego/themer/adapters/gtk"
"Sithego/themer/adapters/intellij"
"Sithego/themer/adapters/qt"
+ "Sithego/themer/adapters/rofi"
"Sithego/themer/adapters/starship"
)
@@ -31,6 +32,10 @@ func main() {
panic(err)
}
+ if err := rofi.New(rofi.WithOutputDir("../dotfiles/.themes/")).Generate(theme); err != nil {
+ panic(err)
+ }
+
if err := dunst.New(dunst.WithOutputDir("../dotfiles/.config/dunst/")).Generate(theme); err != nil {
panic(err)
}
diff --git a/sithego/themer/adapters/rofi/rofi.go b/sithego/themer/adapters/rofi/rofi.go
new file mode 100644
index 0000000..7d56143
--- /dev/null
+++ b/sithego/themer/adapters/rofi/rofi.go
@@ -0,0 +1,64 @@
+package rofi
+
+import (
+ "Sithego/themer"
+ "os"
+ "path/filepath"
+ "text/template"
+)
+
+type Adapter struct {
+ OutputDir string
+}
+
+type Option func(*Adapter)
+
+func WithOutputDir(dir string) Option {
+ return func(a *Adapter) {
+ a.OutputDir = dir
+ }
+}
+
+func New(opt ...Option) *Adapter {
+ home, _ := os.UserHomeDir()
+ a := &Adapter{
+ OutputDir: filepath.Join(home, ".themes"),
+ }
+ for _, o := range opt {
+ o(a)
+ }
+ return a
+}
+
+func (a *Adapter) Name() string {
+ return "Rofi"
+}
+
+func (a *Adapter) Generate(t themer.Theme) error {
+ templatePath := filepath.Join("themer/adapters/rofi", "theme.rasi")
+ templ, err := template.ParseFiles(templatePath)
+ if err != nil {
+ return err
+ }
+ outputPath := filepath.Join(a.OutputDir, t.Meta.Name, "rofi", "theme.rasi")
+
+ if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
+ return err
+ }
+
+ file, err := os.Create(outputPath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ err = templ.Execute(file, struct {
+ Theme themer.Theme
+ }{
+ Theme: t,
+ })
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/sithego/themer/adapters/rofi/theme.rasi b/sithego/themer/adapters/rofi/theme.rasi
new file mode 100644
index 0000000..94c1f7e
--- /dev/null
+++ b/sithego/themer/adapters/rofi/theme.rasi
@@ -0,0 +1,62 @@
+* {
+ /* Colors */
+ background: {{ .Theme.Colors.Semantic.Background.RGBA 80 }};
+ background-alt: {{ .Theme.Colors.Semantic.Surface.RGBA 80 }};
+ background-bar: {{ .Theme.Colors.Semantic.SurfaceAlt.RGBA 80 }};
+ foreground: {{ .Theme.Colors.Semantic.Text.Value }};
+ foreground-alt: {{ .Theme.Colors.Semantic.Text.Value }};
+ accent: {{ .Theme.Colors.Semantic.Accent.Value }};
+ border: {{ .Theme.Colors.Semantic.Accent.Value }};
+ border-alt: {{ .Theme.Colors.Semantic.Accent.Value }};
+ selected: {{ .Theme.Colors.Semantic.SurfaceAlt.RGBA 80 }};
+ urgent: {{ .Theme.Colors.Semantic.Warn.Value }};
+ on-selected: {{ .Theme.Colors.Semantic.Text.Value }};
+ separator: {{ .Theme.Colors.Semantic.Border.Value }};
+ list-bg: {{ .Theme.Colors.Semantic.Background.RGBA 80 }};
+ message-bg: {{ .Theme.Colors.Semantic.Surface.RGBA 80 }};
+ error-bg: {{ .Theme.Colors.Semantic.Warn.Value }};
+ error-border: {{ .Theme.Colors.Semantic.Border.Value }};
+
+ /* Standard Rofi variables for legacy support or internal use */
+ normal-foreground: @foreground;
+ normal-background: transparent;
+ selected-normal-foreground: @on-selected;
+ selected-normal-background: @selected;
+
+ urgent-foreground: @urgent;
+ urgent-background: transparent;
+ selected-urgent-foreground: @foreground;
+ selected-urgent-background: @urgent;
+
+ active-foreground: @accent;
+ active-background: transparent;
+ selected-active-foreground: @on-selected;
+ selected-active-background: @selected;
+
+ alternate-normal-foreground: @foreground;
+ alternate-normal-background: transparent;
+ alternate-urgent-foreground: @urgent;
+ alternate-urgent-background: transparent;
+ alternate-active-foreground: @accent;
+ alternate-active-background: transparent;
+
+ /* Spacing, Padding, Margin */
+ main-spacing: {{ .Theme.Spacing.MarginSmall }};
+ main-padding: {{ .Theme.Spacing.PaddingLarge }};
+ element-spacing: {{ .Theme.Spacing.MarginSmall }};
+ element-padding: {{ .Theme.Spacing.PaddingSmall }};
+ input-padding: {{ .Theme.Spacing.Padding }};
+ message-margin: {{ .Theme.Spacing.MarginSmall }};
+
+ /* Borders and Radius */
+ main-border: 1px;
+ main-radius: {{ .Theme.Spacing.RadiusLarge }};
+ element-border: 1px;
+ element-radius: {{ .Theme.Spacing.RadiusSmall }};
+ button-border: 1px;
+ button-selected-border: 1px;
+ button-selected-radius: {{ .Theme.Spacing.RadiusSmall }};
+ list-border: 1px;
+ list-radius: {{ .Theme.Spacing.RadiusSmall }};
+ sidebar-border: 1px;
+}
diff --git a/sithego/themer/theme.go b/sithego/themer/theme.go
index b965f93..f50c03a 100644
--- a/sithego/themer/theme.go
+++ b/sithego/themer/theme.go
@@ -56,6 +56,17 @@ func (c Color) Alpha(bg Color, amount float64) string {
return formatHexRGB(r, g, b)
}
+func (c Color) RGBA(alpha int) string {
+ r, g, b, ok := parseHexRGB(c.Value)
+ if !ok {
+ return c.Value
+ }
+ if alpha < 100 {
+ return "rgba ( " + strconv.Itoa(int(r)) + ", " + strconv.Itoa(int(g)) + ", " + strconv.Itoa(int(b)) + ", " + strconv.Itoa(alpha) + " % )"
+ }
+ return c.Value
+}
+
func (c *Color) UnmarshalText(text []byte) error {
c.raw = strings.TrimSpace(string(text))
return nil