mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2025-09-11 07:34:45 +08:00
4.2.0 (#762)
* 4.1.19-develop1 * [pre-commit.ci] pre-commit autoupdate (#750) updates: - [github.com/PyCQA/flake8: 7.1.1 → 7.1.2](https://github.com/PyCQA/flake8/compare/7.1.1...7.1.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Update SUPPORTED_VERSIONS.json for master (#760) * 4.1.18-develop1 * Bump flake8 from 7.1.1 to 7.1.2 (#748) Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.1 to 7.1.2. - [Commits](https://github.com/pycqa/flake8/compare/7.1.1...7.1.2) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump humanize from 4.11.0 to 4.12.0 (#749) Bumps [humanize](https://github.com/python-humanize/humanize) from 4.11.0 to 4.12.0. - [Release notes](https://github.com/python-humanize/humanize/releases) - [Commits](https://github.com/python-humanize/humanize/compare/4.11.0...4.12.0) --- updated-dependencies: - dependency-name: humanize dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump humanize from 4.12.0 to 4.12.1 (#753) Bumps [humanize](https://github.com/python-humanize/humanize) from 4.12.0 to 4.12.1. - [Release notes](https://github.com/python-humanize/humanize/releases) - [Commits](https://github.com/python-humanize/humanize/compare/4.12.0...4.12.1) --- updated-dependencies: - dependency-name: humanize dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump qbittorrent-api from 2024.12.71 to 2025.2.0 (#752) * Update SUPPORTED_VERSIONS.json (#754) * chore(docs): Sync wiki to docs [skip-cd] * 4.1.18 * Update SUPPORTED_VERSIONS.json --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: bobokun <jon.cy.lee98@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Actionbot <actions@github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * Add "torrent deleted" and "torrent banned" as unregistered (#746) * 4.1.18-develop1 * Update util.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: bobokun <jon.cy.lee98@gmail.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Adds additional comments for nexusPHP remove_registered * Removes cross-seed deprecated function (Adds #758) * Adds #757 * Fixes #755 * Adds #756 * Fixes #756 * chore(config): make config example more sample-like (#761) users think the config is defaults not a sample * show qbitorrent version info instead of debug * 4.2.0 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Actionbot <actions@github.com> Co-authored-by: Ninboy <ninboy@users.noreply.github.com> Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
This commit is contained in:
parent
54cee3acd0
commit
fc24d1d934
16 changed files with 83 additions and 261 deletions
|
@ -49,7 +49,7 @@ repos:
|
|||
language_version: python3
|
||||
args: [--line-length, '130']
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.1.1
|
||||
rev: 7.1.2
|
||||
hooks:
|
||||
- id: flake8
|
||||
args: [--config=.flake8]
|
||||
|
|
12
CHANGELOG
12
CHANGELOG
|
@ -1,5 +1,9 @@
|
|||
# Requirements Updated
|
||||
qbittorrent-api==2025.2.0
|
||||
humanize==4.12.1
|
||||
# New Updates
|
||||
- Removes deprecated cross-seed command/function (#758)
|
||||
- Adds ability to tag stalled downloads (#757)
|
||||
- Adds ability to customize a list of status to ignore during rem_unregistered command (#756)
|
||||
|
||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.1.17...v4.1.18
|
||||
# Bug Fixes
|
||||
- Fixes bug in Windows during category updates (#755)
|
||||
|
||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.1.18...v4.2.0
|
||||
|
|
|
@ -15,7 +15,6 @@ This is a program used to manage your qBittorrent instance such as:
|
|||
* Apply category based on `save_path` to uncategorized torrents in category's `save_path`
|
||||
* Change categories based on current category (`cat_change`)
|
||||
* Remove unregistered torrents (delete data & torrent if it is not being cross-seeded, otherwise it will just remove the torrent)
|
||||
* Automatically add [cross-seed](https://github.com/cross-seed/cross-seed) torrents in paused state. **\*Note: cross-seed now allows for torrent injections directly to qBit, making this feature rarely needed/used.\***
|
||||
* Recheck paused torrents sorted by lowest size and resume if completed
|
||||
* Remove orphaned files from your root directory that are not referenced by qBittorrent
|
||||
* Tag any torrents that have no hard links outside the root folder (for multi-file torrents the largest file is used)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"master": {
|
||||
"qbit": "v5.0.3",
|
||||
"qbitapi": "2024.12.71"
|
||||
"qbit": "v5.0.4",
|
||||
"qbitapi": "2025.2.0"
|
||||
},
|
||||
"develop": {
|
||||
"qbit": "v5.0.4",
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
4.1.18
|
||||
4.2.0
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# This is an example configuration file that documents all the options.
|
||||
# It will need to be modified for your specific use case.
|
||||
# These are not default values. You MUST review the config settings and properly configure this EXAMPLE file.
|
||||
# Please refer to the link below for more details on how to set up the configuration file
|
||||
# https://github.com/StuffAnThings/qbit_manage/wiki/Config-Setup
|
||||
|
||||
commands:
|
||||
# The commands defined below will IGNORE any commands used in command line and docker env variables.
|
||||
dry_run: True
|
||||
cross_seed: False
|
||||
recheck: False
|
||||
cat_update: False
|
||||
tag_update: False
|
||||
|
@ -36,16 +36,18 @@ settings:
|
|||
share_limits_min_seeding_time_tag: MinSeedTimeNotReached # Tag to be added to torrents that have not yet reached the minimum seeding time
|
||||
share_limits_min_num_seeds_tag: MinSeedsNotMet # Tag to be added to torrents that have not yet reached the minimum number of seeds
|
||||
share_limits_last_active_tag: LastActiveLimitNotReached # Tag to be added to torrents that have not yet reached the last active limit
|
||||
cross_seed_tag: cross-seed # Will set the tag of any torrents that are added by cross-seed command
|
||||
cat_filter_completed: True # Filters for completed torrents only when running cat_update command
|
||||
share_limits_filter_completed: True # Filters for completed torrents only when running share_limits command
|
||||
tag_nohardlinks_filter_completed: True # Filters for completed torrents only when running tag_nohardlinks command
|
||||
cat_update_all: True # Checks and updates all torrent categories if set to True when running cat_update command, otherwise only update torrents that are uncategorized
|
||||
disable_qbt_default_share_limits: True # Allows QBM to handle share limits by disabling qBittorrents default Share limits. Only active when the share_limits command is set to True
|
||||
tag_stalled_torrents: True # Tags any downloading torrents that are stalled with the `stalledDL` tag when running the tag_update command
|
||||
rem_unregistered_ignore_list: # Ignores a list of words found in the status of the tracker when running rem_unregistered command and will not remove the torrent if matched
|
||||
- example placeholder words
|
||||
- ignore if found
|
||||
|
||||
directory:
|
||||
# Do not remove these
|
||||
# Cross-seed var: </your/path/here/> # Output directory of cross-seed
|
||||
# root_dir var: </your/path/here/> # Root downloads directory used to check for orphaned files, noHL, and RecycleBin.
|
||||
# <OPTIONAL> remote_dir var: </your/path/here/> # Path of docker host mapping of root_dir.
|
||||
# remote_dir must be set if you're running qbit_manage locally and qBittorrent/cross_seed is in a docker
|
||||
|
@ -53,12 +55,11 @@ directory:
|
|||
# <OPTIONAL> recycle_bin var: </your/path/here/> # Path of the RecycleBin folder. Default location is set to remote_dir/.RecycleBin
|
||||
# <OPTIONAL> torrents_dir var: </your/path/here/> # Path of the your qbittorrent torrents directory. Required for `save_torrents` attribute in recyclebin
|
||||
# <OPTIONAL> orphaned_dir var: </your/path/here/> # Path of the the Orphaned Data folder. This is similar to RecycleBin, but only for orphaned data.
|
||||
cross_seed: "/your/path/here/"
|
||||
root_dir: "/data/torrents/"
|
||||
remote_dir: "/mnt/user/data/torrents/"
|
||||
recycle_bin: "/mnt/user/data/torrents/.RecycleBin"
|
||||
torrents_dir: "/qbittorrent/data/BT_backup"
|
||||
orphaned_dir: "/data/torrents/orphaned_data"
|
||||
root_dir: "/path/to/torrents/"
|
||||
remote_dir: "/host/path/to/torrents/ifdocker/torrents/"
|
||||
recycle_bin: "/path/to/.RecycleBin"
|
||||
torrents_dir: "/path/to/qbitAppData/BT_backup"
|
||||
orphaned_dir: "/path/to/orphaned_data"
|
||||
|
||||
cat:
|
||||
# Category & Path Parameters
|
||||
|
@ -66,17 +67,15 @@ cat:
|
|||
# If you want to leave a save_path as uncategorized you can use the key 'Uncategorized' as the name of the category.
|
||||
# You can use Unix filename pattern matching as well when specifying the save_path
|
||||
# <Category Name> : <save_path> # Path of your save directory.
|
||||
movies: "/data/torrents/Movies"
|
||||
tv: "/data/torrents/TV"
|
||||
somecategory: "/path/to/torrents/somecategory"
|
||||
anothercategory: "/path/to/torrents/anothercategory"
|
||||
|
||||
cat_change:
|
||||
# This moves all the torrents from one category to another category. This executes on --cat-update
|
||||
# WARNING: if the paths are different and Default Torrent Management Mode is set to automatic the files could be moved !!!
|
||||
# <Old Category Name> : <New Category>
|
||||
Radarr-HD.cross-seed: movies-hd
|
||||
Radarr-UHD.cross-seed: movies-uhd
|
||||
movies-hd.cross-seed: movies-hd
|
||||
movies-uhd.cross-seed: movies-uhd
|
||||
CatA.cross-seed: CatA
|
||||
CatB.cross-seed: CatB
|
||||
|
||||
tracker:
|
||||
# Mandatory
|
||||
|
@ -311,7 +310,6 @@ webhooks:
|
|||
run_start: notifiarr
|
||||
run_end: apprise
|
||||
function:
|
||||
cross_seed: https://mywebhookurl.com/qbt_manage
|
||||
recheck: notifiarr
|
||||
cat_update: apprise
|
||||
tag_update: notifiarr
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
| `-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 |
|
||||
|
|
|
@ -52,12 +52,13 @@ This section defines any settings defined in the configuration.
|
|||
| `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`) | MinSeedTimeNotReached | <center>❌</center> |
|
||||
| `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`) | MinSeedsNotMet | <center>❌</center> |
|
||||
| `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`) | LastActiveLimitNotReached | <center>❌</center> |
|
||||
| `cross_seed_tag` | When running `--cross-seed` function, it will update any added cross-seed torrents with this tag. | cross-seed | <center>❌</center> |
|
||||
| `cat_filter_completed` | When running `--cat-update` function, it will filter for completed torrents only. | True | <center>❌</center> |
|
||||
| `share_limits_filter_completed` | When running `--share-limits` function, it will filter for completed torrents only. | True | <center>❌</center> |
|
||||
| `tag_nohardlinks_filter_completed` | When running `--tag-nohardlinks` function, , it will filter for completed torrents only. | True | <center>❌</center> |
|
||||
| `cat_update_all` | When running `--cat-update` function, it will check and update all torrents categories, otherwise it will only update uncategorized torrents. | True | <center>❌</center> |
|
||||
| `disable_qbt_default_share_limits` | When running `--share-limits` function, it allows QBM to handle share limits by disabling qBittorrents default Share limits. | True | <center>❌</center> |
|
||||
| `tag_stalled_torrents` | Tags any downloading torrents that are stalled with the `stalledDL` tag when running the tag_update command | True | <center>❌</center> |
|
||||
| `rem_unregistered_ignore_list` | Ignores a list of words found in the status of the tracker when running rem_unregistered command and will not remove the torrent if matched | | <center>❌</center> |
|
||||
|
||||
## **directory:**
|
||||
|
||||
|
@ -66,7 +67,6 @@ This section defines the directories that qbit_manage will be looking into for v
|
|||
|
||||
| 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. | <center>❌</center> |
|
||||
| `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. | <center>❌</center> |
|
||||
|
@ -237,7 +237,6 @@ Provide webhook notifications based on event triggers
|
|||
| [error](#error-notifications) | When errors occur during the run | N/A | <center>❌</center> |
|
||||
| [run_start](#run-start-notifications) | At the beginning of every run | N/A | <center>❌</center> |
|
||||
| [run_end](#run-end-notifications) | At the end of every run | N/A | <center>❌</center> |
|
||||
| [cross_seed](#cross-seed-notifications) | During the cross-seed function | N/A | <center>❌</center> |
|
||||
| [recheck](#recheck-notifications) | During the recheck function | N/A | <center>❌</center> |
|
||||
| [cat_update](#category-update-notifications) | During the category update function | N/A | <center>❌</center> |
|
||||
| [tag_update](#tag-update-notifications) | During the tag update function | N/A | <center>❌</center> |
|
||||
|
@ -309,36 +308,6 @@ Payload will be sent at the end of the run
|
|||
}
|
||||
```
|
||||
|
||||
### **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**
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ services:
|
|||
- QBT_SCHEDULE=1440
|
||||
- QBT_CONFIG=/config/config.yml
|
||||
- QBT_LOGFILE=activity.log
|
||||
- QBT_CROSS_SEED=false
|
||||
- QBT_RECHECK=false
|
||||
- QBT_CAT_UPDATE=false
|
||||
- QBT_TAG_UPDATE=false
|
||||
|
|
|
@ -22,7 +22,6 @@ from modules.webhooks import Webhooks
|
|||
logger = util.logger
|
||||
|
||||
COMMANDS = [
|
||||
"cross_seed",
|
||||
"recheck",
|
||||
"cat_update",
|
||||
"tag_update",
|
||||
|
@ -91,7 +90,6 @@ class Config:
|
|||
logger.debug(f" --debug (QBT_DEBUG): {args['debug']}")
|
||||
logger.debug(f" --trace (QBT_TRACE): {args['trace']}")
|
||||
logger.separator("CONFIG OVERRIDE RUN COMMDANDS", space=False, border=False, loglevel="DEBUG")
|
||||
logger.debug(f" --cross-seed (QBT_CROSS_SEED): {self.commands['cross_seed']}")
|
||||
logger.debug(f" --recheck (QBT_RECHECK): {self.commands['recheck']}")
|
||||
logger.debug(f" --cat-update (QBT_CAT_UPDATE): {self.commands['cat_update']}")
|
||||
logger.debug(f" --tag-update (QBT_TAG_UPDATE): {self.commands['tag_update']}")
|
||||
|
@ -119,7 +117,6 @@ class Config:
|
|||
logger.debug(f" --debug (QBT_DEBUG): {args['debug']}")
|
||||
logger.debug(f" --trace (QBT_TRACE): {args['trace']}")
|
||||
logger.separator("DOCKER ENV RUN COMMANDS", space=False, border=False, loglevel="DEBUG")
|
||||
logger.debug(f" --cross-seed (QBT_CROSS_SEED): {args['cross_seed']}")
|
||||
logger.debug(f" --recheck (QBT_RECHECK): {args['recheck']}")
|
||||
logger.debug(f" --cat-update (QBT_CAT_UPDATE): {args['cat_update']}")
|
||||
logger.debug(f" --tag-update (QBT_TAG_UPDATE): {args['tag_update']}")
|
||||
|
@ -175,7 +172,6 @@ class Config:
|
|||
temp["function"][attr] = {}
|
||||
temp["function"][attr] = None
|
||||
|
||||
hooks("cross_seed")
|
||||
hooks("recheck")
|
||||
hooks("cat_update")
|
||||
hooks("tag_update")
|
||||
|
@ -218,7 +214,6 @@ class Config:
|
|||
"share_limits_last_active_tag": self.util.check_for_attribute(
|
||||
self.data, "share_limits_last_active_tag", parent="settings", default="LastActiveLimitNotReached"
|
||||
),
|
||||
"cross_seed_tag": self.util.check_for_attribute(self.data, "cross_seed_tag", parent="settings", default="cross-seed"),
|
||||
"cat_filter_completed": self.util.check_for_attribute(
|
||||
self.data, "cat_filter_completed", parent="settings", var_type="bool", default=True
|
||||
),
|
||||
|
@ -237,6 +232,12 @@ class Config:
|
|||
"disable_qbt_default_share_limits": self.util.check_for_attribute(
|
||||
self.data, "disable_qbt_default_share_limits", parent="settings", var_type="bool", default=True
|
||||
),
|
||||
"tag_stalled_torrents": self.util.check_for_attribute(
|
||||
self.data, "tag_stalled_torrents", parent="settings", var_type="bool", default=True
|
||||
),
|
||||
"rem_unregistered_ignore_list": self.util.check_for_attribute(
|
||||
self.data, "rem_unregistered_ignore_list", parent="settings", var_type="upper_list", default=[]
|
||||
),
|
||||
}
|
||||
|
||||
self.tracker_error_tag = self.settings["tracker_error_tag"]
|
||||
|
@ -246,12 +247,10 @@ class Config:
|
|||
self.share_limits_min_seeding_time_tag = self.settings["share_limits_min_seeding_time_tag"]
|
||||
self.share_limits_min_num_seeds_tag = self.settings["share_limits_min_num_seeds_tag"]
|
||||
self.share_limits_last_active_tag = self.settings["share_limits_last_active_tag"]
|
||||
self.cross_seed_tag = self.settings["cross_seed_tag"]
|
||||
|
||||
self.default_ignore_tags = [
|
||||
self.nohardlinks_tag,
|
||||
self.tracker_error_tag,
|
||||
self.cross_seed_tag,
|
||||
self.share_limits_min_seeding_time_tag,
|
||||
self.share_limits_min_num_seeds_tag,
|
||||
self.share_limits_last_active_tag,
|
||||
|
@ -262,7 +261,6 @@ class Config:
|
|||
self.util.overwrite_attributes(self.settings, "settings")
|
||||
|
||||
default_function = {
|
||||
"cross_seed": None,
|
||||
"recheck": None,
|
||||
"cat_update": None,
|
||||
"tag_update": None,
|
||||
|
@ -662,7 +660,7 @@ class Config:
|
|||
),
|
||||
"",
|
||||
)
|
||||
if self.commands["cross_seed"] or self.commands["tag_nohardlinks"] or self.commands["rem_orphaned"]:
|
||||
if self.commands["tag_nohardlinks"] or self.commands["rem_orphaned"]:
|
||||
self.remote_dir = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"remote_dir",
|
||||
|
@ -685,12 +683,6 @@ class Config:
|
|||
)
|
||||
if not self.remote_dir:
|
||||
self.remote_dir = self.root_dir
|
||||
if self.commands["cross_seed"]:
|
||||
self.cross_seed_dir = self.util.check_for_attribute(self.data, "cross_seed", parent="directory", var_type="path")
|
||||
else:
|
||||
self.cross_seed_dir = self.util.check_for_attribute(
|
||||
self.data, "cross_seed", parent="directory", default_is_none=True
|
||||
)
|
||||
if self.commands["rem_orphaned"]:
|
||||
if "orphaned_dir" in self.data["directory"] and self.data["directory"]["orphaned_dir"] is not None:
|
||||
default_orphaned = os.path.join(
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
import os
|
||||
from collections import Counter
|
||||
|
||||
from modules import util
|
||||
from modules.torrent_hash_generator import TorrentHashGenerator
|
||||
|
||||
logger = util.logger
|
||||
|
||||
|
||||
class CrossSeed:
|
||||
def __init__(self, qbit_manager):
|
||||
self.qbt = qbit_manager
|
||||
self.config = qbit_manager.config
|
||||
self.client = qbit_manager.client
|
||||
self.stats_added = 0
|
||||
self.stats_tagged = 0
|
||||
self.cross_seed_tag = qbit_manager.config.cross_seed_tag
|
||||
|
||||
self.torrents_updated = [] # List of torrents added by cross-seed
|
||||
self.notify_attr = [] # List of single torrent attributes to send to notifiarr
|
||||
|
||||
self.cross_seed()
|
||||
|
||||
def cross_seed(self):
|
||||
"""Move torrents from cross seed directory to correct save directory."""
|
||||
logger.separator("Checking for Cross-Seed Torrents", space=False, border=False)
|
||||
# List of categories for all torrents moved
|
||||
categories = []
|
||||
|
||||
# Only get torrent files
|
||||
cs_files = [f for f in os.listdir(self.config.cross_seed_dir) if f.endswith("torrent")]
|
||||
dir_cs = self.config.cross_seed_dir
|
||||
dir_cs_out = os.path.join(dir_cs, "qbit_manage_added")
|
||||
os.makedirs(dir_cs_out, exist_ok=True)
|
||||
dir_cs_err = os.path.join(dir_cs, "qbit_manage_error")
|
||||
os.makedirs(dir_cs_err, exist_ok=True)
|
||||
for file in cs_files:
|
||||
tr_name = file.split("]", 2)[2].split(".torrent")[0]
|
||||
t_tracker = file.split("]", 2)[1][1:]
|
||||
# Substring Key match in dictionary (used because t_name might not match exactly with self.qbt.torrentinfo key)
|
||||
# Returned the dictionary of filtered item
|
||||
torrentdict_file = dict(filter(lambda item: tr_name in item[0], self.qbt.torrentinfo.items()))
|
||||
src = os.path.join(dir_cs, file)
|
||||
file_cs_out = os.path.join(dir_cs_out, file)
|
||||
file_cs_err = os.path.join(dir_cs_err, file)
|
||||
if torrentdict_file:
|
||||
# Get the exact torrent match name from self.qbt.torrentinfo
|
||||
t_name = next(iter(torrentdict_file))
|
||||
dest = os.path.join(self.qbt.torrentinfo[t_name]["save_path"], "")
|
||||
category = self.qbt.torrentinfo[t_name].get("Category", self.qbt.get_category(dest)[0])
|
||||
# Only add cross-seed torrent if original torrent is complete
|
||||
if self.qbt.torrentinfo[t_name]["is_complete"]:
|
||||
categories.append(category)
|
||||
body = []
|
||||
body += logger.print_line(
|
||||
f"{'Not Adding' if self.config.dry_run else 'Adding'} to qBittorrent:", self.config.loglevel
|
||||
)
|
||||
body += logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel)
|
||||
body += logger.print_line(logger.insert_space(f"Category: {category}", 7), self.config.loglevel)
|
||||
body += logger.print_line(logger.insert_space(f"Save_Path: {dest}", 6), self.config.loglevel)
|
||||
body += logger.print_line(logger.insert_space(f"Tracker: {t_tracker}", 8), self.config.loglevel)
|
||||
attr = {
|
||||
"function": "cross_seed",
|
||||
"title": "Adding New Cross-Seed Torrent",
|
||||
"body": "\n".join(body),
|
||||
"torrents": [t_name],
|
||||
"torrent_category": category,
|
||||
"torrent_save_path": dest,
|
||||
"torrent_tag": self.cross_seed_tag,
|
||||
"torrent_tracker": t_tracker,
|
||||
}
|
||||
self.notify_attr.append(attr)
|
||||
self.torrents_updated.append(t_name)
|
||||
self.stats_added += 1
|
||||
if not self.config.dry_run:
|
||||
self.client.torrents.add(
|
||||
torrent_files=src, save_path=dest, category=category, tags=self.cross_seed_tag, is_paused=True
|
||||
)
|
||||
try:
|
||||
torrent_hash_generator = TorrentHashGenerator(src)
|
||||
torrent_hash = torrent_hash_generator.generate_torrent_hash()
|
||||
util.move_files(src, file_cs_out)
|
||||
except Exception as e:
|
||||
logger.warning(f"Unable to generate torrent hash from cross-seed {t_name}: {e}")
|
||||
try:
|
||||
if torrent_hash:
|
||||
torrent_info = self.qbt.get_torrents({"torrent_hashes": torrent_hash})
|
||||
except Exception as e:
|
||||
logger.warning(f"Unable to find hash {torrent_hash} in qbt: {e}")
|
||||
if torrent_info:
|
||||
torrent = torrent_info[0]
|
||||
self.qbt.add_torrent_files(torrent.hash, torrent.files, torrent.save_path)
|
||||
self.qbt.torrentvalid.append(torrent)
|
||||
self.qbt.torrentinfo[t_name]["torrents"].append(torrent)
|
||||
self.qbt.torrent_list.append(torrent)
|
||||
else:
|
||||
logger.print_line(f"Found {t_name} in {dir_cs} but original torrent is not complete.", self.config.loglevel)
|
||||
logger.print_line("Not adding to qBittorrent", self.config.loglevel)
|
||||
else:
|
||||
error = f"{tr_name} not found in torrents. Cross-seed Torrent not added to qBittorrent."
|
||||
if self.config.dry_run:
|
||||
logger.print_line(error, self.config.loglevel)
|
||||
else:
|
||||
logger.print_line(error, "WARNING")
|
||||
util.move_files(src, file_cs_err)
|
||||
self.config.notify(error, "cross-seed", False)
|
||||
|
||||
self.config.webhooks_factory.notify(self.torrents_updated, self.notify_attr, group_by="category")
|
||||
self.torrents_updated = []
|
||||
self.notify_attr = []
|
||||
# Tag missing cross-seed torrents tags
|
||||
for torrent in self.qbt.torrent_list:
|
||||
t_name = torrent.name
|
||||
t_cat = torrent.category
|
||||
if (
|
||||
not util.is_tag_in_torrent(self.cross_seed_tag, torrent.tags)
|
||||
and self.qbt.is_cross_seed(torrent)
|
||||
and torrent.downloaded == 0
|
||||
and torrent.seeding_time > 0
|
||||
):
|
||||
tracker = self.qbt.get_tags(self.qbt.get_tracker_urls(torrent.trackers))
|
||||
self.stats_tagged += 1
|
||||
body = logger.print_line(
|
||||
f"{'Not Adding' if self.config.dry_run else 'Adding'} '{self.cross_seed_tag}' tag to {t_name}",
|
||||
self.config.loglevel,
|
||||
)
|
||||
attr = {
|
||||
"function": "tag_cross_seed",
|
||||
"title": "Tagging Cross-Seed Torrent",
|
||||
"body": body,
|
||||
"torrents": [t_name],
|
||||
"torrent_category": t_cat,
|
||||
"torrent_tag": self.cross_seed_tag,
|
||||
"torrent_tracker": tracker["url"],
|
||||
}
|
||||
self.notify_attr.append(attr)
|
||||
self.torrents_updated.append(t_name)
|
||||
if not self.config.dry_run:
|
||||
torrent.add_tags(tags=self.cross_seed_tag)
|
||||
self.config.webhooks_factory.notify(self.torrents_updated, self.notify_attr, group_by="category")
|
||||
numcategory = Counter(categories)
|
||||
for cat in numcategory:
|
||||
if numcategory[cat] > 0:
|
||||
logger.print_line(
|
||||
f"{numcategory[cat]} {cat} cross-seed .torrents {'not added' if self.config.dry_run else 'added'}.",
|
||||
self.config.loglevel,
|
||||
)
|
||||
if self.stats_added > 0:
|
||||
logger.print_line(
|
||||
f"Total {self.stats_added} cross-seed .torrents {'not added' if self.config.dry_run else 'added'}.",
|
||||
self.config.loglevel,
|
||||
)
|
||||
if self.stats_tagged > 0:
|
||||
logger.print_line(
|
||||
f"Total {self.stats_tagged} cross-seed .torrents {'not added' if self.config.dry_run else 'added'}.",
|
||||
self.config.loglevel,
|
||||
)
|
|
@ -21,6 +21,7 @@ class RemoveUnregistered:
|
|||
self.tag_error = self.config.tracker_error_tag
|
||||
self.cfg_rem_unregistered = self.config.commands["rem_unregistered"]
|
||||
self.cfg_tag_error = self.config.commands["tag_tracker_error"]
|
||||
self.rem_unregistered_ignore_list = self.config.settings["rem_unregistered_ignore_list"]
|
||||
|
||||
tag_error_msg = "Tagging Torrents with Tracker Errors" if self.cfg_tag_error else ""
|
||||
rem_unregistered_msg = "Removing Unregistered Torrents" if self.cfg_rem_unregistered else ""
|
||||
|
@ -125,7 +126,13 @@ class RemoveUnregistered:
|
|||
if list_in_text(msg_up, TorrentMessages.UNREGISTERED_MSGS) and not list_in_text(
|
||||
msg_up, TorrentMessages.IGNORE_MSGS
|
||||
):
|
||||
self.del_unregistered(msg, tracker, torrent)
|
||||
if list_in_text(msg_up, self.rem_unregistered_ignore_list):
|
||||
logger.print_line(
|
||||
f"Ignoring unregistered torrent {self.t_name} due to matching phrase found in ignore list.",
|
||||
self.config.loglevel,
|
||||
)
|
||||
else:
|
||||
self.del_unregistered(msg, tracker, torrent)
|
||||
else:
|
||||
if self.check_for_unregistered_torrents_in_bhd(tracker, msg_up, torrent.hash):
|
||||
self.del_unregistered(msg, tracker, torrent)
|
||||
|
|
|
@ -12,6 +12,7 @@ class Tags:
|
|||
self.share_limits_tag = qbit_manager.config.share_limits_tag # suffix tag for share limits
|
||||
self.torrents_updated = [] # List of torrents updated
|
||||
self.notify_attr = [] # List of single torrent attributes to send to notifiarr
|
||||
self.stalled_tag = "stalledDL"
|
||||
|
||||
self.tags()
|
||||
self.config.webhooks_factory.notify(self.torrents_updated, self.notify_attr, group_by="tag")
|
||||
|
@ -21,8 +22,26 @@ class Tags:
|
|||
logger.separator("Updating Tags", space=False, border=False)
|
||||
for torrent in self.qbt.torrent_list:
|
||||
tracker = self.qbt.get_tags(self.qbt.get_tracker_urls(torrent.trackers))
|
||||
if torrent.tags == "" or not util.is_tag_in_torrent(tracker["tag"], torrent.tags):
|
||||
if tracker["tag"]:
|
||||
|
||||
# Remove stalled_tag if torrent is no longer stalled
|
||||
if util.is_tag_in_torrent(self.stalled_tag, torrent.tags) and torrent.state != "stalledDL":
|
||||
t_name = torrent.name
|
||||
body = []
|
||||
body += logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel)
|
||||
body += logger.print_line(logger.insert_space(f"Removing Tag: {self.stalled_tag}", 3), self.config.loglevel)
|
||||
body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
||||
if not self.config.dry_run:
|
||||
torrent.remove_tags(self.stalled_tag)
|
||||
if (
|
||||
torrent.tags == ""
|
||||
or not util.is_tag_in_torrent(tracker["tag"], torrent.tags)
|
||||
or (torrent.state == "stalledDL" and not util.is_tag_in_torrent(self.stalled_tag, torrent.tags))
|
||||
):
|
||||
stalled = False
|
||||
if torrent.state == "stalledDL":
|
||||
stalled = True
|
||||
tracker["tag"].append(self.stalled_tag)
|
||||
if tracker["tag"] or stalled:
|
||||
t_name = torrent.name
|
||||
self.stats += len(tracker["tag"])
|
||||
body = []
|
||||
|
|
|
@ -27,7 +27,7 @@ class Qbt:
|
|||
|
||||
SUPPORTED_VERSION = Version.latest_supported_app_version()
|
||||
MIN_SUPPORTED_VERSION = "v4.3.0"
|
||||
TORRENT_DICT_COMMANDS = ["recheck", "cross_seed", "rem_unregistered", "tag_tracker_error", "tag_nohardlinks", "share_limits"]
|
||||
TORRENT_DICT_COMMANDS = ["recheck", "rem_unregistered", "tag_tracker_error", "tag_nohardlinks", "share_limits"]
|
||||
|
||||
def __init__(self, config, params):
|
||||
self.config = config
|
||||
|
@ -48,9 +48,9 @@ class Qbt:
|
|||
)
|
||||
self.client.auth_log_in()
|
||||
self.current_version = self.client.app.version
|
||||
logger.debug(f"qBittorrent: {self.current_version}")
|
||||
logger.debug(f"qBittorrent Web API: {self.client.app.web_api_version}")
|
||||
logger.debug(f"qbit_manage supported versions: {self.MIN_SUPPORTED_VERSION} - {self.SUPPORTED_VERSION}")
|
||||
logger.info(f"qBittorrent: {self.current_version}")
|
||||
logger.info(f"qBittorrent Web API: {self.client.app.web_api_version}")
|
||||
logger.info(f"qbit_manage supported versions: {self.MIN_SUPPORTED_VERSION} - {self.SUPPORTED_VERSION}")
|
||||
if self.current_version < self.MIN_SUPPORTED_VERSION:
|
||||
ex = (
|
||||
f"Qbittorrent Error: qbit_manage is only compatible with {self.MIN_SUPPORTED_VERSION} or higher. "
|
||||
|
@ -311,7 +311,7 @@ class Qbt:
|
|||
self.config.data, "tag", parent="tracker", subparent="other", default_is_none=True, var_type="list", save=False
|
||||
)
|
||||
try:
|
||||
tracker["url"] = util.trunc_val(urls[0], os.sep)
|
||||
tracker["url"] = util.trunc_val(urls[0], "/")
|
||||
except IndexError as e:
|
||||
tracker["url"] = None
|
||||
if not urls:
|
||||
|
@ -331,8 +331,8 @@ class Qbt:
|
|||
default_tag = tracker_other_tag
|
||||
else:
|
||||
try:
|
||||
tracker["url"] = util.trunc_val(url, os.sep)
|
||||
default_tag = tracker["url"].split(os.sep)[2].split(":")[0]
|
||||
tracker["url"] = util.trunc_val(url, "/")
|
||||
default_tag = tracker["url"].split("/")[2].split(":")[0]
|
||||
except IndexError as e:
|
||||
logger.debug(f"Tracker Url:{url}")
|
||||
logger.debug(e)
|
||||
|
@ -380,7 +380,7 @@ class Qbt:
|
|||
if tracker_other_tag:
|
||||
default_tag = tracker_other_tag
|
||||
else:
|
||||
default_tag = tracker["url"].split(os.sep)[2].split(":")[0]
|
||||
default_tag = tracker["url"].split("/")[2].split(":")[0]
|
||||
tracker["tag"] = self.config.util.check_for_attribute(
|
||||
self.config.data, "tag", parent="tracker", subparent=default_tag, default=default_tag, var_type="list"
|
||||
)
|
||||
|
|
|
@ -17,11 +17,15 @@ from ruamel.yaml.constructor import ConstructorError
|
|||
logger = logging.getLogger("qBit Manage")
|
||||
|
||||
|
||||
def get_list(data, lower=False, split=True, int_list=False):
|
||||
def get_list(data, lower=False, split=True, int_list=False, upper=False):
|
||||
"""Return a list from a string or list."""
|
||||
if data is None:
|
||||
return None
|
||||
elif isinstance(data, list):
|
||||
if lower is True:
|
||||
return [d.strip().lower() for d in data]
|
||||
if upper is True:
|
||||
return [d.strip().upper() for d in data]
|
||||
return data
|
||||
elif isinstance(data, dict):
|
||||
return [data]
|
||||
|
@ -29,6 +33,8 @@ def get_list(data, lower=False, split=True, int_list=False):
|
|||
return [str(data)]
|
||||
elif lower is True:
|
||||
return [d.strip().lower() for d in str(data).split(",")]
|
||||
elif upper is True:
|
||||
return [d.strip().upper() for d in str(data).split(",")]
|
||||
elif int_list is True:
|
||||
try:
|
||||
return [int(d.strip()) for d in str(data).split(",")]
|
||||
|
@ -81,6 +87,8 @@ class TorrentMessages:
|
|||
"TRACKER NICHT REGISTRIERT.",
|
||||
"TORRENT EXISTIERT NICHT",
|
||||
"TORRENT NICHT GEFUNDEN",
|
||||
"TORRENT DELETED", # NexusPHP
|
||||
"TORRENT BANNED", # NexusPHP
|
||||
]
|
||||
|
||||
UNREGISTERED_MSGS_BHD = [
|
||||
|
@ -357,6 +365,8 @@ class check:
|
|||
message = "No Paths exist"
|
||||
elif var_type == "lower_list":
|
||||
return get_list(data[attribute], lower=True)
|
||||
elif var_type == "upper_list":
|
||||
return get_list(data[attribute], upper=True)
|
||||
elif test_list is None or data[attribute] in test_list:
|
||||
return data[attribute]
|
||||
else:
|
||||
|
|
|
@ -83,14 +83,6 @@ parser.add_argument(
|
|||
type=str,
|
||||
help="This is used if you want to use a different name for your log file. Example: tv.log",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-cs",
|
||||
"--cross-seed",
|
||||
dest="cross_seed",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Use this after running cross-seed script to add torrents from the cross-seed output folder to qBittorrent",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-re",
|
||||
"--recheck",
|
||||
|
@ -271,7 +263,6 @@ sch = get_arg("QBT_SCHEDULE", args.schedule)
|
|||
startupDelay = get_arg("QBT_STARTUP_DELAY", args.startupDelay)
|
||||
config_files = get_arg("QBT_CONFIG", args.configfiles)
|
||||
log_file = get_arg("QBT_LOGFILE", args.logfile)
|
||||
cross_seed = get_arg("QBT_CROSS_SEED", args.cross_seed, arg_bool=True)
|
||||
recheck = get_arg("QBT_RECHECK", args.recheck, arg_bool=True)
|
||||
cat_update = get_arg("QBT_CAT_UPDATE", args.cat_update, arg_bool=True)
|
||||
tag_update = get_arg("QBT_TAG_UPDATE", args.tag_update, arg_bool=True)
|
||||
|
@ -322,7 +313,6 @@ for v in [
|
|||
"startupDelay",
|
||||
"config_files",
|
||||
"log_file",
|
||||
"cross_seed",
|
||||
"recheck",
|
||||
"cat_update",
|
||||
"tag_update",
|
||||
|
@ -370,7 +360,6 @@ from modules import util # noqa
|
|||
util.logger = logger
|
||||
from modules.config import Config # noqa
|
||||
from modules.core.category import Category # noqa
|
||||
from modules.core.cross_seed import CrossSeed # noqa
|
||||
from modules.core.recheck import ReCheck # noqa
|
||||
from modules.core.remove_orphaned import RemoveOrphaned # noqa
|
||||
from modules.core.remove_unregistered import RemoveUnregistered # noqa
|
||||
|
@ -497,12 +486,6 @@ def start():
|
|||
if cfg.commands["tag_update"]:
|
||||
stats["tagged"] += Tags(qbit_manager).stats
|
||||
|
||||
# Set Cross Seed
|
||||
if cfg.commands["cross_seed"]:
|
||||
cross_seed = CrossSeed(qbit_manager)
|
||||
stats["added"] += cross_seed.stats_added
|
||||
stats["tagged"] += cross_seed.stats_tagged
|
||||
|
||||
# Remove Unregistered Torrents and tag errors
|
||||
if cfg.commands["rem_unregistered"] or cfg.commands["tag_tracker_error"]:
|
||||
rem_unreg = RemoveUnregistered(qbit_manager)
|
||||
|
|
Loading…
Add table
Reference in a new issue