* 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:
bobokun 2025-02-28 20:39:21 -05:00 committed by GitHub
parent 54cee3acd0
commit fc24d1d934
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 83 additions and 261 deletions

View file

@ -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]

View file

@ -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

View file

@ -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)

View file

@ -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",

View file

@ -1 +1 @@
4.1.18
4.2.0

View file

@ -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

View file

@ -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 |

View file

@ -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**

View file

@ -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

View file

@ -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(

View file

@ -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,
)

View file

@ -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)

View file

@ -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 = []

View file

@ -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"
)

View file

@ -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:

View file

@ -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)