mirror of
https://github.com/KevinMidboe/delugeClient.git
synced 2025-10-29 12:00:13 +00:00
Compare commits
33 Commits
8597615e68
...
ci/build-a
| Author | SHA1 | Date | |
|---|---|---|---|
| e81f5f618e | |||
| 4b5500ec3b | |||
| 29c9feeaa1 | |||
| c36bacc264 | |||
| 48e5b3bf3e | |||
| 8853ae85d8 | |||
| 07c6e6fbe1 | |||
| 4d861e1739 | |||
| 5acf8e8848 | |||
| 103696e01a | |||
| b1018d7f9d | |||
| 7d4f4d0e9b | |||
| 2f716e65a3 | |||
| fa59acfd03 | |||
| 120d300b07 | |||
| 0faa42a048 | |||
| e2db73bf2a | |||
| 0841fdc03d | |||
| 76c99568a8 | |||
| 09e496a907 | |||
| 3989523632 | |||
| 30c3e117da | |||
| 3fa8c4b18f | |||
| 11e9677d1a | |||
| 519b51c47c | |||
| 6fd63ff348 | |||
| 74f6f2b06f | |||
| 7ef58745e1 | |||
| b076b1274b | |||
| 97d86253c8 | |||
| d546027df7 | |||
| 2420d9e8c4 | |||
| 2bbf175c2a |
60
.drone.yml
60
.drone.yml
@@ -1,14 +1,66 @@
|
|||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: delugeClient
|
name: Build and test amd64
|
||||||
|
|
||||||
platform:
|
platform:
|
||||||
os: linux
|
os: linux
|
||||||
arch: amd64
|
arch: amd64
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Build package
|
- name: Build source
|
||||||
image: python:3.8
|
image: python:3.10
|
||||||
commands:
|
commands:
|
||||||
- make build
|
- make build
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
image: python:3.10
|
||||||
|
commands:
|
||||||
|
- make dist
|
||||||
|
- pip3 install -r requirements.txt
|
||||||
|
- pip3 install dist/*.whl
|
||||||
|
# - pipenv install pytest
|
||||||
|
|
||||||
|
# - name: Run tests
|
||||||
|
# image: python:3.10
|
||||||
|
# commands:
|
||||||
|
# pipenv run pytest
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: Publish package to PyPi
|
||||||
|
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Newer version to publish?
|
||||||
|
image: python:3.10
|
||||||
|
commands:
|
||||||
|
- pip3 install delugeClient-kevin -q -q
|
||||||
|
- bash publish_version?.sh
|
||||||
|
|
||||||
|
- name: PyPi publish
|
||||||
|
image: python:3.10
|
||||||
|
commands:
|
||||||
|
- make dist
|
||||||
|
- pip3 install twine
|
||||||
|
- twine upload dist/*
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- Build and test amd64
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
event:
|
||||||
|
exclude:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: signature
|
||||||
|
hmac: 08793426ddd2274e2de166144dc15cd63fe6a2c0fd47382d28f20ececee84898
|
||||||
|
|
||||||
|
...
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -7,9 +7,14 @@ install:
|
|||||||
build:
|
build:
|
||||||
python3 setup.py build
|
python3 setup.py build
|
||||||
|
|
||||||
dist:
|
tarball:
|
||||||
python3 setup.py sdist
|
python3 setup.py sdist
|
||||||
|
|
||||||
|
wheel:
|
||||||
|
python3 setup.py bdist_wheel
|
||||||
|
|
||||||
|
dist: tarball wheel
|
||||||
|
|
||||||
upload: clean dist
|
upload: clean dist
|
||||||
twine upload dist/*
|
twine upload dist/*
|
||||||
|
|
||||||
|
|||||||
108
README.md
108
README.md
@@ -4,23 +4,20 @@
|
|||||||
|
|
||||||
<h4 align="center"> A easy to use Deluge CLI that can connect to Deluge RPC (even over ssh) written entirely in python.</h4>
|
<h4 align="center"> A easy to use Deluge CLI that can connect to Deluge RPC (even over ssh) written entirely in python.</h4>
|
||||||
|
|
||||||
| Tested version | PyPi package | Drone CI |
|
| Tested version | PyPi package | License |
|
||||||
|:--------|:------|:------|
|
|:--------|:------|:------|
|
||||||
| [](https://www.python.org/downloads/release/python-380/) | [](https://pypi.org/project/delugeClient_kevin/) | [](https://drone.schleppe.cloud/KevinMidboe/delugeClient)
|
| [](https://www.python.org/downloads/release/python-3100/) | [](https://pypi.org/project/delugeClient_kevin/) |[](LICENSE)
|
||||||
|
|
||||||
|
| Drone CI | Known vulnerabilities |
|
||||||
| Known vulnerabilities | License |
|
|
||||||
|:--------|:------|
|
|:--------|:------|
|
||||||
| [](https://snyk.io/test/github/kevinmidboe/delugeClient?targetFile=requirements.txt) |[](LICENSE)
|
| [](https://drone.schleppe.cloud/KevinMidboe/delugeClient) | [](https://snyk.io/test/github/kevinmidboe/delugeClient?targetFile=requirements.txt)
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="#abstract">Abstract</a> •
|
<a href="#abstract">Abstract</a> •
|
||||||
<a href="#setup_virtualenv">Setup virtualenv</a> •
|
<a href="#install">Install</a> •
|
||||||
<a href="#configure">Configure</a> •
|
|
||||||
<a href="#installation">Install dependencies</a> •
|
|
||||||
<a href="#usage">Usage</a> •
|
<a href="#usage">Usage</a> •
|
||||||
<a href="#running">Running</a> •
|
<a href="#setup_virtualenv">Setup Virtual Environment</a> •
|
||||||
|
<a href="#configure">Configure</a> •
|
||||||
<a href="#contributing">Contributing</a>
|
<a href="#contributing">Contributing</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -28,6 +25,44 @@
|
|||||||
## <a name="abstract"></a> Abstract
|
## <a name="abstract"></a> Abstract
|
||||||
Create a deluge python client for interfacing with deluge for common tasks like listing, adding, removing and setting download directory for torrents.
|
Create a deluge python client for interfacing with deluge for common tasks like listing, adding, removing and setting download directory for torrents.
|
||||||
|
|
||||||
|
## <a name="install"></a> Install
|
||||||
|
Install from source:
|
||||||
|
```bash
|
||||||
|
python3 setup.py install
|
||||||
|
```
|
||||||
|
|
||||||
|
Install from pip:
|
||||||
|
```bash
|
||||||
|
pip3 install delugeClient-kevin
|
||||||
|
```
|
||||||
|
|
||||||
|
## <a name="usage"></a> Usage
|
||||||
|
View delugeClient cli options with `delugeClient --help`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: python -m delugeclient [OPTIONS] COMMAND [ARGS]...
|
||||||
|
|
||||||
|
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ --debug Set log level to debug │
|
||||||
|
│ --info Set log level to info │
|
||||||
|
│ --warning Set log level to warning │
|
||||||
|
│ --error Set log level to error │
|
||||||
|
│ --install-completion Install completion for the current shell. │
|
||||||
|
│ --show-completion Show completion for the current shell, to copy it or customize the installation. │
|
||||||
|
│ --help Show this message and exit. │
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
|
╭─ Commands ──────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ add Add magnet torrent │
|
||||||
|
│ disk Get free disk space │
|
||||||
|
│ get Get torrent by id or hash │
|
||||||
|
│ ls List all torrents │
|
||||||
|
│ remove Remove torrent by id or hash │
|
||||||
|
│ rm Remove torrent by name │
|
||||||
|
│ search Search for string segment in torrent name │
|
||||||
|
│ toggle Toggle torrent download state │
|
||||||
|
│ version Print package version │
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
|
```
|
||||||
|
|
||||||
## <a name="setup_virtualenv"></a> Setup Virtual Environment
|
## <a name="setup_virtualenv"></a> Setup Virtual Environment
|
||||||
Virtual environment allows us to create a local environment for the requirements needed. Because pip does not download packages already downloaded to your system, we can use virtualenv to save our packages in the project folder.
|
Virtual environment allows us to create a local environment for the requirements needed. Because pip does not download packages already downloaded to your system, we can use virtualenv to save our packages in the project folder.
|
||||||
@@ -41,7 +76,7 @@ To install virtualenv, simply run:
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Usage
|
### Virtualenv setup
|
||||||
After you have downloaded this project go to it in your terminal by going to the folder you downloaded and typing the following:
|
After you have downloaded this project go to it in your terminal by going to the folder you downloaded and typing the following:
|
||||||
|
|
||||||
|
|
||||||
@@ -52,14 +87,12 @@ After you have downloaded this project go to it in your terminal by going to the
|
|||||||
The to setup a virtual environment enter this:
|
The to setup a virtual environment enter this:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ virtualenv -p python3.6 env
|
$ virtualenv -p python3.10 env
|
||||||
```
|
```
|
||||||
|
|
||||||
> If you get an error now it might be because you don't have python3.6, please make sure you have python version 3.6 if else you can download it from [here](https://www.python.org/downloads/)
|
> If you get an error now it might be because you don't have python3.10, please make sure you have python version 3.10 if else you can download it from [here](https://www.python.org/downloads/)
|
||||||
|
|
||||||
|
|
||||||
First we navigate to the folder we downloaded.
|
|
||||||
|
|
||||||
Then we use the ```virtualenv``` command to create a ```env``` subdirectory in our project. This is where pip will download everything to and where we can add other specific python versions. Then we need to *activate* our virtual environment by doing:
|
Then we use the ```virtualenv``` command to create a ```env``` subdirectory in our project. This is where pip will download everything to and where we can add other specific python versions. Then we need to *activate* our virtual environment by doing:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -96,51 +129,6 @@ Then you need to change the HOST and PORT to reflect the address for your deluge
|
|||||||
$ cat /home/USER/.config/deluge/auth
|
$ cat /home/USER/.config/deluge/auth
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## <a name="install"></a> Install Required Dependencies
|
|
||||||
Now that we have our virutalenv set up and activated we want to install all the necessary packages listed in `requirements.txt`. To install it's dependencies do the following:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we have our neccessary packages installed!
|
|
||||||
|
|
||||||
|
|
||||||
## <a name="usage"></a> Usage
|
|
||||||
|
|
||||||
```
|
|
||||||
Custom delugeRPC client
|
|
||||||
Usage:
|
|
||||||
deluge_cli add MAGNET [DIR] [--debug | --warning | --error]
|
|
||||||
deluge_cli get TORRENT
|
|
||||||
deluge_cli ls [--downloading | --seeding | --paused]
|
|
||||||
deluge_cli toggle TORRENT
|
|
||||||
deluge_cli rm TORRENT [--debug | --warning | --error]
|
|
||||||
deluge_cli (-h | --help)
|
|
||||||
deluge_cli --version
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
MAGNET Magnet link to add
|
|
||||||
DIR Directory to save to
|
|
||||||
TORRENT A selected torrent
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h --help Show this screen
|
|
||||||
--version Show version
|
|
||||||
--debug Print all debug log
|
|
||||||
--warning Print only logged warnings
|
|
||||||
--error Print error messages (Error/Warning)
|
|
||||||
```
|
|
||||||
|
|
||||||
### <a name="running"></a> Running
|
|
||||||
To interface with deluged :
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./deluge_cli.py ls
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## <a name="contributing"></a> Contributing
|
## <a name="contributing"></a> Contributing
|
||||||
- Fork it!
|
- Fork it!
|
||||||
- Create your feature branch: git checkout -b my-new-feature
|
- Create your feature branch: git checkout -b my-new-feature
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3.6
|
#!/usr/bin/env python3.10
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
from sys import path
|
from sys import path
|
||||||
@@ -7,16 +7,16 @@ from os.path import dirname, join
|
|||||||
path.append(dirname(__file__))
|
path.append(dirname(__file__))
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from utils import BASE_DIR, ColorizeFilter
|
from utils import BASE_DIR
|
||||||
|
|
||||||
|
def addHandler(handler):
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
logger = logging.getLogger('deluge_cli')
|
logger = logging.getLogger('deluge_cli')
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
fh = logging.FileHandler(join(BASE_DIR, 'deluge_cli.log'))
|
fh = logging.FileHandler(join(BASE_DIR, 'deluge_cli.log'))
|
||||||
fh.setLevel(logging.DEBUG)
|
formatter = logging.Formatter('%(asctime)s| %(levelname)s | %(message)s')
|
||||||
|
|
||||||
formatter = logging.Formatter('%(asctime)s %(levelname)8s %(name)s | %(message)s')
|
addHandler(fh)
|
||||||
fh.setFormatter(formatter)
|
|
||||||
|
|
||||||
logger.addHandler(fh)
|
|
||||||
logger.addFilter(ColorizeFilter())
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3.6
|
#!/usr/bin/env python3.10
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -9,27 +9,27 @@ import typer
|
|||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from deluge import Deluge
|
from deluge import Deluge
|
||||||
from utils import ColorizeFilter, BASE_DIR
|
from utils import ColorizeFilter, BASE_DIR, validHash, convertFilesize
|
||||||
from __version__ import __version__
|
from __version__ import __version__
|
||||||
|
from __init__ import addHandler
|
||||||
|
|
||||||
logger = logging.getLogger('deluge_cli')
|
|
||||||
ch = logging.StreamHandler()
|
ch = logging.StreamHandler()
|
||||||
ch.setLevel(logging.ERROR)
|
ch.addFilter(ColorizeFilter())
|
||||||
logger.addHandler(ch)
|
addHandler(ch)
|
||||||
|
logger = logging.getLogger('deluge_cli')
|
||||||
logger.addFilter(ColorizeFilter())
|
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
deluge = Deluge()
|
deluge = None
|
||||||
|
|
||||||
def signal_handler(signal, frame):
|
def signal_handler(signal, frame):
|
||||||
"""
|
"""
|
||||||
Handle exit by Keyboardinterrupt
|
Handle exit by Keyboardinterrupt
|
||||||
"""
|
"""
|
||||||
|
global deluge
|
||||||
del deluge
|
del deluge
|
||||||
|
|
||||||
logger.info('\nGood bye!')
|
logger.info('\nGood bye!')
|
||||||
sys.exit(0)
|
sys.exit(1)
|
||||||
|
|
||||||
def handleKeyboardInterrupt():
|
def handleKeyboardInterrupt():
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
@@ -54,22 +54,30 @@ def printResponse(response, json=False):
|
|||||||
raise error
|
raise error
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def add(magnet: str):
|
def add(magnet: str, json: bool = typer.Option(False, help="Print as json")):
|
||||||
'''
|
'''
|
||||||
Add magnet torrent
|
Add magnet torrent
|
||||||
'''
|
'''
|
||||||
logger.debug('Add command selected')
|
logger.info('Add command selected')
|
||||||
logger.debug(magnet)
|
logger.debug(magnet)
|
||||||
response = deluge.add(magnet)
|
response = deluge.add(magnet)
|
||||||
printResponse(response)
|
if validHash(response):
|
||||||
|
torrent = deluge.get(response)
|
||||||
|
printResponse(torrent, json)
|
||||||
|
else:
|
||||||
|
logger.info('Unable to add torrent')
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def ls(json: bool = typer.Option(False, help="Print as json")):
|
def ls(json: bool = typer.Option(False, help="Print as json")):
|
||||||
'''
|
'''
|
||||||
List all torrents
|
List all torrents
|
||||||
'''
|
'''
|
||||||
logger.debug('List command selected')
|
logger.info('List command selected')
|
||||||
response = deluge.get_all()
|
response = deluge.get_all()
|
||||||
|
if response is None:
|
||||||
|
logger.info('No torrents found')
|
||||||
|
return
|
||||||
|
|
||||||
printResponse(response, json)
|
printResponse(response, json)
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
@@ -77,7 +85,9 @@ def get(id: str, json: bool = typer.Option(False, help="Print as json")):
|
|||||||
'''
|
'''
|
||||||
Get torrent by id or hash
|
Get torrent by id or hash
|
||||||
'''
|
'''
|
||||||
logger.debug('Get command selected for id {}'.format(id))
|
logger.info('Get command selected for id: {}'.format(id))
|
||||||
|
if not validHash(id):
|
||||||
|
return logger.info("Id is not valid")
|
||||||
response = deluge.get(id)
|
response = deluge.get(id)
|
||||||
printResponse(response, json)
|
printResponse(response, json)
|
||||||
|
|
||||||
@@ -86,27 +96,50 @@ def toggle(id: str):
|
|||||||
'''
|
'''
|
||||||
Toggle torrent download state
|
Toggle torrent download state
|
||||||
'''
|
'''
|
||||||
logger.debug('Toggle command selected for id {}'.format(id))
|
logger.info('Toggle command selected for id: {}'.format(id))
|
||||||
response = deluge.toggle(id)
|
if not validHash(id):
|
||||||
printResponse(response)
|
return logger.info("Id is not valid")
|
||||||
|
deluge.toggle(id)
|
||||||
|
torrent = deluge.get(id)
|
||||||
|
printResponse(torrent)
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def search(query: str, json: bool = typer.Option(False, help="Print as json")):
|
def search(query: str, json: bool = typer.Option(False, help="Print as json")):
|
||||||
'''
|
'''
|
||||||
Search for string segment in torrent name
|
Search for string segment in torrent name
|
||||||
'''
|
'''
|
||||||
logger.debug('Search command selected with query: {}'.format(query))
|
logger.info('Search command selected with query: {}'.format(query))
|
||||||
response = deluge.search(query)
|
response = deluge.search(query)
|
||||||
printResponse(response, json)
|
printResponse(response, json)
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def remove(id: str, destroy: bool = typer.Option(False, help="Remove torrent data")):
|
def rm(name: str, destroy: bool = typer.Option(False, help="Remove torrent by name")):
|
||||||
|
'''
|
||||||
|
Remove torrent by name
|
||||||
|
'''
|
||||||
|
logger.info('Removing torrent with name: {}, destroy flag: {}'.format(name, destroy))
|
||||||
|
response = deluge.removeByName(name, destroy)
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def remove(id: str, destroy: bool = typer.Option(False, help="Remove torrent by id")):
|
||||||
'''
|
'''
|
||||||
Remove torrent by id or hash
|
Remove torrent by id or hash
|
||||||
'''
|
'''
|
||||||
logger.debug('Remove command selected for id: {} with destroy: {}'.format(id, destroy))
|
logger.info('Removing torrent with id: {}, destroy flag: {}'.format(id, destroy))
|
||||||
|
if not validHash(id):
|
||||||
|
return logger.info("Id is not valid")
|
||||||
response = deluge.remove(id, destroy)
|
response = deluge.remove(id, destroy)
|
||||||
printResponse(response)
|
|
||||||
|
@app.command()
|
||||||
|
def disk():
|
||||||
|
'''
|
||||||
|
Get free disk space
|
||||||
|
'''
|
||||||
|
response = deluge.freeSpace()
|
||||||
|
if response == None or not isinstance(response, int):
|
||||||
|
logger.error("Unable to get available disk space")
|
||||||
|
return
|
||||||
|
print(convertFilesize(response))
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def version():
|
def version():
|
||||||
@@ -115,11 +148,14 @@ def version():
|
|||||||
'''
|
'''
|
||||||
print(__version__)
|
print(__version__)
|
||||||
|
|
||||||
|
# Runs before any command
|
||||||
@app.callback()
|
@app.callback()
|
||||||
def defaultOptions(debug: bool = typer.Option(False, '--debug', help='Set log level to debug'), info: bool = typer.Option(False, '--info', help='Set log level to info'), warning: bool = typer.Option(False, '--warning', help='Set log level to warning'), error: bool = typer.Option(False, '--error', help='Set log level to error')):
|
def defaultOptions(debug: bool = typer.Option(False, '--debug', help='Set log level to debug'), info: bool = typer.Option(False, '--info', help='Set log level to info'), warning: bool = typer.Option(False, '--warning', help='Set log level to warning'), error: bool = typer.Option(False, '--error', help='Set log level to error')):
|
||||||
ch.setLevel(logging.WARNING)
|
ch.setLevel(logging.INFO)
|
||||||
|
|
||||||
if error == True:
|
if '--json' in sys.argv:
|
||||||
|
ch.setLevel(logging.CRITICAL)
|
||||||
|
elif error == True:
|
||||||
ch.setLevel(logging.ERROR)
|
ch.setLevel(logging.ERROR)
|
||||||
elif warning == True:
|
elif warning == True:
|
||||||
ch.setLevel(logging.WARNING)
|
ch.setLevel(logging.WARNING)
|
||||||
@@ -128,6 +164,10 @@ def defaultOptions(debug: bool = typer.Option(False, '--debug', help='Set log le
|
|||||||
elif debug == True:
|
elif debug == True:
|
||||||
ch.setLevel(logging.DEBUG)
|
ch.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# Initiate deluge
|
||||||
|
global deluge
|
||||||
|
deluge = Deluge()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app()
|
app()
|
||||||
del deluge
|
del deluge
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
__version__ = '0.3.0'
|
__version__ = '0.3.2'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(__version__)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3.6
|
#!/usr/bin/env python3.10
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -7,8 +7,8 @@ import logging
|
|||||||
import requests
|
import requests
|
||||||
import logging.config
|
import logging.config
|
||||||
|
|
||||||
from deluge_client import DelugeRPCClient
|
from deluge_client import DelugeRPCClient, FailedToReconnectException
|
||||||
from sshtunnel import SSHTunnelForwarder
|
from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
|
||||||
from utils import getConfig, BASE_DIR
|
from utils import getConfig, BASE_DIR
|
||||||
|
|
||||||
from torrent import Torrent
|
from torrent import Torrent
|
||||||
@@ -41,7 +41,19 @@ class Deluge(object):
|
|||||||
self.ssh_pkey = config['ssh']['pkey']
|
self.ssh_pkey = config['ssh']['pkey']
|
||||||
self.ssh_password = config['ssh']['password']
|
self.ssh_password = config['ssh']['password']
|
||||||
|
|
||||||
self._connect()
|
try:
|
||||||
|
self._connect()
|
||||||
|
except FailedToReconnectException:
|
||||||
|
logger.error("Unable to connect to deluge, make sure it's running")
|
||||||
|
sys.exit(1)
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
logger.error("Unable to connect to deluge, make sure it's running")
|
||||||
|
sys.exit(1)
|
||||||
|
except BaseException as error:
|
||||||
|
logger.error("Unable to connect to deluge, make sure it's running")
|
||||||
|
if 'nodename nor servname provided' in str(error):
|
||||||
|
sys.exit(1)
|
||||||
|
raise error
|
||||||
|
|
||||||
def freeSpace(self):
|
def freeSpace(self):
|
||||||
return self.client.call('core.get_free_space')
|
return self.client.call('core.get_free_space')
|
||||||
@@ -53,49 +65,68 @@ class Deluge(object):
|
|||||||
torrents.append(Torrent.fromDeluge(torrent))
|
torrents.append(Torrent.fromDeluge(torrent))
|
||||||
return torrents
|
return torrents
|
||||||
|
|
||||||
def _connect(self):
|
def establishSSHTunnel(self):
|
||||||
logger.debug('Checking if script on same server as deluge RPC')
|
logger.debug('Checking if script on same server as deluge RPC')
|
||||||
if self.host != 'localhost' and self.host is not None:
|
|
||||||
try:
|
|
||||||
if self.password:
|
|
||||||
self.tunnel = SSHTunnelForwarder(self.ssh_host, ssh_username=self.ssh_user, ssh_password=self.ssh_password,
|
|
||||||
local_bind_address=('localhost', self.port), remote_bind_address=('localhost', self.port))
|
|
||||||
elif self.pkey is not None:
|
|
||||||
self.tunnel = SSHTunnelForwarder(self.ssh_host, ssh_username=self.ssh_user, ssh_pkey=self.ssh_pkey,
|
|
||||||
local_bind_address=('localhost', self.port), remote_bind_address=('localhost', self.port))
|
|
||||||
except ValueError as error:
|
|
||||||
logger.error("Either password or private key path must be set in config.")
|
|
||||||
raise error
|
|
||||||
|
|
||||||
|
if self.password is not None:
|
||||||
|
self.tunnel = SSHTunnelForwarder(self.ssh_host, ssh_username=self.ssh_user, ssh_password=self.ssh_password,
|
||||||
|
local_bind_address=('localhost', self.port), remote_bind_address=('localhost', self.port))
|
||||||
|
elif self.pkey is not None:
|
||||||
|
self.tunnel = SSHTunnelForwarder(self.ssh_host, ssh_username=self.ssh_user, ssh_pkey=self.ssh_pkey,
|
||||||
|
local_bind_address=('localhost', self.port), remote_bind_address=('localhost', self.port))
|
||||||
|
else:
|
||||||
|
logger.error("Either password or private key path must be set in config.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
self.tunnel.start()
|
self.tunnel.start()
|
||||||
|
except BaseSSHTunnelForwarderError as sshError:
|
||||||
|
logger.warning("SSH host {} online, check your connection".format(self.ssh_host))
|
||||||
|
return
|
||||||
|
|
||||||
|
def _call(self, command, *args):
|
||||||
|
try:
|
||||||
|
return self.client.call(command, *args)
|
||||||
|
except ConnectionRefusedError as error:
|
||||||
|
logger.error("Unable to run command, connection to deluge seems to be offline")
|
||||||
|
except FailedToReconnectException as error:
|
||||||
|
logger.error("Unable to run command, reconnection to deluge failed")
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
if self.host != 'localhost' and self.host is not None:
|
||||||
|
self.establishSSHTunnel()
|
||||||
|
|
||||||
self.client = DelugeRPCClient(self.host, self.port, self.user, self.password)
|
self.client = DelugeRPCClient(self.host, self.port, self.user, self.password)
|
||||||
self.client.connect()
|
self.client.connect()
|
||||||
|
|
||||||
def add(self, url):
|
def add(self, url):
|
||||||
logger.info('Adding magnet with url: {}.'.format(url))
|
|
||||||
response = None
|
response = None
|
||||||
if (url.startswith('magnet')):
|
if (url.startswith('magnet')):
|
||||||
response = self.client.call('core.add_torrent_magnet', url, {})
|
response = self._call('core.add_torrent_magnet', url, {})
|
||||||
elif url.startswith('http'):
|
elif url.startswith('http'):
|
||||||
magnet = self.getMagnetFromFile(url)
|
magnet = self.getMagnetFromFile(url)
|
||||||
response = self.client.call('core.add_torrent_magnet', magnet, {})
|
response = self._call('core.add_torrent_magnet', magnet, {})
|
||||||
|
|
||||||
return responseToString(response)
|
return responseToString(response)
|
||||||
|
|
||||||
def get_all(self, _filter=None):
|
def get_all(self, _filter=None):
|
||||||
|
response = None
|
||||||
if (type(_filter) is list and len(_filter)):
|
if (type(_filter) is list and len(_filter)):
|
||||||
if ('seeding' in _filter):
|
if ('seeding' in _filter):
|
||||||
response = self.client.call('core.get_torrents_status', {'state': 'Seeding'}, [])
|
response = self._call('core.get_torrents_status', {'state': 'Seeding'}, [])
|
||||||
elif ('downloading' in _filter):
|
elif ('downloading' in _filter):
|
||||||
response = self.client.call('core.get_torrents_status', {'state': 'Downloading'}, [])
|
response = self._call('core.get_torrents_status', {'state': 'Downloading'}, [])
|
||||||
elif ('paused' in _filter):
|
elif ('paused' in _filter):
|
||||||
response = self.client.call('core.get_torrents_status', {'paused': 'true'}, [])
|
response = self._call('core.get_torrents_status', {'paused': 'true'}, [])
|
||||||
else:
|
else:
|
||||||
response = self.client.call('core.get_torrents_status', {}, [])
|
response = self.client.call('core.get_torrents_status', {}, [])
|
||||||
|
|
||||||
|
if response == {}:
|
||||||
|
return None
|
||||||
|
|
||||||
return self.parseResponse(response)
|
return self.parseResponse(response)
|
||||||
|
|
||||||
|
|
||||||
def search(self, query):
|
def search(self, query):
|
||||||
allTorrents = self.get_all()
|
allTorrents = self.get_all()
|
||||||
torrentNamesMatchingQuery = []
|
torrentNamesMatchingQuery = []
|
||||||
@@ -112,7 +143,7 @@ class Deluge(object):
|
|||||||
return [ t for t in self.get_all() if (set(q_list) <= set(split_words(t.name))) ]
|
return [ t for t in self.get_all() if (set(q_list) <= set(split_words(t.name))) ]
|
||||||
|
|
||||||
def get(self, id):
|
def get(self, id):
|
||||||
response = self.client.call('core.get_torrent_status', id, {})
|
response = self._call('core.get_torrent_status', id, {})
|
||||||
if response == {}:
|
if response == {}:
|
||||||
logger.warning('No torrent with id: {}'.format(id))
|
logger.warning('No torrent with id: {}'.format(id))
|
||||||
return None
|
return None
|
||||||
@@ -121,10 +152,13 @@ class Deluge(object):
|
|||||||
|
|
||||||
def toggle(self, id):
|
def toggle(self, id):
|
||||||
torrent = self.get(id)
|
torrent = self.get(id)
|
||||||
|
if torrent is None:
|
||||||
|
return
|
||||||
|
|
||||||
if (torrent.paused):
|
if (torrent.paused):
|
||||||
response = self.client.call('core.resume_torrent', [id])
|
response = self._call('core.resume_torrent', [id])
|
||||||
else:
|
else:
|
||||||
response = self.client.call('core.pause_torrent', [id])
|
response = self._call('core.pause_torrent', [id])
|
||||||
|
|
||||||
return responseToString(response)
|
return responseToString(response)
|
||||||
|
|
||||||
@@ -137,7 +171,7 @@ class Deluge(object):
|
|||||||
elif len(matches) == 1:
|
elif len(matches) == 1:
|
||||||
torrent = matches[0]
|
torrent = matches[0]
|
||||||
response = self.remove(torrent.key, destroy)
|
response = self.remove(torrent.key, destroy)
|
||||||
logger.info('Response: {}'.format(str(response)))
|
logger.debug('Response rm: {}'.format(str(response)))
|
||||||
|
|
||||||
if response == False:
|
if response == False:
|
||||||
raise AttributeError('Unable to remove torrent.')
|
raise AttributeError('Unable to remove torrent.')
|
||||||
@@ -146,13 +180,16 @@ class Deluge(object):
|
|||||||
logger.error('ERROR. No torrent found with that name.')
|
logger.error('ERROR. No torrent found with that name.')
|
||||||
|
|
||||||
def remove(self, id, destroy=False):
|
def remove(self, id, destroy=False):
|
||||||
response = self.client.call('core.remove_torrent', id, destroy)
|
try:
|
||||||
logger.info('Response: {}'.format(str(response)))
|
response = self.client.call('core.remove_torrent', id, destroy)
|
||||||
|
logger.debug('Response from remove: {}'.format(str(response)))
|
||||||
|
return responseToString(response)
|
||||||
|
except BaseException as error:
|
||||||
|
if 'torrent_id not in session' in str(error):
|
||||||
|
logger.info('Unable to remove. No torrent with matching id')
|
||||||
|
return None
|
||||||
|
|
||||||
if response == False:
|
raise error
|
||||||
raise AttributeError('Unable to remove torrent.')
|
|
||||||
|
|
||||||
return responseToString(response)
|
|
||||||
|
|
||||||
def filterOnValue(self, torrents, value):
|
def filterOnValue(self, torrents, value):
|
||||||
filteredTorrents = []
|
filteredTorrents = []
|
||||||
@@ -166,7 +203,9 @@ class Deluge(object):
|
|||||||
return filteredTorrents
|
return filteredTorrents
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.client.disconnect()
|
if hasattr(self, 'client') and self.client.connected:
|
||||||
|
logger.debug('Disconnected deluge rpc')
|
||||||
|
self.client.disconnect()
|
||||||
|
|
||||||
if hasattr(self, 'tunnel') and self.tunnel.is_active:
|
if hasattr(self, 'tunnel') and self.tunnel.is_active:
|
||||||
logger.debug('Closing ssh tunnel')
|
logger.debug('Closing ssh tunnel')
|
||||||
|
|||||||
@@ -44,5 +44,5 @@ class Torrent(object):
|
|||||||
return json.dumps(torrentDict)
|
return json.dumps(torrentDict)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{} Progress: {}% ETA: {} State: {} Paused: {}".format(
|
return "{} {} Progress: {}% ETA: {} State: {} Paused: {}".format(
|
||||||
self.name[:59].ljust(60), self.progress.rjust(5), self.eta.rjust(11), self.state.ljust(12), self.paused)
|
self.key, self.name[:59].ljust(60), self.progress.rjust(5), self.eta.rjust(11), self.state.ljust(12), self.paused)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3.10
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# @Author: kevinmidboe
|
# @Author: kevinmidboe
|
||||||
# @Date: 2018-04-17 19:55:38
|
# @Date: 2018-04-17 19:55:38
|
||||||
@@ -60,7 +60,7 @@ class ColorizeFilter(logging.Filter):
|
|||||||
Class for setting specific colors to levels of severity for log output
|
Class for setting specific colors to levels of severity for log output
|
||||||
"""
|
"""
|
||||||
color_by_level = {
|
color_by_level = {
|
||||||
10: 'chartreuse_3b',
|
10: 'cyan',
|
||||||
20: 'white',
|
20: 'white',
|
||||||
30: 'orange_1',
|
30: 'orange_1',
|
||||||
40: 'red'
|
40: 'red'
|
||||||
@@ -79,3 +79,20 @@ def convert(data):
|
|||||||
if isinstance(data, tuple): return map(convert, data)
|
if isinstance(data, tuple): return map(convert, data)
|
||||||
json_data = json.dumps(data)
|
json_data = json.dumps(data)
|
||||||
return json_data
|
return json_data
|
||||||
|
|
||||||
|
def validHash(hash: str):
|
||||||
|
try:
|
||||||
|
return hash and len(hash) == 40 and int(hash, 16)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
def convertFilesize(size_bytes):
|
||||||
|
if size_bytes == None or size_bytes == 0:
|
||||||
|
return "0B"
|
||||||
|
size_name = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
|
||||||
|
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||||
|
p = math.pow(1024, i)
|
||||||
|
s = round(size_bytes / p, 2)
|
||||||
|
return "%s %s" % (s, size_name[i])
|
||||||
|
|||||||
19
publish_version?.sh
Normal file
19
publish_version?.sh
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
PYPI_VERSION=$(pip3 show delugeClient-kevin | awk '$1 ~ /Version:/ { print $2 }')
|
||||||
|
SOURCE_VERSION=$(python3 delugeClient/__version__.py)
|
||||||
|
|
||||||
|
printf "Source version:\t\t %s\n" $SOURCE_VERSION
|
||||||
|
printf "Remote PyPi version:\t %s\n" $PYPI_VERSION
|
||||||
|
|
||||||
|
function version {
|
||||||
|
echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }';
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $(version $SOURCE_VERSION) -gt $(version $PYPI_VERSION) ]; then
|
||||||
|
echo "Soure is newer than remote, publishing!"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Source is same or oldre than remote, nothing to do."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
colored==1.4.2
|
colored==1.4.4
|
||||||
deluge-client==1.9.0
|
deluge-client==1.9.0
|
||||||
docopt==0.6.2
|
requests==2.28.1
|
||||||
requests==2.25.1
|
|
||||||
sshtunnel==0.4.0
|
sshtunnel==0.4.0
|
||||||
websockets==9.1
|
typer==0.7.0
|
||||||
|
|||||||
18
setup.py
18
setup.py
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3.10
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
from sys import path
|
from sys import path
|
||||||
@@ -16,24 +16,24 @@ setup(
|
|||||||
package_data={
|
package_data={
|
||||||
'delugeClient': ['default_config.ini'],
|
'delugeClient': ['default_config.ini'],
|
||||||
},
|
},
|
||||||
python_requires=">=3.6",
|
python_requires=">=3.10",
|
||||||
author="KevinMidboe",
|
author="KevinMidboe",
|
||||||
description="Deluge client with custom functions written in python",
|
description="Deluge client with custom functions written in python",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
url="https://github.com/kevinmidboe/delugeClient",
|
url="https://github.com/kevinmidboe/delugeClient",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'colored',
|
'colored>=1.4.4',
|
||||||
'deluge-client',
|
'deluge-client>=1.9.0',
|
||||||
'requests',
|
'requests>=2.28.1',
|
||||||
'sshtunnel',
|
'sshtunnel>=0.4.0',
|
||||||
'typer',
|
'typer[all]>=0.7.0'
|
||||||
'websockets'
|
|
||||||
],
|
],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python :: 3.6',
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Programming Language :: Python :: 3.10',
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
|
|||||||
Reference in New Issue
Block a user