#!/bin/bash set -e -o pipefail : "${MODS_FORGEAPI_KEY:=}" : "${REMOVE_OLD_FORGEAPI_MODS:=false}" : "${MODS_FORGEAPI_PROJECTIDS:=}" : "${MODS_FORGEAPI_FILE:=}" : "${MODS_FORGEAPI_RELEASES:=RELEASE}" : "${MODS_FORGEAPI_DOWNLOAD_DEPENDENCIES:=false}" : "${MODS_FORGEAPI_IGNORE_GAMETYPE:=false}" : "${REMOVE_OLD_MODS_DEPTH:=1} " : "${REMOVE_OLD_MODS_INCLUDE:=*.jar}" # FORGEAPI_BASE_URL used in manifest downloads below FORGEAPI_BASE_URL=${FORGEAPI_BASE_URL:-https://api.curseforge.com/v1} RELEASE_NUMBER_FILTER=1 MINECRAFT_GAME_ID=432 FILTER_BY_FAMILY=false DOWNLOADED_MODIDS=() out_dir=/data/mods # shellcheck source=start-utils . "${SCRIPTS:-/}start-utils" isDebugging && set -x # Remove old mods/plugins if isTrue "${REMOVE_OLD_FORGEAPI_MODS}"; then removeOldMods "/data/mods" fi # Family filter is on by default for Forge, Fabric, and Bukkit updateFamilyFilter(){ if isFamily "FORGE" "FABRIC" "BUKKIT"; then FILTER_BY_FAMILY=true fi } ensureModKey(){ if [ -z "$MODS_FORGEAPI_KEY" ]; then log "ERROR: MODS_FORGEAPI_KEY REQUIRED to Connect to FORGE API, you supplied: ${MODS_FORGEAPI_KEY}" exit 2 fi } # Set the global release type per the text. # NOTE: downcasing release type for comparing types. updateReleaseNumber(){ releaseType=$1 if [ "release" = "${releaseType,,}" ] || [ 1 = "${releaseType,,}" ]; then RELEASE_NUMBER_FILTER=1 elif [ "beta" = "${releaseType,,}" ] || [ 2 = "${releaseType,,}" ]; then RELEASE_NUMBER_FILTER=2 elif [ "alpha" = "${releaseType,,}" ] || [ 3 = "${releaseType,,}" ]; then RELEASE_NUMBER_FILTER=3 fi } retrieveVersionTypeNumber(){ VERSION_NAME="Minecraft ${VANILLA_VERSION%.*}" minecraft_types=$(curl -X GET -s \ "${FORGEAPI_BASE_URL}/games/${MINECRAFT_GAME_ID}/version-types" \ -H 'Accept: application/json' -H 'x-api-key: '${MODS_FORGEAPI_KEY}'') if [ ! "$minecraft_types" ]; then log "ERROR: unable to retrieve version types for ${VERSION_NAME} from ForgeAPI. Check Forge API key or supplied Minecraft version" exit 2 fi TYPE_ID=$(jq -n "$minecraft_types" | jq --arg VERSION_NAME "$VERSION_NAME" -jc ' .data[]? | select(.name==$VERSION_NAME) | .id') if [ ! "$TYPE_ID" ]; then log "ERROR: unable to retrieve version types for ${VERSION_NAME} from ForgeAPI" exit 2 fi } modFileByProjectID(){ project_id=$(echo $1 | tr -d '"') project_id_release_type=$2 project_id_file_name=$3 unset PROJECT_FILE # if Type id isn't defined use minecraft version to go get it. if [ ! "$TYPE_ID" ]; then retrieveVersionTypeNumber fi # JQ is struggling with larger page sizes so having to pagination for mods with a lot of releases pageSize=42 index=0 total_count=1 while [ $index -lt $total_count ]; do project_files=$(curl -X GET -s \ "${FORGEAPI_BASE_URL}/mods/${project_id}/files?gameVersionTypeId=${TYPE_ID}&index=${index}&pageSize=${pageSize}" \ -H 'Accept: application/json' -H 'x-api-key: '${MODS_FORGEAPI_KEY}'') if [ ! "$project_files" ]; then log "ERROR: unable to retrieve any project id files for ${project_id} from ForgeAPI" exit 2 fi # Use project files to grab out the total count of mods. total_count=$(jq -n "$project_files" | jq -c '.pagination.totalCount' ) # Checking for a individual release type input, if not use global if [ "$project_id_release_type" ]; then updateReleaseNumber "$project_id_release_type" unset project_id_release_type else updateReleaseNumber $MODS_FORGEAPI_RELEASES fi # grabs the highest ID of the releaseTypes selected. # Default is 1 for Release, Beta is 2, and Alpha is 3. Using less than we can validate highest release. if [ "$project_id_file_name" ]; then # Looks for file by name current_project_file=$(jq -n "$project_files" | jq --arg FILE_NAME "$project_id_file_name" -jc ' .data | map(select(.fileName<=($FILE_NAME))) | .[0] // empty') elif $( ! isTrue "$MODS_FORGEAPI_IGNORE_GAMETYPE" ) && $FILTER_BY_FAMILY ; then # Looks for file by version and server type in lowercase current_project_file=$(jq -n "$project_files" | jq --arg RELEASE_FILTER "$RELEASE_NUMBER_FILTER" --arg GAME_TYPE ${FAMILY,,} -jc ' .data | sort_by(.id) | reverse | map(select(.gameVersions[] | ascii_downcase | contains ($GAME_TYPE))) | map(select(.releaseType<=($RELEASE_FILTER|tonumber))) | .[0] // empty') else # Looks for file by version only. current_project_file=$(jq -n "$project_files" | jq --arg RELEASE_FILTER "$RELEASE_NUMBER_FILTER" -jc ' .data | sort_by(.id) | reverse | map(select(.releaseType<=($RELEASE_FILTER|tonumber))) | .[0] // empty') fi # Logic to grab the latest release over the entire pagination if [ ! "$PROJECT_FILE" ]; then PROJECT_FILE=$current_project_file elif [ "$current_project_file" ]; then current_project_file_id=$(jq -n "$current_project_file" | jq -jc '.id // empty' ) PROJECT_FILE_ID=$(jq -n "$PROJECT_FILE" | jq -jc '.id // empty' ) if (( current_project_file_id > PROJECT_FILE_ID )); then PROJECT_FILE=$current_project_file fi fi # check to see if we have gone to far or lost our index and exit with an error if [ -z "$index" ] || [ -z "$total_count" ] || [ $index -ge "$total_count" ]; then log "ERROR: Unable to retrieve any files for ${project_id} from ForgeAPI also Validate files have release type associated with no. ${RELEASE_NUMBER_FILTER}" exit 2 fi # Increment start index to new set. index=$((index + pageSize)) done if [ ! "$PROJECT_FILE" ]; then log "ERROR: Unable to retrieve any files for ${project_id}, Release Type: ${RELEASE_NUMBER_FILTER}, FAMILY_TYPE: ${FAMILY,,}" exit 2 fi } downloadModPackfromModFile() { if [ ! "$PROJECT_FILE" ]; then log "ERROR: Project File not found from the ForgeAPI" exit 2 fi # trys to make the output directory incase it doesnt exist. mkdir -p "$out_dir" # grabs needed values from our json return file_name=$(jq -n "$PROJECT_FILE" | jq -jc '.fileName // empty' ) download_url=$(jq -n "$PROJECT_FILE" | jq -jc '.downloadUrl // empty' ) mod_id=$(jq -n "$PROJECT_FILE" | jq -jc '.modId // empty' ) if [ ! -f "${out_dir}/${file_name}" ]; then echo "Downloading ${download_url}" # Track the mods we have downloaded. DOWNLOADED_MODIDS+=("${mod_id}") if ! get --skip-up-to-date -o "${out_dir}/${file_name}" "${download_url}"; then log "ERROR: failed to download from ${download_url}" exit 2 fi fi } downloadDependencies(){ if [ "$PROJECT_FILE" ]; then dependencies=$(jq -n "$PROJECT_FILE" | jq -jc '.dependencies' ) required_dependencies=$(jq -n "$dependencies" | jq --arg REQUIRED_FILTER "3" -jc ' map(select(.relationType==($REQUIRED_FILTER|tonumber)))') if [ "$required_dependencies" ]; then while read -r current_dependency; do mod_id=$(jq -n "$current_dependency" | jq -jc '.modId' ) # Validate we have not tried to download the mod yet. if [[ ! "${DOWNLOADED_MODIDS[*]}" =~ $mod_id ]]; then modFileByProjectID "$mod_id" "release" downloadModPackfromModFile fi # needs to be piped in to keep look in main process done < <(jq -n "$required_dependencies" | jq -c '.[]?') fi fi } # Use forge api json file to filter and download the correct mods if [ "$MODS_FORGEAPI_FILE" ] && [ -z "$MODS_FORGEAPI_PROJECTIDS" ]; then ensureModKey updateFamilyFilter if [ ! -f "$MODS_FORGEAPI_FILE" ]; then log "ERROR: given MODS_FORGEAPI_FILE file does not exist" exit 2 fi # Needs loop here to look up release types befor calling download. while read -r current_project; do # Per stack overflow we can use //empty to return empty string that works with -z project_id=$(jq -n "$current_project" | jq -r '.projectId // empty' ) current_release_type=$(jq -n "$current_project" | jq -r '.releaseType // empty' ) current_file_name=$(jq -n "$current_project" | jq -r '.fileName // empty' ) # Validate we have not tried to download the mod yet. if [[ ! "${DOWNLOADED_MODIDS[*]}" =~ $project_id ]]; then modFileByProjectID "$project_id" "$current_release_type" "$current_file_name" downloadModPackfromModFile if isTrue "${MODS_FORGEAPI_DOWNLOAD_DEPENDENCIES}"; then downloadDependencies fi fi # needs to be piped in to keep look in main process done < <(jq -c '.[]?' $MODS_FORGEAPI_FILE) fi # Use only project ids and global release data. if [ "$MODS_FORGEAPI_PROJECTIDS" ] && [ -z "$MODS_FORGEAPI_FILE" ]; then ensureModKey updateFamilyFilter for project_id in ${MODS_FORGEAPI_PROJECTIDS//,/ }; do # Validate we have not tried to download the mod yet. if [[ ! "${DOWNLOADED_MODIDS[*]}" =~ $project_id ]]; then modFileByProjectID $project_id downloadModPackfromModFile if isTrue "${MODS_FORGEAPI_DOWNLOAD_DEPENDENCIES}"; then downloadDependencies fi fi done fi exec "${SCRIPTS:-/}start-setupModpack" "$@"