5 changed files with 495 additions and 131 deletions
@ -1,131 +0,0 @@ |
|||||
from __future__ import with_statement |
|
||||
import os |
|
||||
import traceback |
|
||||
import putio |
|
||||
import shutil |
|
||||
|
|
||||
from couchpotato.api import addApiView |
|
||||
from couchpotato.core.event import addEvent |
|
||||
from couchpotato.core._base.downloader.main import DownloaderBase |
|
||||
from couchpotato.core.helpers.encoding import sp |
|
||||
from couchpotato.core.helpers.variable import getDownloadDir |
|
||||
from couchpotato.core.logger import CPLog |
|
||||
from couchpotato.environment import Env |
|
||||
|
|
||||
log = CPLog(__name__) |
|
||||
|
|
||||
autoload = 'Putiodownload' |
|
||||
|
|
||||
|
|
||||
class Putiodownload(DownloaderBase): |
|
||||
|
|
||||
protocol = ['torrent', 'torrent_magnet'] |
|
||||
status_support = False |
|
||||
|
|
||||
def __init__(self): |
|
||||
addApiView('putiodownload.getfrom', self.getFromPutio, docs = { |
|
||||
'desc': 'Allows you to download file from prom Put.io', |
|
||||
}) |
|
||||
return super(Putiodownload,self).__init__() |
|
||||
|
|
||||
|
|
||||
def download(self, data = None, media = None, filedata = None): |
|
||||
if not media: media = {} |
|
||||
if not data: data = {} |
|
||||
log.info ('Sending "%s" to put.io', data.get('name')) |
|
||||
url = data.get('url') |
|
||||
OAUTH_TOKEN = self.conf('oauth_token') |
|
||||
client = putio.Client(OAUTH_TOKEN) |
|
||||
# Need to constuct a the API url a better way. |
|
||||
callbackurl = None |
|
||||
if self.conf('download'): |
|
||||
callbackurl = 'http://'+self.conf('callback_host')+'/'+self.conf('url_base', section='core')+'/api/'+self.conf('api_key', section='core')+'/putiodownload.getfrom/' |
|
||||
client.Transfer.add_url(url,callback_url=callbackurl) |
|
||||
return True |
|
||||
|
|
||||
def test(self): |
|
||||
OAUTH_TOKEN = self.conf('oauth_token') |
|
||||
try: |
|
||||
client = putio.Client(OAUTH_TOKEN) |
|
||||
if client.File.list(): |
|
||||
return True |
|
||||
except: |
|
||||
log.info('Failed to get file listing, check OAUTH_TOKEN') |
|
||||
return False |
|
||||
|
|
||||
def getFromPutio(self, **kwargs): |
|
||||
log.info('Put.io Download has been called') |
|
||||
OAUTH_TOKEN = self.conf('oauth_token') |
|
||||
client = putio.Client(OAUTH_TOKEN) |
|
||||
files = client.File.list() |
|
||||
delete = self.conf('detele_file') |
|
||||
downloaddir = self.conf('download_dir') |
|
||||
tempdownloaddir = self.conf('tempdownload_dir') |
|
||||
for f in files: |
|
||||
if str(f.id) == str(kwargs.get('file_id')): |
|
||||
# Need to read this in from somewhere |
|
||||
client.File.download(f, dest=tempdownloaddir, delete_after_download=delete) |
|
||||
shutil.move(tempdownloaddir+"/"+str(f.name),downloaddir) |
|
||||
return True |
|
||||
|
|
||||
config = [{ |
|
||||
'name': 'putiodownload', |
|
||||
'groups': [ |
|
||||
{ |
|
||||
'tab': 'downloaders', |
|
||||
'list': 'download_providers', |
|
||||
'name': 'putiodownload', |
|
||||
'label': 'put.io Download', |
|
||||
'description': 'This will start a torrent download on Put.io. <BR>Note: you must have a putio account and API', |
|
||||
'wizard': True, |
|
||||
'options': [ |
|
||||
{ |
|
||||
'name': 'enabled', |
|
||||
'default': 0, |
|
||||
'type': 'enabler', |
|
||||
'radio_group': 'torrent', |
|
||||
}, |
|
||||
{ |
|
||||
'name': 'oauth_token', |
|
||||
'label': 'oauth_token', |
|
||||
'description': 'This is the OAUTH_TOKEN from your putio API', |
|
||||
}, |
|
||||
{ |
|
||||
'name': 'callback_host', |
|
||||
'description': 'This is used to generate the callback url', |
|
||||
}, |
|
||||
{ |
|
||||
'name': 'download', |
|
||||
'description': 'Set this to have CouchPotato download the file from Put.io', |
|
||||
'type': 'bool', |
|
||||
'default': 0, |
|
||||
}, |
|
||||
{ |
|
||||
'name': 'detele_file', |
|
||||
'description': 'Set this to remove the file from putio after sucessful download Note: does nothing if you don\'t select download', |
|
||||
'type': 'bool', |
|
||||
'default': 0, |
|
||||
}, |
|
||||
{ |
|
||||
'name': 'download_dir', |
|
||||
'label': 'Download Directory', |
|
||||
'description': 'The Directory to download files to, does nothing if you don\'t select download', |
|
||||
'default': '/', |
|
||||
}, |
|
||||
{ |
|
||||
'name': 'tempdownload_dir', |
|
||||
'label': 'Temporary Download Directory', |
|
||||
'description': 'The Temporary Directory to download files to, does nothing if you don\'t select download', |
|
||||
'default': '/', |
|
||||
}, |
|
||||
{ |
|
||||
'name': 'manual', |
|
||||
'default': 0, |
|
||||
'type': 'bool', |
|
||||
'advanced': True, |
|
||||
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', |
|
||||
}, |
|
||||
], |
|
||||
} |
|
||||
], |
|
||||
}] |
|
@ -0,0 +1,69 @@ |
|||||
|
from .main import PutIO |
||||
|
|
||||
|
|
||||
|
def autoload(): |
||||
|
return PutIO() |
||||
|
|
||||
|
|
||||
|
config = [{ |
||||
|
'name': 'putio', |
||||
|
'groups': [ |
||||
|
{ |
||||
|
'tab': 'downloaders', |
||||
|
'list': 'download_providers', |
||||
|
'name': 'putio', |
||||
|
'label': 'put.io', |
||||
|
'description': 'This will start a torrent download on <a href="http://put.io">Put.io</a>.', |
||||
|
'wizard': True, |
||||
|
'options': [ |
||||
|
{ |
||||
|
'name': 'enabled', |
||||
|
'default': 0, |
||||
|
'type': 'enabler', |
||||
|
'radio_group': 'torrent', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'oauth_token', |
||||
|
'label': 'oauth_token', |
||||
|
'description': 'This is the OAUTH_TOKEN from your putio API', |
||||
|
'advanced': True, |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'callback_host', |
||||
|
'description': 'This is used to generate the callback url', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'download', |
||||
|
'description': 'Set this to have CouchPotato download the file from Put.io', |
||||
|
'type': 'bool', |
||||
|
'default': 0, |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'delete_file', |
||||
|
'description': 'Set this to remove the file from putio after sucessful download Note: does nothing if you don\'t select download', |
||||
|
'type': 'bool', |
||||
|
'default': 0, |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'download_dir', |
||||
|
'type': 'directory', |
||||
|
'label': 'Download Directory', |
||||
|
'description': 'The Directory to download files to, does nothing if you don\'t select download', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'tempdownload_dir', |
||||
|
'type': 'directory', |
||||
|
'label': 'Temporary Download Directory', |
||||
|
'description': 'The Temporary Directory to download files to, does nothing if you don\'t select download', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'manual', |
||||
|
'default': 0, |
||||
|
'type': 'bool', |
||||
|
'advanced': True, |
||||
|
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', |
||||
|
}, |
||||
|
], |
||||
|
} |
||||
|
], |
||||
|
}] |
@ -0,0 +1,271 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
# Changed |
||||
|
# Removed iso8601 library requirement |
||||
|
# Added CP logging |
||||
|
|
||||
|
import os |
||||
|
import re |
||||
|
import json |
||||
|
import webbrowser |
||||
|
from urllib import urlencode |
||||
|
from couchpotato import CPLog |
||||
|
from dateutil.parser import parse |
||||
|
|
||||
|
import requests |
||||
|
|
||||
|
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' |
||||
|
|
||||
|
log = CPLog(__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) |
||||
|
log.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) |
||||
|
self.Account = type('Account', (_Account,), 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 |
||||
|
log.debug('url: %s', url) |
||||
|
|
||||
|
response = self.session.request( |
||||
|
method, url, params=params, data=data, files=files, |
||||
|
headers=headers, allow_redirects=True, stream=stream) |
||||
|
log.debug('response: %s', response) |
||||
|
if raw: |
||||
|
return response |
||||
|
|
||||
|
log.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 = parse(self.created_at) |
||||
|
except AttributeError: |
||||
|
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)}) |
||||
|
|
||||
|
def move(self, parent_id): |
||||
|
return self.client.request('/files/move', method='POST', |
||||
|
data={'file_ids': str(self.id), 'parent_id': str(parent_id)}) |
||||
|
|
||||
|
def rename(self, name): |
||||
|
return self.client.request('/files/rename', method='POST', |
||||
|
data={'file_id': str(self.id), 'name': str(name)}) |
||||
|
|
||||
|
|
||||
|
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') |
||||
|
|
||||
|
|
||||
|
class _Account(_BaseResource): |
||||
|
|
||||
|
@classmethod |
||||
|
def info(cls): |
||||
|
return cls.client.request('/account/info', method='GET') |
||||
|
|
||||
|
@classmethod |
||||
|
def settings(cls): |
||||
|
return cls.client.request('/account/settings', method='GET') |
@ -0,0 +1,87 @@ |
|||||
|
import shutil |
||||
|
|
||||
|
from couchpotato.api import addApiView |
||||
|
from couchpotato.core._base.downloader.main import DownloaderBase |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
import api as pio |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'Putiodownload' |
||||
|
|
||||
|
|
||||
|
class PutIO(DownloaderBase): |
||||
|
protocol = ['torrent', 'torrent_magnet'] |
||||
|
status_support = False |
||||
|
|
||||
|
def __init__(self): |
||||
|
addApiView('downloader.putio.getfrom', self.getFromPutio, docs = { |
||||
|
'desc': 'Allows you to download file from prom Put.io', |
||||
|
}) |
||||
|
|
||||
|
addApiView('downloader.putio.auth_url', self.getAuthorizationUrl) |
||||
|
|
||||
|
return super(PutIO, self).__init__() |
||||
|
|
||||
|
def download(self, data = None, media = None, filedata = None): |
||||
|
if not media: media = {} |
||||
|
if not data: data = {} |
||||
|
|
||||
|
log.info('Sending "%s" to put.io', data.get('name')) |
||||
|
url = data.get('url') |
||||
|
|
||||
|
client = pio.Client(self.conf('oauth_token')) |
||||
|
|
||||
|
# Need to constuct a the API url a better way. |
||||
|
callbackurl = None |
||||
|
if self.conf('download'): |
||||
|
callbackurl = 'http://' + self.conf('callback_host') + '/' + self.conf('url_base', |
||||
|
section = 'core') + '/api/' + self.conf( |
||||
|
'api_key', section = 'core') + '/downloader.putiodownload.getfrom/' |
||||
|
client.Transfer.add_url(url, callback_url = callbackurl) |
||||
|
|
||||
|
return True |
||||
|
|
||||
|
def test(self): |
||||
|
try: |
||||
|
client = pio.Client(self.conf('oauth_token')) |
||||
|
if client.File.list(): |
||||
|
return True |
||||
|
except: |
||||
|
log.info('Failed to get file listing, check OAUTH_TOKEN') |
||||
|
return False |
||||
|
|
||||
|
def getAuthorizationUrl(self): |
||||
|
# See notification/twitter |
||||
|
pass |
||||
|
|
||||
|
def getCredentials(self): |
||||
|
# Save oauth_token here to settings |
||||
|
pass |
||||
|
|
||||
|
def getAllDownloadStatus(self, ids): |
||||
|
# See other downloaders for examples |
||||
|
|
||||
|
# Check putio for status |
||||
|
|
||||
|
# Check "getFromPutio" progress |
||||
|
pass |
||||
|
|
||||
|
def getFromPutio(self, **kwargs): |
||||
|
|
||||
|
log.info('Put.io Download has been called') |
||||
|
client = pio.Client(self.conf('oauth_token')) |
||||
|
files = client.File.list() |
||||
|
|
||||
|
tempdownloaddir = self.conf('tempdownload_dir') |
||||
|
downloaddir = self.conf('download_dir') |
||||
|
|
||||
|
for f in files: |
||||
|
if str(f.id) == str(kwargs.get('file_id')): |
||||
|
# Need to read this in from somewhere |
||||
|
client.File.download(f, dest = tempdownloaddir, delete_after_download = self.conf('delete_file')) |
||||
|
shutil.move(tempdownloaddir + "/" + str(f.name), downloaddir) |
||||
|
|
||||
|
# Mark status of file_id as "done" here for getAllDownloadStatus |
||||
|
|
||||
|
return True |
@ -0,0 +1,68 @@ |
|||||
|
var PutIODownloader = new Class({ |
||||
|
|
||||
|
initialize: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
App.addEvent('loadSettings', self.addRegisterButton.bind(self)); |
||||
|
}, |
||||
|
|
||||
|
addRegisterButton: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
var setting_page = App.getPage('Settings'); |
||||
|
setting_page.addEvent('create', function(){ |
||||
|
|
||||
|
var fieldset = setting_page.tabs.downloaders.groups.putio, |
||||
|
l = window.location; |
||||
|
|
||||
|
var putio_set = 0; |
||||
|
fieldset.getElements('input[type=text]').each(function(el){ |
||||
|
putio_set += +(el.get('value') != ''); |
||||
|
}); |
||||
|
|
||||
|
new Element('.ctrlHolder').adopt( |
||||
|
|
||||
|
// Unregister button
|
||||
|
(putio_set > 0) ? |
||||
|
[ |
||||
|
self.unregister = new Element('a.button.red', { |
||||
|
'text': 'Unregister "'+fieldset.getElement('input[name*=screen_name]').get('value')+'"', |
||||
|
'events': { |
||||
|
'click': function(){ |
||||
|
fieldset.getElements('input[type=text]').set('value', '').fireEvent('change'); |
||||
|
|
||||
|
self.unregister.destroy(); |
||||
|
self.unregister_or.destroy(); |
||||
|
} |
||||
|
} |
||||
|
}), |
||||
|
self.unregister_or = new Element('span[text=or]') |
||||
|
] |
||||
|
: null, |
||||
|
|
||||
|
// Register button
|
||||
|
new Element('a.button', { |
||||
|
'text': putio_set > 0 ? 'Register a different account' : 'Register your put.io account', |
||||
|
'events': { |
||||
|
'click': function(){ |
||||
|
Api.request('downloader.putio.auth_url', { |
||||
|
'data': { |
||||
|
'host': l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') |
||||
|
}, |
||||
|
'onComplete': function(json){ |
||||
|
window.location = json.url; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
).inject(fieldset.getElement('.test_button'), 'before'); |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
window.addEvent('domready', function(){ |
||||
|
new PutIODownloader(); |
||||
|
}); |
Loading…
Reference in new issue