1026 lines
33 KiB
Bash
Executable file
1026 lines
33 KiB
Bash
Executable file
#!/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 <term> - Search known repositories for plugins"
|
|
echo " $0 install <name|guid|plugin_url> - Install a plugin by name, GUID, or URL"
|
|
echo " $0 info <name|guid> - Show detailed information about a specific plugin"
|
|
echo " $0 versions <name|guid> - 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 <manifest_url> - 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
|
|
|
|
|
|
|
|
|