feat: nexus mods integration (#44)
* progress * working download * save details in db, display them * progress * progress * progress * progress * i guess it's shippable * final commit
This commit is contained in:
@@ -41,25 +41,27 @@ h2mm
|
||||
|
||||
### Available commands
|
||||
|
||||
- `install` - Install a mod by the file provided (directory, zip, patch).
|
||||
- `uninstall` - Uninstall a mod.
|
||||
- `list` - List all installed mods.
|
||||
- `enable` - Enable a mod.
|
||||
- `disable` - Disable a mod.
|
||||
- `export` - Export installed mods to a zip file.
|
||||
- `import` - Import mods from a zip file.
|
||||
- `order` - Change load order for a mod.
|
||||
- `modpack-create` - Create a modpack from the currently installed mods.
|
||||
- `modpack-switch` - Switch to a modpack.
|
||||
- `modpack-list` - List all installed modpacks.
|
||||
- `modpack-delete` - Delete a modpack.
|
||||
- `modpack-overwrite` - Overwrite a modpack.
|
||||
- `modpack-reset` - Reset all installed modpacks.
|
||||
- `update` - Update h2mm to latest version.
|
||||
- `reset` - Reset all installed mods.
|
||||
- `help` - Display this help message.
|
||||
- `install` or `i` - Install a mod by the file provided (directory, zip, patch);
|
||||
- `uninstall` or `u` - Uninstall a mod;
|
||||
- `list` or `l` - List all installed mods;
|
||||
- `enable` or `e` - Enable a mod;
|
||||
- `disable` or `d` - Disable a mod;
|
||||
- `order` or `o` - Change load order for a mod;
|
||||
- `export` or `ex` - Export installed mods to a zip file;
|
||||
- `import` or `im` - Import mods from a zip file;
|
||||
- `modpack-create` or `mc` - Create a modpack from the currently installed mods;
|
||||
- `modpack-switch` or `ms` - Switch to a modpack;
|
||||
- `modpack-list` or `ml` - List all installed modpacks;
|
||||
- `modpack-delete` or `md` - Delete a modpack;
|
||||
- `modpack-overwrite` or `mo` - Overwrite a modpack;
|
||||
- `modpack-reset` or `mr` - Reset all installed modpacks;
|
||||
- `nexus-setup` or `ns` - Setup Nexus Mods integration;
|
||||
- `nexus-update` or `nu` - Start Nexus mods upgrade process;
|
||||
- `update` or `up` - Update h2mm to latest version;
|
||||
- `reset` or `r` - Reset all installed mods;
|
||||
- `help` or `h` - Display this help message.
|
||||
|
||||
### Basic usage
|
||||
### Usage
|
||||
|
||||
To find out how to use a command, you can run `h2mm <COMMAND> --help`.
|
||||
|
||||
@@ -67,17 +69,27 @@ To find out how to use a command, you can run `h2mm <COMMAND> --help`.
|
||||
|
||||
```bash
|
||||
h2mm install /path/to/mod.zip
|
||||
h2mm install /path/to/mod/files
|
||||
h2mm install /path/to/mod/directory/
|
||||
h2mm install /path/to/mod.zip /path/to/mod2.zip /path/to/mod/files
|
||||
h2mm install -n "Example mod" mod.patch_0 mod.patch_0.stream # -n is mandatory when using files
|
||||
h2mm install -n "Example mod" mod* # using a wildcard to include all files
|
||||
```
|
||||
|
||||
> It's better to be in the directory where the mod files are located, so you don't have to specify the full path. For mod downloads, you can do `cd ~/Downloads` and then run the command.
|
||||
>
|
||||
> Also, use the Tab key to autocomplete the file names, as it will help you escape special characters likes spaces or quotes.
|
||||
|
||||
#### List installed mods
|
||||
|
||||
```bash
|
||||
h2mm list
|
||||
h2mm list -v # verbose mode
|
||||
```
|
||||
|
||||
#### Uninstall a mod
|
||||
|
||||
```bash
|
||||
h2mm uninstall -n "Example mod"
|
||||
h2mm uninstall -i 3
|
||||
h2mm uninstall -i 3 # get the index from the list command
|
||||
```
|
||||
|
||||
#### Enable/disable mods
|
||||
@@ -89,18 +101,22 @@ h2mm disable -n "Example mod"
|
||||
h2mm disable -i 3
|
||||
```
|
||||
|
||||
#### List installed mods
|
||||
|
||||
```bash
|
||||
h2mm list
|
||||
```
|
||||
|
||||
#### Updating the script
|
||||
|
||||
```bash
|
||||
h2mm update
|
||||
```
|
||||
|
||||
## Nexus Mods integration
|
||||
|
||||
Nexus Mods integration allows you to use the 1-click install feature of Nexus Mods (with the "Vortex" or "Mod manager download" buttons). You can set up Nexus Mods integration by running the following command:
|
||||
|
||||
```bash
|
||||
h2mm nexus-setup
|
||||
```
|
||||
|
||||
You will be walked through the setup process, which will ask you for your Nexus Mods API key and your preferred terminal.
|
||||
|
||||
## Compatibility
|
||||
|
||||
The script is developed and tested on Arch Linux, but it should work on other Linux distributions as well. If you encounter any issues, please open an issue on the repository.
|
||||
@@ -111,62 +127,7 @@ Status of platforms:
|
||||
- Steam Deck :white_check_mark:
|
||||
- Windows - WSL :white_check_mark:
|
||||
|
||||
> The script works on WSL, but you need to specify the path to the Helldivers 2 mods directory manually, to find your Windows partition head to `/mnt/` and from there go to your Helldivers 2 data directory, on a typical install it should be on `/mnt/c/Program\ Files\ \(x86\)/Steam/steamapps/common/Helldivers\ 2/data`. You also need to have `unzip` installed, which can be done by running `sudo apt install unzip`.
|
||||
|
||||
## Advanced usage
|
||||
|
||||
### Shortcuts
|
||||
|
||||
You can use the short form of commands to save some time. The shortcuts are:
|
||||
|
||||
- `i` for `install`
|
||||
- `u` for `uninstall`
|
||||
- `e` for `enable`
|
||||
- `d` for `disable`
|
||||
- `l` for `list`
|
||||
- `ex` for `export`
|
||||
- `im` for `import`
|
||||
- `o` for `order`
|
||||
- `mc` for `modpack-create`
|
||||
- `ms` for `modpack-switch`
|
||||
- `ml` for `modpack-list`
|
||||
- `md` for `modpack-delete`
|
||||
- `mo` for `modpack-overwrite`
|
||||
- `mr` for `modpack-reset`
|
||||
- `up` for `update`
|
||||
- `r` for `reset`
|
||||
|
||||
### Modpacks support
|
||||
|
||||
You can set up modpacks by using the `modpack-*` commands. This allows you to quickly change between a set of mods. For more information, check the help message.
|
||||
|
||||
```bash
|
||||
h2mm modpack-create -n "Modpack 1"
|
||||
# install, enable, disable other mods...
|
||||
h2mm modpack-create -n "Modpack 2"
|
||||
h2mm modpack-switch -n "Modpack 1"
|
||||
```
|
||||
|
||||
### Exporting and importing
|
||||
|
||||
You can export all installed mods to a zip file and import mods from the same file. This can be useful for sharing mods with others or for backing up your mods. The archive file (`.tar.gz`) will be saved in the current directory.
|
||||
|
||||
```bash
|
||||
h2mm export
|
||||
h2mm import /path/to/mods.tar.gz
|
||||
```
|
||||
|
||||
### Resetting all installed mods
|
||||
|
||||
You can reset all installed mods by running the following command. This will remove all installed mods and the database, in case things go wild.
|
||||
|
||||
```bash
|
||||
h2mm reset
|
||||
```
|
||||
|
||||
### Database location and details
|
||||
|
||||
The database is stored in the `Helldivers 2` install directory, under the `data` folder with the name `mods.csv`, where the mods are also installed. The database is a simple CSV file which you can use to manually manage mods if needed, you can mostly use it to rename or reorder mods.
|
||||
> The script works on WSL, but you need to specify the path to the Helldivers 2 mods directory manually, to find your Windows partition head to `/mnt/` and from there go to your Helldivers 2 data directory, on a typical install it should be on `/mnt/c/Program\ Files\ \(x86\)/Steam/steamapps/common/Helldivers\ 2/data`. You also need to have `unzip` installed, which can be done by running `sudo apt install unzip`. Nexus Mods integration is not supported on WSL.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION="0.4.2"
|
||||
VERSION="0.5.0"
|
||||
|
||||
# --- Globals ---
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
ORANGE='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
MODS_DIR=""
|
||||
@@ -16,14 +17,18 @@ MODPACKS_DB_FILE=""
|
||||
|
||||
H2CONFIG_DIR="${HOME}/.config/h2mm"
|
||||
H2PATH_FILE="${H2CONFIG_DIR}/h2path"
|
||||
API_KEY_FILE="${H2CONFIG_DIR}/api_key"
|
||||
UPDATES_AVAILABLE_FILE="${H2CONFIG_DIR}/updates_available"
|
||||
LAST_CHECKED_UPDATE_FILE="${H2CONFIG_DIR}/last_update"
|
||||
BACKUPS_DIR="${H2CONFIG_DIR}/backups"
|
||||
|
||||
VERSION_URL="https://raw.githubusercontent.com/v4n00/h2mm-cli/refs/heads/master/version"
|
||||
|
||||
breaking_changes_patches=(
|
||||
["2"]='sed -i "s/^\([0-9]\+\),/\1,ENABLED,/" "$1/mods.csv"'
|
||||
["3"]='sed -i "1 i\\3" "$1/mods.csv"'
|
||||
["4"]='tmp_file=$(mktemp) && awk '\''BEGIN {FS=OFS=","} NR==1 {print 4; next} {print NR-1, $2, $3, $4, $5}'\'' "$1/mods.csv" > "$tmp_file" && tee "$1/mods.csv" < "$tmp_file" > /dev/null && rm "$tmp_file"'
|
||||
["5"]='sed -i "s/^\([0-9]\+\),\(.*\),\(.*\),\(.*\)/\1,\2,\3,,,,\4/" "$1/mods.csv"; sed -i "1 s/4/5/" "$1/mods.csv"'
|
||||
)
|
||||
|
||||
# --- Utility Functions ---
|
||||
@@ -53,17 +58,37 @@ function get_extension() {
|
||||
}
|
||||
|
||||
function get_files_by_entry_from_db() {
|
||||
echo "$1" | cut -d',' -f4- | tr ',' ' ' | head -1
|
||||
echo "$1" | cut -d',' -f7- | tr ',' ' ' | head -1
|
||||
}
|
||||
|
||||
function get_name_by_entry_from_db() {
|
||||
echo "$1" | awk -F, '{print $3}'
|
||||
}
|
||||
|
||||
function get_index_by_entry_from_db() {
|
||||
echo "$1" | awk -F, '{print $1}'
|
||||
}
|
||||
|
||||
function get_nexus_mod_id_by_entry_from_db() {
|
||||
echo "$1" | awk -F, '{print $4}'
|
||||
}
|
||||
|
||||
function get_nexus_mod_file_id_by_entry_from_db() {
|
||||
echo "$1" | awk -F, '{print $5}'
|
||||
}
|
||||
|
||||
function get_nexus_mod_version_by_entry_from_db() {
|
||||
echo "$1" | awk -F, '{print $6}'
|
||||
}
|
||||
|
||||
function disable_all_modpacks() {
|
||||
sed -i 's/ENABLED/DISABLED/' "$MODPACKS_DB_FILE"
|
||||
}
|
||||
|
||||
function get_entry_from_db_by_nexus_mod_id() {
|
||||
echo "$(awk -F, -v id="$1" 'NR > 1 && $4 == id {print $0}' "$DB_FILE")"
|
||||
}
|
||||
|
||||
function parse_help_no_arguments() {
|
||||
display_help="$1"
|
||||
[[ "$2" == "--help" || "$2" == "-h" ]] && { $display_help; exit 0; }
|
||||
@@ -82,6 +107,10 @@ function log() {
|
||||
[[ "$silent" == "true" ]] && return
|
||||
echo -e "$*" >&2
|
||||
;;
|
||||
WARNING)
|
||||
[[ "$silent" == "true" ]] && return
|
||||
echo -e "${ORANGE}[!]${NC} $*" >&2
|
||||
;;
|
||||
ERROR)
|
||||
echo -e "${RED}[ERROR]${NC} $*" >&2
|
||||
;;
|
||||
@@ -96,6 +125,22 @@ function log() {
|
||||
|
||||
# --- Functions ---
|
||||
|
||||
function check_nexus_mod_arguments() {
|
||||
if [[ -n "$nexus_mod_id" || -n "$nexus_mod_file_id" || -n "$nexus_mod_version" ]]; then
|
||||
if [[ -z "$nexus_mod_id" || -z "$nexus_mod_file_id" || -z "$nexus_mod_version" ]]; then
|
||||
log ERROR "Nexus mod ID, file ID and version are required when specifing at least one of them."
|
||||
exit 1
|
||||
fi
|
||||
has_nexus_mod_arguments=true
|
||||
fi
|
||||
}
|
||||
|
||||
function get_nexus_api_key() {
|
||||
[[ ! -f "$API_KEY_FILE" ]] && { log ERROR "Nexus API key file not found. Please run h2mm nexus-setup."; exit 1; }
|
||||
api_key=$(cat "$API_KEY_FILE")
|
||||
[[ -z "$api_key" ]] && { log ERROR "Nexus API key is empty. Please run h2mm nexus-setup."; exit 1; }
|
||||
}
|
||||
|
||||
function remove_disabled_prefix() {
|
||||
local disabledFile="$1"
|
||||
while [[ "$disabledFile" == disabled_* ]]; do
|
||||
@@ -183,6 +228,11 @@ function initialize_config_directory() {
|
||||
touch "$LAST_CHECKED_UPDATE_FILE"
|
||||
[[ $? -ne 0 ]] && { log ERROR "Could not create last checked update file."; exit 1; }
|
||||
fi
|
||||
|
||||
if [[ ! -f "$UPDATES_AVAILABLE_FILE" ]]; then
|
||||
touch "$UPDATES_AVAILABLE_FILE"
|
||||
[[ $? -ne 0 ]] && { log ERROR "Could not create updates available file."; exit 1; }
|
||||
fi
|
||||
}
|
||||
|
||||
function initialize_directories() {
|
||||
@@ -216,26 +266,6 @@ function initialize_modpack_directories() {
|
||||
fi
|
||||
}
|
||||
|
||||
function check_for_updates() {
|
||||
last_update=$(cat "$LAST_CHECKED_UPDATE_FILE")
|
||||
if [[ -n "$last_update" && "$(date +%s)" -lt $(("$(date +%s -d "$last_update")" + 3600)) ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
latest_version=$(curl -sS "$VERSION_URL")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log ERROR "Could not check for updates."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$latest_version" != "$VERSION" ]]; then
|
||||
log INFO "A new version of h2mm is available: $VERSION -> $latest_version"
|
||||
log INFO "Run \"h2mm update\" to update."
|
||||
fi
|
||||
|
||||
echo "$(date)" > "$LAST_CHECKED_UPDATE_FILE"
|
||||
}
|
||||
|
||||
# --- Help Functions ---
|
||||
|
||||
function display_help_main() {
|
||||
@@ -248,15 +278,17 @@ Commands:
|
||||
l, list List all installed mods.
|
||||
e, enable Enable a mod.
|
||||
d, disable Disable a mod.
|
||||
o, order Change load order of a mod.
|
||||
ex, export Export installed mods to a zip file.
|
||||
im, import Import mods from a zip file.
|
||||
o, order Change load order of a mod.
|
||||
mc, modpack-create Create a modpack from the currently installed mods.
|
||||
ms, modpack-switch Switch to a modpack.
|
||||
ml, modpack-list List all installed modpacks.
|
||||
mc, modpack-delete Delete a modpack.
|
||||
mo, modpack-overwrite Overwrite a modpack.
|
||||
mr, modpack-reset Reset all installed modpacks.
|
||||
ns, nexus-setup Setup Nexus Mods integration.
|
||||
nu, nexus-update Start Nexus mods update process.
|
||||
up, update Update h2mm to the latest version.
|
||||
r, reset Reset all installed mods.
|
||||
help Display this help message.
|
||||
@@ -427,6 +459,24 @@ Options:
|
||||
EOF
|
||||
}
|
||||
|
||||
function display_help_nexus_setup() {
|
||||
cat << EOF
|
||||
Usage: h2mm nexus-setup
|
||||
Setup nexusmods integration.
|
||||
This will create a config file in ~/.config/h2mm/apikey with the API key.
|
||||
This will create a desktop entry in ~/.local/share/applications/h2mm.desktop.
|
||||
Run this again in case you change the API key or want to change the desktop entry.
|
||||
EOF
|
||||
}
|
||||
|
||||
function display_help_nexus_update() {
|
||||
cat << EOF
|
||||
Usage: h2mm nexus-update
|
||||
Display a list of Nexus mods that have updates available.
|
||||
Due to Nexus API limitations, this will prompt the user to access the Nexus website to download the updates.
|
||||
EOF
|
||||
}
|
||||
|
||||
# --- Main Functions ---
|
||||
|
||||
# Upgrade/downgrade logic
|
||||
@@ -677,6 +727,9 @@ function mod_install() {
|
||||
local mod_dir=()
|
||||
local mod_files=()
|
||||
local mod_zip=()
|
||||
local nexus_mod_version=""
|
||||
local nexus_mod_file_id=""
|
||||
local nexus_mod_id=""
|
||||
local is_rar=false
|
||||
|
||||
# parse arguments
|
||||
@@ -686,6 +739,18 @@ function mod_install() {
|
||||
[[ -z "$2" ]] && { log ERROR "Mod name is required."; exit 1; }
|
||||
mod_name="$2"; shift 2
|
||||
;;
|
||||
"--version")
|
||||
[[ -z "$2" ]] && { log ERROR "Nexus mod version is required."; exit 1; }
|
||||
nexus_mod_version="$2"; shift 2
|
||||
;;
|
||||
"--file-id")
|
||||
[[ -z "$2" ]] && { log ERROR "Nexus mod file ID is required."; exit 1; }
|
||||
nexus_mod_file_id="$2"; shift 2
|
||||
;;
|
||||
"--mod-id")
|
||||
[[ -z "$2" ]] && { log ERROR "Nexus mod ID is required."; exit 1; }
|
||||
nexus_mod_id="$2"; shift 2
|
||||
;;
|
||||
*)
|
||||
if [[ "$1" == *.zip || "$1" == *.rar ]]; then
|
||||
mod_zip+=("$1")
|
||||
@@ -700,6 +765,8 @@ function mod_install() {
|
||||
esac
|
||||
done
|
||||
|
||||
check_nexus_mod_arguments
|
||||
|
||||
# edge case when there is a combination of mod zips and directories, call function for all zips and dirs
|
||||
if [[ ${mod_zip} && ${mod_dir} ]]; then
|
||||
mod_install "${mod_zip[@]}"
|
||||
@@ -737,7 +804,7 @@ function mod_install() {
|
||||
|
||||
# if the name is not specified, use the name of the directory, last sed for making nexusmods names not have numbers
|
||||
if [[ -z "$mod_name" ]]; then
|
||||
mod_name=$(basename "$mod_zip" | sed -E 's/\.zip//' | awk -F/ '{print $NF}' | sed -E 's/-[0-9]+-.*//')
|
||||
mod_name=$(get_filename_without_path "$mod_zip" | sed -E 's/\.zip//' | sed -E 's/\.rar//' | sed -E 's/-[0-9]+-[^a-zA-Z]*-[0-9]+$//')
|
||||
fi
|
||||
|
||||
# mod_dir as a temporary directory
|
||||
@@ -820,8 +887,10 @@ function mod_install() {
|
||||
done
|
||||
|
||||
# verify duplicate mod names
|
||||
get_mod_name_and_index --do-not-exit
|
||||
[[ $mod_index -ne -1 ]] && { log ERROR "The mod '$mod_name' is already installed."; exit 1; }
|
||||
if [[ $has_nexus_mod_arguments == false ]]; then
|
||||
get_mod_name_and_index --do-not-exit
|
||||
[[ $mod_index -ne -1 ]] && { log ERROR "The mod '$mod_name' is already installed."; exit 1; }
|
||||
fi
|
||||
|
||||
# store the target files so we can put them in the database later
|
||||
target_files=()
|
||||
@@ -832,7 +901,6 @@ function mod_install() {
|
||||
base_name=$(get_basename "$file")
|
||||
extension=$(get_extension "$file")
|
||||
|
||||
|
||||
# count already installed patches
|
||||
count=$(ls "$MODS_DIR/${base_name}.patch_"* 2>/dev/null | grep -E '([0-9]+$)' 2>/dev/null | wc -l)
|
||||
|
||||
@@ -849,12 +917,14 @@ function mod_install() {
|
||||
|
||||
# verify installation worked
|
||||
[[ $? -ne 0 ]] && { log ERROR "Could not install mod file $file."; exit 1; }
|
||||
log INFO "Mod file ${ORANGE}$file${NC} installed at ${GREEN}\$MODS_DIR/$target_file${NC}."
|
||||
log INFO "Mod file ${ORANGE}$(echo "$file" | cut -d'/' -f4-)${NC} installed at ${GREEN}\$MODS_DIR/$target_file${NC}."
|
||||
done
|
||||
|
||||
# add entry to database
|
||||
next_id=$(awk -F, 'NR > 1 {last_id = $1} END {print last_id + 1}' "$DB_FILE")
|
||||
echo "$next_id,ENABLED,$mod_name,${target_files[*]}" >> "$DB_FILE"
|
||||
entry="$next_id,ENABLED,$mod_name,$nexus_mod_id,$nexus_mod_file_id,$nexus_mod_version,${target_files[*]}"
|
||||
|
||||
echo "$entry" >> "$DB_FILE"
|
||||
log INFO "Mod ${GREEN}successfully${NC} installed: $mod_name."
|
||||
|
||||
# disable any modpack
|
||||
@@ -937,15 +1007,33 @@ function mod_list() {
|
||||
|
||||
log INFO "Installed mods:"
|
||||
|
||||
awk -v GREEN="$GREEN" -v RED="$RED" -v NC="$NC" -v verbose="$verbose" -F, 'NR > 1 {
|
||||
color = ($2 == "DISABLED") ? RED : GREEN;
|
||||
if (verbose == "false") {
|
||||
$4 = "";
|
||||
awk -v GREEN="$GREEN" -v ORANGE="$ORANGE" -v BLUE="$BLUE" -v RED="$RED" -v NC="$NC" -v verbose="$verbose" -F, 'NR > 1 {
|
||||
mod_index = $1;
|
||||
mod_status = $2;
|
||||
mod_name = $3;
|
||||
mod_id = $4;
|
||||
mod_file_id = $5;
|
||||
mod_version = $6;
|
||||
mod_files = $7;
|
||||
mod_details = "";
|
||||
|
||||
STATUS_COLOR = (mod_status == "DISABLED") ? RED : GREEN;
|
||||
if (mod_id == "") {
|
||||
mod_type = "LOCAL";
|
||||
MOD_TYPE_COLOR = BLUE;
|
||||
mod_version = "";
|
||||
} else {
|
||||
gsub(/ /,"\n -> ", $4);
|
||||
$4 = "\n -> " $4;
|
||||
mod_type = "NEXUS";
|
||||
MOD_TYPE_COLOR = ORANGE;
|
||||
mod_version = "| ver: " mod_version;
|
||||
}
|
||||
|
||||
printf "%2s. [%s%s%s/%s%s%s] %s %s\n", mod_index, STATUS_COLOR, mod_status, NC, MOD_TYPE_COLOR, mod_type, NC, mod_name, mod_version;
|
||||
if (verbose == "true") {
|
||||
gsub(/ /,"\n -> ", mod_files);
|
||||
if (mod_id != "") printf " => Nexus mod ID: %s / Nexus file ID: %s\n", mod_id, mod_file_id;
|
||||
printf " -> %s\n", mod_files;
|
||||
}
|
||||
printf "%2s. [%s%s%s] %s %s\n", $1, color, $2, NC, $3, $4
|
||||
}' "$DB_FILE"
|
||||
}
|
||||
|
||||
@@ -1031,7 +1119,7 @@ function mod_import() {
|
||||
[[ -z "$db_version" || ! "$db_version" =~ ^[0-9]+$ ]] && { log ERROR "Invalid version number inside mods.csv from imported archive."; exit 1; }
|
||||
|
||||
if [[ "$db_version" != "$current_version" ]]; then
|
||||
log INFO "Import detected version 0.$db_version.x, current version is 0.$current_version.x."
|
||||
log INFO "Import version mismatch detected: ${ORANGE}0.$db_version.x${NC} -> ${GREEN}0.$current_version.x${NC}."
|
||||
|
||||
# iterate from installed major number to latest major number
|
||||
for ((i = db_version + 1; i <= current_version; i++)); do
|
||||
@@ -1039,7 +1127,7 @@ function mod_import() {
|
||||
# apply breaking changes patch
|
||||
eval $(echo "${breaking_changes_patches[$i]}" | sed "s:\$1:$MODS_EXPORT_DIR:g")
|
||||
else
|
||||
log INFO "No breaking changes for version $i."
|
||||
log INFO "No breaking changes for version 0.$i.x."
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -1049,7 +1137,7 @@ function mod_import() {
|
||||
|
||||
[[ "$response" != "y" && "$response" != "Y" && -n "$response" ]] && { log INFO "Exiting." ; exit 1; }
|
||||
else
|
||||
log INFO "Version upgrade fix ${GREEN}successfully${NC} applied for version $i."
|
||||
log INFO "Upgrade ${GREEN}successfully${NC} applied for mismatch: ${GREEN}0.$i.x${NC}."
|
||||
fi
|
||||
done
|
||||
fi
|
||||
@@ -1059,7 +1147,7 @@ function mod_import() {
|
||||
|
||||
[[ $? -ne 0 ]] && { log ERROR "Failed to import mods."; exit 1; }
|
||||
|
||||
log INFO "Mods ${GREEN}successfully${NC} imported."
|
||||
[[ $modpack == false ]] && log INFO "Mods ${GREEN}successfully${NC} imported."
|
||||
}
|
||||
|
||||
function mod_order {
|
||||
@@ -1127,7 +1215,7 @@ function mod_order {
|
||||
# step 2.1 - iterate over the basenames, find and store the files with the same basenames as the ones we want to upgrade/downgrade
|
||||
declare -A files_to_replace
|
||||
for base_name in "${!replace_count[@]}"; do
|
||||
IFS= files_to_replace["$base_name"]=$(echo "$entries" | awk -F, '{print $4}' | grep -o "$base_name\.patch_[^ ]*"); unset IFS
|
||||
IFS= files_to_replace["$base_name"]=$(echo "$entries" | awk -F, '{print $7}' | grep -o "$base_name\.patch_[^ ]*"); unset IFS
|
||||
done
|
||||
|
||||
# step 2.2 - reverse sort the files_to_replace if we are in descending order
|
||||
@@ -1290,7 +1378,7 @@ function modpack_create() {
|
||||
[[ $(wc -l < "$DB_FILE") -le 1 ]] && { log ERROR "No mods installed."; exit 1; }
|
||||
|
||||
# if the same modpack name already exists, exit
|
||||
[[ -f "$MODPACKS_DIR/$1.tar.gz" ]] && { log ERROR "Modpack $1 already exists."; exit 1; }
|
||||
[[ -f "$MODPACKS_DIR/${modpack_name}.tar.gz" ]] && { log ERROR "Modpack \"$modpack_name\" already exists."; exit 1; }
|
||||
|
||||
# let user select mods to save
|
||||
mod_list
|
||||
@@ -1373,9 +1461,7 @@ function modpack_switch() {
|
||||
get_modpack_name_and_index "$modpack_name" "$modpack_index"
|
||||
|
||||
log INFO "Switching modpacks mods will ${RED}reset${NC} your mods."
|
||||
silent=true
|
||||
mod_import --modpack "$MODPACKS_DIR/$modpack_name.tar.gz"
|
||||
silent=false
|
||||
|
||||
log INFO "Modpack ${GREEN}successfully${NC} switched: $modpack_name."
|
||||
|
||||
@@ -1491,6 +1577,288 @@ function self_update() {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Nexus Mods Integration ---
|
||||
|
||||
function nexus_setup() {
|
||||
parse_help_no_arguments display_help_nexus_setup "$@"
|
||||
local nexus_api_key=""
|
||||
|
||||
log INFO "This is the setup wizard for the Nexus Mods integration."
|
||||
log INFO "You need to provide your Nexus Mods API key to use this feature."
|
||||
log INFO "You can find your API key in your Nexus Mods by:"
|
||||
log INFO " -> Going to Site Preferences -> API Keys"
|
||||
log INFO " -> Going to https://next.nexusmods.com/settings/api-keys"
|
||||
log INFO "Scroll down and request/copy your API key."
|
||||
log INFO ""
|
||||
log PROMPT "Enter your Nexus Mods API key: "
|
||||
read -e nexus_api_key
|
||||
|
||||
[[ -z "$nexus_api_key" ]] && { log ERROR "Nexus Mods API key is required."; exit 1; }
|
||||
|
||||
# save api key to config dir, trim it
|
||||
echo "$nexus_api_key" | tr -d '[:space:]' > "$API_KEY_FILE"
|
||||
[[ $? -ne 0 ]] && { log ERROR "Could not save Nexus Mods API key."; exit 1; }
|
||||
|
||||
# test API key
|
||||
response=$(curl -sSIH "apikey: $nexus_api_key" -X GET "https://api.nexusmods.com/v1/users/validate.json")
|
||||
[[ $? -ne 0 ]] && { log ERROR "curl failed."; exit 1; }
|
||||
[[ ! "$response" =~ "HTTP/2 200" ]] && { log ERROR "Invalid Nexus Mods API key."; exit 1; }
|
||||
|
||||
log INFO "Nexus Mods API key ${GREEN}successfully${NC} tested and saved."
|
||||
log INFO ""
|
||||
|
||||
# ask for default terminal
|
||||
local terminal_command=""
|
||||
|
||||
log INFO "Now the manager needs to create a desktop entry for h2mm."
|
||||
log INFO "In order to do that, it needs to know which terminal you are using."
|
||||
log INFO "Here's a list of supported terminals:"
|
||||
log INFO " -> gnome-terminal (On GNOME systems like Ubuntu)"
|
||||
log INFO " -> konsole (On KDE systems like SteamOS, Steam Deck's default)"
|
||||
log INFO " -> alacritty"
|
||||
log INFO " -> kitty"
|
||||
log INFO ""
|
||||
log INFO "If you are not using any of these terminals, you need to provide"
|
||||
log INFO "the command to spawn your terminal along with the flags to run a command."
|
||||
log INFO "To test if the command is valid, the manager spawn a terminal."
|
||||
log INFO "For example, if you are using ghostty you need to provide this:"
|
||||
log INFO "ghostty -e <COMMAND>"
|
||||
log INFO ""
|
||||
log PROMPT "Enter your terminal (or terminal command): "
|
||||
read -e terminal_command
|
||||
|
||||
[[ -z "$terminal_command" ]] && { log ERROR "Terminal command is required."; exit 1; }
|
||||
command -v "$(echo "$terminal_command" | awk '{print $1}')" >/dev/null 2>&1 || { log ERROR "Terminal command does not work."; exit 1; }
|
||||
|
||||
# check the 4 supported terminals
|
||||
case "$terminal_command" in
|
||||
"gnome-terminal")
|
||||
terminal_command="$terminal_command -- <COMMAND>"
|
||||
;;
|
||||
"konsole"|"alacritty"|"kitty")
|
||||
terminal_command="$terminal_command -e <COMMAND>"
|
||||
;;
|
||||
*)
|
||||
# check if terminal even exists
|
||||
command -v "$(echo "$terminal_command" | awk '{print $1}')" >/dev/null 2>&1 || { log ERROR "Terminal command does not work."; exit 1; }
|
||||
# check if the format is valid
|
||||
[[ "$terminal_command" != *"<COMMAND>"* ]] && { log ERROR "Terminal command is invalid. You did not include <COMMAND>"; exit 1; }
|
||||
|
||||
# test terminal -e command
|
||||
wait_command="read -p 'Press any key to validate terminal...'"
|
||||
test_command=$(echo "$terminal_command" | sed "s/<COMMAND>/$wait_command/")
|
||||
|
||||
log INFO "Using test command: $test_command"
|
||||
$test_command
|
||||
[[ $? -ne 0 ]] && { log ERROR "Terminal command is invalid."; exit 1; }
|
||||
;;
|
||||
esac
|
||||
|
||||
# build terminal command
|
||||
terminal_command=$(echo "$terminal_command" | sed "s/<COMMAND>/h2mm nexus/")
|
||||
|
||||
# build the desktop entry
|
||||
local desktop_entry="[Desktop Entry]
|
||||
Name=h2mm
|
||||
Comment=Link application to be used with Nexus Mods
|
||||
Exec=$terminal_command
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Game;
|
||||
MimeType=x-scheme-handler/nxm;"
|
||||
|
||||
local desktop_file="$HOME/.local/share/applications/h2mm.desktop"
|
||||
|
||||
# create the desktop entry file
|
||||
echo "$desktop_entry" > "$desktop_file"
|
||||
[[ $? -ne 0 ]] && { log ERROR "Could not create desktop entry."; exit 1; }
|
||||
|
||||
log INFO ""
|
||||
log INFO "Desktop entry ${GREEN}successfully${NC} created."
|
||||
log INFO "Your system needs to refresh the desktop database to use the new entry."
|
||||
log INFO "This requires sudo permissions. Please enter your password."
|
||||
|
||||
sudo update-desktop-database
|
||||
[[ $? -ne 0 ]] && { log ERROR "Could not refresh desktop database."; exit 1; }
|
||||
|
||||
log INFO "You can now use the Nexus Mods integration."
|
||||
log INFO "To use it, go to a mod page and click on the \"Vortex\" or \"Download with Manager\" button."
|
||||
log INFO "Your browser will ask you to open the link with h2mm."
|
||||
}
|
||||
|
||||
function nexus_get_mod_info() {
|
||||
local mod_id="$1"
|
||||
local mod_file_id="$2"
|
||||
|
||||
[[ -z "$mod_id" || -z "$mod_file_id" ]] && { log ERROR "Mod ID and file ID are required."; exit 1; }
|
||||
|
||||
# api_key
|
||||
get_nexus_api_key
|
||||
|
||||
api_url="https://api.nexusmods.com/v1/games/helldivers2/mods/$mod_id/files/$mod_file_id.json"
|
||||
response=$(curl -sSw " http:%{http_code}" -H "apikey: $api_key" "$api_url")
|
||||
[[ $? -ne 0 ]] && { log ERROR "curl failed."; exit 1; }
|
||||
|
||||
# checks
|
||||
status="${response: -3}"
|
||||
[[ "$status" != "200" ]] && { log ERROR "Invalid response from Nexus Mods API."; exit 1; }
|
||||
|
||||
# extract info
|
||||
mod_name=$(echo "$response" | grep -oP '"name":"\K[^"]+')
|
||||
nexus_mod_version=$(echo "$response" | grep -oP '"version":"\K[^"]+')
|
||||
[[ -z "$mod_name" || -z "$nexus_mod_version" ]] && { log ERROR "Could not extract mod name and version."; exit 1; }
|
||||
}
|
||||
|
||||
function nexus_check_for_updates() {
|
||||
# get list of entries that have a nexus mod_id
|
||||
IFS=$'\n' nexus_mods=($(awk -F, 'NR > 1 && $4 != "" {print $0}' "$DB_FILE")); unset IFS
|
||||
[[ -z "$nexus_mods" ]] && return
|
||||
|
||||
local update_count=0
|
||||
[[ -f "$UPDATES_AVAILABLE_FILE" ]] && rm -f "$UPDATES_AVAILABLE_FILE"
|
||||
|
||||
# call the API for each mod_id and get the latest version
|
||||
for entry in "${nexus_mods[@]}"; do
|
||||
nexus_mod_id=$(get_nexus_mod_id_by_entry_from_db "$entry")
|
||||
nexus_mod_file_id=$(get_nexus_mod_file_id_by_entry_from_db "$entry")
|
||||
local_mod_version=$(get_nexus_mod_version_by_entry_from_db "$entry")
|
||||
|
||||
nexus_get_mod_info "$nexus_mod_id" "$nexus_mod_file_id"
|
||||
|
||||
# compare the latest version with the installed version
|
||||
if [[ "$nexus_mod_version" != "$local_mod_version" ]]; then
|
||||
mod_name=$(get_name_by_entry_from_db "$entry")
|
||||
|
||||
# store in a temp file (inside config dir) the mods that have updates
|
||||
echo "$mod_name,$local_mod_version,$nexus_mod_version,$nexus_mod_id,$nexus_mod_file_id" >> "$UPDATES_AVAILABLE_FILE"
|
||||
update_count=$((update_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
[[ $update_count -eq 0 ]] && return 0;
|
||||
log WARNING "Updates available for ${ORANGE}$update_count${NC} Nexus mods => run 'h2mm nexus-update'."
|
||||
}
|
||||
|
||||
function nexus_update_mods() {
|
||||
parse_help_no_arguments display_help_nexus_update "$@"
|
||||
|
||||
[[ $(wc -l < "$UPDATES_AVAILABLE_FILE") -eq 0 ]] && { log INFO "No updates available."; return 0; }
|
||||
|
||||
log INFO "Due to Nexus Mods API limitations, the update process is not automated."
|
||||
log INFO "Please visit the direct download links to install the latest version of your mods:"
|
||||
|
||||
awk -v GREEN="$GREEN" -v ORANGE="$ORANGE" -v NC="$NC" -F, '{
|
||||
printf "%2s. %s\n", NR, $1
|
||||
printf " => Update from %s%s%s -> %s%s%s\n", ORANGE, $2, NC, GREEN, $3, NC
|
||||
printf " -> Direct install: https://www.nexusmods.com/helldivers2/mods/%s?tab=files&file_id=%s&nmm=1\n", $4, $5
|
||||
printf " -> Mod page: https://www.nexusmods.com/helldivers2/mods/%s\n", $4
|
||||
}' "$UPDATES_AVAILABLE_FILE"
|
||||
}
|
||||
|
||||
|
||||
function nexus() {
|
||||
trap 'read -p "Press any key to continue..."' EXIT
|
||||
local mod_nxm_link="$1"
|
||||
|
||||
[[ -z "$mod_nxm_link" ]] && { log ERROR "Nexus Mods nxm link is required."; exit 1; }
|
||||
[[ ! "$mod_nxm_link" =~ ^nxm://helldivers2/mods/[0-9]+/files/[0-9]+\?key=.*\&expires=[0-9]+\&user_id=[0-9]+$ ]] && { log ERROR "Invalid Nexus Mods nxm link."; exit 1; }
|
||||
|
||||
log INFO "Received NXM link: $mod_nxm_link"
|
||||
|
||||
# api_key
|
||||
get_nexus_api_key
|
||||
|
||||
# extract info
|
||||
nexus_mod_id=$(echo "$mod_nxm_link" | awk -F/ '{print $5}')
|
||||
nexus_mod_file_id=$(echo "$mod_nxm_link" | awk -F/ '{print $7}' | cut -d\? -f1)
|
||||
key=$(echo "$mod_nxm_link" | awk -F= '{print $2}' | cut -d\& -f1)
|
||||
expires=$(echo "$mod_nxm_link" | awk -F= '{print $3}' | cut -d\& -f1)
|
||||
|
||||
api_url="https://api.nexusmods.com/v1/games/helldivers2/mods/$nexus_mod_id/files/$nexus_mod_file_id/download_link.json?key=$key&expires=$expires"
|
||||
log INFO "Using API URL: $api_url"
|
||||
|
||||
response=$(curl -sSw " http:%{http_code}" -H "apikey: $api_key" "$api_url")
|
||||
[[ $? -ne 0 ]] && { log ERROR "curl failed."; exit 1; }
|
||||
|
||||
# checks
|
||||
status="${response: -3}"
|
||||
[[ "$status" != "200" ]] && { log ERROR "Invalid response from Nexus Mods API."; exit 1; }
|
||||
log INFO "Received response from Nexus Mods API: $response"
|
||||
|
||||
# extract URI
|
||||
download_url=$(printf "$response" | sed -E 's/.*"URI":"([^"]+)".*/\1/' | sed 's/\ /%20/g')
|
||||
log INFO "Using download URL: $download_url"
|
||||
log INFO ""
|
||||
|
||||
# extract file name
|
||||
file_name=$(echo "$download_url" | awk -F/ '{print $6}' | cut -d\? -f1)
|
||||
output_file="$(mktemp -d)/$file_name"
|
||||
|
||||
# download the file
|
||||
log INFO "Download progress:"
|
||||
curl -Lo "$output_file" "$download_url"
|
||||
[[ $? -ne 0 ]] && { log ERROR "Download failed."; exit 1; }
|
||||
|
||||
log INFO "Download ${GREEN}successfully${NC} completed."
|
||||
log INFO ""
|
||||
|
||||
# get mod info
|
||||
nexus_get_mod_info "$nexus_mod_id" "$nexus_mod_file_id"
|
||||
|
||||
# check if the mod is already installed, if yes, update it, otherwise install it
|
||||
existing_mod_entry=$(get_entry_from_db_by_nexus_mod_id "$nexus_mod_id")
|
||||
if [[ -z "$existing_mod_entry" ]]; then
|
||||
# install the mod if it doesn't exist
|
||||
mod_install "$output_file" -n "$mod_name" --mod-id "$nexus_mod_id" --file-id "$nexus_mod_file_id" --version "$nexus_mod_version"
|
||||
else
|
||||
# replace the mod if it exists
|
||||
existing_mod_index=$(get_index_by_entry_from_db "$existing_mod_entry")
|
||||
mod_index=$(get_index_by_entry_from_db "$existing_mod_entry")
|
||||
|
||||
mod_uninstall -i "$mod_index"
|
||||
|
||||
mod_install "$output_file" -n "$mod_name" --mod-id "$nexus_mod_id" --file-id "$nexus_mod_file_id" --version "$nexus_mod_version"
|
||||
|
||||
# get the current mod index from what we just installed, last line of the db
|
||||
new_mod_index=$(get_index_by_entry_from_db "$(awk -F, 'END {print $1}' "$DB_FILE")")
|
||||
[[ -z "$new_mod_index" ]] && { log ERROR "Could not get current mod index."; exit 1; }
|
||||
|
||||
[[ $mod_index -ne $old_mod_index ]] && mod_order -i "$new_mod_index" "$mod_index"
|
||||
|
||||
log INFO "Mod ${GREEN}successfully${NC} updated: $mod_name."
|
||||
|
||||
disable_all_modpacks
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Update Check ---
|
||||
|
||||
function check_h2mm_update() {
|
||||
latest_version=$(curl -sS "$VERSION_URL")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log ERROR "Could not check for updates."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$latest_version" != "$VERSION" ]]; then
|
||||
log WARNING "A new version of h2mm is available: ${ORANGE}$VERSION${NC} -> ${GREEN}$latest_version${NC} => run 'h2mm update'."
|
||||
fi
|
||||
}
|
||||
|
||||
function check_for_updates() {
|
||||
last_update=$(cat "$LAST_CHECKED_UPDATE_FILE")
|
||||
if [[ -n "$last_update" && "$(date +%s)" -lt $(("$(date +%s -d "$last_update")" + 7200)) ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log INFO "Checking for updates..."
|
||||
|
||||
check_h2mm_update
|
||||
nexus_check_for_updates
|
||||
|
||||
echo "$(date)" > "$LAST_CHECKED_UPDATE_FILE"
|
||||
}
|
||||
|
||||
# --- Main ---
|
||||
|
||||
function main() {
|
||||
@@ -1545,6 +1913,15 @@ function main() {
|
||||
"modpack-reset"|"mr")
|
||||
modpack_reset "$@"
|
||||
;;
|
||||
"nexus-setup"|"ns")
|
||||
nexus_setup "$@"
|
||||
;;
|
||||
"nexus-update"|"nu")
|
||||
nexus_update_mods "$@"
|
||||
;;
|
||||
"nexus")
|
||||
nexus "$@"
|
||||
;;
|
||||
"reset"|"r")
|
||||
mod_reset "$@"
|
||||
;;
|
||||
|
||||
@@ -45,6 +45,7 @@ breaking_changes_patches=(
|
||||
["2"]='sed -i "s/^\([0-9]\+\),/\1,ENABLED,/" "$1/mods.csv"'
|
||||
["3"]='sed -i "1 i\\3" "$1/mods.csv"'
|
||||
["4"]='tmp_file=$(mktemp) && awk '\''BEGIN {FS=OFS=","} NR==1 {print 4; next} {print NR-1, $2, $3, $4, $5}'\'' "$1/mods.csv" > "$tmp_file" && tee "$1/mods.csv" < "$tmp_file" > /dev/null && rm "$tmp_file"'
|
||||
["5"]='sed -i "s/^\([0-9]\+\),\(.*\),\(.*\),\(.*\)/\1,\2,\3,,,,\4/" "$1/mods.csv"; sed -i "1 s/4/5/" "$1/mods.csv"'
|
||||
)
|
||||
|
||||
# notify if update is happening
|
||||
|
||||
Reference in New Issue
Block a user