mirror of
				https://github.com/KevinMidboe/delugeClient.git
				synced 2025-10-29 12:00:13 +00:00 
			
		
		
		
	Compare commits
	
		
			43 Commits
		
	
	
		
			feature/pa
			...
			snyk-fix-7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					405d59e06d | ||
| adf8f3f1ef | |||
| 8f2327c065 | |||
| 9bfa3c4c59 | |||
| 078d422498 | |||
| a6fb543b3f | |||
| 519b51c47c | |||
| 6fd63ff348 | |||
| 74f6f2b06f | |||
| 7ef58745e1 | |||
| b076b1274b | |||
| 97d86253c8 | |||
| d546027df7 | |||
| 2420d9e8c4 | |||
| 2bbf175c2a | |||
| 8597615e68 | |||
| 9f959dd171 | |||
| 869fe579ad | |||
| 724af16f45 | |||
| 201f944fdc | |||
| 9273666fed | |||
| 0cc33c98c1 | |||
| 5ffb97824f | |||
| 365cfd0911 | |||
| 61d1734954 | |||
| c48b4aa68b | |||
| 32cb0e51a7 | |||
| e03247bcc6 | |||
| 1b2620b6f2 | |||
| b7eb06e266 | |||
| c09d35670e | |||
| 39661ea3db | |||
| df8fdbd4fa | |||
| 177d73c516 | |||
| 518b5b0e78 | |||
| 9d34802957 | |||
| e36ba428a7 | |||
| 57b1b4e1f2 | |||
| fada382a32 | |||
| 8d943cb1ad | |||
| bd516f34d8 | |||
| cfe14dd1ba | |||
| 5e39ecb8bc | 
							
								
								
									
										90
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker
 | 
			
		||||
name: Build and test amd64
 | 
			
		||||
 | 
			
		||||
