diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index c3d8d98..031aaa3 100755
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -12,6 +12,15 @@ updates:
target-branch: "develop"
assignees:
- "bobokun"
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ target-branch: "master"
+ assignees:
+ - "bobokun"
+ allow:
+ - dependency-name: "qbittorrent-api"
- package-ecosystem: github-actions
directory: '/'
schedule:
diff --git a/.github/workflows/update-supported-versions.yml b/.github/workflows/update-supported-versions.yml
new file mode 100644
index 0000000..25b95c9
--- /dev/null
+++ b/.github/workflows/update-supported-versions.yml
@@ -0,0 +1,50 @@
+name: Update Supported Versions
+
+on:
+ push:
+ branches:
+ - master
+ - develop
+ paths:
+ - "requirements.txt"
+ workflow_dispatch:
+ inputs:
+ targetBranch:
+ description: "Branch to run the script on (default: develop)"
+ required: false
+ default: "develop"
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ update-versions:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.targetBranch || github.ref_name }}
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.x"
+
+ - name: Install dependencies from requirements.txt
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+
+ - name: Run update script
+ run: python scripts/update-readme-version.py ${{ github.event.inputs.targetBranch || github.ref_name }}
+
+ - name: Create Pull Request
+ uses: peter-evans/create-pull-request@v6
+ with:
+ commit-message: Update SUPPORTED_VERSIONS.json
+ title: "Update SUPPORTED_VERSIONS.json for ${{ github.event.inputs.targetBranch || github.ref_name }}"
+ branch: update-supported-versions-${{ github.event.inputs.targetBranch || github.ref_name }}
+ base: develop
+ body: "This PR updates the SUPPORTED_VERSIONS.json to reflect new versions."
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b65cb64..5d6fb45 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -61,3 +61,11 @@ repos:
language: script
pass_filenames: false
stages: [commit]
+ - repo: local
+ hooks:
+ - id: update-readme-version
+ name: Update readme version
+ entry: ./scripts/pre-commit/update-readme-version.sh
+ language: script
+ pass_filenames: false
+ stages: [commit]
diff --git a/CHANGELOG b/CHANGELOG
index 1ea4bed..51a3afd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,17 +1,12 @@
-# Requirements Updated
-- qbittorrent-api==2024.3.60
-- GitPython==3.1.43
-
-# New Features
-- Added Group Uplaod Speed to share limits config to apply upload speed limits at a group level. Closes [#494](https://github.com/StuffAnThings/qbit_manage/issues/494)
-- Added Force Retag All in config settings to force retagging of all torrents. Closes [#513](https://github.com/StuffAnThings/qbit_manage/issues/513)
-- Added `--mover-old` command in mover script for users that use Mover Tuning Plugin
-
+# New Updates
+- Added docs to qbit-manage repo (under docs folder)
+- Bi-directional wiki sync
+- Automatically update supported version to README
+- Pre-commit hooks to update readme version
+- Dependabot to automatically update qbittorrent-api on master
# Bug Fixes
-- Fixes [#501](https://github.com/StuffAnThings/qbit_manage/issues/501)
-- Adds additional error checking for invalid share limits defined in config
-- Minor bug fixes in share limits
+- fixes [#522](https://github.com/StuffAnThings/qbit_manage/issues/522)
-Special thanks to @walkerp1 for their contributions!
-**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.0.9...v4.1.0
+Special thanks to @bakerboy448 for their contributions!
+**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.1.0...v4.1.1
diff --git a/README.md b/README.md
index d6ebf1a..d97957c 100755
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-#
qBit Manage
+# Qbittorrent Manage
+
+
[](https://github.com/StuffAnThings/qbit_manage/releases)
[](https://github.com/StuffAnThings/qbit_manage/tree/develop)
@@ -27,29 +29,34 @@ This is a program used to manage your qBittorrent instance such as:
### Master
-
-
+
+
+
### Develop
-
-
+
+
+
## Getting Started
Check out the [wiki](https://github.com/StuffAnThings/qbit_manage/wiki) for installation help
-1. Install qbit_manage either by installing Python 3.8.1+ on the localhost and following the [Local Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Local-Installations) Guide or by installing Docker and following the [Docker Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Docker-Installation) Guide or the [unRAID Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Unraid-Installation) Guide.
-2. Once installed, you have to [set up your Configuration](https://github.com/StuffAnThings/qbit_manage/wiki/Config-Setup) by create a [Configuration File](https://github.com/StuffAnThings/qbit_manage/blob/master/config/config.yml.sample) filled with all your values to connect to your qBittorrent instance.
-3. Please refer to the list of [Commands](https://github.com/StuffAnThings/qbit_manage/wiki/Commands) that can be used with this tool.
+1. Install qbit_manage either by installing Python 3.8.1+ on the localhost and following the [Local Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Local-Installations) Guide or by installing Docker and following the [Docker Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Docker-Installation) Guide or the [unRAID Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Unraid-Installation) Guide.
+1. Once installed, you have to [set up your Configuration](https://github.com/StuffAnThings/qbit_manage/wiki/Config-Setup) by create a [Configuration File](https://github.com/StuffAnThings/qbit_manage/blob/master/config/config.yml.sample) filled with all your values to connect to your qBittorrent instance.
+1. Please refer to the list of [Commands](https://github.com/StuffAnThings/qbit_manage/wiki/Commands) that can be used with this tool.
## Usage
+
To run the script in an interactive terminal with a list of possible commands run:
+
```bash
python qbit_manage.py -h
```
## Support
+
* If you have any questions or require support please join the [Notifiarr Discord](https://discord.com/invite/AURf8Yz) and post your question under the `qbit-manage` channel.
* If you're getting an Error or have an Enhancement post in the [Issues](https://github.com/StuffAnThings/qbit_manage/issues/new).
* If you have a configuration question post in the [Discussions](https://github.com/StuffAnThings/qbit_manage/discussions/new).
diff --git a/SUPPORTED_VERSIONS.json b/SUPPORTED_VERSIONS.json
new file mode 100644
index 0000000..a3648bd
--- /dev/null
+++ b/SUPPORTED_VERSIONS.json
@@ -0,0 +1,10 @@
+{
+ "master": {
+ "qbit": "v4.6.4",
+ "qbitapi": "2024.3.60"
+ },
+ "develop": {
+ "qbit": "v4.6.4",
+ "qbitapi": "2024.3.60"
+ }
+}
diff --git a/VERSION b/VERSION
index ee74734..627a3f4 100755
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-4.1.0
+4.1.1
diff --git a/config/config.yml.sample b/config/config.yml.sample
index ce11396..834aa0c 100755
--- a/config/config.yml.sample
+++ b/config/config.yml.sample
@@ -200,7 +200,7 @@ share_limits:
last_active: 43200
# Limit Upload Speed : Will limit the upload speed KiB/s (KiloBytes/second) (`-1` : No Limit)
limit_upload_speed: 0
- # Enable Group Upload Speed : Upload speed limits are applied at the group level. This will take limit_upload_speed defined and divide it equally among the number of torrents for the group.
+ # Enable Group Upload Speed : Upload speed limits are applied at the group level. This will take limit_upload_speed defined and divide it equally among the number of torrents in the group.
enable_group_upload_speed: false
# cleanup : WARNING!! Setting this as true Will remove and delete contents of any torrents that satisfies the share limits (max time OR max ratio)
cleanup: false
diff --git a/docs/Commands.md b/docs/Commands.md
new file mode 100644
index 0000000..35acd4d
--- /dev/null
+++ b/docs/Commands.md
@@ -0,0 +1,26 @@
+# qbit_manage Run Commands
+
+| **Shell Command** | **Docker Environment Variable** | **Config Command** | **Description** | **Default Value** |
+|:---------------------------------------:|:-------------------------------:|:---------------------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------:|
+| `-r` or`--run` | QBT_RUN | N/A | Run without the scheduler. Script will exit after completion. | False |
+| `-sch` or `--schedule` | QBT_SCHEDULE | N/A | Schedule to run every x minutes. (Default set to 1440 (1 day)) | 1440 |
+| `-sd` or `--startup-delay` | QBT_STARTUP_DELAY | N/A | Set delay in seconds on the first run of a schedule (Default set to 0) | 0 |
+| `-c CONFIG` or `--config-file CONFIG` | QBT_CONFIG | N/A | This is used if you want to use a different name for your config.yml. `Example: tv.yml` | config.yml |
+| `-lf LOGFILE,` or `--log-file LOGFILE,` | QBT_LOGFILE | N/A | This is used if you want to use a different name for your log file. `Example: tv.log` | activity.log |
+| `-cs` or `--cross-seed` | QBT_CROSS_SEED | cross_seed | Use this after running [cross-seed script](https://github.com/mmgoodnow/cross-seed) to add torrents from the cross-seed output folder to qBittorrent | False |
+| `-re` or `--recheck` | QBT_RECHECK | recheck | Recheck paused torrents sorted by lowest size. Resume if Completed. | False |
+| `-cu` or `--cat-update` | QBT_CAT_UPDATE | cat_update | Use this if you would like to update your categories or move from one category to another. | False |
+| `-tu` or `--tag-update` | QBT_TAG_UPDATE | tag_update | Use this if you would like to update your tags and/or set seed goals/limit upload speed by tag. (Only adds tags to untagged torrents) | False |
+| `-ru` or `--rem-unregistered` | QBT_REM_UNREGISTERED | rem_unregistered | Use this if you would like to remove unregistered torrents. (It will the delete data & torrent if it is not being cross-seeded, otherwise it will just remove the torrent without deleting data). Trackers that have an error and not covered by the remove unregistered logic will also be tagged as `issue` for manual review. | False |
+| `-tte` or `--tag-tracker-error` | QBT_TAG_TRACKER_ERROR | tag_tracker_error | Use this if you would like to tag torrents that do not have a working tracker. | False |
+| `-ro` or `--rem-orphaned` | QBT_REM_ORPHANED | rem_orphaned | Use this if you would like to remove orphaned files from your `root_dir` directory that are not referenced by any torrents. It will scan your `root_dir` directory and compare it with what is in qBittorrent. Any data not referenced in qBittorrent will be moved into `/data/torrents/orphaned_data` folder for you to review/delete. | False |
+| `-tnhl` or `--tag-nohardlinks` | QBT_TAG_NOHARDLINKS | tag_nohardlinks | Use this to tag any torrents that do not have any hard links associated with any of the files. This is useful for those that use Sonarr/Radarr that hard links your media files with the torrents for seeding. When files get upgraded they no longer become linked with your media therefore will be tagged with a new tag noHL. You can then safely delete/remove these torrents to free up any extra space that is not being used by your media folder. | False |
+| `-sl` or `--share-limits` | QBT_SHARE_LIMITS | share_limits | Control how torrent share limits are set depending on the priority of your grouping. Each torrent will be matched with the share limit group with the highest priority that meets the group filter criteria. Each torrent can only be matched with one share limit group. | False |
+| `-sc` or `--skip-cleanup` | QBT_SKIP_CLEANUP | skip_cleanup | Use this to skip emptying the Recycle Bin folder (`/root_dir/.RecycleBin`) and Orphaned directory. (`/root_dir/orphaned_data`) | False |
+| `-dr` or `--dry-run` | QBT_DRY_RUN | dry_run | If you would like to see what is gonna happen but not actually move/delete or tag/categorize anything. | False |
+| `-ll` or `--log-level LOGLEVEL` | QBT_LOG_LEVEL | N/A | Change the output log level. | INFO |
+| `--debug` | QBT_DEBUG | N/A | Adds debug logs | False |
+| `--trace` | QBT_TRACE | N/A | Adds trace logs | False |
+| `-d` or `--divider` | QBT_DIVIDER | N/A | Character that divides the sections (Default: '=') | = |
+| `-w` or `--width` | QBT_WIDTH | N/A | Screen Width (Default: 100) | 100 |
+| `-svc` or `--skip-qb-version-check` | QBT_SKIP_QB_VERSION_CHECK | skip_qb_version_check | Bypass qBittorrent/libtorrent version compatibility check. You run the risk of undesirable behavior and WILL RECIEVE NO SUPPORT. | False |
diff --git a/docs/Config-Setup.md b/docs/Config-Setup.md
new file mode 100644
index 0000000..88da97d
--- /dev/null
+++ b/docs/Config-Setup.md
@@ -0,0 +1,539 @@
+
+# Overview
+
+The script utilizes a YAML config file to load information to connect to the various APIs you can connect with.
+
+By default, the script looks at /config/config.yml for the Configuration File unless otherwise specified.
+
+A template Configuration File can be found in the repo [config/config.yml.sample](https://github.com/StuffAnThings/qbit_manage/blob/master/config/config.yml.sample).
+
+**WARNING**
+> As this software is constantly evolving and this wiki might not be up to date the sample shown here might not might not be current. Please refer to the repo for the most current version.
+
+# Config File
+
+## [Config Sample File](https://github.com/StuffAnThings/qbit_manage/blob/master/config/config.yml.sample)
+
+# **List of variables**
+
+## **commands:**
+
+---
+This section will ignore any [commands](Commands) that are defined via environment variable or command line and use the ones defined in this yaml file instead. Useful if you want to run qbm with multiple configurations files that execute different commands for each qbt instance.
+
+| Variable | Definition | Required |
+| :-------- | :---------------------------------------- | :----------------- |
+| `command` | The [command](Commands) that you want qbm to execute. | ❌ |
+
+## **qbt:**
+
+---
+This section defines your qBittorrent instance.
+
+| Variable | Definition | Required |
+| :------- | :---------------------------------- | :----------------- |
+| `host` | IP address of your qB installation. | ✅ |
+| `user` | The user name of your qB's webUI. | ❌ |
+| `pass` | Thee password of your qB's webUI. | ❌ |
+
+
+
+## **settings:**
+
+---
+This section defines any settings defined in the configuration.
+
+| Variable | Definition | Required |
+| :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------- |
+| `force_auto_tmm` | Will force qBittorrent to enable Automatic Torrent Management for each torrent. | ❌ |
+| `tracker_error_tag` | Define the tag of any torrents that do not have a working tracker. (Used in `--rem-unregistered` and `--tag-tracker-error`) | ❌ |
+| `nohardlinks_tag` | Define the tag of any torrents that don't have hardlinks (Used in `--tag-nohardlinks`) | ❌ |
+| `share_limits_tag` | Will add this tag when applying share limits to provide an easy way to filter torrents by share limit group/priority for each torrent. For example, if you have a share-limit group `cross-seed` with a priority of 2 and the default share_limits_tag `~share_limits` would add the tag `~share_limit_2.cross-seed` (Used in `--share-limits`) | ❌ |
+| `share_limits_min_seeding_time_tag` | Will add this tag when applying share limits to torrents that have not yet reached the minimum seeding time (Used in `--share-limits`) | ❌ |
+| `share_limits_min_num_seeds_tag` | Will add this tag when applying share limits to torrents that have not yet reached the minimum number of seeds (Used in `--share-limits`) | ❌ |
+| `share_limits_last_active_tag` | Will add this tag when applying share limits to torrents that have not yet reached the last active limit (Used in `--share-limits`) | ❌ |
+| `ignoreTags_OnUpdate` | When running `--tag-update` function, it will update torrent tags for a given torrent even if the torrent has one or more of the tags defined here. | ❌ |
+| `cross_seed_tag` | When running `--cross-seed` function, it will update any added cross-seed torrents with this tag. | ❌ |
+| `cat_filter_completed` | When running `--cat-update` function, it will filter for completed torrents only. | ❌ |
+| `share_limits_filter_completed` | When running `--share-limits` function, it will filter for completed torrents only. | ❌ |
+| `tag_nohardlinks_filter_completed` | When running `--tag-nohardlinks` function, , it will filter for completed torrents only. | ❌ |
+| `force_retag_all` | When running --tag-update function, it will force all torrents to update their tags. (May cause performance issues). **This does not remove any previous tags assigned** | ❌ |
+## **directory:**
+
+---
+This section defines the directories that qbit_manage will be looking into for various parts of the script.
+
+| Variable | Definition | Required |
+| :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ |
+| `cross_seed` | Output directory of cross-seed, originally the application [cross-seed](https://github.com/mmgoodnow/cross-seed) was incapable of injecting cross-seed torrent into qB, this was built to inject them for the application. This is no longer required if you're using injects with that software. However, you can find other uses for this as it is more of a watch directory now. | QBT_CROSS_SEED |
+| `root_dir` | Root downloads directory used to check for orphaned files, noHL, and remove unregistered. This directory is where you place all your downloads. This will need to be how qB views the directory where it places the downloads. This is required if you're using qbit_managee and/or qBittorrent within a container. | QBT_REM_ORPHANED / QBT_TAG_NOHARDLINKS / QBT_REM_UNREGISTERED |
+| `remote_dir` | Path of docker host mapping of root_dir, this must be set if you're running qbit_manage locally (not required if running qbit_manage in a container) and qBittorrent/cross_seed is in a docker. Essentially this is where your downloads are being kept on the host. | ❌ |
+| `recycle_bin` | Path of the RecycleBin folder. Default location is set to `remote_dir/.RecycleBin`. All files in this folder will be cleaned up based on your recycle bin settings. | ❌ |
+| `torrents_dir` | Path of the your qbittorrent torrents directory. Required for `save_torrents` attribute in recyclebin `/qbittorrent/data/BT_backup`. | ❌ |
+| `orphaned_dir` | Path of the Orphaned Directory folder. Default location is set to `remote_dir/orphaned_data`. All files in this folder will be cleaned up based on your orphaned data settings. Only orphaned data shall exist in this path as all contents are considered disposable. | ❌ |
+
+## **cat:**
+
+---
+This section defines the categories that you are currently using and the path's that are associated with them.
+> **NOTE** ALL categories must be defined, if it is in your qBit, then it **MUST** be defined here, if not the script will throw errors.
+
+| Configuration | Definition | Required |
+| :------------ | :------------------------ | :----------------- |
+| `key` | Name of the category | ✅ |
+| `value` | Save Path of the category | ✅ |
+
+The syntax for all the categories are as follows
+
+```yaml
+category: //category
+```
+
+## **cat_change:**
+
+---
+This moves all the torrents from one category to another category if the torrents are marked as complete.
+> **NOTE** **WARNING**: if the paths are different and Default Torrent Management Mode is set to automatic the files could be moved !!!
+
+| Configuration | Definition | Required |
+| :------------ | :---------------------------- | :----------------- |
+| `key` | Name of the original category | ✅ |
+| `value` | Name of the new category | ✅ |
+
+The syntax for the categories are as follows
+
+```yaml
+old_category_name: new_category_name
+```
+
+## **tracker:**
+
+---
+This section defines the tags used based upon the tracker's URL.
+| Configuration | Definition | Required |
+| :------------ | :------------------ | :----------------- |
+| `key` | Tracker URL Keyword. You can define multiple tracker urls by splitting with `\|` delimiter | ✅ |
+
+| Variable | Definition | Default Values | Required |
+| :------------------- | :-------------------------------------------------------------------------------------------------------------------------------- | :------------- | :----------------- |
+| `tag` | The tracker tag or additional list of tags defined | Tracker URL | ✅ |
+| `notifiarr` | Set this to the notifiarr react name. This is used to add indexer reactions to the notifications sent by Notifiarr | None | ❌ |
+
+If you are unsure what key word to use. Simply select a torrent within qB and down at the bottom you should see a tab that says `Trackers` within the list that is populated there are ea list of trackers that are associated with this torrent, select a key word from there and add it to the config file. Make sure this key word is unique enough that the script will not get confused with any other tracker.
+
+## **nohardlinks:**
+
+---
+Hardlinking data allows you to have your data in both the torrent directory and your media direectory at the same time without using double the amount of data.
+
+If you're needing information regarding hardlinks here are some excellent resources.
+
+[](https://www.youtube.com/watch?v=AMcHsQJ7My0)
+
+* [Trash-Guides: Hardlinks and Instant Moves (Atomic-Moves)](https://trash-guides.info/Hardlinks/Hardlinks-and-Instant-Moves/)
+* [Wikipedia: Hardlinks](https://en.wikipedia.org/wiki/Hard_link)
+
+Mandatory to fill out [directory parameter](#directory) above to use this function (root_dir/remote_dir)
+Beyond this you'll need to use one of the [categories](#cat) above as the key, and should be pointed to the `completed-` directory of your torrents.
+
+| Configuration | Definition | Required |
+| :------------ | :--------------------------------------------------------------- | :----------------- |
+| `key` | Category name of your completed movies/completed series in qbit. | ✅ |
+
+| Variable | Definition | Default Values | Required |
+| :------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :----------------- |
+| `exclude_tags` | List of tags to exclude from the check. Torrents with any of these tags will not be processed. This is useful to exclude certain trackers from being scanned for hardlinking purposes | None | ❌ |
+
+## **share_limits:**
+
+Control how torrent share limits are set depending on the priority of your grouping. This can apply a max ratio, seed time limits to your torrents or limit your torrent upload speed as well. Each torrent will be matched with the share limit group with the highest priority that meets the group filter criteria. Each torrent can only be matched with one share limit group.
+| Configuration | Definition | Required |
+| :------------ | :--------------------------------------------------------------- | :----------------- |
+| `key` | This variable is mandatory and is a text defining the name of your grouping. This can be any string you want. | ✅ |
+
+| Variable | Definition | Default Values | Type | Required |
+| :------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :----------------- | :----------------- |
+| `priority` | This is the priority of your grouping. The lower the number the higher the priority. This determines the order in which share limits are applied based on the filters set in this group | largest priority + 1 | int/float | ✅ |
+| `include_all_tags` | Filter the group based on one or more tags. Multiple include_all_tags are checked with an **AND** condition. All tags defined here must be present in the torrent for it to be included in this group | None | list | ❌ |
+| `include_any_tags` | Filter the group based on one or more tags. Multiple include_any_tags are checked with an **OR** condition. Any tags defined here must be present in the torrent for it to be included in this group | None | list | ❌ |
+| `exclude_all_tags` | Filter the group based on one or more tags. Multiple exclude_all_tags are checked with an **AND** condition. All tags defined here must be present in the torrent for it to be excluded in this group | None | list | ❌ |
+| `exclude_any_tags` | Filter the group based on one or more tags. Multiple exclude_any_tags are checked with an **OR** condition. Any tags defined here must be present in the torrent for it to be excluded in this group | None | list | ❌ |
+| `categories` | Filter by including one or more categories. Multiple categories are checked with an **OR** condition. Since one torrent can only be associated with a single category, multiple categories are checked with an **OR** condition | None | list | ❌ |
+| `cleanup` | **WARNING!!** Setting this as true will remove and delete contents of any torrents that satisfies the share limits **(max time OR max ratio)** It will also delete the torrent's data if and only if no other torrents are using the same folder/files. | False | bool | ❌ |
+| `max_ratio` | Will set the torrent Maximum share ratio until torrent is stopped from seeding/uploading and may be cleaned up / removed if the minimums have been met. (`-2` : Global Limit , `-1` : No Limit) | -1 | float | ❌ |
+| `max_seeding_time` | Will set the torrent Maximum seeding time (minutes) until torrent is stopped from seeding/uploading and may be cleaned up / removed if the minimums have been met. (`-2` : Global Limit , `-1` : No Limit) | -1 | int | ❌ |
+| `min_seeding_time` | Will prevent torrent deletion by cleanup variable if torrent has not yet met minimum seeding time (minutes) even if maximum seeding ratio is met. If the torrent has not yet reached this minimum seeding time, it will change the share limits back to no limits and resume the torrent to continue seeding. Mandatory that `max_seeding_time` is greater than or equal to `min_seeding_time`. If you are not setting a `max_ratio`, then use `max_seeding_time` instead. Mandatory that `max_ratio` is configured and greater than 0. | 0 | int | ❌ |
+| `last_active` |Will prevent torrent deletion by cleanup variable if torrent has been active within the last x minutes. If the torrent has been active within the last x minutes, it will change the share limits back to no limits and resume the torrent to continue seeding. | 0 | int | ❌ |
+| `limit_upload_speed` | Will limit the upload speed KiB/s (KiloBytes/second) (`-1` : No Limit) | -1 | int | ❌ |
+| `enable_group_upload_speed` | Upload speed limits are applied at the group level. This will take `limit_upload_speed` defined and divide it equally among the number of torrents in the group. | False | bool | ❌ |
+| `resume_torrent_after_change` | Will resume your torrent after changing share limits. | True | bool | ❌ |
+| `add_group_to_tag` | This adds your grouping as a tag with a prefix defined in settings (share_limits_tag). Example: A grouping named noHL with a priority of 1 will have a tag set to `~share_limit_1.noHL` (if using the default prefix). | True | bool | ❌ |
+| `min_num_seeds` | Will prevent torrent deletion by cleanup variable if the number of seeds is less than the value set here (depending on the tracker, you may or may not be included). If the torrent has less number of seeds than the min_num_seeds, the share limits will be changed back to no limits and resume the torrent to continue seeding. | 0 | int | ❌ |
+## **recyclebin:**
+
+---
+ Recycle Bin method of deletion will move files into the recycle bin (Located in /root_dir/.RecycleBin) instead of directly deleting them in qbit.
+
+ This is very useful if you're hesitant about using this script to delete information off your system hingswithout first checking it. Plus with the ability of this script to remove trumped/unregistered torrents there is a very small chance that something may happen to cause the script to go to town on your library. With the recycling bin in place your data is secure (unless the bin is emptied before this issue is caught). All you'd need to do to recover would be to place the data back into the correct directory, redownload the torrent file from the tracker and recheck the torrent with the tracker from the UI.
+
+| Variable | Definition | Default Values | Required |
+| :------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :----------------- |
+| `enable` | `true` or `false` | `true` | ✅ |
+| `empty_after_x_days` | Will delete Recycle Bin contents if the files have been in the Recycle Bin for more than x days. (Uses date modified to track the time) | None | ❌ |
+| `save_torrents` | This will save a copy of your .torrent and .fastresume file in the recycle bin before deleting it from qbittorrent. This requires the [torrents_dir](#directory) to be defined | False | ❌ |
+| `split_by_category` | This will split the recycle bin folder by the save path defined in the [cat](#cat) attribute and add the base folder name of the recycle bin that was defined in [recycle_bin](#directory) | False | ❌ |
+> Note: The more time you place for the `empty_after_x_days:` variable the better, allowing you more time to catch any mistakes by the script. If the variable is set to `0` it will delete contents immediately after every script run. If the variable is not set it will never delete the contents of the Recycle Bin.
+
+## **orphaned:**
+
+---
+This section allows for the exclusion of certain files from being considered "Orphaned"
+
+This is handy when you have automatically generated files that certain OSs decide to make. `.DS_Store` Is a primary example, for those who use MacOS.
+
+| Variable | Definition | Default Values | Required |
+| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | :------------- | :----------------- |
+| `empty_after_x_days` | Will delete Orphaned data contents if the files have been in the Orphaned data for more than x days. (Uses date modified to track the time) | None | ❌ |
+| `exclude_patterns` | List of [patterns](https://commandbox.ortusbooks.com/usage/parameters/globbing-patterns) to exclude certain files from orphaned | None | ❌ |
+> Note: The more time you place for the `empty_after_x_days:` variable the better, allowing you more time to catch any mistakes by the script. If the variable is set to `0` it will delete contents immediately after every script run. If the variable is not set it will never delete the contents of the Orphaned Data.
+
+## **apprise:**
+
+---
+[Apprise](https://github.com/caronc/apprise) integration is used in conjunction with webhooks to allow notifications via apprise-api.
+
+| Variable | Definition | Default Values | Required |
+| :----------- | :------------------------------------------------------------------ | :------------- | :----------------- |
+| `api_url` | Apprise API Endpoint URL | N/A | ✅ |
+| `notify_url` | [Notification Services URL](https://github.com/caronc/apprise/wiki) | N/A | ✅ |
+
+## **notifiarr:**
+
+---
+[Notifiarr](https://notifiarr.com/) integration is used in conjunction with webhooks to allow discord notifications via Notifiarr.
+
+| Variable | Definition | Default Values | Required |
+| :--------- | :------------------------------------------------------------------------------------------------------ | :------------- | :----------------- |
+| `apikey` | Notifiarr API Key | N/A | ✅ |
+| `instance` | Optional unique value used to identify your instance. (could be your username on notifiarr for example) | N/A | ❌ |
+
+## **webhooks:**
+
+---
+Provide webhook notifications based on event triggers
+
+| Variable | Notification Sent | Default Values | Required |
+| :-------------------------------------------------------------- | :------------------------------------------------------------------- | :------------- | :----------------- |
+| [error](#error-notifications) | When errors occur during the run | N/A | ❌ |
+| [run_start](#run-start-notifications) | At the beginning of every run | N/A | ❌ |
+| [run_end](#run-end-notifications) | At the end of every run | N/A | ❌ |
+| [cross_seed](#cross-seed-notifications) | During the cross-seed function | N/A | ❌ |
+| [recheck](#recheck-notifications) | During the recheck function | N/A | ❌ |
+| [cat_update](#category-update-notifications) | During the category update function | N/A | ❌ |
+| [tag_update](#tag-update-notifications) | During the tag update function | N/A | ❌ |
+| [rem_unregistered](#remove-unregistered-torrents-notifications) | During the removing unregistered torrents function | N/A | ❌ |
+| [tag_tracker_error](#tag-tracker-error-notifications) | During the removing unregistered torrents/tag tracker error function | N/A | ❌ |
+| [rem_orphaned](#remove-orphaned-files-notifications) | During the removing orphaned function | N/A | ❌ |
+| [tag_nohardlinks](#tag-no-hardlinks-notifications) | During the tag no hardlinks function | N/A | ❌ |
+| [share_limits](#share-limits-notifications) | During the share limits function | N/A | ❌ |
+| [cleanup_dirs](#cleanup-directories-notifications) | When files are deleted from certain directories | N/A | ❌ |
+
+### **Error Notifications**
+
+Payload will be sent on any errors
+
+```yaml
+{
+ "function": "run_error", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Error Message of the Payload
+ "critical": bool, // Critical Error
+ "type": str // severity of error
+}
+```
+
+### **Run Start Notifications**
+
+Payload will be sent at the start of the run
+
+```yaml
+{
+ "function": "run_start", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "start_time": str, // Time Run is started Format "YYYY-mm-dd HH:MM:SS"
+ "dry_run": bool // Dry-Run
+}
+```
+
+### **Run End Notifications**
+
+Payload will be sent at the end of the run
+
+```yaml
+{
+ "function": "run_end", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "start_time": str, // Time Run started Format "YYYY-mm-dd HH:MM:SS"
+ "end_time": str, // Time Run ended Format "YYYY-mm-dd HH:MM:SS"
+ "next_run": str, // Time Next Run Format "YYYY-mm-dd HH:MM:SS"
+ "run_time": str, // Total Run Time "H:MM:SS"
+ "torrents_added": int, // Total Torrents Added
+ "torrents_deleted": int, // Total Torrents Deleted
+ "torrents_deleted_and_contents_count": int, // Total Torrents + Contents Deleted
+ "torrents_resumed": int, // Total Torrents Resumed
+ "torrents_rechecked": int, // Total Torrents Rechecked
+ "torrents_categorized": int, // Total Torrents Categorized
+ "torrents_tagged": int, // Total Torrents Tagged
+ "remove_unregistered": int, // Total Unregistered Torrents Removed
+ "torrents_tagged_tracker_error": int, // Total Tracker Error Torrents Tagged
+ "torrents_untagged_tracker_error": int, // Total Tracker Error Torrents untagged
+ "orphaned_files_found": int, // Total Orphaned Files Found
+ "torrents_tagged_no_hardlinks": int, // Total noHL Torrents Tagged
+ "torrents_untagged_no_hardlinks": int, // Total noHL Torrents untagged
+ "torrents_updated_share_limits": int // Total Share Limits updated
+ "torrents_cleaned_share_limits": int // Total Share Limit Torrents Cleaned (Deleted + Contents Deleted)
+ "files_deleted_from_recyclebin": int, // Total Files Deleted from Recycle Bin
+ "files_deleted_from_orphaned": int // Total Files Deleted from Orphaned Data
+}
+```
+
+### **Cross-Seed Notifications**
+
+Payload will be sent when adding a cross-seed torrent to qBittorrent if the original torrent is complete
+
+```yaml
+{
+ "function": "cross_seed", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // Torrent Category
+ "torrent_save_path": str, // Torrent Download directory
+ "torrent_tag": "cross-seed", // Total Torrents Added
+ "torrent_tracker": str // Torrent Tracker
+}
+```
+
+Payload will be sent when there are existing torrents found that are missing the cross-seed tag
+
+```yaml
+{
+ "function": "tag_cross_seed", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // Torrent Category
+ "torrent_tag": "cross-seed", // Tag Added
+ "torrent_tracker": str // Torrent Tracker
+}
+```
+
+### **Recheck Notifications**
+
+Payload will be sent when rechecking/resuming a torrent that is paused
+
+```yaml
+{
+ "function": "recheck", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_tag": str, // Torrent Tags
+ "torrent_category": str, // Torrent Category
+ "torrent_tracker": str, // Torrent Tracker URL
+ "notifiarr_indexer": str, // Notifiarr React name/id for indexer
+}
+```
+
+### **Category Update Notifications**
+
+Payload will be sent when updating torrents with missing category
+
+```yaml
+{
+ "function": "cat_update", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // New Torrent Category
+ "torrent_tag": str, // Torrent Tags
+ "torrent_tracker": str, // Torrent Tracker URL
+ "notifiarr_indexer": str, // Notifiarr React name/id for indexer
+}
+```
+
+### **Tag Update Notifications**
+
+Payload will be sent when updating torrents with missing tag
+
+```yaml
+{
+ "function": "tag_update", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // Torrent Category
+ "torrent_tag": str, // New Torrent Tag
+ "torrent_tracker": str, // Torrent Tracker URL
+ "notifiarr_indexer": str, // Notifiarr React name/id for indexer
+}
+```
+
+### **Remove Unregistered Torrents Notifications**
+
+Payload will be sent when Unregistered Torrents are found
+
+```yaml
+{
+ "function": "rem_unregistered", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // Torrent Category
+ "torrent_status": str, // Torrent Tracker Status message
+ "torrent_tag": str, // Torrent Tags
+ "torrent_tracker": str, // Torrent Tracker URL
+ "notifiarr_indexer": str, // Notifiarr React name/id for indexer
+ "torrents_deleted_and_contents": bool, // Deleted Torrents and contents or Deleted just the torrent
+}
+```
+
+### **Tag Tracker Error Notifications**
+
+Payload will be sent when trackers with errors are tagged/untagged
+
+```yaml
+{
+ "function": "tag_tracker_error", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // Torrent Category
+ "torrent_tag": "issue", // Tag Added
+ "torrent_status": str, // Torrent Tracker Status message
+ "torrent_tracker": str, // Torrent Tracker URL
+ "notifiarr_indexer": str, // Notifiarr React name/id for indexer
+}
+```
+
+```yaml
+{
+ "function": "untag_tracker_error", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // Torrent Category
+ "torrent_tag": str, // Tag Added
+ "torrent_tracker": str, // Torrent Tracker URL
+ "notifiarr_indexer": str, // Notifiarr React name/id for indexer
+}
+```
+
+### **Remove Orphaned Files Notifications**
+
+Payload will be sent when Orphaned Files are found and moved into the orphaned folder
+
+```yaml
+{
+ "function": "rem_orphaned", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "orphaned_files": list, // List of orphaned files
+ "orphaned_directory": str, // Folder path where orphaned files will be moved to
+ "total_orphaned_files": int, // Total number of orphaned files found
+}
+```
+
+### **Tag No Hardlinks Notifications**
+
+Payload will be sent when no hard links are found for any files in a particular torrent
+
+```yaml
+{
+ "function": "tag_nohardlinks", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // Torrent Category
+ "torrent_tag": 'noHL', // Add `noHL` to Torrent Tags
+ "torrent_tracker": str, // Torrent Tracker URL
+ "notifiarr_indexer": str, // Notifiarr React name/id for indexer
+}
+```
+
+Payload will be sent when hard links are found for any torrents that were previously tagged with `noHL`
+
+```yaml
+{
+ "function": "untag_nohardlinks", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // Torrent Category
+ "torrent_tag": 'noHL', // Remove `noHL` from Torrent Tags
+ "torrent_tracker": str, // Torrent Tracker URL
+ "notifiarr_indexer": str, // Notifiarr React name/id for indexer
+}
+```
+
+### **Share Limits Notifications**
+Payload will be sent when Share Limits are updated for a specific group
+```yaml
+{
+ "function": "share_limits", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "grouping": str, // Share Limit group name
+ "torrents": [str], // List of Torrent Names
+ "torrent_tag": str, // Torrent Tags
+ "torrent_max_ratio": float, // Set the Max Ratio Share Limit
+ "torrent_max_seeding_time": int, // Set the Max Seeding Time (minutes) Share Limit
+ "torrent_min_seeding_time": int, // Set the Min Seeding Time (minutes) Share Limit
+ "torrent_limit_upload_speed": int // Set the the torrent upload speed limit (kB/s)
+}
+```
+
+Payload will be sent when `cleanup` flag is set to true and torrent meets share limit criteria.
+
+```yaml
+{
+ "function": "cleanup_share_limits", // Webhook Trigger keyword
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "grouping": str, // Share Limit group name
+ "torrents": [str], // List of Torrent Names
+ "torrent_category": str, // Torrent Category
+ "cleanup": True, // Cleanup flag
+ "torrent_tracker": str, // Torrent Tracker URL
+ "notifiarr_indexer": str, // Notifiarr React name/id for indexer
+ "torrents_deleted_and_contents": bool, // Deleted Torrents and contents or Deleted just the torrent
+}
+```
+
+
+### **Cleanup directories Notifications**
+
+Payload will be sent when files are deleted/cleaned up from the various folders
+
+```yaml
+{
+ "function": "cleanup_dirs", // Webhook Trigger keyword
+ "location": str, // Location of the folder that is being cleaned
+ "title": str, // Title of the Payload
+ "body": str, // Message of the Payload
+ "files": list, // List of files that were deleted from the location
+ "empty_after_x_days": int, // Number of days that the files will be kept in the location
+ "size_in_bytes": int, // Total number of bytes deleted from the location
+}
+```
+
+## **bhd:**
+
+---
+BHD integration is used if you are on the private tracker BHD. (Used to identify any unregistered torrents from this tracker)
+
+| Variable | Definition | Default Values | Required |
+| :------- | :---------- | :------------- | :----------------- |
+| `apikey` | BHD API Key | `None` (blank) | ✅ |
diff --git a/docs/Docker-Installation.md b/docs/Docker-Installation.md
new file mode 100644
index 0000000..aa6d22a
--- /dev/null
+++ b/docs/Docker-Installation.md
@@ -0,0 +1,85 @@
+# Docker Installation
+
+A simple Dockerfile is available in this repo if you'd like to build it yourself. The official build is also available from dockerhub [here](https://hub.docker.com/r/bobokun/qbit_manage):
+
+`docker run -it -v :/config:rw bobokun/qbit_manage`
+
+* The -v :/config:rw mounts the location you choose as a persistent volume to store your files.
+ * Change to a folder where your config.yml and other files are.
+ * The docker image defaults to running the config named config.yml in your persistent volume.
+ * Use quotes around the whole thing if your path has spaces i.e. -v ":/config:rw"
+
+* Fill out your location for your downloads downloads folder (`Root_Dir`).
+ 1. qbit_manage needs to be able to view all torrents the way that your qbittorrent views them.
+ 1. Example: If you have qbittorrent mapped to `/mnt/user/data/:/data` This means that you **MUST** have qbit_managed mapped the same way.
+ 2. Furthermore, the config file must map the root directory you wish to monitor. This means that in our example of `/data` (which is how qbittorrent views the torrents) that if in your `/data` directory you drill down to `/torrents` that you'll need to update your config file to `/data/torrents`
+ 2. This could be different depending on your specific setup.
+ 3. The key takeaways are
+ 1. Both qbit_manage needs to have the same mappings as qbittorrent
+ 2. The config file needs to drill down (if required) further to the desired root dir.
+* `remote_dir`: is not required and can be commented out with `#`
+
+Below is a list of the docker environment variables
+| Docker Environment Variable | Description | Default Value |
+| :-------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ |
+| QBT_RUN | Run without the scheduler. Script will exit after completion. | False |
+| QBT_SCHEDULE | Schedule to run every x minutes. (Default set to 1440) | 1440 |
+| QBT_STARTUP_DELAY | Initial run will start after x seconds (Default set to 0) | 0 |
+| QBT_CONFIG | This is used if you want to use a different name for your config.yml. `Example: tv.yml` This variable can also be used to allow the use of multiple config files for a single instance of qbit-manage. For example, listing a wildcard value `Example: QBIT_CONFIG=config_*.yml` and naming your config files accordingly `Example: config_movies.yml` and `config_tv.yml` will instruct qbit-manage to utilize each config file that matches the specified naming convention during every run. | config.yml |
+| QBT_LOGFILE | This is used if you want to use a different name for your log file. `Example: tv.log` | activity.log |
+| QBT_CROSS_SEED | Use this after running [cross-seed script](https://github.com/mmgoodnow/cross-seed) to add torrents from the cross-seed output folder to qBittorrent | False |
+| QBT_RECHECK | Recheck paused torrents sorted by lowest size. Resume if Completed. | False |
+| QBT_CAT_UPDATE | Use this if you would like to update your categories or move from one category to another.. | False |
+| QBT_TAG_UPDATE | Use this if you would like to update your tags. (Only adds tags to untagged torrents) | False |
+| QBT_REM_UNREGISTERED | Use this if you would like to remove unregistered torrents. (It will the delete data & torrent if it is not being cross-seeded, otherwise it will just remove the torrent without deleting data) | False |
+| QBT_TAG_TRACKER_ERROR | Use this to tag any torrents with tracker errors, such as unregistered torrents or unreachable trackers. | False |
+| QBT_REM_ORPHANED | Use this if you would like to remove orphaned files from your `root_dir` directory that are not referenced by any torrents. It will scan your `root_dir` directory and compare it with what is in qBittorrent. Any data not referenced in qBittorrent will be moved into `/data/torrents/orphaned_data` folder for you to review/delete. | False |
+| QBT_TAG_NOHARDLINKS | Use this to tag any torrents that do not have any hard links associated with any of the files. This is useful for those that use Sonarr/Radarr that hard links your media files with the torrents for seeding. When files get upgraded they no longer become linked with your media therefore will be tagged with a new tag noHL. You can then safely delete/remove these torrents to free up any extra space that is not being used by your media folder. | False |
+| QBT_SHARE_LIMITS | Control how torrent share limits are set depending on the priority of your grouping. This can apply a max ratio, seed time limits to your torrents or limit your torrent upload speed as well. Each torrent will be matched with the share limit group with the highest priority that meets the group filter criteria. Each torrent can only be matched with one share limit group. | False |
+| QBT_SKIP_CLEANUP | Use this to skip emptying the Recycle Bin folder (`/root_dir/.RecycleBin`) and Orphaned directory. (`/root_dir/orphaned_data`) | False |
+| QBT_SKIP_QB_VERSION_CHECK | Use this to bypass qBittorrent/libtorrent version compatibility check. You run the risk of undesirable behavior and will receive no support. | False |
+| QBT_DRY_RUN | If you would like to see what is gonna happen but not actually move/delete or tag/categorize anything. | False |
+| QBT_LOG_LEVEL | Change the output log level. | INFO |
+| QBT_DIVIDER | Character that divides the sections (Default: '=') | = |
+| QBT_WIDTH | Screen Width (Default: 100) | 100 |
+| QBT_DEBUG | Enable Debug logs | False |
+| QBT_TRACE | Enable Trace logs | False |
+
+Here is an example of a docker compose
+
+```yaml
+version: "3.7"
+services:
+ qbit_manage:
+ container_name: qbit_manage
+ image: bobokun/qbit_manage
+ volumes:
+ - /mnt/user/appdata/qbit_manage/:/config:rw
+ - /mnt/user/data/torrents/:/data/torrents:rw
+ - /mnt/user/appdata/qbittorrent/:/qbittorrent/:ro
+ environment:
+ - QBT_RUN=false
+ - QBT_SCHEDULE=1440
+ - QBT_CONFIG=config.yml
+ - QBT_LOGFILE=activity.log
+ - QBT_CROSS_SEED=false
+ - QBT_RECHECK=false
+ - QBT_CAT_UPDATE=false
+ - QBT_TAG_UPDATE=false
+ - QBT_REM_UNREGISTERED=false
+ - QBT_REM_ORPHANED=false
+ - QBT_TAG_TRACKER_ERROR=false
+ - QBT_TAG_NOHARDLINKS=false
+ - QBT_SHARE_LIMITS=false
+ - QBT_SKIP_CLEANUP=false
+ - QBT_DRY_RUN=false
+ - QBT_LOG_LEVEL=INFO
+ - QBT_DIVIDER==
+ - QBT_WIDTH=100
+ restart: on-failure:2
+```
+
+You will also need to define not just the config volume but the volume to your torrents, this is in order to use the recycling bin, remove orphans and the no hard link options
+
+Here we have `/mnt/user/data/torrents/` mapped to `/data/torrents/` furthermore in the config file associated with it the root_dir is mapped to `/data/torrents/`
+We also have `/mnt/user/appdata/qbittorrent/` mapped to `/qbittorrent` and in the config file we associated torrents_dir to `/qbittorrent/data/BT_backup` to use the save_torrents functionality
diff --git a/docs/Home.md b/docs/Home.md
new file mode 100644
index 0000000..b8161a4
--- /dev/null
+++ b/docs/Home.md
@@ -0,0 +1,45 @@
+# qBit_manage Wiki
+
+This wiki should tell you everything you need to know about the script to get it working.
+
+## Getting Started
+
+1. Install qbit_manage either by installing Python3.8.1+ on the localhost and following the [Local Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Local-Installations) Guide or by installing Docker and following the [Docker Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Docker-Installation) Guide or the [unRAID Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Unraid-Installation) Guide.
+2. Once installed, you have to [set up your Configuration](https://github.com/StuffAnThings/qbit_manage/wiki/Config-Setup) by create a [Configuration File](https://github.com/StuffAnThings/qbit_manage/blob/master/config/config.yml.sample) filled with all your values to connect to your qBittorrent instance.
+3. Please refer to the list of [Commands](https://github.com/StuffAnThings/qbit_manage/wiki/Commands) that can be used with this tool.
+
+## Support
+
+* If you have any questions or require support please join the [Notifiarr Discord](https://discord.com/invite/AURf8Yz) and post your question under the `qbit-manage` channel.
+* If you're getting an Error or have an Enhancement post in the [Issues](https://github.com/StuffAnThings/qbit_manage/issues/new).
+* If you have a configuration question post in the [Discussions](https://github.com/StuffAnThings/qbit_manage/discussions/new).
+* Pull Request are welcome but please submit them to the [develop branch](https://github.com/StuffAnThings/qbit_manage/tree/develop).
+
+## Table of Contents
+
+* [Home](Home)
+ * [Installation](Installation)
+ * [unRAID Installation](Unraid-Installation)
+ * [Local Installation](Local-Installations)
+ * [NIX Installation](Nix-Installation)
+ * [Docker Installation](Docker-Installation)
+ * [V4 Migration Guide](v4-Migration-Guide)
+ * [Config Setup](Config-Setup)
+ * [Sample Config File](Config-Setup#config-file)
+ * [List of variables](Config-Setup#list-of-variables)
+ * [commands](Config-Setup#commands)
+ * [qbt](Config-Setup#qbt)
+ * [settings](Config-Setup#settings)
+ * [directory](Config-Setup#directory)
+ * [cat](Config-Setup#cat)
+ * [cat_changes](Config-Setup#cat_changes)
+ * [tracker](Config-Setup#tracker)
+ * [nohardlinks](Config-Setup#nohardlinks)
+ * [share_limits](Config-Setup#share_limits)
+ * [recyclebin](Config-Setup#recyclebin)
+ * [orphaned](Config-Setup#orphaned)
+ * [apprise](Config-Setup#apprise)
+ * [notifiarr](Config-Setup#notifiarr)
+ * [webhooks](Config-Setup#webhooks)
+ * [bhd](Config-Setup#bhd)
+ * [Commands](Commands)
diff --git a/docs/Installation.md b/docs/Installation.md
new file mode 100644
index 0000000..2392133
--- /dev/null
+++ b/docs/Installation.md
@@ -0,0 +1,7 @@
+# Installation Table of Contents
+
+- [Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Installation)
+ - [unRAID Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Unraid-Installation)
+ - [Local Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Local-Installations)
+ - [NIX Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Nix-Installation)
+ - [Docker Installation](https://github.com/StuffAnThings/qbit_manage/wiki/Docker-Installation)
diff --git a/docs/Local-Installations.md b/docs/Local-Installations.md
new file mode 100644
index 0000000..14090d1
--- /dev/null
+++ b/docs/Local-Installations.md
@@ -0,0 +1,52 @@
+# Local Installations
+
+* Requires `python 3.8.1`. Dependencies must be installed by running:
+
+Navigate to the directory you'd liked to clone the repo into
+
+Clone the repo
+
+```bash
+git clone https://github.com/StuffAnThings/qbit_manage
+```
+
+Install requirements
+
+```bash
+pip install -r requirements.txt
+```
+
+If there are issues installing dependencies try:
+
+```bash
+pip install -r requirements.txt --ignore-installed
+```
+
+## Usage
+
+To run the script in an interactive terminal run:
+
+* copy the `config.yml.sample` file to `config.yml`
+* Fill out the config file as outlined in the [Config-Setup](https://github.com/StuffAnThings/qbit_manage/wiki/Config-Setup)
+
+Run the script `-h` to see a list of commands
+
+```bash
+python qbit_manage.py -h
+```
+
+### Config
+
+To choose the location of the YAML config file
+
+```bash
+python qbit_manage.py --config-file
+```
+
+### Log
+
+To choose the location of the Log File
+
+```bash
+python qbit_manage.py --log-file
+```
diff --git a/docs/Nix-Installation.md b/docs/Nix-Installation.md
new file mode 100644
index 0000000..339c562
--- /dev/null
+++ b/docs/Nix-Installation.md
@@ -0,0 +1,70 @@
+# \*Nix Installation
+
+* Download the script
+
+```bash
+wget -O - https://github.com/StuffAnThings/qbit_manage/archive/master.tar.gz | tar xz --strip=1 "qbit_manage-master"
+```
+
+* Make it executable
+
+```bash
+chmod +x qbit_manage.py
+```
+
+* Get & Install Requirements
+
+```bash
+pip install -r requirements.txt
+```
+
+* Create Config
+
+```bash
+cd config
+cp config.yml.sample config.yml
+nano -e config.yml
+```
+
+* Create the update script
+
+```bash
+nano qbm-update.sh
+```
+
+* Paste the below into the update script and update the Paths and Service Name (if using systemd)
+
+```bash
+#!/bin/bash
+
+qbmPath="/home/bakerboy448/QbitManage"
+qbmVenvPath="$qbmPath"/"qbit-venv/"
+qbmServiceName="qbm"
+cd "$qbmPath" || exit
+currentVersion=$(cat VERSION)
+branch=$(git rev-parse --abbrev-ref HEAD)
+git fetch
+if [ "$(git rev-parse HEAD)" = "$(git rev-parse @'{u}')" ]; then
+ echo "=== Already up to date $currentVersion on $branch ==="
+ exit 0
+fi
+git pull
+newVersion=$(cat VERSION)
+"$qbmVenvPath"/bin/python -m pip install -r requirements.txt
+echo "=== Updated from $currentVersion to $newVersion on $branch ==="
+echo "=== Restarting qbm Service ==="
+sudo systemctl restart "$qbmServiceName"
+exit 0
+```
+
+* Make the update script executable
+
+```bash
+chmod +x qbm-update.sh
+```
+
+* Run the update script
+
+```bash
+./qbm-update.sh
+```
diff --git a/docs/Unraid-Installation.md b/docs/Unraid-Installation.md
new file mode 100644
index 0000000..e91c537
--- /dev/null
+++ b/docs/Unraid-Installation.md
@@ -0,0 +1,104 @@
+
+# Unraid Installation - Docker (Recommended)
+
+Thankfully, getting qbit_manager working on unRAID is a fairly simple task. unRAID works mostly with docker containers, so the pre-built container available on docker hub works perfectly with a little configuration. To install a container from docker hub, you will need community applications - a very popular plugin for unRAID servers. If you don't already have this installed, you can install it [here](https://forums.unraid.net/topic/38582-plug-in-community-applications/)
+
+## Basic Installation
+
+1. Head to the Apps tab of unRAID (Community Applications), and search qbit_manage in the upper left search box.
+2. Once you have searched for qbit_manage you can simply select it from the list of containers and select install.
+3. The template should show all variables that can be edited.
+4. Fill out your location for your downloads downloads folder (`Root_Dir`).
+ 1. qbit_manage needs to be able to view all torrents the way that your qbittorrent views them.
+ 1. Example: If you have qbittorrent mapped to `/mnt/user/data/:/data` This means that you **MUST** have qbit_managed mapped the same way.
+ 2. Furthermore, the config file must map the root directory you wish to monitor. This means that in our example of `/data` (which is how qbittorrent views the torrents) that if in your `/data` directory you drill down to `/torrents` that you'll need to update your config file to `/data/torrents`
+ 2. This could be different depending on your specific setup.
+ 3. The key takeaways are
+ 1. Both qbit_manage needs to have the same mappings as qbittorrent
+ 2. The config file needs to drill down (if required) further to the desired root dir.
+5. Select what QBT env options you want to enable or disable (true/false).
+6. Hit Apply, and allow unRAID to download the docker container.
+7. Navigate to the Docker tab in unRAID, and stop the qbit_manage container if it has auto-started.
+8. Create the [config.yml](https://github.com/StuffAnThings/qbit_manage/blob/master/config/config.yml.sample) file as-per the [config-setup documentation](https://github.com/StuffAnThings/qbit_manage/wiki/Config-Setup) and place in the Appdata folder (`/mnt/user/appdata/qbit_manage/` in the example) **Remember to remove the .sample from the filename**
+ 1. `remote_dir`: is not required and can be commented out with `#`
+9. Once finished, run the container. Voila! Logs are located in `/mnt/user/appdata/qbit_manage/logs`.
+
+# Unraid Installation - Localhost (Alternative)
+
+We recommend using the Docker method to install qBit Manage but here is an alternative way to install it locally without the use of docker with the user of userscripts.
+
+**qBit Management**
+First, we are going to need [Nerd Pack](https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc/).
+This can be also downloaded from the **Apps** store
+
+Nerd pack will be located in the settings tab
+When you open it up you'll see a bunch of packages that you can install.
We'll need:
+
+* `python-pip`
+
+* `python3`
+
+* `python-setuptools`
+
+To get this running in unRAID go ahead and download the repo to your computer.
+
+Then take all the data from the zip file and place it somewhere on your server.
+
+An example of this would be: `/mnt/user/data/scripts/qbit/`
+
+Now we need to install the requirements for this script.
+
+Head back over to **User Scripts**
+
+Create a new script: An example of this would be `install-requirements`
+
+In the new text field you'll need to place:
+
+```bash
+#!/bin/bash
+echo "Installing required packages"
+python3 -m pip install -r /mnt/user/path/to/requirements.txt
+echo "Required packages installed"
+```
+
+Replace `path/to/` with your path example mines `/data/scripts/qbit/` or `/mnt/user/data/scripts/qbit/requirements.txt`
+
+Now click **Save Changes**
+
+Now to set a schedule for this bash script to run.
+
+Select **At First Array Start Only** This will run this script every time the array starts on every boot
+
+Now we need to edit the config file that came with the zip file.
+
The config file should be pretty self-explanatory.
+
The only thing that must be followed is that **ALL** categories that you see in your qBit **MUST** be added to the config file with associated directories, each directory must be unique for each category.
+
+> If you'd like a guide on setting up cross-seed on unRAID please visit [here](https://github.com/Drazzilb08/cross-seed-guide)
+
+Now we need to go back to **User Scripts** and create our script to run this script
+
+## Add a new script
+
+ You can name yours something like `auto-manage-qbittorrent`
+ Here is an example script:
+
+ ```bash
+ #!/bin/bash
+echo "Running qBitTorrent Management"
+python3 /mnt/user/data/scripts/qbit/qbit_manage.py -c /mnt/user/data/scripts/qbit/config.yml -l /mnt/user/data/scripts/qbit/activity.log -r -
+echo "qBitTorrent Management Completed"
+```
+
+However, at the core, you'll want
+
+```bash
+python3 //qbit_manage.py -c //config.yml -l //activity.log -r -
+```
+
+if you want to change the arguments in the ``. The full list of arguments can be seen by using the `-h` command or on the README.
+
+ Once you've got the config file set up you should be all set.
+ Don't forget to set a cron schedule mines
`*/30 * * * *` <-- Runs every 30 min
+
+**Final note:**
+If you're wanting to do a test run please use the `--dry-run` argument anywhere w/in the call to test how things will look. Please do this before running a full run.
diff --git a/docs/_Footer.md b/docs/_Footer.md
new file mode 100644
index 0000000..eaa9dc0
--- /dev/null
+++ b/docs/_Footer.md
@@ -0,0 +1,4 @@
+### Want to contribute to this Wiki or have suggestions?
+[Fork it and send a pull request.](https://github.com/StuffAnThings/qbit_manage/fork)
+
+[Submit a Docs request.](https://github.com/StuffAnThings/qbit_manage/issues/new?assignees=bobokun&labels=status%3Anot-yet-viewed%2Cdocumentation&template=3.docs_request.yml&title=%5BDocs%5D%3A+)
diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md
new file mode 100644
index 0000000..a3d1d5c
--- /dev/null
+++ b/docs/_Sidebar.md
@@ -0,0 +1,26 @@
+- [Home](Home)
+ - [Installation](Installation)
+ - [unRAID Installation](Unraid-Installation)
+ - [Local Installation](Local-Installations)
+ - [NIX Installation](Nix-Installation)
+ - [Docker Installation](Docker-Installation)
+ - [V4 Migration Guide](v4-Migration-Guide)
+ - [Config Setup](Config-Setup)
+ - [Sample Config File](Config-Setup#config-file)
+ - [List of variables](Config-Setup#list-of-variables)
+ - [commands](Config-Setup#commands)
+ - [qbt](Config-Setup#qbt)
+ - [settings](Config-Setup#settings)
+ - [directory](Config-Setup#directory)
+ - [cat](Config-Setup#cat)
+ - [cat_changes](Config-Setup#cat_changes)
+ - [tracker](Config-Setup#tracker)
+ - [nohardlinks](Config-Setup#nohardlinks)
+ - [share_limits](Config-Setup#share_limits)
+ - [recyclebin](Config-Setup#recyclebin)
+ - [orphaned](Config-Setup#orphaned)
+ - [apprise](Config-Setup#apprise)
+ - [notifiarr](Config-Setup#notifiarr)
+ - [webhooks](Config-Setup#webhooks)
+ - [bhd](Config-Setup#bhd)
+ - [Commands](Commands)
diff --git a/docs/v4-Migration-Guide.md b/docs/v4-Migration-Guide.md
new file mode 100644
index 0000000..8eb4c55
--- /dev/null
+++ b/docs/v4-Migration-Guide.md
@@ -0,0 +1,100 @@
+# Qbit-Manage Migration
+
+Currently the qbit-manage (qbm) config file manages torrents in two ways: via tracker and via hardlinks. The section of the config where you specify your trackers is also where you can specify share limits (duration and ratio) on a per-tracker basis. The section of the config where you address no hardlinks (noHL) is where you specify share limits for files that are not hardlinked.
+
+Starting with develop version 4.0.0 torrents are no longer configured solely by tracker or noHL status. You now create groups of torrents based on tags and you can set specific share limits for each group. This means max_seeding_time, min_seeding_time and max_ratio are no longer used in the tracker or noHL section of the config, they are used for each group of torrents.
+
+## Old config
+
+```yml
+cat:
+ movies: “/data/torrents/movies”
+ tv: “/data/torrents/tv”
+tracker:
+ Tracker-a:
+ tag: a
+ max_seeding_time: 100
+ max_ratio: 5
+ Tracker-b:
+ tag: b
+ max_seeding_time: 100
+ max_ratio: 5
+ Tracker-c:
+ tag: c
+ max_seeding_time: 50
+ max_ratio: 3
+nohardlinks:
+ movies:
+ cleanup: true
+ max_seeding_time: 75
+ max_ratio: 2
+ tv:
+ cleanup: true
+ max_seeding_time: 25
+ max_ratio: 1
+```
+
+### New config
+
+```yml
+cat:
+- movies: “/data/torrents/movies”
+- tv: “/data/torrents/tv”
+tracker:
+ Tracker-a:
+ tag: a
+ Tracker-b:
+ tag: b
+ Tracker-c:
+ tag: c
+nohardlinks:
+- movies
+- tv
+share_limits:
+ group1.noHL:
+ priority: 1
+ include_any_tags:
+ - a
+ - b
+ include_all_tags:
+ - noHL
+ categories:
+ - movies
+ max_ratio: 2
+ max_seeding_time: 75
+ cleanup: true
+ group1:
+ priority: 2
+ include_any_tags:
+ - a
+ - b
+ categories:
+ - movies
+ max_ratio: 5
+ max_seeding_time: 100
+ group2.noHL:
+ priority: 3
+ include_any_tags:
+ - c
+ include_all_tags:
+ - noHL
+ categories:
+ - tv
+ max_ratio: 1
+ max_seeding_time: 25
+ group2:
+ priority: 4
+ include_any_tags:
+ - c
+ categories:
+ - tv
+ max_ratio:
+ max_seeding_time:
+```
+
+The new config will operate as follows:
+Torrents from tracker a and tracker b that are tagged as noHL and in the movie category will have a share limit of 75 minutes and a ratio of 2. These same torrents, when not tagged as noHL, will then have a share limit of 100 minutes and a ratio of 5.
+
+Torrents from tracker c that are tagged as noHL and in the tv category will have a share limit of 50 minutes and a ratio of 3. These same torrents, when not tagged as noHL, will have no share limit applied and will seed indefinitely.
+
+There is now much greater flexibility to apply different share limits to torrents based on how you group them and which tags and categories are assigned to each group. When assigning priority it is best to determine what limits/restrictions you want based on your preferences and assign the more restrictive limits as higher priority groups since share limits will not transfer when a torrent is moved from one group to another. In the examples above, the settings are more restrictive for noHL torrents so those are listed as higher priority within the group.
diff --git a/modules/core/share_limits.py b/modules/core/share_limits.py
index 45abdba..82f2f48 100644
--- a/modules/core/share_limits.py
+++ b/modules/core/share_limits.py
@@ -372,7 +372,8 @@ class ShareLimits:
body.append(msg)
# Update Torrents
if not self.config.dry_run:
- torrent.add_tags(tags)
+ if tags:
+ torrent.add_tags(tags)
torrent_upload_limit = -1 if round(torrent.up_limit / 1024) == 0 else round(torrent.up_limit / 1024)
if limit_upload_speed is not None and limit_upload_speed != torrent_upload_limit:
if limit_upload_speed == -1:
diff --git a/scripts/pre-commit/increase_version.sh b/scripts/pre-commit/increase_version.sh
index 11fa945..3f166ab 100755
--- a/scripts/pre-commit/increase_version.sh
+++ b/scripts/pre-commit/increase_version.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
# Check if there are any changes staged for commit
if [[ -z $(git diff --cached --name-only) ]]; then
diff --git a/scripts/pre-commit/update-readme-version.sh b/scripts/pre-commit/update-readme-version.sh
new file mode 100755
index 0000000..9f2ae66
--- /dev/null
+++ b/scripts/pre-commit/update-readme-version.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+# Try to get the name of the current branch
+branch_name=$(git symbolic-ref --short HEAD 2> /dev/null)
+
+# If the command failed, exit with code 0
+if [ $? -ne 0 ]; then
+ echo "Error: ref HEAD is not a symbolic ref"
+ exit 0
+fi
+
+# Run the python script with the branch name as an argument
+python3 scripts/update-readme-version.py $branch_name
diff --git a/scripts/update-readme-version.py b/scripts/update-readme-version.py
new file mode 100644
index 0000000..252e2cc
--- /dev/null
+++ b/scripts/update-readme-version.py
@@ -0,0 +1,52 @@
+import json
+import re
+import subprocess
+import sys
+
+try:
+ from qbittorrentapi import Version
+except ImportError:
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "qbittorrent-api"])
+ from qbittorrentapi import Version
+
+# Check if a branch name was provided
+if len(sys.argv) != 2:
+ print("Usage: python update_versions.py ")
+ sys.exit(1)
+
+branch_name = sys.argv[1]
+print(f"Branch name: {branch_name}")
+
+# Load or initialize the SUPPORTED_VERSIONS.json file
+versions_file_path = "SUPPORTED_VERSIONS.json"
+try:
+ with open(versions_file_path, encoding="utf-8") as file:
+ supported_versions = json.load(file)
+except FileNotFoundError:
+ supported_versions = {}
+
+# Extract the current qbittorrent-api version from requirements.txt
+print("Reading requirements.txt...")
+with open("requirements.txt", encoding="utf-8") as file:
+ requirements = file.read()
+ qbittorrent_api_version = re.search(r"qbittorrent-api==(.+)", requirements).group(1)
+
+print(f"Current qbittorrent-api version: {qbittorrent_api_version}")
+
+# Fetch the latest supported qBittorrent version
+supported_version = Version.latest_supported_app_version()
+print(f"Latest supported qBittorrent version: {supported_version}")
+
+# Ensure the branch is initialized in the dictionary
+if branch_name not in supported_versions:
+ supported_versions[branch_name] = {}
+
+# Update the versions in the dictionary
+supported_versions[branch_name]["qbit"] = supported_version
+supported_versions[branch_name]["qbitapi"] = qbittorrent_api_version
+
+print("Writing updated versions to SUPPORTED_VERSIONS.json...")
+# Write the updated versions back to SUPPORTED_VERSIONS.json
+with open(versions_file_path, "w", encoding="utf-8") as file:
+ json.dump(supported_versions, file, indent=4)
+ file.write("\n")