mirror of
				https://github.com/KevinMidboe/delugeClient.git
				synced 2025-10-29 12:00:13 +00:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			519b51c47c
			...
			socket
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 499b0116e8 | |||
| e22de16d8c | |||
| bb5da9c455 | |||
| 35b43e1b10 | |||
| 0616d8eeed | |||
| f101d81b43 | |||
| 1e1a270b07 | |||
| 6450041198 | |||
| 50fe8adaf1 | 
							
								
								
									
										14
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -1,14 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
kind: pipeline
 | 
					 | 
				
			||||||
type: docker
 | 
					 | 
				
			||||||
name: delugeClient
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
platform:
 | 
					 | 
				
			||||||
  os: linux
 | 
					 | 
				
			||||||
  arch: amd64
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
steps:
 | 
					 | 
				
			||||||
  - name: Build package
 | 
					 | 
				
			||||||
    image: python:3.10
 | 
					 | 
				
			||||||
    commands:
 | 
					 | 
				
			||||||
      - make build
 | 
					 | 
				
			||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,3 @@
 | 
				
			|||||||
deluge_cli.log
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Byte-compiled / optimized / DLL files
 | 
					# Byte-compiled / optimized / DLL files
 | 
				
			||||||
__pycache__/
 | 
					__pycache__/
 | 
				
			||||||
*.py[cod]
 | 
					*.py[cod]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,17 +0,0 @@
 | 
				
			|||||||
.PHONY: clean
 | 
					 | 
				
			||||||
binaries=dist build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
install:
 | 
					 | 
				
			||||||
	python3 setup.py install
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
build:
 | 
					 | 
				
			||||||
	python3 setup.py build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dist:
 | 
					 | 
				
			||||||
	python3 setup.py sdist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
upload: clean dist
 | 
					 | 
				
			||||||
	twine upload dist/*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
clean:
 | 
					 | 
				
			||||||
	rm -rf $(binaries)
 | 
					 | 
				
			||||||
							
								
								
									
										20
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
									
									
									
									
								
							@@ -4,15 +4,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<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 |
 | 
					<p align="center">
 | 
				
			||||||
|:--------|:------|:------|
 | 
					  <a href="https://snyk.io/test/github/kevinmidboe/delugeclient?targetFile=requirements.txt">
 | 
				
			||||||
| [](https://www.python.org/downloads/release/python-3100/) | [](https://pypi.org/project/delugeClient_kevin/) | [](https://drone.schleppe.cloud/KevinMidboe/delugeClient)
 | 
					  	<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>
 | 
				
			||||||
 | 
					 | 
				
			||||||
| Known vulnerabilities | License |
 | 
					 | 
				
			||||||
|:--------|:------|
 | 
					 | 
				
			||||||
| [](https://snyk.io/test/github/kevinmidboe/delugeClient?targetFile=requirements.txt) |[](LICENSE)
 | 
					 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  <a href="https://opensource.org/licenses/MIT">
 | 
				
			||||||
 | 
					    <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="">
 | 
				
			||||||
 | 
					  </a>
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<p align="center">
 | 
					<p align="center">
 | 
				
			||||||
  <a href="#abstract">Abstract</a> •
 | 
					  <a href="#abstract">Abstract</a> •
 | 
				
			||||||
@@ -52,10 +52,10 @@ 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.10 env
 | 
					 $ virtualenv -p python3.6 env
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 > 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/)
 | 
					 > 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/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
First we navigate to the folder we downloaded.
 | 
					First we navigate to the folder we downloaded.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								config.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								config.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					[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
 | 
				
			||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3.10
 | 
					 | 
				
			||||||
# -*- encoding: utf-8 -*-
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from sys import path
 | 
					 | 
				
			||||||
from os.path import dirname, join
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
path.append(dirname(__file__))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
from utils import BASE_DIR
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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)
 | 
					 | 
				
			||||||
@@ -1,177 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3.10
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import signal
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import typer
 | 
					 | 
				
			||||||
from pprint import pprint
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from deluge import Deluge
 | 
					 | 
				
			||||||
from utils import ColorizeFilter, BASE_DIR, validHash, convertFilesize
 | 
					 | 
				
			||||||
from __version__ import __version__
 | 
					 | 
				
			||||||
from __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()
 | 
					 | 
				
			||||||
@@ -1 +0,0 @@
 | 
				
			|||||||
__version__ = '0.3.1'
 | 
					 | 
				
			||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
[deluge]
 | 
					 | 
				
			||||||
host=
 | 
					 | 
				
			||||||
port=58846
 | 
					 | 
				
			||||||
user=
 | 
					 | 
				
			||||||
password=
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[ssh]
 | 
					 | 
				
			||||||
host=
 | 
					 | 
				
			||||||
user=
 | 
					 | 
				
			||||||
password=
 | 
					 | 
				
			||||||
pkey=
 | 
					 | 
				
			||||||
@@ -1,220 +0,0 @@
 | 
				
			|||||||
#!/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 utils import getConfig, BASE_DIR
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from 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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -1,48 +0,0 @@
 | 
				
			|||||||
import json
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
from distutils.util import strtobool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from 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.key, self.name[:59].ljust(60), self.progress.rjust(5), self.eta.rjust(11), self.state.ljust(12), self.paused)
 | 
					 | 
				
			||||||
@@ -1,98 +0,0 @@
 | 
				
			|||||||
#!/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
									
								
								deluge_cli.log
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								deluge_cli.log
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										319
									
								
								deluge_cli.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										319
									
								
								deluge_cli.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,319 @@
 | 
				
			|||||||
 | 
					#!/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 json
 | 
				
			||||||
 | 
					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])
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      return 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(arg=None):
 | 
				
			||||||
 | 
					   """
 | 
				
			||||||
 | 
					   Main function, parse the input
 | 
				
			||||||
 | 
					   """
 | 
				
			||||||
 | 
					   signal.signal(signal.SIGINT, signal_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   arguments = docopt(__doc__, argv=arg, 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)
 | 
				
			||||||
 | 
					      return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   elif arguments['search']:
 | 
				
			||||||
 | 
					      logger.info('Search cmd selected for query: {}'.format(query))
 | 
				
			||||||
 | 
					      response = deluge.search(query)
 | 
				
			||||||
 | 
					      [ pprint(t.toJSON()) for t in response ]
 | 
				
			||||||
 | 
					      return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   elif arguments['progress']:
 | 
				
			||||||
 | 
					      logger.info('Progress cmd selected.')
 | 
				
			||||||
 | 
					      response = deluge.progress()
 | 
				
			||||||
 | 
					      print(response)
 | 
				
			||||||
 | 
					      # [ pprint(t.toJSON()) for t in response ]
 | 
				
			||||||
 | 
					      return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   elif arguments['get']:
 | 
				
			||||||
 | 
					      logger.info('Get cmd selected for id: {}'.format(_id))
 | 
				
			||||||
 | 
					      response = deluge.get(_id)
 | 
				
			||||||
 | 
					      pprint(response.toJSON())
 | 
				
			||||||
 | 
					      return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   elif arguments['ls']:
 | 
				
			||||||
 | 
					      logger.info('List cmd selected')
 | 
				
			||||||
 | 
					      response = deluge.get_all(_filter=_filter)
 | 
				
			||||||
 | 
					      response = [t.toJSON() for t in response]
 | 
				
			||||||
 | 
					      # pprint(response)
 | 
				
			||||||
 | 
					      return json.dumps(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   elif arguments['toggle']:
 | 
				
			||||||
 | 
					      logger.info('Toggling id: {}'.format(_id))
 | 
				
			||||||
 | 
					      response = deluge.togglePaused(_id)
 | 
				
			||||||
 | 
					      print('toggle response: ', response)
 | 
				
			||||||
 | 
					      return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   elif arguments['rm']:
 | 
				
			||||||
 | 
					      logger.info('Remove by name: {}'.format(name))
 | 
				
			||||||
 | 
					      response = deluge.remove(name)
 | 
				
			||||||
 | 
					      print('rm response: ', response)
 | 
				
			||||||
 | 
					      return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					   main()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2,6 +2,9 @@ import asyncio
 | 
				
			|||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
import random
 | 
					import random
 | 
				
			||||||
import websockets
 | 
					import websockets
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import deluge_cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def hello(websocket, path):
 | 
					async def hello(websocket, path):
 | 
				
			||||||
	name = await websocket.recv()
 | 
						name = await websocket.recv()
 | 
				
			||||||
@@ -19,9 +22,16 @@ async def time(websocket, path):
 | 
				
			|||||||
		await asyncio.sleep(1)
 | 
							await asyncio.sleep(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def deluge(websocket, path):
 | 
				
			||||||
 | 
						while True:
 | 
				
			||||||
 | 
							downloading = deluge_cli.main(['progress'])
 | 
				
			||||||
 | 
							await websocket.send(json.dumps(downloading))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							await asyncio.sleep(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
serve_hello = websockets.serve(hello, '0.0.0.0', 8765)
 | 
					serve_hello = websockets.serve(hello, '0.0.0.0', 8765)
 | 
				
			||||||
serve_time = websockets.serve(time, '0.0.0.0', 5678)
 | 
					serve_deluge = websockets.serve(deluge, '0.0.0.0', 5678)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
asyncio.get_event_loop().run_until_complete(serve_hello)
 | 
					asyncio.get_event_loop().run_until_complete(serve_hello)
 | 
				
			||||||
asyncio.get_event_loop().run_until_complete(serve_time)
 | 
					asyncio.get_event_loop().run_until_complete(serve_deluge)
 | 
				
			||||||
asyncio.get_event_loop().run_forever()
 | 
					asyncio.get_event_loop().run_forever()
 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
[build-system]
 | 
					 | 
				
			||||||
requires = [
 | 
					 | 
				
			||||||
    "setuptools>=42",
 | 
					 | 
				
			||||||
    "wheel"
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
build-backend = "setuptools.build_meta"
 | 
					 | 
				
			||||||
@@ -1,5 +1,15 @@
 | 
				
			|||||||
colored==1.4.4
 | 
					asn1crypto==0.24.0
 | 
				
			||||||
deluge-client==1.9.0
 | 
					bcrypt==3.1.4
 | 
				
			||||||
requests==2.28.1
 | 
					cffi==1.11.5
 | 
				
			||||||
sshtunnel==0.4.0
 | 
					colored==1.3.5
 | 
				
			||||||
typer==0.7.0
 | 
					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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										42
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								setup.py
									
									
									
									
									
								
							@@ -1,42 +0,0 @@
 | 
				
			|||||||
#!/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',
 | 
					 | 
				
			||||||
    'Programming Language :: Python :: 3.10',
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  entry_points={
 | 
					 | 
				
			||||||
    'console_scripts': [
 | 
					 | 
				
			||||||
      'delugeclient = delugeClient.__main__:main',
 | 
					 | 
				
			||||||
   ],
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
							
								
								
									
										42
									
								
								utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					#!/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