From ff0be9b328f2dd4b501c0bf7d0b8373d15cfb705 Mon Sep 17 00:00:00 2001 From: arindy Date: Fri, 20 Feb 2026 18:53:24 +0100 Subject: [PATCH] Introduce Rofi adapter, Serpensortia theme, and additional scripts for anime playback and theme customization --- dotfiles/.config/rofi/config.rasi | 205 +++++++ dotfiles/.local/bin/anime | 579 ++++++++++++++++++ .../Serpensortia/intellij/Serpensortia.jar | Bin 1290 -> 1290 bytes dotfiles/.themes/Serpensortia/rofi/theme.rasi | 62 ++ sithego/main.go | 5 + sithego/themer/adapters/rofi/rofi.go | 64 ++ sithego/themer/adapters/rofi/theme.rasi | 62 ++ sithego/themer/theme.go | 11 + 8 files changed, 988 insertions(+) create mode 100644 dotfiles/.config/rofi/config.rasi create mode 100755 dotfiles/.local/bin/anime create mode 100644 dotfiles/.themes/Serpensortia/rofi/theme.rasi create mode 100644 sithego/themer/adapters/rofi/rofi.go create mode 100644 sithego/themer/adapters/rofi/theme.rasi 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 <http://www.gnu.org/licenses/>. +# +# 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 386e945bb9f9115db8007c566bdb21c5a471de5a..1e1e2db1382161f835bdb6460d4572916cebbca1 100644 GIT binary patch delta 169 zcmeC;>f+)J@MdNaVPIh3VCb6`GLcu8J$n9(Od#IqD97Xm6p8`L!2u_O41=$0h@-Bj zpPPPY2qyzGRCQ))1vdjD%L`@(1~7qQ>g1(NnlNKGUu9BbWCof$`5&`2NcUtL78x)< Tnnf4P?}qYMv6zGTPgv9d?LRL- delta 169 zcmeC;>f+)J@MdNaVPIh3U|`u6IFVPEUHs9MOd#IqD97Xm6p8`L!2u_O41=$0h@-Bj zpPPPY2qyzGRCQ))1vdjD%L`@(1~7qQ>g1(NnlNKGUu9BbWCof$`5&`2NcUtL78x)< Tnnf4P?}qYMv6zGTPgv9d9nma> 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