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:
v4n
2025-04-11 23:29:06 +03:00
committed by GitHub
parent 5aafd2f16b
commit 146b711a9b
4 changed files with 467 additions and 128 deletions
+44 -83
View File
@@ -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
+421 -44
View File
@@ -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 "$@"
;;
+1
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
0.4.2
0.5.0