platform:
 | 
			
		||||
  os: linux
 | 
			
		||||
  arch: amd64
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
  - name: Build source
 | 
			
		||||
    image: python:3.10
 | 
			
		||||
    commands:
 | 
			
		||||
    - 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 verify
 | 
			
		||||
    image: python:3.10
 | 
			
		||||
    commands:
 | 
			
		||||
      - make dist
 | 
			
		||||
      - pip3 install twine
 | 
			
		||||
      - twine check dist/*
 | 
			
		||||
 | 
			
		||||
  - name: PyPi test publish
 | 
			
		||||
    image: python:3.10
 | 
			
		||||
    environment:
 | 
			
		||||
      TWINE_USERNAME:
 | 
			
		||||
        from_secret: TWINE_USERNAME
 | 
			
		||||
      TWINE_PASSWORD:
 | 
			
		||||
        from_secret: TWINE_TEST_PASSWORD
 | 
			
		||||
    commands:
 | 
			
		||||
      - make dist
 | 
			
		||||
      - pip3 install twine
 | 
			
		||||
      - twine upload --repository-url https://test.pypi.org/legacy/ dist/*
 | 
			
		||||
 | 
			
		||||
  - name: PyPi publish
 | 
			
		||||
    image: python:3.10
 | 
			
		||||
    environment:
 | 
			
		||||
      TWINE_USERNAME:
 | 
			
		||||
        from_secret: TWINE_USERNAME
 | 
			
		||||
      TWINE_PASSWORD:
 | 
			
		||||
        from_secret: TWINE_PASSWORD
 | 
			
		||||
    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: 60604a21f35e11d078d5d381bbea8e25b903175c018ba9e6f4a4379285e89883
 | 
			
		||||
 | 
			
		||||
...
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,5 @@
 | 
			
		||||
deluge_cli.log
 | 
			
		||||
 | 
			
		||||
# Byte-compiled / optimized / DLL files
 | 
			
		||||
__pycache__/
 | 
			
		||||
*.py[cod]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
.PHONY: clean
 | 
			
		||||
binaries=dist build
 | 
			
		||||
 | 
			
		||||
install:
 | 
			
		||||
	python3 setup.py install
 | 
			
		||||
 | 
			
		||||
build:
 | 
			
		||||
	python3 setup.py build
 | 
			
		||||
 | 
			
		||||
tarball:
 | 
			
		||||
	python3 setup.py sdist
 | 
			
		||||
 | 
			
		||||
wheel:
 | 
			
		||||
	python3 setup.py bdist_wheel
 | 
			
		||||
 | 
			
		||||
dist: tarball wheel
 | 
			
		||||
 | 
			
		||||
upload: clean dist
 | 
			
		||||
	twine upload dist/*
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	rm -rf $(binaries)
 | 
			
		||||
							
								
								
									
										119
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										119
									
								
								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>
 | 
			
		||||
 | 
			
		||||
<p align="center">
 | 
			
		||||
  <a href="https://snyk.io/test/github/kevinmidboe/delugeclient?targetFile=requirements.txt">
 | 
			
		||||
  	<img src="https://snyk.io/test/github/kevinmidboe/delugeclient/badge.svg?targetFile=requirements.txt" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/kevinmidboe/delugeclient?targetFile=requirements.txt" style="max-width:100%;">
 | 
			
		||||
  </a>
 | 
			
		||||
| Tested version | PyPi package | License |
 | 
			
		||||
|:--------|:------|:------|
 | 
			
		||||
| [](https://www.python.org/downloads/release/python-3100/) | [](https://pypi.org/project/delugeClient_kevin/) |[](LICENSE)
 | 
			
		||||
 | 
			
		||||
  <a href="https://opensource.org/licenses/MIT">
 | 
			
		||||
    <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="">
 | 
			
		||||
  </a>
 | 
			
		||||
</p>
 | 
			
		||||
| Drone CI | Known vulnerabilities |
 | 
			
		||||
|:--------|:------|
 | 
			
		||||
| [](https://drone.schleppe.cloud/KevinMidboe/delugeClient) | [](https://snyk.io/test/github/kevinmidboe/delugeClient?targetFile=requirements.txt)
 | 
			
		||||
 | 
			
		||||
<p align="center">
 | 
			
		||||
  <a href="#abstract">Abstract</a> •
 | 
			
		||||
  <a href="#setup_virtualenv">Setup virtualenv</a> •
 | 
			
		||||
  <a href="#configure">Configure</a> •
 | 
			
		||||
  <a href="#installation">Install dependencies</a> •
 | 
			
		||||
  <a href="#install">Install</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>
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
@@ -28,6 +25,51 @@
 | 
			
		||||
## <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. 
 | 
			
		||||
 | 
			
		||||
## <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     │
 | 
			
		||||
│ --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                                │
 | 
			
		||||
╰─────────────────────────────────────────────────────────────────────────╯
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Running from source
 | 
			
		||||
Run from source for fun or during development using module flag:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
python3 -m delugeClient --help
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## <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.
 | 
			
		||||
@@ -41,7 +83,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:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -52,14 +94,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:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 $ 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:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
@@ -96,51 +136,6 @@ Then you need to change the HOST and PORT to reflect the address for your deluge
 | 
			
		||||
 $ 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
 | 
			
		||||
- Fork it!
 | 
			
		||||
- Create your feature branch: git checkout -b my-new-feature
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								config.ini
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								config.ini
									
									
									
									
									
								
							@@ -1,10 +0,0 @@
 | 
			
		||||
[Deluge]
 | 
			
		||||
HOST = YOUR_DELUGE_HOST
 | 
			
		||||
PORT = YOUR_DELUGE_PORT
 | 
			
		||||
USER = YOUR_DELUGE_USER
 | 
			
		||||
PASSWORD = YOUR_DELUGE_PASSWORD
 | 
			
		||||
 | 
			
		||||
[ssh]
 | 
			
		||||
HOST = YOUR_DELUGE_SERVER_IP
 | 
			
		||||
USER = YOUR_SSH_USER
 | 
			
		||||
PKEY = YOUR_SSH_PRIVATE_KEY_DIRECTORY
 | 
			
		||||
							
								
								
									
										24
									
								
								delugeClient/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								delugeClient/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
#!/usr/bin/env python3.10
 | 
			
		||||
# -*- encoding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from sys import path
 | 
			
		||||
from os.path import dirname, join, abspath
 | 
			
		||||
 | 
			
		||||
SCRIPT_DIR = dirname(abspath(__file__))
 | 
			
		||||
path.append(dirname(SCRIPT_DIR))
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from delugeClient.utils import BASE_DIR
 | 
			
		||||
from delugeClient.deluge import Deluge
 | 
			
		||||
 | 
			
		||||
def addHandler(handler):
 | 
			
		||||
  handler.setFormatter(formatter)
 | 
			
		||||
  logger.addHandler(handler)
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger('deluge_cli')
 | 
			
		||||
logger.setLevel(logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
fh = logging.FileHandler(join(BASE_DIR, 'deluge_cli.log'))
 | 
			
		||||
formatter = logging.Formatter('%(asctime)s| %(levelname)s | %(message)s')
 | 
			
		||||
 | 
			
		||||
addHandler(fh)
 | 
			
		||||
							
								
								
									
										176
									
								
								delugeClient/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								delugeClient/__main__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
#!/usr/bin/env python3.10
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import signal
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import typer
 | 
			
		||||
from pprint import pprint
 | 
			
		||||
 | 
			
		||||
from delugeClient.deluge import Deluge
 | 
			
		||||
from delugeClient.utils import ColorizeFilter, BASE_DIR, validHash, convertFilesize
 | 
			
		||||
from delugeClient.__version__ import __version__
 | 
			
		||||
from delugeClient.__init__ import addHandler
 | 
			
		||||
 | 
			
		||||
ch = logging.StreamHandler()
 | 
			
		||||
ch.addFilter(ColorizeFilter())
 | 
			
		||||
addHandler(ch)
 | 
			
		||||
logger = logging.getLogger('deluge_cli')
 | 
			
		||||
 | 
			
		||||
app = typer.Typer()
 | 
			
		||||
deluge = None
 | 
			
		||||
 | 
			
		||||
def signal_handler(signal, frame):
 | 
			
		||||
  """
 | 
			
		||||
  Handle exit by Keyboardinterrupt
 | 
			
		||||
  """
 | 
			
		||||
  global deluge
 | 
			
		||||
  del deluge
 | 
			
		||||
 | 
			
		||||
  logger.info('\nGood bye!')
 | 
			
		||||
  sys.exit(1)
 | 
			
		||||
 | 
			
		||||
def handleKeyboardInterrupt():
 | 
			
		||||
  signal.signal(signal.SIGINT, signal_handler)
 | 
			
		||||
 | 
			
		||||
def printResponse(response, json=False):
 | 
			
		||||
  try:
 | 
			
		||||
    if json:
 | 
			
		||||
      if isinstance(response, list):
 | 
			
		||||
        print('[{}]'.format(','.join([t.toJSON() for t in response])))
 | 
			
		||||
      else:
 | 
			
		||||
        print(response.toJSON())
 | 
			
		||||
 | 
			
		||||
    elif isinstance(response, list):
 | 
			
		||||
      for el in response:
 | 
			
		||||
        print(el)
 | 
			
		||||
 | 
			
		||||
    elif response:
 | 
			
		||||
      print(response)
 | 
			
		||||
 | 
			
		||||
  except KeyError as error:
 | 
			
		||||
    logger.error('Unexpected error while trying to print')
 | 
			
		||||
    raise error
 | 
			
		||||
 | 
			
		||||
@app.command()
 | 
			
		||||
def add(magnet: str, json: bool = typer.Option(False, help="Print as json")):
 | 
			
		||||
  '''
 | 
			
		||||
  Add magnet torrent
 | 
			
		||||
  '''
 | 
			
		||||
  logger.info('Add command selected')
 | 
			
		||||
  logger.debug(magnet)
 | 
			
		||||
  response = deluge.add(magnet)
 | 
			
		||||
  if validHash(response):
 | 
			
		||||
    torrent = deluge.get(response)
 | 
			
		||||
    printResponse(torrent, json)
 | 
			
		||||
  else:
 | 
			
		||||
    logger.info('Unable to add torrent')
 | 
			
		||||
 | 
			
		||||
@app.command()
 | 
			
		||||
def ls(json: bool = typer.Option(False, help="Print as json")):
 | 
			
		||||
  '''
 | 
			
		||||
  List all torrents
 | 
			
		||||
  '''
 | 
			
		||||
  logger.info('List command selected')
 | 
			
		||||
  response = deluge.get_all()
 | 
			
		||||
  if response is None:
 | 
			
		||||
    logger.info('No torrents found')
 | 
			
		||||
    return
 | 
			
		||||
 | 
			
		||||
  printResponse(response, json)
 | 
			
		||||
 | 
			
		||||
@app.command()
 | 
			
		||||
def get(id: str, json: bool = typer.Option(False, help="Print as json")):
 | 
			
		||||
  '''
 | 
			
		||||
  Get torrent by id or hash
 | 
			
		||||
  '''
 | 
			
		||||
  logger.info('Get command selected for id: {}'.format(id))
 | 
			
		||||
  if not validHash(id):
 | 
			
		||||
    return logger.info("Id is not valid")
 | 
			
		||||
  response = deluge.get(id)
 | 
			
		||||
  printResponse(response, json)
 | 
			
		||||
 | 
			
		||||
@app.command()
 | 
			
		||||
def toggle(id: str):
 | 
			
		||||
  '''
 | 
			
		||||
  Toggle torrent download state
 | 
			
		||||
  '''
 | 
			
		||||
  logger.info('Toggle command selected for id: {}'.format(id))
 | 
			
		||||
  if not validHash(id):
 | 
			
		||||
    return logger.info("Id is not valid")
 | 
			
		||||
  deluge.toggle(id)
 | 
			
		||||
  torrent = deluge.get(id)
 | 
			
		||||
  printResponse(torrent)
 | 
			
		||||
 | 
			
		||||
@app.command()
 | 
			
		||||
def search(query: str, json: bool = typer.Option(False, help="Print as json")):
 | 
			
		||||
  '''
 | 
			
		||||
  Search for string segment in torrent name
 | 
			
		||||
  '''
 | 
			
		||||
  logger.info('Search command selected with query: {}'.format(query))
 | 
			
		||||
  response = deluge.search(query)
 | 
			
		||||
  printResponse(response, json)
 | 
			
		||||
 | 
			
		||||
@app.command()
 | 
			
		||||
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
 | 
			
		||||
  '''
 | 
			
		||||
  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)
 | 
			
		||||
 | 
			
		||||
@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()
 | 
			
		||||
def version():
 | 
			
		||||
  '''
 | 
			
		||||
  Print package version
 | 
			
		||||
  '''
 | 
			
		||||
  print(__version__)
 | 
			
		||||
 | 
			
		||||
# Runs before any command
 | 
			
		||||
@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')):
 | 
			
		||||
  ch.setLevel(logging.INFO)
 | 
			
		||||
 | 
			
		||||
  if '--json' in sys.argv:
 | 
			
		||||
    ch.setLevel(logging.CRITICAL)
 | 
			
		||||
  elif error == True:
 | 
			
		||||
    ch.setLevel(logging.ERROR)
 | 
			
		||||
  elif warning == True:
 | 
			
		||||
    ch.setLevel(logging.WARNING)
 | 
			
		||||
  elif info == True:
 | 
			
		||||
    ch.setLevel(logging.INFO)
 | 
			
		||||
  elif debug == True:
 | 
			
		||||
    ch.setLevel(logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
  # Initiate deluge
 | 
			
		||||
  global deluge
 | 
			
		||||
  deluge = Deluge()
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
  app()
 | 
			
		||||
  del deluge
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
  handleKeyboardInterrupt()
 | 
			
		||||
  main()
 | 
			
		||||
							
								
								
									
										4
									
								
								delugeClient/__version__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								delugeClient/__version__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
__version__ = '0.3.5'
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
  print(__version__)
 | 
			
		||||
							
								
								
									
										11
									
								
								delugeClient/default_config.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								delugeClient/default_config.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
[deluge]
 | 
			
		||||
host=
 | 
			
		||||
port=58846
 | 
			
		||||
user=
 | 
			
		||||
password=
 | 
			
		||||
 | 
			
		||||
[ssh]
 | 
			
		||||
host=
 | 
			
		||||
user=
 | 
			
		||||
password=
 | 
			
		||||
pkey=
 | 
			
		||||
							
								
								
									
										220
									
								
								delugeClient/deluge.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								delugeClient/deluge.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,220 @@
 | 
			
		||||
#!/usr/bin/env python3.10
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import requests
 | 
			
		||||
import logging.config
 | 
			
		||||
 | 
			
		||||
from deluge_client import DelugeRPCClient, FailedToReconnectException
 | 
			
		||||
from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
 | 
			
		||||
 | 
			
		||||
from delugeClient.utils import getConfig, BASE_DIR
 | 
			
		||||
from delugeClient.torrent import Torrent
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger('deluge_cli')
 | 
			
		||||
 | 
			
		||||
def split_words(string):
 | 
			
		||||
   logger.debug('Splitting input: {} (type: {}) with split_words'.format(string, type(string)))
 | 
			
		||||
   return re.findall(r"[\w\d']+", string.lower())
 | 
			
		||||
 | 
			
		||||
def responseToString(response=None):
 | 
			
		||||
   try:
 | 
			
		||||
      response = response.decode('utf-8')
 | 
			
		||||
   except (UnicodeDecodeError, AttributeError):
 | 
			
		||||
      pass
 | 
			
		||||
 | 
			
		||||
   return response
 | 
			
		||||
 | 
			
		||||
class Deluge(object):
 | 
			
		||||
   """docstring for ClassName"""
 | 
			
		||||
   def __init__(self):
 | 
			
		||||
      config = getConfig()
 | 
			
		||||
      self.host = config['deluge']['host']
 | 
			
		||||
      self.port = int(config['deluge']['port'])
 | 
			
		||||
      self.user = config['deluge']['user']
 | 
			
		||||
      self.password = config['deluge']['password']
 | 
			
		||||
 | 
			
		||||
      self.ssh_host = config['ssh']['host']
 | 
			
		||||
      self.ssh_user = config['ssh']['user']
 | 
			
		||||
      self.ssh_pkey = config['ssh']['pkey']
 | 
			
		||||
      self.ssh_password = config['ssh']['password']
 | 
			
		||||
 | 
			
		||||
      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):
 | 
			
		||||
      return self.client.call('core.get_free_space')
 | 
			
		||||
 | 
			
		||||
   def parseResponse(self, response):
 | 
			
		||||
      torrents = []
 | 
			
		||||
      for key in response:
 | 
			
		||||
         torrent = response[key]
 | 
			
		||||
         torrents.append(Torrent.fromDeluge(torrent))
 | 
			
		||||
      return torrents
 | 
			
		||||
 | 
			
		||||
   def establishSSHTunnel(self):
 | 
			
		||||
      logger.debug('Checking if script on same server as deluge RPC')
 | 
			
		||||
 | 
			
		||||
      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()
 | 
			
		||||
      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.connect()
 | 
			
		||||
 | 
			
		||||
   def add(self, url):
 | 
			
		||||
      response = None
 | 
			
		||||
      if (url.startswith('magnet')):
 | 
			
		||||
         response = self._call('core.add_torrent_magnet', url, {})
 | 
			
		||||
      elif url.startswith('http'):
 | 
			
		||||
         magnet = self.getMagnetFromFile(url)
 | 
			
		||||
         response = self._call('core.add_torrent_magnet', magnet, {})
 | 
			
		||||
 | 
			
		||||
      return responseToString(response)
 | 
			
		||||
 | 
			
		||||
   def get_all(self, _filter=None):
 | 
			
		||||
      response = None
 | 
			
		||||
      if (type(_filter) is list and len(_filter)):
 | 
			
		||||
         if ('seeding' in _filter):
 | 
			
		||||
            response = self._call('core.get_torrents_status', {'state': 'Seeding'}, [])
 | 
			
		||||
         elif ('downloading' in _filter):
 | 
			
		||||
            response = self._call('core.get_torrents_status', {'state': 'Downloading'}, [])
 | 
			
		||||
         elif ('paused' in _filter):
 | 
			
		||||
            response = self._call('core.get_torrents_status', {'paused': 'true'}, [])
 | 
			
		||||
      else:
 | 
			
		||||
         response = self.client.call('core.get_torrents_status', {}, [])
 | 
			
		||||
 | 
			
		||||
      if response == {}:
 | 
			
		||||
         return None
 | 
			
		||||
 | 
			
		||||
      return self.parseResponse(response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   def search(self, query):
 | 
			
		||||
      allTorrents = self.get_all()
 | 
			
		||||
      torrentNamesMatchingQuery = []
 | 
			
		||||
      if len(allTorrents):
 | 
			
		||||
         for torrent in allTorrents:
 | 
			
		||||
            if query in torrent.name.lower():
 | 
			
		||||
               torrentNamesMatchingQuery.append(torrent)
 | 
			
		||||
 | 
			
		||||
         allTorrents = torrentNamesMatchingQuery
 | 
			
		||||
 | 
			
		||||
      return allTorrents
 | 
			
		||||
 | 
			
		||||
      q_list = split_words(query)
 | 
			
		||||
      return [ t for t in self.get_all() if (set(q_list) <= set(split_words(t.name))) ]
 | 
			
		||||
 | 
			
		||||
   def get(self, id):
 | 
			
		||||
      response = self._call('core.get_torrent_status', id, {})
 | 
			
		||||
      if response == {}:
 | 
			
		||||
         logger.warning('No torrent with id: {}'.format(id))
 | 
			
		||||
         return None
 | 
			
		||||
 | 
			
		||||
      return Torrent.fromDeluge(response)
 | 
			
		||||
 | 
			
		||||
   def toggle(self, id):
 | 
			
		||||
      torrent = self.get(id)
 | 
			
		||||
      if torrent is None:
 | 
			
		||||
         return
 | 
			
		||||
 | 
			
		||||
      if (torrent.paused):
 | 
			
		||||
         response = self._call('core.resume_torrent', [id])
 | 
			
		||||
      else:
 | 
			
		||||
         response = self._call('core.pause_torrent', [id])
 | 
			
		||||
 | 
			
		||||
      return responseToString(response)
 | 
			
		||||
 | 
			
		||||
   def removeByName(self, name, destroy=False):
 | 
			
		||||
      matches = list(filter(lambda t: t.name == name, self.get_all()))
 | 
			
		||||
      logger.info('Matches for {}: {}'.format(name, matches))
 | 
			
		||||
      
 | 
			
		||||
      if len(matches) > 1:
 | 
			
		||||
         raise ValueError('Multiple files found matching key. Unable to remove.')
 | 
			
		||||
      elif len(matches) == 1:
 | 
			
		||||
         torrent = matches[0]
 | 
			
		||||
         response = self.remove(torrent.key, destroy)
 | 
			
		||||
         logger.debug('Response rm: {}'.format(str(response)))
 | 
			
		||||
 | 
			
		||||
         if response == False:
 | 
			
		||||
            raise AttributeError('Unable to remove torrent.')
 | 
			
		||||
         return responseToString(response)
 | 
			
		||||
      else:
 | 
			
		||||
         logger.error('ERROR. No torrent found with that name.')
 | 
			
		||||
 | 
			
		||||
   def remove(self, id, destroy=False):
 | 
			
		||||
      try:
 | 
			
		||||
         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
 | 
			
		||||
 | 
			
		||||
         raise error
 | 
			
		||||
 | 
			
		||||
   def filterOnValue(self, torrents, value):
 | 
			
		||||
      filteredTorrents = []
 | 
			
		||||
      for t in torrents:
 | 
			
		||||
         value_template = {'key': None, 'name': None, value: None}
 | 
			
		||||
         value_template['key'] = t.key
 | 
			
		||||
         value_template['name'] = t.name
 | 
			
		||||
         value_template[value] = getattr(t, value)
 | 
			
		||||
         
 | 
			
		||||
         filteredTorrents.append(value_template)
 | 
			
		||||
      return filteredTorrents
 | 
			
		||||
 | 
			
		||||
   def __del__(self):
 | 
			
		||||
      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:
 | 
			
		||||
         logger.debug('Closing ssh tunnel')
 | 
			
		||||
         self.tunnel.stop(True)
 | 
			
		||||
 | 
			
		||||
   def getMagnetFromFile(self, url):
 | 
			
		||||
      logger.info('File url found, fetching magnet.')
 | 
			
		||||
      r = requests.get(url, allow_redirects=False)
 | 
			
		||||
      magnet = r.headers['Location']
 | 
			
		||||
      logger.info('Found magnet: {}.'.format(magnet))
 | 
			
		||||
      return magnet
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								delugeClient/torrent.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								delugeClient/torrent.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
import json
 | 
			
		||||
import logging
 | 
			
		||||
from distutils.util import strtobool
 | 
			
		||||
 | 
			
		||||
from delugeClient.utils import convert
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger('deluge_cli')
 | 
			
		||||
 | 
			
		||||
class Torrent(object):
 | 
			
		||||
  def __init__(self, key, name, progress, eta, save_path, state, paused, finished, files):
 | 
			
		||||
    super(Torrent, self).__init__()
 | 
			
		||||
    self.key = key
 | 
			
		||||
    self.name = name
 | 
			
		||||
    self.progress = "{0:.2f}".format(float(progress))
 | 
			
		||||
    self.eta = eta
 | 
			
		||||
    self.save_path = save_path
 | 
			
		||||
    self.state = state
 | 
			
		||||
    self.paused = paused
 | 
			
		||||
    self.finished = finished
 | 
			
		||||
    self.files = list(files)
 | 
			
		||||
 | 
			
		||||
  def isFolder(self):
 | 
			
		||||
    return len(self.files) > 1
 | 
			
		||||
 | 
			
		||||
  def toBool(self, value):
 | 
			
		||||
    return True if strtobool(value) else False
 | 
			
		||||
 | 
			
		||||
  @classmethod
 | 
			
		||||
  def fromDeluge(cls, d):
 | 
			
		||||
    # Receive a dict with byte values, convert all elements to string values
 | 
			
		||||
    d = convert(d)
 | 
			
		||||
    toBool = lambda val: True if strtobool(val) else False
 | 
			
		||||
    return cls(d['hash'], d['name'], d['progress'], d['eta'], d['save_path'], d['state'], 
 | 
			
		||||
      toBool(d['paused']), toBool(d['is_finished']), d['files'])
 | 
			
		||||
 | 
			
		||||
  def toJSON(self, files=False):
 | 
			
		||||
    torrentDict = {'key': self.key, 'name': self.name, 'progress': self.progress, 'eta': self.eta,
 | 
			
		||||
      'save_path': self.save_path, 'state': self.state, 'paused': self.paused,
 | 
			
		||||
      'finished': self.finished, 'files': self.files, 'is_folder': self.isFolder()}
 | 
			
		||||
 | 
			
		||||
    if (files is False):
 | 
			
		||||
      del torrentDict['files']
 | 
			
		||||
 | 
			
		||||
    return json.dumps(torrentDict)
 | 
			
		||||
 | 
			
		||||
  def __str__(self):
 | 
			
		||||
    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)
 | 
			
		||||
							
								
								
									
										98
									
								
								delugeClient/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								delugeClient/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
#!/usr/bin/env python3.10
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
# @Author: kevinmidboe
 | 
			
		||||
# @Date:   2018-04-17 19:55:38
 | 
			
		||||
# @Last Modified by:   KevinMidboe
 | 
			
		||||
# @Last Modified time: 2018-05-04 00:04:25
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import json
 | 
			
		||||
import shutil
 | 
			
		||||
import logging
 | 
			
		||||
import colored
 | 
			
		||||
import configparser
 | 
			
		||||
from pprint import pprint
 | 
			
		||||
 | 
			
		||||
from colored import stylize
 | 
			
		||||
 | 
			
		||||
__all__ = ('ColorizeFilter', )
 | 
			
		||||
 | 
			
		||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
logger = logging.getLogger('deluge_cli')
 | 
			
		||||
 | 
			
		||||
def checkConfigExists():
 | 
			
		||||
  user_config_dir = os.path.expanduser("~") + "/.config/delugeClient"
 | 
			
		||||
  config_dir = os.path.join(user_config_dir, 'config.ini')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getConfig():
 | 
			
		||||
  """
 | 
			
		||||
  Read path and get configuartion file with site settings
 | 
			
		||||
  :return: config settings read from 'config.ini'
 | 
			
		||||
  :rtype: configparser.ConfigParser
 | 
			
		||||
  """
 | 
			
		||||
  config = configparser.ConfigParser()
 | 
			
		||||
  user_config_dir = os.path.expanduser("~") + "/.config/delugeClient"
 | 
			
		||||
 | 
			
		||||
  config_dir = os.path.join(user_config_dir, 'config.ini')
 | 
			
		||||
  if not os.path.isfile(config_dir):
 | 
			
		||||
    defaultConfig = os.path.join(BASE_DIR, 'default_config.ini')
 | 
			
		||||
    logger.error('Missing config! Moved default.config.ini to {}.\nOpen this file and set all varaibles!'.format(config_dir))
 | 
			
		||||
    os.makedirs(user_config_dir, exist_ok=True)
 | 
			
		||||
    shutil.copyfile(defaultConfig, config_dir)
 | 
			
		||||
 | 
			
		||||
  config.read(config_dir)
 | 
			
		||||
 | 
			
		||||
  requiredParameters = [('deluge host', config['deluge']['host']), ('deluge port', config['deluge']['port']),
 | 
			
		||||
    ('deluge user', config['deluge']['user']), ('deluge password', config['deluge']['password']),
 | 
			
		||||
    ('ssh password', config['ssh']['user'])]
 | 
			
		||||
  for key, value in requiredParameters:
 | 
			
		||||
    if value == '':
 | 
			
		||||
      logger.error('Missing value for variable: "{}" in config: \
 | 
			
		||||
"{}.'.format(key, user_config_dir))
 | 
			
		||||
      exit(1)
 | 
			
		||||
 | 
			
		||||
  return config
 | 
			
		||||
 | 
			
		||||
class ColorizeFilter(logging.Filter):
 | 
			
		||||
  """
 | 
			
		||||
  Class for setting specific colors to levels of severity for log output
 | 
			
		||||
  """
 | 
			
		||||
  color_by_level = {
 | 
			
		||||
    10: 'cyan',
 | 
			
		||||
    20: 'white',
 | 
			
		||||
    30: 'orange_1',
 | 
			
		||||
    40: 'red'
 | 
			
		||||
  }
 | 
			
		||||
   
 | 
			
		||||
  def filter(self, record):
 | 
			
		||||
    record.raw_msg = record.msg
 | 
			
		||||
    color = self.color_by_level.get(record.levelno)
 | 
			
		||||
    if color:
 | 
			
		||||
      record.msg = stylize(record.msg, colored.fg(color))
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
def convert(data):
 | 
			
		||||
  if isinstance(data, bytes):  return data.decode('utf-8')
 | 
			
		||||
  if isinstance(data, dict):   return dict(map(convert, data.items()))
 | 
			
		||||
  if isinstance(data, tuple):  return map(convert, data)
 | 
			
		||||
  json_data = json.dumps(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])
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										306
									
								
								deluge_cli.py
									
									
									
									
									
								
							
							
						
						
									
										306
									
								
								deluge_cli.py
									
									
									
									
									
								
							@@ -1,306 +0,0 @@
 | 
			
		||||
#!/usr/bin/env python3.6
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
"""Custom delugeRPC client
 | 
			
		||||
Usage:
 | 
			
		||||
   deluge_cli add MAGNET [DIR] [--debug | --warning | --error]
 | 
			
		||||
   deluge_cli search NAME
 | 
			
		||||
   deluge_cli get  TORRENT
 | 
			
		||||
   deluge_cli ls [--downloading | --seeding | --paused]
 | 
			
		||||
   deluge_cli toggle TORRENT
 | 
			
		||||
   deluge_cli progress
 | 
			
		||||
   deluge_cli rm NAME [--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)
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import argparse
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import re
 | 
			
		||||
import signal
 | 
			
		||||
import socket
 | 
			
		||||
import logging
 | 
			
		||||
import logging.config
 | 
			
		||||
import configparser
 | 
			
		||||
 | 
			
		||||
from distutils.util import strtobool
 | 
			
		||||
from pprint import pprint
 | 
			
		||||
 | 
			
		||||
from deluge_client import DelugeRPCClient
 | 
			
		||||
from sshtunnel import SSHTunnelForwarder
 | 
			
		||||
from docopt import docopt
 | 
			
		||||
from .utils import ColorizeFilter, convert
 | 
			
		||||
 | 
			
		||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger('deluge_cli')
 | 
			
		||||
logger.setLevel(logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
fh = logging.FileHandler(os.path.join(BASE_DIR, 'deluge_cli.log'))
 | 
			
		||||
fh.setLevel(logging.DEBUG)
 | 
			
		||||
ch = logging.StreamHandler()
 | 
			
		||||
ch.setLevel(logging.ERROR)
 | 
			
		||||
 | 
			
		||||
formatter = logging.Formatter('%(asctime)s %(levelname)8s %(name)s | %(message)s')
 | 
			
		||||
fh.setFormatter(formatter)
 | 
			
		||||
logger.addHandler(fh)
 | 
			
		||||
logger.addHandler(ch)
 | 
			
		||||
 | 
			
		||||
logger.addFilter(ColorizeFilter())   
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getConfig():
 | 
			
		||||
   """
 | 
			
		||||
   Read path and get configuartion file with site settings
 | 
			
		||||
   :return: config settings read from 'config.ini'
 | 
			
		||||
   :rtype: configparser.ConfigParser
 | 
			
		||||
   """
 | 
			
		||||
   config = configparser.ConfigParser()
 | 
			
		||||
   config_dir = os.path.join(BASE_DIR, 'config.ini')
 | 
			
		||||
   config.read(config_dir)
 | 
			
		||||
 | 
			
		||||
   config_values = list(dict(config.items('Deluge')).values())
 | 
			
		||||
   config_values.extend(list(dict(config.items('ssh')).values()))
 | 
			
		||||
 | 
			
		||||
   if any(value.startswith('YOUR') for value in config_values):
 | 
			
		||||
      raise ValueError('Please set variables in config.ini file.')
 | 
			
		||||
 | 
			
		||||
   return config
 | 
			
		||||
 | 
			
		||||
def split_words(string):
 | 
			
		||||
   logger.debug('Splitting input: {} (type: {}) with split_words'.format(string, type(string)))
 | 
			
		||||
   return re.findall(r"[\w\d']+", string.lower())
 | 
			
		||||
 | 
			
		||||
class Deluge(object):
 | 
			
		||||
   """docstring for ClassName"""
 | 
			
		||||
   def __init__(self):
 | 
			
		||||
      config = getConfig()
 | 
			
		||||
      self.host = config['Deluge']['HOST']
 | 
			
		||||
      self.port = int(config['Deluge']['PORT'])
 | 
			
		||||
      self.user = config['Deluge']['USER']
 | 
			
		||||
      self.password = config['Deluge']['PASSWORD']
 | 
			
		||||
 | 
			
		||||
      self.ssh_host = config['ssh']['HOST']
 | 
			
		||||
      self.ssh_user = config['ssh']['USER']
 | 
			
		||||
      self.ssh_pkey = config['ssh']['PKEY']
 | 
			
		||||
   
 | 
			
		||||
      self._connect()
 | 
			
		||||
 | 
			
		||||
   def parseResponse(self, response):
 | 
			
		||||
      torrents = []
 | 
			
		||||
      for key in response:
 | 
			
		||||
         torrent = response[key]
 | 
			
		||||
         torrents.append(Torrent.fromDeluge(torrent))
 | 
			
		||||
      return torrents
 | 
			
		||||
 | 
			
		||||
   def _connect(self):
 | 
			
		||||
      logger.info('Checking if script on same server as deluge RPC')
 | 
			
		||||
      if (socket.gethostbyname(socket.gethostname()) != self.host):
 | 
			
		||||
         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))
 | 
			
		||||
         self.tunnel.start()
 | 
			
		||||
 | 
			
		||||
      self.client = DelugeRPCClient(self.host, self.port, self.user, self.password)
 | 
			
		||||
      self.client.connect()
 | 
			
		||||
 | 
			
		||||
   def add(self, url):
 | 
			
		||||
      if (url.startswith('magnet')):
 | 
			
		||||
         return self.client.call('core.add_torrent_magnet', url, {})
 | 
			
		||||
 | 
			
		||||
   def get_all(self, _filter=None):
 | 
			
		||||
      if (type(_filter) is list and len(_filter)):
 | 
			
		||||
         if ('seeding' in _filter):
 | 
			
		||||
            response = self.client.call('core.get_torrents_status', {'state': 'Seeding'}, [])
 | 
			
		||||
         elif ('downloading' in _filter):
 | 
			
		||||
            response = self.client.call('core.get_torrents_status', {'state': 'Downloading'}, [])
 | 
			
		||||
         elif ('paused' in _filter):
 | 
			
		||||
            response = self.client.call('core.get_torrents_status', {'paused': 'true'}, [])
 | 
			
		||||
      else:
 | 
			
		||||
         response = self.client.call('core.get_torrents_status', {}, [])
 | 
			
		||||
 | 
			
		||||
      return self.parseResponse(response)
 | 
			
		||||
 | 
			
		||||
   def search(self, query):
 | 
			
		||||
      q_list = split_words(query)
 | 
			
		||||
      return [ t for t in self.get_all() if (set(q_list) <= set(split_words(t.name))) ]
 | 
			
		||||
 | 
			
		||||
   def get(self, id):
 | 
			
		||||
      response = self.client.call('core.get_torrent_status', id, {})
 | 
			
		||||
      return Torrent.fromDeluge(response)
 | 
			
		||||
 | 
			
		||||
   def togglePaused(self, id):
 | 
			
		||||
      torrent = self.get(id)
 | 
			
		||||
      if (torrent.paused):
 | 
			
		||||
         response = self.client.call('core.resume_torrent', [id])
 | 
			
		||||
      else:
 | 
			
		||||
         response = self.client.call('core.pause_torrent', [id])
 | 
			
		||||
      
 | 
			
		||||
      print('Response:', response)
 | 
			
		||||
 | 
			
		||||
   def remove(self, name):
 | 
			
		||||
      matches = list(filter(lambda t: t.name == name, self.get_all()))
 | 
			
		||||
      logger.info('Matches for {}: {}'.format(name, matches))
 | 
			
		||||
      
 | 
			
		||||
      if (len(matches) > 1):
 | 
			
		||||
         raise ValueError('Multiple files found matching key. Unable to remove.')
 | 
			
		||||
      elif (len(matches) == 1):
 | 
			
		||||
         torrent = matches[0]
 | 
			
		||||
         response = self.client.call('core.remove_torrent', torrent.key, False)
 | 
			
		||||
         logger.info('Response: {}'.format(str(response)))
 | 
			
		||||
 | 
			
		||||
         if (response == False):
 | 
			
		||||
            raise AttributeError('Unable to remove torrent.')
 | 
			
		||||
         return response
 | 
			
		||||
      else:
 | 
			
		||||
         logger.error('ERROR. No torrent found with that name.')
 | 
			
		||||
 | 
			
		||||
   def filterOnValue(self, torrents, value):
 | 
			
		||||
      filteredTorrents = []
 | 
			
		||||
      for t in torrents:
 | 
			
		||||
         value_template = {'key': None, 'name': None, value: None}
 | 
			
		||||
         value_template['key'] = t.key
 | 
			
		||||
         value_template['name'] = t.name
 | 
			
		||||
         value_template[value] = getattr(t, value)
 | 
			
		||||
         
 | 
			
		||||
         filteredTorrents.append(value_template)
 | 
			
		||||
      return filteredTorrents
 | 
			
		||||
 | 
			
		||||
   def progress(self):
 | 
			
		||||
      attributes = ['progress', 'eta', 'state', 'finished']
 | 
			
		||||
      all_torrents = self.get_all()
 | 
			
		||||
 | 
			
		||||
      torrents = []
 | 
			
		||||
      for i, attribute in enumerate(attributes):
 | 
			
		||||
         if i < 1:
 | 
			
		||||
            torrents = self.filterOnValue(all_torrents, attribute)
 | 
			
		||||
            continue
 | 
			
		||||
         torrents = [dict(e, **v) for e,v in zip(torrents, self.filterOnValue(all_torrents, attribute))]
 | 
			
		||||
 | 
			
		||||
      return torrents
 | 
			
		||||
 | 
			
		||||
   def __del__(self):
 | 
			
		||||
      if hasattr(self, 'tunnel'):
 | 
			
		||||
         logger.info('Closing ssh tunnel')
 | 
			
		||||
         self.tunnel.stop()
 | 
			
		||||
 | 
			
		||||
class Torrent(object):
 | 
			
		||||
   def __init__(self, key, name, progress, eta, save_path, state, paused, finished, files):
 | 
			
		||||
      super(Torrent, self).__init__()
 | 
			
		||||
      self.key = key
 | 
			
		||||
      self.name = name
 | 
			
		||||
      self.progress = "{0:.2f}".format(float(progress))
 | 
			
		||||
      self.eta = eta
 | 
			
		||||
      self.save_path = save_path
 | 
			
		||||
      self.state = state
 | 
			
		||||
      self.paused = paused
 | 
			
		||||
      self.finished = finished
 | 
			
		||||
      self.files = list(files)
 | 
			
		||||
 | 
			
		||||
   def isFolder(self):
 | 
			
		||||
      return len(self.files) > 1
 | 
			
		||||
 | 
			
		||||
   def toBool(self, value):
 | 
			
		||||
      return True if strtobool(value) else False
 | 
			
		||||
 | 
			
		||||
   @classmethod
 | 
			
		||||
   def fromDeluge(cls, d):
 | 
			
		||||
      # Receive a dict with byte values, convert all elements to string values
 | 
			
		||||
      d = convert(d)
 | 
			
		||||
      toBool = lambda val: True if strtobool(val) else False
 | 
			
		||||
      return cls(d['hash'], d['name'], d['progress'], d['eta'], d['save_path'], d['state'], 
 | 
			
		||||
                 toBool(d['paused']), toBool(d['is_finished']), d['files'])
 | 
			
		||||
 | 
			
		||||
   def toJSON(self):
 | 
			
		||||
      return {'key': self.key, 'name': self.name, 'progress': self.progress, 'eta': self.eta,
 | 
			
		||||
              'save_path': self.save_path, 'state': self.state, 'paused': self.paused,
 | 
			
		||||
              'finished': self.finished, 'files': self.files, 'is_folder': self.isFolder()}
 | 
			
		||||
 | 
			
		||||
   def __str__(self):
 | 
			
		||||
      return "Name: {}, Progress: {}%, ETA: {}, State: {}, Paused: {}".format(
 | 
			
		||||
         self.name, self.progress, self.eta, self.state, self.paused)
 | 
			
		||||
 | 
			
		||||
def signal_handler(signal, frame):
 | 
			
		||||
   """
 | 
			
		||||
   Handle exit by Keyboardinterrupt
 | 
			
		||||
   """
 | 
			
		||||
   logger.info('\nGood bye!')
 | 
			
		||||
   sys.exit(0)
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
   """
 | 
			
		||||
   Main function, parse the input
 | 
			
		||||
   """
 | 
			
		||||
   signal.signal(signal.SIGINT, signal_handler)
 | 
			
		||||
 | 
			
		||||
   arguments = docopt(__doc__, version='1')
 | 
			
		||||
 | 
			
		||||
   # Set logging level for streamHandler
 | 
			
		||||
   if arguments['--debug']:
 | 
			
		||||
      ch.setLevel(logging.DEBUG)
 | 
			
		||||
   elif arguments['--warning']:
 | 
			
		||||
      ch.setLevel(logging.WARNING)
 | 
			
		||||
   elif arguments['--error']:
 | 
			
		||||
      ch.setLevel(logging.ERROR)
 | 
			
		||||
 | 
			
		||||
   logger.info('Deluge client')
 | 
			
		||||
   logger.debug(arguments)
 | 
			
		||||
 | 
			
		||||
   # Get config settings
 | 
			
		||||
   deluge = Deluge()
 | 
			
		||||
 | 
			
		||||
   _id = arguments['TORRENT']
 | 
			
		||||
   query = arguments['NAME']
 | 
			
		||||
   magnet = arguments['MAGNET']
 | 
			
		||||
   name = arguments['NAME']
 | 
			
		||||
   _filter = [ a[2:] for a in ['--downloading', '--seeding', '--paused'] if arguments[a] ]
 | 
			
		||||
   print(_id, query, _filter)
 | 
			
		||||
 | 
			
		||||
   if arguments['add']:
 | 
			
		||||
      logger.info('Add cmd selected with link {}'.format(magnet))
 | 
			
		||||
      response = deluge.add(magnet)
 | 
			
		||||
      print('Add response: ', response)
 | 
			
		||||
 | 
			
		||||
   elif arguments['search']:
 | 
			
		||||
      logger.info('Search cmd selected for query: {}'.format(query))
 | 
			
		||||
      response = deluge.search(query)
 | 
			
		||||
      [ pprint(t.toJSON()) for t in response ]
 | 
			
		||||
 | 
			
		||||
   elif arguments['progress']:
 | 
			
		||||
      logger.info('Progress cmd selected.')
 | 
			
		||||
      pprint(deluge.progress())
 | 
			
		||||
      exit(0)
 | 
			
		||||
      [ pprint(t.toJSON()) for t in deluge.progress() ]
 | 
			
		||||
 | 
			
		||||
   elif arguments['get']:
 | 
			
		||||
      logger.info('Get cmd selected for id: {}'.format(_id))
 | 
			
		||||
      response = deluge.get(_id)
 | 
			
		||||
      pprint(response.toJSON())
 | 
			
		||||
 | 
			
		||||
   elif arguments['ls']:
 | 
			
		||||
      logger.info('List cmd selected')
 | 
			
		||||
      [ pprint(t.toJSON()) for t in deluge.get_all(_filter=_filter) ]
 | 
			
		||||
 | 
			
		||||
   elif arguments['toggle']:
 | 
			
		||||
      logger.info('Toggling id: {}'.format(_id))
 | 
			
		||||
      deluge.togglePaused(_id)
 | 
			
		||||
 | 
			
		||||
   elif arguments['rm']:
 | 
			
		||||
      logger.info('Remove by name: {}'.format(name))
 | 
			
		||||
      deluge.remove(name)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
   main()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										7
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
[build-system]
 | 
			
		||||
requires = [
 | 
			
		||||
    "setuptools>=42",
 | 
			
		||||
    "wheel"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
build-backend = "setuptools.build_meta"
 | 
			
		||||
@@ -1,15 +1,5 @@
 | 
			
		||||
asn1crypto==0.24.0
 | 
			
		||||
bcrypt==3.1.4
 | 
			
		||||
cffi==1.11.5
 | 
			
		||||
colored==1.3.5
 | 
			
		||||
cryptography==2.3
 | 
			
		||||
deluge-client==1.6.0
 | 
			
		||||
docopt==0.6.2
 | 
			
		||||
idna==2.7
 | 
			
		||||
paramiko==2.4.1
 | 
			
		||||
pyasn1==0.4.4
 | 
			
		||||
pycparser==2.18
 | 
			
		||||
PyNaCl==1.2.1
 | 
			
		||||
six==1.11.0
 | 
			
		||||
sshtunnel==0.1.4
 | 
			
		||||
websockets==6.0
 | 
			
		||||
colored==1.4.4
 | 
			
		||||
deluge-client==1.9.0
 | 
			
		||||
requests==2.31.0
 | 
			
		||||
sshtunnel==0.4.0
 | 
			
		||||
typer==0.7.0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								setup.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								setup.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
#!/usr/bin/env python3.10
 | 
			
		||||
# -*- encoding: utf-8 -*-
 | 
			
		||||
from setuptools import setup, find_packages
 | 
			
		||||
from sys import path
 | 
			
		||||
from os.path import dirname
 | 
			
		||||
 | 
			
		||||
with open("README.md", "r", encoding="utf-8") as fh:
 | 
			
		||||
  long_description = fh.read()
 | 
			
		||||
 | 
			
		||||
exec(open('delugeClient/__version__.py').read())
 | 
			
		||||
 | 
			
		||||
setup(
 | 
			
		||||
  name="delugeClient-kevin",
 | 
			
		||||
  version=__version__,
 | 
			
		||||
  packages=find_packages(),
 | 
			
		||||
  package_data={
 | 
			
		||||
    'delugeClient': ['default_config.ini'],
 | 
			
		||||
  },
 | 
			
		||||
  python_requires=">=3.10",
 | 
			
		||||
  author="KevinMidboe",
 | 
			
		||||
  description="Deluge client with custom functions written in python",
 | 
			
		||||
  long_description=long_description,
 | 
			
		||||
  long_description_content_type="text/markdown",
 | 
			
		||||
  url="https://github.com/kevinmidboe/delugeClient",
 | 
			
		||||
  install_requires=[
 | 
			
		||||
    'colored>=1.4.4',
 | 
			
		||||
    'deluge-client>=1.9.0',
 | 
			
		||||
    'requests>=2.28.1',
 | 
			
		||||
    'sshtunnel>=0.4.0',
 | 
			
		||||
    'typer[all]>=0.7.0'
 | 
			
		||||
  ],
 | 
			
		||||
  classifiers=[
 | 
			
		||||
    'Programming Language :: Python',
 | 
			
		||||
    'Operating System :: OS Independent',
 | 
			
		||||
    'License :: OSI Approved :: MIT License',
 | 
			
		||||
    'Programming Language :: Python :: 3.10',
 | 
			
		||||
  ],
 | 
			
		||||
  entry_points={
 | 
			
		||||
    'console_scripts': [
 | 
			
		||||
      'delugeclient = delugeClient.__main__:main',
 | 
			
		||||
   ],
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										42
									
								
								utils.py
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								utils.py
									
									
									
									
									
								
							@@ -1,42 +0,0 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
# @Author: kevinmidboe
 | 
			
		||||
# @Date:   2018-04-17 19:55:38
 | 
			
		||||
# @Last Modified by:   KevinMidboe
 | 
			
		||||
# @Last Modified time: 2018-05-04 00:04:25
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import colored
 | 
			
		||||
import json
 | 
			
		||||
from pprint import pprint
 | 
			
		||||
 | 
			
		||||
from colored import stylize
 | 
			
		||||
 | 
			
		||||
__all__ = ('ColorizeFilter', )
 | 
			
		||||
 | 
			
		||||
class ColorizeFilter(logging.Filter):
 | 
			
		||||
   """
 | 
			
		||||
   Class for setting specific colors to levels of severity for log output
 | 
			
		||||
   """
 | 
			
		||||
   color_by_level = {
 | 
			
		||||
      10: 'chartreuse_3b',
 | 
			
		||||
      20: 'white',
 | 
			
		||||
      30: 'orange_1',
 | 
			
		||||
      40: 'red'
 | 
			
		||||
   }
 | 
			
		||||
   
 | 
			
		||||
   logger = logging.getLogger('deluge_cli')
 | 
			
		||||
 | 
			
		||||
   def filter(self, record):
 | 
			
		||||
      record.raw_msg = record.msg
 | 
			
		||||
      color = self.color_by_level.get(record.levelno)
 | 
			
		||||
      if color:
 | 
			
		||||
         record.msg = stylize(record.msg, colored.fg(color))
 | 
			
		||||
      return True
 | 
			
		||||
 | 
			
		||||
def convert(data):
 | 
			
		||||
    if isinstance(data, bytes):  return data.decode('ascii')
 | 
			
		||||
    if isinstance(data, dict):   return dict(map(convert, data.items()))
 | 
			
		||||
    if isinstance(data, tuple):  return map(convert, data)
 | 
			
		||||
    json_data = json.dumps(data)
 | 
			
		||||
    return json_data
 | 
			
		||||
		Reference in New Issue
	
	Block a user