#!/bin/bash # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' NC='\033[0m' # No Color PLUGIN_DIR="./App/config/plugins" CACHE_BASE_DIR="./plugin_manifests" # More persistent location TEMP_DIR="/tmp/jellyfin_plugins" MANIFEST_CACHE_DIR="${CACHE_BASE_DIR}/$(date +%Y.%m.%d)" # Known repositories array declare -A REPOSITORIES=( ["Official"]="https://sfo1.mirror.jellyfin.org/files/plugin/manifest.json" ["9p4's SSO"]="https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json" ["Ani-Sync"]="https://raw.githubusercontent.com/vosmiic/jellyfin-ani-sync/master/manifest.json" ["danieladov"]="https://raw.githubusercontent.com/danieladov/JellyfinPluginManifest/master/manifest.json" ["dkanada"]="https://raw.githubusercontent.com/dkanada/jellyfin-plugin-intros/master/manifest.json" ["k-matti"]="https://raw.githubusercontent.com/k-matti/jellyfin-plugin-repository/master/manifest.json" ["Linfor"]="https://raw.githubusercontent.com/LinFor/jellyfin-plugin-kinopoisk/master/dist/manifest.json" ["LizardByte"]="https://app.lizardbyte.dev/jellyfin-plugin-repo/manifest.json" ["ShokoAnime"]="https://raw.githubusercontent.com/ShokoAnime/Shokofin/metadata/stable/manifest.json" ["TubeArchivist"]="https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/manifest.json" ["Streamyfin"]="https://raw.githubusercontent.com/streamyfin/jellyfin-plugin-streamyfin/main/manifest.json" ["jellyscrub"]="https://raw.githubusercontent.com/nicknsy/jellyscrub/main/manifest.json" # ["Jellyfin Unstable"]="https://repo.jellyfin.org/files/plugin-unstable/manifest.json" ) # Function to show usage show_usage() { echo -e "${BLUE}Jellyfin Plugin Installer${NC}" echo "Usage:" echo " $0 search - Search known repositories for plugins" echo " $0 install - Install a plugin by name, GUID, or URL" echo " $0 info - Show detailed information about a specific plugin" echo " $0 versions - Show latest 10 versions of a plugin" echo " $0 repos - List known repositories" echo " $0 update - Update plugin manifests" echo " $0 upgrade - Check and upgrade installed plugins" echo " $0 catalog - List plugins from a manifest" echo echo "Example manifests:" echo " https://raw.githubusercontent.com/nicknsy/jellyscrub/main/manifest.json" echo " https://raw.githubusercontent.com/danieladov/JellyfinPluginManifest/master/manifest.json" } # Function to list repositories list_repos() { echo -e "${BLUE}Known Repositories:${NC}" for repo in "${!REPOSITORIES[@]}"; do echo -e "${GREEN}$repo${NC}" echo " ${REPOSITORIES[$repo]}" done } # Function to add new function to handle manifest caching fetch_manifests() { local force=$1 local today=$(date +%Y.%m.%d) # Create base cache directory if it doesn't exist mkdir -p "$CACHE_BASE_DIR" # Check if we should use cached manifests if [ "$force" != "true" ] && [ -d "$MANIFEST_CACHE_DIR" ]; then echo -e "${BLUE}Using cached manifests from ${MANIFEST_CACHE_DIR}${NC}" >&2 return 0 fi # Cleanup old manifest directories (keep last 7 days) find "$CACHE_BASE_DIR" -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \; # Create fresh cache directory mkdir -p "$MANIFEST_CACHE_DIR" echo -e "${BLUE}Downloading manifests to ${MANIFEST_CACHE_DIR}${NC}" >&2 # Download all manifests with dynamic status line local total=${#REPOSITORIES[@]} local current=0 for repo in "${!REPOSITORIES[@]}"; do ((current++)) printf "\rFetching manifests: [%-50s] %d/%d - %s" \ "$(printf '#%.0s' $(seq 1 $((current * 50 / total))))" \ "$current" "$total" "$repo" >&2 # Download with error checking if ! curl -s -f "${REPOSITORIES[$repo]}" > "${MANIFEST_CACHE_DIR}/${repo}.json.tmp"; then continue fi # Validate JSON and move to final location if valid if jq empty "${MANIFEST_CACHE_DIR}/${repo}.json.tmp" 2>/dev/null; then mv "${MANIFEST_CACHE_DIR}/${repo}.json.tmp" "${MANIFEST_CACHE_DIR}/${repo}.json" else rm "${MANIFEST_CACHE_DIR}/${repo}.json.tmp" fi done printf "\n" >&2 } # Add spinner function at the top with other functions spinner() { local pid=$1 local delay=0.1 local spinstr='|/-\' while ps -p $pid > /dev/null; do local temp=${spinstr#?} printf "\r%c" "$spinstr" local spinstr=$temp${spinstr%"$temp"} sleep $delay done printf "\r" } # Function to search repositories search_repos() { local search_term="$1" local force=$2 local search_term_lower=$(echo "$search_term" | tr '[:upper:]' '[:lower:]') local found=0 # Fetch/update manifests fetch_manifests "$force" # Print header printf "${BLUE}%-30s %-20s %-15s %-20s %-36s${NC}\n" "Name" "Version" "ABI" "Source" "GUID" printf "%.s-" {1..121} # Increased for GUID column printf "\n" # Create temp file for results local results_file=$(mktemp) # Run search in background and show spinner ( # Search through cached manifests for repo in "${!REPOSITORIES[@]}"; do local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json" [ ! -f "$manifest_file" ] && continue while read -r line; do if [ -n "$line" ]; then echo "$line" found=1 fi done < <(jq -r --arg term "$search_term_lower" ' # Convert input to array if not already if type == "array" then . else [.] end | .[] | # Get all searchable fields and ensure guid is included { name: (.name // ""), description: (.description // ""), overview: (.overview // ""), category: (.category // ""), owner: (.owner // ""), guid: (.guid // "unknown"), # Ensure GUID has a default value versions: (.versions // []) } | # Convert all fields to lowercase for searching select( (.name | ascii_downcase | contains($term)) or (.description | ascii_downcase | contains($term)) or (.overview | ascii_downcase | contains($term)) or (.category | ascii_downcase | contains($term)) or (.owner | ascii_downcase | contains($term)) ) | # Get latest version info . as $plugin | ($plugin.versions | sort_by(.timestamp) | last) as $latest | # Format output with guid "\($plugin.name)|\($latest.version)|\($latest.targetAbi)|\("'"$repo"'")|\($plugin.guid)" ' "$manifest_file" 2>/dev/null | while IFS='|' read -r name version abi repo guid; do printf "%-30s %-20s %-15s %-20s %-36s\n" "$name" "$version" "$abi" "$repo" "$guid" >> "$results_file" done) done ) & spinner $! # Print results if [ -s "$results_file" ]; then cat "$results_file" echo "" else echo -e "\n${RED}No plugins found matching: $search_term${NC}" fi # Cleanup rm -f "$results_file" } # Function to list plugins from a manifest list_catalog() { local manifest_url="$1" echo -e "${BLUE}Fetching plugin catalog from: ${manifest_url}${NC}" # Create temp directory if it doesn't exist mkdir -p "${TEMP_DIR}" # Download and parse manifest if ! curl -s "${manifest_url}" > "${TEMP_DIR}/manifest.json"; then echo -e "${RED}Failed to download manifest${NC}" exit 1 fi # Use jq to parse the manifest if available, otherwise use grep/sed if command -v jq >/dev/null 2>&1; then echo -e "\n${GREEN}Available Plugins:${NC}" jq -r '.[] | "Name: \(.name)\nDescription: \(.description)\nVersions:\n\(.versions[] | " Version: \(.version)\n Download URL: \(.sourceUrl)\n Checksum: \(.checksum)\n Target ABI: \(.targetAbi)\n")---"' "${TEMP_DIR}/manifest.json" else echo -e "${RED}jq not found, showing raw manifest:${NC}" cat "${TEMP_DIR}/manifest.json" fi } # Function to find plugin by name or GUID find_plugin() { local search_term="$1" local results_file=$(mktemp) local matches=0 for repo in "${!REPOSITORIES[@]}"; do local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json" [ ! -f "$manifest_file" ] && continue jq -r --arg term "$search_term" ' if type == "array" then . else [.] end | .[] | select( (.name == $term) or (.guid == $term) ) | . as $plugin | ($plugin.versions | sort_by(.timestamp) | last) as $latest | { name: $plugin.name, guid: $plugin.guid, version: $latest.version, sourceUrl: $latest.sourceUrl, checksum: $latest.checksum, repo: "'"$repo"'" } | @json ' "$manifest_file" 2>/dev/null >> "$results_file" done # Count unique matches matches=$(jq -s 'length' "$results_file") if [ "$matches" -eq 0 ]; then echo -e "${RED}No plugin found matching: $search_term${NC}" >&2 rm -f "$results_file" return 1 elif [ "$matches" -gt 1 ]; then echo -e "${RED}Multiple plugins found matching: $search_term${NC}" >&2 echo "Please use the GUID to specify which plugin to install:" >&2 jq -r '.name + " (" + .guid + ") from " + .repo' "$results_file" >&2 rm -f "$results_file" return 1 else cat "$results_file" rm -f "$results_file" return 0 fi } # Function to find checksum for a URL find_checksum_for_url() { local search_url="$1" local results_file=$(mktemp) local found=false for repo in "${!REPOSITORIES[@]}"; do local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json" [ ! -f "$manifest_file" ] && continue if jq -e --arg url "$search_url" ' if type == "array" then . else [.] end | .[] | (.versions[] | select(.sourceUrl == $url)) | .checksum ' "$manifest_file" 2>/dev/null > "$results_file"; then found=true break fi done if [ "$found" = true ]; then cat "$results_file" rm -f "$results_file" return 0 else rm -f "$results_file" return 1 fi } # Update verify_checksum function verify_checksum() { local file="$1" local expected_checksum="$2" # Get directory and filename separately local dir=$(dirname "$file") local filename=$(basename "$file") # Determine hash length to choose verification method local checksum_length=${#expected_checksum} case $checksum_length in 32) # MD5 if ! (cd "$dir" && echo "$expected_checksum $filename" | md5sum -c --status); then return 1 fi ;; 40) # SHA1 if ! (cd "$dir" && echo "$expected_checksum $filename" | sha1sum -c --status); then return 1 fi ;; 64) # SHA256 if ! (cd "$dir" && echo "$expected_checksum $filename" | sha256sum -c --status); then return 1 fi ;; 128) # SHA512 if ! (cd "$dir" && echo "$expected_checksum $filename" | sha512sum -c --status); then return 1 fi ;; *) echo -e "${YELLOW}Warning: Unknown checksum format, skipping verification${NC}" return 0 ;; esac return 0 } # Function to download plugin image download_plugin_image() { local image_url="$1" local plugin_dir="$2" local plugin_name="$3" if [ -n "$image_url" ]; then # Get file extension from URL local ext=$(echo "$image_url" | grep -oP '\.[^.]+$') if [ -z "$ext" ]; then ext=".png" # Default to .png if no extension found fi # Download image if curl -s -f -L "$image_url" -o "${plugin_dir}/${plugin_name}${ext}"; then echo -e "${GREEN}Downloaded plugin image${NC}" else echo -e "${YELLOW}Failed to download plugin image${NC}" fi fi } # Function to generate meta.json generate_meta_json() { local plugin_name="$1" local plugin_version="$2" local plugin_guid="$3" local plugin_description="$4" local plugin_overview="$5" local plugin_owner="$6" local plugin_category="$7" local plugin_timestamp="$8" local plugin_abi="$9" local plugin_changelog="${10}" local plugin_dir="${11}" local plugin_image_url="${12}" # Start building JSON content local json_content='{ "category": "'"${plugin_category}"'", "changelog": "'"${plugin_changelog}"'", "description": "'"${plugin_description}"'", "guid": "'"${plugin_guid}"'", "name": "'"${plugin_name}"'", "overview": "'"${plugin_overview}"'", "owner": "'"${plugin_owner}"'", "targetAbi": "'"${plugin_abi}"'", "timestamp": "'"${plugin_timestamp}"'", "version": "'"${plugin_version}"'", "status": "Active", "autoUpdate": true' # Add imagePath only if image URL exists if [ -n "$plugin_image_url" ]; then local ext=$(echo "$plugin_image_url" | grep -oP '\.[^.]+$') if [ -z "$ext" ]; then ext=".png" fi json_content+=', "imagePath": "/config/plugins/'"${plugin_name}_${plugin_version}/${plugin_name}_${plugin_version}${ext}"'"' fi # Close the JSON json_content+=', "assemblies": [] }' # Write to file echo "$json_content" > "${plugin_dir}/meta.json" } # Function to find plugin info from URL in manifests find_plugin_info_from_url() { local url="$1" local results_file=$(mktemp) local found=false # Search through cached manifests for manifest_file in "${MANIFEST_CACHE_DIR}"/*.json; do if [[ ! -f "$manifest_file" ]]; then continue fi # Try to find plugin info by matching the URL if jq -r --arg url "$url" ' if type == "array" then . else [.] end | .[] | . as $plugin | (.versions[] | select(.sourceUrl == $url)) as $version | if $version then { name: $plugin.name, guid: $plugin.guid, description: ($plugin.description // ""), overview: ($plugin.overview // ""), owner: ($plugin.owner // ""), category: ($plugin.category // ""), imageUrl: ($plugin.imageUrl // ""), version: $version.version, timestamp: $version.timestamp, targetAbi: $version.targetAbi, changelog: ($version.changelog // ""), checksum: $version.checksum } else empty end ' "$manifest_file" > "$results_file" 2>/dev/null; then if [ -s "$results_file" ]; then found=true break fi fi done if [ "$found" = true ]; then cat "$results_file" rm -f "$results_file" return 0 fi rm -f "$results_file" return 1 } # Update install_from_url function install_from_url() { local plugin_url="$1" local checksum="$2" local plugin_filename=$(basename "${plugin_url}") local plugin_info local plugin_name local plugin_version # Try to find plugin info in manifests if plugin_info=$(find_plugin_info_from_url "$plugin_url"); then # Use info from manifest plugin_name=$(echo "$plugin_info" | jq -r '.name') plugin_version=$(echo "$plugin_info" | jq -r '.version') echo -e "${GREEN}Found plugin information in manifests${NC}" # Create plugin directory with correct naming convention local plugin_dir="${PLUGIN_DIR}/${plugin_name}_${plugin_version}" mkdir -p "$plugin_dir" # Download and install plugin echo -e "${BLUE}Downloading plugin...${NC}" if ! curl -L -o "${TEMP_DIR}/${plugin_filename}" "${plugin_url}"; then echo -e "${RED}Failed to download plugin${NC}" exit 1 fi # Verify checksum echo -e "${BLUE}Verifying checksum...${NC}" if ! verify_checksum "${TEMP_DIR}/${plugin_filename}" "$(echo "$plugin_info" | jq -r '.checksum')"; then echo -e "${RED}Checksum verification failed${NC}" exit 1 fi echo -e "${GREEN}Checksum verified${NC}" # Extract plugin echo -e "${BLUE}Installing plugin...${NC}" if [[ "${plugin_filename}" == *.zip ]]; then if ! unzip -o "${TEMP_DIR}/${plugin_filename}" -d "$plugin_dir"; then echo -e "${RED}Failed to extract plugin${NC}" exit 1 fi else cp "${TEMP_DIR}/${plugin_filename}" "$plugin_dir/" fi # Generate meta.json with manifest info generate_meta_json \ "$(echo "$plugin_info" | jq -r '.name')" \ "$(echo "$plugin_info" | jq -r '.version')" \ "$(echo "$plugin_info" | jq -r '.guid')" \ "$(echo "$plugin_info" | jq -r '.description')" \ "$(echo "$plugin_info" | jq -r '.overview')" \ "$(echo "$plugin_info" | jq -r '.owner')" \ "$(echo "$plugin_info" | jq -r '.category')" \ "$(echo "$plugin_info" | jq -r '.timestamp')" \ "$(echo "$plugin_info" | jq -r '.targetAbi')" \ "$(echo "$plugin_info" | jq -r '.changelog')" \ "$plugin_dir" \ "$(echo "$plugin_info" | jq -r '.imageUrl')" # Download image if available local image_url=$(echo "$plugin_info" | jq -r '.imageUrl') if [ -n "$image_url" ] && [ "$image_url" != "null" ]; then download_plugin_image "$image_url" "$plugin_dir" "${plugin_name}_${plugin_version}" fi else # No manifest info found - try to extract name and version from filename echo -e "${YELLOW}No manifest information found for this plugin${NC}" # Try to get name and version from filename plugin_name=$(echo "${plugin_filename%.*}" | sed -E 's/[-_][0-9].*//' | sed 's/\.zip$//') plugin_version=$(echo "${plugin_filename}" | grep -oP '[\d.]+(?=\.zip)' || echo "1.0.0.0") if [ -z "$plugin_version" ]; then plugin_version="1.0.0.0" fi local plugin_dir="${PLUGIN_DIR}/${plugin_name}_${plugin_version}" mkdir -p "$plugin_dir" # Download and install echo -e "${BLUE}Downloading plugin...${NC}" if ! curl -L -o "${TEMP_DIR}/${plugin_filename}" "${plugin_url}"; then echo -e "${RED}Failed to download plugin${NC}" exit 1 fi # Extract plugin echo -e "${BLUE}Installing plugin...${NC}" if [[ "${plugin_filename}" == *.zip ]]; then if ! unzip -o "${TEMP_DIR}/${plugin_filename}" -d "$plugin_dir"; then echo -e "${RED}Failed to extract plugin${NC}" exit 1 fi else cp "${TEMP_DIR}/${plugin_filename}" "$plugin_dir/" fi # Generate basic meta.json generate_meta_json \ "$plugin_name" \ "$plugin_version" \ "" \ "" \ "" \ "Unknown" \ "General" \ "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ "" \ "" \ "$plugin_dir" \ "" fi # Set correct permissions if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then chown -R ${PUID}:${PGID} "$plugin_dir" fi # Cleanup rm -f "${TEMP_DIR}/${plugin_filename}" echo -e "${GREEN}Plugin installed successfully${NC}" echo "Restart Jellyfin to complete installation:" echo "docker compose restart jellyfin" } # Update show_plugin_info function to include current version show_plugin_info() { local search_term="$1" local found=0 local found_repo="" local results_file=$(mktemp) # Fetch manifests if needed fetch_manifests "false" # Search through cached manifests for repo in "${!REPOSITORIES[@]}"; do local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json" [ ! -f "$manifest_file" ] && continue # Search by name or GUID jq -r --arg term "$search_term" ' # Convert input to array if not already if type == "array" then . else [.] end | .[] | select( (.name == $term) or (.guid == $term) ) | . as $plugin | ($plugin.versions | sort_by(.timestamp) | last) as $latest | { name: $plugin.name, guid: $plugin.guid, description: ($plugin.description // "No description available"), overview: ($plugin.overview // "No overview available"), owner: ($plugin.owner // "Unknown"), category: ($plugin.category // "Unknown"), versions: ($plugin.versions | length | tostring + " version(s) available"), current: $latest.version } | "Name: \(.name)\n" + "GUID: \(.guid)\n" + "Owner: \(.owner)\n" + "Category: \(.category)\n" + "Description: \(.description)\n" + "Overview: \(.overview)\n" + "Versions: \(.versions)\n" + "Current: \(.current)" ' "$manifest_file" 2>/dev/null > "$results_file" if [ -s "$results_file" ]; then found=1 found_repo="$repo" break fi done if [ $found -eq 1 ]; then echo -e "${BLUE}Plugin Information:${NC}" echo "----------------------------------------" cat "$results_file" echo -e "Repository: ${GREEN}${found_repo}${NC}" echo "----------------------------------------" else echo -e "${RED}No plugin found matching: $search_term${NC}" fi rm -f "$results_file" } # Add show_plugin_versions function show_plugin_versions() { local search_term="$1" local found=0 local results_file=$(mktemp) # Fetch manifests if needed fetch_manifests "false" # Search through cached manifests for repo in "${!REPOSITORIES[@]}"; do local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json" [ ! -f "$manifest_file" ] && continue # Search by name or GUID and get version info jq -r --arg term "$search_term" ' # Convert input to array if not already if type == "array" then . else [.] end | .[] | select( (.name == $term) or (.guid == $term) ) | { name: .name, versions: (.versions | sort_by(.timestamp) | reverse | .[0:10]) } | "Plugin: \(.name)\n" + "----------------------------------------\n" + (.versions[] | "Timestamp: \(.timestamp)\n" + "Version: \(.version)\n" + "Target ABI: \(.targetAbi)\n" + "Changelog: \(.changelog // "No changelog available")\n" + "Download URL: \(.sourceUrl)\n" + "----------------------------------------\n" ) ' "$manifest_file" 2>/dev/null > "$results_file" if [ -s "$results_file" ]; then found=1 break fi done if [ $found -eq 1 ]; then echo -e "${BLUE}Latest 10 Versions:${NC}" cat "$results_file" else echo -e "${RED}No plugin found matching: $search_term${NC}" fi rm -f "$results_file" } # Add update_manifests function update_manifests() { echo -e "${BLUE}Updating plugin manifests...${NC}" fetch_manifests "true" # Force refresh echo -e "${GREEN}Manifests updated successfully${NC}" } # Function to remove old plugin version remove_old_plugin() { local plugin_name="$1" local new_version="$2" local current_version="$3" if [ -n "$current_version" ]; then local old_dir="$PLUGIN_DIR/${plugin_name}_${current_version}" if [ -d "$old_dir" ] && [ "$current_version" != "$new_version" ]; then echo -e "${BLUE}Removing old version: $current_version${NC}" rm -rf "$old_dir" fi fi } # Update install_plugin function to handle cleanup install_plugin() { local input="$1" local force="$2" local current_version="$3" # Add parameter for current version local temp_dir="/tmp/jellyfin_plugins" # Create temp directory mkdir -p "$temp_dir" # Check if input is a URL if [[ "$input" =~ ^https?:// ]]; then echo -e "${BLUE}Installing from URL: $input${NC}" # Try to find checksum for this URL local checksum if checksum=$(find_checksum_for_url "$input"); then echo -e "${GREEN}Found matching checksum in manifests${NC}" else echo -e "${YELLOW}Warning: No checksum found for this URL${NC}" read -p "Continue without checksum verification? (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo -e "${RED}Installation cancelled${NC}" exit 1 fi fi install_from_url "$input" "$checksum" return fi # If not a URL, treat as GUID or name # Fetch/update manifests fetch_manifests "$force" # Find plugin in manifests local found=false local plugin_name="" local plugin_version="" local plugin_url="" local plugin_checksum="" local plugin_repo="" local plugin_guid="" local plugin_description="" local plugin_overview="" local plugin_owner="" local plugin_category="" local plugin_timestamp="" local plugin_abi="" local plugin_changelog="" local plugin_image_url="" for manifest_file in "${MANIFEST_CACHE_DIR}"/*.json; do if [[ ! -f "$manifest_file" ]]; then continue fi # Search for plugin by GUID or name while IFS='|' read -r name version url checksum repo guid description overview owner category timestamp abi changelog; do if [[ "$input" == "$guid" ]] || [[ "$input" == "$name" ]]; then found=true plugin_name="$name" plugin_version="$version" plugin_url="$url" plugin_checksum="$checksum" plugin_repo="$repo" plugin_guid="$guid" plugin_description="$description" plugin_overview="$overview" plugin_owner="$owner" plugin_category="$category" plugin_timestamp="$timestamp" plugin_abi="$abi" plugin_changelog="$changelog" plugin_image_url="$(jq -r --arg guid "$guid" '.[] | select(.guid == $guid) | .imageUrl // ""' "$manifest_file")" break 2 fi done < <(jq -r --arg input "$input" ' .[] | select(.guid == $input or .name == $input) | . as $plugin | ($plugin.versions | sort_by(.timestamp) | last) as $latest | [ $plugin.name, $latest.version, $latest.sourceUrl, $latest.checksum, .owner, .guid, (.description // ""), (.overview // ""), (.owner // ""), (.category // ""), $latest.timestamp, $latest.targetAbi, ($latest.changelog // "") ] | join("|") ' "$manifest_file") done if [[ "$found" != "true" ]]; then echo -e "${RED}Plugin not found with ID: $input${NC}" exit 1 fi echo -e "Found plugin: ${GREEN}$plugin_name${NC} (version $plugin_version) in $plugin_repo" echo -e "Installing from: ${BLUE}$plugin_url${NC}" # Create temp directory mkdir -p "$temp_dir" local download_file="$temp_dir/${plugin_name,,}-${plugin_version}.zip" # Download plugin echo "Downloading plugin..." curl -L "$plugin_url" -o "$download_file" # Verify checksum echo "Verifying checksum..." if ! verify_checksum "$download_file" "$plugin_checksum"; then echo -e "${RED}Checksum verification failed${NC}" rm -f "$download_file" exit 1 fi echo "Checksum verified" # Install plugin echo "Installing plugin..." # Create plugin directory with correct naming convention local plugin_dir="$PLUGIN_DIR/${plugin_name}_${plugin_version}" mkdir -p "$plugin_dir" # Extract to plugin directory unzip -o "$download_file" -d "$plugin_dir" # Generate meta.json generate_meta_json \ "$plugin_name" \ "$plugin_version" \ "$plugin_guid" \ "$plugin_description" \ "$plugin_overview" \ "$plugin_owner" \ "$plugin_category" \ "$plugin_timestamp" \ "$plugin_abi" \ "$plugin_changelog" \ "$plugin_dir" \ "$plugin_image_url" # Download plugin image if available if [ -n "$plugin_image_url" ]; then download_plugin_image "$plugin_image_url" "$plugin_dir" "${plugin_name}_${plugin_version}" fi # Set correct permissions if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then chown -R ${PUID}:${PGID} "$plugin_dir" fi # Cleanup rm -f "$download_file" # After successful installation and before final message if [ -n "$current_version" ]; then remove_old_plugin "$plugin_name" "$plugin_version" "$current_version" fi echo -e "${GREEN}Plugin installed successfully${NC}" echo "Restart Jellyfin to complete installation:" echo "docker compose restart jellyfin" } # Update upgrade_plugins function to pass current version upgrade_plugins() { # Fetch latest manifests fetch_manifests "true" echo -e "${BLUE}Checking for plugin updates...${NC}" local updates_found=false # Scan plugins directory for plugin_dir in "$PLUGIN_DIR"/*; do if [ -f "$plugin_dir/meta.json" ]; then # Read current plugin info local current_guid=$(jq -r '.guid' "$plugin_dir/meta.json") local current_version=$(jq -r '.version' "$plugin_dir/meta.json") local plugin_name=$(jq -r '.name' "$plugin_dir/meta.json") # Search manifests for newer version for manifest_file in "${MANIFEST_CACHE_DIR}"/*.json; do if [[ ! -f "$manifest_file" ]]; then continue fi # Get latest version info local latest_version=$(jq -r --arg guid "$current_guid" ' .[] | select(.guid == $guid) | .versions | sort_by(.timestamp) | last | .version // empty ' "$manifest_file") if [ -n "$latest_version" ] && [ "$latest_version" != "$current_version" ]; then updates_found=true echo -e "\nUpdate available for ${GREEN}$plugin_name${NC}" echo "Current version: $current_version" echo "Latest version: $latest_version" read -p "Do you want to upgrade this plugin? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then if install_plugin "$current_guid" "true" "$current_version"; then echo -e "${GREEN}Upgrade successful${NC}" else echo -e "${RED}Upgrade failed${NC}" fi fi fi done fi done if [ "$updates_found" = false ]; then echo -e "${GREEN}All plugins are up to date${NC}" fi } # Update main script to handle info command case "$1" in "catalog") if [ -z "$2" ]; then show_usage exit 1 fi list_catalog "$2" ;; "install") if [ -z "$2" ]; then show_usage exit 1 fi install_plugin "$2" ;; "search") if [ -z "$2" ]; then show_usage exit 1 fi mkdir -p "${TEMP_DIR}" search_repos "$2" ;; "info") if [ -z "$2" ]; then show_usage exit 1 fi show_plugin_info "$2" ;; "versions") if [ -z "$2" ]; then show_usage exit 1 fi show_plugin_versions "$2" ;; "repos") list_repos ;; "update") update_manifests ;; "upgrade") upgrade_plugins ;; *) show_usage exit 1 ;; esac