mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2025-10-11 22:36:35 +08:00
* Fixes #388 * Bump docker/setup-buildx-action from 2 to 3 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Bump docker/login-action from 2 to 3 Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Bump docker/build-push-action from 4 to 5 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v4...v5) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Bump gitpython from 3.1.35 to 3.1.36 Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.35 to 3.1.36. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.35...3.1.36) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.11.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.11.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.11.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.11.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) * Error handling when BHD API doesn't respond * add BHD specific announce related issues * handle JSONDecodeError * Special mapping to leave torrents uncategorized on cat-update (#398) Special mapping to leave torrents uncategorized on cat-update (closes #395) * Bump gitpython from 3.1.36 to 3.1.37 Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.36 to 3.1.37. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.36...3.1.37) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.32 to 0.17.33 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.32 to 0.17.33. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * last_active flag for share_limits (#397) Added a last_active flag for share_limits to resume torrents and avoid cleanup if there was activity in the last X minutes. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#405) updates: - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.11.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.11.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.13.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * Bump schedule from 1.2.0 to 1.2.1 Bumps [schedule](https://github.com/dbader/schedule) from 1.2.0 to 1.2.1. - [Changelog](https://github.com/dbader/schedule/blob/master/HISTORY.rst) - [Commits](https://github.com/dbader/schedule/compare/1.2.0...1.2.1) --- updated-dependencies: - dependency-name: schedule dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.33 to 0.17.34 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.33 to 0.17.34. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * fix exit codes when program fails (#411) Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * Bump ruamel-yaml from 0.17.34 to 0.17.35 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.34 to 0.17.35. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * [pre-commit.ci] pre-commit autoupdate (#409) updates: - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.12.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.12.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.14.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.14.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * New option cat in trackers (#400) * New option cat in trackers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * update config.sample for #200 * add additional script to edit trackers * clarify remote_dir usage (#417) * Bump gitpython from 3.1.35 to 3.1.37 (#414) Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.35 to 3.1.37. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.35...3.1.37) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#413) updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.12.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.12.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.15.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: bobokun <12660469+bobokun@users.noreply.github.com> * 4.0.5 * Fixes #419 * Bump pre-commit from 3.4.0 to 3.5.0 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.4.0...v3.5.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump gitpython from 3.1.37 to 3.1.38 Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.37 to 3.1.38. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.37...3.1.38) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Remove duplicates from when processing cleanup_dirs (#422) remove duplicates from cleanup_dirs * Bump gitpython from 3.1.38 to 3.1.40 Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.38 to 3.1.40. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.38...3.1.40) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.35 to 0.17.39 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.35 to 0.17.39. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * fixes #426 * Bump qbittorrent-api from 2023.9.53 to 2023.10.54 Bumps [qbittorrent-api](https://github.com/rmartin16/qbittorrent-api) from 2023.9.53 to 2023.10.54. - [Release notes](https://github.com/rmartin16/qbittorrent-api/releases) - [Changelog](https://github.com/rmartin16/qbittorrent-api/blob/main/CHANGELOG.md) - [Commits](https://github.com/rmartin16/qbittorrent-api/compare/v2023.9.53...v2023.10.54) --- updated-dependencies: - dependency-name: qbittorrent-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.39 to 0.17.40 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.39 to 0.17.40. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump ruamel-yaml from 0.17.40 to 0.18.0 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.40 to 0.18.0. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Fixes #429 * Fixes bug in edit_tracker * Bump ruamel-yaml from 0.18.0 to 0.18.2 Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.18.0 to 0.18.2. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * 4.0.6 --------- Signed-off-by: dependabot[bot] <support@github.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: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Esteban Thilliez <77675611+estebanthi@users.noreply.github.com> Co-authored-by: Fabricio Silva <hi@fabricio.dev> Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Co-authored-by: garypiner <36236331+garypiner@users.noreply.github.com>
233 lines
11 KiB
Python
Executable file
233 lines
11 KiB
Python
Executable file
"""Class to handle webhooks."""
|
|
import time
|
|
from json import JSONDecodeError
|
|
|
|
from requests.exceptions import JSONDecodeError as requestsJSONDecodeError
|
|
|
|
from modules import util
|
|
from modules.util import Failed
|
|
|
|
logger = util.logger
|
|
|
|
GROUP_NOTIFICATION_LIMIT = 10
|
|
|
|
|
|
class Webhooks:
|
|
"""Class to handle webhooks."""
|
|
|
|
def __init__(self, config, system_webhooks, notifiarr=None, apprise=None):
|
|
"""Initialize the class."""
|
|
self.config = config
|
|
self.error_webhooks = system_webhooks["error"] if "error" in system_webhooks else []
|
|
self.run_start_webhooks = system_webhooks["run_start"] if "run_start" in system_webhooks else []
|
|
self.run_end_webhooks = system_webhooks["run_end"] if "run_end" in system_webhooks else []
|
|
if "function" in system_webhooks and system_webhooks["function"] is not None:
|
|
try:
|
|
self.function_webhooks = system_webhooks["function"][0]
|
|
except (IndexError, KeyError):
|
|
self.function_webhooks = []
|
|
else:
|
|
self.function_webhooks = []
|
|
self.notifiarr = notifiarr
|
|
self.apprise = apprise
|
|
|
|
def request_and_check(self, webhook, json):
|
|
"""
|
|
Send a webhook request and check for errors.
|
|
retry up to 6 times if the response is a 500+ error.
|
|
"""
|
|
retry_count = 0
|
|
retry_attempts = 6
|
|
request_delay = 2
|
|
for retry_count in range(retry_attempts):
|
|
if webhook == "notifiarr":
|
|
response = self.notifiarr.notification(json=json)
|
|
else:
|
|
webhook_post = webhook
|
|
if webhook == "apprise":
|
|
json["urls"] = self.apprise.notify_url
|
|
webhook_post = f"{self.apprise.api_url}/notify"
|
|
response = self.config.post(webhook_post, json=json)
|
|
if response.status_code < 500:
|
|
return response
|
|
logger.debug(f"({response.status_code} [{response.reason}]) Retrying in {request_delay} seconds.")
|
|
time.sleep(request_delay)
|
|
logger.debug(f"(Retry {retry_count + 1} of {retry_attempts}.")
|
|
retry_count += 1
|
|
logger.warning(f"({response.status_code} [{response.reason}]) after {retry_attempts} attempts.")
|
|
|
|
def _request(self, webhooks, json):
|
|
"""
|
|
Send a webhook request via request_and_check.
|
|
Check for errors and log them.
|
|
"""
|
|
logger.trace("")
|
|
logger.trace(f"JSON: {json}")
|
|
for webhook in list(set(webhooks)):
|
|
response = None
|
|
logger.trace(f"Webhook: {webhook}")
|
|
if webhook is None:
|
|
break
|
|
elif (webhook == "notifiarr" and self.notifiarr is None) or (webhook == "apprise" and self.apprise is None):
|
|
logger.warning(f"Webhook attribute set to {webhook} but {webhook} attribute is not configured.")
|
|
break
|
|
response = self.request_and_check(webhook, json)
|
|
if response:
|
|
skip = False
|
|
try:
|
|
response_json = response.json()
|
|
logger.trace(f"Response: {response_json}")
|
|
if (
|
|
"result" in response_json
|
|
and response_json["result"] == "error"
|
|
and "details" in response_json
|
|
and "response" in response_json["details"]
|
|
):
|
|
if "trigger is not enabled" in response_json["details"]["response"]:
|
|
logger.info(f"Notifiarr Warning: {response_json['details']['response']}")
|
|
skip = True
|
|
else:
|
|
raise Failed(f"Notifiarr Error: {response_json['details']['response']}")
|
|
if (
|
|
response.status_code >= 400 or ("result" in response_json and response_json["result"] == "error")
|
|
) and skip is False:
|
|
raise Failed(f"({response.status_code} [{response.reason}]) {response_json}")
|
|
except (JSONDecodeError, requestsJSONDecodeError) as exc:
|
|
if response.status_code >= 400:
|
|
raise Failed(f"({response.status_code} [{response.reason}])") from exc
|
|
|
|
def start_time_hooks(self, start_time):
|
|
"""Send a webhook to notify that the run has started."""
|
|
if self.run_start_webhooks:
|
|
dry_run = self.config.commands["dry_run"]
|
|
if dry_run:
|
|
start_type = "Dry-"
|
|
else:
|
|
start_type = ""
|
|
self._request(
|
|
self.run_start_webhooks,
|
|
{
|
|
"function": "run_start",
|
|
"title": None,
|
|
"body": f"Starting {start_type}Run",
|
|
"start_time": start_time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
"dry_run": self.config.commands["dry_run"],
|
|
},
|
|
)
|
|
|
|
def end_time_hooks(self, start_time, end_time, run_time, next_run, stats, body):
|
|
"""Send a webhook to notify that the run has ended."""
|
|
if self.run_end_webhooks:
|
|
self._request(
|
|
self.run_end_webhooks,
|
|
{
|
|
"function": "run_end",
|
|
"title": None,
|
|
"body": body,
|
|
"start_time": start_time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
"end_time": end_time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
"next_run": next_run.strftime("%Y-%m-%d %H:%M:%S") if next_run is not None else next_run,
|
|
"run_time": run_time,
|
|
"torrents_added": stats["added"],
|
|
"torrents_deleted": stats["deleted"],
|
|
"torrents_deleted_and_contents_count": stats["deleted_contents"],
|
|
"torrents_resumed": stats["resumed"],
|
|
"torrents_rechecked": stats["rechecked"],
|
|
"torrents_categorized": stats["categorized"],
|
|
"torrents_tagged": stats["tagged"],
|
|
"remove_unregistered": stats["rem_unreg"],
|
|
"torrents_tagged_tracker_error": stats["tagged_tracker_error"],
|
|
"torrents_untagged_tracker_error": stats["untagged_tracker_error"],
|
|
"orphaned_files_found": stats["orphaned"],
|
|
"torrents_tagged_no_hardlinks": stats["tagged_noHL"],
|
|
"torrents_untagged_no_hardlinks": stats["untagged_noHL"],
|
|
"torrents_updated_share_limits": stats["updated_share_limits"],
|
|
"torrents_cleaned_share_limits": stats["cleaned_share_limits"],
|
|
"files_deleted_from_recyclebin": stats["recycle_emptied"],
|
|
"files_deleted_from_orphaned": stats["orphaned_emptied"],
|
|
},
|
|
)
|
|
|
|
def error_hooks(self, text, function_error=None, critical=True):
|
|
"""Send a webhook to notify that an error has occurred."""
|
|
if self.error_webhooks:
|
|
err_type = "failure" if critical is True else "warning"
|
|
json = {
|
|
"function": "run_error",
|
|
"title": f"{function_error} Error",
|
|
"body": str(text),
|
|
"critical": critical,
|
|
"type": err_type,
|
|
}
|
|
if function_error:
|
|
json["function_error"] = function_error
|
|
self._request(self.error_webhooks, json)
|
|
|
|
def function_hooks(self, webhook, json):
|
|
"""Send a webhook to notify that a function has completed."""
|
|
if self.function_webhooks:
|
|
self._request(webhook, json)
|
|
|
|
def notify(self, torrents_updated=[], payload={}, group_by=""):
|
|
if len(torrents_updated) > GROUP_NOTIFICATION_LIMIT:
|
|
logger.trace(
|
|
f"Number of torrents updated > {GROUP_NOTIFICATION_LIMIT}, grouping notifications"
|
|
f"{f' by {group_by}' if group_by else ''}",
|
|
)
|
|
if group_by == "category":
|
|
group_attr = group_notifications_by_key(payload, "torrent_category")
|
|
elif group_by == "tag":
|
|
group_attr = group_notifications_by_key(payload, "torrent_tag")
|
|
elif group_by == "tracker":
|
|
group_attr = group_notifications_by_key(payload, "torrent_tracker")
|
|
|
|
# group notifications by grouping attribute
|
|
for group in group_attr:
|
|
num_torrents_updated = len(group_attr[group]["torrents"])
|
|
only_one_torrent_updated = num_torrents_updated == 1
|
|
|
|
attr = {
|
|
"function": group_attr[group]["function"],
|
|
"title": f"{group_attr[group]['title']} for {group}",
|
|
"body": (
|
|
group_attr[group]["body"]
|
|
if only_one_torrent_updated
|
|
else (
|
|
f"Updated {num_torrents_updated} "
|
|
f"{'torrent' if only_one_torrent_updated else 'torrents'} with {group_by} '{group}'"
|
|
)
|
|
),
|
|
"torrents": group_attr[group]["torrents"],
|
|
}
|
|
if group_by == "category":
|
|
attr["torrent_category"] = group
|
|
attr["torrent_tag"] = group_attr[group].get("torrent_tag") if only_one_torrent_updated else None
|
|
attr["torrent_tracker"] = group_attr[group].get("torrent_tracker") if only_one_torrent_updated else None
|
|
attr["notifiarr_indexer"] = group_attr[group].get("notifiarr_indexer") if only_one_torrent_updated else None
|
|
elif group_by == "tag":
|
|
attr["torrent_tag"] = group
|
|
attr["torrent_category"] = group_attr[group].get("torrent_category") if only_one_torrent_updated else None
|
|
attr["torrent_tracker"] = group_attr[group].get("torrent_tracker")
|
|
attr["notifiarr_indexer"] = group_attr[group].get("notifiarr_indexer")
|
|
elif group_by == "tracker":
|
|
attr["torrent_tracker"] = group
|
|
attr["torrent_category"] = group_attr[group].get("torrent_category") if only_one_torrent_updated else None
|
|
attr["torrent_tag"] = group_attr[group].get("torrent_tag") if only_one_torrent_updated else None
|
|
attr["notifiarr_indexer"] = group_attr[group].get("notifiarr_indexer")
|
|
|
|
self.config.send_notifications(attr)
|
|
else:
|
|
for attr in payload:
|
|
self.config.send_notifications(attr)
|
|
|
|
|
|
def group_notifications_by_key(payload, key):
|
|
"""Group notifications by key"""
|
|
group_attr = {}
|
|
for attr in payload:
|
|
group = attr[key]
|
|
if group not in group_attr:
|
|
group_attr[group] = attr
|
|
else:
|
|
group_attr[group]["torrents"].append(attr.get("torrents", [None])[0])
|
|
return group_attr
|