mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2025-09-12 16:15:25 +08:00
commit
16d545067f
14 changed files with 121 additions and 54 deletions
|
@ -27,16 +27,16 @@ repos:
|
|||
args: [--format, parsable, --strict]
|
||||
exclude: ^.github/
|
||||
- repo: https://github.com/lyz-code/yamlfix
|
||||
rev: 1.10.0
|
||||
rev: 1.11.0
|
||||
hooks:
|
||||
- id: yamlfix
|
||||
exclude: ^.github/
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.9.0
|
||||
rev: v3.10.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.6.0
|
||||
rev: v3.7.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py3-plus]
|
||||
|
|
29
CHANGELOG
29
CHANGELOG
|
@ -1,26 +1,13 @@
|
|||
# Requirements Updated
|
||||
- qbitorrent-api updated to 2023.6.49
|
||||
- qbitorrent-api updated to 2023.6.50
|
||||
- ruamel.yaml updated to 0.17.32
|
||||
# New Features
|
||||
- cross-seed will move torrent to an error folder if it fails to inject torrent
|
||||
- Define multiple announce urls for one tracker (#328 Thanks to @buthed010203 for the PR)
|
||||
- Adds min_num_seeds condition to share_limits (Closes [#321](https://github.com/StuffAnThings/qbit_manage/issues/321))
|
||||
|
||||
# Bug Fixes
|
||||
- Fixes #329 (Updates missing share_limits tag even when share_limits are satisfied)
|
||||
- Fixes #327 (Zero value share limits not being applied correctly)
|
||||
Fixes [#333](https://github.com/StuffAnThings/qbit_manage/issues/333)
|
||||
Fixes [#340](https://github.com/StuffAnThings/qbit_manage/issues/340)
|
||||
Fixes [#343](https://github.com/StuffAnThings/qbit_manage/issues/343)
|
||||
Fixes bug when checking tags in torrents
|
||||
|
||||
# Enhancements
|
||||
- Logic for `share_limits_suffix_tag` changed to become a prefix tag instead along with adding the priority of the group. The reason for this change is so it's easier to see the share limit groups togethered in qbitorrent ordered by priority.
|
||||
- `share_limits_suffix_tag` key is now `share_limits_tag`
|
||||
- No config changes are required as the qbm will automatically change the previous `share_limits_suffix_tag` key to `share_limits_tag`
|
||||
- Changes the default value of `share_limits_tag` to `~share_limit`. The `~` is used to sort the `share_limits_tag` in the qbt webUI to the bottom for less clutter
|
||||
|
||||
Example based on config.sample:
|
||||
|
||||
| old tag (v4.0.0) | new tag (v4.0.1) |
|
||||
| ----------- | ----------- |
|
||||
| noHL.share_limit | ~share_limit_1.noHL |
|
||||
| cross-seed.share_limit | ~share_limit_2.cross-seed |
|
||||
| PTP.share_limit | ~share_limit_3.PTP |
|
||||
| default.share_limit | ~share_limit_999.default |
|
||||
|
||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.0.0...v4.0.1
|
||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.0.1...v4.0.2
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
4.0.1
|
||||
4.0.2
|
||||
|
|
|
@ -176,6 +176,7 @@ share_limits:
|
|||
# Will default to -1 (no limit) if not specified for the group.
|
||||
max_seeding_time: 129600
|
||||
# <OPTIONAL> min_seeding_time <int>: Will prevent torrent deletion by cleanup variable if torrent has not yet minimum seeding time (minutes).
|
||||
# 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.
|
||||
# Will default to 0 if not specified for the group.
|
||||
min_seeding_time: 43200
|
||||
# <OPTIONAL> Limit Upload Speed <int>: Will limit the upload speed KiB/s (KiloBytes/second) (`-1` : No Limit)
|
||||
|
@ -187,6 +188,10 @@ share_limits:
|
|||
# <OPTIONAL> add_group_to_tag <bool>: This adds your grouping as a tag with a prefix defined in settings . Default is true
|
||||
# Example: A grouping defined as noHL will have a tag set to ~share_limit.noHL (if using the default prefix)
|
||||
add_group_to_tag: true
|
||||
# <OPTIONAL> min_num_seeds <int>: This will prevent torrent deletion by cleanup variable if the number of seeds is less than the value set here.
|
||||
# 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.
|
||||
# Will default to 0 if not specified for the group.
|
||||
min_num_seeds: 0
|
||||
cross-seed:
|
||||
priority: 2
|
||||
include_all_tags:
|
||||
|
@ -237,7 +242,7 @@ orphaned:
|
|||
- "**/@eaDir"
|
||||
- "/data/torrents/temp/**"
|
||||
- "**/*.!qB"
|
||||
- "**/_unpackerred"
|
||||
- "**/*_unpackerred"
|
||||
|
||||
apprise:
|
||||
# Apprise integration with webhooks
|
||||
|
|
|
@ -432,6 +432,17 @@ class Config:
|
|||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["min_num_seeds"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"min_num_seeds",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="int",
|
||||
min_int=0,
|
||||
default=0,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["resume_torrent_after_change"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"resume_torrent_after_change",
|
||||
|
|
|
@ -112,7 +112,7 @@ class CrossSeed:
|
|||
t_name = torrent.name
|
||||
t_cat = torrent.category
|
||||
if (
|
||||
"cross-seed" not in torrent.tags
|
||||
not util.is_tag_in_torrent("cross-seed", torrent.tags)
|
||||
and self.qbt.torrentinfo[t_name]["count"] > 1
|
||||
and self.qbt.torrentinfo[t_name]["first_hash"] != torrent.hash
|
||||
):
|
||||
|
|
|
@ -31,7 +31,7 @@ class RemoveOrphaned:
|
|||
orphaned_files = []
|
||||
excluded_orphan_files = []
|
||||
|
||||
root_files = self.executor.submit(util.get_root_files, self.remote_dir, self.root_dir, self.orphaned_dir)
|
||||
root_files = self.executor.submit(util.get_root_files, self.root_dir, self.remote_dir, self.orphaned_dir)
|
||||
|
||||
# Get an updated list of torrents
|
||||
logger.print_line("Locating orphan files", self.config.loglevel)
|
||||
|
|
|
@ -108,12 +108,6 @@ class RemoveUnregistered:
|
|||
msg_up = trk.msg.upper()
|
||||
msg = trk.msg
|
||||
if TrackerStatus(trk.status) == TrackerStatus.NOT_WORKING:
|
||||
# Tag any error torrents
|
||||
if self.cfg_tag_error and self.tag_error not in check_tags:
|
||||
if not list_in_text(msg_up, TorrentMessages.IGNORE_MSGS) and not list_in_text(
|
||||
msg_up, TorrentMessages.UNREGISTERED_MSGS
|
||||
):
|
||||
self.tag_tracker_error(msg, tracker, torrent)
|
||||
# Check for unregistered torrents
|
||||
if self.cfg_rem_unregistered:
|
||||
if list_in_text(msg_up, TorrentMessages.UNREGISTERED_MSGS) and not list_in_text(
|
||||
|
@ -125,6 +119,9 @@ class RemoveUnregistered:
|
|||
if self.check_for_unregistered_torrents_using_bhd_api(tracker, msg_up, torrent.hash):
|
||||
self.del_unregistered(msg, tracker, torrent)
|
||||
break
|
||||
# Tag any error torrents
|
||||
if self.cfg_tag_error and self.tag_error not in check_tags:
|
||||
self.tag_tracker_error(msg, tracker, torrent)
|
||||
|
||||
except NotFound404Error:
|
||||
continue
|
||||
|
|
|
@ -2,10 +2,14 @@ import os
|
|||
from datetime import timedelta
|
||||
|
||||
from modules import util
|
||||
from modules.util import is_tag_in_torrent
|
||||
from modules.webhooks import GROUP_NOTIFICATION_LIMIT
|
||||
|
||||
logger = util.logger
|
||||
|
||||
MIN_SEEDING_TIME_TAG = "MinSeedTimeNotReached"
|
||||
MIN_NUM_SEEDS_TAG = "MinSeedsNotMet"
|
||||
|
||||
|
||||
class ShareLimits:
|
||||
def __init__(self, qbit_manager):
|
||||
|
@ -179,7 +183,9 @@ class ShareLimits:
|
|||
group_config["limit_upload_speed"] = -1
|
||||
check_limit_upload_speed = group_config["limit_upload_speed"] != torrent_upload_limit
|
||||
hash_not_prev_checked = t_hash not in self.torrent_hash_checked
|
||||
share_limits_not_yet_tagged = True if self.group_tag and self.group_tag not in torrent.tags else False
|
||||
share_limits_not_yet_tagged = (
|
||||
True if self.group_tag and not is_tag_in_torrent(self.group_tag, torrent.tags) else False
|
||||
)
|
||||
logger.trace(f"Torrent: {t_name} [Hash: {t_hash}]")
|
||||
logger.trace(f"Torrent Category: {torrent.category}")
|
||||
logger.trace(f"Torrent Tags: {torrent.tags}")
|
||||
|
@ -201,7 +207,9 @@ class ShareLimits:
|
|||
if (
|
||||
check_max_ratio or check_max_seeding_time or check_limit_upload_speed or share_limits_not_yet_tagged
|
||||
) and hash_not_prev_checked:
|
||||
if "MinSeedTimeNotReached" not in torrent.tags:
|
||||
if not is_tag_in_torrent(MIN_SEEDING_TIME_TAG, torrent.tags) and not is_tag_in_torrent(
|
||||
MIN_NUM_SEEDS_TAG, torrent.tags
|
||||
):
|
||||
logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel)
|
||||
logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
||||
if self.group_tag:
|
||||
|
@ -217,6 +225,7 @@ class ShareLimits:
|
|||
max_ratio=group_config["max_ratio"],
|
||||
max_seeding_time=group_config["max_seeding_time"],
|
||||
min_seeding_time=group_config["min_seeding_time"],
|
||||
min_num_seeds=group_config["min_num_seeds"],
|
||||
resume_torrent=group_config["resume_torrent_after_change"],
|
||||
tracker=tracker["url"],
|
||||
)
|
||||
|
@ -232,10 +241,9 @@ class ShareLimits:
|
|||
"""Removes previous share limits tag, updates tag and share limits for a torrent, and resumes the torrent"""
|
||||
# Remove previous share_limits tag
|
||||
if not self.config.dry_run:
|
||||
tags = util.get_list(torrent.tags)
|
||||
for tag in tags:
|
||||
if self.share_limits_tag in tag:
|
||||
torrent.remove_tags(tag)
|
||||
tag = is_tag_in_torrent(self.share_limits_tag, torrent.tags, exact=False)
|
||||
if tag:
|
||||
torrent.remove_tags(tag)
|
||||
|
||||
# Will tag the torrent with the group name if add_group_to_tag is True and set the share limits
|
||||
self.set_tags_and_limits(
|
||||
|
@ -345,7 +353,7 @@ class ShareLimits:
|
|||
body.append(msg)
|
||||
# Update Torrents
|
||||
if not self.config.dry_run:
|
||||
if tags and tags not in torrent.tags:
|
||||
if tags and not is_tag_in_torrent(tags, torrent.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:
|
||||
|
@ -357,24 +365,28 @@ class ShareLimits:
|
|||
max_ratio = torrent.max_ratio
|
||||
if max_seeding_time is None:
|
||||
max_seeding_time = torrent.max_seeding_time
|
||||
if "MinSeedTimeNotReached" in torrent.tags:
|
||||
if is_tag_in_torrent(MIN_SEEDING_TIME_TAG, torrent.tags):
|
||||
return []
|
||||
if is_tag_in_torrent(MIN_NUM_SEEDS_TAG, torrent.tags):
|
||||
return []
|
||||
torrent.set_share_limits(max_ratio, max_seeding_time)
|
||||
return body
|
||||
|
||||
def has_reached_seed_limit(self, torrent, max_ratio, max_seeding_time, min_seeding_time, resume_torrent, tracker):
|
||||
def has_reached_seed_limit(
|
||||
self, torrent, max_ratio, max_seeding_time, min_seeding_time, min_num_seeds, resume_torrent, tracker
|
||||
):
|
||||
"""Check if torrent has reached seed limit"""
|
||||
body = ""
|
||||
|
||||
def _has_reached_min_seeding_time_limit():
|
||||
print_log = []
|
||||
if torrent.seeding_time >= min_seeding_time * 60:
|
||||
if "MinSeedTimeNotReached" in torrent.tags:
|
||||
if is_tag_in_torrent(MIN_SEEDING_TIME_TAG, torrent.tags):
|
||||
if not self.config.dry_run:
|
||||
torrent.remove_tags(tags="MinSeedTimeNotReached")
|
||||
torrent.remove_tags(tags=MIN_SEEDING_TIME_TAG)
|
||||
return True
|
||||
else:
|
||||
if "MinSeedTimeNotReached" not in torrent.tags:
|
||||
if not is_tag_in_torrent(MIN_SEEDING_TIME_TAG, torrent.tags):
|
||||
print_log += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel)
|
||||
print_log += logger.print_line(logger.insert_space(f"Tracker: {tracker}", 8), self.config.loglevel)
|
||||
print_log += logger.print_line(
|
||||
|
@ -389,15 +401,47 @@ class ShareLimits:
|
|||
self.config.loglevel,
|
||||
)
|
||||
print_log += logger.print_line(
|
||||
logger.insert_space("Adding Tag: MinSeedTimeNotReached", 8), self.config.loglevel
|
||||
logger.insert_space(f"Adding Tag: {MIN_SEEDING_TIME_TAG}", 8), self.config.loglevel
|
||||
)
|
||||
if not self.config.dry_run:
|
||||
torrent.add_tags("MinSeedTimeNotReached")
|
||||
torrent.add_tags(MIN_SEEDING_TIME_TAG)
|
||||
torrent.set_share_limits(-1, -1)
|
||||
if resume_torrent:
|
||||
torrent.resume()
|
||||
return False
|
||||
|
||||
def _is_less_than_min_num_seeds():
|
||||
print_log = []
|
||||
if min_num_seeds == 0 or torrent.num_complete >= min_num_seeds:
|
||||
if is_tag_in_torrent(MIN_NUM_SEEDS_TAG, torrent.tags):
|
||||
if not self.config.dry_run:
|
||||
torrent.remove_tags(tags=MIN_NUM_SEEDS_TAG)
|
||||
return False
|
||||
else:
|
||||
if not is_tag_in_torrent(MIN_NUM_SEEDS_TAG, torrent.tags):
|
||||
print_log += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel)
|
||||
print_log += logger.print_line(logger.insert_space(f"Tracker: {tracker}", 8), self.config.loglevel)
|
||||
print_log += logger.print_line(
|
||||
logger.insert_space(
|
||||
(
|
||||
f"Min number of seeds not met: Total Seeds ({torrent.num_complete}) <"
|
||||
f"min_num_seeds({min_num_seeds}). Removing Share Limits so qBittorrent can continue"
|
||||
" seeding."
|
||||
),
|
||||
8,
|
||||
),
|
||||
self.config.loglevel,
|
||||
)
|
||||
print_log += logger.print_line(
|
||||
logger.insert_space(f"Adding Tag: {MIN_NUM_SEEDS_TAG}", 8), self.config.loglevel
|
||||
)
|
||||
if not self.config.dry_run:
|
||||
torrent.add_tags(MIN_NUM_SEEDS_TAG)
|
||||
torrent.set_share_limits(-1, -1)
|
||||
if resume_torrent:
|
||||
torrent.resume()
|
||||
return True
|
||||
|
||||
def _has_reached_seeding_time_limit():
|
||||
nonlocal body
|
||||
seeding_time_limit = None
|
||||
|
@ -421,6 +465,9 @@ class ShareLimits:
|
|||
return True
|
||||
return False
|
||||
|
||||
if min_num_seeds is not None:
|
||||
if _is_less_than_min_num_seeds():
|
||||
return body
|
||||
if max_ratio is not None:
|
||||
if max_ratio >= 0:
|
||||
if torrent.ratio >= max_ratio and _has_reached_min_seeding_time_limit():
|
||||
|
|
|
@ -57,7 +57,7 @@ class TagNoHardLinks:
|
|||
Checks for any previous torrents that were tagged with the nohardlinks tag and have since had hardlinks added.
|
||||
If any are found, the nohardlinks tag is removed
|
||||
"""
|
||||
if not (has_nohardlinks) and (self.nohardlinks_tag in torrent.tags):
|
||||
if not (has_nohardlinks) and (util.is_tag_in_torrent(self.nohardlinks_tag, torrent.tags)):
|
||||
self.stats_untagged += 1
|
||||
body = []
|
||||
body += logger.print_line(
|
||||
|
@ -102,7 +102,7 @@ class TagNoHardLinks:
|
|||
has_nohardlinks = check_hardlinks.nohardlink(
|
||||
torrent["content_path"].replace(self.root_dir, self.remote_dir), self.config.notify
|
||||
)
|
||||
if any(tag in torrent.tags for tag in nohardlinks[category]["exclude_tags"]):
|
||||
if any(util.is_tag_in_torrent(tag, torrent.tags) for tag in nohardlinks[category]["exclude_tags"]):
|
||||
# Skip to the next torrent if we find any torrents that are in the exclude tag
|
||||
continue
|
||||
else:
|
||||
|
@ -111,7 +111,7 @@ class TagNoHardLinks:
|
|||
if has_nohardlinks:
|
||||
tracker = self.qbt.get_tags(torrent.trackers)
|
||||
# Will only tag new torrents that don't have nohardlinks_tag tag
|
||||
if self.nohardlinks_tag not in torrent.tags:
|
||||
if not util.is_tag_in_torrent(self.nohardlinks_tag, torrent.tags):
|
||||
self.add_tag_no_hl(
|
||||
torrent=torrent,
|
||||
tracker=tracker,
|
||||
|
|
|
@ -36,7 +36,13 @@ class Qbt:
|
|||
logger.debug(f"Host: {self.host}, Username: {self.username}, Password: {self.password}")
|
||||
ex = ""
|
||||
try:
|
||||
self.client = Client(host=self.host, username=self.username, password=self.password, VERIFY_WEBUI_CERTIFICATE=False)
|
||||
self.client = Client(
|
||||
host=self.host,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
VERIFY_WEBUI_CERTIFICATE=False,
|
||||
REQUESTS_ARGS={"timeout": (45, 60)},
|
||||
)
|
||||
self.client.auth_log_in()
|
||||
self.current_version = self.client.app.version
|
||||
logger.debug(f"qBittorrent: {self.current_version}")
|
||||
|
|
|
@ -34,6 +34,18 @@ def get_list(data, lower=False, split=True, int_list=False):
|
|||
return [d.strip() for d in str(data).split(",")]
|
||||
|
||||
|
||||
def is_tag_in_torrent(check_tag, torrent_tags, exact=True):
|
||||
"""Check if tag is in torrent_tags"""
|
||||
tags = get_list(torrent_tags)
|
||||
if exact:
|
||||
return check_tag in tags
|
||||
else:
|
||||
for t in tags:
|
||||
if check_tag in t:
|
||||
return t
|
||||
return False
|
||||
|
||||
|
||||
class TorrentMessages:
|
||||
"""Contains list of messages to check against a status of a torrent"""
|
||||
|
||||
|
|
|
@ -370,6 +370,8 @@ with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "VERSION")) a
|
|||
version = util.parse_version(line)
|
||||
break
|
||||
branch = util.guess_branch(version, env_version, git_branch)
|
||||
if branch is None:
|
||||
branch = "Unknown"
|
||||
version = (version[0].replace("develop", branch), version[1].replace("develop", branch), version[2])
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
bencodepy==0.9.5
|
||||
GitPython==3.1.31
|
||||
qbittorrent-api==2023.6.49
|
||||
qbittorrent-api==2023.6.50
|
||||
requests==2.31.0
|
||||
retrying==1.3.4
|
||||
ruamel.yaml==0.17.31
|
||||
ruamel.yaml==0.17.32
|
||||
schedule==1.2.0
|
||||
|
|
Loading…
Add table
Reference in a new issue