4 Commits

17 changed files with 268 additions and 4 deletions

173
.dockerignore Executable file
View File

@ -0,0 +1,173 @@
*.example
LICENSE
*.log
README.md
requirements.txt
test_*
Dockerfile
*docker-test*
*.log
*.json
*.csv
*.toml
*.git*
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

0
.gitignore vendored Normal file → Executable file
View File

40
AppriseClient.py Executable file
View File

@ -0,0 +1,40 @@
import requests as r
from tomllib import load
import os
def apprise_notify(req_obj, host, port, aurls, title, body):
payload = {'urls': aurls,'title': title,'body': body,}
url = f'http://{host}:{port}/notify/'
apprise_response = req_obj.post(url, json = payload ,verify=False)
return apprise_response
class AppriseClient:
def __init__(self):
self.config = ''
try:
if os.environ["DOCKER"]:
self.host = os.environ["host"]
self.port = os.environ["port"]
self.aurls = os.environ["aurls"]
self.title = os.environ["title"]
self.body = os.environ["body"]
if os.environ["toml_path"]:
config_file_path=os.environ["toml_path"]
with open(config_file_path, 'rb') as c:
self.config = load(c)
except:
KeyError
if os.path.exists('./config.toml'):
config_file_path = './config.toml'
with open(config_file_path, 'rb') as c:
self.config = load(c)
if self.config:
self.host = self.config["apprise"]["host"]
self.port = self.config["apprise"]["port"]
self.aurls = self.config["apprise"]["aurls"]
self.title = self.config["apprise"]["title"]
self.body = self.config["apprise"]["body"]
self.apprise_response = apprise_notify(r,self.host,self.port,self.aurls,self.title,self.body)
if __name__ == "__main__":
AppriseClient()

8
Dockerfile Executable file
View File

@ -0,0 +1,8 @@
FROM python:alpine3.18
WORKDIR /
COPY . opt
RUN pip install requests
RUN pip install qbittorrent-api
RUN crontab /opt/crontab
RUN chmod +x /opt/entrypoint.sh
CMD ["/opt/entrypoint.sh"]

0
LICENSE Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

5
config.toml.example Normal file → Executable file
View File

@ -23,6 +23,11 @@ use_pushover = false
po_key = "" po_key = ""
po_token = "" po_token = ""
[apprise]
host = "192.168.x.x"
port = 8088
aurls = 'mailto://user:pass@gmail.com'
[dragnet] [dragnet]
enable_dragnet = true enable_dragnet = true
dragnet_outfile = "./orphaned.csv" dragnet_outfile = "./orphaned.csv"

1
crontab Executable file
View File

@ -0,0 +1 @@
0 1 * * * . /etc/environment; python /opt/qbit-maid.py >> /logfile

5
entrypoint.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
printenv | grep -v "no_proxy" >> /etc/environment
crond -f

0
pushover.py Normal file → Executable file
View File

25
qbit-maid.py Normal file → Executable file
View File

@ -4,20 +4,29 @@ from tomllib import load
from qlist import * from qlist import *
from qlogging import * from qlogging import *
from qprocess import * from qprocess import *
from AppriseClient import apprise_notify
import time import time
import datetime import datetime
import logging import logging
from collections import Counter from collections import Counter
import csv import csv
import requests as r
import os
import sys
class Qbt: class Qbt:
def __init__(self): def __init__(self):
"""Main object, should be calling functions from qlist.py, qlogging.py and qprocess.py""" """Main object, should be calling functions from qlist.py, qlogging.py and qprocess.py"""
# Open the config. Needs a json file with the data in config.json.example # Open the config. Needs a json file with the data in config.json.example
self.st = datetime.datetime.now() self.st = datetime.datetime.now()
c = open('./config.toml', 'rb')
self.config = load(c) config_file_path=os.environ["toml_path"]
with open(config_file_path, 'rb') as c:
self.config = load(c)
if os.path.exists('./config.toml'):
config_file_path = './config.toml'
with open(config_file_path, 'rb') as c:
self.config = load(c)
# # Create the api object # # Create the api object
self.qbt_client = qbittorrentapi.Client( self.qbt_client = qbittorrentapi.Client(
# qbittorrent # qbittorrent
@ -52,6 +61,12 @@ class Qbt:
self.po_key = self.config["pushover"]["po_key"] self.po_key = self.config["pushover"]["po_key"]
self.po_token = self.config["pushover"]["po_token"] self.po_token = self.config["pushover"]["po_token"]
#apprise
self.use_apprise = self.config["apprise"]["use_apprise"]
self.apprise_host = self.config["apprise"]["host"]
self.apprise_port = self.config["apprise"]["port"]
self.apprise_aurls = self.config["apprise"]["aurls"]
#dragnet #dragnet
self.enable_dragnet = self.config["dragnet"]["enable_dragnet"] self.enable_dragnet = self.config["dragnet"]["enable_dragnet"]
self.dragnet_outfile = self.config["dragnet"]["dragnet_outfile"] self.dragnet_outfile = self.config["dragnet"]["dragnet_outfile"]
@ -104,6 +119,8 @@ class Qbt:
get_script_runtime(self) get_script_runtime(self)
if self.use_pushover: if self.use_pushover:
tor_notify_summary(self) tor_notify_summary(self)
if self.use_apprise:
tor_notify_apprise(self, r, apprise_notify)
# Run # Run
if __name__== "__main__": if __name__== "__main__":
Qbt() Qbt()

0
qlist.py Normal file → Executable file
View File

15
qlogging.py Normal file → Executable file
View File

@ -16,6 +16,19 @@ def tor_notify(self):
if self.use_pushover: if self.use_pushover:
self.poc = self.po.Pushover(self.po_token) self.poc = self.po.Pushover(self.po_token)
def tor_notify_apprise(self, req_obj, app_obj):
"""Use apprise"""
body = f" Total: {self.total_torrents}\n\
Premature: {self.preme_tor_counter}\n\
Ignored: {self.ignored_counter}\n\
Protected: {self.c[self.tracker_protected_tag]}\n\
Non-protected: {self.c[self.tracker_non_protected_tag]}\n\
Orphaned: {self.up_tor_counter}\n\
Marked for deletion: {len(self.torrent_hash_delete_list)}\n\
{self.extm}"
title = "--- qbit-maid summary ---"
app_obj(req_obj, self.apprise_host, self.apprise_port, self.apprise_aurls, title, body)
def tornotifytest(self): def tornotifytest(self):
"""Used to make sure tornotify is working and messages are getting to the client""" """Used to make sure tornotify is working and messages are getting to the client"""
self.poc.message(self.po_key, "Test Message", title="qbit-maid") self.poc.message(self.po_key, "Test Message", title="qbit-maid")
@ -69,4 +82,6 @@ def get_script_runtime(self):
if self.use_log: if self.use_log:
self.tl.info(f'Execution time: [{elapsed_time}]') self.tl.info(f'Execution time: [{elapsed_time}]')
if self.use_pushover: if self.use_pushover:
self.extm = f"Execution time: [{elapsed_time}]"
if self.use_apprise:
self.extm = f"Execution time: [{elapsed_time}]" self.extm = f"Execution time: [{elapsed_time}]"

0
qprocess.py Normal file → Executable file
View File

0
requirements.txt Normal file → Executable file
View File

0
test_dragnet.py Normal file → Executable file
View File

0
test_qbitmaid.py Normal file → Executable file
View File