]*>(.*)\(.*\)/(\1)\2/;s/^\(.*\)<\/li><\/ul> <\/div>.*\t\([0-9]*\)/\1\t\2/')
365 | if [ -z "$translations" ]; then
366 | translator_id=$default_translator_id
367 | else
368 | translator_id=$(printf "%s" "$translations" | fzf --cycle --reverse --with-nth 1 -d "\t" --header "Choose a translation" | cut -f2)
369 | fi
370 | }
371 |
372 | download_thumbnails() {
373 | printf "%s\n" "$1" | while read -r cover_url media_id title; do
374 | curl -s -o "$images_cache_dir/$media_id.jpg" "$cover_url" &
375 | if [ "$use_external_menu" = true ]; then
376 | entry=/tmp/jerry/applications/"$media_id.desktop"
377 | generate_desktop "$title" "$images_cache_dir/$media_id.jpg" >"$entry" &
378 | fi
379 | done
380 | sleep $2
381 | }
382 |
383 | image_preview_fzf() {
384 | if [ -n "$use_ueberzugpp" ]; then
385 | UB_PID_FILE="/tmp/.$(uuidgen)"
386 | if [ -z "$ueberzug_output" ]; then
387 | ueberzugpp layer --no-stdin --silent --use-escape-codes --pid-file "$UB_PID_FILE" 2>/dev/null
388 | else
389 | ueberzugpp layer -o "$ueberzug_output" --no-stdin --silent --use-escape-codes --pid-file "$UB_PID_FILE" 2>/dev/null
390 | fi
391 | UB_PID="$(cat "$UB_PID_FILE")"
392 | JERRY_UEBERZUG_SOCKET=/tmp/ueberzugpp-"$UB_PID".socket
393 | choice=$(printf "%s" "$3" | fzf -i -q "$1" --prompt "$2" --cycle --preview-window="$preview_window_size" --preview="ueberzugpp cmd -s $JERRY_UEBERZUG_SOCKET -i fzfpreview -a add -x $ueberzug_x -y $ueberzug_y --max-width $ueberzug_max_width --max-height $ueberzug_max_height -f $images_cache_dir/{2}.jpg" --reverse --with-nth "3" -d "\t")
394 | ueberzugpp cmd -s "$JERRY_UEBERZUG_SOCKET" -a exit
395 | else
396 | case "$(uname -s)" in
397 | MINGW* | *Msys)
398 | # Chafa on windows fails to detect terminal preview window size, so it is determined by view_dim
399 | # TODO: Make $view_dim properly reflect $preview_window_size
400 | view_dim="$(($(tput cols) / 2 - 4))x$(($(tput lines) - 2))"
401 | choice=$(printf "%s" "$3" | fzf -i -q "$1" --prompt "$2" --cycle --preview-window="$preview_window_size" --preview="test -f $images_cache_dir/{2}.jpg && chafa --size=$view_dim $chafa_options $images_cache_dir/{2}.jpg || echo 'File not Found'" --reverse --with-nth "3" -d "\t")
402 | ;;
403 | *)
404 | choice=$(printf "%s" "$3" | fzf -i -q "$1" --prompt "$2" --cycle --preview-window="$preview_window_size" --preview="test -f $images_cache_dir/{2}.jpg && chafa $chafa_options $images_cache_dir/{2}.jpg || echo 'File not Found'" --reverse --with-nth "3" -d "\t")
405 | ;;
406 | esac
407 | fi
408 | }
409 |
410 | select_desktop_entry() {
411 | if [ "$use_external_menu" = true ]; then
412 | [ -n "$image_config_path" ] && choice=$(rofi -config "$rofi_prompt_config" -show drun -drun-categories jerry -filter "$1" -show-icons -theme "$image_config_path" -i -matching fuzzy -sorting-method fzf |
413 | $sed -nE "s@.*/([0-9]*)\.desktop@\1@p") 2>/dev/null ||
414 | choice=$(rofi -config "$rofi_prompt_config" -show drun -drun-categories jerry -filter "$1" -show-icons -i -matching fuzzy -sorting-method fzf | $sed -nE "s@.*/([0-9]*)\.desktop@\1@p") 2>/dev/null
415 | else
416 | image_preview_fzf "$1" "$2" "$3"
417 | fi
418 | }
419 |
420 | #### ANILIST ANIME FUNCTIONS ####
421 | get_anime_from_list() {
422 | anime_list=$(curl -s -X POST "$anilist_base" \
423 | -H 'Content-Type: application/json' \
424 | -H "Authorization: Bearer $access_token" \
425 | -d "{\"query\":\"query(\$userId:Int,\$userName:String,\$type:MediaType){MediaListCollection(userId:\$userId,userName:\$userName,type:\$type){lists{name isCustomList isCompletedList:isSplitCompletedList entries{...mediaListEntry}}user{id name avatar{large}mediaListOptions{scoreFormat rowOrder animeList{sectionOrder customLists splitCompletedSectionByFormat theme}mangaList{sectionOrder customLists splitCompletedSectionByFormat theme}}}}}fragment mediaListEntry on MediaList{id mediaId status score progress progressVolumes repeat priority private hiddenFromStatusLists customLists advancedScores notes updatedAt startedAt{year month day}completedAt{year month day}media{id title{userPreferred romaji english native}coverImage{extraLarge large}type format status(version:2)episodes volumes chapters averageScore popularity isAdult countryOfOrigin genres bannerImage nextAiringEpisode{airingAt timeUntilAiring episode} startDate{year month day}}}\",\"variables\":{\"userId\":$user_id,\"type\":\"ANIME\"}}" | $sed "s@},{@\n@g" | $sed -nE "s@.*\"mediaId\":([0-9]*),\"status\":\"($1)\",\"score\":(.*),\"progress\":([0-9]*),.*\"userPreferred\":\"([^\"]*)\".*\"coverImage\":\{\"extraLarge\":\"([^\"]*)\".*\"episode([\"]*)s*[\"]*:([0-9]*).*\"startDate\":\{\"year\":([0-9]*).*@\6\t\1\t\5 \(\9\) \4|\8 episodes \7 \[\3\]@p" | $sed 's/\\\//\//g;s/\"/(releasing)/')
426 | if [ -z "$anime_list" ]; then
427 | send_notification "No anime found in your list" "2000"
428 | exit 1
429 | fi
430 | if [ "$use_external_menu" = true ]; then
431 | case "$image_preview" in
432 | true)
433 | download_thumbnails "$anime_list" "1"
434 | select_desktop_entry "" "Choose anime: " "$anime_list"
435 | [ -z "$choice" ] && exit 1
436 | start_year=$(printf "%s" "$choice" | $sed -nE "s@.*\(([0-9?]{4})\).*@\1@p")
437 | choice=$(printf "%s" "$choice" | $sed -nE "s@\([0-9?]{4}\)@ @p") # remove year from the title
438 | media_id=$(printf "%s" "$choice" | cut -d\ -f1)
439 | title=$(printf "%s" "$choice" | $sed -nE "s@$media_id (.*) [0-9?|]* episodes.*@\1@p" | $sed -E 's|\\u.{4}|+|g')
440 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* episodes.*@\1@p")
441 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
442 | [ -z "$episodes_total" ] && episodes_total=9999
443 | score=$(printf "%s" "$choice" | $sed -nE "s@.* episodes \[([0-9]*)\].*@\1@p")
444 | ;;
445 | *)
446 | tmp_anime_list=$(printf "%s" "$anime_list" | $sed -nE "s@(.*\.[jpneg]*)[[:space:]]*([0-9]*)[[:space:]]*(.*)@\3\t\2\t\1@p")
447 | choice=$(printf "%s" "$tmp_anime_list" | launcher "Choose anime: " "1")
448 | [ -z "$choice" ] && exit 1
449 | start_year=$(printf "%s" "$choice" | $sed -nE "s@.*\(([0-9?]{4})\).*@\1@p")
450 | choice=$(printf "%s" "$choice" | $sed -nE "s@\([0-9?]{4}\)@ @p") # remove year from the title
451 | media_id=$(printf "%s" "$choice" | cut -f2)
452 | title=$(printf "%s" "$choice" | $sed -nE "s@(.*) [0-9?|]* episodes.*@\1@p" | $sed -E 's|\\u.{4}|+|g')
453 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* episodes.*@\1@p")
454 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
455 | [ -z "$episodes_total" ] && episodes_total=9999
456 | score=$(printf "%s" "$choice" | $sed -nE "s@.* episodes \[([0-9]*)\].*@\1@p")
457 | ;;
458 | esac
459 | else
460 | case "$image_preview" in
461 | true)
462 | download_thumbnails "$anime_list" "2"
463 | select_desktop_entry "" "Choose anime: " "$anime_list"
464 | [ -z "$choice" ] && exit 0
465 | start_year=$(printf "%s" "$choice" | $sed -nE "s@.*\(([0-9?]{4})\).*@\1@p")
466 | choice=$(printf "%s" "$choice" | $sed -nE "s@\([0-9?]{4}\)@ @p") # remove year from the title
467 | media_id=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\.jpg@\1@p")
468 | title=$(printf "%s" "$choice" | $sed -nE "s@[[:space:]]*(.*) [0-9?|]* episodes.*@\1@p" | $sed -E 's|\\u.{4}|+|g')
469 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* episodes.*@\1@p")
470 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
471 | [ -z "$episodes_total" ] && episodes_total=9999
472 | score=$(printf "%s" "$choice" | $sed -nE "s@.* episodes \[([0-9]*)\].*@\1@p")
473 | ;;
474 | *)
475 | choice=$(printf "%s" "$anime_list" | launcher "Choose anime: " "3")
476 | [ -z "$choice" ] && exit 1
477 | start_year=$(printf "%s" "$choice" | $sed -nE "s@.*\(([0-9?]{4})\).*@\1@p")
478 | choice=$(printf "%s" "$choice" | $sed -nE "s@\([0-9?]{4}\)@ @p") # remove year from the title
479 | media_id=$(printf "%s" "$choice" | cut -f2)
480 | title=$(printf "%s" "$choice" | $sed -nE "s@.*$media_id\t(.*) [0-9?|]* episodes.*@\1@p" | $sed -E 's|\\u.{4}|+|g')
481 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* episodes.*@\1@p")
482 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
483 | [ -z "$episodes_total" ] && episodes_total=9999
484 | score=$(printf "%s" "$choice" | $sed -nE "s@.* episodes \[([0-9]*)\].*@\1@p")
485 | ;;
486 | esac
487 |
488 | [ -z "$choice" ] && exit 1
489 | media_id=$(printf "%s" "$choice" | cut -f2)
490 | title=$(printf "%s" "$choice" | $sed -nE "s@.*$media_id\t(.*) [0-9?|]* episodes.*@\1@p" | $sed -E 's|\\u.{4}|+|g')
491 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* episodes.*@\1@p")
492 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
493 | [ -z "$episodes_total" ] && episodes_total=9999
494 | score=$(printf "%s" "$choice" | $sed -nE "s@.* episodes \[([0-9]*)\].*@\1@p")
495 | fi
496 | }
497 |
498 | search_anime_anilist() {
499 | if [ "$show_adult_content" = true ]; then
500 | isAdult_variable=""
501 | else
502 | isAdult_variable=",\"isAdult\":false"
503 | fi
504 | anime_list=$(curl -s -X POST "$anilist_base" \
505 | -H 'Content-Type: application/json' \
506 | -d "{\"query\":\"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$search:String \$format:[MediaFormat]\$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:20){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id title{userPreferred}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}\",\"variables\":{\"page\":1,\"type\":\"ANIME\",\"sort\":\"SEARCH_MATCH\",\"search\":\"$1\"}}" | $sed "s@edges@\n@g" | $sed -nE "s@.*\"id\":([0-9]*),.*\"userPreferred\":\"(.*)\"\},\"coverImage\":.*\"extraLarge\":\"([^\"]*)\".*\"startDate\":\{\"year\":([0-9]*).*\"episode([\"]*)s*[\"]*:([0-9]*).*@\3\t\1\t\2 \(\4\) \6 episodes \5@p" | $sed 's/\\\//\//g;s/\"/(releasing)/')
507 |
508 | if [ "$use_external_menu" = true ]; then
509 | case "$image_preview" in
510 | true)
511 | download_thumbnails "$anime_list" "1"
512 | select_desktop_entry "" "Choose anime: " "$anime_list"
513 | [ -z "$choice" ] && exit 1
514 | start_year=$(printf "%s" "$choice" | $sed -nE "s@.*\(([0-9?]{4})\).*@\1@p")
515 | choice=$(printf "%s" "$choice" | $sed -nE "s@\([0-9?]{4}\)@ @p") # remove year from the title
516 | media_id=$(printf "%s" "$choice" | cut -d\ -f1)
517 | title=$(printf "%s" "$choice" | $sed -nE "s@$media_id (.*) [0-9?]* episodes.*@\1@p")
518 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
519 | [ -z "$episodes_total" ] && episodes_total=9999
520 | ;;
521 | *)
522 | tmp_anime_list=$(printf "%s" "$anime_list" | $sed -nE "s@(.*\.[jpneg]*)[[:space:]]*([0-9]*)[[:space:]]*(.*)@\3\t\2\t\1@p")
523 | choice=$(printf "%s" "$tmp_anime_list" | launcher "Choose anime: " "1")
524 | start_year=$(printf "%s" "$choice" | $sed -nE "s@.*\(([0-9?]{4})\).*@\1@p")
525 | choice=$(printf "%s" "$choice" | $sed -nE "s@\([0-9?]{4}\)@ @p") # remove year from the title
526 | media_id=$(printf "%s" "$choice" | cut -f2)
527 | title=$(printf "%s" "$choice" | $sed -nE "s@(.*) [0-9?|]* episodes.*@\1@p")
528 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* episodes.*@\1@p")
529 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
530 | [ -z "$episodes_total" ] && episodes_total=9999
531 | ;;
532 | esac
533 | else
534 | case "$image_preview" in
535 | true)
536 | download_thumbnails "$anime_list" "2"
537 | select_desktop_entry "" "Choose anime: " "$anime_list"
538 | [ -z "$choice" ] && exit 0
539 | start_year=$(printf "%s" "$choice" | $sed -nE "s@.*\(([0-9?]{4})\).*@\1@p")
540 | choice=$(printf "%s" "$choice" | $sed -nE "s@\([0-9?]{4}\)@ @p") # remove year from the title
541 | media_id=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\.jpg@\1@p")
542 | title=$(printf "%s" "$choice" | $sed -nE "s@[[:space:]]*(.*) [0-9?|]* episodes.*@\1@p")
543 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
544 | [ -z "$episodes_total" ] && episodes_total=9999
545 | ;;
546 | *)
547 | choice=$(printf "%s" "$anime_list" | launcher "Choose anime: " "3")
548 | start_year=$(printf "%s" "$choice" | $sed -nE "s@.*\(([0-9?]{4})\).*@\1@p")
549 | choice=$(printf "%s" "$choice" | $sed -nE "s@\([0-9?]{4}\)@ @p") # remove year from the title
550 | media_id=$(printf "%s" "$choice" | cut -f2)
551 | title=$(printf "%s" "$choice" | $sed -nE "s@.*$media_id\t(.*) [0-9?|]* episodes.*@\1@p")
552 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* episodes.*@\1@p")
553 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
554 | ;;
555 | esac
556 |
557 | [ -z "$choice" ] && exit 0
558 | media_id=$(printf "%s" "$choice" | cut -f2)
559 | title=$(printf "%s" "$choice" | $sed -nE "s@.*$media_id\t(.*) [0-9?|]* episodes.*@\1@p")
560 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* episodes.*@\1@p")
561 | episodes_total=$(printf "%s" "$choice" | $sed -nE "s@.*[\| ]([0-9?]*) episodes.*@\1@p")
562 | fi
563 | [ -z "$title" ] && exit 0
564 |
565 | if [ -n "$no_anilist" ]; then
566 | if [ "$show_adult_content" = true ]; then
567 | isAdult_variable=""
568 | else
569 | isAdult_variable=",\"isAdult\":false"
570 | fi
571 |
572 | episodes_total=$(curl -s -X POST "https://graphql.anilist.co" -A "uwu" -H "Accept: application/json" -H "Content-Type: application/json" --data-raw "{\"query\":\"query media(\$id:Int,\$type:MediaType,\$isAdult:Boolean){Media(id:\$id,type:\$type,isAdult:\$isAdult){episodes nextAiringEpisode{episode}}}\",\"variables\":{\"id\":\"${media_id}\",\"type\":\"ANIME\"$isAdult_variable}}" | $sed -nE "s@.*episode([s]{0,1})\":([0-9]+).*@\1\2@p")
573 | numeric_part=$(printf "%s" "$episodes_total" | sed -nE "s@s([0-9]*)@\1@p")
574 | [ -n "$numeric_part" ] && episodes_total=$numeric_part || episodes_total=$((episodes_total - 1))
575 |
576 | [ -n "$progress" ] && return
577 | if [ "$episodes_total" = 1 ]; then
578 | progress=0
579 | else
580 | [ -z "$episodes_total" ] && episodes_total=9999
581 | if [ "$use_external_menu" = true ]; then
582 | if [ -n "$rofi_prompt_config" ]; then
583 | progress=$(printf "" | rofi -config "$rofi_prompt_config" -sort -dmenu -i -width 1500 -p "" -mesg "Please enter the episode number (1-${episodes_total}): ")
584 | else
585 | progress=$(printf "" | launcher "Please enter the episode number (1-${episodes_total}): ")
586 | fi
587 | else
588 | printf "%s" "Please enter the episode number (1-${episodes_total}): " && read -r progress
589 | [ -z "$progress" ] && progress=$episodes_total
590 | fi
591 | if [ -z "$progress" ]; then
592 | send_notification "Error" "1000" "$images_cache_dir/$media_id.jpg" "No episode number provided"
593 | exit 1
594 | fi
595 | progress=$((progress - 1))
596 | fi
597 | fi
598 | }
599 |
600 | update_progress() {
601 | curl -s -X POST "$anilist_base" \
602 | -H 'Content-Type: application/json' \
603 | -H "Authorization: Bearer $access_token" \
604 | -d "{\"query\":\"mutation(\$id:Int \$mediaId:Int \$status:MediaListStatus \$score:Float \$progress:Int \$progressVolumes:Int \$repeat:Int \$private:Boolean \$notes:String \$customLists:[String]\$hiddenFromStatusLists:Boolean \$advancedScores:[Float]\$startedAt:FuzzyDateInput \$completedAt:FuzzyDateInput){SaveMediaListEntry(id:\$id mediaId:\$mediaId status:\$status score:\$score progress:\$progress progressVolumes:\$progressVolumes repeat:\$repeat private:\$private notes:\$notes customLists:\$customLists hiddenFromStatusLists:\$hiddenFromStatusLists advancedScores:\$advancedScores startedAt:\$startedAt completedAt:\$completedAt){id mediaId status score advancedScores progress progressVolumes repeat priority private hiddenFromStatusLists customLists notes updatedAt startedAt{year month day}completedAt{year month day}user{id name}media{id title{userPreferred}coverImage{large}type format status episodes volumes chapters averageScore popularity isAdult startDate{year}}}}\",\"variables\":{\"status\":\"$3\",\"progress\":$1,\"mediaId\":$2}}"
605 | [ "$3" = "COMPLETED" ] && send_notification "Completed $title" "5000"
606 | [ "$3" = "COMPLETED" ] && $sed -i "/$media_id/d" "$history_file"
607 | }
608 |
609 | update_episode_from_list() {
610 | status_choice=$(printf "CURRENT\nREPEATING\nCOMPLETED\nPAUSED\nDROPPED\nPLANNING" | launcher "Filter by status: ")
611 | get_anime_from_list "$status_choice"
612 |
613 | if [ -z "$title" ] || [ -z "$progress" ]; then
614 | exit 0
615 | fi
616 |
617 | send_notification "Current progress: $progress/$episodes_total episodes watched" "5000"
618 |
619 | if [ "$use_external_menu" = false ]; then
620 | printf "Enter a new episode number: "
621 | read -r new_episode_number
622 | else
623 | new_episode_number=$(printf "" | launcher "Enter a new episode number: ")
624 | fi
625 | [ "$new_episode_number" -gt "$episodes_total" ] && new_episode_number=$episodes_total
626 | [ "$new_episode_number" -lt 0 ] && new_episode_number=0
627 |
628 | if [ -z "$new_episode_number" ]; then
629 | send_notification "No episode number given"
630 | exit 1
631 | fi
632 |
633 | send_notification "Updating progress for $title..."
634 | [ "$new_episode_number" -eq "$episodes_total" ] && status="COMPLETED" || status="CURRENT"
635 | response=$(update_progress "$new_episode_number" "$media_id" "$status")
636 | send_notification "New progress: $new_episode_number/$episodes_total episodes watched"
637 | [ "$new_episode_number" -eq "$episodes_total" ] && send_notification "Completed $title"
638 | }
639 |
640 | #### ANILIST MANGA FUNCTIONS ####
641 | get_manga_from_list() {
642 | manga_list=$(curl -s -X POST "$anilist_base" \
643 | -H 'Content-Type: application/json' \
644 | -H "Authorization: Bearer $access_token" \
645 | -d "{\"query\":\"query(\$userId:Int,\$userName:String,\$type:MediaType){MediaListCollection(userId:\$userId,userName:\$userName,type:\$type){lists{name isCustomList isCompletedList:isSplitCompletedList entries{...mediaListEntry}}user{id name avatar{large}mediaListOptions{scoreFormat rowOrder animeList{sectionOrder customLists splitCompletedSectionByFormat theme}mangaList{sectionOrder customLists splitCompletedSectionByFormat theme}}}}}fragment mediaListEntry on MediaList{id mediaId status score progress progressVolumes repeat priority private hiddenFromStatusLists customLists advancedScores notes updatedAt startedAt{year month day}completedAt{year month day}media{id title{userPreferred romaji english native}coverImage{extraLarge large}type format status(version:2)episodes volumes chapters averageScore popularity isAdult countryOfOrigin genres bannerImage startDate{year month day}}}\",\"variables\":{\"userId\":$user_id,\"type\":\"MANGA\"}}" |
646 | tr "\[|\]" "\n" | $sed -nE "s@.*\"mediaId\":([0-9]*),\"status\":\"($1)\",\"score\":(.*),\"progress\":([0-9]*),.*\"userPreferred\":\"([^\"]*)\".*\"coverImage\":\{\"extraLarge\":\"([^\"]*)\".*\"chapters\":([0-9]*).*@\6\t\1\t\5 \4|\7 chapters \[\3\]@p" | $sed 's/\\\//\//g')
647 |
648 | if [ "$use_external_menu" = true ]; then
649 | case "$image_preview" in
650 | true)
651 | download_thumbnails "$manga_list" "1"
652 | select_desktop_entry "" "Choose manga: " "$manga_list"
653 | [ -z "$choice" ] && exit 1
654 | media_id=$(printf "%s" "$choice" | cut -d\ -f1)
655 | title=$(printf "%s" "$choice" | $sed -nE "s@$media_id (.*) [0-9?|]* chapters.*@\1@p" | $sed -E 's|\\u.{4}|+|g')
656 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* chapters.*@\1@p")
657 | chapters_total=$(printf "%s" "$choice" | $sed -nE "s@.*\|([0-9?]*) chapters.*@\1@p")
658 | score=$(printf "%s" "$choice" | $sed -nE "s@.*\|[0-9?]* chapters[[:space:]]*\[([0-9]*)\][[:space:]]*.*@\1@p")
659 | ;;
660 | *)
661 | tmp_manga_list=$(printf "%s" "$manga_list" | $sed -nE "s@(.*\.[jpneg]*)[[:space:]]*([0-9]*)[[:space:]]*(.*)@\3\t\2\t\1@p")
662 | choice=$(printf "%s" "$tmp_manga_list" | launcher "Choose manga: " "1")
663 | [ -z "$choice" ] && exit 1
664 | media_id=$(printf "%s" "$choice" | cut -f2)
665 | title=$(printf "%s" "$choice" | $sed -nE "s@(.*) [0-9?|]* chapters.*@\1@p" | $sed -E 's|\\u.{4}|+|g')
666 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* chapters.*@\1@p")
667 | chapters_total=$(printf "%s" "$choice" | $sed -nE "s@.*\|([0-9?]*) chapters.*@\1@p")
668 | score=$(printf "%s" "$choice" | $sed -nE "s@.*\|[0-9?]* chapters[[:space:]]*\[([0-9]*)\][[:space:]]*.*@\1@p")
669 | ;;
670 | esac
671 | else
672 | case "$image_preview" in
673 | true)
674 | download_thumbnails "$manga_list" "1"
675 | select_desktop_entry "" "Choose manga: " "$manga_list"
676 | ;;
677 | *)
678 | choice=$(printf "%s" "$manga_list" | launcher "Choose manga: " "3")
679 | ;;
680 | esac
681 | [ -z "$choice" ] && exit 1
682 | media_id=$(printf "%s" "$choice" | cut -f2)
683 | title=$(printf "%s" "$choice" | $sed -nE "s@.*$media_id\t(.*) [0-9?|]* chapters.*@\1@p" | $sed -E 's|\\u.{4}|+|g')
684 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* chapters.*@\1@p")
685 | chapters_total=$(printf "%s" "$choice" | $sed -nE "s@.*\|([0-9?]*) chapters.*@\1@p")
686 | score=$(printf "%s" "$choice" | $sed -nE "s@.*\|[0-9?]* chapters[[:space:]]*\[([0-9]*)\][[:space:]]*.*@\1@p")
687 | fi
688 | }
689 |
690 | search_manga_anilist() {
691 | if [ "$show_adult_content" = true ]; then
692 | isAdult_variable=""
693 | else
694 | isAdult_variable=",\"isAdult\":false"
695 | fi
696 |
697 | manga_list=$(curl -s -X POST "$anilist_base" \
698 | -H 'Content-Type: application/json' \
699 | -d "{\"query\":\"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean \$search:String \$format:[MediaFormat]\$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:20){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id title{userPreferred}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}\",\"variables\":{\"page\":1,\"type\":\"MANGA\",\"sort\":\"SEARCH_MATCH\",\"search\":\"$1\"$isAdult_variable}}" |
700 | tr "\[\]" "\n" | $sed -nE "s@.*\"id\":([0-9]*),.*\"userPreferred\":\"(.*)\"\},\"coverImage\":.*\"extraLarge\":\"([^\"]*)\".*\"chapters\":([^,]*),.*@\3\t\1\t\2 \4 chapters@p" | $sed 's/\\\//\//g;s/null/?/')
701 |
702 | if [ "$use_external_menu" = true ]; then
703 | case "$image_preview" in
704 | true)
705 | download_thumbnails "$manga_list" "1"
706 | select_desktop_entry "" "Choose manga: " "$manga_list"
707 | [ -z "$choice" ] && exit 1
708 | media_id=$(printf "%s" "$choice" | cut -d\ -f1)
709 | title=$(printf "%s" "$choice" | $sed -nE "s@$media_id (.*) [0-9?]* chapters.*@\1@p")
710 | chapters_total=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9?]*) chapters.*@\1@p")
711 | ;;
712 | *)
713 | tmp_manga_list=$(printf "%s" "$manga_list" | $sed -nE "s@(.*\.[jpneg]*)[[:space:]]*([0-9]*)[[:space:]]*(.*)@\3\t\2\t\1@p")
714 | choice=$(printf "%s" "$tmp_manga_list" | launcher "Choose manga: " "1")
715 | media_id=$(printf "%s" "$choice" | cut -f2)
716 | title=$(printf "%s" "$choice" | $sed -nE "s@(.*) [0-9?|]* chapters.*@\1@p")
717 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* chapters.*@\1@p")
718 | chapters_total=$(printf "%s" "$choice" | $sed -nE "s@.*\|([0-9?]*) chapters.*@\1@p")
719 | ;;
720 | esac
721 | else
722 | case "$image_preview" in
723 | true)
724 | download_thumbnails "$manga_list" "1"
725 | select_desktop_entry "" "Choose manga: " "$manga_list"
726 | ;;
727 | *)
728 | choice=$(printf "%s" "$manga_list" | launcher "Choose manga: " "3")
729 | ;;
730 | esac
731 | [ -z "$choice" ] && exit 0
732 | media_id=$(printf "%s" "$choice" | cut -f2)
733 | title=$(printf "%s" "$choice" | $sed -nE "s@.*$media_id\t(.*) [0-9?|]* chapters.*@\1@p")
734 | [ -z "$progress" ] && progress=$(printf "%s" "$choice" | $sed -nE "s@.* ([0-9]*)\|[0-9?]* chapters.*@\1@p")
735 | chapters_total=$(printf "%s" "$choice" | $sed -nE "s@.*\|([0-9?]*) chapters.*@\1@p")
736 | fi
737 |
738 | [ -z "$title" ] && exit 0
739 | }
740 |
741 | update_chapter_from_list() {
742 | status_choice=$(printf "CURRENT\nREPEATING\nCOMPLETED\nPAUSED\nDROPPED\nPLANNING" | launcher "Filter by status: ")
743 | get_manga_from_list "$status_choice"
744 |
745 | if [ -z "$title" ] || [ -z "$progress" ]; then
746 | exit 0
747 | fi
748 |
749 | send_notification "Current progress: $progress/$chapters_total chapters read" "5000"
750 |
751 | if [ "$use_external_menu" = false ]; then
752 | printf "Enter a new chapters read number: "
753 | read -r new_chapter_number
754 | else
755 | new_chapter_number=$(printf "" | launcher "Enter a new chapters read number: ")
756 | fi
757 | [ "$new_chapter_number" -gt "$chapters_total" ] && new_chapter_number=$chapters_total
758 | [ "$new_chapter_number" -lt 0 ] && new_chapter_number=0
759 |
760 | if [ -z "$chapters_total" ]; then
761 | send_notification "No chapter number given"
762 | exit 1
763 | fi
764 |
765 | send_notification "Updating progress for $title..."
766 | [ "$new_chapter_number" -eq "$chapters_total" ] && status="COMPLETED" || status="CURRENT"
767 | response=$(update_progress "$new_chapter_number" "$media_id" "$status")
768 | send_notification "New progress: $new_chapter_number/$chapters_total chapters read"
769 | [ "$new_chapter_number" -eq "$chapters_total" ] && send_notification "Completed $title"
770 | }
771 |
772 | #### ANILIST META FUICTIONS ####
773 |
774 | update_status() {
775 | status_choice=$(printf "CURRENT\nREPEATING\nCOMPLETED\nPAUSED\nDROPPED\nPLANNING" | launcher "Filter by status: ")
776 | if [ "$1" = "ANIME" ]; then
777 | get_anime_from_list "$status_choice"
778 | else
779 | get_manga_from_list "$status_choice"
780 | fi
781 | [ -z "$title" ] && exit 0
782 | send_notification "Choose a new status for $title" "5000"
783 | new_status=$(printf "CURRENT\nREPEATING\nCOMPLETED\nPAUSED\nDROPPED\nPLANNING" | launcher "Choose a new status: ")
784 | [ -z "$new_status" ] && exit 0
785 | send_notification "Updating status for $title..."
786 | response=$(update_progress "$progress" "$media_id" "$new_status")
787 | if printf "%s" "$response" | grep -q "errors"; then
788 | send_notification "Failed to update status for $title"
789 | else
790 | send_notification "New status: $new_status"
791 | fi
792 | }
793 |
794 | update_score() {
795 | if [ "$2" = "immediate" ]; then
796 | [ -z "$percentage_progress" ] || [ "$percentage_progress" -lt 85 ] && return
797 | [ "$1" = "ANIME" ] && total="$episodes_total"
798 | [ "$1" = "MANGA" ] && total="$chapters_total"
799 | [ $((progress + 1)) != "$total" ] && return
800 | else
801 | status_choice=$(printf "CURRENT\nREPEATING\nCOMPLETED\nPAUSED\nDROPPED\nPLANNING" | launcher "Filter by status: ")
802 | case "$1" in
803 | "ANIME") get_anime_from_list "$status_choice" ;;
804 | "MANGA") get_manga_from_list "$status_choice" ;;
805 | esac
806 | fi
807 | send_notification "Enter new score for: \"$title\"" "5000"
808 | send_notification "Current score: $score" "5000"
809 | if [ "$use_external_menu" = false ]; then
810 | printf "Enter new score: "
811 | read -r new_score
812 | else
813 | new_score=$(printf "" | launcher "Enter new score: ")
814 | fi
815 | [ -z "$new_score" ] && send_notification "No score given" && exit 1
816 | send_notification "Updating score for $title..."
817 | response=$(curl -s -X POST "$anilist_base" \
818 | -H 'Content-Type: application/json' \
819 | -H "Authorization: Bearer $access_token" \
820 | -d "{\"query\":\"mutation(\$id:Int \$mediaId:Int \$status:MediaListStatus \$score:Float \$progress:Int \$progressVolumes:Int \$repeat:Int \$private:Boolean \$notes:String \$customLists:[String]\$hiddenFromStatusLists:Boolean \$advancedScores:[Float]\$startedAt:FuzzyDateInput \$completedAt:FuzzyDateInput){SaveMediaListEntry(id:\$id mediaId:\$mediaId status:\$status score:\$score progress:\$progress progressVolumes:\$progressVolumes repeat:\$repeat private:\$private notes:\$notes customLists:\$customLists hiddenFromStatusLists:\$hiddenFromStatusLists advancedScores:\$advancedScores startedAt:\$startedAt completedAt:\$completedAt){id mediaId status score advancedScores progress progressVolumes repeat priority private hiddenFromStatusLists customLists notes updatedAt startedAt{year month day}completedAt{year month day}user{id name}media{id title{userPreferred}coverImage{large}type format status episodes volumes chapters averageScore popularity isAdult startDate{year}}}}\",\"variables\":{\"score\":$new_score,\"mediaId\":$media_id}}")
821 | if printf "%s" "$response" | grep -q "errors"; then
822 | send_notification "Failed to update score for $title"
823 | else
824 | send_notification "New score: $new_score"
825 | fi
826 | }
827 |
828 | get_anilist_info() {
829 | case "$1" in
830 | "ANIME")
831 | get_input "Search anime: "
832 | [ -z "$query" ] && exit 1
833 | search_anime_anilist "$query"
834 | ;;
835 | "MANGA")
836 | get_input "Search manga: "
837 | [ -z "$query" ] && exit 1
838 | search_manga_anilist "$query"
839 | ;;
840 | esac
841 | [ -z "$media_id" ] && exit 1
842 | info="$(curl -s -X POST "$anilist_base" \
843 | -H 'Content-Type: application/json' \
844 | -d "{\"query\":\"query media(\$id:Int,\$type:MediaType,\$isAdult:Boolean){Media(id:\$id,type:\$type,isAdult:\$isAdult){id title{userPreferred romaji english native}coverImage{extraLarge large}bannerImage startDate{year month day}endDate{year month day}description season seasonYear type format status(version:2)episodes duration chapters volumes genres synonyms source(version:3)isAdult isLocked meanScore averageScore popularity favourites isFavouriteBlocked hashtag countryOfOrigin isLicensed isFavourite isRecommendationBlocked isFavouriteBlocked isReviewBlocked nextAiringEpisode{airingAt timeUntilAiring episode}relations{edges{id relationType(version:2)node{id title{userPreferred}format type status(version:2)bannerImage coverImage{large}}}}characterPreview:characters(perPage:6,sort:[ROLE,RELEVANCE,ID]){edges{id role name voiceActors(language:JAPANESE,sort:[RELEVANCE,ID]){id name{userPreferred}language:languageV2 image{large}}node{id name{userPreferred}image{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{id role node{id name{userPreferred}language:languageV2 image{large}}}}studios{edges{isMain node{id name}}}reviewPreview:reviews(perPage:2,sort:[RATING_DESC,ID]){pageInfo{total}nodes{id summary rating ratingAmount user{id name avatar{large}}}}recommendations(perPage:7,sort:[RATING_DESC,ID]){pageInfo{total}nodes{id rating userRating mediaRecommendation{id title{userPreferred}format type status(version:2)bannerImage coverImage{large}}user{id name avatar{large}}}}externalLinks{id site url type language color icon notes isDisabled}streamingEpisodes{site title thumbnail url}trailer{id site}rankings{id rank type format year season allTime context}tags{id name description rank isMediaSpoiler isGeneralSpoiler userId}mediaListEntry{id status score}stats{statusDistribution{status amount}scoreDistribution{score amount}}}}\",\"variables\":{\"id\":$media_id,\"type\":\"$1\"}}" |
845 | jq -r '.data.Media.description' | $sed "s/
/\n/g")"
846 | if [ "$use_external_menu" = true ]; then
847 | if ! command -v "zenity" >/dev/null; then
848 | send_notification "For this feature to work in the rofi mode, you must have zenity installed."
849 | exit 1
850 | fi
851 | zenity --info --text="$info"
852 | else
853 | echo "$info" | $display
854 | fi
855 | }
856 |
857 | #### ANIME SCRAPING FUNCTIONS ####
858 | get_episode_info() {
859 | case "$provider" in
860 | allanime)
861 | query_title=$(printf "%s" "$title" | tr ' ' '+')
862 | response=$(curl -e "$allanime_refr" -s -G "https://api.$allanime_base/api" --data-urlencode 'variables={"search":{"allowAdult":false,"allowUnknown":false,"query":"'"$query_title"'"},"limit":40,"page":1,"translationType":"sub","countryOrigin":"ALL"}' --data-urlencode 'query=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 } }}' | sed 's|Show|\n|g' | sed -nE 's|.*_id":"([^"]*)","name":"([^"]*)".*sub":([1-9][^,]*).*|\1\t\2 (\3 episodes)|p')
863 | [ -z "$response" ] && exit 1
864 | # if it is only one line long, then auto select it
865 | if [ "$(printf "%s\n" "$response" | wc -l)" -eq 1 ]; then
866 | send_notification "Jerry" "" "" "Since there is only one result, it was automatically selected"
867 | choice=$response
868 | else
869 | choice=$(printf "%s" "$response" | launcher "Choose anime: " 2)
870 | fi
871 | [ -z "$choice" ] && exit 1
872 | title=$(printf "%s" "$choice" | cut -f2 | sed -E 's| \([0-9]* episodes\)||')
873 | allanime_id=$(printf "%s" "$choice" | cut -f1)
874 | episode_number=$((progress + 1))
875 | episode_info=$(printf "%s\t%s" "$allanime_id" "$title")
876 | ;;
877 | aniwatch)
878 | aniwatch_id=$(curl -s "https://raw.githubusercontent.com/bal-mackup/mal-backup/master/anilist/anime/${media_id}.json" |
879 | tr -d '\n ' | tr '}' '\n' | $sed -nE "s@.*\"Zoro\".*\"url\":\".*-([0-9]*)\".*@\1@p")
880 | episode_info=$(curl -s "https://hianime.to/ajax/v2/episode/list/${aniwatch_id}" | $sed -e "s/\n/g" -e "s/\\\\//g" | $sed -nE "s_.*a title=\"([^\"]*)\".*data-id=\"([0-9]*)\".*_\2\t\1_p" | $sed -n "$((progress + 1))p")
881 | ;;
882 | yugen)
883 | href=$(curl -s "https://raw.githubusercontent.com/bal-mackup/mal-backup/master/anilist/anime/${media_id}.json" |
884 | tr -d '\n' | tr '}' '\n' | $sed -nE 's@.*"YugenAnime".*"url": *"([^"]*)".*@\1@p' | $sed -e "s@tv/anime@tv/watch@")
885 | href="$href$((progress + 1))/"
886 | ep_title=$(curl -s "$href" | $sed -nE "s@.*$((progress + 1))\s:\s([^<]*).*@\1@p")
887 | if [ "$translation_type" = "dub" ]; then
888 | href=$(printf "%s" "$href" | $sed -E 's|(/[^/]+)/([0-9]+)/$|\1-dub/\2/|')
889 | fi
890 | yugen_id=$(curl -s "$href" | $sed -nE "s@.*id=\"main-embed\" src=\".*/e/([^/]*)/\".*@\1@p")
891 | episode_info=$(printf "%s\t%s" "$yugen_id" "$ep_title" | head -1)
892 | ;;
893 | hdrezka)
894 | query=$(curl -s "https://raw.githubusercontent.com/bal-mackup/mal-backup/master/anilist/anime/${media_id}.json" |
895 | sed -nE "s@.*\"title\":.\"([^\"]*)\".*@\1@p" | head -1 | tr ' ' '+')
896 | request=$(curl -s "https://hdrezka.website/search/?do=search&subaction=search&q=${query}" -A "uwu" --compressed)
897 | response=$(printf "%s" "$request" | sed "s/
([^<]*) ([0-9]*).*@\3/\4\t\5 [\6]\t\2@p")
898 | if [ -z "$response" ]; then
899 | send_notification "Error" "Could not query the anime on hdrezka"
900 | exit 1
901 | fi
902 | if [ "$(printf "%s\n" "$response" | wc -l)" -eq 1 ]; then
903 | send_notification "Jerry" "" "" "Since there is only one result, it was automatically selected"
904 | episode_info=$response
905 | else
906 | episode_info=$(printf "%s" "$response" | launcher "Choose anime: " 2)
907 | fi
908 | ;;
909 | aniworld)
910 | query=$(curl -s "https://raw.githubusercontent.com/bal-mackup/mal-backup/master/anilist/anime/${media_id}.json" |
911 | sed -nE "s@.*\"title\":.\"([^\"]*)\".*@\1@p" | head -1 | tr ' ' '+')
912 | request=$(curl -s "https://aniworld.to/ajax/search" -X POST --data-raw "keyword=${query}")
913 | response=$(printf "%s" "$request" | tr '{}' '\n' | sed -nE "s@.*title\":\"([^\"]*)\".*\"link\":\"([^\"]*)\".*@\1\t\2@p" | sed 's/<\\\/em>//g; s///g')
914 | if [ -z "$response" ]; then
915 | send_notification "Error" "Could not query the anime on aniworld"
916 | exit 1
917 | fi
918 | if [ "$(printf "%s\n" "$response" | wc -l)" -eq 1 ]; then
919 | send_notification "Jerry" "" "" "Since there is only one result, it was automatically selected"
920 | episode_info=$response
921 | else
922 | episode_info=$(printf "%s" "$response" | launcher "Choose anime: " 1)
923 | fi
924 | ;;
925 | crunchyroll)
926 | cr_href=$(curl -s "https://raw.githubusercontent.com/bal-mackup/mal-backup/master/anilist/anime/${media_id}.json" |
927 | tr -d '\n ' | tr '}' '\n' | $sed -nE "s@.*\"Crunchyroll\".*\"url\":\"([^\"]*)\".*@\1@p" | head -1)
928 | cr_id=$(printf "%s" "$cr_href" | sed -nE "s@.*/([^\/]*)/.*@\1@p")
929 | access_token=$(curl -s -X POST -H "Authorization: Basic Y3Jfd2ViOg==" -d "grant_type=client_id&scope=offline_access" "https://www.crunchyroll.com/auth/v1/token" | sed -nE "s@.*\"access_token\":\"([^\"]*)\".*@\1@p")
930 | season_id=$(curl -s "https://www.crunchyroll.com/content/v2/cms/series/${cr_id}/seasons" \
931 | -H "authorization: Bearer ${access_token}" | sed -nE "s@.*\"id\":\"([^\"]*)\".*@\1@p")
932 | response=$(curl -s "https://www.crunchyroll.com/content/v2/cms/seasons/${season_id}/episodes" \
933 | -H "authorization: Bearer ${access_token}" | sed "s/},{/\n/g")
934 | title=$(printf "%s" "$response" | sed -nE "s@.*\"title\":\"([^\"]*)\".*@\1@p" | sed -n "$((progress + 1))p")
935 | eid=$(printf "%s\n" "$response" | sed -nE "s@.*\"id\":\"([^\"]*)\".*@\1@p" | sed -n $((progress + 1))p)
936 | episode_info="$(printf "%s\t%s" "$eid" "$title")"
937 | ;;
938 | esac
939 | }
940 |
941 | extract_from_json() {
942 | case "$provider" in
943 | allanime)
944 | resp=$(printf "%s" "$json_data" | tr '{}' '\n' | sed 's|\\u002F|\/|g;s|\\||g' | sed -nE 's|.*sourceUrl":"--([^"]*)".*sourceName":"([^"]*)".*|\2 :\1|p')
945 | # generate links into sequential files
946 | cache_dir="$(mktemp -d)"
947 | link_providers="1 2 3 4 5"
948 | for link_provider in $link_providers; do
949 | generate_links "$link_provider" >"$cache_dir"/"$link_provider" &
950 | done
951 | wait
952 | # select the link with matching quality
953 | links=$(cat "$cache_dir"/* | sed 's|^Mp4-||g;/http/!d' | sort -g -r -s)
954 | if [ "$json_output" = true ]; then
955 | printf '{\n'
956 | for file in $cache_dir/*; do
957 | if [ -s "$file" ]; then
958 | provider=$(basename "$file")
959 | printf ' "%s": {\n' "provider_$provider"
960 | cat $file | while read -r quality url; do
961 | printf ' "%s": "%s",\n' "$quality" "${url#>}"
962 | done
963 | [ "$provider" = "5" ] && printf ' }\n' || printf ' },\n'
964 | fi
965 | done
966 | printf '}\n'
967 | exit 0
968 | fi
969 | video_link=$(select_quality "$quality")
970 | rm -r "$cache_dir"
971 | ;;
972 | aniwatch)
973 |
974 | video_link=$(printf "%s" "$json_data" | tr '[' '\n' | $sed -nE 's@.*\"file\":\"(.*\.m3u8).*@\1@p' | head -1)
975 | [ -n "$quality" ] && video_link=$(printf "%s" "$video_link" | $sed -e "s|/playlist.m3u8|/$quality/index.m3u8|")
976 |
977 | subs_links=$(printf "%s" "$json_data" | tr "{}" "\n" | $sed -nE "s@.*\"file\":\"([^\"]*)\",\"label\":\"(.$subs_language)[,\"\ ].*@\1@p")
978 | subs_arg="--sub-file"
979 | num_subs=$(printf "%s" "$subs_links" | wc -l)
980 | if [ "$num_subs" -gt 0 ]; then
981 | subs_links=$(printf "%s" "$subs_links" | $sed -e "s/:/\\$path_thing:/g" -e "H;1h;\$!d;x;y/\n/$separator/" -e "s/$separator\$//")
982 | subs_arg="--sub-files"
983 | fi
984 | [ -z "$subs_links" ] && send_notification "No subtitles found"
985 |
986 | if [ "$json_output" = true ]; then
987 | printf "%s\n" "$json_data"
988 | exit 0
989 | fi
990 | subs_links=$(printf "%s" "$json_data" | tr "{}" "\n" | $sed -nE "s@\"file\":\"([^\"]*)\",\"label\":\"(.$subs_language)[,\"\ ].*@\1@p")
991 | num_subs=$(printf "%s" "$subs_links" | wc -l)
992 | if [ "$num_subs" -gt 0 ]; then
993 | subs_links=$(printf "%s" "$subs_links" | $sed -e "s/:/\\$path_thing:/g" -e "H;1h;\$!d;x;y/\n/$separator/" -e "s/$separator\$//")
994 | subs_arg="--sub-files=$subs_links"
995 | else
996 | subs_arg="--sub-file=$subs_links"
997 | fi
998 | [ -z "$subs_links" ] && send_notification "No subtitles found"
999 | ;;
1000 | yugen)
1001 | if [ "$json_output" = true ]; then
1002 | printf "%s\n" "$json_data"
1003 | exit 0
1004 | fi
1005 | hls_link_1=$(printf "%s" "$json_data" | tr '{}' '\n' | $sed -nE "s@.*\"hls\": \[\"([^\"]*)\".*@\1@p")
1006 | # hls_link_2=$(printf "%s" "$json_data" | tr '{}' '\n' | $sed -nE "s@.*hls.*, \"([^\"]*)\".\]*@\1@p")
1007 | # gogo_link=$(printf "%s" "$json_data" | tr '{}' '\n' | $sed -nE "s@.*\"src\": \"([^\"]*)\", \"type\": \"embed.*@\1@p")
1008 | if [ -n "$quality" ]; then
1009 | video_link=$(printf "%s" "$hls_link_1" | $sed -e "s/\.m3u8$/\.$quality.m3u8/")
1010 | else
1011 | video_link=$hls_link_1
1012 | fi
1013 | ;;
1014 | hdrezka)
1015 | encrypted_video_link=$(printf "%s" "$json_data" | sed -nE "s@.*\"url\":\"([^\"]*)\".*@\1@p" | sed "s/\\\//g" | cut -c'3-' | sed 's|//_//||g')
1016 | # the part below is pain
1017 | subs_links=$(printf "%s" "$json_data" | sed -nE "s@.*\"subtitle\":\"([^\"]*)\".*@\1@p" |
1018 | sed -e 's/\[[^]]*\]//g' -e 's/,/\n/g' -e 's/\\//g' -e "s/:/\\$path_thing:/g" -e "H;1h;\$!d;x;y/\n/$separator/" -e "s/$separator\$//")
1019 | # TODO: fix subs
1020 | subs_arg="--sub-files=$subs_links"
1021 |
1022 | # ty @CoolnsX for helping me out with the decryption
1023 | table='ISE=,IUA=,IV4=,ISM=,ISQ=,QCE=,QEA=,QF4=,QCM=,QCQ=,XiE=,XkA=,Xl4=,XiM=,XiQ=,IyE=,I0A=,I14=,IyM=,IyQ=,JCE=,JEA=,JF4=,JCM=,JCQ=,ISEh,ISFA,ISFe,ISEj,ISEk,IUAh,IUBA,IUBe,IUAj,IUAk,IV4h,IV5A,IV5e,IV4j,IV4k,ISMh,ISNA,ISNe,ISMj,ISMk,ISQh,ISRA,ISRe,ISQj,ISQk,QCEh,QCFA,QCFe,QCEj,QCEk,QEAh,QEBA,QEBe,QEAj,QEAk,QF4h,QF5A,QF5e,QF4j,QF4k,QCMh,QCNA,QCNe,QCMj,QCMk,QCQh,QCRA,QCRe,QCQj,QCQk,XiEh,XiFA,XiFe,XiEj,XiEk,XkAh,XkBA,XkBe,XkAj,XkAk,Xl4h,Xl5A,Xl5e,Xl4j,Xl4k,XiMh,XiNA,XiNe,XiMj,XiMk,XiQh,XiRA,XiRe,XiQj,XiQk,IyEh,IyFA,IyFe,IyEj,IyEk,I0Ah,I0BA,I0Be,I0Aj,I0Ak,I14h,I15A,I15e,I14j,I14k,IyMh,IyNA,IyNe,IyMj,IyMk,IyQh,IyRA,IyRe,IyQj,IyQk,JCEh,JCFA,JCFe,JCEj,JCEk,JEAh,JEBA,JEBe,JEAj,JEAk,JF4h,JF5A,JF5e,JF4j,JF4k,JCMh,JCNA,JCNe,JCMj,JCMk,JCQh,JCRA,JCRe,JCQj,JCQk'
1024 |
1025 | for i in $(printf "%s" "$table" | tr ',' '\n'); do
1026 | encrypted_video_link=$(printf "%s" "$encrypted_video_link" | sed "s/$i//g")
1027 | done
1028 |
1029 | video_links=$(printf "%s" "$encrypted_video_link" | sed 's/_//g' | base64 -d | tr ',' '\n' | sed -nE "s@\[([^\]*)\](.*)@\"\1\":\"\2\",@p")
1030 | video_links_json=$(printf "%s" "$video_links" | tr -d '\n' | sed "s/,$//g")
1031 | json_data=$(printf "%s" "$json_data" | sed -E "s@\"url\":\"[^\"]*\"@\"url\":\{$video_links_json\}@")
1032 | if [ "$json_output" = true ]; then
1033 | printf "%s\n" "$json_data"
1034 | exit 0
1035 | fi
1036 | if [ -n "$quality" ]; then
1037 | video_link=$(printf "%s" "$video_links" | sed -nE "s@\"${quality}.*\":\".* or ([^\"]*)\".*@\1@p" | tail -1)
1038 | else
1039 | # auto selects best quality
1040 | video_link=$(printf "%s" "$video_links" | sed -nE "s@\".*\":\".* or ([^\"]*)\".*@\1@p" | tail -1)
1041 | fi
1042 | [ -z "$video_link" ] && exit 1
1043 | ;;
1044 | esac
1045 | [ "$((progress + 1))" -eq "$episodes_total" ] && status="COMPLETED" || status="CURRENT"
1046 | }
1047 |
1048 | get_json() {
1049 | case "$provider" in
1050 | allanime)
1051 | json_data=$(curl -e "$allanime_refr" -s -G "https://api.$allanime_base/api" --data-urlencode 'variables={"showId":"'"$episode_id"'","translationType":"'"$translation_type"'","episodeString":"'"$episode_number"'"}' --data-urlencode 'query=query ($showId: String!, $translationType: VaildTranslationTypeEnumType!, $episodeString: String!) { episode( showId: $showId translationType: $translationType episodeString: $episodeString ) { episodeString sourceUrls }}')
1052 | ;;
1053 | aniwatch)
1054 | source_id=$(curl -s "https://hianime.to/ajax/v2/episode/servers?episodeId=$episode_id" |
1055 | $sed "s/\n/g;s/\\\//g" | $sed -nE "s@.*data-type=\"$translation_type\" data-id=\"([0-9]*)\".*@\1@p" | head -1)
1056 | [ -z "$source_id" ] && source_id=$(curl -s "https://aniwatch.to/ajax/v2/episode/servers?episodeId=$episode_id" |
1057 | $sed "s/\n/g;s/\\\//g" | $sed -nE "s@.*data-type=\"raw\" data-id=\"([0-9]*)\".*@\1@p" | head -1)
1058 | embed_link=$(curl -s "https://hianime.to/ajax/v2/episode/sources?id=$source_id" | $sed -nE "s_.*\"link\":\"([^\"]*)\".*_\1_p")
1059 |
1060 | # get the juicy links
1061 | parse_embed=$(printf "%s" "$embed_link" | $sed -nE "s_(.*)/embed-(2|4|6)/e-([0-9])/(.*)\?k=1\$_\1\t\2\t\3\t\4_p")
1062 | provider_link=$(printf "%s" "$parse_embed" | cut -f1)
1063 | embed_type=$(printf "%s" "$parse_embed" | cut -f2)
1064 | e_number=$(printf "%s" "$parse_embed" | cut -f3)
1065 | source_id=$(printf "%s" "$parse_embed" | cut -f4)
1066 |
1067 | json_data=$(curl -s "${provider_link}/embed-${embed_type}/ajax/e-${e_number}/getSources?id=${source_id}" -H "X-Requested-With: XMLHttpRequest")
1068 | ;;
1069 | yugen)
1070 | json_data=$(curl -s 'https://yugenanime.tv/api/embed/' -X POST -H 'X-Requested-With: XMLHttpRequest' --data-raw "id=$episode_id&ac=0")
1071 | ;;
1072 | hdrezka)
1073 | hdrezka_data_and_translation_id
1074 | tmp_season_id=$(curl -s "https://hdrezka.website/${media_type}/${episode_id}.html" -A "uwu" --compressed | sed "s/([^<]*).*@\2\t\1@p")
1076 | if [ -n "$tmp_season_id" ]; then
1077 | tmp_season_id=$(printf "%s" "$tmp_season_id" | fzf -1 --cycle --reverse --with-nth 1 -d '\t' --header "Choose a season: ")
1078 | [ -z "$tmp_season_id" ] && exit 1
1079 | season_title=$(printf "%s" "$tmp_season_id" | cut -f1)
1080 | season_id=$(printf "%s" "$tmp_season_id" | cut -f2)
1081 | fi
1082 | episode_id=$((progress + 1))
1083 | json_data=$(curl -s -X POST "https://hdrezka.website/ajax/get_cdn_series/" -A "uwu" --data-raw "id=${data_id}&translator_id=${translator_id}&season=${season_id}&episode=${episode_id}&action=get_stream" --compressed)
1084 | ;;
1085 | aniworld)
1086 | anime_link=$(printf "%s" "$episode_info" | cut -f2 | sed 's/\\//g')
1087 | episode=$(curl -s "https://aniworld.to${anime_link}" | sed -nE "s@.*href=\"([^\"]*-$((progress + 1)))\".*data-episode-id=\"[0-9]*\".*title=\"([^\"]*)\".*@\1\t\2@p")
1088 | if [ -z "$episode" ]; then
1089 | send_notification "Error" "Could not find this episode on aniworld"
1090 | exit 1
1091 | fi
1092 | episode_href=$(printf "%s" "$episode" | cut -f1)
1093 | episode_title=$(printf "%s" "$episode" | cut -f2)
1094 | redirect_url=$(curl -s "https://aniworld.to${episode_href}" | sed -nE "s@.*href=\"(/redirect[^\"]*)\".*@\1@p" | head -1)
1095 | video_link=$(curl -sL "https://aniworld.to${redirect_url}" | sed -nE "s@.*\"(.*\.m3u8[^\"]*)\".*@\1@p" | head -1)
1096 | ;;
1097 | crunchyroll)
1098 | if [ -n "$cr_token" ]; then
1099 | video_link=$("$cr_wrapper" --eid "$episode_id" --token "$cr_token")
1100 | else
1101 | video_link=$("$cr_wrapper" --eid "$episode_id" --email "$cr_email" --password "$cr_password")
1102 | fi
1103 | ;;
1104 | esac
1105 |
1106 | [ -n "$json_data" ] && extract_from_json
1107 | }
1108 |
1109 | #### MANGA SCRAPING FUNCTIONS ####
1110 | get_chapter_info() {
1111 | manga_provider="mangadex"
1112 | case "$manga_provider" in
1113 | mangadex)
1114 | mangadex_id=$(curl -s "https://raw.githubusercontent.com/bal-mackup/mal-backup/master/anilist/manga/${media_id}.json" | tr -d "\n" | $sed -nE "s@.*\"Mangadex\":[[:space:]{]*\"([^\"]*)\".*@\1@p")
1115 | chapter_info=$(curl -s "https://api.mangadex.org/chapter?manga=$mangadex_id&translatedLanguage[]=en&chapter=$((progress + 1))&includeEmptyPages=0" | $sed "s/}]},/\n/g" |
1116 | $sed -nE "s@.*\"id\":\"([^\"]*)\".*\"chapter\":\"$((progress + 1))\",\"title\":\"([^\"]*)\".*@\1\t\2@p" | head -1)
1117 | ;;
1118 | esac
1119 | }
1120 |
1121 | get_manga_json() {
1122 | case "$manga_provider" in
1123 | mangadex)
1124 | json_data=$(curl -s "https://api.mangadex.org/at-home/server/$chapter_id" | $sed "s/\\\//g")
1125 | if [ "$json_output" = true ]; then
1126 | printf "%s\n" "$json_data"
1127 | exit 0
1128 | fi
1129 | mangadex_data_base_url=$(printf "%s" "$json_data" | $sed -nE "s@.*\"baseUrl\":\"([^\"]*)\".*@\1@p")
1130 | mangadex_hash=$(printf "%s" "$json_data" | $sed -nE "s@.*\"hash\":\"([^\"]*)\".*@\1@p")
1131 | image_links=$(printf "%s" "$json_data" | $sed -nE "s@.*data\":\[(.*)\],.*@\1@p" | $sed "s/,/\n/g;s/\"//g")
1132 | if [ -z "$mangadex_data_base_url" ] || [ -z "$mangadex_hash" ] || [ -z "$image_links" ]; then
1133 | send_notification "Jerry" "3000" "" "Error: could not get manga"
1134 | exit 1
1135 | fi
1136 | download_images "$image_links"
1137 | ;;
1138 | esac
1139 | }
1140 |
1141 | #### MEDIA FUNCTIONS ####
1142 |
1143 | add_to_history() {
1144 | if [ -n "$percentage_progress" ] && [ "$percentage_progress" -gt 85 ]; then
1145 | if [ -z "$no_anilist" ]; then
1146 | response=$(update_progress "$((progress + 1))" "$media_id" "$status")
1147 | if printf "%s" "$response" | grep -q "errors"; then
1148 | send_notification "Error" "" "" "Could not update progress"
1149 | else
1150 | send_notification "Updated progress to $((progress + 1))/$episodes_total episodes watched" ""
1151 | [ -n "$history" ] && $sed -i "/^$media_id/d" "$history_file"
1152 | fi
1153 | else
1154 | if [ -n "$history" ]; then
1155 | if [ $((progress + 1)) -eq "$episodes_total" ]; then
1156 | $sed -i "/^$media_id/d" "$history_file"
1157 | send_notification "Completed" "" "" "$title"
1158 | else
1159 | $sed -i "s/^${media_id}\t[0-9/]*\t[0-9:]*/${media_id}\t$((progress + 2))\/${episodes_total}\t00:00:00/" "$history_file"
1160 | send_notification "Updated progress to $((progress + 1)) episodes watched"
1161 | fi
1162 | else
1163 | printf "%s\t%s/%s\t00:00:00\t%s\n" "$media_id" "$((progress + 2))" "$episodes_total" "$title" >>"$history_file"
1164 | send_notification "Updated progress to $((progress + 1)) episodes watched"
1165 | fi
1166 | fi
1167 | else
1168 | send_notification "Current progress" "" "" "$progress/$episodes_total episodes watched"
1169 | [ -z "$no_anilist" ] && send_notification "Your progress has not been updated"
1170 | if ! grep -q "^$media_id" "$history_file" 2>&1; then
1171 | printf "%s\t%s/%s\t%s\t%s\n" "$media_id" "$((progress + 1))" "$episodes_total" "$stopped_at" "$title" >>"$history_file"
1172 | else
1173 | $sed -i "s/^${media_id}\t[0-9/]*\t[0-9:]*/${media_id}\t$((progress + 1))\/${episodes_total}\t${stopped_at}/" "$history_file"
1174 | fi
1175 | send_notification "Stopped at: $stopped_at" "5000"
1176 | fi
1177 | }
1178 |
1179 | play_video() {
1180 | case "$provider" in
1181 | aniwatch) displayed_title="$title - Ep $((progress + 1)) $episode_title" ;;
1182 | yugen) displayed_title="$title - Ep $episode_title" ;;
1183 | hdrezka) displayed_title="$episode_title - Ep $((progress + 1))" ;;
1184 | aniworld) displayed_title="$episode_title" ;;
1185 | *) displayed_title="$title - Ep $((progress + 1))" ;;
1186 | esac
1187 | case $player in
1188 | mpv | mpv.exe)
1189 | if [ -f "$history_file" ] && [ -z "$using_number" ]; then
1190 | history=$(grep -E "^${media_id}[[:space:]]*$((progress + 1))" "$history_file")
1191 | elif [ -f "$history_file" ]; then
1192 | history=$(grep -E "^${media_id}[[:space:]]*[0-9/]*" "$history_file")
1193 | fi
1194 | [ -n "$history" ] && resume_from=$(printf "%s" "$history" | cut -f3)
1195 | opts="$player_arguments"
1196 | [ "$provider" = "allanime" ] && opts="$opts --http-header-fields-append=Referer:https://allanime.day/"
1197 | if [ -n "$resume_from" ]; then
1198 | opts="$opts --start=${resume_from}"
1199 | send_notification "Resuming from" "" "" "$resume_from"
1200 | fi
1201 | if [ -n "$subs_links" ]; then
1202 | send_notification "$title" "4000" "$images_cache_dir/$media_id.jpg" "$title"
1203 | if [ "$discord_presence" = "true" ]; then
1204 | eval "$presence_script_path" \"$player\" \"${title}\" \"${start_year}\" \"$((progress + 1))\" \"${video_link}\" \"${subs_links}\" ${opts} 2>&1 | tee $tmp_position
1205 | else
1206 | $player "$video_link" $opts "$subs_arg" "$subs_links" --force-media-title="$displayed_title" --msg-level=ffmpeg/demuxer=error 2>&1 | tee $tmp_position
1207 | fi
1208 | else
1209 | send_notification "$title" "4000" "$images_cache_dir/$media_id.jpg" "$title"
1210 | if [ "$discord_presence" = "true" ]; then
1211 | eval "$presence_script_path" \"$player\" \"${title}\" \"${start_year}\" \"$((progress + 1))\" \"${video_link}\" \"\" ${opts} 2>&1 | tee $tmp_position
1212 | else
1213 | $player "$video_link" $opts --force-media-title="$displayed_title" --msg-level=ffmpeg/demuxer=error 2>&1 | tee $tmp_position
1214 | fi
1215 | fi
1216 | stopped_at=$($sed -nE "s@.*AV: ([0-9:]*) / ([0-9:]*) \(([0-9]*)%\).*@\1@p" "$tmp_position" | tail -1)
1217 | percentage_progress=$($sed -nE "s@.*AV: ([0-9:]*) / ([0-9:]*) \(([0-9]*)%\).*@\3@p" "$tmp_position" | tail -1)
1218 | add_to_history
1219 | ;;
1220 | *yncpla*) syncplay "$video_link" -- --force-media-title="${title}" >/dev/null 2>&1 ;;
1221 | vlc) vlc --play-and-exit --meta-title="${title}" "$video_link" >/dev/null 2>&1 & ;;
1222 | iina) iina --no-stdin --keep-running --mpv-force-media-title="${title}" "$video_link" >/dev/null 2>&1 & ;;
1223 | esac
1224 | if [ "$player" != "mpv" ] && [ "$player" != "mpv.exe" ]; then
1225 | completed_episode=$(printf "Yes\nNo" | launcher "Have you completed watching this episode? [Y/n] ")
1226 | case "$completed_episode" in
1227 | [Yy]*)
1228 | percentage_progress=100
1229 | add_to_history
1230 | ;;
1231 | *) exit 0 ;;
1232 | esac
1233 | fi
1234 | }
1235 |
1236 | read_chapter() {
1237 | # zathura --mode fullscreen
1238 | case "$manga_format" in
1239 | pdf)
1240 | [ -f "$manga_dir/$title/chapter_$((progress + 1))/$title - Chapter $((progress + 1)).pdf" ] || convert_to_pdf
1241 | send_notification "Opening - $title" "1000" "$images_cache_dir/$media_id.jpg" "Chapter: $((progress + 1)) $chapter_title"
1242 | ${manga_opener} "$manga_dir/$title/chapter_$((progress + 1))/$title - Chapter $((progress + 1)).pdf"
1243 | ;;
1244 | image)
1245 | send_notification "Opening - $title" "1000" "$images_cache_dir/$media_id.jpg" "Chapter: $((progress + 1)) $chapter_title"
1246 | ${manga_opener} "$manga_dir/$title/chapter_$((progress + 1))"
1247 | ;;
1248 | esac
1249 | [ "$((progress + 1))" -eq "$chapters_total" ] && status="COMPLETED" || status="CURRENT"
1250 | completed_chapter=$(printf "Yes\nNo" | launcher "Do you want to update progress? [Y/n] ")
1251 | case "$completed_chapter" in
1252 | [Yy]*)
1253 | response=$(update_progress "$((progress + 1))" "$media_id" "$status")
1254 | if printf "%s" "$response" | grep -q "errors"; then
1255 | send_notification "Error" "" "" "Could not update progress"
1256 | else
1257 | send_notification "Updated progress to $((progress + 1))/$chapters_total chapters read" "" "$images_cache_dir/$media_id.jpg"
1258 | progress=$((progress + 1))
1259 | fi
1260 | ;;
1261 | [Nn]*)
1262 | send_notification "Your progress has not been updated"
1263 | ;;
1264 | *) exit 0 ;;
1265 | esac
1266 | }
1267 |
1268 | watch_anime() {
1269 | if [ "$sub_or_dub" = "dub" ] || [ "$sub_or_dub" = "sub" ]; then
1270 | translation_type="$sub_or_dub"
1271 | elif [ -z "$translation_type" ]; then
1272 | dub_choice=$(printf "Sub\nDub" | launcher "Would you like to watch Sub or Dub?")
1273 | case "$dub_choice" in
1274 | "Sub")
1275 | translation_type="sub"
1276 | ;;
1277 | "Dub")
1278 | translation_type="dub"
1279 | ;;
1280 | *)
1281 | send_notification "Error" "" "" "Invalid translation type"
1282 | exit 1
1283 | ;;
1284 | esac
1285 | fi
1286 |
1287 | get_episode_info
1288 |
1289 | if [ -z "$episode_info" ]; then
1290 | send_notification "Error" "" "" "$title not found"
1291 | exit 1
1292 | fi
1293 | if [ "$provider" != "aniworld" ]; then
1294 | episode_id=$(printf "%s" "$episode_info" | cut -f1)
1295 | episode_title=$(printf "%s" "$episode_info" | cut -f2)
1296 | [ "$provider" = "hdrezka" ] && media_type=$(printf "%s" "$episode_info" | cut -f3)
1297 | if [ "$episode_id" = "$episode_title" ]; then
1298 | episode_title=""
1299 | fi
1300 | fi
1301 |
1302 | get_json
1303 | [ -z "$video_link" ] && exit 1
1304 | play_video
1305 |
1306 | }
1307 |
1308 | read_manga() {
1309 |
1310 | get_chapter_info
1311 | if [ -z "$chapter_info" ]; then
1312 | send_notification "Error" "" "" "$title not found"
1313 | exit 1
1314 | fi
1315 | chapter_id=$(printf "%s" "$chapter_info" | cut -f1)
1316 | chapter_title=$(printf "%s" "$chapter_info" | cut -f2)
1317 |
1318 | get_manga_json
1319 | read_chapter
1320 |
1321 | }
1322 |
1323 | watch_anime_choice() {
1324 | if [ -z "$media_id" ] && [ -z "$no_anilist" ]; then
1325 | get_anime_from_list "CURRENT|REPEATING"
1326 | elif [ -z "$media_id" ]; then
1327 | [ -z "$query" ] && get_input "Search anime: "
1328 | [ -z "$query" ] && exit 1
1329 | search_anime_anilist "$query"
1330 | fi
1331 | if [ -z "$media_id" ] || [ -z "$title" ] || [ -z "$episodes_total" ]; then
1332 | send_notification "Jerry" "" "" "Error, no anime found"
1333 | exit 1
1334 | fi
1335 | send_notification "Loading" "3000" "$images_cache_dir/$media_id.jpg" "$title"
1336 | watch_anime
1337 | [ "$score_on_completion" = true ] && update_score "ANIME" "immediate"
1338 | }
1339 |
1340 | read_manga_choice() {
1341 | [ -z "$media_id" ] && get_manga_from_list "CURRENT|REPEATING"
1342 | [ -z "$chapters_total" ] && chapters_total="9999"
1343 | if [ -z "$media_id" ] || [ -z "$title" ] || [ -z "$progress" ]; then
1344 | send_notification "Jerry" "" "" "Error, no manga found"
1345 | exit 1
1346 | fi
1347 | send_notification "Loading" "" "$images_cache_dir/$media_id.jpg" "$title"
1348 | read_manga
1349 | [ "$score_on_completion" = true ] && update_score "MANGA" "immediate"
1350 | }
1351 |
1352 | binge() {
1353 | while :; do
1354 | if [ "$1" = "ANIME" ]; then
1355 | watch_anime_choice
1356 | [ -z "$percentage_progress" ] || [ "$percentage_progress" -lt 85 ] && break
1357 | [ $((progress + 1)) = "$episodes_total" ] && break
1358 | if [ $player != mpv ] && [ $player != mpv.exe ]; then
1359 | send_notification "Please only select Yes if you have finished watching the episode" "5000"
1360 | binge_watching=$(printf "Yes\nNo" | launcher "Do you want to keep binge watching? [Y/n] ")
1361 |
1362 | case $binge_watching in
1363 | [Nn]*) break ;;
1364 | esac
1365 | fi
1366 | progress=$((progress + 1))
1367 | resume_from=""
1368 | elif [ "$1" = "MANGA" ]; then
1369 | read_manga_choice
1370 | [ $((progress + 1)) = "$chapters_total" ] && break
1371 | case $completed_chapter in
1372 | [Nn]*) break ;;
1373 | esac
1374 | else
1375 | exit 1
1376 | fi
1377 | done
1378 | }
1379 |
1380 | main() {
1381 | if [ -z "$no_anilist" ]; then
1382 | check_credentials
1383 | if [ -z "$access_token" ] || [ -z "$user_id" ]; then
1384 | exit 1
1385 | fi
1386 | [ -n "$query" ] && mode_choice="Watch New Anime"
1387 | [ -z "$mode_choice" ] && mode_choice=$(printf "Watch Anime\nRead Manga\nUpdate (Episodes, Status, Score)\nInfo\nWatch New Anime\nRead New Manga" | launcher "Choose an option: ")
1388 | else
1389 | # TODO: implement manga stuff for no_anilist
1390 | [ -n "$query" ] && mode_choice="Watch Anime"
1391 | [ -z "$mode_choice" ] && mode_choice=$(printf "Resume from History\nWatch Anime" | launcher "Choose an option: ")
1392 | fi
1393 | case "$mode_choice" in
1394 | "Watch Anime") binge "ANIME" ;;
1395 | "Read Manga") binge "MANGA" ;;
1396 | "Update (Episodes, Status, Score)")
1397 | update_choice=$(printf "Change Episodes Watched\nChange Chapters Read\nChange Status\nChange Score" | launcher "Choose an option: ")
1398 | case "$update_choice" in
1399 | "Change Episodes Watched") update_episode_from_list ;;
1400 | "Change Chapters Read") update_chapter_from_list ;;
1401 | "Change Status")
1402 | media_type=$(printf "ANIME\nMANGA" | launcher "Choose a media type: ")
1403 | [ -z "$media_type" ] && exit 0
1404 | update_status "$media_type"
1405 | ;;
1406 | "Change Score")
1407 | media_type=$(printf "ANIME\nMANGA" | launcher "Choose a media type: ")
1408 | [ -z "$media_type" ] && exit 0
1409 | update_score "$media_type"
1410 | ;;
1411 | esac
1412 | ;;
1413 | # TODO: implement more info features
1414 | "Info")
1415 | if ! command -v "jq" >/dev/null; then
1416 | send_notification "For this feature to work, you must have jq installed."
1417 | exit 1
1418 | fi
1419 | media_type=$(printf "ANIME\nMANGA" | launcher "Choose a media type: ")
1420 | [ -z "$media_type" ] && exit 0
1421 | get_anilist_info "$media_type"
1422 | ;;
1423 | "Watch New Anime")
1424 | [ -z "$query" ] && get_input "Search anime: "
1425 | [ -z "$query" ] && exit 1
1426 | search_anime_anilist "$query"
1427 | [ -z "$progress" ] && progress=0
1428 | [ "$json_output" = true ] || send_notification "Disclaimer" "5000" "" "You need to complete the 1st episode to update your progress"
1429 | binge "ANIME"
1430 | ;;
1431 | "Read New Manga")
1432 | [ -z "$query" ] && get_input "Search manga: "
1433 | [ -z "$query" ] && exit 1
1434 | search_manga_anilist "$query"
1435 | [ -z "$progress" ] && progress=0
1436 | [ "$json_output" = true ] || send_notification "Disclaimer" "5000" "" "You need to complete the 1st chapter to update your progress"
1437 | binge "MANGA"
1438 | ;;
1439 | "Resume from History")
1440 | history_choice=$($sed -n "1h;1!{x;H;};\${g;p;}" "$history_file" | nl -w 1 | nth "Choose an entry: ")
1441 | media_id=$(printf "%s" "$history_choice" | cut -f1)
1442 | progress=$(printf "%s" "$history_choice" | cut -f2 | cut -d'/' -f1)
1443 | progress=$((progress - 1))
1444 | episodes_total=$(printf "%s" "$history_choice" | cut -f2 | cut -d'/' -f2)
1445 | resume_from=$(printf "%s" "$history_choice" | cut -f3)
1446 | title=$(printf "%s" "$history_choice" | cut -f4)
1447 | [ -z "$media_id" ] && exit 1
1448 | binge "ANIME"
1449 | ;;
1450 | esac
1451 | }
1452 |
1453 | configuration
1454 | query=""
1455 | # TODO: add an argument for video_providers
1456 | while [ $# -gt 0 ]; do
1457 | case "$1" in
1458 | --)
1459 | shift
1460 | query="$*"
1461 | break
1462 | ;;
1463 | -c | --continue) mode_choice="Watch Anime" && shift ;;
1464 | --clear-history | --delete-history)
1465 | while true; do
1466 | printf "This will delete your jerry history. Are you sure? [Y/n] "
1467 | read -r choice
1468 | case $choice in
1469 | [Yy]* | "")
1470 | #shellcheck disable=1090
1471 | [ -f "$config_file" ] && . "$config_file"
1472 | [ -z "$history_file" ] && history_file="$HOME/.local/share/jerry/jerry_history.txt"
1473 | rm "$history_file"
1474 | echo "History deleted."
1475 | exit 0
1476 | ;;
1477 | [Nn]*)
1478 | return 1
1479 | ;;
1480 | *) echo "Please answer yes or no." ;;
1481 | esac
1482 | done
1483 | shift
1484 | ;;
1485 | -d | --discord) discord_presence=true && shift ;;
1486 | --dub) sub_or_dub="dub" && shift ;;
1487 | -e | --edit) edit_configuration ;;
1488 | -h | --help)
1489 | usage && exit 0
1490 | ;;
1491 | -i | --image-preview)
1492 | image_preview=true
1493 | shift
1494 | ;;
1495 | -j | --json)
1496 | json_output=true
1497 | no_anilist=1
1498 | shift
1499 | ;;
1500 | -l | --language)
1501 | subs_language="$2"
1502 | if [ -z "$subs_language" ]; then
1503 | subs_language="english"
1504 | shift
1505 | else
1506 | if [ "${subs_language#-}" != "$subs_language" ]; then
1507 | subs_language="english"
1508 | shift
1509 | else
1510 | subs_language="$(echo "$subs_language" | cut -c2-)"
1511 | shift 2
1512 | fi
1513 | fi
1514 | ;;
1515 | -n | --number)
1516 | progress=$(($2 - 1))
1517 | using_number=1
1518 | shift 2
1519 | ;;
1520 | --no-anilist) no_anilist=1 && shift ;;
1521 | --rofi | --dmenu | --external-menu)
1522 | use_external_menu=true
1523 | shift
1524 | ;;
1525 | -q | --quality)
1526 | quality="$2"
1527 | if [ -z "$quality" ]; then
1528 | quality="1080"
1529 | shift
1530 | else
1531 | if [ "${quality#-}" != "$quality" ]; then
1532 | quality="1080"
1533 | shift
1534 | else
1535 | shift 2
1536 | fi
1537 | fi
1538 | ;;
1539 | -s | --syncplay)
1540 | player="syncplay"
1541 | shift
1542 | ;;
1543 | -u | -U | --update)
1544 | update_script
1545 | ;;
1546 | -v | -V | --version)
1547 | send_notification "Jerry Version: $JERRY_VERSION"
1548 | exit 0
1549 | ;;
1550 | --vlc)
1551 | player="vlc"
1552 | shift
1553 | ;;
1554 | -w | --website)
1555 | provider="$2"
1556 | if [ -z "$provider" ]; then
1557 | provider="allanime"
1558 | shift
1559 | else
1560 | if [ "${provider#-}" != "$provider" ]; then
1561 | provider="allanime"
1562 | shift
1563 | else
1564 | shift 2
1565 | fi
1566 | fi
1567 | ;;
1568 | *)
1569 | if [ "${1#-}" != "$1" ]; then
1570 | query="$query $1"
1571 | else
1572 | query="$query $1"
1573 | fi
1574 | shift
1575 | ;;
1576 | esac
1577 | done
1578 | # check for update
1579 | # check_update "A new update is out. Would you like to update jerry? [Y/n] "
1580 | query="$(printf "%s" "$query" | tr ' ' '-' | $sed "s/^-//g")"
1581 | case "$provider" in
1582 | allanime)
1583 | provider="allanime"
1584 | allanime_refr="https://allanime.to"
1585 | allanime_base="allanime.day"
1586 | ;;
1587 | zoro | kaido | aniwatch) provider="aniwatch" ;;
1588 | yugen | yugenanime) provider="yugen" ;;
1589 | hdrezka | rezka) provider="hdrezka" ;;
1590 | crunchyroll | cr) provider="crunchyroll" ;;
1591 | aniworld) provider="aniworld" ;;
1592 | *) send_notification "Invalid provider" && exit 1 ;;
1593 | esac
1594 | if [ "$image_preview" = true ]; then
1595 | test -d "$images_cache_dir" || mkdir -p "$images_cache_dir"
1596 | if [ "$use_external_menu" = true ]; then
1597 | mkdir -p "/tmp/jerry/applications/"
1598 | [ ! -L "$applications" ] && ln -sf "/tmp/jerry/applications/" "$applications"
1599 | fi
1600 | fi
1601 |
1602 | main
1603 |
--------------------------------------------------------------------------------
/jerrydiscordpresence.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import subprocess
3 | import sys
4 | import re
5 | import os
6 |
7 | import httpx
8 | from pypresence import Presence
9 |
10 | CLIENT_ID = "1084791136981352558"
11 | ENDPONT = "https://kitsu.io/api/"
12 |
13 | rpc_client = Presence(CLIENT_ID)
14 | rpc_client.connect()
15 |
16 | http_client = httpx.Client(base_url=ENDPONT)
17 |
18 | (
19 | _,
20 | mpv_executable,
21 | anime_name,
22 | release_year,
23 | episode_count,
24 | content_stream,
25 | subtitle_stream,
26 | *opts,
27 | ) = sys.argv
28 |
29 |
30 | anime = http_client.get("edge/anime", params={"filter[text]": anime_name, "filter[year]": f"{release_year}..{release_year}"}).json()[
31 | "data"
32 | ]
33 |
34 | if not anime:
35 | raise SystemExit()
36 |
37 | media = anime[0]["attributes"]
38 | media_title = "%s %s" % (media["canonicalTitle"], "- Episode "+episode_count)
39 |
40 | if subtitle_stream != "":
41 | args = [
42 | mpv_executable,
43 | content_stream,
44 | f"--force-media-title={media_title}",
45 | f"--sub-files={subtitle_stream}",
46 | "--msg-level=ffmpeg/demuxer=error",
47 | ] + opts
48 | else:
49 | args = [
50 | mpv_executable,
51 | content_stream,
52 | f"--force-media-title={media_title}",
53 | "--msg-level=ffmpeg/demuxer=error",
54 | ] + opts
55 |
56 |
57 | process = subprocess.Popen(
58 | args
59 | )
60 |
61 | if sys.platform == 'win32':
62 | file_path = os.path.join(os.environ['LocalAppData'], 'Temp', 'jerry_position')
63 | else:
64 | file_path = '/tmp/jerry_position'
65 |
66 | while True:
67 | with open(file_path, 'r') as file:
68 | content = file.read()
69 | pattern = r'(\(Paused\)\s)?AV:\s([0-9:]*) / ([0-9:]*) \(([0-9]*)%\)'
70 | matches = re.findall(pattern, content)
71 | small_image = "https://images-ext-1.discordapp.net/external/dUSRf56flwFeOMFjafsUhIMMS_1Xs-ptjeDHo6TWn6c/%3Fquality%3Dlossless%26size%3D48/https/cdn.discordapp.com/emojis/1138835294506975262.png"
72 | if matches:
73 | if matches[-1][0] == "(Paused) ":
74 | elapsed = matches[-1][1]
75 | else:
76 | elapsed = matches[-1][1]
77 | duration = matches[-1][2]
78 | position = f"{elapsed} / {duration}"
79 | else:
80 | position = "00:00:00"
81 |
82 | rpc_client.update(
83 | details=media_title,
84 | state=position,
85 | large_image=media["posterImage"]["original"],
86 | large_text=media_title,
87 | small_image=small_image,
88 | small_text=f"Episode {episode_count}"
89 | )
90 |
91 | if process.poll() is not None:
92 | break
93 |
94 | process.wait()
95 |
--------------------------------------------------------------------------------
/nix/hm-module.nix:
--------------------------------------------------------------------------------
1 | self: {
2 | lib,
3 | pkgs,
4 | config,
5 | ...
6 | }: let
7 | inherit (lib) mkEnableOption mkPackageOption mkOption types mkIf literalExpression;
8 |
9 | packages = self.packages.${pkgs.stdenv.hostPlatform.system};
10 |
11 | cfg = config.programs.jerry;
12 | in {
13 | options = {
14 | programs.jerry = {
15 | enable = mkEnableOption "jerry";
16 | package = mkPackageOption packages "jerry" {
17 | default = "default";
18 | pkgsText = "jerry.packages.\${pkgs.stdenv.hostPlatform.system}";
19 | };
20 |
21 | config = mkOption {
22 | type = types.attrs;
23 | default = {};
24 | description = ''
25 | Configuration written to `$XDG_CONFIG_HOME/jerry/jerry.conf`.
26 | Booleans have to be passed as literal strings, e.g.: "true" or "false"
27 |
28 | See for the full list of options.
29 | '';
30 | example = literalExpression ''
31 | {
32 | provider = "yugen";
33 | score_on_completion = "true";
34 | }
35 | '';
36 | };
37 | };
38 | };
39 |
40 | config = mkIf cfg.enable {
41 | home.packages = [cfg.package];
42 |
43 | xdg.configFile."jerry/jerry.conf".text = mkIf (cfg.config != {}) (lib.toShellVars cfg.config);
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/nix/package.nix:
--------------------------------------------------------------------------------
1 | {
2 | coreutils,
3 | curl,
4 | ffmpeg,
5 | fzf,
6 | gnugrep,
7 | gnupatch,
8 | gnused,
9 | html-xml-utils,
10 | lib,
11 | makeWrapper,
12 | mpv,
13 | openssl,
14 | stdenvNoCC,
15 | testers,
16 | rofi,
17 | chafa,
18 | jq,
19 | withRofi ? false,
20 | imagePreviewSupport ? false,
21 | infoSupport ? false,
22 | }:
23 | stdenvNoCC.mkDerivation (finalAttrs: {
24 | pname = "jerry";
25 | version = "1.9.9";
26 |
27 | src = builtins.path {
28 | name = "${finalAttrs.pname}-source";
29 | path = lib.fileset.toSource {
30 | root = ../.;
31 | fileset = lib.fileset.unions [
32 | ../jerry.sh
33 | ../jerrydiscordpresence.py
34 | ];
35 | };
36 | };
37 |
38 | nativeBuildInputs = [makeWrapper];
39 | runtimeInputs =
40 | [
41 | coreutils # wc
42 | curl
43 | ffmpeg
44 | fzf
45 | gnugrep
46 | gnupatch
47 | gnused
48 | html-xml-utils
49 | mpv
50 | openssl
51 | ]
52 | ++ lib.optional withRofi rofi
53 | ++ lib.optional imagePreviewSupport chafa
54 | ++ lib.optional infoSupport jq;
55 |
56 | installPhase = ''
57 | runHook preInstall
58 |
59 | mkdir -p $out/bin
60 | install -Dm 755 jerry.sh $out/bin/jerry
61 | wrapProgram $out/bin/jerry --prefix PATH : ${lib.makeBinPath finalAttrs.runtimeInputs}
62 |
63 | runHook postInstall
64 | '';
65 |
66 | passthru.tests.version = testers.testVersion {
67 | package = finalAttrs.finalPackage;
68 | };
69 |
70 | meta = with lib; {
71 | description = "Watch anime with automatic anilist syncing and other cool stuff";
72 | homepage = "https://github.com/justchokingaround/jerry";
73 | license = licenses.gpl3;
74 | maintainers = with maintainers; [justchokingaround diniamo];
75 | platforms = platforms.unix;
76 | mainProgram = "jerry";
77 | };
78 | })
79 |
--------------------------------------------------------------------------------