Skip to content

Instantly share code, notes, and snippets.

@harperreed
Last active September 10, 2024 20:07
Show Gist options
  • Select an option

  • Save harperreed/6119285 to your computer and use it in GitHub Desktop.

Select an option

Save harperreed/6119285 to your computer and use it in GitHub Desktop.

Revisions

  1. harperreed revised this gist Jul 31, 2013. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion config.py.example
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,3 @@

    CLIENT_ID = ''
    CLIENT_SECRET = ''
    OAUTH_TOKEN = ''
  2. harperreed revised this gist Jul 31, 2013. 5 changed files with 350 additions and 1 deletion.
    57 changes: 57 additions & 0 deletions DownloadStationAPI.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,57 @@
    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)
    10 changes: 10 additions & 0 deletions config.py.example
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@

    CLIENT_ID = ''
    CLIENT_SECRET = ''
    OAUTH_TOKEN = ''

    SYNOLOGY_URL = "http://192.168.0.100:5000/"
    SYNOLOGY_USERNAME = ""
    SYNOLOGY_PASSWORD = ""

    DOWNLOAD_DIR_ID = 000
    1 change: 0 additions & 1 deletion gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -1 +0,0 @@
    test
    246 changes: 246 additions & 0 deletions putio.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,246 @@
    # -*- 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')
    37 changes: 37 additions & 0 deletions putio_SDLS.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,37 @@
    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)
  3. harperreed created this gist Jul 31, 2013.
    1 change: 1 addition & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    test