Skip to content

Instantly share code, notes, and snippets.

@harperreed
Last active January 4, 2022 08:13
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save harperreed/6119285 to your computer and use it in GitHub Desktop.
Save harperreed/6119285 to your computer and use it in GitHub Desktop.
example script to sync/download/etc put.io downloads to your synology download station
CLIENT_ID = ''
CLIENT_SECRET = ''
OAUTH_TOKEN = ''
SYNOLOGY_URL = "http://192.168.0.100:5000/"
SYNOLOGY_USERNAME = ""
SYNOLOGY_PASSWORD = ""
DOWNLOAD_DIR_ID = 000
import time
import requests
import json
class DownloadStationAPI():
def __init__(self, host=None, username=None, password=None):
self.name = 'DownloadStation'
self.username = username
self.password = password
self.host = host
self.url = None
self.response = None
self.auth = None
self.last_time = time.time()
self.session = requests.session()
self.url = self.host + 'webapi/DownloadStation/task.cgi'
self._get_auth()
def _get_auth(self):
auth_url = self.host + 'webapi/auth.cgi?api=SYNO.API.Auth&version=2&method=login&account=' + self.username + '&passwd=' + self.password + '&session=DownloadStation&format=sid'
try:
self.response = self.session.get(auth_url)
self.auth = json.loads(self.response.text)['data']['sid']
except:
return None
return self.auth
def add_uri(self, url):
data = {'api': 'SYNO.DownloadStation.Task',
'version': '1', 'method': 'create',
'session': 'DownloadStation',
'_sid': self.auth,
'uri': url
}
self.response = self.session.post(url=self.url, data=data)
return json.loads(self.response.text)
def get_status(self):
data = {'api': 'SYNO.DownloadStation.Task',
'version': '1', 'method': 'list',
'additional': 'detail,file',
'session': 'DownloadStation',
'_sid': self.auth,
}
self.response = requests.post(url=self.url, data=data)
return json.loads(self.response.text)
# -*- coding: utf-8 -*-
import os
import re
import json
import logging
import webbrowser
from urllib import urlencode
import requests
import iso8601
BASE_URL = 'https://api.put.io/v2'
ACCESS_TOKEN_URL = 'https://api.put.io/v2/oauth2/access_token'
AUTHENTICATION_URL = 'https://api.put.io/v2/oauth2/authenticate'
logger = logging.getLogger(__name__)
class AuthHelper(object):
def __init__(self, client_id, client_secret, redirect_uri, type='code'):
self.client_id = client_id
self.client_secret = client_secret
self.callback_url = redirect_uri
self.type = type
@property
def authentication_url(self):
"""Redirect your users to here to authenticate them."""
params = {
'client_id': self.client_id,
'response_type': self.type,
'redirect_uri': self.callback_url
}
return AUTHENTICATION_URL + "?" + urlencode(params)
def open_authentication_url(self):
webbrowser.open(self.authentication_url)
def get_access_token(self, code):
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'authorization_code',
'redirect_uri': self.callback_url,
'code': code
}
response = requests.get(ACCESS_TOKEN_URL, params=params)
logger.debug(response)
assert response.status_code == 200
return response.json()['access_token']
class Client(object):
def __init__(self, access_token):
self.access_token = access_token
self.session = requests.session()
# Keep resource classes as attributes of client.
# Pass client to resource classes so resource object
# can use the client.
attributes = {'client': self}
self.File = type('File', (_File,), attributes)
self.Transfer = type('Transfer', (_Transfer,), attributes)
def request(self, path, method='GET', params=None, data=None, files=None,
headers=None, raw=False, stream=False):
"""
Wrapper around requests.request()
Prepends BASE_URL to path.
Inserts oauth_token to query params.
Parses response as JSON and returns it.
"""
if not params:
params = {}
if not headers:
headers = {}
# All requests must include oauth_token
params['oauth_token'] = self.access_token
headers['Accept'] = 'application/json'
url = BASE_URL + path
logger.debug('url: %s', url)
response = self.session.request(
method, url, params=params, data=data, files=files,
headers=headers, allow_redirects=True, stream=stream)
logger.debug('response: %s', response)
if raw:
return response
logger.debug('content: %s', response.content)
try:
response = json.loads(response.content)
except ValueError:
raise Exception('Server didn\'t send valid JSON:\n%s\n%s' % (
response, response.content))
if response['status'] == 'ERROR':
raise Exception(response['error_type'])
return response
class _BaseResource(object):
client = None
def __init__(self, resource_dict):
"""Constructs the object from a dict."""
# All resources must have id and name attributes
self.id = None
self.name = None
self.__dict__.update(resource_dict)
try:
self.created_at = iso8601.parse_date(self.created_at)
except (AttributeError, iso8601.ParseError):
self.created_at = None
def __str__(self):
return self.name.encode('utf-8')
def __repr__(self):
# shorten name for display
name = self.name[:17] + '...' if len(self.name) > 20 else self.name
return '<%s id=%r, name="%r">' % (
self.__class__.__name__, self.id, name)
class _File(_BaseResource):
@classmethod
def get(cls, id):
d = cls.client.request('/files/%i' % id, method='GET')
t = d['file']
return cls(t)
@classmethod
def list(cls, parent_id=0):
d = cls.client.request('/files/list', params={'parent_id': parent_id})
files = d['files']
return [cls(f) for f in files]
@classmethod
def upload(cls, path, name=None):
with open(path) as f:
if name:
files = {'file': (name, f)}
else:
files = {'file': f}
d = cls.client.request('/files/upload', method='POST', files=files)
f = d['file']
return cls(f)
def dir(self):
"""List the files under directory."""
return self.list(parent_id=self.id)
def download(self, dest='.', delete_after_download=False):
if self.content_type == 'application/x-directory':
self._download_directory(dest, delete_after_download)
else:
self._download_file(dest, delete_after_download)
def _download_directory(self, dest='.', delete_after_download=False):
name = self.name
if isinstance(name, unicode):
name = name.encode('utf-8', 'replace')
dest = os.path.join(dest, name)
if not os.path.exists(dest):
os.mkdir(dest)
for sub_file in self.dir():
sub_file.download(dest, delete_after_download)
if delete_after_download:
self.delete()
def _download_file(self, dest='.', delete_after_download=False):
response = self.client.request(
'/files/%s/download' % self.id, raw=True, stream=True)
filename = re.match(
'attachment; filename=(.*)',
response.headers['content-disposition']).groups()[0]
# If file name has spaces, it must have quotes around.
filename = filename.strip('"')
with open(os.path.join(dest, filename), 'wb') as f:
for chunk in response.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
f.flush()
if delete_after_download:
self.delete()
def delete(self):
return self.client.request('/files/delete', method='POST',
data={'file_ids': str(self.id)})
class _Transfer(_BaseResource):
@classmethod
def list(cls):
d = cls.client.request('/transfers/list')
transfers = d['transfers']
return [cls(t) for t in transfers]
@classmethod
def get(cls, id):
d = cls.client.request('/transfers/%i' % id, method='GET')
t = d['transfer']
return cls(t)
@classmethod
def add_url(cls, url, parent_id=0, extract=False, callback_url=None):
d = cls.client.request('/transfers/add', method='POST', data=dict(
url=url, parent_id=parent_id, extract=extract,
callback_url=callback_url))
t = d['transfer']
return cls(t)
@classmethod
def add_torrent(cls, path, parent_id=0, extract=False, callback_url=None):
with open(path) as f:
files = {'file': f}
d = cls.client.request('/files/upload', method='POST', files=files,
data=dict(parent_id=parent_id,
extract=extract,
callback_url=callback_url))
t = d['transfer']
return cls(t)
@classmethod
def clean(cls):
return cls.client.request('/transfers/clean', method='POST')
import putio
from DownloadStationAPI import DownloadStationAPI
import config
import logging
# add filemode="w" to overwrite
logging.basicConfig(level=logging.INFO)
logging.info('Put.io <> Synology Download Station Sync')
logging.info('---------------------------------')
client = putio.Client(config.OAUTH_TOKEN)
d = DownloadStationAPI(host=config.SYNOLOGY_URL, username=config.SYNOLOGY_USERNAME, password=config.SYNOLOGY_PASSWORD)
# list files
files = client.File.list(config.DOWNLOAD_DIR_ID)
current_downloads = d.get_status()
logging.info("Currently downloading: " + str(current_downloads['data']['total']))
if not files:
logging.warning("No files to download!")
for f in files:
download_status = True
zip_url = 'https://api.put.io/v2/files/zip?oauth_token=' + config.OAUTH_TOKEN + '&file_ids=' + str(f.id)
for download in current_downloads['data']['tasks']:
if zip_url == download['additional']['detail']['uri']:
logging.info(download['status'] + ": " + download['title'])
download_status = False
if download['status'] == 'finished':
logging.info("Finished downloading: " + download['title'])
logging.info("Deleting from put.io file id: " + str(f.id))
f.delete()
if download_status:
logging.info("Adding file id: " + str(f.id))
d.add_uri(zip_url)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment