Compare commits
210 Commits
build/2.6.
...
redesign
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4ed4791bf | ||
|
|
adb744a526 | ||
|
|
0f82cda811 | ||
|
|
0d6c3c8ecb | ||
|
|
6598f53fd4 | ||
|
|
6b8458d87f | ||
|
|
99a0621238 | ||
|
|
c52666309a | ||
|
|
84a458d40b | ||
|
|
f8631c6d53 | ||
|
|
b19b0775c7 | ||
|
|
2dc1c1dd38 | ||
|
|
7db8b233c8 | ||
|
|
427c77a9ef | ||
|
|
94c3969f10 | ||
|
|
debd1855dd | ||
|
|
9f77597c11 | ||
|
|
afc9039625 | ||
|
|
920d3cb44e | ||
|
|
b1fc8ad862 | ||
|
|
11b9bc39ab | ||
|
|
6dcb3f3bf2 | ||
|
|
ce768f45c5 | ||
|
|
9b91d1d6c0 | ||
|
|
d9c7a97604 | ||
|
|
0fd01aa697 | ||
|
|
58615e6f9b | ||
|
|
2277322e57 | ||
|
|
18020e609e | ||
|
|
6a31b920ac | ||
|
|
c1266a36e4 | ||
|
|
578effc538 | ||
|
|
d881120013 | ||
|
|
da5318033a | ||
|
|
31df5bce01 | ||
|
|
d5622b7cba | ||
|
|
26ad1b354f | ||
|
|
7a616a81f7 | ||
|
|
275aefc3cc | ||
|
|
2b32490f72 | ||
|
|
7b9043c16b | ||
|
|
cf83f99be0 | ||
|
|
fb8a66d207 | ||
|
|
e8a3645bc6 | ||
|
|
592e40993c | ||
|
|
b00e69e222 | ||
|
|
c9b4c8167f | ||
|
|
cdb9cfe756 | ||
|
|
e52f50b204 | ||
|
|
770c2be14c | ||
|
|
ab61961a64 | ||
|
|
6aca799bbb | ||
|
|
89836be1d1 | ||
|
|
20e1283627 | ||
|
|
ee8406e026 | ||
|
|
514941b785 | ||
|
|
1510e37652 | ||
|
|
e1e39cd3f4 | ||
|
|
e1bb8c5419 | ||
|
|
17fa33a496 | ||
|
|
601f0b54cf | ||
|
|
51d44bfc3e | ||
|
|
12148217a2 | ||
|
|
132fa12ef4 | ||
|
|
1827c2e4cd | ||
|
|
f423bca06b | ||
|
|
e7b089edf5 | ||
|
|
b8b7d94a6a | ||
|
|
2c080fec3d | ||
|
|
4c68566c77 | ||
|
|
a3af784c18 | ||
|
|
ac6f295c93 | ||
|
|
2c72cd7d9f | ||
|
|
d012dc5c85 | ||
|
|
038b4c63ee | ||
|
|
17e37996c4 | ||
|
|
9318e19347 | ||
|
|
045c8f4dc8 | ||
|
|
02e25a9e25 | ||
|
|
819f619297 | ||
|
|
c303789817 | ||
|
|
8f4e03d04b | ||
|
|
229d67c086 | ||
|
|
d84897ff33 | ||
|
|
387a711538 | ||
|
|
7a1b914824 | ||
|
|
5e62801666 | ||
|
|
00d887153f | ||
|
|
1a2d79f719 | ||
|
|
6d5882001a | ||
|
|
4a6b45c65c | ||
|
|
b0d1fe5c33 | ||
|
|
a6e49098c8 | ||
|
|
ffcd36cbf4 | ||
|
|
3bf2d844a0 | ||
|
|
dd24eb8893 | ||
|
|
ac382d5131 | ||
|
|
abc9e78027 | ||
|
|
538f51dd5b | ||
|
|
c94d79cc6c | ||
|
|
9883a7a85a | ||
|
|
eea9f40501 | ||
|
|
576bcb9f4b | ||
|
|
f4a486c47b | ||
|
|
80cf144e8b | ||
|
|
cf5a774313 | ||
|
|
b9b77042dc | ||
|
|
9e96aa14b7 | ||
|
|
6a0220b496 | ||
|
|
02ff0acc64 | ||
|
|
ae6affdb52 | ||
|
|
a08df704be | ||
|
|
af9a47d528 | ||
|
|
62c5365329 | ||
|
|
ddf575a86e | ||
|
|
0155c8de2d | ||
|
|
6b9383ce92 | ||
|
|
cb8d24ef1f | ||
|
|
5bfdb121df | ||
|
|
814ddfb79f | ||
|
|
766f819c0b | ||
|
|
b8b6024592 | ||
|
|
d77cfb3e69 | ||
|
|
858d8b4291 | ||
|
|
3852fc720d | ||
|
|
5145618c39 | ||
|
|
d6cfcae45b | ||
|
|
5609536f46 | ||
|
|
f992c00eb7 | ||
|
|
87086a0336 | ||
|
|
62cb57f217 | ||
|
|
2a0e46fe00 | ||
|
|
1f7555e8fd | ||
|
|
ff43df9ef1 | ||
|
|
2e907e93e7 | ||
|
|
4d329d6a36 | ||
|
|
752191bc23 | ||
|
|
1d73fd9d7e | ||
|
|
79688c412a | ||
|
|
fc1c95fefb | ||
|
|
6a174716af | ||
|
|
defe256f1b | ||
|
|
8a5f154d9e | ||
|
|
fe56a69e8f | ||
|
|
c6d326f973 | ||
|
|
9e5f670feb | ||
|
|
9ebacf8816 | ||
|
|
df2d7ec9c2 | ||
|
|
ddab74582b | ||
|
|
2801079bc8 | ||
|
|
1deb49b524 | ||
|
|
49d550f652 | ||
|
|
1a43ce6ecc | ||
|
|
15a0131587 | ||
|
|
0dca34958c | ||
|
|
4b231e36ea | ||
|
|
52478a00db | ||
|
|
e177766270 | ||
|
|
ff8da7c8f8 | ||
|
|
89c8c5a0c7 | ||
|
|
38c6266f9c | ||
|
|
16f8e7e123 | ||
|
|
7110c7a11f | ||
|
|
6d79f316a6 | ||
|
|
c1b6811b8a | ||
|
|
7d7b76b2e9 | ||
|
|
657aa52fa7 | ||
|
|
8e9ef8db39 | ||
|
|
92a0096b54 | ||
|
|
87338760ad | ||
|
|
28019b0a09 | ||
|
|
248b007f4a | ||
|
|
9e31c59de8 | ||
|
|
269e785888 | ||
|
|
3669aef42d | ||
|
|
1087eb3a06 | ||
|
|
43af80a137 | ||
|
|
0766a27a71 | ||
|
|
61f634a21e | ||
|
|
d626fda710 | ||
|
|
b40d1f3463 | ||
|
|
1030d0d748 | ||
|
|
2e52c8124a | ||
|
|
8de5fcdac6 | ||
|
|
4aa9801be4 | ||
|
|
3e58378490 | ||
|
|
2c40db3074 | ||
|
|
fba228fd9d | ||
|
|
ef2b8e88b4 | ||
|
|
c77b270fa8 | ||
|
|
872a4f4650 | ||
|
|
d0f1e7c6a3 | ||
|
|
53e7e383a3 | ||
|
|
c06e1f3135 | ||
|
|
bb73cb8eec | ||
|
|
5acab98025 | ||
|
|
ed6a46e9c0 | ||
|
|
4291e2233d | ||
|
|
6ccbad031f | ||
|
|
d1dfed2833 | ||
|
|
3986de4ebc | ||
|
|
d80fe99609 | ||
|
|
43b6e3ac07 | ||
|
|
58acd53a9a | ||
|
|
05a97a19ab | ||
|
|
db23f5cdef | ||
|
|
85163443e3 | ||
|
|
6ea49405f4 | ||
|
|
4776cef473 | ||
|
|
e8fe9da602 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@
|
||||
/_source/
|
||||
.project
|
||||
.pydevproject
|
||||
node_modules
|
||||
.tmp
|
||||
121
Gruntfile.js
Normal file
121
Gruntfile.js
Normal file
@@ -0,0 +1,121 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(grunt){
|
||||
|
||||
require('time-grunt')(grunt);
|
||||
|
||||
// Configurable paths
|
||||
var config = {
|
||||
tmp: '.tmp',
|
||||
base: 'couchpotato',
|
||||
css_dest: 'couchpotato/static/style/combined.min.css'
|
||||
};
|
||||
|
||||
grunt.initConfig({
|
||||
|
||||
// Project settings
|
||||
config: config,
|
||||
|
||||
// Make sure code styles are up to par and there are no obvious mistakes
|
||||
jshint: {
|
||||
options: {
|
||||
reporter: require('jshint-stylish'),
|
||||
unused: false,
|
||||
camelcase: false,
|
||||
devel: true
|
||||
},
|
||||
all: [
|
||||
'<%= config.base %>/{,**/}*.js',
|
||||
'!<%= config.base %>/static/scripts/vendor/{,**/}*.js'
|
||||
]
|
||||
},
|
||||
|
||||
// Compiles Sass to CSS and generates necessary files if requested
|
||||
sass: {
|
||||
options: {
|
||||
compass: true,
|
||||
update: true
|
||||
},
|
||||
server: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= config.base %>/',
|
||||
src: ['**/*.scss'],
|
||||
dest: '<%= config.tmp %>/styles/',
|
||||
ext: '.css'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Add vendor prefixed styles
|
||||
autoprefixer: {
|
||||
options: {
|
||||
browsers: ['> 1%', 'Android >= 2.1', 'Chrome >= 21', 'Explorer >= 7', 'Firefox >= 17', 'Opera >= 12.1', 'Safari >= 6.0']
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= config.tmp %>/styles/',
|
||||
src: '{,**/}*.css',
|
||||
dest: '<%= config.tmp %>/styles/'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
cssmin: {
|
||||
dist: {
|
||||
files: {
|
||||
'<%= config.css_dest %>': ['<%= config.tmp %>/styles/**/*.css']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
shell: {
|
||||
runCouchPotato: {
|
||||
command: 'python CouchPotato.py'
|
||||
}
|
||||
},
|
||||
|
||||
// COOL TASKS ==============================================================
|
||||
watch: {
|
||||
scss: {
|
||||
files: ['<%= config.base %>/**/*.{scss,sass}'],
|
||||
tasks: ['sass:server', 'autoprefixer', 'cssmin']
|
||||
},
|
||||
js: {
|
||||
files: [
|
||||
'<%= config.base %>/**/*.js'
|
||||
],
|
||||
tasks: ['jshint']
|
||||
},
|
||||
livereload: {
|
||||
options: {
|
||||
livereload: 35729
|
||||
},
|
||||
files: [
|
||||
'<%= config.css_dest %>'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
concurrent: {
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
},
|
||||
tasks: ['shell:runCouchPotato', 'sass:server', 'autoprefixer', 'cssmin', 'watch']
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
//grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-sass');
|
||||
grunt.loadNpmTasks('grunt-contrib-cssmin');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-autoprefixer');
|
||||
grunt.loadNpmTasks('grunt-concurrent');
|
||||
grunt.loadNpmTasks('grunt-shell');
|
||||
|
||||
grunt.registerTask('default', ['concurrent']);
|
||||
|
||||
};
|
||||
45
config.rb
Normal file
45
config.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
# First, require any additional compass plugins installed on your system.
|
||||
# require 'zen-grids'
|
||||
require 'susy'
|
||||
# require 'breakpoint'
|
||||
|
||||
|
||||
# Toggle this between :development and :production when deploying the CSS to the
|
||||
# live server. Development mode will retain comments and spacing from the
|
||||
# original Sass source and adds line numbering comments for easier debugging.
|
||||
environment = :development
|
||||
# environment = :development
|
||||
|
||||
# In development, we can turn on the FireSass-compatible debug_info.
|
||||
firesass = false
|
||||
# firesass = true
|
||||
|
||||
|
||||
# Location of the your project's resources.
|
||||
|
||||
|
||||
# Set this to the root of your project. All resource locations above are
|
||||
# considered to be relative to this path.
|
||||
http_path = "/"
|
||||
|
||||
# To use relative paths to assets in your compiled CSS files, set this to true.
|
||||
# relative_assets = true
|
||||
|
||||
|
||||
##
|
||||
## You probably don't need to edit anything below this.
|
||||
##
|
||||
|
||||
sass_dir = "./"
|
||||
css_dir = "./static/style_compiled"
|
||||
|
||||
# You can select your preferred output style here (can be overridden via the command line):
|
||||
# output_style = :expanded or :nested or :compact or :compressed
|
||||
output_style = (environment == :development) ? :expanded : :compressed
|
||||
|
||||
# To disable debugging comments that display the original location of your selectors. Uncomment:
|
||||
# line_comments = false
|
||||
|
||||
# Pass options to sass. For development, we turn on the FireSass-compatible
|
||||
# debug_info if the firesass config variable above is true.
|
||||
sass_options = (environment == :development && firesass == true) ? {:debug_info => true} : {}
|
||||
@@ -40,6 +40,8 @@ class WebHandler(BaseHandler):
|
||||
return
|
||||
|
||||
try:
|
||||
if route == 'robots.txt':
|
||||
self.set_header('Content-Type', 'text/plain')
|
||||
self.write(views[route]())
|
||||
except:
|
||||
log.error("Failed doing web request '%s': %s", (route, traceback.format_exc()))
|
||||
@@ -60,6 +62,13 @@ def index():
|
||||
addView('', index)
|
||||
|
||||
|
||||
# Web view
|
||||
def robots():
|
||||
return 'User-agent: * \n' \
|
||||
'Disallow: /'
|
||||
addView('robots.txt', robots)
|
||||
|
||||
|
||||
# API docs
|
||||
def apiDocs():
|
||||
routes = list(api.keys())
|
||||
|
||||
@@ -7,6 +7,7 @@ import urllib
|
||||
|
||||
from couchpotato.core.helpers.request import getParams
|
||||
from couchpotato.core.logger import CPLog
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import RequestHandler, asynchronous
|
||||
|
||||
|
||||
@@ -50,24 +51,22 @@ class NonBlockHandler(RequestHandler):
|
||||
start, stop = api_nonblock[route]
|
||||
self.stopper = stop
|
||||
|
||||
start(self.onNewMessage, last_id = self.get_argument('last_id', None))
|
||||
start(self.sendData, last_id = self.get_argument('last_id', None))
|
||||
|
||||
def onNewMessage(self, response):
|
||||
if self.request.connection.stream.closed():
|
||||
self.on_connection_close()
|
||||
return
|
||||
def sendData(self, response):
|
||||
if not self.request.connection.stream.closed():
|
||||
try:
|
||||
self.finish(response)
|
||||
except:
|
||||
log.debug('Failed doing nonblock request, probably already closed: %s', (traceback.format_exc()))
|
||||
try: self.finish({'success': False, 'error': 'Failed returning results'})
|
||||
except: pass
|
||||
|
||||
try:
|
||||
self.finish(response)
|
||||
except:
|
||||
log.debug('Failed doing nonblock request, probably already closed: %s', (traceback.format_exc()))
|
||||
try: self.finish({'success': False, 'error': 'Failed returning results'})
|
||||
except: pass
|
||||
|
||||
def on_connection_close(self):
|
||||
self.removeStopper()
|
||||
|
||||
def removeStopper(self):
|
||||
if self.stopper:
|
||||
self.stopper(self.onNewMessage)
|
||||
self.stopper(self.sendData)
|
||||
|
||||
self.stopper = None
|
||||
|
||||
@@ -83,10 +82,11 @@ def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
|
||||
|
||||
# Blocking API handler
|
||||
class ApiHandler(RequestHandler):
|
||||
route = None
|
||||
|
||||
@asynchronous
|
||||
def get(self, route, *args, **kwargs):
|
||||
route = route.strip('/')
|
||||
self.route = route = route.strip('/')
|
||||
if not api.get(route):
|
||||
self.write('API call doesn\'t seem to exist')
|
||||
self.finish()
|
||||
@@ -123,11 +123,15 @@ class ApiHandler(RequestHandler):
|
||||
except:
|
||||
log.error('Failed write error "%s": %s', (route, traceback.format_exc()))
|
||||
|
||||
api_locks[route].release()
|
||||
self.unlock()
|
||||
|
||||
post = get
|
||||
|
||||
def taskFinished(self, result, route):
|
||||
IOLoop.current().add_callback(self.sendData, result, route)
|
||||
self.unlock()
|
||||
|
||||
def sendData(self, result, route):
|
||||
|
||||
if not self.request.connection.stream.closed():
|
||||
try:
|
||||
@@ -135,14 +139,12 @@ class ApiHandler(RequestHandler):
|
||||
jsonp_callback = self.get_argument('callback_func', default = None)
|
||||
|
||||
if jsonp_callback:
|
||||
self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
|
||||
self.set_header("Content-Type", "text/javascript")
|
||||
self.finish()
|
||||
self.set_header('Content-Type', 'text/javascript')
|
||||
self.finish(str(jsonp_callback) + '(' + json.dumps(result) + ')')
|
||||
elif isinstance(result, tuple) and result[0] == 'redirect':
|
||||
self.redirect(result[1])
|
||||
else:
|
||||
self.write(result)
|
||||
self.finish()
|
||||
self.finish(result)
|
||||
except UnicodeDecodeError:
|
||||
log.error('Failed proper encode: %s', traceback.format_exc())
|
||||
except:
|
||||
@@ -150,7 +152,9 @@ class ApiHandler(RequestHandler):
|
||||
try: self.finish({'success': False, 'error': 'Failed returning results'})
|
||||
except: pass
|
||||
|
||||
api_locks[route].release()
|
||||
def unlock(self):
|
||||
try: api_locks[self.route].release()
|
||||
except: pass
|
||||
|
||||
|
||||
def addApiView(route, func, static = False, docs = None, **kwargs):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import ss
|
||||
@@ -8,8 +7,6 @@ from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
from minify.cssmin import cssmin
|
||||
from minify.jsmin import jsmin
|
||||
from tornado.web import StaticFileHandler
|
||||
|
||||
|
||||
@@ -22,30 +19,26 @@ class ClientScript(Plugin):
|
||||
|
||||
core_static = {
|
||||
'style': [
|
||||
'style/main.css',
|
||||
'style/uniform.generic.css',
|
||||
'style/uniform.css',
|
||||
'style/settings.css',
|
||||
'style/combined.min.css',
|
||||
],
|
||||
'script': [
|
||||
'scripts/library/mootools.js',
|
||||
'scripts/library/mootools_more.js',
|
||||
'scripts/vendor/mootools.js',
|
||||
'scripts/vendor/mootools_more.js',
|
||||
'scripts/vendor/form_replacement/form_check.js',
|
||||
'scripts/vendor/form_replacement/form_radio.js',
|
||||
'scripts/vendor/form_replacement/form_dropdown.js',
|
||||
'scripts/vendor/form_replacement/form_selectoption.js',
|
||||
'scripts/vendor/Array.stableSort.js',
|
||||
'scripts/vendor/history.js',
|
||||
'scripts/library/uniform.js',
|
||||
'scripts/library/form_replacement/form_check.js',
|
||||
'scripts/library/form_replacement/form_radio.js',
|
||||
'scripts/library/form_replacement/form_dropdown.js',
|
||||
'scripts/library/form_replacement/form_selectoption.js',
|
||||
'scripts/library/question.js',
|
||||
'scripts/library/scrollspy.js',
|
||||
'scripts/library/spin.js',
|
||||
'scripts/library/Array.stableSort.js',
|
||||
'scripts/library/async.js',
|
||||
'scripts/couchpotato.js',
|
||||
'scripts/api.js',
|
||||
'scripts/library/history.js',
|
||||
'scripts/page.js',
|
||||
'scripts/block.js',
|
||||
'scripts/block/navigation.js',
|
||||
'scripts/block/header.js',
|
||||
'scripts/block/footer.js',
|
||||
'scripts/block/menu.js',
|
||||
'scripts/page/home.js',
|
||||
@@ -54,8 +47,9 @@ class ClientScript(Plugin):
|
||||
],
|
||||
}
|
||||
|
||||
urls = {'style': {}, 'script': {}}
|
||||
minified = {'style': {}, 'script': {}}
|
||||
watches = {}
|
||||
|
||||
original_paths = {'style': {}, 'script': {}}
|
||||
paths = {'style': {}, 'script': {}}
|
||||
comment = {
|
||||
'style': '/*** %s:%d ***/\n',
|
||||
@@ -74,8 +68,7 @@ class ClientScript(Plugin):
|
||||
addEvent('clientscript.get_styles', self.getStyles)
|
||||
addEvent('clientscript.get_scripts', self.getScripts)
|
||||
|
||||
if not Env.get('dev'):
|
||||
addEvent('app.load', self.minify)
|
||||
addEvent('app.load', self.compile)
|
||||
|
||||
self.addCore()
|
||||
|
||||
@@ -91,7 +84,7 @@ class ClientScript(Plugin):
|
||||
else:
|
||||
self.registerStyle(core_url, file_path, position = 'front')
|
||||
|
||||
def minify(self):
|
||||
def compile(self):
|
||||
|
||||
# Create cache dir
|
||||
cache = Env.get('cache_dir')
|
||||
@@ -102,47 +95,43 @@ class ClientScript(Plugin):
|
||||
|
||||
for file_type in ['style', 'script']:
|
||||
ext = 'js' if file_type is 'script' else 'css'
|
||||
positions = self.paths.get(file_type, {})
|
||||
positions = self.original_paths.get(file_type, {})
|
||||
for position in positions:
|
||||
files = positions.get(position)
|
||||
self._minify(file_type, files, position, position + '.' + ext)
|
||||
self._compile(file_type, files, position, position + '.' + ext)
|
||||
|
||||
def _minify(self, file_type, files, position, out):
|
||||
def _compile(self, file_type, paths, position, out):
|
||||
|
||||
cache = Env.get('cache_dir')
|
||||
out_name = out
|
||||
out = os.path.join(cache, 'minified', out_name)
|
||||
minified_dir = os.path.join(cache, 'minified')
|
||||
|
||||
data_combined = ''
|
||||
|
||||
new_paths = []
|
||||
for x in paths:
|
||||
file_path, url_path = x
|
||||
|
||||
raw = []
|
||||
for file_path in files:
|
||||
f = open(file_path, 'r').read()
|
||||
|
||||
if file_type == 'script':
|
||||
data = jsmin(f)
|
||||
else:
|
||||
data = self.prefix(f)
|
||||
data = cssmin(data)
|
||||
data = data.replace('../images/', '../static/images/')
|
||||
data = data.replace('../fonts/', '../static/fonts/')
|
||||
data = data.replace('../../static/', '../static/') # Replace inside plugins
|
||||
if not Env.get('dev'):
|
||||
data = f
|
||||
|
||||
raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data})
|
||||
data_combined += self.comment.get(file_type) % (ss(file_path), int(os.path.getmtime(file_path)))
|
||||
data_combined += data + '\n\n'
|
||||
else:
|
||||
new_paths.append(x)
|
||||
|
||||
# Combine all files together with some comments
|
||||
data = ''
|
||||
for r in raw:
|
||||
data += self.comment.get(file_type) % (ss(r.get('file')), r.get('date'))
|
||||
data += r.get('data') + '\n\n'
|
||||
if not Env.get('dev'):
|
||||
|
||||
self.createFile(out, data.strip())
|
||||
out_path = os.path.join(minified_dir, out_name)
|
||||
self.createFile(out_path, data_combined.strip())
|
||||
|
||||
if not self.minified.get(file_type):
|
||||
self.minified[file_type] = {}
|
||||
if not self.minified[file_type].get(position):
|
||||
self.minified[file_type][position] = []
|
||||
minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out)))
|
||||
new_paths.append((out_path, {'url': minified_url}))
|
||||
|
||||
minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out)))
|
||||
self.minified[file_type][position].append(minified_url)
|
||||
self.paths[file_type][position] = new_paths
|
||||
|
||||
def getStyles(self, *args, **kwargs):
|
||||
return self.get('style', *args, **kwargs)
|
||||
@@ -150,22 +139,12 @@ class ClientScript(Plugin):
|
||||
def getScripts(self, *args, **kwargs):
|
||||
return self.get('script', *args, **kwargs)
|
||||
|
||||
def get(self, type, as_html = False, location = 'head'):
|
||||
def get(self, type, location = 'head'):
|
||||
if type in self.paths and location in self.paths[type]:
|
||||
paths = self.paths[type][location]
|
||||
return [x[1] for x in paths]
|
||||
|
||||
data = '' if as_html else []
|
||||
|
||||
try:
|
||||
try:
|
||||
if not Env.get('dev'):
|
||||
return self.minified[type][location]
|
||||
except:
|
||||
pass
|
||||
|
||||
return self.urls[type][location]
|
||||
except:
|
||||
log.error('Error getting minified %s, %s: %s', (type, location, traceback.format_exc()))
|
||||
|
||||
return data
|
||||
return []
|
||||
|
||||
def registerStyle(self, api_path, file_path, position = 'head'):
|
||||
self.register(api_path, file_path, 'style', position)
|
||||
@@ -177,36 +156,10 @@ class ClientScript(Plugin):
|
||||
|
||||
api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path)))
|
||||
|
||||
if not self.urls[type].get(location):
|
||||
self.urls[type][location] = []
|
||||
self.urls[type][location].append(api_path)
|
||||
if not self.original_paths[type].get(location):
|
||||
self.original_paths[type][location] = []
|
||||
self.original_paths[type][location].append((file_path, api_path))
|
||||
|
||||
if not self.paths[type].get(location):
|
||||
self.paths[type][location] = []
|
||||
self.paths[type][location].append(file_path)
|
||||
|
||||
prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow']
|
||||
prefix_tags = ['ms', 'moz', 'webkit']
|
||||
|
||||
def prefix(self, data):
|
||||
|
||||
trimmed_data = re.sub('(\t|\n|\r)+', '', data)
|
||||
|
||||
new_data = ''
|
||||
colon_split = trimmed_data.split(';')
|
||||
for splt in colon_split:
|
||||
curl_split = splt.strip().split('{')
|
||||
for curly in curl_split:
|
||||
curly = curly.strip()
|
||||
for prop in self.prefix_properties:
|
||||
if curly[:len(prop) + 1] == prop + ':':
|
||||
for tag in self.prefix_tags:
|
||||
new_data += ' -%s-%s; ' % (tag, curly)
|
||||
|
||||
new_data += curly + (' { ' if len(curl_split) > 1 else ' ')
|
||||
|
||||
new_data += '; '
|
||||
|
||||
new_data = new_data.replace('{ ;', '; ').replace('} ;', '} ')
|
||||
|
||||
return new_data
|
||||
self.paths[type][location].append((file_path, api_path))
|
||||
|
||||
@@ -16,8 +16,8 @@ var DownloadersBase = new Class({
|
||||
|
||||
var setting_page = App.getPage('Settings');
|
||||
setting_page.addEvent('create', function(){
|
||||
Object.each(setting_page.tabs.downloaders.groups, self.addTestButton.bind(self))
|
||||
})
|
||||
Object.each(setting_page.tabs.downloaders.groups, self.addTestButton.bind(self));
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
@@ -44,19 +44,19 @@ var DownloadersBase = new Class({
|
||||
if(json.success){
|
||||
message = new Element('span.success', {
|
||||
'text': 'Connection successful'
|
||||
}).inject(button, 'after')
|
||||
}).inject(button, 'after');
|
||||
}
|
||||
else {
|
||||
var msg_text = 'Connection failed. Check logs for details.';
|
||||
if(json.hasOwnProperty('msg')) msg_text = json.msg;
|
||||
message = new Element('span.failed', {
|
||||
'text': msg_text
|
||||
}).inject(button, 'after')
|
||||
}).inject(button, 'after');
|
||||
}
|
||||
|
||||
(function(){
|
||||
message.destroy();
|
||||
}).delay(3000)
|
||||
}).delay(3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ var UpdaterBase = new Class({
|
||||
App.trigger('message', ['No updates available']);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
@@ -50,8 +50,8 @@ var UpdaterBase = new Class({
|
||||
self.message.destroy();
|
||||
}
|
||||
}
|
||||
})
|
||||
}, (timeout || 0))
|
||||
});
|
||||
}, (timeout || 0));
|
||||
|
||||
},
|
||||
|
||||
@@ -84,7 +84,7 @@ var UpdaterBase = new Class({
|
||||
'click': self.doUpdate.bind(self)
|
||||
}
|
||||
})
|
||||
).inject(document.body)
|
||||
).inject(document.body);
|
||||
},
|
||||
|
||||
doUpdate: function(){
|
||||
@@ -96,7 +96,7 @@ var UpdaterBase = new Class({
|
||||
if(json.success)
|
||||
self.updating();
|
||||
else
|
||||
App.unBlockPage()
|
||||
App.unBlockPage();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -20,14 +20,31 @@ class Blackhole(DownloaderBase):
|
||||
status_support = False
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
""" Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
directory = self.conf('directory')
|
||||
|
||||
# The folder needs to exist
|
||||
if not directory or not os.path.isdir(directory):
|
||||
log.error('No directory set for blackhole %s download.', data.get('protocol'))
|
||||
else:
|
||||
try:
|
||||
# Filedata can be empty, which probably means it a magnet link
|
||||
if not filedata or len(filedata) < 50:
|
||||
try:
|
||||
if data.get('protocol') == 'torrent_magnet':
|
||||
@@ -36,13 +53,16 @@ class Blackhole(DownloaderBase):
|
||||
except:
|
||||
log.error('Failed download torrent via magnet url: %s', traceback.format_exc())
|
||||
|
||||
# If it's still empty, don't know what to do!
|
||||
if not filedata or len(filedata) < 50:
|
||||
log.error('No nzb/torrent available: %s', data.get('url'))
|
||||
return False
|
||||
|
||||
# Create filename with imdb id and other nice stuff
|
||||
file_name = self.createFileName(data, filedata, media)
|
||||
full_path = os.path.join(directory, file_name)
|
||||
|
||||
# People want thinks nice and tidy, create a subdir
|
||||
if self.conf('create_subdir'):
|
||||
try:
|
||||
new_path = os.path.splitext(full_path)[0]
|
||||
@@ -53,6 +73,8 @@ class Blackhole(DownloaderBase):
|
||||
log.error('Couldnt create sub dir, reverting to old one: %s', full_path)
|
||||
|
||||
try:
|
||||
|
||||
# Make sure the file doesn't exist yet, no need in overwriting it
|
||||
if not os.path.isfile(full_path):
|
||||
log.info('Downloading %s to %s.', (data.get('protocol'), full_path))
|
||||
with open(full_path, 'wb') as f:
|
||||
@@ -74,6 +96,10 @@ class Blackhole(DownloaderBase):
|
||||
return False
|
||||
|
||||
def test(self):
|
||||
""" Test and see if the directory is writable
|
||||
:return: boolean
|
||||
"""
|
||||
|
||||
directory = self.conf('directory')
|
||||
if directory and os.path.isdir(directory):
|
||||
|
||||
@@ -88,6 +114,10 @@ class Blackhole(DownloaderBase):
|
||||
return False
|
||||
|
||||
def getEnabledProtocol(self):
|
||||
""" What protocols is this downloaded used for
|
||||
:return: list with protocols
|
||||
"""
|
||||
|
||||
if self.conf('use_for') == 'both':
|
||||
return super(Blackhole, self).getEnabledProtocol()
|
||||
elif self.conf('use_for') == 'torrent':
|
||||
@@ -96,6 +126,12 @@ class Blackhole(DownloaderBase):
|
||||
return ['nzb']
|
||||
|
||||
def isEnabled(self, manual = False, data = None):
|
||||
""" Check if protocol is used (and enabled)
|
||||
:param manual: The user has clicked to download a link through the webUI
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:return: boolean
|
||||
"""
|
||||
if not data: data = {}
|
||||
for_protocol = ['both']
|
||||
if data and 'torrent' in data.get('protocol'):
|
||||
|
||||
@@ -25,6 +25,11 @@ class Deluge(DownloaderBase):
|
||||
drpc = None
|
||||
|
||||
def connect(self, reconnect = False):
|
||||
""" Connect to the delugeRPC, re-use connection when already available
|
||||
:param reconnect: force reconnect
|
||||
:return: DelugeRPC instance
|
||||
"""
|
||||
|
||||
# Load host from config and split out port.
|
||||
host = cleanHost(self.conf('host'), protocol = False).split(':')
|
||||
|
||||
@@ -42,6 +47,20 @@ class Deluge(DownloaderBase):
|
||||
return self.drpc
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
""" Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -96,11 +115,21 @@ class Deluge(DownloaderBase):
|
||||
return self.downloadReturnId(remote_torrent)
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
:return: bool
|
||||
"""
|
||||
if self.connect(True) and self.drpc.test():
|
||||
return True
|
||||
return False
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
""" Get status of all active downloads
|
||||
|
||||
:param ids: list of (mixed) downloader ids
|
||||
Used to match the releases for this downloader as there could be
|
||||
other downloaders active that it should ignore
|
||||
:return: list of releases
|
||||
"""
|
||||
|
||||
log.debug('Checking Deluge download status.')
|
||||
|
||||
|
||||
427
couchpotato/core/downloaders/hadouken.py
Normal file
427
couchpotato/core/downloaders/hadouken.py
Normal file
@@ -0,0 +1,427 @@
|
||||
from base64 import b16encode, b32decode, b64encode
|
||||
from distutils.version import LooseVersion
|
||||
from hashlib import sha1
|
||||
import httplib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import urllib2
|
||||
|
||||
from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownloadList
|
||||
from couchpotato.core.helpers.encoding import isInt, sp
|
||||
from couchpotato.core.helpers.variable import cleanHost
|
||||
from couchpotato.core.logger import CPLog
|
||||
from bencode import bencode as benc, bdecode
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
autoload = 'Hadouken'
|
||||
|
||||
|
||||
class Hadouken(DownloaderBase):
|
||||
protocol = ['torrent', 'torrent_magnet']
|
||||
hadouken_api = None
|
||||
|
||||
def connect(self):
|
||||
# Load host from config and split out port.
|
||||
host = cleanHost(self.conf('host'), protocol = False).split(':')
|
||||
|
||||
if not isInt(host[1]):
|
||||
log.error('Config properties are not filled in correctly, port is missing.')
|
||||
return False
|
||||
|
||||
if not self.conf('api_key'):
|
||||
log.error('Config properties are not filled in correctly, API key is missing.')
|
||||
return False
|
||||
|
||||
self.hadouken_api = HadoukenAPI(host[0], port = host[1], api_key = self.conf('api_key'))
|
||||
|
||||
return True
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
""" Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
log.debug("Sending '%s' (%s) to Hadouken.", (data.get('name'), data.get('protocol')))
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
|
||||
torrent_params = {}
|
||||
|
||||
if self.conf('label'):
|
||||
torrent_params['label'] = self.conf('label')
|
||||
|
||||
torrent_filename = self.createFileName(data, filedata, media)
|
||||
|
||||
if data.get('protocol') == 'torrent_magnet':
|
||||
torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
|
||||
torrent_params['trackers'] = self.torrent_trackers
|
||||
torrent_params['name'] = torrent_filename
|
||||
else:
|
||||
info = bdecode(filedata)['info']
|
||||
torrent_hash = sha1(benc(info)).hexdigest().upper()
|
||||
|
||||
# Convert base 32 to hex
|
||||
if len(torrent_hash) == 32:
|
||||
torrent_hash = b16encode(b32decode(torrent_hash))
|
||||
|
||||
# Send request to Hadouken
|
||||
if data.get('protocol') == 'torrent_magnet':
|
||||
self.hadouken_api.add_magnet_link(data.get('url'), torrent_params)
|
||||
else:
|
||||
self.hadouken_api.add_file(filedata, torrent_params)
|
||||
|
||||
return self.downloadReturnId(torrent_hash)
|
||||
|
||||
def test(self):
|
||||
""" Tests the given host:port and API key """
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
|
||||
version = self.hadouken_api.get_version()
|
||||
|
||||
if not version:
|
||||
log.error('Could not get Hadouken version.')
|
||||
return False
|
||||
|
||||
# The minimum required version of Hadouken is 4.5.6.
|
||||
if LooseVersion(version) >= LooseVersion('4.5.6'):
|
||||
return True
|
||||
|
||||
log.error('Hadouken v4.5.6 (or newer) required. Found v%s', version)
|
||||
return False
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
""" Get status of all active downloads
|
||||
|
||||
:param ids: list of (mixed) downloader ids
|
||||
Used to match the releases for this downloader as there could be
|
||||
other downloaders active that it should ignore
|
||||
:return: list of releases
|
||||
"""
|
||||
|
||||
log.debug('Checking Hadouken download status.')
|
||||
|
||||
if not self.connect():
|
||||
return []
|
||||
|
||||
release_downloads = ReleaseDownloadList(self)
|
||||
queue = self.hadouken_api.get_by_hash_list(ids)
|
||||
|
||||
if not queue:
|
||||
return []
|
||||
|
||||
for torrent in queue:
|
||||
if torrent is None:
|
||||
continue
|
||||
|
||||
torrent_filelist = self.hadouken_api.get_files_by_hash(torrent['InfoHash'])
|
||||
torrent_files = []
|
||||
|
||||
save_path = torrent['SavePath']
|
||||
|
||||
# The 'Path' key for each file_item contains
|
||||
# the full path to the single file relative to the
|
||||
# torrents save path.
|
||||
|
||||
# For a single file torrent the result would be,
|
||||
# - Save path: "C:\Downloads"
|
||||
# - file_item['Path'] = "file1.iso"
|
||||
# Resulting path: "C:\Downloads\file1.iso"
|
||||
|
||||
# For a multi file torrent the result would be,
|
||||
# - Save path: "C:\Downloads"
|
||||
# - file_item['Path'] = "dirname/file1.iso"
|
||||
# Resulting path: "C:\Downloads\dirname/file1.iso"
|
||||
|
||||
for file_item in torrent_filelist:
|
||||
torrent_files.append(sp(os.path.join(save_path, file_item['Path'])))
|
||||
|
||||
release_downloads.append({
|
||||
'id': torrent['InfoHash'].upper(),
|
||||
'name': torrent['Name'],
|
||||
'status': self.get_torrent_status(torrent),
|
||||
'seed_ratio': self.get_seed_ratio(torrent),
|
||||
'original_status': torrent['State'],
|
||||
'timeleft': -1,
|
||||
'folder': sp(save_path if len(torrent_files == 1) else os.path.join(save_path, torrent['Name'])),
|
||||
'files': torrent_files
|
||||
})
|
||||
|
||||
return release_downloads
|
||||
|
||||
def get_seed_ratio(self, torrent):
|
||||
""" Returns the seed ratio for a given torrent.
|
||||
|
||||
Keyword arguments:
|
||||
torrent -- The torrent to calculate seed ratio for.
|
||||
"""
|
||||
|
||||
up = torrent['TotalUploadedBytes']
|
||||
down = torrent['TotalDownloadedBytes']
|
||||
|
||||
if up > 0 and down > 0:
|
||||
return up / down
|
||||
|
||||
return 0
|
||||
|
||||
def get_torrent_status(self, torrent):
|
||||
""" Returns the CouchPotato status for a given torrent.
|
||||
|
||||
Keyword arguments:
|
||||
torrent -- The torrent to translate status for.
|
||||
"""
|
||||
|
||||
if torrent['IsSeeding'] and torrent['IsFinished'] and torrent['Paused']:
|
||||
return 'completed'
|
||||
|
||||
if torrent['IsSeeding']:
|
||||
return 'seeding'
|
||||
|
||||
return 'busy'
|
||||
|
||||
def pause(self, release_download, pause = True):
|
||||
""" Pauses or resumes the torrent specified by the ID field
|
||||
in release_download.
|
||||
|
||||
Keyword arguments:
|
||||
release_download -- The CouchPotato release_download to pause/resume.
|
||||
pause -- Boolean indicating whether to pause or resume.
|
||||
"""
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
|
||||
return self.hadouken_api.pause(release_download['id'], pause)
|
||||
|
||||
def removeFailed(self, release_download):
|
||||
""" Removes a failed torrent and also remove the data associated with it.
|
||||
|
||||
Keyword arguments:
|
||||
release_download -- The CouchPotato release_download to remove.
|
||||
"""
|
||||
|
||||
log.info('%s failed downloading, deleting...', release_download['name'])
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
|
||||
return self.hadouken_api.remove(release_download['id'], remove_data = True)
|
||||
|
||||
def processComplete(self, release_download, delete_files = False):
|
||||
""" Removes the completed torrent from Hadouken and optionally removes the data
|
||||
associated with it.
|
||||
|
||||
Keyword arguments:
|
||||
release_download -- The CouchPotato release_download to remove.
|
||||
delete_files: Boolean indicating whether to remove the associated data.
|
||||
"""
|
||||
|
||||
log.debug('Requesting Hadouken to remove the torrent %s%s.',
|
||||
(release_download['name'], ' and cleanup the downloaded files' if delete_files else ''))
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
|
||||
return self.hadouken_api.remove(release_download['id'], remove_data = delete_files)
|
||||
|
||||
|
||||
class HadoukenAPI(object):
|
||||
def __init__(self, host = 'localhost', port = 7890, api_key = None):
|
||||
self.url = 'http://' + str(host) + ':' + str(port)
|
||||
self.api_key = api_key
|
||||
self.requestId = 0;
|
||||
|
||||
self.opener = urllib2.build_opener()
|
||||
self.opener.addheaders = [('User-agent', 'couchpotato-hadouken-client/1.0'), ('Accept', 'application/json')]
|
||||
|
||||
if not api_key:
|
||||
log.error('API key missing.')
|
||||
|
||||
def add_file(self, filedata, torrent_params):
|
||||
""" Add a file to Hadouken with the specified parameters.
|
||||
|
||||
Keyword arguments:
|
||||
filedata -- The binary torrent data.
|
||||
torrent_params -- Additional parameters for the file.
|
||||
"""
|
||||
data = {
|
||||
'method': 'torrents.addFile',
|
||||
'params': [b64encode(filedata), torrent_params]
|
||||
}
|
||||
|
||||
return self._request(data)
|
||||
|
||||
def add_magnet_link(self, magnetLink, torrent_params):
|
||||
""" Add a magnet link to Hadouken with the specified parameters.
|
||||
|
||||
Keyword arguments:
|
||||
magnetLink -- The magnet link to send.
|
||||
torrent_params -- Additional parameters for the magnet link.
|
||||
"""
|
||||
data = {
|
||||
'method': 'torrents.addUrl',
|
||||
'params': [magnetLink, torrent_params]
|
||||
}
|
||||
|
||||
return self._request(data)
|
||||
|
||||
def get_by_hash_list(self, infoHashList):
|
||||
""" Gets a list of torrents filtered by the given info hash list.
|
||||
|
||||
Keyword arguments:
|
||||
infoHashList -- A list of info hashes.
|
||||
"""
|
||||
data = {
|
||||
'method': 'torrents.getByInfoHashList',
|
||||
'params': [infoHashList]
|
||||
}
|
||||
|
||||
return self._request(data)
|
||||
|
||||
def get_files_by_hash(self, infoHash):
|
||||
""" Gets a list of files for the torrent identified by the
|
||||
given info hash.
|
||||
|
||||
Keyword arguments:
|
||||
infoHash -- The info hash of the torrent to return files for.
|
||||
"""
|
||||
data = {
|
||||
'method': 'torrents.getFiles',
|
||||
'params': [infoHash]
|
||||
}
|
||||
|
||||
return self._request(data)
|
||||
|
||||
def get_version(self):
|
||||
""" Gets the version, commitish and build date of Hadouken. """
|
||||
data = {
|
||||
'method': 'core.getVersion',
|
||||
'params': None
|
||||
}
|
||||
|
||||
result = self._request(data)
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
return result['Version']
|
||||
|
||||
def pause(self, infoHash, pause):
|
||||
""" Pauses/unpauses the torrent identified by the given info hash.
|
||||
|
||||
Keyword arguments:
|
||||
infoHash -- The info hash of the torrent to operate on.
|
||||
pause -- If true, pauses the torrent. Otherwise resumes.
|
||||
"""
|
||||
data = {
|
||||
'method': 'torrents.pause',
|
||||
'params': [infoHash]
|
||||
}
|
||||
|
||||
if not pause:
|
||||
data['method'] = 'torrents.resume'
|
||||
|
||||
return self._request(data)
|
||||
|
||||
def remove(self, infoHash, remove_data = False):
|
||||
""" Removes the torrent identified by the given info hash and
|
||||
optionally removes the data as well.
|
||||
|
||||
Keyword arguments:
|
||||
infoHash -- The info hash of the torrent to remove.
|
||||
remove_data -- If true, removes the data associated with the torrent.
|
||||
"""
|
||||
data = {
|
||||
'method': 'torrents.remove',
|
||||
'params': [infoHash, remove_data]
|
||||
}
|
||||
|
||||
return self._request(data)
|
||||
|
||||
|
||||
def _request(self, data):
|
||||
self.requestId += 1
|
||||
|
||||
data['jsonrpc'] = '2.0'
|
||||
data['id'] = self.requestId
|
||||
|
||||
request = urllib2.Request(self.url + '/jsonrpc', data = json.dumps(data))
|
||||
request.add_header('Authorization', 'Token ' + self.api_key)
|
||||
request.add_header('Content-Type', 'application/json')
|
||||
|
||||
try:
|
||||
f = self.opener.open(request)
|
||||
response = f.read()
|
||||
f.close()
|
||||
|
||||
obj = json.loads(response)
|
||||
|
||||
if not 'error' in obj.keys():
|
||||
return obj['result']
|
||||
|
||||
log.error('JSONRPC error, %s: %s', obj['error']['code'], obj['error']['message'])
|
||||
except httplib.InvalidURL as err:
|
||||
log.error('Invalid Hadouken host, check your config %s', err)
|
||||
except urllib2.HTTPError as err:
|
||||
if err.code == 401:
|
||||
log.error('Invalid Hadouken API key, check your config')
|
||||
else:
|
||||
log.error('Hadouken HTTPError: %s', err)
|
||||
except urllib2.URLError as err:
|
||||
log.error('Unable to connect to Hadouken %s', err)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
config = [{
|
||||
'name': 'hadouken',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'list': 'download_providers',
|
||||
'name': 'hadouken',
|
||||
'label': 'Hadouken',
|
||||
'description': 'Use <a href="http://www.hdkn.net">Hadouken</a> (>= v4.5.6) to download torrents.',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': 0,
|
||||
'type': 'enabler',
|
||||
'radio_group': 'torrent'
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'localhost:7890'
|
||||
},
|
||||
{
|
||||
'name': 'api_key',
|
||||
'label': 'API key',
|
||||
'type': 'password'
|
||||
},
|
||||
{
|
||||
'name': 'label',
|
||||
'description': 'Label to add torrent as.'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -23,6 +23,20 @@ class NZBGet(DownloaderBase):
|
||||
rpc = 'xmlrpc'
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
""" Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -71,6 +85,10 @@ class NZBGet(DownloaderBase):
|
||||
return False
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
:return: bool
|
||||
"""
|
||||
|
||||
rpc = self.getRPC()
|
||||
|
||||
try:
|
||||
@@ -91,6 +109,13 @@ class NZBGet(DownloaderBase):
|
||||
return True
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
""" Get status of all active downloads
|
||||
|
||||
:param ids: list of (mixed) downloader ids
|
||||
Used to match the releases for this downloader as there could be
|
||||
other downloaders active that it should ignore
|
||||
:return: list of releases
|
||||
"""
|
||||
|
||||
log.debug('Checking NZBGet download status.')
|
||||
|
||||
@@ -163,12 +188,12 @@ class NZBGet(DownloaderBase):
|
||||
nzb_id = nzb['NZBID']
|
||||
|
||||
if nzb_id in ids:
|
||||
log.debug('Found %s in NZBGet history. ParStatus: %s, ScriptStatus: %s, Log: %s', (nzb['NZBFilename'] , nzb['ParStatus'], nzb['ScriptStatus'] , nzb['Log']))
|
||||
log.debug('Found %s in NZBGet history. TotalStatus: %s, ParStatus: %s, ScriptStatus: %s, Log: %s', (nzb['NZBFilename'] , nzb['Status'], nzb['ParStatus'], nzb['ScriptStatus'] , nzb['Log']))
|
||||
release_downloads.append({
|
||||
'id': nzb_id,
|
||||
'name': nzb['NZBFilename'],
|
||||
'status': 'completed' if nzb['ParStatus'] in ['SUCCESS', 'NONE'] and nzb['ScriptStatus'] in ['SUCCESS', 'NONE'] else 'failed',
|
||||
'original_status': nzb['ParStatus'] + ', ' + nzb['ScriptStatus'],
|
||||
'status': 'completed' if 'SUCCESS' in nzb['Status'] else 'failed',
|
||||
'original_status': nzb['Status'],
|
||||
'timeleft': str(timedelta(seconds = 0)),
|
||||
'folder': sp(nzb['DestDir'])
|
||||
})
|
||||
|
||||
@@ -24,6 +24,20 @@ class NZBVortex(DownloaderBase):
|
||||
session_id = None
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
""" Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -45,6 +59,10 @@ class NZBVortex(DownloaderBase):
|
||||
return False
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
:return: bool
|
||||
"""
|
||||
|
||||
try:
|
||||
login_result = self.login()
|
||||
except:
|
||||
@@ -53,6 +71,13 @@ class NZBVortex(DownloaderBase):
|
||||
return login_result
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
""" Get status of all active downloads
|
||||
|
||||
:param ids: list of (mixed) downloader ids
|
||||
Used to match the releases for this downloader as there could be
|
||||
other downloaders active that it should ignore
|
||||
:return: list of releases
|
||||
"""
|
||||
|
||||
raw_statuses = self.call('nzb')
|
||||
|
||||
|
||||
@@ -19,6 +19,20 @@ class Pneumatic(DownloaderBase):
|
||||
status_support = False
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
""" Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -63,6 +77,10 @@ class Pneumatic(DownloaderBase):
|
||||
return False
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
:return: bool
|
||||
"""
|
||||
|
||||
directory = self.conf('directory')
|
||||
if directory and os.path.isdir(directory):
|
||||
|
||||
|
||||
68
couchpotato/core/downloaders/putio/__init__.py
Normal file
68
couchpotato/core/downloaders/putio/__init__.py
Normal file
@@ -0,0 +1,68 @@
|
||||
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': 'folder',
|
||||
'description': ('The folder on putio where you want the upload to go','Will find the first first folder that matches this name'),
|
||||
'default': 0,
|
||||
},
|
||||
{
|
||||
'name': 'callback_host',
|
||||
'description': 'External reachable url to CP so put.io can do it\'s thing',
|
||||
},
|
||||
{
|
||||
'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','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': 'manual',
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'advanced': True,
|
||||
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
181
couchpotato/core/downloaders/putio/main.py
Normal file
181
couchpotato/core/downloaders/putio/main.py
Normal file
@@ -0,0 +1,181 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEventAsync
|
||||
from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownloadList
|
||||
from couchpotato.core.helpers.variable import cleanHost
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.environment import Env
|
||||
from pio import api as pio
|
||||
import datetime
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
autoload = 'Putiodownload'
|
||||
|
||||
|
||||
class PutIO(DownloaderBase):
|
||||
|
||||
protocol = ['torrent', 'torrent_magnet']
|
||||
downloading_list = []
|
||||
oauth_authenticate = 'https://api.couchpota.to/authorize/putio/'
|
||||
|
||||
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)
|
||||
addApiView('downloader.putio.credentials', self.getCredentials)
|
||||
addEvent('putio.download', self.putioDownloader)
|
||||
|
||||
return super(PutIO, self).__init__()
|
||||
|
||||
# This is a recusive function to check for the folders
|
||||
def recursionFolder(self, client, folder = 0, tfolder = ''):
|
||||
files = client.File.list(folder)
|
||||
for f in files:
|
||||
if f.content_type == 'application/x-directory':
|
||||
if f.name == tfolder:
|
||||
return f.id
|
||||
else:
|
||||
result = self.recursionFolder(client, f.id, tfolder)
|
||||
if result != 0:
|
||||
return result
|
||||
return 0
|
||||
|
||||
# This will check the root for the folder, and kick of recusively checking sub folder
|
||||
def convertFolder(self, client, folder):
|
||||
if folder == 0:
|
||||
return 0
|
||||
else:
|
||||
return self.recursionFolder(client, 0, folder)
|
||||
|
||||
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'))
|
||||
putioFolder = self.convertFolder(client, self.conf('folder'))
|
||||
log.debug('putioFolder ID is %s', putioFolder)
|
||||
# It might be possible to call getFromPutio from the renamer if we can then we don't need to do this.
|
||||
# Note callback_host is NOT our address, it's the internet host that putio can call too
|
||||
callbackurl = None
|
||||
if self.conf('download'):
|
||||
callbackurl = 'http://' + self.conf('callback_host') + '%sdownloader.putio.getfrom/' %Env.get('api_base'.strip('/'))
|
||||
resp = client.Transfer.add_url(url, callback_url = callbackurl, parent_id = putioFolder)
|
||||
log.debug('resp is %s', resp.id);
|
||||
return self.downloadReturnId(resp.id)
|
||||
|
||||
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, host = None, **kwargs):
|
||||
|
||||
callback_url = cleanHost(host) + '%sdownloader.putio.credentials/' % (Env.get('api_base').lstrip('/'))
|
||||
log.debug('callback_url is %s', callback_url)
|
||||
|
||||
target_url = self.oauth_authenticate + "?target=" + callback_url
|
||||
log.debug('target_url is %s', target_url)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'url': target_url,
|
||||
}
|
||||
|
||||
def getCredentials(self, **kwargs):
|
||||
try:
|
||||
oauth_token = kwargs.get('oauth')
|
||||
except:
|
||||
return 'redirect', Env.get('web_base') + 'settings/downloaders/'
|
||||
log.debug('oauth_token is: %s', oauth_token)
|
||||
self.conf('oauth_token', value = oauth_token);
|
||||
return 'redirect', Env.get('web_base') + 'settings/downloaders/'
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
|
||||
log.debug('Checking putio download status.')
|
||||
client = pio.Client(self.conf('oauth_token'))
|
||||
|
||||
transfers = client.Transfer.list()
|
||||
|
||||
log.debug(transfers);
|
||||
release_downloads = ReleaseDownloadList(self)
|
||||
for t in transfers:
|
||||
if t.id in ids:
|
||||
|
||||
log.debug('downloading list is %s', self.downloading_list)
|
||||
if t.status == "COMPLETED" and self.conf('download') == False :
|
||||
status = 'completed'
|
||||
|
||||
# So check if we are trying to download something
|
||||
elif t.status == "COMPLETED" and self.conf('download') == True:
|
||||
# Assume we are done
|
||||
status = 'completed'
|
||||
if not self.downloading_list:
|
||||
now = datetime.datetime.utcnow()
|
||||
date_time = datetime.datetime.strptime(t.finished_at,"%Y-%m-%dT%H:%M:%S")
|
||||
# We need to make sure a race condition didn't happen
|
||||
if (now - date_time) < datetime.timedelta(minutes=5):
|
||||
# 5 minutes haven't passed so we wait
|
||||
status = 'busy'
|
||||
else:
|
||||
# If we have the file_id in the downloading_list mark it as busy
|
||||
if str(t.file_id) in self.downloading_list:
|
||||
status = 'busy'
|
||||
else:
|
||||
status = 'busy'
|
||||
release_downloads.append({
|
||||
'id' : t.id,
|
||||
'name': t.name,
|
||||
'status': status,
|
||||
'timeleft': t.estimated_time,
|
||||
})
|
||||
|
||||
return release_downloads
|
||||
|
||||
def putioDownloader(self, fid):
|
||||
|
||||
log.info('Put.io Real downloader called with file_id: %s',fid)
|
||||
client = pio.Client(self.conf('oauth_token'))
|
||||
|
||||
log.debug('About to get file List')
|
||||
putioFolder = self.convertFolder(client, self.conf('folder'))
|
||||
log.debug('PutioFolderID is %s', putioFolder)
|
||||
files = client.File.list(parent_id=putioFolder)
|
||||
downloaddir = self.conf('download_dir')
|
||||
|
||||
for f in files:
|
||||
if str(f.id) == str(fid):
|
||||
client.File.download(f, dest = downloaddir, delete_after_download = self.conf('delete_file'))
|
||||
# Once the download is complete we need to remove it from the running list.
|
||||
self.downloading_list.remove(fid)
|
||||
|
||||
return True
|
||||
|
||||
def getFromPutio(self, **kwargs):
|
||||
|
||||
try:
|
||||
file_id = str(kwargs.get('file_id'))
|
||||
except:
|
||||
return {
|
||||
'success' : False,
|
||||
}
|
||||
|
||||
log.info('Put.io Download has been called file_id is %s', file_id)
|
||||
if file_id not in self.downloading_list:
|
||||
self.downloading_list.append(file_id)
|
||||
fireEventAsync('putio.download',fid = file_id)
|
||||
return {
|
||||
'success': True,
|
||||
}
|
||||
|
||||
return {
|
||||
'success': False,
|
||||
}
|
||||
|
||||
68
couchpotato/core/downloaders/putio/static/putio.js
Normal file
68
couchpotato/core/downloaders/putio/static/putio.js
Normal file
@@ -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*=oauth_token]').get('value')+'"',
|
||||
'events': {
|
||||
'click': function(){
|
||||
fieldset.getElements('input[name*=oauth_token]').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();
|
||||
});
|
||||
@@ -41,12 +41,30 @@ class qBittorrent(DownloaderBase):
|
||||
return self.qb
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
:return: bool
|
||||
"""
|
||||
|
||||
if self.connect():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
""" Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -95,6 +113,14 @@ class qBittorrent(DownloaderBase):
|
||||
return 'busy'
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
""" Get status of all active downloads
|
||||
|
||||
:param ids: list of (mixed) downloader ids
|
||||
Used to match the releases for this downloader as there could be
|
||||
other downloaders active that it should ignore
|
||||
:return: list of releases
|
||||
"""
|
||||
|
||||
log.debug('Checking qBittorrent download status.')
|
||||
|
||||
if not self.connect():
|
||||
|
||||
@@ -84,6 +84,10 @@ class rTorrent(DownloaderBase):
|
||||
return self.rt
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
:return: bool
|
||||
"""
|
||||
|
||||
if self.connect(True):
|
||||
return True
|
||||
|
||||
@@ -94,6 +98,20 @@ class rTorrent(DownloaderBase):
|
||||
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
""" Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -161,6 +179,14 @@ class rTorrent(DownloaderBase):
|
||||
return 'completed'
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
""" Get status of all active downloads
|
||||
|
||||
:param ids: list of (mixed) downloader ids
|
||||
Used to match the releases for this downloader as there could be
|
||||
other downloaders active that it should ignore
|
||||
:return: list of releases
|
||||
"""
|
||||
|
||||
log.debug('Checking rTorrent download status.')
|
||||
|
||||
if not self.connect():
|
||||
|
||||
@@ -21,6 +21,21 @@ class Sabnzbd(DownloaderBase):
|
||||
protocol = ['nzb']
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
"""
|
||||
Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -69,6 +84,11 @@ class Sabnzbd(DownloaderBase):
|
||||
return False
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
Return message if an old version of SAB is used
|
||||
:return: bool
|
||||
"""
|
||||
|
||||
try:
|
||||
sab_data = self.call({
|
||||
'mode': 'version',
|
||||
@@ -89,6 +109,13 @@ class Sabnzbd(DownloaderBase):
|
||||
return True
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
""" Get status of all active downloads
|
||||
|
||||
:param ids: list of (mixed) downloader ids
|
||||
Used to match the releases for this downloader as there could be
|
||||
other downloaders active that it should ignore
|
||||
:return: list of releases
|
||||
"""
|
||||
|
||||
log.debug('Checking SABnzbd download status.')
|
||||
|
||||
|
||||
@@ -19,6 +19,21 @@ class Synology(DownloaderBase):
|
||||
status_support = False
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
"""
|
||||
Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -50,6 +65,10 @@ class Synology(DownloaderBase):
|
||||
return self.downloadReturnId('') if response else False
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
:return: bool
|
||||
"""
|
||||
|
||||
host = cleanHost(self.conf('host'), protocol = False).split(':')
|
||||
try:
|
||||
srpc = SynologyRPC(host[0], host[1], self.conf('username'), self.conf('password'))
|
||||
@@ -118,7 +137,7 @@ class SynologyRPC(object):
|
||||
def _req(self, url, args, files = None):
|
||||
response = {'success': False}
|
||||
try:
|
||||
req = requests.post(url, data = args, files = files)
|
||||
req = requests.post(url, data = args, files = files, verify = False)
|
||||
req.raise_for_status()
|
||||
response = json.loads(req.text)
|
||||
if response['success']:
|
||||
|
||||
@@ -34,6 +34,21 @@ class Transmission(DownloaderBase):
|
||||
return self.trpc
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
"""
|
||||
Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -88,11 +103,22 @@ class Transmission(DownloaderBase):
|
||||
return self.downloadReturnId(data['hashString'])
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
:return: bool
|
||||
"""
|
||||
|
||||
if self.connect() and self.trpc.get_session():
|
||||
return True
|
||||
return False
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
""" Get status of all active downloads
|
||||
|
||||
:param ids: list of (mixed) downloader ids
|
||||
Used to match the releases for this downloader as there could be
|
||||
other downloaders active that it should ignore
|
||||
:return: list of releases
|
||||
"""
|
||||
|
||||
log.debug('Checking Transmission download status.')
|
||||
|
||||
@@ -121,6 +147,8 @@ class Transmission(DownloaderBase):
|
||||
status = 'failed'
|
||||
elif torrent['status'] == 0 and torrent['percentDone'] == 1:
|
||||
status = 'completed'
|
||||
elif torrent['status'] == 16 and torrent['percentDone'] == 1:
|
||||
status = 'completed'
|
||||
elif torrent['status'] in [5, 6]:
|
||||
status = 'seeding'
|
||||
|
||||
|
||||
@@ -51,6 +51,21 @@ class uTorrent(DownloaderBase):
|
||||
return self.utorrent_api
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
"""
|
||||
Send a torrent/nzb file to the downloader
|
||||
|
||||
:param data: dict returned from provider
|
||||
Contains the release information
|
||||
:param media: media dict with information
|
||||
Used for creating the filename when possible
|
||||
:param filedata: downloaded torrent/nzb filedata
|
||||
The file gets downloaded in the searcher and send to this function
|
||||
This is done to have failed checking before using the downloader, so the downloader
|
||||
doesn't need to worry about that
|
||||
:return: boolean
|
||||
One faile returns false, but the downloaded should log his own errors
|
||||
"""
|
||||
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
@@ -120,6 +135,10 @@ class uTorrent(DownloaderBase):
|
||||
return self.downloadReturnId(torrent_hash)
|
||||
|
||||
def test(self):
|
||||
""" Check if connection works
|
||||
:return: bool
|
||||
"""
|
||||
|
||||
if self.connect():
|
||||
build_version = self.utorrent_api.get_build()
|
||||
if not build_version:
|
||||
@@ -131,6 +150,13 @@ class uTorrent(DownloaderBase):
|
||||
return False
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
""" Get status of all active downloads
|
||||
|
||||
:param ids: list of (mixed) downloader ids
|
||||
Used to match the releases for this downloader as there could be
|
||||
other downloaders active that it should ignore
|
||||
:return: list of releases
|
||||
"""
|
||||
|
||||
log.debug('Checking uTorrent download status.')
|
||||
|
||||
|
||||
@@ -37,15 +37,18 @@ def toUnicode(original, *args):
|
||||
except:
|
||||
try:
|
||||
detected = detect(original)
|
||||
if detected.get('encoding') == 'utf-8':
|
||||
return original.decode('utf-8')
|
||||
try:
|
||||
if detected.get('confidence') > 0.8:
|
||||
return original.decode(detected.get('encoding'))
|
||||
except:
|
||||
pass
|
||||
|
||||
return ek(original, *args)
|
||||
except:
|
||||
raise
|
||||
except:
|
||||
log.error('Unable to decode value "%s..." : %s ', (repr(original)[:20], traceback.format_exc()))
|
||||
ascii_text = str(original).encode('string_escape')
|
||||
return toUnicode(ascii_text)
|
||||
return 'ERROR DECODING STRING'
|
||||
|
||||
|
||||
def ss(original, *args):
|
||||
@@ -92,7 +95,7 @@ def ek(original, *args):
|
||||
if isinstance(original, (str, unicode)):
|
||||
try:
|
||||
from couchpotato.environment import Env
|
||||
return original.decode(Env.get('encoding'))
|
||||
return original.decode(Env.get('encoding'), 'ignore')
|
||||
except UnicodeDecodeError:
|
||||
raise
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from couchpotato import CPLog
|
||||
from couchpotato import CPLog, md5
|
||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import getExt
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
import six
|
||||
|
||||
@@ -92,7 +93,15 @@ class MediaBase(Plugin):
|
||||
if not isinstance(image, (str, unicode)):
|
||||
continue
|
||||
|
||||
if file_type not in existing_files or len(existing_files.get(file_type, [])) == 0:
|
||||
# Check if it has top image
|
||||
filename = '%s.%s' % (md5(image), getExt(image))
|
||||
existing = existing_files.get(file_type, [])
|
||||
has_latest = False
|
||||
for x in existing:
|
||||
if filename in x:
|
||||
has_latest = True
|
||||
|
||||
if not has_latest or file_type not in existing_files or len(existing_files.get(file_type, [])) == 0:
|
||||
file_path = fireEvent('file.download', url = image, single = True)
|
||||
if file_path:
|
||||
existing_files[file_type] = [toUnicode(file_path)]
|
||||
|
||||
@@ -94,6 +94,8 @@ class Provider(Plugin):
|
||||
try:
|
||||
data = XMLTree.fromstring(ss(data))
|
||||
return self.getElements(data, item_path)
|
||||
except XMLTree.ParseError:
|
||||
log.error('Invalid XML returned, check "%s" manually for issues', url)
|
||||
except:
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
|
||||
@@ -68,8 +68,12 @@ class Base(NZBProvider, RSS):
|
||||
if not date:
|
||||
date = self.getTextElement(nzb, 'pubDate')
|
||||
|
||||
nzb_id = self.getTextElement(nzb, 'guid').split('/')[-1:].pop()
|
||||
name = self.getTextElement(nzb, 'title')
|
||||
detail_url = self.getTextElement(nzb, 'guid')
|
||||
nzb_id = detail_url.split('/')[-1:].pop()
|
||||
|
||||
if '://' not in detail_url:
|
||||
detail_url = (cleanHost(host['host']) + self.urls['detail']) % tryUrlencode(nzb_id)
|
||||
|
||||
if not name:
|
||||
continue
|
||||
@@ -103,7 +107,7 @@ class Base(NZBProvider, RSS):
|
||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
||||
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
|
||||
'url': ((self.getUrl(host['host']) + self.urls['download']) % tryUrlencode(nzb_id)) + self.getApiExt(host),
|
||||
'detail_url': (cleanHost(host['host']) + self.urls['detail']) % tryUrlencode(nzb_id),
|
||||
'detail_url': detail_url,
|
||||
'content': self.getTextElement(nzb, 'description'),
|
||||
'description': description,
|
||||
'score': host['extra_score'],
|
||||
@@ -183,7 +187,7 @@ class Base(NZBProvider, RSS):
|
||||
return 'try_next'
|
||||
|
||||
try:
|
||||
data = self.urlopen(url, show_error = False)
|
||||
data = self.urlopen(url, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
|
||||
self.limits_reached[host] = False
|
||||
return data
|
||||
except HTTPError as e:
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
from urlparse import urlparse, parse_qs
|
||||
import time
|
||||
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media._base.providers.nzb.base import NZBProvider
|
||||
from dateutil.parser import parse
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -16,27 +12,19 @@ log = CPLog(__name__)
|
||||
class Base(NZBProvider, RSS):
|
||||
|
||||
urls = {
|
||||
'search': 'https://rss.omgwtfnzbs.org/rss-search.php?%s',
|
||||
'detail_url': 'https://omgwtfnzbs.org/details.php?id=%s',
|
||||
'search': 'https://api.omgwtfnzbs.org/json/?%s',
|
||||
}
|
||||
|
||||
http_time_between_calls = 1 # Seconds
|
||||
|
||||
cat_ids = [
|
||||
([15], ['dvdrip']),
|
||||
([15], ['dvdrip', 'scr', 'r5', 'tc', 'ts', 'cam']),
|
||||
([15, 16], ['brrip']),
|
||||
([16], ['720p', '1080p', 'bd50']),
|
||||
([17], ['dvdr']),
|
||||
]
|
||||
cat_backup_id = 'movie'
|
||||
|
||||
def search(self, movie, quality):
|
||||
|
||||
if quality['identifier'] in fireEvent('quality.pre_releases', single = True):
|
||||
return []
|
||||
|
||||
return super(Base, self).search(movie, quality)
|
||||
|
||||
def _searchOnTitle(self, title, movie, quality, results):
|
||||
|
||||
q = '%s %s' % (title, movie['info']['year'])
|
||||
@@ -47,22 +35,20 @@ class Base(NZBProvider, RSS):
|
||||
'api': self.conf('api_key', default = ''),
|
||||
})
|
||||
|
||||
nzbs = self.getRSSData(self.urls['search'] % params)
|
||||
nzbs = self.getJsonData(self.urls['search'] % params)
|
||||
|
||||
for nzb in nzbs:
|
||||
if isinstance(nzbs, list):
|
||||
for nzb in nzbs:
|
||||
|
||||
enclosure = self.getElement(nzb, 'enclosure').attrib
|
||||
nzb_id = parse_qs(urlparse(self.getTextElement(nzb, 'link')).query).get('id')[0]
|
||||
|
||||
results.append({
|
||||
'id': nzb_id,
|
||||
'name': toUnicode(self.getTextElement(nzb, 'title')),
|
||||
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, 'pubDate')).timetuple()))),
|
||||
'size': tryInt(enclosure['length']) / 1024 / 1024,
|
||||
'url': enclosure['url'],
|
||||
'detail_url': self.urls['detail_url'] % nzb_id,
|
||||
'description': self.getTextElement(nzb, 'description')
|
||||
})
|
||||
results.append({
|
||||
'id': nzb.get('nzbid'),
|
||||
'name': toUnicode(nzb.get('release')),
|
||||
'age': self.calculateAge(tryInt(nzb.get('usenetage'))),
|
||||
'size': tryInt(nzb.get('sizebytes')) / 1024 / 1024,
|
||||
'url': nzb.get('getnzb'),
|
||||
'detail_url': nzb.get('details'),
|
||||
'description': nzb.get('weblink')
|
||||
})
|
||||
|
||||
|
||||
config = [{
|
||||
|
||||
130
couchpotato/core/media/_base/providers/torrent/hdaccess.py
Normal file
130
couchpotato/core/media/_base/providers/torrent/hdaccess.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from couchpotato.core.helpers.variable import tryInt, getIdentifier
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Base(TorrentProvider):
|
||||
|
||||
urls = {
|
||||
'test': 'https://hdaccess.net/',
|
||||
'detail': 'https://hdaccess.net/details.php?id=%s',
|
||||
'search': 'https://hdaccess.net/searchapi.php?apikey=%s&username=%s&imdbid=%s&internal=%s',
|
||||
'download': 'https://hdaccess.net/grab.php?torrent=%s&apikey=%s',
|
||||
}
|
||||
|
||||
http_time_between_calls = 1 # Seconds
|
||||
|
||||
def _search(self, movie, quality, results):
|
||||
data = self.getJsonData(self.urls['search'] % (self.conf('apikey'), self.conf('username'), getIdentifier(movie), self.conf('internal_only')))
|
||||
|
||||
if data:
|
||||
try:
|
||||
#for result in data[]:
|
||||
for key, result in data.iteritems():
|
||||
if tryInt(result['total_results']) == 0:
|
||||
return
|
||||
torrentscore = self.conf('extra_score')
|
||||
releasegroup = result['releasegroup']
|
||||
resolution = result['resolution']
|
||||
encoding = result['encoding']
|
||||
freeleech = tryInt(result['freeleech'])
|
||||
seeders = tryInt(result['seeders'])
|
||||
torrent_desc = '/ %s / %s / %s / %s seeders' % (releasegroup, resolution, encoding, seeders)
|
||||
|
||||
if freeleech > 0 and self.conf('prefer_internal'):
|
||||
torrent_desc += '/ Internal'
|
||||
torrentscore += 200
|
||||
|
||||
if seeders == 0:
|
||||
torrentscore = 0
|
||||
|
||||
name = result['release_name']
|
||||
year = tryInt(result['year'])
|
||||
|
||||
results.append({
|
||||
'id': tryInt(result['torrentid']),
|
||||
'name': re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) %s' % (name, year, torrent_desc)),
|
||||
'url': self.urls['download'] % (result['torrentid'], self.conf('apikey')),
|
||||
'detail_url': self.urls['detail'] % result['torrentid'],
|
||||
'size': tryInt(result['size']),
|
||||
'seeders': tryInt(result['seeders']),
|
||||
'leechers': tryInt(result['leechers']),
|
||||
'age': tryInt(result['age']),
|
||||
'score': torrentscore
|
||||
})
|
||||
except:
|
||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||
config = [{
|
||||
'name': 'hdaccess',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'searcher',
|
||||
'list': 'torrent_providers',
|
||||
'name': 'HDAccess',
|
||||
'wizard': True,
|
||||
'description': '<a href="https://hdaccess.net">HDAccess</a>',
|
||||
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAADuUlEQVQ4yz3T209bdQAH8O/vnNNzWno5FIpAKZdSLi23gWMDtumWuSXOyzJj9M1kyIOPS1xiYuKe9GUPezZZnGIiMTqTxS1bdIuYkG2MWKBAKYVszOgKFkrbA+259HfO+fli/PwPHzI+Pg5CCEAI2VcUlEsl1tHdU7P5bGOkWChEaaUCwvHpmkD93POn6bwgCMQGAMYYYwyCruuQnE7SPzjIstvb8l+bm5fXkokJSmlQEkUQAIpSRH5vd0tyum7I/sA1Z5VH2ctmiGWZjHw4McE1NAZtQ9fD25kXt1VN7es7dNjuGRjiJFeVpWo6slsZPhF/Ys/PPeIs2056ff7zIOS5rpU5/viJEwwEnu3Mi18dojjw0aWP6amz57h9RSE/35zinq2nuGjvIQwOj7K2SKeZWkk0auXSSZ+/ZopSy+CbW1pQKpWu6Jr2/qVPPqWRjm6HWi6Tm999g3RyGbndLCqGgVBrO3F7fHykK0YX47NNtGLYlBq/c+H2iD+3k704dHQUDcFmQVXLyP6zhfTqCl45fQYjx17FemoJunoAk1bQFGoVhkdPwNC0ix2dMT+3llodM02rKdo7gN3dHAEhuH/vNgDg3Pl3cPaNt2GZJpYX5lBbFwClBukfGobL5WrayW6NccVCISY4HIQxYts2Q3J5CXOPHuLlo6NoCoXQ2hbG0JFRpJYWcVDIQ5ZlyL5qW5b9hNlWjKsYBgzDgKppMCoGHty7A0orOHbyNNweL+obGnDm9TdhWSYS8Vn4a2shOZ0QJRGSKIHjeGGtWNhjqqpyG+k04k8eozPai9ZwByavf4kfpyZxZGwMfYOHsbwQx34hB5dL4syKweRq/xpXHwzNapqWSSYWMDszzYqFPEaOn4KiKJiZfoCZ6d8Am+GtC++iXCpjaf4P9vefT8HzfKarp3eWRKMxCILwuWXSz977YIK2RTodDoGH1+OG1+tDlbsKkuiAJEngeWBjNUUnv7rucIiOLyzTvMKJTgnVtbVXLctK3L31g+NAUajL5bEptaDpOnTdgGkzVHl9drms0ju3fnJIkphoaQtfbQiFwAcCAY5wnCE5Xff3i8XX4o9nGksH+8zl9hAGZlWMCivkc9z0L3fZ999+LTCGZKi55YJTFHfye3sc6e/vB88LpK6+iWlqSS4WcpcNXZtwOp3B6mo/REmCSSkEgd+qq3vpRkt75Fp9Y1BZWZwnhq4zEovF/u/MATAti4U7umvyu9kR27aikihC9vvTnV2xufVUMu/2uIksy/9tZvgX49fLmAMx3bsAAAAASUVORK5CYII=',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'type': 'enabler',
|
||||
'default': False,
|
||||
},
|
||||
{
|
||||
'name': 'username',
|
||||
'default': '',
|
||||
'description': 'Enter your site username.',
|
||||
},
|
||||
{
|
||||
'name': 'apikey',
|
||||
'default': '',
|
||||
'label': 'API Key',
|
||||
'description': 'Enter your site api key. This can be find on <a href="https://hdaccess.net/usercp.php?action=security">Profile Security</a>',
|
||||
},
|
||||
{
|
||||
'name': 'seed_ratio',
|
||||
'label': 'Seed ratio',
|
||||
'type': 'float',
|
||||
'default': 0,
|
||||
'description': 'Will not be (re)moved until this seed ratio is met. HDAccess minimum is 1:1.',
|
||||
},
|
||||
{
|
||||
'name': 'seed_time',
|
||||
'label': 'Seed time',
|
||||
'type': 'int',
|
||||
'default': 0,
|
||||
'description': 'Will not be (re)moved until this seed time (in hours) is met. HDAccess minimum is 48 hours.',
|
||||
},
|
||||
{
|
||||
'name': 'prefer_internal',
|
||||
'advanced': True,
|
||||
'type': 'bool',
|
||||
'default': 1,
|
||||
'description': 'Favors internal releases over non-internal releases.',
|
||||
},
|
||||
{
|
||||
'name': 'internal_only',
|
||||
'advanced': True,
|
||||
'label': 'Internal Only',
|
||||
'type': 'bool',
|
||||
'default': False,
|
||||
'description': 'Only download releases marked as HDAccess internal',
|
||||
},
|
||||
{
|
||||
'name': 'extra_score',
|
||||
'advanced': True,
|
||||
'label': 'Extra Score',
|
||||
'type': 'int',
|
||||
'default': 0,
|
||||
'description': 'Starting score for each release found via this provider.',
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
@@ -29,6 +29,9 @@ class Base(TorrentProvider):
|
||||
}
|
||||
post_data.update(params)
|
||||
|
||||
if self.conf('internal_only'):
|
||||
post_data.update({'origin': [1]})
|
||||
|
||||
try:
|
||||
result = self.getJsonData(self.urls['api'], data = json.dumps(post_data))
|
||||
|
||||
@@ -110,6 +113,14 @@ config = [{
|
||||
'default': 0,
|
||||
'description': 'Starting score for each release found via this provider.',
|
||||
},
|
||||
{
|
||||
'name': 'internal_only',
|
||||
'advanced': True,
|
||||
'label': 'Internal Only',
|
||||
'type': 'bool',
|
||||
'default': False,
|
||||
'description': 'Only download releases marked as HDBits internal'
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -14,11 +14,11 @@ log = CPLog(__name__)
|
||||
class Base(TorrentProvider):
|
||||
|
||||
urls = {
|
||||
'test': 'https://www.iptorrents.com/',
|
||||
'base_url': 'https://www.iptorrents.com',
|
||||
'login': 'https://www.iptorrents.com/torrents/',
|
||||
'login_check': 'https://www.iptorrents.com/inbox.php',
|
||||
'search': 'https://www.iptorrents.com/torrents/?%s%%s&q=%s&qf=ti&p=%%d',
|
||||
'test': 'https://iptorrents.eu/',
|
||||
'base_url': 'https://iptorrents.eu',
|
||||
'login': 'https://iptorrents.eu/torrents/',
|
||||
'login_check': 'https://iptorrents.eu/inbox.php',
|
||||
'search': 'https://iptorrents.eu/torrents/?%s%%s&q=%s&qf=ti&p=%%d',
|
||||
}
|
||||
|
||||
http_time_between_calls = 1 # Seconds
|
||||
@@ -120,7 +120,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'list': 'torrent_providers',
|
||||
'name': 'IPTorrents',
|
||||
'description': '<a href="http://www.iptorrents.com">IPTorrents</a>',
|
||||
'description': '<a href="https://iptorrents.eu">IPTorrents</a>',
|
||||
'wizard': True,
|
||||
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABRklEQVR42qWQO0vDUBiG8zeKY3EqQUtNO7g0J6ZJ1+ifKIIFQXAqDYKCyaaYxM3udrZLHdRFhXrZ6liCW6mubfk874EESgqaeOCF7/Y8hEh41aq6yZi2nyZgBGya9XKtZs4No05pAkZV2YbEmyMMsoSxLQeC46wCTdPPY4HruPQyGIhF97qLWsS78Miydn4XdK46NJ9OsQAYBzMIMf8MQ9wtCnTdWCaIDx/u7uljOIQEe0hiIWPamSTLay3+RxOCSPI9+RJAo7Er9r2bnqjBFAqyK+VyK4f5/Cr5ni8OFKVCz49PFI5GdNvvU7ttE1M1zMU+8AMqFksEhrMnQsBDzqmDAwzx2ehRLwT7yyCI+vSC99c3mozH1NxrJgWWtR1BOECfEJSVCm6WCzJGCA7+IWhBsM4zywDPwEp4vCjx2DzBH2ODAfsDb33Ps6dQwJgAAAAASUVORK5CYII=',
|
||||
'options': [
|
||||
|
||||
@@ -42,6 +42,7 @@ class Base(TorrentProvider):
|
||||
|
||||
link = result.find('td', attrs = {'class': 'ttr_name'}).find('a')
|
||||
url = result.find('td', attrs = {'class': 'td_dl'}).find('a')
|
||||
seeders = result.find('td', attrs = {'class': 'ttr_seeders'}).find('a')
|
||||
leechers = result.find('td', attrs = {'class': 'ttr_leechers'}).find('a')
|
||||
torrent_id = link['href'].replace('details?id=', '')
|
||||
|
||||
@@ -51,7 +52,7 @@ class Base(TorrentProvider):
|
||||
'url': self.urls['download'] % url['href'],
|
||||
'detail_url': self.urls['detail'] % torrent_id,
|
||||
'size': self.parseSize(result.find('td', attrs = {'class': 'ttr_size'}).contents[0]),
|
||||
'seeders': tryInt(result.find('td', attrs = {'class': 'ttr_seeders'}).find('a').string),
|
||||
'seeders': tryInt(seeders.string) if seeders else 0,
|
||||
'leechers': tryInt(leechers.string) if leechers else 0,
|
||||
'get_more_info': self.getMoreInfo,
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import traceback
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
||||
@@ -56,11 +56,12 @@ class Base(TorrentProvider):
|
||||
|
||||
full_id = link['href'].replace('details.php?id=', '')
|
||||
torrent_id = full_id[:6]
|
||||
name = toUnicode(link.get('title', link.contents[0]).encode('ISO-8859-1')).strip()
|
||||
|
||||
results.append({
|
||||
'id': torrent_id,
|
||||
'name': link.contents[0],
|
||||
'url': self.urls['download'] % (torrent_id, link.contents[0]),
|
||||
'name': name,
|
||||
'url': self.urls['download'] % (torrent_id, name),
|
||||
'detail_url': self.urls['detail'] % torrent_id,
|
||||
'size': self.parseSize(cells[6].contents[0] + cells[6].contents[2]),
|
||||
'seeders': tryInt(cells[8].find('span').contents[0]),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import re
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
||||
@@ -8,12 +9,12 @@ log = CPLog(__name__)
|
||||
class Base(TorrentProvider):
|
||||
|
||||
urls = {
|
||||
'test': 'http://www.td.af/',
|
||||
'login': 'http://www.td.af/torrents/',
|
||||
'login_check': 'http://www.torrentday.com/userdetails.php',
|
||||
'detail': 'http://www.td.af/details.php?id=%s',
|
||||
'search': 'http://www.td.af/V3/API/API.php',
|
||||
'download': 'http://www.td.af/download.php/%s/%s',
|
||||
'test': 'https://torrentday.eu/',
|
||||
'login': 'https://torrentday.eu/torrents/',
|
||||
'login_check': 'https://torrentday.eu/userdetails.php',
|
||||
'detail': 'https://torrentday.eu/details.php?id=%s',
|
||||
'search': 'https://torrentday.eu/V3/API/API.php',
|
||||
'download': 'https://torrentday.eu/download.php/%s/%s',
|
||||
}
|
||||
|
||||
http_time_between_calls = 1 # Seconds
|
||||
@@ -55,6 +56,10 @@ class Base(TorrentProvider):
|
||||
}
|
||||
|
||||
def loginSuccess(self, output):
|
||||
often = re.search('You tried too often, please wait .*</div>', output)
|
||||
if often:
|
||||
raise Exception(often.group(0)[:-6].strip())
|
||||
|
||||
return 'Password not correct' not in output
|
||||
|
||||
def loginCheckSuccess(self, output):
|
||||
@@ -68,7 +73,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'list': 'torrent_providers',
|
||||
'name': 'TorrentDay',
|
||||
'description': '<a href="http://www.td.af/">TorrentDay</a>',
|
||||
'description': '<a href="https://torrentday.eu/">TorrentDay</a>',
|
||||
'wizard': True,
|
||||
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC5ElEQVQ4y12TXUgUURTH//fO7Di7foeQJH6gEEEIZZllVohfSG/6UA+RSFAQQj74VA8+Bj30lmAlRVSEvZRfhNhaka5ZUG1paKaW39tq5O6Ou+PM3M4o6m6X+XPPzD3zm/+dcy574r515WfIW8CZBM4YAA5Gc/aQC3yd7oXYEONcsISE5dTDh91HS0t7FEWhBUAeN9ynV/d9qJAgE4AECURAcVsGlCCnly26LMA0IQwTa52dje3d3e3hcPi8qqrrMjcVYI3EHCQZlkFOHBwR2QHh2ASAAIJxWGAQEDxjePhs3527XjJwnb37OHBq0T+Tyyjh+9KnEzNJ7nouc1Q/3A3HGsOvnJy+PSUlj81w2Lny9WuJ6+3AmTjD4HOcrdR2dWXLRQePvyaSLfQOPMPC8mC9iHCsOxSyzJCelzdSXlNzD5ujpb25Wbfc/XXJemTXF4+nnCNq+AMLe50uFfEJTiw4GXSFtiHL0SnIq66+p0kSArqO+eH3RdsAv9+f5vW7L7GICq6rmM8XBCAXlBw90rOyxibn5yzfkg/L09M52/jxqdESaIrBXHYZZbB1GX8cEpySxKIB8S5XcOnvqpli1zuwmrTtoLjw5LOK/eeuWsE4JH5IRPaPZKiKigmPp+5pa+u1aEjIMhEgrRkmi9mgxGUhM7LNJSzOzsE3+cOeExovXOjdytE0LV4zqNZUtV0uZzAGoGkhDH/2YHZiErmv4uyWQnZZWc+hoqL3WzlTExN5hhA8IEwkZWZOxwB++30YG/9GkYCPvqAaHAW5uWPROW86OmqCprUR7z1yZDAGQNuCvkoB/baIKUBWMTYymv+gra3eJNvjXu+B562tFyXqTJ6YuHK8rKwvBmC3vR7cOCPQLWFz8LnfXWUrJo9U19BwMyUlJRjTSMJ2ENxUiGxq9KXQfwqYlnWstvbR5aamG9g0uzM8Q4OFt++3NNixQ2NgYmeN03FOTUv7XVpV9aKisvLl1vN/WVhNc/Fi1NEAAAAASUVORK5CYII=',
|
||||
'options': [
|
||||
|
||||
126
couchpotato/core/media/_base/providers/torrent/torrentleech.py
Normal file
126
couchpotato/core/media/_base/providers/torrent/torrentleech.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import traceback
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
||||
import six
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Base(TorrentProvider):
|
||||
|
||||
urls = {
|
||||
'test': 'https://www.torrentleech.org/',
|
||||
'login': 'https://www.torrentleech.org/user/account/login/',
|
||||
'login_check': 'https://torrentleech.org/user/messages',
|
||||
'detail': 'https://www.torrentleech.org/torrent/%s',
|
||||
'search': 'https://www.torrentleech.org/torrents/browse/index/query/%s/categories/%s',
|
||||
'download': 'https://www.torrentleech.org%s',
|
||||
}
|
||||
|
||||
http_time_between_calls = 1 # Seconds
|
||||
cat_backup_id = None
|
||||
|
||||
def _searchOnTitle(self, title, media, quality, results):
|
||||
|
||||
url = self.urls['search'] % self.buildUrl(title, media, quality)
|
||||
|
||||
data = self.getHTMLData(url)
|
||||
|
||||
if data:
|
||||
html = BeautifulSoup(data)
|
||||
|
||||
try:
|
||||
result_table = html.find('table', attrs = {'id': 'torrenttable'})
|
||||
if not result_table:
|
||||
return
|
||||
|
||||
entries = result_table.find_all('tr')
|
||||
|
||||
for result in entries[1:]:
|
||||
|
||||
link = result.find('td', attrs = {'class': 'name'}).find('a')
|
||||
url = result.find('td', attrs = {'class': 'quickdownload'}).find('a')
|
||||
details = result.find('td', attrs = {'class': 'name'}).find('a')
|
||||
|
||||
results.append({
|
||||
'id': link['href'].replace('/torrent/', ''),
|
||||
'name': six.text_type(link.string),
|
||||
'url': self.urls['download'] % url['href'],
|
||||
'detail_url': self.urls['download'] % details['href'],
|
||||
'size': self.parseSize(result.find_all('td')[4].string),
|
||||
'seeders': tryInt(result.find('td', attrs = {'class': 'seeders'}).string),
|
||||
'leechers': tryInt(result.find('td', attrs = {'class': 'leechers'}).string),
|
||||
})
|
||||
|
||||
except:
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
def getLoginParams(self):
|
||||
return {
|
||||
'username': self.conf('username'),
|
||||
'password': self.conf('password'),
|
||||
'remember_me': 'on',
|
||||
'login': 'submit',
|
||||
}
|
||||
|
||||
def loginSuccess(self, output):
|
||||
return '/user/account/logout' in output.lower() or 'welcome back' in output.lower()
|
||||
|
||||
loginCheckSuccess = loginSuccess
|
||||
|
||||
|
||||
config = [{
|
||||
'name': 'torrentleech',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'searcher',
|
||||
'list': 'torrent_providers',
|
||||
'name': 'TorrentLeech',
|
||||
'description': '<a href="http://torrentleech.org">TorrentLeech</a>',
|
||||
'wizard': True,
|
||||
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACHUlEQVR4AZVSO48SYRSdGTCBEMKzILLAWiybkKAGMZRUUJEoDZX7B9zsbuQPYEEjNLTQkYgJDwsoSaxspEBsCITXjjNAIKi8AkzceXgmbHQ1NJ5iMufmO9/9zrmXlCSJ+B8o75J8Pp/NZj0eTzweBy0Wi4PBYD6f12o1r9ebTCZx+22HcrnMsuxms7m6urTZ7LPZDMVYLBZ8ZV3yo8aq9Pq0wzCMTqe77dDv9y8uLyAWBH6xWOyL0K/56fcb+rrPgPZ6PZfLRe1fsl6vCUmGKIqoqNXqdDr9Dbjps9znUV0uTqdTjuPkDoVCIfcuJ4gizjMMm8u9vW+1nr04czqdK56c37CbKY9j2+1WEARZ0Gq1RFHAz2q1qlQqXxoN69HRcDjUarW8ZD6QUigUOnY8uKYH8N1sNkul9yiGw+F6vS4Rxn8EsodEIqHRaOSnq9T7ajQazWQycEIR1AEBYDabSZJyHDucJyegwWBQr9ebTCaKvHd4cCQANUU9evwQ1Ofz4YvUKUI43GE8HouSiFiNRhOowWBIpVLyHITJkuW3PwgAEf3pgIwxF5r+OplMEsk3CPT5szCMnY7EwUdhwUh/CXiej0Qi3idPz89fdrpdbsfBzH7S3Q9K5pP4c0sAKpVKoVAQGO1ut+t0OoFAQHkH2Da/3/+but3uarWK0ZMQoNdyucRutdttmqZxMTzY7XaYxsrgtUjEZrNhkSwWyy/0NCatZumrNQAAAABJRU5ErkJggg==',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'type': 'enabler',
|
||||
'default': False,
|
||||
},
|
||||
{
|
||||
'name': 'username',
|
||||
'default': '',
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'default': '',
|
||||
'type': 'password',
|
||||
},
|
||||
{
|
||||
'name': 'seed_ratio',
|
||||
'label': 'Seed ratio',
|
||||
'type': 'float',
|
||||
'default': 1,
|
||||
'description': 'Will not be (re)moved until this seed ratio is met.',
|
||||
},
|
||||
{
|
||||
'name': 'seed_time',
|
||||
'label': 'Seed time',
|
||||
'type': 'int',
|
||||
'default': 40,
|
||||
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
|
||||
},
|
||||
{
|
||||
'name': 'extra_score',
|
||||
'advanced': True,
|
||||
'label': 'Extra Score',
|
||||
'type': 'int',
|
||||
'default': 20,
|
||||
'description': 'Starting score for each release found via this provider.',
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
@@ -13,12 +13,12 @@ log = CPLog(__name__)
|
||||
class Base(TorrentProvider):
|
||||
|
||||
urls = {
|
||||
'test': 'http://torrentshack.eu/',
|
||||
'login': 'http://torrentshack.eu/login.php',
|
||||
'login_check': 'http://torrentshack.eu/inbox.php',
|
||||
'detail': 'http://torrentshack.eu/torrent/%s',
|
||||
'search': 'http://torrentshack.eu/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1',
|
||||
'download': 'http://torrentshack.eu/%s',
|
||||
'test': 'https://theshack.us.to/',
|
||||
'login': 'https://theshack.us.to/login.php',
|
||||
'login_check': 'https://theshack.us.to/inbox.php',
|
||||
'detail': 'https://theshack.us.to/torrent/%s',
|
||||
'search': 'https://theshack.us.to/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1',
|
||||
'download': 'https://theshack.us.to/%s',
|
||||
}
|
||||
|
||||
http_time_between_calls = 1 # Seconds
|
||||
@@ -42,6 +42,7 @@ class Base(TorrentProvider):
|
||||
|
||||
link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent
|
||||
url = result.find('td', attrs = {'class': 'torrent_td'}).find('a')
|
||||
size = result.find('td', attrs = {'class': 'size'}).contents[0].strip('\n ')
|
||||
tds = result.find_all('td')
|
||||
|
||||
results.append({
|
||||
@@ -49,7 +50,7 @@ class Base(TorrentProvider):
|
||||
'name': six.text_type(link.span.string).translate({ord(six.u('\xad')): None}),
|
||||
'url': self.urls['download'] % url['href'],
|
||||
'detail_url': self.urls['download'] % link['href'],
|
||||
'size': self.parseSize(result.find_all('td')[5].string),
|
||||
'size': self.parseSize(size),
|
||||
'seeders': tryInt(tds[len(tds)-2].string),
|
||||
'leechers': tryInt(tds[len(tds)-1].string),
|
||||
})
|
||||
|
||||
@@ -22,12 +22,12 @@ class Base(TorrentMagnetProvider, RSS):
|
||||
|
||||
http_time_between_calls = 0
|
||||
|
||||
def _search(self, media, quality, results):
|
||||
def _searchOnTitle(self, title, media, quality, results):
|
||||
|
||||
search_url = self.urls['verified_search'] if self.conf('verified_only') else self.urls['search']
|
||||
|
||||
# Create search parameters
|
||||
search_params = self.buildUrl(media)
|
||||
search_params = self.buildUrl(title, media, quality)
|
||||
|
||||
smin = quality.get('size_min')
|
||||
smax = quality.get('size_max')
|
||||
|
||||
@@ -2,28 +2,25 @@ import traceback
|
||||
|
||||
from couchpotato.core.helpers.variable import tryInt, getIdentifier
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media._base.providers.torrent.base import TorrentMagnetProvider
|
||||
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Base(TorrentMagnetProvider):
|
||||
class Base(TorrentProvider):
|
||||
|
||||
urls = {
|
||||
'test': '%s/api',
|
||||
'search': '%s/api/list.json?keywords=%s&quality=%s',
|
||||
'detail': '%s/api/movie.json?id=%s'
|
||||
'test': '%s/api/v2',
|
||||
'search': '%s/api/v2/list_movies.json?limit=50&query_term=%s'
|
||||
}
|
||||
|
||||
http_time_between_calls = 1 # seconds
|
||||
|
||||
proxy_list = [
|
||||
'http://yify.unlocktorrent.com',
|
||||
'http://yify-torrents.com.come.in',
|
||||
'http://yts.re',
|
||||
'http://yts.im'
|
||||
'http://yify-torrents.im',
|
||||
'https://yts.re',
|
||||
'https://yts.wf',
|
||||
'https://yts.im',
|
||||
]
|
||||
|
||||
def search(self, movie, quality):
|
||||
@@ -39,28 +36,31 @@ class Base(TorrentMagnetProvider):
|
||||
if not domain:
|
||||
return
|
||||
|
||||
search_url = self.urls['search'] % (domain, getIdentifier(movie), quality['identifier'])
|
||||
search_url = self.urls['search'] % (domain, getIdentifier(movie))
|
||||
|
||||
data = self.getJsonData(search_url)
|
||||
data = data.get('data')
|
||||
|
||||
if data and data.get('MovieList'):
|
||||
if isinstance(data, dict) and data.get('movies'):
|
||||
try:
|
||||
for result in data.get('MovieList'):
|
||||
for result in data.get('movies'):
|
||||
|
||||
if result['Quality'] and result['Quality'] not in result['MovieTitle']:
|
||||
title = result['MovieTitle'] + ' BrRip ' + result['Quality']
|
||||
else:
|
||||
title = result['MovieTitle'] + ' BrRip'
|
||||
for release in result.get('torrents', []):
|
||||
|
||||
results.append({
|
||||
'id': result['MovieID'],
|
||||
'name': title,
|
||||
'url': result['TorrentMagnetUrl'],
|
||||
'detail_url': self.urls['detail'] % (domain, result['MovieID']),
|
||||
'size': self.parseSize(result['Size']),
|
||||
'seeders': tryInt(result['TorrentSeeds']),
|
||||
'leechers': tryInt(result['TorrentPeers']),
|
||||
})
|
||||
if release['quality'] and release['quality'] not in result['title_long']:
|
||||
title = result['title_long'] + ' BRRip ' + release['quality']
|
||||
else:
|
||||
title = result['title_long'] + ' BRRip'
|
||||
|
||||
results.append({
|
||||
'id': release['hash'],
|
||||
'name': title,
|
||||
'url': release['url'],
|
||||
'detail_url': result['url'],
|
||||
'size': self.parseSize(release['size']),
|
||||
'seeders': tryInt(release['seeds']),
|
||||
'leechers': tryInt(release['peers']),
|
||||
})
|
||||
|
||||
except:
|
||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
.search_form {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
position: absolute;
|
||||
right: 105px;
|
||||
top: 0;
|
||||
text-align: right;
|
||||
height: 100%;
|
||||
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
||||
z-index: 20;
|
||||
border: 0 solid transparent;
|
||||
border-bottom-width: 4px;
|
||||
}
|
||||
.search_form:hover {
|
||||
border-color: #047792;
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
.search_form {
|
||||
right: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.search_form.focused,
|
||||
.search_form.shown {
|
||||
border-color: #04bce6;
|
||||
}
|
||||
|
||||
.search_form .input {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 45px;
|
||||
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
||||
}
|
||||
|
||||
.search_form.focused .input,
|
||||
.search_form.shown .input {
|
||||
width: 380px;
|
||||
background: #4e5969;
|
||||
}
|
||||
|
||||
.search_form .input input {
|
||||
border-radius: 0;
|
||||
display: block;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: #FFF;
|
||||
font-size: 25px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
padding: 0 40px 0 10px;
|
||||
transition: all .4s ease-in-out .2s;
|
||||
}
|
||||
.search_form.focused .input input,
|
||||
.search_form.shown .input input {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.search_form input::-ms-clear {
|
||||
width : 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
.search_form .input input {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.search_form.focused .input,
|
||||
.search_form.shown .input {
|
||||
width: 277px;
|
||||
}
|
||||
}
|
||||
|
||||
.search_form .input a {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 44px;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
line-height: 66px;
|
||||
font-size: 15px;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.search_form .input a:after {
|
||||
content: "\e03e";
|
||||
}
|
||||
|
||||
.search_form.shown.filled .input a:after {
|
||||
content: "\e04e";
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
.search_form .input a {
|
||||
line-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.search_form .results_container {
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
background: #5c697b;
|
||||
margin: 4px 0 0;
|
||||
width: 470px;
|
||||
min-height: 50px;
|
||||
box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55);
|
||||
display: none;
|
||||
}
|
||||
@media all and (max-width: 480px) {
|
||||
.search_form .results_container {
|
||||
width: 320px;
|
||||
}
|
||||
}
|
||||
.search_form.focused.filled .results_container,
|
||||
.search_form.shown.filled .results_container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search_form .results {
|
||||
max-height: 570px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.media_result {
|
||||
overflow: hidden;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.media_result .options {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
right: 0;
|
||||
padding: 13px;
|
||||
border: 1px solid transparent;
|
||||
border-width: 1px 0;
|
||||
border-radius: 0;
|
||||
box-shadow: inset 0 1px 8px rgba(0,0,0,0.25);
|
||||
}
|
||||
.media_result .options > .in_library_wanted {
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.media_result .options > div {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.media_result .options .thumbnail {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.media_result .options select {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.media_result .options select[name=title] { width: 170px; }
|
||||
.media_result .options select[name=profile] { width: 90px; }
|
||||
.media_result .options select[name=category] { width: 80px; }
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
|
||||
.media_result .options select[name=title] { width: 90px; }
|
||||
.media_result .options select[name=profile] { width: 50px; }
|
||||
.media_result .options select[name=category] { width: 50px; }
|
||||
|
||||
}
|
||||
|
||||
.media_result .options .button {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.media_result .options .message {
|
||||
height: 100%;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.media_result .data {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
right: 0;
|
||||
background: #5c697b;
|
||||
cursor: pointer;
|
||||
border-top: 1px solid rgba(255,255,255, 0.08);
|
||||
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
||||
}
|
||||
.media_result .data.open {
|
||||
left: 100% !important;
|
||||
}
|
||||
|
||||
.media_result:last-child .data { border-bottom: 0; }
|
||||
|
||||
.media_result .in_wanted, .media_result .in_library {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 14px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.media_result .thumbnail {
|
||||
width: 34px;
|
||||
min-height: 100%;
|
||||
display: block;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.media_result .info {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 15px;
|
||||
right: 7px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.media_result .info h2 {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
font-size: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.search_form .info h2 {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.media_result .info h2 .title {
|
||||
display: block;
|
||||
margin: 0;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search_form .info h2 .title {
|
||||
position: absolute;
|
||||
width: 88%;
|
||||
}
|
||||
|
||||
.media_result .info h2 .year {
|
||||
padding: 0 5px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
width: 12%;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
|
||||
.search_form .info h2 .year {
|
||||
font-size: 12px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.search_form .mask,
|
||||
.media_result .mask {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
Block.Search = new Class({
|
||||
var BlockSearch = new Class({
|
||||
|
||||
Extends: BlockBase,
|
||||
|
||||
@@ -9,45 +9,46 @@ Block.Search = new Class({
|
||||
|
||||
var focus_timer = 0;
|
||||
self.el = new Element('div.search_form').adopt(
|
||||
new Element('div.input').adopt(
|
||||
self.input = new Element('input', {
|
||||
'placeholder': 'Search & add a new media',
|
||||
new Element('a.icon-search', {
|
||||
'events': {
|
||||
'click': self.clear.bind(self),
|
||||
'touchend': self.clear.bind(self)
|
||||
}
|
||||
}),
|
||||
new Element('div.wrapper').adopt(
|
||||
self.result_container = new Element('div.results_container', {
|
||||
'tween': {
|
||||
'duration': 200
|
||||
},
|
||||
'events': {
|
||||
'input': self.keyup.bind(self),
|
||||
'paste': self.keyup.bind(self),
|
||||
'change': self.keyup.bind(self),
|
||||
'keyup': self.keyup.bind(self),
|
||||
'focus': function(){
|
||||
if(focus_timer) clearTimeout(focus_timer);
|
||||
self.el.addClass('focused');
|
||||
if(this.get('value'))
|
||||
self.hideResults(false)
|
||||
},
|
||||
'blur': function(){
|
||||
focus_timer = (function(){
|
||||
self.el.removeClass('focused')
|
||||
}).delay(100);
|
||||
'mousewheel': function(e){
|
||||
(e).stopPropagation();
|
||||
}
|
||||
}
|
||||
}),
|
||||
new Element('a.icon2', {
|
||||
'events': {
|
||||
'click': self.clear.bind(self),
|
||||
'touchend': self.clear.bind(self)
|
||||
}
|
||||
})
|
||||
),
|
||||
self.result_container = new Element('div.results_container', {
|
||||
'tween': {
|
||||
'duration': 200
|
||||
},
|
||||
'events': {
|
||||
'mousewheel': function(e){
|
||||
(e).stopPropagation();
|
||||
}
|
||||
}
|
||||
}).adopt(
|
||||
self.results = new Element('div.results')
|
||||
}).grab(
|
||||
self.results = new Element('div.results')
|
||||
),
|
||||
new Element('div.input').grab(
|
||||
self.input = new Element('input', {
|
||||
'placeholder': 'Search & add a new media',
|
||||
'events': {
|
||||
'input': self.keyup.bind(self),
|
||||
'paste': self.keyup.bind(self),
|
||||
'change': self.keyup.bind(self),
|
||||
'keyup': self.keyup.bind(self),
|
||||
'focus': function(){
|
||||
if(focus_timer) clearTimeout(focus_timer);
|
||||
if(this.get('value'))
|
||||
self.hideResults(false);
|
||||
},
|
||||
'blur': function(){
|
||||
focus_timer = (function(){
|
||||
self.el.removeClass('focused');
|
||||
}).delay(100);
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -67,11 +68,12 @@ Block.Search = new Class({
|
||||
|
||||
self.last_q = '';
|
||||
self.input.set('value', '');
|
||||
self.el.addClass('focused');
|
||||
self.input.focus();
|
||||
|
||||
self.media = {};
|
||||
self.results.empty();
|
||||
self.el.removeClass('filled')
|
||||
self.el.removeClass('filled');
|
||||
|
||||
}
|
||||
},
|
||||
@@ -105,7 +107,7 @@ Block.Search = new Class({
|
||||
self.api_request.cancel();
|
||||
|
||||
if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer);
|
||||
self.autocomplete_timer = self.autocomplete.delay(300, self)
|
||||
self.autocomplete_timer = self.autocomplete.delay(300, self);
|
||||
}
|
||||
|
||||
},
|
||||
@@ -115,10 +117,10 @@ Block.Search = new Class({
|
||||
|
||||
if(!self.q()){
|
||||
self.hideResults(true);
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
self.list()
|
||||
self.list();
|
||||
},
|
||||
|
||||
list: function(){
|
||||
@@ -139,7 +141,7 @@ Block.Search = new Class({
|
||||
'q': q
|
||||
},
|
||||
'onComplete': self.fill.bind(self, q)
|
||||
})
|
||||
});
|
||||
}
|
||||
else
|
||||
self.fill(q, cache);
|
||||
@@ -158,30 +160,25 @@ Block.Search = new Class({
|
||||
|
||||
Object.each(json, function(media){
|
||||
if(typeOf(media) == 'array'){
|
||||
Object.each(media, function(m){
|
||||
Object.each(media, function(me){
|
||||
|
||||
var m = new Block.Search[m.type.capitalize() + 'Item'](m);
|
||||
var m = new window['BlockSearch' + me.type.capitalize() + 'Item'](me);
|
||||
$(m).inject(self.results);
|
||||
self.media[m.imdb || 'r-'+Math.floor(Math.random()*10000)] = m;
|
||||
|
||||
if(q == m.imdb)
|
||||
m.showOptions()
|
||||
m.showOptions();
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate result heights
|
||||
var w = window.getSize(),
|
||||
rc = self.result_container.getCoordinates();
|
||||
|
||||
self.results.setStyle('max-height', (w.y - rc.top - 50) + 'px');
|
||||
self.mask.fade('out')
|
||||
self.mask.fade('out');
|
||||
|
||||
},
|
||||
|
||||
loading: function(bool){
|
||||
this.el[bool ? 'addClass' : 'removeClass']('loading')
|
||||
this.el[bool ? 'addClass' : 'removeClass']('loading');
|
||||
},
|
||||
|
||||
q: function(){
|
||||
|
||||
242
couchpotato/core/media/_base/search/static/search.scss
Normal file
242
couchpotato/core/media/_base/search/static/search.scss
Normal file
@@ -0,0 +1,242 @@
|
||||
@import "couchpotato/static/style/mixins";
|
||||
|
||||
.search_form {
|
||||
display: inline-block;
|
||||
z-index: 200;
|
||||
width: 44px;
|
||||
position: relative;
|
||||
|
||||
.icon-search {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
color: #FFF;
|
||||
font-size: 20px;
|
||||
|
||||
@include translateY(-50%);
|
||||
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: absolute;
|
||||
left: 44px;
|
||||
bottom: 0;
|
||||
background: $primary_color;
|
||||
border-radius: $border_radius 0 0 $border_radius;
|
||||
display: none;
|
||||
box-shadow: 0 0 15px 2px rgba(0,0,0,.15);
|
||||
|
||||
&:before {
|
||||
@include transform(rotate(45deg));
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
background: $primary_color;
|
||||
left: -6px;
|
||||
bottom: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
background: $background_color;
|
||||
border-radius: $border_radius 0 0 $border_radius;
|
||||
position: relative;
|
||||
left: 4px;
|
||||
height: 44px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
&::-ms-clear {
|
||||
width : 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.focused,
|
||||
&.shown {
|
||||
border-color: #04bce6;
|
||||
|
||||
.wrapper {
|
||||
display: block;
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
.input {
|
||||
|
||||
input {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.results_container {
|
||||
min-height: 50px;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
left: 4px;
|
||||
display: none;
|
||||
background: $background_color;
|
||||
border-radius: $border_radius 0 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
.results {
|
||||
max-height: 280px;
|
||||
overflow-x: hidden;
|
||||
|
||||
.media_result {
|
||||
overflow: hidden;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
|
||||
.options {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
right: 0;
|
||||
padding: 10px;
|
||||
background: rgba(0,0,0,.3);
|
||||
|
||||
> .in_library_wanted {
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
> div {
|
||||
border: 0;
|
||||
@include flexbox();
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
select {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
min-width: 70px;
|
||||
@include flex(1 auto);
|
||||
}
|
||||
|
||||
.button {
|
||||
@include flex(1 auto);
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.message {
|
||||
height: 100%;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
width: 30px;
|
||||
min-height: 100%;
|
||||
display: block;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.data {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 30px;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
border-top: 1px solid rgba(255,255,255, 0.08);
|
||||
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
||||
@include translateX(0%);
|
||||
background: $background_color;
|
||||
|
||||
&.open {
|
||||
@include translateX(100%);
|
||||
}
|
||||
|
||||
.in_wanted,
|
||||
.in_library {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 14px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.info {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 15px;
|
||||
right: 7px;
|
||||
vertical-align: middle;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-weight: 300;
|
||||
font-size: 1.25em;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
@include flexbox();
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
@include flex(1 auto);
|
||||
}
|
||||
|
||||
.year {
|
||||
opacity: .4;
|
||||
padding: 0 5px;
|
||||
width: auto;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .info h2 .year {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:last-child .data {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&.focused.filled,
|
||||
&.shown.filled {
|
||||
.results_container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input {
|
||||
border-radius: 0 0 0 $border_radius;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class MovieBase(MovieTypeBase):
|
||||
return False
|
||||
elif not params.get('info'):
|
||||
try:
|
||||
is_movie = fireEvent('movie.is_movie', identifier = params.get('identifier'), single = True)
|
||||
is_movie = fireEvent('movie.is_movie', identifier = params.get('identifier'), adding = True, single = True)
|
||||
if not is_movie:
|
||||
msg = 'Can\'t add movie, seems to be a TV show.'
|
||||
log.error(msg)
|
||||
|
||||
52
couchpotato/core/media/movie/_base/static/details.js
Normal file
52
couchpotato/core/media/movie/_base/static/details.js
Normal file
@@ -0,0 +1,52 @@
|
||||
var MovieDetails = new Class({
|
||||
|
||||
Extends: BlockBase,
|
||||
|
||||
sections: null,
|
||||
|
||||
initialize: function(parent, options){
|
||||
var self = this;
|
||||
|
||||
self.sections = {};
|
||||
|
||||
self.el = new Element('div',{
|
||||
'class': 'page active movie_details level_' + (options.level || 0)
|
||||
}).adopt(
|
||||
self.overlay = new Element('div.overlay', {
|
||||
'events': {
|
||||
'click': self.close.bind(self)
|
||||
}
|
||||
}).grab(
|
||||
new Element('a.close.icon-left-arrow')
|
||||
),
|
||||
self.content = new Element('div.content').grab(
|
||||
new Element('h1', {
|
||||
'text': parent.getTitle() + (parent.get('year') ? ' (' + parent.get('year') + ')' : '')
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
self.addSection('description', new Element('div', {
|
||||
'text': parent.get('plot')
|
||||
}));
|
||||
|
||||
},
|
||||
|
||||
addSection: function(name, section_el){
|
||||
var self = this;
|
||||
name = name.toLowerCase();
|
||||
|
||||
self.content.grab(
|
||||
self.sections[name] = new Element('div', {
|
||||
'class': 'section section_' + name
|
||||
}).grab(section_el)
|
||||
);
|
||||
},
|
||||
|
||||
close: function(){
|
||||
var self = this;
|
||||
|
||||
self.el.dispose();
|
||||
}
|
||||
|
||||
});
|
||||
@@ -45,15 +45,16 @@ var MovieList = new Class({
|
||||
}) : null
|
||||
);
|
||||
|
||||
if($(window).getSize().x <= 480 && !self.options.force_view)
|
||||
self.changeView('list');
|
||||
else
|
||||
self.changeView(self.getSavedView() || self.options.view || 'details');
|
||||
self.changeView(self.getSavedView() || self.options.view || 'thumb');
|
||||
|
||||
// Create the alphabet nav
|
||||
if(self.options.navigation)
|
||||
self.createNavigation();
|
||||
|
||||
self.getMovies();
|
||||
|
||||
App.on('movie.added', self.movieAdded.bind(self));
|
||||
App.on('movie.deleted', self.movieDeleted.bind(self))
|
||||
App.on('movie.deleted', self.movieDeleted.bind(self));
|
||||
},
|
||||
|
||||
movieDeleted: function(notification){
|
||||
@@ -67,7 +68,7 @@ var MovieList = new Class({
|
||||
self.setCounter(self.counter_count-1);
|
||||
self.total_movies--;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
self.checkIfEmpty();
|
||||
@@ -89,15 +90,11 @@ var MovieList = new Class({
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
// Create the alphabet nav
|
||||
if(self.options.navigation)
|
||||
self.createNavigation();
|
||||
|
||||
if(self.options.load_more)
|
||||
self.scrollspy = new ScrollSpy({
|
||||
min: function(){
|
||||
var c = self.load_more.getCoordinates();
|
||||
return c.top - window.document.getSize().y - 300
|
||||
return c.top - window.document.getSize().y - 300;
|
||||
},
|
||||
onEnter: self.loadMore.bind(self)
|
||||
});
|
||||
@@ -138,7 +135,7 @@ var MovieList = new Class({
|
||||
self.empty_message = null;
|
||||
}
|
||||
|
||||
if(self.total_movies && count == 0 && !self.empty_message){
|
||||
if(self.total_movies && count === 0 && !self.empty_message){
|
||||
var message = (self.filter.search ? 'for "'+self.filter.search+'"' : '') +
|
||||
(self.filter.starts_with ? ' in <strong>'+self.filter.starts_with+'</strong>' : '');
|
||||
|
||||
@@ -230,30 +227,33 @@ var MovieList = new Class({
|
||||
),
|
||||
new Element('div.menus').adopt(
|
||||
self.navigation_counter = new Element('span.counter[title=Total]'),
|
||||
self.filter_menu = new Block.Menu(self, {
|
||||
'class': 'filter'
|
||||
self.filter_menu = new BlockMenu(self, {
|
||||
'class': 'filter',
|
||||
'button_class': 'icon-filter'
|
||||
}),
|
||||
self.navigation_actions = new Element('ul.actions', {
|
||||
self.navigation_actions = new Element('div.actions', {
|
||||
'events': {
|
||||
'click:relay(li)': function(e, el){
|
||||
'click': function(e, el){
|
||||
(e).stop();
|
||||
|
||||
var new_view = self.current_view == 'list' ? 'thumb' : 'list';
|
||||
|
||||
var a = 'active';
|
||||
self.navigation_actions.getElements('.'+a).removeClass(a);
|
||||
self.changeView(el.get('data-view'));
|
||||
this.addClass(a);
|
||||
self.changeView(new_view);
|
||||
|
||||
self.navigation_actions.getElement('[data-view='+new_view+']')
|
||||
.addClass(a);
|
||||
|
||||
el.inject(el.getParent(), 'top');
|
||||
el.getSiblings().hide();
|
||||
setTimeout(function(){
|
||||
el.getSiblings().setStyle('display', null);
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}),
|
||||
self.navigation_menu = new Block.Menu(self, {
|
||||
'class': 'extra'
|
||||
self.navigation_menu = new BlockMenu(self, {
|
||||
'class': 'extra',
|
||||
'button_class': 'icon-dots'
|
||||
})
|
||||
)
|
||||
).inject(self.el, 'top');
|
||||
);
|
||||
|
||||
// Mass edit
|
||||
self.mass_edit_select_class = new Form.Check(self.mass_edit_select);
|
||||
@@ -261,7 +261,7 @@ var MovieList = new Class({
|
||||
new Element('option', {
|
||||
'value': profile.get('_id'),
|
||||
'text': profile.get('label')
|
||||
}).inject(self.mass_edit_quality)
|
||||
}).inject(self.mass_edit_quality);
|
||||
});
|
||||
|
||||
self.filter_menu.addLink(
|
||||
@@ -273,7 +273,7 @@ var MovieList = new Class({
|
||||
'change': self.search.bind(self)
|
||||
}
|
||||
})
|
||||
).addClass('search');
|
||||
).addClass('search icon-search');
|
||||
|
||||
var available_chars;
|
||||
self.filter_menu.addEvent('open', function(){
|
||||
@@ -289,8 +289,8 @@ var MovieList = new Class({
|
||||
available_chars = json.chars;
|
||||
|
||||
available_chars.each(function(c){
|
||||
self.letters[c.capitalize()].addClass('available')
|
||||
})
|
||||
self.letters[c.capitalize()].addClass('available');
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
@@ -301,23 +301,23 @@ var MovieList = new Class({
|
||||
'events': {
|
||||
'click:relay(li.available)': function(e, el){
|
||||
self.activateLetter(el.get('data-letter'));
|
||||
self.getMovies(true)
|
||||
self.getMovies(true);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Actions
|
||||
['mass_edit', 'details', 'list'].each(function(view){
|
||||
['thumb', 'list'].each(function(view){
|
||||
var current = self.current_view == view;
|
||||
new Element('li', {
|
||||
'class': 'icon2 ' + view + (current ? ' active ' : ''),
|
||||
new Element('a', {
|
||||
'class': 'icon-' + view + (current ? ' active ' : ''),
|
||||
'data-view': view
|
||||
}).inject(self.navigation_actions, current ? 'top' : 'bottom');
|
||||
});
|
||||
|
||||
// All
|
||||
self.letters['all'] = new Element('li.letter_all.available.active', {
|
||||
self.letters.all = new Element('li.letter_all.available.active', {
|
||||
'text': 'ALL'
|
||||
}).inject(self.navigation_alpha);
|
||||
|
||||
@@ -346,7 +346,7 @@ var MovieList = new Class({
|
||||
var selected = 0,
|
||||
movies = self.movies.length;
|
||||
self.movies.each(function(movie){
|
||||
selected += movie.isSelected() ? 1 : 0
|
||||
selected += movie.isSelected() ? 1 : 0;
|
||||
});
|
||||
|
||||
var indeterminate = selected > 0 && selected < movies,
|
||||
@@ -441,10 +441,10 @@ var MovieList = new Class({
|
||||
var ids = [];
|
||||
self.movies.each(function(movie){
|
||||
if (movie.isSelected())
|
||||
ids.include(movie.get('_id'))
|
||||
ids.include(movie.get('_id'));
|
||||
});
|
||||
|
||||
return ids
|
||||
return ids;
|
||||
},
|
||||
|
||||
massEditToggleAll: function(){
|
||||
@@ -453,10 +453,10 @@ var MovieList = new Class({
|
||||
var select = self.mass_edit_select.get('checked');
|
||||
|
||||
self.movies.each(function(movie){
|
||||
movie.select(select)
|
||||
movie.select(select);
|
||||
});
|
||||
|
||||
self.calculateSelected()
|
||||
self.calculateSelected();
|
||||
},
|
||||
|
||||
reset: function(){
|
||||
@@ -493,12 +493,12 @@ var MovieList = new Class({
|
||||
.addClass(new_view+'_list');
|
||||
|
||||
self.current_view = new_view;
|
||||
Cookie.write(self.options.identifier+'_view2', new_view, {duration: 1000});
|
||||
Cookie.write(self.options.identifier+'_view3', new_view, {duration: 1000});
|
||||
},
|
||||
|
||||
getSavedView: function(){
|
||||
var self = this;
|
||||
return Cookie.read(self.options.identifier+'_view2');
|
||||
return Cookie.read(self.options.identifier+'_view3');
|
||||
},
|
||||
|
||||
search: function(){
|
||||
@@ -537,7 +537,7 @@ var MovieList = new Class({
|
||||
self.load_more.set('text', 'loading...');
|
||||
}
|
||||
|
||||
if(self.movies.length == 0 && self.options.loader){
|
||||
if(self.movies.length === 0 && self.options.loader){
|
||||
|
||||
self.loader_first = new Element('div.loading').adopt(
|
||||
new Element('div.message', {'text': self.options.title ? 'Loading \'' + self.options.title + '\'' : 'Loading...'})
|
||||
@@ -590,7 +590,7 @@ var MovieList = new Class({
|
||||
loadMore: function(){
|
||||
var self = this;
|
||||
if(self.offset >= self.options.limit)
|
||||
self.getMovies()
|
||||
self.getMovies();
|
||||
},
|
||||
|
||||
store: function(movies){
|
||||
@@ -603,7 +603,7 @@ var MovieList = new Class({
|
||||
checkIfEmpty: function(){
|
||||
var self = this;
|
||||
|
||||
var is_empty = self.movies.length == 0 && (self.total_movies == 0 || self.total_movies === undefined);
|
||||
var is_empty = self.movies.length === 0 && (self.total_movies === 0 || self.total_movies === undefined);
|
||||
|
||||
if(self.title)
|
||||
self.title[is_empty ? 'hide' : 'show']();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Page.Manage = new Class({
|
||||
var MoviesManage = new Class({
|
||||
|
||||
Extends: PageBase,
|
||||
|
||||
@@ -126,12 +126,12 @@ Page.Manage = new Class({
|
||||
(folder_progress.eta > 0 ? ', ' + new Date ().increment('second', folder_progress.eta).timeDiffInWords().replace('from now', 'to go') : '')
|
||||
}),
|
||||
new Element('span.percentage', {'text': folder_progress.total ? Math.round(((folder_progress.total-folder_progress.to_go)/folder_progress.total)*100) + '%' : '0%'})
|
||||
).inject(self.progress_container)
|
||||
).inject(self.progress_container);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
}, 1000);
|
||||
},
|
||||
@@ -141,10 +141,10 @@ Page.Manage = new Class({
|
||||
|
||||
for (folder in progress_object) {
|
||||
if (progress_object.hasOwnProperty(folder)) {
|
||||
temp_array.push(folder)
|
||||
temp_array.push(folder);
|
||||
}
|
||||
}
|
||||
return temp_array.stableSort()
|
||||
return temp_array.stableSort();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -2,7 +2,10 @@ var MovieAction = new Class({
|
||||
|
||||
Implements: [Options],
|
||||
|
||||
class_name: 'action icon2',
|
||||
class_name: 'action',
|
||||
label: 'UNKNOWN',
|
||||
button: null,
|
||||
details: null,
|
||||
|
||||
initialize: function(movie, options){
|
||||
var self = this;
|
||||
@@ -11,20 +14,33 @@ var MovieAction = new Class({
|
||||
self.movie = movie;
|
||||
|
||||
self.create();
|
||||
if(self.el)
|
||||
self.el.addClass(self.class_name)
|
||||
|
||||
if(self.button)
|
||||
self.button.addClass(self.class_name);
|
||||
},
|
||||
|
||||
create: function(){},
|
||||
|
||||
getButton: function(){
|
||||
return this.button || null;
|
||||
},
|
||||
|
||||
getDetails: function(){
|
||||
return this.details || null;
|
||||
},
|
||||
|
||||
getLabel: function(){
|
||||
return this.label;
|
||||
},
|
||||
|
||||
disable: function(){
|
||||
if(this.el)
|
||||
this.el.addClass('disable')
|
||||
this.el.addClass('disable');
|
||||
},
|
||||
|
||||
enable: function(){
|
||||
if(this.el)
|
||||
this.el.removeClass('disable')
|
||||
this.el.removeClass('disable');
|
||||
},
|
||||
|
||||
getTitle: function(){
|
||||
@@ -37,7 +53,7 @@ var MovieAction = new Class({
|
||||
try {
|
||||
return self.movie.original_title ? self.movie.original_title : self.movie.titles[0];
|
||||
}
|
||||
catch(e){
|
||||
catch(e2){
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
@@ -46,10 +62,10 @@ var MovieAction = new Class({
|
||||
get: function(key){
|
||||
var self = this;
|
||||
try {
|
||||
return self.movie.get(key)
|
||||
return self.movie.get(key);
|
||||
}
|
||||
catch(e){
|
||||
return self.movie[key]
|
||||
return self.movie[key];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -63,7 +79,7 @@ var MovieAction = new Class({
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
return this.el || null
|
||||
return this.el || null;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -80,7 +96,8 @@ MA.IMDB = new Class({
|
||||
|
||||
self.id = self.movie.getIdentifier ? self.movie.getIdentifier() : self.get('imdb');
|
||||
|
||||
self.el = new Element('a.imdb', {
|
||||
self.button = new Element('a.imdb', {
|
||||
'text': 'IMDB',
|
||||
'title': 'Go to the IMDB page of ' + self.getTitle(),
|
||||
'href': 'http://www.imdb.com/title/'+self.id+'/',
|
||||
'target': '_blank'
|
||||
@@ -94,22 +111,11 @@ MA.IMDB = new Class({
|
||||
MA.Release = new Class({
|
||||
|
||||
Extends: MovieAction,
|
||||
label: 'Releases',
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
self.el = new Element('a.releases.download', {
|
||||
'title': 'Show the releases that are available for ' + self.getTitle(),
|
||||
'events': {
|
||||
'click': self.show.bind(self)
|
||||
}
|
||||
});
|
||||
|
||||
if(!self.movie.data.releases || self.movie.data.releases.length == 0)
|
||||
self.el.hide();
|
||||
else
|
||||
self.showHelper();
|
||||
|
||||
App.on('movie.searcher.ended', function(notification){
|
||||
if(self.movie.data._id != notification.data._id) return;
|
||||
|
||||
@@ -118,7 +124,7 @@ MA.Release = new Class({
|
||||
// Releases are currently displayed
|
||||
if(self.options_container.isDisplayed()){
|
||||
self.options_container.destroy();
|
||||
self.createReleases();
|
||||
self.getDetails();
|
||||
}
|
||||
else {
|
||||
self.options_container.destroy();
|
||||
@@ -129,16 +135,7 @@ MA.Release = new Class({
|
||||
|
||||
},
|
||||
|
||||
show: function(e){
|
||||
var self = this;
|
||||
if(e)
|
||||
(e).preventDefault();
|
||||
|
||||
self.createReleases();
|
||||
|
||||
},
|
||||
|
||||
createReleases: function(refresh){
|
||||
getDetails: function(refresh){
|
||||
var self = this;
|
||||
|
||||
if(!self.options_container || refresh){
|
||||
@@ -162,14 +159,14 @@ MA.Release = new Class({
|
||||
|
||||
var quality = Quality.getQuality(release.quality) || {},
|
||||
info = release.info || {},
|
||||
provider = self.get(release, 'provider') + (info['provider_extra'] ? self.get(release, 'provider_extra') : '');
|
||||
provider = self.get(release, 'provider') + (info.provider_extra ? self.get(release, 'provider_extra') : '');
|
||||
|
||||
var release_name = self.get(release, 'name');
|
||||
if(release.files && release.files.length > 0){
|
||||
try {
|
||||
var movie_file = release.files.filter(function(file){
|
||||
var type = File.Type.get(file.type_id);
|
||||
return type && type.identifier == 'movie'
|
||||
return type && type.identifier == 'movie';
|
||||
}).pick();
|
||||
release_name = movie_file.path.split(Api.getOption('path_sep')).getLast();
|
||||
}
|
||||
@@ -177,19 +174,19 @@ MA.Release = new Class({
|
||||
}
|
||||
|
||||
// Create release
|
||||
release['el'] = new Element('div', {
|
||||
release.el = new Element('div', {
|
||||
'class': 'item '+release.status,
|
||||
'id': 'release_'+release._id
|
||||
}).adopt(
|
||||
new Element('span.name', {'text': release_name, 'title': release_name}),
|
||||
new Element('span.status', {'text': release.status, 'class': 'release_status '+release.status}),
|
||||
new Element('span.status', {'text': release.status, 'class': 'status '+release.status}),
|
||||
new Element('span.quality', {'text': quality.label + (release.is_3d ? ' 3D' : '') || 'n/a'}),
|
||||
new Element('span.size', {'text': info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}),
|
||||
new Element('span.size', {'text': info.size ? Math.floor(self.get(release, 'size')) : 'n/a'}),
|
||||
new Element('span.age', {'text': self.get(release, 'age')}),
|
||||
new Element('span.score', {'text': self.get(release, 'score')}),
|
||||
new Element('span.provider', { 'text': provider, 'title': provider }),
|
||||
info['detail_url'] ? new Element('a.info.icon2', {
|
||||
'href': info['detail_url'],
|
||||
info.detail_url ? new Element('a.info.icon2', {
|
||||
'href': info.detail_url,
|
||||
'target': '_blank'
|
||||
}) : new Element('a'),
|
||||
new Element('a.download.icon2', {
|
||||
@@ -283,7 +280,7 @@ MA.Release = new Class({
|
||||
new Element('span.or', {
|
||||
'text': 'or pick one below'
|
||||
})] : null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
self.last_release = null;
|
||||
@@ -291,9 +288,7 @@ MA.Release = new Class({
|
||||
|
||||
}
|
||||
|
||||
// Show it
|
||||
self.options_container.inject(self.movie, 'top');
|
||||
self.movie.slide('in', self.options_container);
|
||||
return self.options_container;
|
||||
|
||||
},
|
||||
|
||||
@@ -342,13 +337,13 @@ MA.Release = new Class({
|
||||
'click': self.markMovieDone.bind(self)
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
get: function(release, type){
|
||||
return (release.info && release.info[type] !== undefined) ? release.info[type] : 'n/a'
|
||||
return (release.info && release.info[type] !== undefined) ? release.info[type] : 'n/a';
|
||||
},
|
||||
|
||||
download: function(release){
|
||||
@@ -386,7 +381,7 @@ MA.Release = new Class({
|
||||
'data': {
|
||||
'id': release._id
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
@@ -403,7 +398,7 @@ MA.Release = new Class({
|
||||
movie.set('tween', {
|
||||
'duration': 300,
|
||||
'onComplete': function(){
|
||||
self.movie.destroy()
|
||||
self.movie.destroy();
|
||||
}
|
||||
});
|
||||
movie.tween('height', 0);
|
||||
@@ -429,49 +424,35 @@ MA.Trailer = new Class({
|
||||
|
||||
Extends: MovieAction,
|
||||
id: null,
|
||||
label: 'Trailer',
|
||||
|
||||
create: function(){
|
||||
getDetails: function(){
|
||||
var self = this;
|
||||
|
||||
self.el = new Element('a.trailer', {
|
||||
'title': 'Watch the trailer of ' + self.getTitle(),
|
||||
'events': {
|
||||
'click': self.watch.bind(self)
|
||||
}
|
||||
});
|
||||
if(!self.player_container){
|
||||
var id = 'trailer-'+randomString();
|
||||
self.player_container = new Element('div.icon-play[id='+id+']', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
self.watch(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
self.container = new Element('div.trailer_container')
|
||||
.grab(self.player_container);
|
||||
}
|
||||
|
||||
return self.player_container;
|
||||
},
|
||||
|
||||
watch: function(offset){
|
||||
watch: function(){
|
||||
var self = this;
|
||||
|
||||
var data_url = 'https://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18';
|
||||
var url = data_url.substitute({
|
||||
var data_url = 'https://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18',
|
||||
url = data_url.substitute({
|
||||
'title': encodeURI(self.getTitle()),
|
||||
'year': self.get('year'),
|
||||
'offset': offset || 1
|
||||
}),
|
||||
size = $(self.movie).getSize(),
|
||||
height = self.options.height || (size.x/16)*9,
|
||||
id = 'trailer-'+randomString();
|
||||
|
||||
self.player_container = new Element('div[id='+id+']');
|
||||
self.container = new Element('div.hide.trailer_container')
|
||||
.adopt(self.player_container)
|
||||
.inject($(self.movie), 'top');
|
||||
|
||||
self.container.setStyle('height', 0);
|
||||
self.container.removeClass('hide');
|
||||
|
||||
self.close_button = new Element('a.hide.hide_trailer', {
|
||||
'text': 'Hide trailer',
|
||||
'events': {
|
||||
'click': self.stop.bind(self)
|
||||
}
|
||||
}).inject(self.movie);
|
||||
|
||||
self.container.setStyle('height', height);
|
||||
$(self.movie).setStyle('height', height);
|
||||
'year': self.get('year')
|
||||
});
|
||||
|
||||
new Request.JSONP({
|
||||
'url': url,
|
||||
@@ -491,8 +472,6 @@ MA.Trailer = new Class({
|
||||
}
|
||||
});
|
||||
|
||||
self.close_button.removeClass('hide');
|
||||
|
||||
var quality_set = false;
|
||||
var change_quality = function(state){
|
||||
if(!quality_set && (state.data == 1 || state.data || 2)){
|
||||
@@ -508,7 +487,9 @@ MA.Trailer = new Class({
|
||||
self.player.addEventListener('onStateChange', change_quality);
|
||||
|
||||
}
|
||||
}).send()
|
||||
}).send();
|
||||
|
||||
return self.container;
|
||||
|
||||
},
|
||||
|
||||
@@ -523,7 +504,7 @@ MA.Trailer = new Class({
|
||||
setTimeout(function(){
|
||||
self.container.destroy();
|
||||
self.close_button.destroy();
|
||||
}, 1800)
|
||||
}, 1800);
|
||||
}
|
||||
|
||||
|
||||
@@ -536,7 +517,8 @@ MA.Edit = new Class({
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
self.el = new Element('a.edit', {
|
||||
self.button = new Element('a.edit', {
|
||||
'text': 'Edit',
|
||||
'title': 'Change movie information, like title and quality.',
|
||||
'events': {
|
||||
'click': self.editMovie.bind(self)
|
||||
@@ -585,7 +567,7 @@ MA.Edit = new Class({
|
||||
// Fill categories
|
||||
var categories = CategoryList.getAll();
|
||||
|
||||
if(categories.length == 0)
|
||||
if(categories.length === 0)
|
||||
self.category_select.hide();
|
||||
else {
|
||||
self.category_select.show();
|
||||
@@ -659,7 +641,8 @@ MA.Refresh = new Class({
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
self.el = new Element('a.refresh', {
|
||||
self.button = new Element('a.refresh', {
|
||||
'text': 'Refresh',
|
||||
'title': 'Refresh the movie info and do a forced search',
|
||||
'events': {
|
||||
'click': self.doRefresh.bind(self)
|
||||
@@ -670,7 +653,7 @@ MA.Refresh = new Class({
|
||||
|
||||
doRefresh: function(e){
|
||||
var self = this;
|
||||
(e).preventDefault();
|
||||
(e).stop();
|
||||
|
||||
Api.request('media.refresh', {
|
||||
'data': {
|
||||
@@ -686,17 +669,18 @@ MA.Readd = new Class({
|
||||
Extends: MovieAction,
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
var self = this,
|
||||
movie_done = self.movie.data.status == 'done',
|
||||
snatched;
|
||||
|
||||
var movie_done = self.movie.data.status == 'done';
|
||||
if(self.movie.data.releases && !movie_done)
|
||||
var snatched = self.movie.data.releases.filter(function(release){
|
||||
snatched = self.movie.data.releases.filter(function(release){
|
||||
return release.status && (release.status == 'snatched' || release.status == 'seeding' || release.status == 'downloaded' || release.status == 'done');
|
||||
}).length;
|
||||
|
||||
if(movie_done || snatched && snatched > 0)
|
||||
self.el = new Element('a.readd', {
|
||||
'title': 'Readd the movie and mark all previous snatched/downloaded as ignored',
|
||||
'title': 'Re-add the movie and mark all previous snatched/downloaded as ignored',
|
||||
'events': {
|
||||
'click': self.doReadd.bind(self)
|
||||
}
|
||||
@@ -792,7 +776,7 @@ MA.Delete = new Class({
|
||||
movie.set('tween', {
|
||||
'duration': 300,
|
||||
'onComplete': function(){
|
||||
self.movie.destroy()
|
||||
self.movie.destroy();
|
||||
}
|
||||
});
|
||||
movie.tween('height', 0);
|
||||
@@ -847,7 +831,7 @@ MA.Files = new Class({
|
||||
new Element('div.file.item').adopt(
|
||||
new Element('span.name', {'text': file}),
|
||||
new Element('span.type', {'text': type})
|
||||
).inject(rel)
|
||||
).inject(rel);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,22 +2,51 @@ var Movie = new Class({
|
||||
|
||||
Extends: BlockBase,
|
||||
|
||||
action: {},
|
||||
actions: [],
|
||||
details: null,
|
||||
|
||||
initialize: function(list, options, data){
|
||||
var self = this;
|
||||
|
||||
self.data = data;
|
||||
self.view = options.view || 'details';
|
||||
self.list = list;
|
||||
|
||||
self.el = new Element('div.movie');
|
||||
self.el = new Element('a.movie', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stop();
|
||||
self.openDetails();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.profile = Quality.getProfile(data.profile_id) || {};
|
||||
self.category = CategoryList.getCategory(data.category_id) || {};
|
||||
self.parent(self, options);
|
||||
|
||||
self.addEvents();
|
||||
|
||||
if(data.identifiers.imdb == 'tt1228705')
|
||||
self.openDetails();
|
||||
},
|
||||
|
||||
openDetails: function(){
|
||||
var self = this;
|
||||
|
||||
if(!self.details){
|
||||
self.details = new MovieDetails(self, {
|
||||
'level': 3
|
||||
});
|
||||
|
||||
// Add action items
|
||||
self.actions.each(function(action, nr){
|
||||
var details = action.getDetails();
|
||||
if(details)
|
||||
self.details.addSection(action.getLabel(), details);
|
||||
});
|
||||
}
|
||||
|
||||
App.getPageContainer().grab(self.details);
|
||||
},
|
||||
|
||||
addEvents: function(){
|
||||
@@ -30,7 +59,6 @@ var Movie = new Class({
|
||||
if(self.data._id != notification.data._id) return;
|
||||
|
||||
self.busy(false);
|
||||
self.removeView();
|
||||
self.update.delay(2000, self, notification);
|
||||
};
|
||||
App.on('movie.update', self.global_events['movie.update']);
|
||||
@@ -47,7 +75,7 @@ var Movie = new Class({
|
||||
// Remove spinner
|
||||
self.global_events['movie.searcher.ended'] = function(notification){
|
||||
if(notification.data && self.data._id == notification.data._id)
|
||||
self.busy(false)
|
||||
self.busy(false);
|
||||
};
|
||||
App.on('movie.searcher.ended', self.global_events['movie.searcher.ended']);
|
||||
|
||||
@@ -62,7 +90,7 @@ var Movie = new Class({
|
||||
var updated = false;
|
||||
self.data.releases.each(function(release){
|
||||
if(release._id == data._id){
|
||||
release['status'] = data.status;
|
||||
release.status = data.status;
|
||||
updated = true;
|
||||
}
|
||||
});
|
||||
@@ -102,12 +130,12 @@ var Movie = new Class({
|
||||
if(self.mask)
|
||||
self.mask.destroy();
|
||||
if(self.spinner)
|
||||
self.spinner.el.destroy();
|
||||
self.spinner.destroy();
|
||||
self.spinner = null;
|
||||
self.mask = null;
|
||||
}, timeout || 400);
|
||||
}
|
||||
}, timeout || 1000)
|
||||
}, timeout || 1000);
|
||||
}
|
||||
else if(!self.spinner) {
|
||||
self.createMask();
|
||||
@@ -130,7 +158,6 @@ var Movie = new Class({
|
||||
|
||||
self.data = notification.data;
|
||||
self.el.empty();
|
||||
self.removeView();
|
||||
|
||||
self.profile = Quality.getProfile(self.data.profile_id) || {};
|
||||
self.category = CategoryList.getCategory(self.data.category_id) || {};
|
||||
@@ -150,7 +177,7 @@ var Movie = new Class({
|
||||
|
||||
if(self.data.info.release_date)
|
||||
[self.data.info.release_date.dvd, self.data.info.release_date.theater].each(function(timestamp){
|
||||
if (timestamp > 0 && (eta == null || Math.abs(timestamp - now) < Math.abs(eta - now)))
|
||||
if (timestamp > 0 && (eta === null || Math.abs(timestamp - now) < Math.abs(eta - now)))
|
||||
eta = timestamp;
|
||||
});
|
||||
|
||||
@@ -163,7 +190,7 @@ var Movie = new Class({
|
||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||
'events': {
|
||||
'change': function(){
|
||||
self.fireEvent('select')
|
||||
self.fireEvent('select');
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -181,9 +208,6 @@ var Movie = new Class({
|
||||
'text': self.data.info.year || 'n/a'
|
||||
})
|
||||
),
|
||||
self.description = new Element('div.description.tiny_scroll', {
|
||||
'text': self.data.info.plot
|
||||
}),
|
||||
self.eta = eta_date && (now+8035200 > eta) ? new Element('div.eta', {
|
||||
'text': eta_date,
|
||||
'title': 'ETA'
|
||||
@@ -193,19 +217,24 @@ var Movie = new Class({
|
||||
'click': function(e){
|
||||
var releases = self.el.getElement('.actions .releases');
|
||||
if(releases.isVisible())
|
||||
releases.fireEvent('click', [e])
|
||||
releases.fireEvent('click', [e]);
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
self.actions = new Element('div.actions')
|
||||
self.actions_el = new Element('div.actions', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stopPropagation();
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
if(!self.thumbnail)
|
||||
self.el.addClass('no_thumbnail');
|
||||
|
||||
//self.changeView(self.view);
|
||||
self.select_checkbox_class = new Form.Check(self.select_checkbox);
|
||||
|
||||
// Add profile
|
||||
@@ -213,9 +242,9 @@ var Movie = new Class({
|
||||
self.profile.getTypes().each(function(type){
|
||||
|
||||
var q = self.addQuality(type.get('quality'), type.get('3d'));
|
||||
if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){
|
||||
if((type.finish === true || type.get('finish')) && !q.hasClass('finish')){
|
||||
q.addClass('finish');
|
||||
q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.')
|
||||
q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -223,17 +252,20 @@ var Movie = new Class({
|
||||
// Add releases
|
||||
self.updateReleases();
|
||||
|
||||
Object.each(self.options.actions, function(action, key){
|
||||
self.action[key.toLowerCase()] = action = new self.options.actions[key](self);
|
||||
if(action.el)
|
||||
self.actions.adopt(action)
|
||||
self.options.actions.each(function(action){
|
||||
var action = new action(self),
|
||||
button = action.getButton();
|
||||
if(button)
|
||||
self.actions_el.grab(button);
|
||||
|
||||
self.actions.push(action);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
updateReleases: function(){
|
||||
var self = this;
|
||||
if(!self.data.releases || self.data.releases.length == 0) return;
|
||||
if(!self.data.releases || self.data.releases.length === 0) return;
|
||||
|
||||
self.data.releases.each(function(release){
|
||||
|
||||
@@ -245,7 +277,7 @@ var Movie = new Class({
|
||||
|
||||
if (q && !q.hasClass(status)){
|
||||
q.addClass(status);
|
||||
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status)
|
||||
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -271,7 +303,7 @@ var Movie = new Class({
|
||||
else if(self.data.info.titles.length > 0)
|
||||
return self.getUnprefixedTitle(self.data.info.titles[0]);
|
||||
|
||||
return 'Unknown movie'
|
||||
return 'Unknown movie';
|
||||
},
|
||||
|
||||
getUnprefixedTitle: function(t){
|
||||
@@ -284,49 +316,6 @@ var Movie = new Class({
|
||||
return t;
|
||||
},
|
||||
|
||||
slide: function(direction, el){
|
||||
var self = this;
|
||||
|
||||
if(direction == 'in'){
|
||||
self.temp_view = self.view;
|
||||
self.changeView('details');
|
||||
|
||||
self.el.addEvent('outerClick', function(){
|
||||
self.removeView();
|
||||
self.slide('out')
|
||||
});
|
||||
el.show();
|
||||
self.data_container.addClass('hide_right');
|
||||
}
|
||||
else {
|
||||
self.el.removeEvents('outerClick');
|
||||
|
||||
setTimeout(function(){
|
||||
if(self.el)
|
||||
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
|
||||
}, 600);
|
||||
|
||||
self.data_container.removeClass('hide_right');
|
||||
}
|
||||
},
|
||||
|
||||
changeView: function(new_view){
|
||||
var self = this;
|
||||
|
||||
if(self.el)
|
||||
self.el
|
||||
.removeClass(self.view+'_view')
|
||||
.addClass(new_view+'_view');
|
||||
|
||||
self.view = new_view;
|
||||
},
|
||||
|
||||
removeView: function(){
|
||||
var self = this;
|
||||
|
||||
self.el.removeClass(self.view+'_view')
|
||||
},
|
||||
|
||||
getIdentifier: function(){
|
||||
var self = this;
|
||||
|
||||
@@ -339,12 +328,12 @@ var Movie = new Class({
|
||||
},
|
||||
|
||||
get: function(attr){
|
||||
return this.data[attr] || this.data.info[attr]
|
||||
return this.data[attr] || this.data.info[attr];
|
||||
},
|
||||
|
||||
select: function(bool){
|
||||
var self = this;
|
||||
self.select_checkbox_class[bool ? 'check' : 'uncheck']()
|
||||
self.select_checkbox_class[bool ? 'check' : 'uncheck']();
|
||||
},
|
||||
|
||||
isSelected: function(){
|
||||
|
||||
367
couchpotato/core/media/movie/_base/static/movie.scss
Normal file
367
couchpotato/core/media/movie/_base/static/movie.scss
Normal file
@@ -0,0 +1,367 @@
|
||||
@import "couchpotato/static/style/mixins";
|
||||
|
||||
.page.movies {
|
||||
z-index: 21; // Sets navigation above
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
.page.movies_wanted, .page.movies_manage {
|
||||
top: $header_height;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.list_list {
|
||||
font-weight: 300;
|
||||
|
||||
.poster {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.movie {
|
||||
display: block;
|
||||
border-top: 1px solid $theme_off;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
.data {
|
||||
padding: $padding/2 $padding;
|
||||
|
||||
.info {
|
||||
|
||||
@include flexbox();
|
||||
flex-flow: row nowrap;
|
||||
|
||||
.title {
|
||||
@include flex(1 auto);
|
||||
|
||||
.year {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.quality span {
|
||||
float: left;
|
||||
color: #FFF;
|
||||
font-size: .7em;
|
||||
padding: 2px 4px;
|
||||
background: rgba(0,0,0,.2);
|
||||
border-radius: 1px;
|
||||
margin: 2px 0 0 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.thumb_list {
|
||||
|
||||
font-size: 12px;
|
||||
padding: 0 $padding;
|
||||
|
||||
.movie {
|
||||
@include span(6);
|
||||
float: left;
|
||||
margin-bottom: $padding;
|
||||
position: relative;
|
||||
|
||||
&:nth-child(4n+4){
|
||||
@include span(last);
|
||||
}
|
||||
|
||||
&:nth-child(4n+5){
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.poster {
|
||||
border-radius: $border_radius;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.data {
|
||||
clear: both;
|
||||
|
||||
.info {
|
||||
height: 44px;
|
||||
|
||||
.title {
|
||||
@include flexbox();
|
||||
padding: 3px 0;
|
||||
|
||||
span {
|
||||
@include flex(1 auto);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.year {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.quality {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
span {
|
||||
color: #FFF;
|
||||
font-size: .8em;
|
||||
padding: 2px 4px;
|
||||
background: rgba(0,0,0,.2);
|
||||
border-radius: 1px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
top: $padding / 2;
|
||||
right: $padding / 2;
|
||||
display: none;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
background: $background_color;
|
||||
padding: $padding / 3;
|
||||
width: auto;
|
||||
margin-bottom: 1px;
|
||||
clear: both;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mask {
|
||||
bottom: 44px;
|
||||
border-radius: $border_radius;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.check {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: $padding;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.eta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page.movie_details {
|
||||
|
||||
$gab-width: $header_width/3;
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: $header_width;
|
||||
background: rgba(0,0,0,.6);
|
||||
border-radius: 3px 0 0 3px;
|
||||
|
||||
.close {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
font-size: 60px;
|
||||
line-height: $header_height;
|
||||
color: #FFF;
|
||||
width: $gab-width;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: $header_width + $gab-width;
|
||||
background: $background_color;
|
||||
z-index: 200;
|
||||
border-radius: 3px 0 0 3px;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
padding: 0 $padding;
|
||||
font-size: 24px;
|
||||
line-height: $header_height;
|
||||
color: rgba(0,0,0,.5);
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: $padding $padding;
|
||||
border-top: 1px solid rgba(0,0,0,.1);
|
||||
}
|
||||
}
|
||||
|
||||
.releases {
|
||||
|
||||
.buttons {
|
||||
margin-bottom: $padding/2;
|
||||
}
|
||||
|
||||
.item span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item .name {
|
||||
@include flex(1 auto);
|
||||
text-align: left;
|
||||
}
|
||||
.status { min-width: 70px; max-width: 70px; }
|
||||
.quality { min-width: 60px; max-width: 60px; }
|
||||
.size { min-width: 40px; max-width: 40px; }
|
||||
.age { min-width: 40px; max-width: 40px; }
|
||||
.score { min-width: 45px; max-width: 45px; }
|
||||
.provider { min-width: 110px; max-width: 110px; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.alph_nav {
|
||||
|
||||
.mass_edit_form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menus {
|
||||
margin-right: $padding;
|
||||
|
||||
.button {
|
||||
padding: 0 $padding/2;
|
||||
line-height: $header_height;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.counter, .more_menu, .actions {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.counter {
|
||||
line-height: $header_height;
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.active {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.filter {
|
||||
.wrapper {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.search {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
line-height: 38px;
|
||||
padding-left: $padding/2;
|
||||
font-size: 16px;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: $padding/2 $padding/2 $padding/2 $padding*1.5;
|
||||
background: $background_color;
|
||||
border: none;
|
||||
border-bottom: 1px solid $theme_off;
|
||||
}
|
||||
}
|
||||
|
||||
.numbers {
|
||||
padding: $padding/2;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
width: 10%;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
color: rgba(0,0,0,.2);
|
||||
cursor: default;
|
||||
|
||||
&.active {
|
||||
background: $theme_off;
|
||||
}
|
||||
|
||||
&.available {
|
||||
color: rgba(0,0,0,1);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: $theme_off;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.more_menu {
|
||||
|
||||
&.show .button {
|
||||
color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
top: $header_height - 10px;
|
||||
padding-top: 4px;
|
||||
border-radius: $border_radius $border_radius 0 0;
|
||||
|
||||
&:before {
|
||||
top: 0;
|
||||
left: auto;
|
||||
right: 22px;
|
||||
}
|
||||
|
||||
ul {
|
||||
border-radius: $border_radius $border_radius 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
49
couchpotato/core/media/movie/_base/static/page.js
Normal file
49
couchpotato/core/media/movie/_base/static/page.js
Normal file
@@ -0,0 +1,49 @@
|
||||
Page.Movies = new Class({
|
||||
|
||||
Extends: PageBase,
|
||||
|
||||
name: 'movies',
|
||||
sub_pages: ['Wanted', 'Manage'],
|
||||
default_page: 'Wanted',
|
||||
current_page: null,
|
||||
|
||||
initialize: function(parent, options){
|
||||
var self = this;
|
||||
self.parent(parent, options);
|
||||
|
||||
self.navigation = new BlockNavigation();
|
||||
$(self.navigation).inject(self.el, 'top');
|
||||
|
||||
},
|
||||
|
||||
defaultAction: function(action, params){
|
||||
var self = this;
|
||||
|
||||
if(self.current_page){
|
||||
self.current_page.hide();
|
||||
|
||||
if(self.current_page.list && self.current_page.list.navigation)
|
||||
self.current_page.list.navigation.dispose();
|
||||
}
|
||||
|
||||
var route = new Route();
|
||||
route.parse(action);
|
||||
|
||||
var page_name = route.getPage() != 'index' ? route.getPage().capitalize() : self.default_page;
|
||||
|
||||
var page = self.sub_pages.filter(function(page){
|
||||
return page.name == page_name;
|
||||
}).pick()['class'];
|
||||
|
||||
page.open(route.getAction() || 'index', params);
|
||||
page.show();
|
||||
|
||||
if(page.list && page.list.navigation)
|
||||
page.list.navigation.inject(self.navigation);
|
||||
|
||||
self.current_page = page;
|
||||
self.navigation.activate(page_name.toLowerCase());
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
Block.Search.MovieItem = new Class({
|
||||
var BlockSearchMovieItem = new Class({
|
||||
|
||||
Implements: [Options, Events],
|
||||
|
||||
@@ -31,9 +31,11 @@ Block.Search.MovieItem = new Class({
|
||||
}
|
||||
}).adopt(
|
||||
self.info_container = new Element('div.info').adopt(
|
||||
new Element('h2').adopt(
|
||||
new Element('h2', {
|
||||
'title': self.getTitle()
|
||||
}).adopt(
|
||||
self.title = new Element('span.title', {
|
||||
'text': info.titles && info.titles.length > 0 ? info.titles[0] : 'Unknown'
|
||||
'text': self.getTitle()
|
||||
}),
|
||||
self.year = info.year ? new Element('span.year', {
|
||||
'text': info.year
|
||||
@@ -48,7 +50,7 @@ Block.Search.MovieItem = new Class({
|
||||
self.alternativeTitle({
|
||||
'title': title
|
||||
});
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
alternativeTitle: function(alternative){
|
||||
@@ -68,7 +70,7 @@ Block.Search.MovieItem = new Class({
|
||||
},
|
||||
|
||||
get: function(key){
|
||||
return this.info[key]
|
||||
return this.info[key];
|
||||
},
|
||||
|
||||
showOptions: function(){
|
||||
@@ -77,7 +79,7 @@ Block.Search.MovieItem = new Class({
|
||||
self.createOptions();
|
||||
|
||||
self.data_container.addClass('open');
|
||||
self.el.addEvent('outerClick', self.closeOptions.bind(self))
|
||||
self.el.addEvent('outerClick', self.closeOptions.bind(self));
|
||||
|
||||
},
|
||||
|
||||
@@ -85,7 +87,7 @@ Block.Search.MovieItem = new Class({
|
||||
var self = this;
|
||||
|
||||
self.data_container.removeClass('open');
|
||||
self.el.removeEvents('outerClick')
|
||||
self.el.removeEvents('outerClick');
|
||||
},
|
||||
|
||||
add: function(e){
|
||||
@@ -132,10 +134,11 @@ Block.Search.MovieItem = new Class({
|
||||
|
||||
if(!self.options_el.hasClass('set')){
|
||||
|
||||
var in_library;
|
||||
if(info.in_library){
|
||||
var in_library = [];
|
||||
in_library = [];
|
||||
(info.in_library.releases || []).each(function(release){
|
||||
in_library.include(release.quality)
|
||||
in_library.include(release.quality);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -171,14 +174,14 @@ Block.Search.MovieItem = new Class({
|
||||
Array.each(self.alternative_titles, function(alt){
|
||||
new Element('option', {
|
||||
'text': alt.title
|
||||
}).inject(self.title_select)
|
||||
}).inject(self.title_select);
|
||||
});
|
||||
|
||||
|
||||
// Fill categories
|
||||
var categories = CategoryList.getAll();
|
||||
|
||||
if(categories.length == 0)
|
||||
if(categories.length === 0)
|
||||
self.category_select.hide();
|
||||
else {
|
||||
self.category_select.show();
|
||||
@@ -199,12 +202,12 @@ Block.Search.MovieItem = new Class({
|
||||
new Element('option', {
|
||||
'value': profile.get('_id'),
|
||||
'text': profile.get('label')
|
||||
}).inject(self.profile_select)
|
||||
}).inject(self.profile_select);
|
||||
});
|
||||
|
||||
self.options_el.addClass('set');
|
||||
|
||||
if(categories.length == 0 && self.title_select.getElements('option').length == 1 && profiles.length == 1 &&
|
||||
if(categories.length === 0 && self.title_select.getElements('option').length == 1 && profiles.length == 1 &&
|
||||
!(self.info.in_wanted && self.info.in_wanted.profile_id || in_library))
|
||||
self.add();
|
||||
|
||||
@@ -218,12 +221,12 @@ Block.Search.MovieItem = new Class({
|
||||
self.mask = new Element('div.mask').inject(self.el).fade('hide');
|
||||
|
||||
createSpinner(self.mask);
|
||||
self.mask.fade('in')
|
||||
self.mask.fade('in');
|
||||
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
return this.el
|
||||
return this.el;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Page.Wanted = new Class({
|
||||
var MoviesWanted = new Class({
|
||||
|
||||
Extends: PageBase,
|
||||
|
||||
@@ -10,7 +10,7 @@ Page.Wanted = new Class({
|
||||
indexAction: function(){
|
||||
var self = this;
|
||||
|
||||
if(!self.wanted){
|
||||
if(!self.list){
|
||||
|
||||
self.manual_search = new Element('a', {
|
||||
'title': 'Force a search for the full wanted list',
|
||||
@@ -20,7 +20,6 @@ Page.Wanted = new Class({
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
self.scan_folder = new Element('a', {
|
||||
'title': 'Scan a folder and rename all movies in it',
|
||||
'text': 'Manual folder scan',
|
||||
@@ -30,7 +29,7 @@ Page.Wanted = new Class({
|
||||
});
|
||||
|
||||
// Wanted movies
|
||||
self.wanted = new MovieList({
|
||||
self.list = new MovieList({
|
||||
'identifier': 'wanted',
|
||||
'status': 'active',
|
||||
'actions': [MA.IMDB, MA.Trailer, MA.Release, MA.Edit, MA.Refresh, MA.Readd, MA.Delete],
|
||||
@@ -38,7 +37,7 @@ Page.Wanted = new Class({
|
||||
'menu': [self.manual_search, self.scan_folder],
|
||||
'on_empty_element': App.createUserscriptButtons().addClass('empty_wanted')
|
||||
});
|
||||
$(self.wanted).inject(self.el);
|
||||
$(self.list).inject(self.el);
|
||||
|
||||
// Check if search is in progress
|
||||
self.startProgressInterval.delay(4000, self);
|
||||
@@ -91,7 +90,7 @@ Page.Wanted = new Class({
|
||||
};
|
||||
|
||||
if(!self.folder_browser){
|
||||
self.folder_browser = new Option['Directory']("Scan", "folder", "", options);
|
||||
self.folder_browser = new Option.Directory("Scan", "folder", "", options);
|
||||
|
||||
self.folder_browser.save = function() {
|
||||
var folder = self.folder_browser.getValue();
|
||||
|
||||
@@ -44,11 +44,12 @@ var Charts = new Class({
|
||||
|
||||
if( Cookie.read('suggestions_charts_menu_selected') === 'charts'){
|
||||
self.show();
|
||||
self.fireEvent.delay(0, self, 'created');
|
||||
}
|
||||
else
|
||||
self.el.hide();
|
||||
|
||||
self.fireEvent.delay(0, self, 'created');
|
||||
|
||||
},
|
||||
|
||||
fill: function(json){
|
||||
@@ -58,7 +59,7 @@ var Charts = new Class({
|
||||
self.el_refreshing_text.hide();
|
||||
self.el_refresh_link.show();
|
||||
|
||||
if(!json || json.count == 0){
|
||||
if(!json || json.count === 0){
|
||||
self.el_no_charts_enabled.show();
|
||||
self.el_refresh_link.show();
|
||||
self.el_refreshing_text.hide();
|
||||
@@ -83,17 +84,16 @@ var Charts = new Class({
|
||||
|
||||
Object.each(chart.list, function(movie){
|
||||
|
||||
var m = new Block.Search.MovieItem(movie, {
|
||||
var m = new BlockSearchMovieItem(movie, {
|
||||
'onAdded': function(){
|
||||
self.afterAdded(m, movie)
|
||||
self.afterAdded(m, movie);
|
||||
}
|
||||
});
|
||||
|
||||
var in_database_class = (chart.hide_wanted && movie.in_wanted) ? 'hidden' : (movie.in_wanted ? 'chart_in_wanted' : ((chart.hide_library && movie.in_library) ? 'hidden': (movie.in_library ? 'chart_in_library' : ''))),
|
||||
in_database_title = movie.in_wanted ? 'Movie in wanted list' : (movie.in_library ? 'Movie in library' : '');
|
||||
|
||||
m.el
|
||||
.addClass(in_database_class)
|
||||
m.el.addClass(in_database_class)
|
||||
.grab(
|
||||
new Element('div.chart_number', {
|
||||
'text': it++,
|
||||
@@ -135,7 +135,7 @@ var Charts = new Class({
|
||||
'text': plot,
|
||||
'events': {
|
||||
'click': function(){
|
||||
this.toggleClass('full')
|
||||
this.toggleClass('full');
|
||||
}
|
||||
}
|
||||
}) : null
|
||||
|
||||
89
couchpotato/core/media/movie/providers/automation/crowdai.py
Normal file
89
couchpotato/core/media/movie/providers/automation/crowdai.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import re
|
||||
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media.movie.providers.automation.base import Automation
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
autoload = 'CrowdAI'
|
||||
|
||||
|
||||
class CrowdAI(Automation, RSS):
|
||||
|
||||
interval = 1800
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
movies = []
|
||||
|
||||
urls = dict(zip(splitString(self.conf('automation_urls')), [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]))
|
||||
|
||||
for url in urls:
|
||||
|
||||
if not urls[url]:
|
||||
continue
|
||||
|
||||
rss_movies = self.getRSSData(url)
|
||||
|
||||
for movie in rss_movies:
|
||||
|
||||
description = self.getTextElement(movie, 'description')
|
||||
grabs = 0
|
||||
|
||||
for item in movie:
|
||||
if item.attrib.get('name') == 'grabs':
|
||||
grabs = item.attrib.get('value')
|
||||
break
|
||||
|
||||
if int(grabs) > tryInt(self.conf('number_grabs')):
|
||||
title = re.match(r'.*Title: .a href.*/">(.*) \(\d{4}\).*', description).group(1)
|
||||
log.info2('%s grabs for movie: %s, enqueue...', (grabs, title))
|
||||
year = re.match(r'.*Year: (\d{4}).*', description).group(1)
|
||||
imdb = self.search(title, year)
|
||||
|
||||
if imdb and self.isMinimalMovie(imdb):
|
||||
movies.append(imdb['imdb'])
|
||||
|
||||
return movies
|
||||
|
||||
|
||||
config = [{
|
||||
'name': 'crowdai',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'automation_providers',
|
||||
'name': 'crowdai_automation',
|
||||
'label': 'CrowdAI',
|
||||
'description': 'Imports from any newznab powered NZB providers RSS feed depending on the number of grabs per movie. Go to your newznab site and find the RSS section. Then copy the copy paste the link under "Movies > x264 feed" here.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'automation_enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'automation_urls_use',
|
||||
'label': 'Use',
|
||||
'default': '1',
|
||||
},
|
||||
{
|
||||
'name': 'automation_urls',
|
||||
'label': 'url',
|
||||
'type': 'combined',
|
||||
'combine': ['automation_urls_use', 'automation_urls'],
|
||||
'default': 'http://YOUR_PROVIDER/rss?t=THE_MOVIE_CATEGORY&i=YOUR_USER_ID&r=YOUR_API_KEY&res=2&rls=2&num=100',
|
||||
},
|
||||
{
|
||||
'name': 'number_grabs',
|
||||
'default': '500',
|
||||
'label': 'Grab threshold',
|
||||
'description': 'Number of grabs required',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
@@ -48,11 +48,12 @@ class Letterboxd(Automation):
|
||||
|
||||
soup = BeautifulSoup(self.getHTMLData(self.url % username))
|
||||
|
||||
for movie in soup.find_all('a', attrs = {'class': 'frame'}):
|
||||
match = removeEmpty(self.pattern.split(movie['title']))
|
||||
for movie in soup.find_all('li', attrs = {'class': 'poster-container'}):
|
||||
img = movie.find('img', movie)
|
||||
title = img.get('alt')
|
||||
|
||||
movies.append({
|
||||
'title': match[0],
|
||||
'year': match[1]
|
||||
'title': title
|
||||
})
|
||||
|
||||
return movies
|
||||
|
||||
@@ -39,15 +39,14 @@ class Rottentomatoes(Automation, RSS):
|
||||
|
||||
if result:
|
||||
|
||||
log.info2('Something smells...')
|
||||
rating = tryInt(self.getTextElement(movie, rating_tag))
|
||||
name = result.group(0)
|
||||
|
||||
print rating, tryInt(self.conf('tomatometer_percent'))
|
||||
if rating < tryInt(self.conf('tomatometer_percent')):
|
||||
log.info2('%s seems to be rotten...', name)
|
||||
else:
|
||||
|
||||
log.info2('Found %s fresh enough movies, enqueuing: %s', (rating, name))
|
||||
log.info2('Found %s with fresh rating %s', (name, rating))
|
||||
year = datetime.datetime.now().strftime("%Y")
|
||||
imdb = self.search(name, year)
|
||||
|
||||
|
||||
@@ -69,12 +69,15 @@ class CouchPotatoApi(MovieProvider):
|
||||
name_enc = base64.b64encode(ss(name))
|
||||
return self.getJsonData(self.urls['validate'] % name_enc, headers = self.getRequestHeaders())
|
||||
|
||||
def isMovie(self, identifier = None):
|
||||
def isMovie(self, identifier = None, adding = False):
|
||||
|
||||
if not identifier:
|
||||
return
|
||||
|
||||
data = self.getJsonData(self.urls['is_movie'] % identifier, headers = self.getRequestHeaders())
|
||||
url = self.urls['is_movie'] % identifier
|
||||
url += '?adding=1' if adding else ''
|
||||
|
||||
data = self.getJsonData(url, headers = self.getRequestHeaders())
|
||||
if data:
|
||||
return data.get('is_movie', True)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import json
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from couchpotato import Env
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.helpers.variable import tryInt, tryFloat, splitString
|
||||
@@ -17,8 +18,8 @@ autoload = 'OMDBAPI'
|
||||
class OMDBAPI(MovieProvider):
|
||||
|
||||
urls = {
|
||||
'search': 'http://www.omdbapi.com/?%s',
|
||||
'info': 'http://www.omdbapi.com/?i=%s',
|
||||
'search': 'http://www.omdbapi.com/?type=movie&%s',
|
||||
'info': 'http://www.omdbapi.com/?type=movie&i=%s',
|
||||
}
|
||||
|
||||
http_time_between_calls = 0
|
||||
@@ -38,7 +39,8 @@ class OMDBAPI(MovieProvider):
|
||||
}
|
||||
|
||||
cache_key = 'omdbapi.cache.%s' % q
|
||||
cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode({'t': name_year.get('name'), 'y': name_year.get('year', '')}), timeout = 3)
|
||||
url = self.urls['search'] % tryUrlencode({'t': name_year.get('name'), 'y': name_year.get('year', '')})
|
||||
cached = self.getCache(cache_key, url, timeout = 3, headers = {'User-Agent': Env.getIdentifier()})
|
||||
|
||||
if cached:
|
||||
result = self.parseMovie(cached)
|
||||
@@ -56,7 +58,7 @@ class OMDBAPI(MovieProvider):
|
||||
return {}
|
||||
|
||||
cache_key = 'omdbapi.cache.%s' % identifier
|
||||
cached = self.getCache(cache_key, self.urls['info'] % identifier, timeout = 3)
|
||||
cached = self.getCache(cache_key, self.urls['info'] % identifier, timeout = 3, headers = {'User-Agent': Env.getIdentifier()})
|
||||
|
||||
if cached:
|
||||
result = self.parseMovie(cached)
|
||||
|
||||
@@ -11,7 +11,7 @@ autoload = 'Bitsoup'
|
||||
class Bitsoup(MovieProvider, Base):
|
||||
cat_ids = [
|
||||
([17], ['3d']),
|
||||
([41], ['720p', '1080p']),
|
||||
([80], ['720p', '1080p']),
|
||||
([20], ['dvdr']),
|
||||
([19], ['brrip', 'dvdrip']),
|
||||
]
|
||||
|
||||
11
couchpotato/core/media/movie/providers/torrent/hdaccess.py
Normal file
11
couchpotato/core/media/movie/providers/torrent/hdaccess.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media._base.providers.torrent.hdaccess import Base
|
||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
autoload = 'HDAccess'
|
||||
|
||||
|
||||
class HDAccess(MovieProvider, Base):
|
||||
pass
|
||||
@@ -0,0 +1,27 @@
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media._base.providers.torrent.torrentleech import Base
|
||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
autoload = 'TorrentLeech'
|
||||
|
||||
|
||||
class TorrentLeech(MovieProvider, Base):
|
||||
|
||||
cat_ids = [
|
||||
([13], ['720p', '1080p', 'bd50']),
|
||||
([8], ['cam']),
|
||||
([9], ['ts', 'tc']),
|
||||
([10], ['r5', 'scr']),
|
||||
([11], ['dvdrip']),
|
||||
([13, 14], ['brrip']),
|
||||
([12], ['dvdr']),
|
||||
]
|
||||
|
||||
def buildUrl(self, title, media, quality):
|
||||
return (
|
||||
tryUrlencode(title.replace(':', '')),
|
||||
','.join([str(x) for x in self.getCatId(quality)])
|
||||
)
|
||||
@@ -22,8 +22,8 @@ class TorrentShack(MovieProvider, Base):
|
||||
# Movies-SD Pack - 983 (not included)
|
||||
|
||||
cat_ids = [
|
||||
([970], ['bd50']),
|
||||
([300], ['720p', '1080p']),
|
||||
([970, 320], ['bd50']),
|
||||
([300, 320], ['720p', '1080p']),
|
||||
([350], ['dvdr']),
|
||||
([400], ['brrip', 'dvdrip']),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.media._base.providers.torrent.torrentz import Base
|
||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||
|
||||
@@ -11,5 +10,5 @@ autoload = 'Torrentz'
|
||||
|
||||
class Torrentz(MovieProvider, Base):
|
||||
|
||||
def buildUrl(self, media):
|
||||
return tryUrlencode('"%s"' % fireEvent('library.query', media, single = True))
|
||||
def buildUrl(self, title, media, quality):
|
||||
return tryUrlencode('"%s %s"' % (title, media['info']['year']))
|
||||
@@ -12,7 +12,7 @@ autoload = 'RottenTomatoes'
|
||||
|
||||
class RottenTomatoes(UserscriptBase):
|
||||
|
||||
includes = ['*://www.rottentomatoes.com/m/*/']
|
||||
includes = ['*://www.rottentomatoes.com/m/*']
|
||||
excludes = ['*://www.rottentomatoes.com/m/*/*/']
|
||||
|
||||
version = 2
|
||||
|
||||
@@ -394,8 +394,9 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
log.info('Trying next release for: %s', getTitle(media))
|
||||
self.single(media, manual = manual, force_download = force_download)
|
||||
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
except:
|
||||
log.error('Failed searching for next release: %s', traceback.format_exc())
|
||||
return False
|
||||
|
||||
@@ -51,8 +51,8 @@ var SuggestList = new Class({
|
||||
self.show();
|
||||
else
|
||||
self.hide();
|
||||
|
||||
self.fireEvent('created');
|
||||
|
||||
self.fireEvent.delay(0, self, 'created');
|
||||
|
||||
},
|
||||
|
||||
@@ -60,16 +60,16 @@ var SuggestList = new Class({
|
||||
|
||||
var self = this;
|
||||
|
||||
if(!json || json.count == 0){
|
||||
if(!json || json.count === 0){
|
||||
self.el.hide();
|
||||
}
|
||||
else {
|
||||
|
||||
Object.each(json.suggestions, function(movie){
|
||||
|
||||
var m = new Block.Search.MovieItem(movie, {
|
||||
var m = new BlockSearchMovieItem(movie, {
|
||||
'onAdded': function(){
|
||||
self.afterAdded(m, movie)
|
||||
self.afterAdded(m, movie);
|
||||
}
|
||||
});
|
||||
m.data_container.grab(
|
||||
@@ -114,7 +114,7 @@ var SuggestList = new Class({
|
||||
'text': plot,
|
||||
'events': {
|
||||
'click': function(){
|
||||
this.toggleClass('full')
|
||||
this.toggleClass('full');
|
||||
}
|
||||
}
|
||||
}) : null
|
||||
|
||||
@@ -14,6 +14,7 @@ from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from .index import NotificationIndex, NotificationUnreadIndex
|
||||
from couchpotato.environment import Env
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -110,11 +111,11 @@ class CoreNotifier(Notification):
|
||||
|
||||
if limit_offset:
|
||||
splt = splitString(limit_offset)
|
||||
limit = splt[0]
|
||||
offset = 0 if len(splt) is 1 else splt[1]
|
||||
results = db.get_many('notification', limit = limit, offset = offset, with_doc = True)
|
||||
limit = tryInt(splt[0])
|
||||
offset = tryInt(0 if len(splt) is 1 else splt[1])
|
||||
results = db.all('notification', limit = limit, offset = offset, with_doc = True)
|
||||
else:
|
||||
results = db.get_many('notification', limit = 200, with_doc = True)
|
||||
results = db.all('notification', limit = 200, with_doc = True)
|
||||
|
||||
notifications = []
|
||||
for n in results:
|
||||
@@ -148,16 +149,15 @@ class CoreNotifier(Notification):
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
n = {
|
||||
'_t': 'notification',
|
||||
'time': int(time.time()),
|
||||
}
|
||||
|
||||
try:
|
||||
db = get_db()
|
||||
|
||||
data['notification_type'] = listener if listener else 'unknown'
|
||||
|
||||
n = {
|
||||
'_t': 'notification',
|
||||
'time': int(time.time()),
|
||||
'message': toUnicode(message)
|
||||
}
|
||||
n['message'] = toUnicode(message)
|
||||
|
||||
if data.get('sticky'):
|
||||
n['sticky'] = True
|
||||
@@ -170,7 +170,7 @@ class CoreNotifier(Notification):
|
||||
|
||||
return True
|
||||
except:
|
||||
log.error('Failed notify: %s', traceback.format_exc())
|
||||
log.error('Failed notify "%s": %s', (n, traceback.format_exc()))
|
||||
|
||||
def frontend(self, type = 'notification', data = None, message = None):
|
||||
if not data: data = {}
|
||||
@@ -190,7 +190,7 @@ class CoreNotifier(Notification):
|
||||
while len(self.listeners) > 0 and not self.shuttingDown():
|
||||
try:
|
||||
listener, last_id = self.listeners.pop()
|
||||
listener({
|
||||
IOLoop.current().add_callback(listener, {
|
||||
'success': True,
|
||||
'result': [notification],
|
||||
})
|
||||
|
||||
@@ -20,8 +20,8 @@ var NotificationBase = new Class({
|
||||
self.notifications = [];
|
||||
App.addEvent('load', function(){
|
||||
|
||||
App.block.notification = new Block.Menu(self, {
|
||||
'button_class': 'icon2.eye-open',
|
||||
App.block.notification = new BlockMenu(self, {
|
||||
'button_class': 'icon-notifications',
|
||||
'class': 'notification_menu',
|
||||
'onOpen': self.markAsRead.bind(self)
|
||||
});
|
||||
@@ -32,7 +32,7 @@ var NotificationBase = new Class({
|
||||
|
||||
window.addEvent('load', function(){
|
||||
self.startInterval.delay($(window).getSize().x <= 480 ? 2000 : 100, self);
|
||||
})
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
@@ -46,16 +46,15 @@ var NotificationBase = new Class({
|
||||
new Element('span.'+(result.read ? 'read' : '' )).adopt(
|
||||
new Element('span.message', {'html': result.message}),
|
||||
new Element('span.added', {'text': added.timeDiffInWords(), 'title': added})
|
||||
)
|
||||
, 'top');
|
||||
), 'top');
|
||||
self.notifications.include(result);
|
||||
|
||||
if((result.important !== undefined || result.sticky !== undefined) && !result.read){
|
||||
var sticky = true;
|
||||
App.trigger('message', [result.message, sticky, result])
|
||||
App.trigger('message', [result.message, sticky, result]);
|
||||
}
|
||||
else if(!result.read){
|
||||
self.setBadge(self.notifications.filter(function(n){ return !n.read}).length)
|
||||
self.setBadge(self.notifications.filter(function(n){ return !n.read; }).length);
|
||||
}
|
||||
|
||||
},
|
||||
@@ -63,7 +62,7 @@ var NotificationBase = new Class({
|
||||
setBadge: function(value){
|
||||
var self = this;
|
||||
self.badge.set('text', value);
|
||||
self.badge[value ? 'show' : 'hide']()
|
||||
self.badge[value ? 'show' : 'hide']();
|
||||
},
|
||||
|
||||
markAsRead: function(force_ids){
|
||||
@@ -72,13 +71,13 @@ var NotificationBase = new Class({
|
||||
|
||||
if(!force_ids) {
|
||||
var rn = self.notifications.filter(function(n){
|
||||
return !n.read && n.important === undefined
|
||||
return !n.read && n.important === undefined;
|
||||
});
|
||||
|
||||
var ids = [];
|
||||
ids = [];
|
||||
rn.each(function(n){
|
||||
ids.include(n._id)
|
||||
})
|
||||
ids.include(n._id);
|
||||
});
|
||||
}
|
||||
|
||||
if(ids.length > 0)
|
||||
@@ -87,9 +86,9 @@ var NotificationBase = new Class({
|
||||
'ids': ids.join(',')
|
||||
},
|
||||
'onSuccess': function(){
|
||||
self.setBadge('')
|
||||
self.setBadge('');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
@@ -104,7 +103,7 @@ var NotificationBase = new Class({
|
||||
self.request = Api.request('notification.listener', {
|
||||
'data': {'init':true},
|
||||
'onSuccess': function(json){
|
||||
self.processData(json, true)
|
||||
self.processData(json, true);
|
||||
}
|
||||
}).send();
|
||||
|
||||
@@ -112,7 +111,7 @@ var NotificationBase = new Class({
|
||||
|
||||
if(self.request && self.request.isRunning()){
|
||||
self.request.cancel();
|
||||
self.startPoll()
|
||||
self.startPoll();
|
||||
}
|
||||
|
||||
}, 120000);
|
||||
@@ -130,15 +129,15 @@ var NotificationBase = new Class({
|
||||
|
||||
self.request = Api.request('nonblock/notification.listener', {
|
||||
'onSuccess': function(json){
|
||||
self.processData(json, false)
|
||||
self.processData(json, false);
|
||||
},
|
||||
'data': {
|
||||
'last_id': self.last_id
|
||||
},
|
||||
'onFailure': function(){
|
||||
self.startPoll.delay(2000, self)
|
||||
self.startPoll.delay(2000, self);
|
||||
}
|
||||
}).send()
|
||||
}).send();
|
||||
|
||||
},
|
||||
|
||||
@@ -160,7 +159,7 @@ var NotificationBase = new Class({
|
||||
});
|
||||
|
||||
if(json.result.length > 0)
|
||||
self.last_id = json.result.getLast().message_id
|
||||
self.last_id = json.result.getLast().message_id;
|
||||
}
|
||||
|
||||
// Restart poll
|
||||
@@ -175,11 +174,11 @@ var NotificationBase = new Class({
|
||||
|
||||
var new_message = new Element('div', {
|
||||
'class': 'message' + (sticky ? ' sticky' : ''),
|
||||
'html': message
|
||||
'html': '<div class="inner">' + message + '</div>'
|
||||
}).inject(self.message_container, 'top');
|
||||
|
||||
setTimeout(function(){
|
||||
new_message.addClass('show')
|
||||
new_message.addClass('show');
|
||||
}, 10);
|
||||
|
||||
var hide_message = function(){
|
||||
@@ -211,8 +210,8 @@ var NotificationBase = new Class({
|
||||
|
||||
var setting_page = App.getPage('Settings');
|
||||
setting_page.addEvent('create', function(){
|
||||
Object.each(setting_page.tabs.notifications.groups, self.addTestButton.bind(self))
|
||||
})
|
||||
Object.each(setting_page.tabs.notifications.groups, self.addTestButton.bind(self));
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
@@ -235,20 +234,21 @@ var NotificationBase = new Class({
|
||||
|
||||
button.set('text', button_name);
|
||||
|
||||
var message;
|
||||
if(json.success){
|
||||
var message = new Element('span.success', {
|
||||
message = new Element('span.success', {
|
||||
'text': 'Notification successful'
|
||||
}).inject(button, 'after')
|
||||
}).inject(button, 'after');
|
||||
}
|
||||
else {
|
||||
var message = new Element('span.failed', {
|
||||
message = new Element('span.failed', {
|
||||
'text': 'Notification failed. Check logs for details.'
|
||||
}).inject(button, 'after')
|
||||
}).inject(button, 'after');
|
||||
}
|
||||
|
||||
(function(){
|
||||
message.destroy();
|
||||
}).delay(3000)
|
||||
}).delay(3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,6 +23,26 @@ config = [{
|
||||
'default': 'localhost',
|
||||
'description': 'Hostname/IP, default localhost'
|
||||
},
|
||||
{
|
||||
'name': 'username',
|
||||
'label': 'Username',
|
||||
'default': '',
|
||||
'description': 'Required for myPlex'
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'label': 'Password',
|
||||
'default': '',
|
||||
'type': 'password',
|
||||
'description': 'Required for myPlex'
|
||||
},
|
||||
{
|
||||
'name': 'auth_token',
|
||||
'label': 'Auth Token',
|
||||
'default': '',
|
||||
'advanced': True,
|
||||
'description': 'Required for myPlex'
|
||||
},
|
||||
{
|
||||
'name': 'clients',
|
||||
'default': '',
|
||||
|
||||
@@ -35,11 +35,46 @@ class PlexServer(object):
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
|
||||
data = self.plex.urlopen('%s/%s' % (
|
||||
self.createHost(self.plex.conf('media_server'), port = 32400),
|
||||
path
|
||||
))
|
||||
#Maintain support for older Plex installations without myPlex
|
||||
if not self.plex.conf('auth_token') and not self.plex.conf('username') and not self.plex.conf('password'):
|
||||
data = self.plex.urlopen('%s/%s' % (
|
||||
self.createHost(self.plex.conf('media_server'), port = 32400),
|
||||
path
|
||||
))
|
||||
else:
|
||||
#Fetch X-Plex-Token if it doesn't exist but a username/password do
|
||||
if not self.plex.conf('auth_token') and (self.plex.conf('username') and self.plex.conf('password')):
|
||||
import urllib2, base64
|
||||
log.info("Fetching a new X-Plex-Token from plex.tv")
|
||||
username = self.plex.conf('username')
|
||||
password = self.plex.conf('password')
|
||||
req = urllib2.Request("https://plex.tv/users/sign_in.xml", data="")
|
||||
authheader = "Basic %s" % base64.encodestring('%s:%s' % (username, password))[:-1]
|
||||
req.add_header("Authorization", authheader)
|
||||
req.add_header("X-Plex-Product", "Couchpotato Notifier")
|
||||
req.add_header("X-Plex-Client-Identifier", "b3a6b24dcab2224bdb101fc6aa08ea5e2f3147d6")
|
||||
req.add_header("X-Plex-Version", "1.0")
|
||||
|
||||
try:
|
||||
response = urllib2.urlopen(req)
|
||||
except urllib2.URLError, e:
|
||||
log.info("Error fetching token from plex.tv")
|
||||
|
||||
try:
|
||||
auth_tree = etree.parse(response)
|
||||
token = auth_tree.findall(".//authentication-token")[0].text
|
||||
self.plex.conf('auth_token', token)
|
||||
|
||||
except (ValueError, IndexError) as e:
|
||||
log.info("Error parsing plex.tv response: " + ex(e))
|
||||
|
||||
#Add X-Plex-Token header for myPlex support workaround
|
||||
data = self.plex.urlopen('%s/%s?X-Plex-Token=%s' % (
|
||||
self.createHost(self.plex.conf('media_server'), port = 32400),
|
||||
path,
|
||||
self.plex.conf('auth_token')
|
||||
))
|
||||
|
||||
if data_type == 'xml':
|
||||
return etree.fromstring(data)
|
||||
else:
|
||||
|
||||
@@ -16,7 +16,7 @@ var TwitterNotification = new Class({
|
||||
|
||||
var twitter_set = 0;
|
||||
fieldset.getElements('input[type=text]').each(function(el){
|
||||
twitter_set += +(el.get('value') != '');
|
||||
twitter_set += +(el.get('value') !== '');
|
||||
});
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ var TwitterNotification = new Class({
|
||||
}
|
||||
})
|
||||
).inject(fieldset.getElement('.test_button'), 'before');
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
68
couchpotato/core/notifications/webhook.py
Normal file
68
couchpotato/core/notifications/webhook.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import traceback
|
||||
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import getIdentifier
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
autoload = 'Webhook'
|
||||
|
||||
class Webhook(Notification):
|
||||
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
post_data = {
|
||||
'message': toUnicode(message)
|
||||
}
|
||||
|
||||
if getIdentifier(data):
|
||||
post_data.update({
|
||||
'imdb_id': getIdentifier(data)
|
||||
})
|
||||
|
||||
headers = {
|
||||
'Content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
|
||||
try:
|
||||
self.urlopen(self.conf('url'), headers = headers, data = post_data, show_error = False)
|
||||
return True
|
||||
except:
|
||||
log.error('Webhook notification failed: %s', traceback.format_exc())
|
||||
|
||||
return False
|
||||
|
||||
|
||||
config = [{
|
||||
'name': 'webhook',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'webhook',
|
||||
'label': 'Webhook',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': 0,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'url',
|
||||
'description': 'The URL to send notification data to when '
|
||||
},
|
||||
{
|
||||
'name': 'on_snatch',
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'advanced': True,
|
||||
'description': 'Also send message when movie is snatched.',
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -39,7 +39,7 @@ class Plugin(object):
|
||||
|
||||
_locks = {}
|
||||
|
||||
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20130519 Firefox/24.0'
|
||||
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:34.0) Gecko/20100101 Firefox/34.0'
|
||||
http_last_use = {}
|
||||
http_time_between_calls = 0
|
||||
http_failed_request = {}
|
||||
@@ -196,7 +196,7 @@ class Plugin(object):
|
||||
headers['Host'] = headers.get('Host', None)
|
||||
headers['User-Agent'] = headers.get('User-Agent', self.user_agent)
|
||||
headers['Accept-encoding'] = headers.get('Accept-encoding', 'gzip')
|
||||
headers['Connection'] = headers.get('Connection', 'close')
|
||||
headers['Connection'] = headers.get('Connection', 'keep-alive')
|
||||
headers['Cache-Control'] = headers.get('Cache-Control', 'max-age=0')
|
||||
|
||||
r = Env.get('http_opener')
|
||||
@@ -206,7 +206,7 @@ class Plugin(object):
|
||||
if self.http_failed_disabled[host] > (time.time() - 900):
|
||||
log.info2('Disabled calls to %s for 15 minutes because so many failed requests.', host)
|
||||
if not show_error:
|
||||
raise Exception('Disabled calls to %s for 15 minutes because so many failed requests')
|
||||
raise Exception('Disabled calls to %s for 15 minutes because so many failed requests' % host)
|
||||
else:
|
||||
return ''
|
||||
else:
|
||||
|
||||
@@ -87,6 +87,7 @@ class FileBrowser(Plugin):
|
||||
try:
|
||||
dirs = self.getDirectories(path = path, show_hidden = show_hidden)
|
||||
except:
|
||||
log.error('Failed getting directory "%s" : %s', (path, traceback.format_exc()))
|
||||
dirs = []
|
||||
|
||||
parent = os.path.dirname(path.rstrip(os.path.sep))
|
||||
|
||||
@@ -52,7 +52,7 @@ var CategoryListBase = new Class({
|
||||
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
@@ -71,7 +71,7 @@ var CategoryListBase = new Class({
|
||||
'events': {
|
||||
'click': function(){
|
||||
var category = self.createCategory();
|
||||
$(category).inject(self.category_container)
|
||||
$(category).inject(self.category_container);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -79,15 +79,15 @@ var CategoryListBase = new Class({
|
||||
|
||||
// Add categories, that aren't part of the core (for editing)
|
||||
Array.each(self.categories, function(category){
|
||||
$(category).inject(self.category_container)
|
||||
$(category).inject(self.category_container);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
getCategory: function(id){
|
||||
return this.categories.filter(function(category){
|
||||
return category.data._id == id
|
||||
}).pick()
|
||||
return category.data._id == id;
|
||||
}).pick();
|
||||
},
|
||||
|
||||
getAll: function(){
|
||||
@@ -97,7 +97,7 @@ var CategoryListBase = new Class({
|
||||
createCategory: function(data){
|
||||
var self = this;
|
||||
|
||||
var data = data || {'id': randomString()};
|
||||
data = data || {'id': randomString()};
|
||||
var category = new Category(data);
|
||||
self.categories.include(category);
|
||||
|
||||
@@ -225,7 +225,7 @@ var Category = new Class({
|
||||
)
|
||||
);
|
||||
|
||||
self.makeSortable()
|
||||
self.makeSortable();
|
||||
|
||||
},
|
||||
|
||||
@@ -248,7 +248,7 @@ var Category = new Class({
|
||||
}
|
||||
});
|
||||
|
||||
}).delay(delay || 0, self)
|
||||
}).delay(delay || 0, self);
|
||||
|
||||
},
|
||||
|
||||
@@ -262,13 +262,13 @@ var Category = new Class({
|
||||
'preferred' : self.el.getElement('.category_preferred input').get('value'),
|
||||
'ignored' : self.el.getElement('.category_ignored input').get('value'),
|
||||
'destination': self.data.destination
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
del: function(){
|
||||
var self = this;
|
||||
|
||||
if(self.data.label == undefined){
|
||||
if(self.data.label === undefined){
|
||||
self.el.destroy();
|
||||
return;
|
||||
}
|
||||
@@ -318,11 +318,11 @@ var Category = new Class({
|
||||
},
|
||||
|
||||
get: function(attr){
|
||||
return this.data[attr]
|
||||
return this.data[attr];
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
return this.el
|
||||
return this.el;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -8,20 +8,19 @@ Page.Log = new Class({
|
||||
has_tab: false,
|
||||
|
||||
log_items: [],
|
||||
report_text: '\
|
||||
### Steps to reproduce:\n\
|
||||
1. ..\n\
|
||||
2. ..\n\
|
||||
\n\
|
||||
### Information:\n\
|
||||
Movie(s) I have this with: ...\n\
|
||||
Quality of the movie being searched: ...\n\
|
||||
Providers I use: ...\n\
|
||||
Version of CouchPotato: {version}\n\
|
||||
Running on: ...\n\
|
||||
\n\
|
||||
### Logs:\n\
|
||||
```\n{issue}```',
|
||||
report_text: '### Steps to reproduce:\n'+
|
||||
'1. ..\n'+
|
||||
'2. ..\n'+
|
||||
'\n'+
|
||||
'### Information:\n'+
|
||||
'Movie(s) I have this with: ...\n'+
|
||||
'Quality of the movie being searched: ...\n'+
|
||||
'Providers I use: ...\n'+
|
||||
'Version of CouchPotato: {version}\n'+
|
||||
'Running on: ...\n'+
|
||||
'\n'+
|
||||
'### Logs:\n'+
|
||||
'```\n{issue}```',
|
||||
|
||||
indexAction: function () {
|
||||
var self = this;
|
||||
@@ -133,7 +132,7 @@ Running on: ...\n\
|
||||
new Element('span.message', {
|
||||
'text': log.message
|
||||
})
|
||||
))
|
||||
));
|
||||
});
|
||||
|
||||
return elements;
|
||||
|
||||
@@ -81,7 +81,7 @@ var Profile = new Class({
|
||||
'quality': quality,
|
||||
'finish': data.finish[nr] || false,
|
||||
'3d': data['3d'] ? data['3d'][nr] || false : false
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ var Profile = new Class({
|
||||
}
|
||||
});
|
||||
|
||||
}).delay(delay, self)
|
||||
}).delay(delay, self);
|
||||
|
||||
},
|
||||
|
||||
@@ -148,7 +148,7 @@ var Profile = new Class({
|
||||
});
|
||||
});
|
||||
|
||||
return data
|
||||
return data;
|
||||
},
|
||||
|
||||
addType: function(data){
|
||||
@@ -177,7 +177,7 @@ var Profile = new Class({
|
||||
var self = this;
|
||||
|
||||
return self.types.filter(function(type){
|
||||
return type.get('quality')
|
||||
return type.get('quality');
|
||||
});
|
||||
|
||||
},
|
||||
@@ -231,15 +231,15 @@ var Profile = new Class({
|
||||
},
|
||||
|
||||
get: function(attr){
|
||||
return this.data[attr]
|
||||
return this.data[attr];
|
||||
},
|
||||
|
||||
isCore: function(){
|
||||
return this.data.core
|
||||
return this.data.core;
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
return this.el
|
||||
return this.el;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -342,7 +342,7 @@ Profile.Type = new Class({
|
||||
'text': q.label,
|
||||
'value': q.identifier,
|
||||
'data-allow_3d': q.allow_3d
|
||||
}).inject(self.qualities)
|
||||
}).inject(self.qualities);
|
||||
});
|
||||
|
||||
self.qualities.set('value', self.data.quality);
|
||||
@@ -358,7 +358,7 @@ Profile.Type = new Class({
|
||||
'quality': self.qualities.get('value'),
|
||||
'finish': +self.finish.checked,
|
||||
'3d': +self['3d'].checked
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
get: function(key){
|
||||
|
||||
@@ -240,7 +240,7 @@ class QualityPlugin(Plugin):
|
||||
|
||||
# Add additional size score if only 1 size validated
|
||||
if len(size_scores) == 1:
|
||||
self.calcScore(score, size_scores[0], 8)
|
||||
self.calcScore(score, size_scores[0], 7)
|
||||
del size_scores
|
||||
|
||||
# Return nothing if all scores are <= 0
|
||||
@@ -491,6 +491,7 @@ class QualityPlugin(Plugin):
|
||||
'Movie Name.2014.720p Web-Dl Aac2.0 h264-ReleaseGroup': {'size': 3800, 'quality': 'brrip'},
|
||||
'Movie Name.2014.720p.WEBRip.x264.AC3-ReleaseGroup': {'size': 3000, 'quality': 'scr'},
|
||||
'Movie.Name.2014.1080p.HDCAM.-.ReleaseGroup': {'size': 5300, 'quality': 'cam'},
|
||||
'Movie.Name.2014.720p.HDSCR.4PARTS.MP4.AAC.ReleaseGroup': {'size': 2401, 'quality': 'scr'},
|
||||
}
|
||||
|
||||
correct = 0
|
||||
|
||||
@@ -12,20 +12,20 @@ var QualityBase = new Class({
|
||||
self.profiles = [];
|
||||
Array.each(data.profiles, self.createProfilesClass.bind(self));
|
||||
|
||||
App.addEvent('loadSettings', self.addSettings.bind(self))
|
||||
App.addEvent('loadSettings', self.addSettings.bind(self));
|
||||
|
||||
},
|
||||
|
||||
getProfile: function(id){
|
||||
return this.profiles.filter(function(profile){
|
||||
return profile.data._id == id
|
||||
}).pick()
|
||||
return profile.data._id == id;
|
||||
}).pick();
|
||||
},
|
||||
|
||||
// Hide items when getting profiles
|
||||
getActiveProfiles: function(){
|
||||
return Array.filter(this.profiles, function(profile){
|
||||
return !profile.data.hide
|
||||
return !profile.data.hide;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -37,7 +37,7 @@ var QualityBase = new Class({
|
||||
}
|
||||
catch(e){}
|
||||
|
||||
return {}
|
||||
return {};
|
||||
},
|
||||
|
||||
addSettings: function(){
|
||||
@@ -58,7 +58,7 @@ var QualityBase = new Class({
|
||||
self.createProfileOrdering();
|
||||
self.createSizes();
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
@@ -68,7 +68,7 @@ var QualityBase = new Class({
|
||||
createProfiles: function(){
|
||||
var self = this;
|
||||
|
||||
var non_core_profiles = Array.filter(self.profiles, function(profile){ return !profile.isCore() });
|
||||
var non_core_profiles = Array.filter(self.profiles, function(profile){ return !profile.isCore(); });
|
||||
var count = non_core_profiles.length;
|
||||
|
||||
self.settings.createGroup({
|
||||
@@ -81,7 +81,7 @@ var QualityBase = new Class({
|
||||
'events': {
|
||||
'click': function(){
|
||||
var profile = self.createProfilesClass();
|
||||
$(profile).inject(self.profile_container)
|
||||
$(profile).inject(self.profile_container);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -89,7 +89,7 @@ var QualityBase = new Class({
|
||||
|
||||
// Add profiles, that aren't part of the core (for editing)
|
||||
Array.each(non_core_profiles, function(profile){
|
||||
$(profile).inject(self.profile_container)
|
||||
$(profile).inject(self.profile_container);
|
||||
});
|
||||
|
||||
},
|
||||
@@ -97,7 +97,7 @@ var QualityBase = new Class({
|
||||
createProfilesClass: function(data){
|
||||
var self = this;
|
||||
|
||||
var data = data || {'id': randomString()};
|
||||
data = data || {'id': randomString()};
|
||||
var profile = new Profile(data);
|
||||
self.profiles.include(profile);
|
||||
|
||||
@@ -190,7 +190,6 @@ var QualityBase = new Class({
|
||||
'name': 'sizes'
|
||||
}).inject(self.content);
|
||||
|
||||
|
||||
new Element('div.item.head.ctrlHolder').adopt(
|
||||
new Element('span.label', {'text': 'Quality'}),
|
||||
new Element('span.min', {'text': 'Min'}),
|
||||
@@ -204,7 +203,7 @@ var QualityBase = new Class({
|
||||
'value': quality.size_min,
|
||||
'events': {
|
||||
'keyup': function(e){
|
||||
self.changeSize(quality.identifier, 'size_min', e.target.get('value'))
|
||||
self.changeSize(quality.identifier, 'size_min', e.target.get('value'));
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -212,11 +211,11 @@ var QualityBase = new Class({
|
||||
'value': quality.size_max,
|
||||
'events': {
|
||||
'keyup': function(e){
|
||||
self.changeSize(quality.identifier, 'size_max', e.target.get('value'))
|
||||
self.changeSize(quality.identifier, 'size_max', e.target.get('value'));
|
||||
}
|
||||
}
|
||||
})
|
||||
).inject(group)
|
||||
).inject(group);
|
||||
});
|
||||
|
||||
},
|
||||
@@ -235,7 +234,7 @@ var QualityBase = new Class({
|
||||
'value': value
|
||||
}
|
||||
});
|
||||
}).delay(300)
|
||||
}).delay(300);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class Renamer(Plugin):
|
||||
'desc': 'For the renamer to check for new files to rename in a folder',
|
||||
'params': {
|
||||
'async': {'desc': 'Optional: Set to 1 if you dont want to fire the renamer.scan asynchronous.'},
|
||||
'to_folder': {'desc': 'Optional: The folder to move releases to. Leave empty for default folder.'},
|
||||
'media_folder': {'desc': 'Optional: The folder of the media to scan. Keep empty for default renamer folder.'},
|
||||
'files': {'desc': 'Optional: Provide the release files if more releases are in the same media_folder, delimited with a \'|\'. Note that no dedicated release folder is expected for releases with one file.'},
|
||||
'base_folder': {'desc': 'Optional: The folder to find releases in. Leave empty for default folder.'},
|
||||
@@ -44,6 +45,13 @@ class Renamer(Plugin):
|
||||
},
|
||||
})
|
||||
|
||||
addApiView('renamer.progress', self.getProgress, docs = {
|
||||
'desc': 'Get the progress of current renamer scan',
|
||||
'return': {'type': 'object', 'example': """{
|
||||
'progress': False || True,
|
||||
}"""},
|
||||
})
|
||||
|
||||
addEvent('renamer.scan', self.scan)
|
||||
addEvent('renamer.check_snatched', self.checkSnatched)
|
||||
|
||||
@@ -67,11 +75,17 @@ class Renamer(Plugin):
|
||||
|
||||
return True
|
||||
|
||||
def getProgress(self, **kwargs):
|
||||
return {
|
||||
'progress': self.renaming_started
|
||||
}
|
||||
|
||||
def scanView(self, **kwargs):
|
||||
|
||||
async = tryInt(kwargs.get('async', 0))
|
||||
base_folder = kwargs.get('base_folder')
|
||||
media_folder = sp(kwargs.get('media_folder'))
|
||||
to_folder = kwargs.get('to_folder')
|
||||
|
||||
# Backwards compatibility, to be removed after a few versions :)
|
||||
if not media_folder:
|
||||
@@ -95,13 +109,13 @@ class Renamer(Plugin):
|
||||
})
|
||||
|
||||
fire_handle = fireEvent if not async else fireEventAsync
|
||||
fire_handle('renamer.scan', base_folder = base_folder, release_download = release_download)
|
||||
fire_handle('renamer.scan', base_folder = base_folder, release_download = release_download, to_folder = to_folder)
|
||||
|
||||
return {
|
||||
'success': True
|
||||
}
|
||||
|
||||
def scan(self, base_folder = None, release_download = None):
|
||||
def scan(self, base_folder = None, release_download = None, to_folder = None):
|
||||
if not release_download: release_download = {}
|
||||
|
||||
if self.isDisabled():
|
||||
@@ -115,7 +129,9 @@ class Renamer(Plugin):
|
||||
base_folder = sp(self.conf('from'))
|
||||
|
||||
from_folder = sp(self.conf('from'))
|
||||
to_folder = sp(self.conf('to'))
|
||||
|
||||
if not to_folder:
|
||||
to_folder = sp(self.conf('to'))
|
||||
|
||||
# Get media folder to process
|
||||
media_folder = sp(release_download.get('folder'))
|
||||
@@ -869,7 +885,9 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
#If information is not available, we don't want the tag in the filename
|
||||
replaced = replaced.replace('<' + x + '>', '')
|
||||
|
||||
replaced = self.replaceDoubles(replaced.lstrip('. '))
|
||||
if self.conf('replace_doubles'):
|
||||
replaced = self.replaceDoubles(replaced.lstrip('. '))
|
||||
|
||||
for x, r in replacements.items():
|
||||
if x in ['thename', 'namethe']:
|
||||
replaced = replaced.replace(six.u('<%s>') % toUnicode(x), toUnicode(r))
|
||||
@@ -1326,6 +1344,14 @@ config = [{
|
||||
'type': 'choice',
|
||||
'options': rename_options
|
||||
},
|
||||
{
|
||||
'advanced': True,
|
||||
'name': 'replace_doubles',
|
||||
'type': 'bool',
|
||||
'label': 'Clean Name',
|
||||
'description': ('Attempt to clean up double separaters due to missing data for fields.','Sometimes this eliminates wanted white space (see <a href="https://github.com/RuudBurger/CouchPotatoServer/issues/2782">#2782</a>).'),
|
||||
'default': True
|
||||
},
|
||||
{
|
||||
'name': 'unrar',
|
||||
'type': 'bool',
|
||||
|
||||
@@ -16,7 +16,7 @@ autoload = 'Subtitle'
|
||||
|
||||
class Subtitle(Plugin):
|
||||
|
||||
services = ['opensubtitles', 'thesubdb', 'subswiki', 'podnapisi']
|
||||
services = ['opensubtitles', 'thesubdb', 'subswiki', 'subscenter']
|
||||
|
||||
def __init__(self):
|
||||
addEvent('renamer.before', self.searchSingle)
|
||||
|
||||
@@ -35,7 +35,7 @@ Page.Userscript = new Class({
|
||||
if(json.error)
|
||||
self.frame.set('html', json.error);
|
||||
else {
|
||||
var item = new Block.Search.MovieItem(json.movie);
|
||||
var item = new BlockSearchMovieItem(json.movie);
|
||||
self.frame.adopt(item);
|
||||
item.showOptions();
|
||||
}
|
||||
@@ -54,7 +54,7 @@ var UserscriptSettingTab = new Class({
|
||||
initialize: function(){
|
||||
var self = this;
|
||||
|
||||
App.addEvent('loadSettings', self.addSettings.bind(self))
|
||||
App.addEvent('loadSettings', self.addSettings.bind(self));
|
||||
|
||||
},
|
||||
|
||||
@@ -88,7 +88,7 @@ var UserscriptSettingTab = new Class({
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stop();
|
||||
alert('Drag it to your bookmark ;)')
|
||||
alert('Drag it to your bookmark ;)');
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -41,7 +41,7 @@ Page.Wizard = new Class({
|
||||
'content': function(){
|
||||
return App.createUserscriptButtons().setStyles({
|
||||
'background-image': "url('https://couchpota.to/media/images/userscript.gif')"
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
'finish': {
|
||||
@@ -103,7 +103,7 @@ Page.Wizard = new Class({
|
||||
(function(){
|
||||
var sc = self.el.getElement('.wgroup_'+action);
|
||||
self.scroll.start(0, sc.getCoordinates().top-80);
|
||||
}).delay(1)
|
||||
}).delay(1);
|
||||
},
|
||||
|
||||
orderGroups: function(){
|
||||
@@ -114,8 +114,9 @@ Page.Wizard = new Class({
|
||||
|
||||
self.groups.each(function(group){
|
||||
|
||||
var group_container;
|
||||
if(self.headers[group]){
|
||||
var group_container = new Element('.wgroup_'+group, {
|
||||
group_container = new Element('.wgroup_'+group, {
|
||||
'styles': {
|
||||
'opacity': 0.2
|
||||
},
|
||||
@@ -127,7 +128,7 @@ Page.Wizard = new Class({
|
||||
if(self.headers[group].include){
|
||||
self.headers[group].include.each(function(inc){
|
||||
group_container.addClass('wgroup_'+inc);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
var content = self.headers[group].content;
|
||||
@@ -148,7 +149,7 @@ Page.Wizard = new Class({
|
||||
tab_navigation = [];
|
||||
self.headers[group].include.each(function(inc){
|
||||
tab_navigation.include(tabs.getElement('.t_'+inc));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if(tab_navigation && group_container){
|
||||
@@ -165,7 +166,7 @@ Page.Wizard = new Class({
|
||||
'href': App.createUrl('wizard/'+group),
|
||||
'text': (self.headers[group].label || group).capitalize()
|
||||
})
|
||||
).inject(tabs)
|
||||
).inject(tabs);
|
||||
|
||||
}
|
||||
else
|
||||
@@ -190,7 +191,7 @@ Page.Wizard = new Class({
|
||||
}
|
||||
|
||||
if(self.headers[group] && self.headers[group].event)
|
||||
self.headers[group].event.call()
|
||||
self.headers[group].event.call();
|
||||
});
|
||||
|
||||
// Remove toggle
|
||||
@@ -220,22 +221,22 @@ Page.Wizard = new Class({
|
||||
g.tween('opacity', 1);
|
||||
};
|
||||
|
||||
if(nr == 0)
|
||||
if(nr === 0)
|
||||
func();
|
||||
|
||||
new ScrollSpy( {
|
||||
min: function(){
|
||||
var c = g.getCoordinates();
|
||||
var top = c.top-(window.getSize().y/2);
|
||||
return top > minimum ? minimum : top
|
||||
return top > minimum ? minimum : top;
|
||||
},
|
||||
max: function(){
|
||||
var c = g.getCoordinates();
|
||||
return c.top+(c.height/2)
|
||||
return c.top+(c.height/2);
|
||||
},
|
||||
onEnter: func,
|
||||
onLeave: function(){
|
||||
g.tween('opacity', 0.2)
|
||||
g.tween('opacity', 0.2);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,7 +157,15 @@ class Settings(object):
|
||||
values[section] = {}
|
||||
for option in self.p.items(section):
|
||||
(option_name, option_value) = option
|
||||
|
||||
is_password = False
|
||||
try: is_password = self.types[section][option_name] == 'password'
|
||||
except: pass
|
||||
|
||||
values[section][option_name] = self.get(option_name, section)
|
||||
if is_password and values[section][option_name]:
|
||||
values[section][option_name] = len(values[section][option_name]) * '*'
|
||||
|
||||
return values
|
||||
|
||||
def save(self):
|
||||
|
||||
@@ -244,11 +244,13 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
|
||||
# Basic config
|
||||
host = Env.setting('host', default = '0.0.0.0')
|
||||
# app.debug = development
|
||||
host6 = Env.setting('host6', default = '::')
|
||||
|
||||
config = {
|
||||
'use_reloader': reloader,
|
||||
'port': tryInt(Env.setting('port', default = 5050)),
|
||||
'host': host if host and len(host) > 0 else '0.0.0.0',
|
||||
'host6': host6 if host6 and len(host6) > 0 else '::',
|
||||
'ssl_cert': Env.setting('ssl_cert', default = None),
|
||||
'ssl_key': Env.setting('ssl_key', default = None),
|
||||
}
|
||||
@@ -331,6 +333,10 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
while try_restart:
|
||||
try:
|
||||
server.listen(config['port'], config['host'])
|
||||
|
||||
try: server.listen(config['port'], config['host6'])
|
||||
except: log.info2('Tried to bind to IPV6 but failed')
|
||||
|
||||
loop.start()
|
||||
server.close_all_connections()
|
||||
server.stop()
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 213 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,242 +3,36 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata></metadata>
|
||||
<defs>
|
||||
<font id="lobster_1.4regular" horiz-adv-x="1034" >
|
||||
<font id="lobster_14regular" horiz-adv-x="2048" >
|
||||
<font-face units-per-em="2048" ascent="1638" descent="-410" />
|
||||
<missing-glyph horiz-adv-x="444" />
|
||||
<glyph unicode="fi" horiz-adv-x="1093" d="M-424 -283q0 53 21 96.5t51.5 72t82 55t93.5 41.5t106 34l228 1078q93 440 479 440q367 0 367 -236q0 -86 -46 -125.5t-116 -39.5q-52 0 -87.5 25t-35.5 85q0 37 19.5 68.5t49.5 31.5q15 0 17 -2q-1 39 -32 60.5t-81 21.5q-168 0 -217 -231l-35 -168h164l-16 -82h-164 l-234 -1106q-21 -101 -62 -173t-93 -108t-101.5 -51.5t-103.5 -15.5q-114 0 -184 62t-70 167zM-311 -248q0 -32 17.5 -52t34 -25t32.5 -5q35 0 68 39.5t50 120.5l25 119q-102 -32 -164.5 -83.5t-62.5 -113.5zM530 233q0 51 17 134l139 657h295l-147 -696q-9 -40 -9 -66 q0 -42 20 -59t64 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -159 61t-61 184z" />
|
||||
<glyph unicode="fl" horiz-adv-x="1122" d="M-428 -291q0 55 19.5 99t48 73t79 55.5t94.5 42.5t113 37l246 1170q18 84 50 148t69 101t83 60.5t86.5 31t84.5 7.5q86 0 185.5 -22t170.5 -47.5t186 -71.5l-225 -1065q-8 -36 -8 -66q0 -42 20 -59t64 -17q59 0 109.5 51.5t74.5 129.5h86q-33 -95 -79 -167t-90.5 -111 t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184q0 56 16 134l213 1005q-35 15 -87.5 24t-79.5 9q-43 0 -82 -35t-58 -127l-45 -219h191l-17 -82h-190l-234 -1106q-18 -84 -49.5 -148t-68.5 -101.5t-83 -60.5t-86 -30.5t-85 -7.5q-97 0 -169.5 62t-72.5 159z M-315 -262q0 -23 25 -45.5t57 -22.5q36 0 69.5 38.5t50.5 121.5l25 119q-109 -36 -168 -85t-59 -126z" />
|
||||
<glyph unicode="ffi" horiz-adv-x="1564" d="M-428 -291q0 55 19.5 99t48 73t79 55.5t94.5 42.5t113 37l246 1170q18 84 50 148t68.5 101.5t82.5 60.5t86.5 30.5t85.5 7.5q170 0 252 -107q119 107 309 107q367 0 367 -236q0 -86 -46 -125.5t-116 -39.5q-52 0 -87.5 25t-35.5 85q0 37 19.5 68.5t49.5 31.5q15 0 17 -2 q-1 39 -32 60.5t-81 21.5q-168 0 -217 -231l-35 -168h543l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -159 61t-61 184q0 51 17 134l121 575h-246l-234 -1106 q-21 -101 -62 -173t-93 -108t-101.5 -51.5t-103.5 -15.5q-75 0 -133.5 29t-89.5 80q-99 -109 -262 -109q-97 0 -169.5 62t-72.5 159zM-303 -262q0 -23 25 -45.5t57 -22.5q36 0 70 38.5t51 121.5l24 119q-109 -36 -168 -85t-59 -126zM145 -293q5 -36 29 -50.5t47 -14.5 q45 0 81.5 45.5t57.5 142.5l48 223l-183 -37l-39 -180q-13 -65 -41 -129zM238 74l180 30l176 838h-172zM438 1024h174l15 70q32 153 102 256q-31 34 -82 34q-78 0 -119.5 -56t-56.5 -136z" />
|
||||
<glyph unicode="ffl" horiz-adv-x="1597" d="M-428 -291q0 55 19.5 99t48 73t79 55.5t94.5 42.5t113 37l246 1170q18 84 50 148t68.5 101.5t82.5 60.5t86.5 30.5t85.5 7.5q152 0 235 -86q94 86 240 86q86 0 186 -22t171 -47.5t186 -71.5l-226 -1065q-8 -36 -8 -66q0 -42 20 -59t64 -17q59 0 109.5 51.5t74.5 129.5h86 q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184q0 51 17 134l213 1005q-35 15 -88 24t-80 9q-43 0 -82 -35t-58 -127l-45 -219h191l-17 -82h-190l-236 -1106q-21 -101 -62 -173t-93 -108t-101.5 -51.5t-103.5 -15.5q-76 0 -134 29 t-89 82q-98 -111 -262 -111q-97 0 -169.5 62t-72.5 159zM-303 -262q0 -23 25 -45.5t57 -22.5q36 0 70 38.5t51 121.5l24 119q-109 -36 -168 -85t-59 -126zM145 -291q4 -37 28.5 -52t47.5 -15q45 0 81.5 45.5t57.5 142.5l48 223l-183 -37l-39 -180q-13 -63 -41 -127zM238 74 l180 30l178 838h-174zM438 1024h174l35 162q19 99 66 178q-27 20 -66 20q-78 0 -119.5 -56t-56.5 -136z" />
|
||||
<glyph horiz-adv-x="2048" />
|
||||
<glyph horiz-adv-x="2048" />
|
||||
<glyph unicode="
" horiz-adv-x="2048" />
|
||||
<glyph />
|
||||
<glyph />
|
||||
<glyph unicode="
" />
|
||||
<glyph unicode=" " horiz-adv-x="444" />
|
||||
<glyph unicode="	" horiz-adv-x="444" />
|
||||
<glyph unicode=" " horiz-adv-x="444" />
|
||||
<glyph unicode="!" horiz-adv-x="526" d="M70 164q0 67 47.5 114.5t115.5 47.5q67 0 114.5 -47t47.5 -115t-47.5 -116t-114.5 -48t-115 48t-48 116zM201 473l145 961h295l-266 -961h-174z" />
|
||||
<glyph unicode=""" horiz-adv-x="860" d="M127 1149l125 387h215l-186 -387h-154zM449 1149l124 387h215l-186 -387h-153z" />
|
||||
<glyph unicode="#" horiz-adv-x="1349" d="M92 227l43 209h213l43 232h-209l43 209h203l107 593h180l-133 -593h215l106 593h180l-133 -593h213l-41 -209h-217l-51 -232h219l-41 -209h-225l-62 -282h-116l51 282h-242l-61 -282h-117l51 282h-219zM485 436h232l43 232h-223z" />
|
||||
<glyph unicode="$" horiz-adv-x="825" d="M49 455q0 78 43 127t105 49q41 0 64.5 -15t23.5 -36q-82 -14 -82 -152q0 -42 25 -75.5t61 -49.5l73 402q-16 14 -49 39.5t-51.5 41t-44 41t-39 46.5t-23.5 50.5t-10 61.5q0 109 98 186t218 82l39 217h118l-51 -223q178 -28 178 -170q0 -71 -26.5 -113t-71.5 -42 q-42 0 -80 47q49 28 49 106q0 26 -16.5 51.5t-48.5 38.5l-74 -329l46.5 -31t43.5 -31t46 -38t36.5 -41t33 -50t18 -55.5t8.5 -67.5q0 -142 -102 -221t-267 -82l-59 -274h-56l49 276q-103 8 -164 64t-61 170zM340 1024q0 -68 57 -125l49 277q-55 -11 -80.5 -55t-25.5 -97z M358 295q69 8 102.5 59.5t33.5 124.5q0 82 -60 156z" />
|
||||
<glyph unicode="%" horiz-adv-x="1318" d="M219 999q0 187 91 313t241 126q87 0 127.5 -58.5t40.5 -173.5q0 -68 -20.5 -142.5t-57 -142t-96 -111.5t-129.5 -44q-113 0 -155 57t-42 176zM248 0l790 1536h148l-791 -1536h-147zM367 975q0 -133 55 -133q53 0 93 56t60.5 145t20.5 194q0 62 -10.5 95.5t-40.5 33.5 q-50 0 -106.5 -83.5t-67.5 -219.5q-4 -50 -4 -88zM727 233q0 187 91 313t241 126q90 0 139 -60t49 -172q0 -55 -10.5 -112.5t-35 -117t-60.5 -106t-92.5 -75.5t-124.5 -29q-113 0 -155 57t-42 176zM864 209q0 -133 56 -133q53 0 91.5 52.5t57 137.5t18.5 191q0 139 -57 139 q-49 0 -100 -81t-62 -218q-4 -50 -4 -88z" />
|
||||
<glyph unicode="&" horiz-adv-x="1396" d="M23 401q0 102 39.5 193.5t125.5 163.5t205 96q-101 55 -152 137t-51 174q0 71 31 137t87 118.5t144 84t193 31.5q74 0 139.5 -15.5t120 -46.5t86 -85.5t31.5 -126.5q0 -87 -34.5 -138t-108.5 -51q-71 0 -117 53q35 10 62.5 67t27.5 101q0 15 -1 29t-9 38.5t-21 41.5 t-40 30t-64 13q-110 0 -180 -73.5t-70 -178.5q0 -96 66.5 -186.5t203.5 -151.5q-183 -75 -269.5 -185.5t-86.5 -226.5q0 -112 71 -192t169 -80q57 0 109.5 19t104 60.5t92.5 120t64 185.5q-18 1 -47.5 4.5t-53 5.5t-44.5 2q-134 0 -193 -92l-14 2q32 130 136.5 209.5 t258.5 79.5q29 0 80 -5t76 -5q61 0 115 22.5t79 65.5h13q0 -88 -93.5 -176t-218.5 -109q-25 -91 -56.5 -167t-63 -131.5t-71 -99.5t-73.5 -72t-78 -48.5t-76.5 -31t-77.5 -16t-73 -6.5t-70 -1q-103 0 -185 34.5t-133 93t-78 131.5t-27 154z" />
|
||||
<glyph unicode="'" horiz-adv-x="548" d="M209 1149l125 387h215l-187 -387h-153z" />
|
||||
<glyph unicode="(" horiz-adv-x="894" d="M283 205q0 200 48 406.5t133 386.5t211.5 323.5t274.5 214.5l27 -82q-148 -74 -275 -284t-198.5 -483t-71.5 -537q0 -381 150 -603l-68 -59q-116 128 -173.5 314t-57.5 403z" />
|
||||
<glyph unicode=")" horiz-adv-x="845" d="M-59 -430q148 74 274.5 283t198 482t71.5 539q0 382 -149 603l67 59q116 -128 174 -314t58 -403q0 -270 -81 -538t-235.5 -483t-351.5 -310z" />
|
||||
<glyph unicode="*" horiz-adv-x="1054" d="M176 1026l223 129l-223 129l62 107l219 -127v276h131v-276l219 127l61 -107l-223 -129l223 -129l-61 -106l-219 127v-277h-131v277l-219 -127z" />
|
||||
<glyph unicode="+" horiz-adv-x="1054" d="M109 420l49 229h262l55 250h232l-56 -250h260l-47 -229h-262l-55 -252h-232l56 252h-262z" />
|
||||
<glyph unicode="," horiz-adv-x="442" d="M49 154q0 68 47.5 115.5t114.5 47.5q66 0 102.5 -43t36.5 -112q0 -97 -72.5 -216.5t-193.5 -203.5q-18 13 -18 35q0 25 16.5 50t36.5 42.5t36.5 43t16.5 50.5q0 23 -27 41q-96 63 -96 150z" />
|
||||
<glyph unicode="-" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="." horiz-adv-x="538" d="M63 154q0 68 47.5 115.5t114.5 47.5q68 0 116 -48t48 -115t-48 -114.5t-116 -47.5q-67 0 -114.5 47t-47.5 115z" />
|
||||
<glyph unicode="/" horiz-adv-x="636" d="M23 -512l434 2048h147l-434 -2048h-147z" />
|
||||
<glyph unicode="0" horiz-adv-x="1118" d="M61 520q0 167 31.5 320t92.5 279.5t146 219.5t198.5 145t242.5 52q188 0 276.5 -135.5t88.5 -399.5q0 -103 -19.5 -217t-56 -228.5t-93.5 -217.5t-127 -182.5t-162.5 -126.5t-193.5 -47q-104 0 -179 24t-122.5 66.5t-75 112t-37.5 148t-10 187.5zM375 463 q0 -309 121 -309q84 0 156 86t118 226t72 313.5t26 353.5q0 70 -4 118.5t-15 92t-33.5 65t-57.5 21.5q-47 0 -105.5 -56t-114 -152t-98.5 -244t-55 -312q-10 -138 -10 -203z" />
|
||||
<glyph unicode="1" horiz-adv-x="649" d="M43 0l270 1278h-194l24 94q112 0 233.5 31t287.5 129l-326 -1532h-295z" />
|
||||
<glyph unicode="2" horiz-adv-x="1007" d="M-23 61q0 91 37 178t96.5 160.5t131.5 147.5t144 151.5t131.5 159t96.5 184.5t37 213q0 72 -39 120t-86 48q-96 0 -131 -41t-35 -125q0 -43 19.5 -85.5t58.5 -53.5q-78 -69 -155 -69q-65 0 -106.5 48.5t-41.5 135.5q0 43 14 84.5t46 81.5t79.5 69.5t120.5 47.5t162 18 q389 0 389 -330q0 -94 -31 -180.5t-84 -157t-118 -134.5t-138 -123t-138.5 -112t-126 -113t-93.5 -114h10q56 0 183 -18.5t208 -18.5q30 0 54 2t47 11t36 13.5t35.5 23t30 24t35.5 33t37 35.5q2 -33 2 -90q0 -81 -10 -134t-36 -94.5t-74.5 -60.5t-123.5 -19q-113 0 -259 37 t-249 37q-21 0 -51 -10.5t-63.5 -26.5t-46.5 -20q-5 29 -5 67z" />
|
||||
<glyph unicode="3" horiz-adv-x="1052" d="M12 338q0 132 64.5 212.5t183.5 80.5q138 0 160 -105q-180 -14 -180 -221q0 -106 43 -144t112 -38q127 0 206 103t79 243q0 126 -68.5 232.5t-203.5 154.5q83 23 151.5 68.5t110.5 101.5t65 115t23 114q0 74 -40.5 121t-115.5 47q-73 0 -117.5 -43.5t-44.5 -115.5 q0 -48 19.5 -91.5t58.5 -54.5q-75 -69 -156 -69q-64 0 -105.5 46.5t-41.5 127.5q0 138 121.5 224.5t302.5 86.5t281 -86t100 -213q0 -106 -72.5 -209.5t-208.5 -171.5q129 -42 193.5 -132.5t64.5 -205.5q0 -74 -26 -149.5t-77 -143t-119.5 -120t-160.5 -83t-194 -30.5 q-45 0 -89 5.5t-105 27t-105.5 56.5t-76.5 102.5t-32 156.5z" />
|
||||
<glyph unicode="4" horiz-adv-x="974" d="M16 563q22 67 77 184.5t101 214.5t83.5 222.5t37.5 228.5q0 66 -14 123q95 0 161 -51.5t66 -143.5q0 -90 -33.5 -181.5t-75 -154t-101.5 -149t-89 -141.5h320l166 780l303 41l-176 -821h131l-41 -152h-123l-119 -563h-295l121 563h-500z" />
|
||||
<glyph unicode="5" horiz-adv-x="1058" d="M31 285q0 77 24 135.5t63.5 91t84.5 48t94 15.5q125 0 145 -104q-84 -9 -134 -59t-50 -138q0 -42 15 -72.5t41 -46t53.5 -22t58.5 -6.5q81 0 142 58t89.5 142t28.5 177q0 145 -60 221t-165 76q-122 0 -305 -115l168 817q38 -4 100 -12t97.5 -12t83 -7.5t87.5 -3.5 q164 0 332 66q4 -46 4 -68q0 -107 -56 -172t-179 -65q-64 0 -195.5 24.5t-146.5 26.5l-82 -371q134 64 256 64q90 0 160.5 -34t113 -93t64.5 -134.5t22 -162.5q0 -110 -35 -207.5t-104.5 -177.5t-186.5 -127t-268 -47q-360 0 -360 295z" />
|
||||
<glyph unicode="6" horiz-adv-x="1017" d="M33 395q0 49 2 76q12 187 47.5 347.5t96.5 293.5t144.5 226.5t196 145.5t246.5 52q109 0 172 -51.5t63 -118.5q0 -55 -46.5 -94t-135.5 -43q33 29 33 74q0 43 -29.5 76.5t-72.5 33.5q-11 0 -24.5 -2t-39 -12t-50.5 -26t-57.5 -48.5t-61.5 -76.5t-60.5 -112.5t-57 -152 t-48 -201t-36.5 -254.5q-10 -102 -10 -155q0 -74 11 -120t35.5 -68t50.5 -28.5t67 -6.5q65 0 130.5 61.5t108 163t42.5 207.5q0 49 -8 90t-34 74.5t-67 33.5q-104 0 -180 -155q-41 58 -41 118q0 88 85 146.5t204 58.5q70 0 125 -30t87 -79.5t48 -108t16 -120.5 q0 -130 -45.5 -246.5t-121 -197.5t-176.5 -128.5t-210 -47.5q-96 0 -167.5 22t-115 57.5t-70.5 89.5t-36.5 110t-9.5 126z" />
|
||||
<glyph unicode="7" horiz-adv-x="950" d="M68 256q0 70 19 146t47.5 145t80.5 154.5t98.5 152.5t122.5 162t132 161t145 169q-89 12 -174 12q-292 0 -424 -146q-17 73 -17 119q0 103 66 154t213 51h672q-124 -185 -352 -545.5t-265 -447.5q-65 -147 -65 -264q0 -62 22.5 -108t77.5 -91q-30 -80 -188 -80 q-103 0 -157 55t-54 201z" />
|
||||
<glyph unicode="8" horiz-adv-x="1021" d="M-16 401q0 149 95 276t273 177q-88 51 -131 123.5t-43 155.5q0 74 34 146t92.5 128.5t145.5 91.5t185 35q185 0 286 -85t101 -212q0 -107 -75 -211.5t-212 -171.5q125 -38 186.5 -123.5t61.5 -195.5q0 -94 -43 -190.5t-116 -175.5t-181 -129t-227 -50q-102 0 -185.5 34.5 t-136.5 92t-81.5 131t-28.5 153.5zM281 334q0 -88 37.5 -142.5t107.5 -54.5q101 0 168.5 103.5t67.5 240.5q0 87 -32.5 167t-97.5 136q-81 -54 -139.5 -134t-85 -160.5t-26.5 -155.5zM446 1167q0 -70 27.5 -134.5t81.5 -108.5q97 64 152 168.5t55 199.5q0 64 -26 104t-74 40 q-97 0 -156.5 -80.5t-59.5 -188.5z" />
|
||||
<glyph unicode="9" horiz-adv-x="1001" d="M59 184q0 69 38.5 110.5t138.5 41.5q-29 -29 -29 -70q0 -44 31.5 -79t76.5 -35q20 0 38.5 2.5t59 19t74.5 46.5t77 93t75 149.5t59.5 224.5t40.5 310q4 78 4 107q0 93 -12 153.5t-37.5 91t-55.5 41.5t-75 11q-65 0 -125.5 -66.5t-97.5 -170t-37 -208.5q0 -36 5 -70 t17 -67.5t36 -53.5t57 -20q61 0 103.5 27.5t68.5 95.5q43 -52 43 -102q0 -77 -80.5 -132.5t-194.5 -55.5q-73 0 -130.5 32.5t-91 85.5t-50 114.5t-16.5 125.5q0 167 70 305t191.5 216.5t268.5 78.5q92 0 162.5 -26t114 -68.5t70.5 -106t37.5 -131t10.5 -149.5 q0 -308 -82.5 -548.5t-241 -378.5t-370.5 -138q-111 0 -176.5 59t-65.5 135z" />
|
||||
<glyph unicode=":" horiz-adv-x="546" d="M84 276q0 68 47.5 116t114.5 48q68 0 116 -48t48 -116q0 -67 -48 -114t-116 -47t-115 47t-47 114zM186 768q0 68 47.5 116t114.5 48q68 0 116 -48t48 -116q0 -67 -48 -114.5t-116 -47.5t-115 47t-47 115z" />
|
||||
<glyph unicode=";" horiz-adv-x="565" d="M94 186q0 68 47.5 116t114.5 48q66 0 102.5 -43t36.5 -112q0 -97 -72.5 -216.5t-193.5 -203.5q-18 13 -18 35q0 25 16.5 50t36.5 42.5t36.5 43t16.5 50.5q0 23 -27 41q-96 63 -96 149zM193 678q0 68 47 116t114 48q68 0 116 -48t48 -116q0 -67 -48 -114.5t-116 -47.5 q-67 0 -114 47t-47 115z" />
|
||||
<glyph unicode="<" horiz-adv-x="735" d="M41 487l18 84l629 308l-20 -113l-494 -246l397 -196l-24 -117z" />
|
||||
<glyph unicode="=" horiz-adv-x="1054" d="M66 207l49 229h753l-47 -229h-755zM156 647l49 230h753l-47 -230h-755z" />
|
||||
<glyph unicode=">" horiz-adv-x="704" d="M31 207l28 117l492 196l-397 246l18 113l504 -308l-23 -110z" />
|
||||
<glyph unicode="?" horiz-adv-x="903" d="M98 164q0 67 48 114.5t116 47.5t115 -47t47 -115t-47.5 -116t-114.5 -48q-68 0 -116 48t-48 116zM113 1104q0 55 24.5 110.5t73.5 106t135.5 82t196.5 31.5q102 0 169 -18.5t100.5 -55t45.5 -77t12 -97.5q0 -81 -21.5 -147.5t-58.5 -113.5t-82 -88t-94 -79t-93.5 -77 t-83 -92t-58.5 -114h-135q0 58 20.5 113t53.5 99.5t72.5 88.5t79 88.5t72.5 91t53.5 105t20.5 121.5q0 71 -32 106t-80 35q-62 0 -113 -48.5t-51 -121.5q0 -42 19.5 -83t58.5 -52q-26 -42 -66 -64t-84 -22q-64 0 -109.5 45t-45.5 127z" />
|
||||
<glyph unicode="@" horiz-adv-x="1150" d="M-16 336q0 219 94.5 404.5t255 293t348.5 107.5q44 0 82.5 -6t85 -26.5t79 -53.5t55 -94t22.5 -142q0 -130 -50.5 -256t-132 -205t-166.5 -79q-64 0 -90 47q-58 -80 -121 -80q-131 0 -131 141q0 56 19 137t51.5 163.5t86.5 140t114 57.5q49 0 78 -37l8 33l112 -13 l-92 -393q-8 -32 -8 -47q0 -39 27 -39q39 0 77 92t59 198t21 169q0 162 -180 162q-87 0 -165 -39t-134 -104.5t-97.5 -151t-62 -179.5t-20.5 -188q0 -129 52.5 -185.5t197.5 -56.5q111 0 182.5 33t124.5 103q47 -20 47 -92q0 -99 -113 -163.5t-301 -64.5q-100 0 -173.5 21 t-129.5 67.5t-84 128t-28 197.5zM449 434q0 -63 36 -63q24 0 68 28q0 22 6 43l88 340q-14 17 -37 17q-42 0 -75 -44.5t-50.5 -107.5t-26.5 -119.5t-9 -93.5z" />
|
||||
<glyph unicode="A" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486z" />
|
||||
<glyph unicode="B" horiz-adv-x="1300" d="M12 993q0 104 62.5 204t164 174t238.5 119.5t279 45.5q109 0 194.5 -27t137 -73.5t78 -103.5t26.5 -122q0 -93 -54 -181t-151 -144q123 -25 182.5 -115t59.5 -215q0 -64 -13.5 -137.5t-47 -155t-82.5 -146.5t-128.5 -108t-177.5 -43q-37 0 -69.5 6.5t-64 22t-50 47.5 t-18.5 76q0 51 26 119q71 -74 137 -74q53 0 94.5 35t64 91.5t33.5 117.5t11 124q0 261 -194 261q-30 0 -95 -7l-168 -784h-294l278 1303l303 40l-96 -454h12q79 0 148 51t108 127.5t39 153.5q0 96 -66.5 158.5t-203.5 62.5q-73 0 -145 -19t-138.5 -59.5t-117.5 -98 t-81 -140.5t-30 -181q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-219 0 -219 188z" />
|
||||
<glyph unicode="C" horiz-adv-x="1017" d="M39 471q0 83 11 176t37 197.5t64 202t96.5 187t129.5 156.5t168.5 106.5t208.5 39.5q75 0 135.5 -15t107.5 -47t73 -85.5t26 -126.5q0 -87 -35 -138t-109 -51q-70 0 -116 53q37 19 65.5 75t28.5 114q0 55 -30 90t-95 35q-108 0 -208.5 -152t-159 -374t-58.5 -431 q0 -59 6.5 -105.5t23.5 -87.5t44.5 -68t70.5 -42.5t101 -15.5q119 0 223.5 54.5t175.5 150.5l47 -21q-39 -98 -110 -174.5t-154 -119.5t-164.5 -65t-155.5 -22q-232 0 -340 116.5t-108 387.5z" />
|
||||
<glyph unicode="D" horiz-adv-x="1357" d="M12 995q0 104 61 203t163 173t244 119.5t294 45.5q144 0 252 -47.5t171 -131t93.5 -186.5t30.5 -225q0 -174 -46.5 -350.5t-123 -313.5t-182 -223t-215.5 -86q-124 0 -250 109l-17 -82h-294l278 1303l303 40l-246 -1148q55 -41 117 -41q71 0 134.5 54t109.5 144.5 t80.5 206.5t51.5 243.5t17 252.5q0 201 -79 294t-220 93q-234 0 -385 -135t-151 -363q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-219 0 -219 190z" />
|
||||
<glyph unicode="E" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138z" />
|
||||
<glyph unicode="F" horiz-adv-x="940" d="M18 1004q0 84 23.5 160t77 144.5t133 118.5t199 79.5t266.5 29.5q35 0 152.5 -12.5t187.5 -12.5q105 0 184 25q-5 -14 -13.5 -45t-14.5 -50t-20 -46.5t-31 -45t-47 -34.5t-68 -25q-104 0 -287 39l-86 -407h299l-33 -152h-299l-166 -770h-295l295 1368q-63 -2 -109 -23 t-82.5 -67.5t-55.5 -130t-19 -203.5q0 -46 6.5 -76t14.5 -44.5t8 -18.5q-110 0 -165 46t-55 153z" />
|
||||
<glyph unicode="G" horiz-adv-x="1183" d="M39 748q0 118 31 232t93 214.5t147.5 176.5t203 120.5t250.5 44.5q74 0 140 -15.5t121 -46.5t87.5 -85.5t32.5 -126.5q0 -87 -35 -138t-109 -51q-95 0 -141 53q35 10 62.5 67t27.5 101q0 14 -2 28.5t-10.5 37t-22.5 39t-41.5 29t-64.5 12.5q-109 0 -216 -108.5 t-173 -274.5t-66 -326q0 -148 59 -243t179 -95q117 0 174 76l88 389l264 -2l-168 -770q-27 -125 -64 -213t-97 -158t-150 -104.5t-213 -34.5q-137 0 -211 83.5t-74 199.5q0 112 67.5 196t182.5 84q31 0 53 -1.5t52.5 -9t49 -21.5t32.5 -40.5t14 -64.5q0 -36 -12 -82 q-37 31 -64 42t-59 11q-67 0 -105.5 -46.5t-38.5 -108.5q0 -58 34 -99t95 -41q37 0 64.5 10t54 36.5t48 77.5t38.5 128l82 367q-54 -28 -113.5 -46.5t-90.5 -23t-46 -4.5q-121 0 -209 41.5t-137 116t-71.5 165t-22.5 202.5z" />
|
||||
<glyph unicode="H" horiz-adv-x="1361" d="M25 1004q0 132 107.5 256.5t274.5 200t336 75.5q28 0 82 -4l-131 -610h301l131 610h295l-325 -1532h-295l164 770h-301l-164 -770h-295l297 1393q-137 -58 -212 -173t-75 -276q0 -46 6.5 -76t14.5 -44.5t8 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="I" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="J" horiz-adv-x="1097" d="M35 -135q0 111 67 192.5t183 81.5q31 0 53 -1.5t52 -9t49 -21.5t32.5 -40.5t13.5 -64.5q0 -36 -12 -82q-37 31 -64 42t-59 11q-67 0 -105 -45t-38 -106q0 -72 43 -108t96 -36q50 0 82 18.5t61 74.5t52 159l321 1463q-136 -57 -212.5 -172.5t-76.5 -276.5q0 -46 6.5 -76 t14.5 -44.5t8 -18.5q-110 0 -164.5 46t-54.5 153q0 132 108 256.5t275 200t336 75.5h82l-307 -1454q-26 -123 -65.5 -212t-102 -157.5t-155.5 -102.5t-216 -34q-143 0 -223 79.5t-80 209.5z" />
|
||||
<glyph unicode="K" horiz-adv-x="1230" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-133 -627q60 81 174.5 234.5t202.5 270.5t88 116h215l-569 -656q14 0 37 1t34 1q140 0 197 -58t57 -161q0 -89 -50 -273.5t-50 -244.5q0 -46 22 -84t58 -51q-120 -35 -203 -35q-160 0 -160 137q0 65 54.5 261.5 t54.5 277.5q0 139 -123 139q-18 0 -68 -8l-164 -772h-294l296 1393q-137 -58 -211.5 -173t-74.5 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="L" horiz-adv-x="776" d="M12 983q0 99 64.5 197.5t166 171.5t232 118.5t256.5 45.5q15 0 82 -5l-315 -1470q57 -13 127 -45.5t134 -68.5t132.5 -70.5t145 -57t149.5 -22.5q94 0 172 33q-15 -148 -72.5 -235t-140.5 -87q-80 0 -170.5 48t-175.5 116t-172.5 136.5t-191.5 116.5t-204 48 q-31 0 -47 -2l305 1419q-136 -57 -211 -171t-75 -275q0 -46 6.5 -76t14 -45t7.5 -19q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="M" horiz-adv-x="1611" d="M137 0l520 1407q-155 -52 -240 -170.5t-85 -290.5q0 -48 6.5 -78.5t14 -44.5t7.5 -18q-219 0 -219 186q0 128 97.5 255.5t250 208.5t304.5 81q76 0 155 -29l-47 -1079l451 1079h295l-215 -1507h-295l131 920l-385 -920h-281l41 924l-342 -924h-164z" />
|
||||
<glyph unicode="N" horiz-adv-x="1368" d="M12 1004q0 70 26 144.5t77.5 143.5t120 123.5t161.5 87.5t195 33q131 0 190 -29l162 -1085l223 1110h295l-325 -1532h-248l-174 1071l-228 -1071h-294l294 1391q-135 -56 -209.5 -171t-74.5 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="O" horiz-adv-x="1361" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172z" />
|
||||
<glyph unicode="P" horiz-adv-x="1173" d="M12 1004q0 132 108 256.5t275 200t336 75.5q122 0 216.5 -33t151.5 -90t86 -128t29 -152q0 -99 -41.5 -196t-115 -176t-187.5 -128t-247 -49h-11l-125 -584h-294l278 1303l303 40l-145 -684q75 6 143 55t114 121t73.5 159t27.5 169q0 123 -63 201t-191 78 q-249 0 -389.5 -132.5t-140.5 -365.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="Q" horiz-adv-x="1361" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -153 -39.5 -317.5t-131 -328.5t-220.5 -260q37 -52 65.5 -83t79 -56.5t111.5 -25.5q13 0 45 4q-37 -100 -88 -138t-123 -38q-52 0 -93 15t-66.5 36t-47.5 57.5t-32.5 65 t-24.5 73.5q-89 -28 -190 -28q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -61 4 -88q78 19 127 19q91 0 164 -56q106 134 179 366.5t73 450.5q0 95 -11 157.5 t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79l10 -31q-207 0 -207 172zM545 238q29 -84 98 -84q44 0 78 22q-43 70 -117 70q-28 0 -59 -8z" />
|
||||
<glyph unicode="R" horiz-adv-x="1230" d="M12 1004q0 132 108 256.5t275 200t336 75.5q117 0 208.5 -27.5t146 -74t82.5 -104t28 -122.5q0 -96 -59 -192.5t-162 -157.5q141 -40 141 -205q0 -89 -50 -273.5t-50 -244.5q0 -46 22 -84t58 -51q-120 -35 -203 -35q-160 0 -160 137q0 65 54.5 261.5t54.5 277.5 q0 139 -123 139q-14 0 -68 -8l-164 -772h-294l278 1303l303 40l-100 -473h16q81 0 149.5 57t105 138.5t36.5 161.5q0 94 -56 154.5t-169 60.5q-251 0 -402 -133.5t-151 -364.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="S" horiz-adv-x="1167" d="M55 309q0 80 26.5 145.5t70.5 106t96.5 62.5t107.5 22q30 0 58.5 -6.5t56.5 -21t46 -42t20 -65.5q-44 0 -85 -10.5t-76.5 -32.5t-57 -60.5t-21.5 -89.5q0 -82 52 -139t145 -57q105 0 171 73t66 199q0 96 -43.5 184t-105 155.5t-123 132.5t-105 143t-43.5 161 q0 158 136.5 262.5t336.5 104.5q37 0 72 -3.5t84.5 -18.5t85.5 -40t62 -72.5t26 -111.5q0 -90 -46 -148.5t-122 -58.5q-57 0 -94 43q41 24 66.5 74.5t25.5 102.5q0 60 -36 98.5t-115 38.5q-87 0 -138 -48.5t-51 -138.5q0 -61 25.5 -114.5t66 -96t89.5 -84.5t98.5 -90 t90 -102t66 -130.5t25.5 -166.5q0 -125 -48 -224.5t-129 -161t-183 -94t-216 -32.5q-90 0 -166.5 20.5t-137.5 61.5t-95.5 110t-34.5 160z" />
|
||||
<glyph unicode="T" horiz-adv-x="985" d="M12 1001q0 106 39 198.5t119.5 169t219 121t321.5 44.5q130 0 279.5 -23.5t244.5 -23.5q135 0 238 43q-6 -260 -256 -260q-118 0 -318 45l-283 -1315h-294l290 1356h-24q-69 0 -124 -11t-105 -40t-83.5 -74.5t-53 -118.5t-19.5 -170q0 -46 6.5 -76t14 -44.5t7.5 -18.5 q-110 0 -164.5 45.5t-54.5 152.5z" />
|
||||
<glyph unicode="U" horiz-adv-x="1466" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z " />
|
||||
<glyph unicode="V" horiz-adv-x="1259" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l18 -1260l431 1254h143l-569 -1526h-312v1399q-321 -101 -321 -455q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="W" horiz-adv-x="1970" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l18 -1260l431 1254h268v-1254l440 1254h144l-570 -1526h-303v1042l-405 -1042h-301v1399q-165 -52 -248.5 -168t-83.5 -287q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="X" horiz-adv-x="1269" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l88 -426l238 420h143l-344 -596l192 -930h-325l-109 512l-297 -512h-125l385 680l-151 719q-144 -53 -224.5 -170.5t-80.5 -284.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="Y" horiz-adv-x="1269" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l178 -733l303 727h136l-398 -895l-135 -631h-328l136 631l-197 768q-144 -53 -224.5 -170.5t-80.5 -284.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="Z" horiz-adv-x="1110" d="M23 0l716 1425q-18 0 -82 2.5t-102 2.5q-188 0 -188 -46q13 0 23.5 -16t10.5 -49q0 -61 -37 -89t-83 -28q-54 0 -79.5 36t-25.5 85q0 83 68 148t184 65h702l-706 -1403q25 0 93 -2t104 -2q180 0 213 45q-13 0 -25 15.5t-12 48.5q0 59 37.5 88.5t85.5 29.5 q53 0 78.5 -34.5t25.5 -83.5q0 -86 -73.5 -162t-188.5 -76h-739z" />
|
||||
<glyph unicode="[" horiz-adv-x="819" d="M-49 -512l434 2048h395l-26 -127h-248l-381 -1794h248l-27 -127h-395z" />
|
||||
<glyph unicode="\" horiz-adv-x="741" d="M86 1536h147l435 -2048h-148z" />
|
||||
<glyph unicode="]" horiz-adv-x="819" d="M-49 -512l26 127h248l381 1794h-248l27 127h395l-434 -2048h-395z" />
|
||||
<glyph unicode="^" horiz-adv-x="862" d="M102 1149l435 387h43l122 -387h-65l-123 164l-321 -164h-91z" />
|
||||
<glyph unicode="_" horiz-adv-x="909" d="M74 0l37 162h716l-34 -162h-719z" />
|
||||
<glyph unicode="`" horiz-adv-x="411" d="M49 1536h215l60 -387h-154z" />
|
||||
<glyph unicode="a" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="b" horiz-adv-x="909" d="M-14 250q0 57 14 117l219 1026l303 41l-98 -461q94 57 168 57q242 0 242 -350q0 -54 -8 -114t-27.5 -128t-48.5 -131t-74.5 -122t-102 -102t-134 -69t-167.5 -26q-139 0 -212.5 68.5t-73.5 193.5zM279 281q0 -95 108 -95q63 0 120 81t88.5 196.5t31.5 224.5 q0 81 -28.5 139t-80.5 58q-29 0 -67.5 -9.5t-53.5 -27.5l-112 -520q-6 -24 -6 -47z" />
|
||||
<glyph unicode="c" horiz-adv-x="786" d="M-27 313q0 54 8.5 115.5t28.5 133t49 138.5t74 130t99.5 109.5t130 74.5t161.5 28q125 0 176.5 -53t51.5 -135q0 -72 -31 -110.5t-78 -38.5q-35 0 -72 24q25 70 25 121q0 84 -57 84q-61 0 -124 -103.5t-101 -244t-38 -254.5q0 -100 35 -136t113 -36q77 0 144.5 29.5 t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79t-84.5 246z" />
|
||||
<glyph unicode="d" horiz-adv-x="1075" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-8l100 471l303 41l-235 -1106q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-33 -96 -77.5 -166t-85.5 -107.5t-89.5 -60t-83.5 -28t-75 -5.5 q-84 0 -136.5 45.5t-64.5 130.5q-122 -188 -303 -188q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q48 0 98.5 45.5t69.5 124.5v11l100 469q-4 26 -24 48.5t-60 22.5q-62 0 -118 -60t-91.5 -148.5t-56.5 -183 t-21 -169.5z" />
|
||||
<glyph unicode="e" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5z" />
|
||||
<glyph unicode="f" horiz-adv-x="546" d="M-426 -291q0 55 19 99t47.5 73t78.5 55.5t94 42.5t113 37l248 1170q18 84 50 148t68.5 101.5t82.5 60.5t86.5 30.5t85.5 7.5q97 0 169 -62t72 -159q0 -47 -6 -84h-96q4 40 4 55q0 34 -22.5 52t-55.5 18q-43 0 -83 -38t-60 -130l-35 -162h150l-15 -82h-151l-236 -1106 q-72 -348 -366 -348q-97 0 -169.5 62t-72.5 159zM-313 -262q0 -23 25 -45.5t57 -22.5q80 0 116 160l27 119q-108 -36 -166.5 -85t-58.5 -126z" />
|
||||
<glyph unicode="g" horiz-adv-x="1038" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-12l22 106h295l-203 -950q98 35 157.5 99.5t100.5 193.5h86q-23 -78 -56.5 -140t-68.5 -101.5t-78.5 -69t-79.5 -45t-79 -27.5l-31 -148q-72 -348 -369 -348 q-99 0 -164 51.5t-65 147.5q0 203 334 305l25 108q-105 -112 -246 -112q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM246 -285q0 -24 19 -45.5t50 -21.5q34 0 67 47.5t52 134.5l12 59q-200 -75 -200 -174zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5 q45 0 94.5 43.5t69.5 116.5l104 485q-2 26 -21.5 51t-62.5 25q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="h" d="M-78 0l297 1393l303 41l-110 -516q100 114 239 114q104 0 166 -56.5t62 -174.5q0 -89 -49.5 -283.5t-49.5 -245.5q0 -86 82 -86q61 0 96.5 43t75.5 138h86q-33 -97 -73.5 -168t-78.5 -110.5t-81.5 -63t-76.5 -30.5t-71 -7q-124 0 -182.5 66.5t-58.5 164.5q0 71 46 264 t46 258q0 105 -76 105q-53 0 -98.5 -63t-57.5 -117l-141 -666h-295z" />
|
||||
<glyph unicode="i" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM180 1296q0 68 47.5 116t114.5 48q68 0 116 -48t48 -116 q0 -67 -48 -114t-116 -47t-115 47t-47 114z" />
|
||||
<glyph unicode="j" horiz-adv-x="491" d="M-414 -313q0 204 336 305l219 1032h295l-203 -950q98 35 158 100t101 193h86q-29 -96 -71.5 -167.5t-94 -113.5t-97 -64.5t-100.5 -37.5l-31 -148q-72 -348 -368 -348q-99 0 -164.5 52t-65.5 147zM-301 -285q0 -24 19.5 -45.5t50.5 -21.5q34 0 66.5 47.5t51.5 134.5 l13 59q-201 -75 -201 -174zM164 1298q0 68 47.5 116t114.5 48t115 -48t48 -116q0 -67 -47.5 -114t-115.5 -47t-115 47t-47 114z" />
|
||||
<glyph unicode="k" horiz-adv-x="1060" d="M-78 0l297 1393l303 41l-153 -721l395 311h203l-410 -285q52 9 68 9q109 0 167 -68t58 -172q0 -42 -8 -78l-23 -102q-10 -50 -10 -66q0 -76 80 -76q61 0 96.5 43t75.5 138h86q-33 -98 -75.5 -171t-80.5 -111.5t-82 -61.5t-74.5 -29t-64.5 -6q-126 0 -192 60t-66 173 q0 49 12 107l17 80q8 36 8 75q0 99 -74 99q-59 0 -151 -76l-107 -506h-295z" />
|
||||
<glyph unicode="l" horiz-adv-x="546" d="M-16 233q0 56 16 134l219 1026l303 41l-235 -1106q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184z" />
|
||||
<glyph unicode="m" horiz-adv-x="1540" d="M-78 0l217 1024h295l-22 -106q102 116 250 116q178 0 210 -176q112 174 285 174q104 0 165.5 -56.5t61.5 -174.5q0 -89 -49 -283.5t-49 -245.5q0 -86 82 -86q61 0 96.5 43t75.5 138h86q-33 -97 -73.5 -168t-78.5 -110.5t-81.5 -63t-76.5 -30.5t-71 -7q-124 0 -182.5 66.5 t-58.5 164.5q0 71 46 264t46 258q0 105 -74 105q-52 0 -93.5 -53t-66.5 -136l-139 -657h-295l150 707q6 24 6 51q0 38 -15.5 64t-46.5 26q-56 0 -98 -53.5t-68 -137.5l-139 -657h-295z" />
|
||||
<glyph unicode="n" d="M-78 0l217 1024h295l-22 -106q100 114 239 114q104 0 166 -56.5t62 -174.5q0 -89 -49.5 -283.5t-49.5 -245.5q0 -86 82 -86q61 0 96.5 43t75.5 138h86q-33 -97 -73.5 -168t-78.5 -110.5t-81.5 -63t-76.5 -30.5t-71 -7q-124 0 -182.5 66.5t-58.5 164.5q0 71 46 264t46 258 q0 105 -74 105q-52 0 -93.5 -53t-66.5 -136l-139 -657h-295z" />
|
||||
<glyph unicode="h" horiz-adv-x="1034" d="M-78 0l297 1393l303 41l-110 -516q100 114 239 114q104 0 166 -56.5t62 -174.5q0 -89 -49.5 -283.5t-49.5 -245.5q0 -86 82 -86q61 0 96.5 43t75.5 138h86q-33 -97 -73.5 -168t-78.5 -110.5t-81.5 -63t-76.5 -30.5t-71 -7q-124 0 -182.5 66.5t-58.5 164.5q0 71 46 264 t46 258q0 105 -76 105q-53 0 -98.5 -63t-57.5 -117l-141 -666h-295z" />
|
||||
<glyph unicode="o" horiz-adv-x="899" d="M-29 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q263 0 263 -327q8 -4 22 -4q66 0 155 36.5t161 86.5l18 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM276 342 q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 83 64 111q-3 65 -20.5 92t-57.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5z" />
|
||||
<glyph unicode="p" horiz-adv-x="958" d="M-186 -512l342 1612h295l-33 -154q102 88 239 88q114 0 182 -75.5t68 -247.5q0 -74 -10 -150t-32 -159.5t-61.5 -156t-93 -131t-132.5 -92.5t-175 -34q-131 0 -174 73l-106 -499zM262 213q16 -47 78 -47q61 0 112 39t82.5 99t53 136t30.5 145.5t9 131.5q0 170 -103 170 q-35 0 -74 -26.5t-67 -74.5z" />
|
||||
<glyph unicode="q" horiz-adv-x="995" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -29t41 -79l20 98h295l-311 -1462l-310 -74l129 612q-105 -112 -243 -112q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5 q43 0 90 39t70 107l108 506q-4 25 -24.5 47t-59.5 22q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="r" horiz-adv-x="770" d="M-78 0l217 1024h295l-26 -127q49 42 76.5 62.5t76 42.5t94.5 22q66 0 104 -45.5t37 -108.5q0 -59 -38.5 -104t-108.5 -45q-32 0 -50.5 14t-24 33.5t-9 39.5t-11 34t-24.5 14q-42 0 -73 -18t-76 -60l-164 -778h-295z" />
|
||||
<glyph unicode="s" horiz-adv-x="782" d="M-66 250q0 66 32 117t77 73q138 244 252 607l303 40q11 -224 22.5 -387.5t17 -233.5t5.5 -124q0 -45 -8 -78q87 52 147 103h86q-127 -147 -313 -256q-60 -63 -147.5 -93t-174.5 -30q-75 0 -134.5 23t-94 61.5t-52.5 83.5t-18 94zM43 293q0 -73 29.5 -108t95.5 -35 q76 0 127 45.5t51 144.5q0 51 -14.5 186t-24.5 299q-51 -165 -166 -387q47 -21 47 -71q0 -39 -26 -70t-64 -31q-42 0 -55 27z" />
|
||||
<glyph unicode="t" horiz-adv-x="546" d="M-16 233q0 56 16 134l123 575h-68l17 82h67l62 283l303 41l-70 -324h123l-16 -82h-123l-131 -614q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184z" />
|
||||
<glyph unicode="u" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184z" />
|
||||
<glyph unicode="v" horiz-adv-x="847" d="M-18 229q0 50 10 99l147 696h295l-147 -696q-11 -53 -11 -74q0 -68 70 -68q58 0 114 49.5t98 127t75 169.5t50 181t17 157q-4 -9 -26 -14.5t-39 -5.5q-34 0 -54 33t-20 71q0 50 32.5 80.5t94.5 30.5q68 0 98.5 -50.5t30.5 -127.5q0 -85 -14.5 -180.5t-43.5 -198 t-77.5 -194.5t-111 -165.5t-149.5 -117t-188 -43.5q-119 0 -185 61t-66 180z" />
|
||||
<glyph unicode="w" horiz-adv-x="1353" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -24 -6 -52q0 -90 61 -90q109 0 164 181l139 657h295l-147 -696q-11 -53 -11 -74q0 -68 70 -68q58 0 114 49.5t98 127t75 169.5t50 181t17 157q-4 -9 -26 -14.5t-39 -5.5q-34 0 -54 33t-20 71q0 50 32.5 80.5t94.5 30.5 q68 0 98.5 -50.5t30.5 -127.5q0 -85 -14.5 -180.5t-43.5 -198t-77.5 -194.5t-111 -165.5t-149.5 -117t-188 -43.5t-163.5 43.5t-81.5 128.5q-117 -172 -291 -172q-98 0 -158.5 61t-60.5 184z" />
|
||||
<glyph unicode="x" horiz-adv-x="1069" d="M0 367q20 98 48 186.5t70.5 178.5t94 155t121.5 106t149 41q58 0 101.5 -22.5t67 -61t36 -76.5t17.5 -83l18 -156q173 194 178 295q-24 -4 -35 -4q-96 0 -96 114q0 50 39.5 75.5t99.5 25.5q53 0 84 -57t31 -130q0 -89 -60.5 -177.5t-224.5 -268.5l19 -143 q12 -98 42 -138.5t85 -40.5q59 0 109.5 51.5t74.5 129.5h86q-32 -91 -75 -160.5t-85.5 -110t-91 -66t-87.5 -34t-79 -8.5q-192 0 -250 356l-282 -344h-148l414 467l-33 229q-14 99 -31.5 134.5t-46.5 35.5q-66 0 -144.5 -131t-129.5 -368h-86z" />
|
||||
<glyph unicode="y" horiz-adv-x="995" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -24 -6 -52q0 -90 61 -90q54 0 94.5 45.5t65.5 122.5l143 670h295l-203 -950q98 35 157.5 99.5t100.5 193.5h86q-23 -78 -56.5 -140t-68.5 -101.5t-78.5 -69t-79.5 -45t-79 -27.5l-31 -148q-72 -348 -369 -348 q-99 0 -164 51.5t-65 147.5q0 203 334 305l25 110q-102 -114 -246 -114q-98 0 -158.5 61t-60.5 184zM203 -285q0 -24 19 -45.5t50 -21.5q34 0 67 47.5t52 134.5l12 59q-200 -75 -200 -174z" />
|
||||
<glyph unicode="z" horiz-adv-x="876" d="M0 0l522 913h-114q-56 0 -84.5 -6t-28.5 -26q0 -3 5.5 -5t11 -10.5t5.5 -29.5q0 -53 -33 -81t-77 -28q-38 0 -66 24t-28 66q0 71 68 139t169 68h527l-502 -866q7 0 65 -6.5t90 -6.5q90 0 97 41q-39 3 -39 43q0 32 30.5 60t83.5 28q44 0 67.5 -27.5t23.5 -70.5 q0 -79 -70 -149t-180 -70h-543z" />
|
||||
<glyph unicode="{" horiz-adv-x="720" d="M61 -287q0 65 30.5 150t69 157t81 167t59.5 171q6 24 6 45q0 64 -84 64q-25 0 -65 -6l24 117q44 -7 64 -7q38 0 64.5 15.5t40.5 36.5t21.5 55t9 59.5t1.5 61.5q0 58 -7 166t-7 159q0 50 10 103t35 109t61 100t93.5 72t127.5 28h88l-18 -102q-72 0 -113.5 -10t-72.5 -44 q-52 -58 -72.5 -125t-22.5 -157l13 -350q-6 -164 -82 -228q24 -27 24 -74q0 -60 -38 -157t-83.5 -184t-83.5 -188.5t-38 -170.5q0 -164 174 -164l-19 -92h-18q-105 0 -151 14t-83 72q-39 58 -39 137z" />
|
||||
<glyph unicode="|" horiz-adv-x="1089" d="M449 -512v2050h190v-2050h-190z" />
|
||||
<glyph unicode="}" horiz-adv-x="720" d="M61 -510l19 102q72 0 113.5 10t72.5 44q52 58 72 124.5t22 157.5l-12 351q5 163 82 227q-24 27 -24 74q0 60 38 157t83.5 184t83.5 188.5t38 170.5q0 164 -174 164l19 92h18q105 0 150.5 -14t82.5 -72q39 -58 39 -137q0 -65 -30.5 -150t-69 -157t-80.5 -167t-59 -171 q-6 -24 -6 -45q0 -64 84 -64q25 0 65 6l-24 -116q-38 6 -64 6q-38 0 -64.5 -15.5t-40.5 -36.5t-21.5 -55t-9 -59.5t-1.5 -61.5q0 -58 7 -166t7 -159q0 -50 -10 -103t-35 -109t-61 -100t-93.5 -72t-127.5 -28h-89z" />
|
||||
<glyph unicode="~" horiz-adv-x="882" d="M100 1192q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l13 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode="¡" horiz-adv-x="448" d="M-125 -508l266 961h174l-145 -961h-295zM121 762q0 68 47.5 116t114.5 48t115 -48t48 -116q0 -67 -47.5 -114.5t-115.5 -47.5q-67 0 -114.5 47t-47.5 115z" />
|
||||
<glyph unicode="¢" horiz-adv-x="825" d="M63 647q0 141 48 262.5t146.5 206.5t231.5 103l46 251h118l-55 -249q81 -5 132.5 -38.5t51.5 -101.5q0 -33 -10 -67h-117q11 42 11 57q0 70 -76 70h-10l-156 -703q10 -2 31 -2q40 0 76 10.5t72.5 34t59 41.5t62.5 53q-45 -267 -332 -278l-78 -352h-55l64 354 q-112 16 -186.5 107t-74.5 241zM279 653q0 -55 18.5 -105t56.5 -79l113 625q-82 -64 -135 -190t-53 -251z" />
|
||||
<glyph unicode="£" horiz-adv-x="952" d="M51 686q67 136 215 182l148 605l307 63l-164 -678q8 -2 32.5 -10.5t35.5 -11.5t31.5 -8t38 -7t36.5 -2q85 0 129 47l12 -2q-18 -75 -117 -140t-212 -65h-33l-115 -473q32 4 101.5 6.5t111.5 7.5t115.5 36t150.5 88q-15 -148 -72 -236t-140 -88h-605l176 733q-5 0 -16.5 1 t-17.5 1q-80 0 -136 -53z" />
|
||||
<glyph unicode="¤" horiz-adv-x="1024" d="M139 1008l78 73l115 -114q83 61 186 61q101 0 182 -59l115 114l74 -73l-113 -113q62 -84 62 -188q0 -103 -62 -187l113 -112l-74 -74l-113 113q-83 -60 -184 -60q-104 0 -188 62l-115 -115l-74 74l115 114q-59 81 -59 183q0 103 59 184zM313 709q0 -85 60 -145t145 -60 t145 60t60 145t-60 144.5t-145 59.5t-145 -59.5t-60 -144.5z" />
|
||||
<glyph unicode="¥" horiz-adv-x="970" d="M39 291l29 102h196l29 133h-203l29 103h196l-229 907h336l180 -739l307 739h135l-401 -907h268l-28 -103h-262l-29 -133h268l-29 -102h-262l-61 -291h-328l62 291h-203z" />
|
||||
<glyph unicode="¦" horiz-adv-x="1089" d="M449 455h190v-967h-190v967zM449 616v922h190v-922h-190z" />
|
||||
<glyph unicode="§" horiz-adv-x="1110" d="M16 158q0 111 56.5 181.5t140.5 70.5q38 0 72 -18.5t49.5 -35.5t40.5 -49q-91 -7 -128.5 -42.5t-37.5 -112.5q0 -81 39.5 -120.5t128.5 -39.5q78 0 121 48.5t43 149.5q0 68 -20.5 125.5t-53 99t-71.5 78.5t-78.5 71t-72 69.5t-53 82t-20.5 99.5q0 95 59 170t154 111 q-10 45 -10 86q0 88 38.5 157.5t101.5 111.5t138.5 63.5t155.5 21.5q29 0 57.5 -2.5t68.5 -14.5t69 -31.5t50 -57.5t21 -89q0 -72 -36.5 -118.5t-98.5 -46.5q-46 0 -76 32q33 20 53.5 60.5t20.5 81.5q0 46 -29 78t-86 32q-82 0 -137 -65.5t-55 -153.5q0 -51 22.5 -96 t58 -79.5t79 -68t86.5 -71t78.5 -79t58 -99.5t22.5 -127q0 -79 -27 -150.5t-82 -124t-128 -67.5q0 -182 -118 -286t-327 -104q-43 0 -82 4.5t-88.5 21.5t-84.5 44.5t-59 78.5t-24 120zM389 924q0 -47 34.5 -94t87 -92t107 -94.5t102.5 -119.5t66 -149q39 48 39 129 q0 67 -29 123.5t-76.5 100.5t-98.5 82t-106 87t-87 98q-39 -20 -39 -71z" />
|
||||
<glyph unicode="¨" horiz-adv-x="802" d="M115 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM483 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="©" horiz-adv-x="1202" d="M104 678q0 144 67.5 265t181.5 190t247 69t247 -68.5t181.5 -189t67.5 -264.5t-67.5 -265t-181.5 -190t-247 -69t-247 69t-181.5 189.5t-67.5 263.5zM199 678q0 -183 119 -309.5t286 -126.5q166 0 285 126.5t119 309.5t-119 309.5t-285 126.5q-167 0 -286 -126.5 t-119 -309.5zM311 688q0 129 88.5 216t214.5 87q95 -4 164.5 -58t89.5 -136l-96 -37q-7 26 -14 41.5t-23.5 38t-46 34t-70.5 11.5q-82 0 -138 -48.5t-56 -138.5q0 -92 56 -155.5t138 -63.5q48 0 89.5 24.5t64.5 67.5l90 -59q-43 -65 -110 -102t-142 -37q-124 0 -211.5 93.5 t-87.5 221.5z" />
|
||||
<glyph unicode="ª" horiz-adv-x="706" d="M88 1096q0 60 9 116.5t32 112t57.5 96.5t89 66.5t123.5 25.5q40 0 80 -28l6 31h170l-86 -377q-6 -36 -6 -37q0 -41 43 -41q21 0 31 4q-15 -45 -38.5 -69t-41 -28.5t-43.5 -4.5q-99 0 -119 81q-72 -88 -162 -88q-77 0 -111 41t-34 99zM248 1126q0 -77 69 -77q35 0 74 53 l76 330q-23 20 -59 20q-42 0 -80 -50.5t-59 -125.5t-21 -150z" />
|
||||
<glyph unicode="«" horiz-adv-x="1206" d="M41 487l18 84l629 308l-20 -113l-494 -246l397 -196l-24 -117zM471 487l18 84l629 308l-20 -113l-494 -246l397 -196l-24 -117z" />
|
||||
<glyph unicode="¬" horiz-adv-x="909" d="M39 455l37 161h741l-104 -512h-232l72 351h-514z" />
|
||||
<glyph unicode="­" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="®" horiz-adv-x="1132" d="M70 684q0 108 39.5 205.5t106.5 167t158 110.5t191 41q133 0 247 -68.5t181.5 -189t67.5 -264.5t-67.5 -265t-181.5 -190t-247 -69q-100 0 -191 41t-158 110.5t-106.5 166.5t-39.5 204zM164 684q0 -183 119 -309.5t286 -126.5q166 0 285 126.5t119 309.5t-119 309.5 t-285 126.5q-167 0 -286 -126.5t-119 -309.5zM360 393l2 596h193q110 0 178 -45.5t68 -126.5q0 -57 -33.5 -100t-87.5 -58l133 -256l-96 -14l-127 256h-135v-252h-95zM455 723h106q36 0 65.5 6t55 27.5t25.5 56.5q0 49 -44.5 70.5t-105.5 21.5h-102v-182z" />
|
||||
<glyph unicode="°" horiz-adv-x="643" d="M76 999q0 187 91 313t241 126q87 0 127 -58.5t40 -173.5q0 -68 -20.5 -142.5t-57 -142t-96 -111.5t-129.5 -44q-113 0 -154.5 57t-41.5 176zM223 975q0 -133 56 -133q53 0 93 56t60.5 145t20.5 194q0 129 -52 129q-50 0 -106.5 -83.5t-67.5 -219.5q-4 -50 -4 -88z" />
|
||||
<glyph unicode="±" horiz-adv-x="1091" d="M63 139l37 162h752l-35 -162h-754zM170 633l49 229h262l56 250h231l-55 -250h260l-47 -229h-262l-56 -252h-231l55 252h-262z" />
|
||||
<glyph unicode="²" horiz-adv-x="614" d="M6 739q0 66 40.5 130t97.5 118t114.5 110.5t98 131t40.5 155.5q0 38 -22 64.5t-49 26.5q-56 0 -76.5 -22t-20.5 -67q0 -24 11 -47t34 -28q-45 -39 -92 -39q-37 0 -60.5 26t-23.5 74q0 65 61 113.5t185 48.5q225 0 225 -176q0 -70 -34 -132.5t-88 -111t-110 -90 t-111 -87.5t-81 -85h9q38 0 114 -10.5t117 -10.5q16 0 29.5 1.5t26 6t20 7.5t20 12.5t17.5 13.5t20 18t21 19q0 -37 -0.5 -57t-3.5 -47t-8.5 -40.5t-15.5 -29.5t-25 -23.5t-37 -12.5t-52 -5q-65 0 -149 20.5t-144 20.5q-13 0 -30.5 -6t-37 -15.5t-26.5 -11.5q-4 24 -4 37z " />
|
||||
<glyph unicode="³" horiz-adv-x="647" d="M27 879q0 79 36.5 123.5t104.5 44.5q82 0 96 -56q-34 -3 -56.5 -15.5t-32 -33t-12.5 -39t-3 -43.5q0 -52 26.5 -71t77.5 -19q63 0 104.5 58.5t41.5 134.5q0 68 -38.5 124.5t-115.5 81.5q96 23 149.5 88.5t53.5 131.5q0 38 -20 62t-56 24q-107 0 -107 -89q0 -24 11.5 -47 t34.5 -28q-45 -39 -93 -39q-37 0 -60.5 26t-23.5 74q0 64 66 113t190 49q98 0 153.5 -47t55.5 -115q0 -59 -40.5 -113.5t-120.5 -91.5q147 -44 147 -180q0 -105 -89.5 -196t-215.5 -91q-44 0 -83 6t-83.5 23t-71 55.5t-26.5 94.5z" />
|
||||
<glyph unicode="´" horiz-adv-x="647" d="M246 1149l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="µ" d="M-150 -340l289 1364h295l-147 -696q-6 -24 -6 -52q0 -39 14.5 -64.5t46.5 -25.5q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-33 -96 -77.5 -166t-85.5 -107.5t-89.5 -60t-83.5 -28t-75 -5.5q-86 0 -139 47.5 t-62 136.5q-113 -180 -293 -184l-55 -266z" />
|
||||
<glyph unicode="¶" horiz-adv-x="1110" d="M147 1006q0 77 20.5 152.5t63 143.5t102 119.5t144 82t182.5 30.5q80 0 146 -18.5t119.5 -57t83.5 -105.5t30 -157q0 -62 -16 -147l-260 -1215h-193l166 778q-33 -11 -88 -22l-158 -756h-192l158 756q-148 29 -228 146t-80 270zM670 694h12q41 0 72 8l98 465 q12 59 12 101q0 85 -47 129z" />
|
||||
<glyph unicode="·" horiz-adv-x="888" d="M246 547q0 82 57 140t139 58t140.5 -58t58.5 -140t-58.5 -139.5t-140.5 -57.5t-139 57.5t-57 139.5z" />
|
||||
<glyph unicode="¸" horiz-adv-x="464" d="M0 -389q35 65 82 65q45 0 85.5 -27.5t65.5 -27.5q26 0 44 26.5t18 59.5q0 31 -20 52.5t-60 21.5q-48 0 -106 -25l108 303l72 -47l-60 -147q66 16 109 16q63 0 95 -27t32 -75q0 -104 -74.5 -164.5t-171.5 -60.5q-70 0 -117.5 14t-101.5 43z" />
|
||||
<glyph unicode="¹" horiz-adv-x="444" d="M45 707l158 690h-113l14 51q148 0 302 86l-189 -827h-172z" />
|
||||
<glyph unicode="º" horiz-adv-x="673" d="M86 1137q0 98 25.5 174t69.5 122t99 69.5t117 23.5q45 0 79 -14t34 -35q0 -37 -53 -54q-16 39 -54 39q-41 0 -73.5 -36t-50 -89t-26.5 -103t-9 -87q0 -47 22 -65.5t62 -18.5q50 0 80 65.5t41 155.5q-31 8 -31 45q0 30 25.5 57t54.5 27q22 0 30.5 -8t8.5 -31q14 8 23.5 17 t22.5 25t21 24l12 -23q-27 -87 -79 -117q-4 -82 -30.5 -150t-65 -109.5t-82.5 -64t-87 -22.5q-96 0 -141 43t-45 140z" />
|
||||
<glyph unicode="»" horiz-adv-x="1200" d="M31 207l28 117l492 196l-397 246l18 113l504 -308l-23 -110zM485 207l29 117l492 196l-398 246l19 113l503 -308l-22 -110z" />
|
||||
<glyph unicode="¼" horiz-adv-x="1411" d="M143 461l158 690h-113l15 51q147 0 301 86l-189 -827h-172zM285 -338l540 1874h148l-541 -1874h-147zM766 307q14 41 60 129t79 172t33 156q0 36 -8 67q56 0 94.5 -27t38.5 -77q0 -49 -20 -99t-44 -84t-58.5 -81t-51.5 -76h184l96 422l177 22l-103 -444h78l-25 -80h-71 l-70 -305h-170l70 305h-289z" />
|
||||
<glyph unicode="½" horiz-adv-x="1382" d="M143 461l158 690h-113l15 51q147 0 301 86l-189 -827h-172zM274 -338l541 1874h148l-541 -1874h-148zM750 43q0 49 21.5 96t56 86.5t76.5 79.5t83.5 82t76 86t56 99.5t21.5 115.5q0 38 -22 64t-50 26q-56 0 -76 -21.5t-20 -66.5q0 -24 11.5 -47.5t33.5 -28.5 q-45 -39 -92 -39q-37 0 -60.5 26.5t-23.5 74.5q0 66 60.5 114t184.5 48q226 0 226 -176q0 -70 -34 -132.5t-88 -111t-110 -90t-111 -87.5t-81 -85h8q37 0 114 -10.5t117 -10.5q16 0 29.5 1.5t26 6t20 7.5t20 12.5t17.5 13.5t20 18t21 19q0 -52 -1.5 -77.5t-8.5 -57.5 t-21.5 -46t-41.5 -24t-68 -10q-65 0 -149 20.5t-144 20.5q-13 0 -30.5 -6t-37 -15.5t-26.5 -11.5q-4 24 -4 37z" />
|
||||
<glyph unicode="¾" horiz-adv-x="1677" d="M125 633q0 79 36.5 123.5t104.5 44.5q82 0 96 -56q-34 -3 -56.5 -15.5t-32 -33t-12.5 -39t-3 -43.5q0 -52 26.5 -71t77.5 -19q63 0 104.5 58.5t41.5 134.5q0 68 -38.5 125t-115.5 82q96 23 149.5 88.5t53.5 130.5q0 38 -20 62t-56 24q-106 0 -106 -88q0 -24 11.5 -47.5 t33.5 -28.5q-45 -39 -92 -39q-37 0 -60.5 26t-23.5 74q0 64 66 113t190 49q98 0 153.5 -47t55.5 -115q0 -58 -41 -112.5t-121 -91.5q75 -22 111 -70t36 -111q0 -105 -89.5 -195.5t-215.5 -90.5q-264 0 -264 178zM522 -338l541 1874h147l-540 -1874h-148zM1010 307 q14 41 60 129t79 172t33 156q0 36 -8 67q56 0 94.5 -27t38.5 -77q0 -49 -20 -99t-44 -84t-58.5 -81t-51.5 -76h184l96 422l176 22l-102 -444h78l-25 -80h-72l-69 -305h-170l69 305h-288z" />
|
||||
<glyph unicode="¿" horiz-adv-x="899" d="M25 -262q0 78 20 142.5t55 112t77.5 88.5t91.5 80.5t93.5 79.5t87 95t68.5 117h135q0 -58 -20.5 -113t-53.5 -99.5t-72.5 -88.5t-79 -88.5t-72.5 -91t-53.5 -105t-20.5 -121.5q0 -71 32 -106t80 -35q62 0 113 48.5t51 121.5q0 42 -19.5 83t-58.5 52q26 42 66 64t84 22 q64 0 109.5 -45t45.5 -127q0 -55 -24.5 -110.5t-73.5 -106t-135.5 -82t-196.5 -31.5q-329 0 -329 244zM473 762q0 69 47.5 117.5t114.5 48.5q68 0 116 -48.5t48 -117.5q0 -67 -48 -114.5t-116 -47.5q-67 0 -114.5 47t-47.5 115z" />
|
||||
<glyph unicode="À" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM895 2048h215l59 -387h-153z" />
|
||||
<glyph unicode="Á" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM995 1661l187 387h215l-248 -387h-154z" />
|
||||
<glyph unicode="Â" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM834 1661l434 387h43l123 -387h-66l-123 164l-321 -164h-90z" />
|
||||
<glyph unicode="Ã" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM838 1704q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34 q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode="Ä" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM881 1810q0 51 36.5 88t88.5 37q51 0 87.5 -37t36.5 -88 q0 -52 -36.5 -88t-87.5 -36q-53 0 -89 36t-36 88zM1249 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="Å" horiz-adv-x="1368" d="M18 686q0 105 119 178.5t299 73.5q42 0 107 -6q80 127 165.5 233.5t177 191t187 132t182.5 47.5q91 0 162 -29l-321 -1507h-295l157 743q-137 62 -274 74q-116 -252 -191 -514.5t-75 -421.5q0 -72 28 -114q-31 0 -50.5 0.5t-51.5 5t-53 12.5t-45.5 24t-39 38.5 t-24.5 57.5t-10 79q0 137 83.5 367t217.5 466q-204 -13 -240 -106q8 0 17.5 -21.5t9.5 -42.5q0 -36 -33 -58t-82 -22q-55 0 -91 30.5t-36 88.5zM727 905q148 -29 250 -71l119 557q-171 -92 -369 -486zM993 1786q0 29 10.5 60.5t30 60t54.5 47t79 18.5q72 0 110 -44t38 -113 q0 -74 -44.5 -123.5t-133.5 -49.5q-64 0 -104 42t-40 102zM1075 1790q0 -76 70 -76q46 0 70 25t24 69q0 20 -1.5 32.5t-7.5 29.5t-21 26t-38 9q-49 0 -72.5 -35t-23.5 -80z" />
|
||||
<glyph unicode="Æ" horiz-adv-x="1564" d="M18 696q0 61 55.5 116t148 87.5t196.5 32.5q54 0 121 -6q81 129 166 236.5t177 193.5t187.5 134t183.5 48q53 0 173.5 -20.5t195.5 -20.5q119 0 240 39q-3 -9 -10.5 -34.5t-12 -38.5t-15 -36.5t-20 -36.5t-26 -30.5t-35 -26.5t-44.5 -15.5t-57 -6.5q-21 0 -40.5 1.5 t-41.5 6.5t-34.5 7.5t-37.5 11.5t-31 11t-36 14t-32 13l-111 -518h317l-41 -151h-309l-104 -490h458l-43 -217h-755l157 743q-151 66 -276 74q-116 -249 -190 -511.5t-74 -422.5q0 -36 4 -59t9 -30.5t9.5 -13.5t5.5 -11q-118 7 -163 24q-98 36 -109 165q-2 19 -2 40 q0 131 83.5 359t217.5 460q-100 -7 -160 -30.5t-80 -75.5q8 0 17.5 -21.5t9.5 -42.5q0 -35 -31.5 -57.5t-93.5 -22.5q-49 0 -83 32.5t-34 96.5zM723 903q146 -27 254 -69l119 557q-176 -96 -373 -488z" />
|
||||
<glyph unicode="Ç" horiz-adv-x="1017" d="M39 471q0 83 11 176t37 197.5t64 202t96.5 187t129.5 156.5t168.5 106.5t208.5 39.5q75 0 135.5 -15t107.5 -47t73 -85.5t26 -126.5q0 -87 -35 -138t-109 -51q-70 0 -116 53q37 19 65.5 75t28.5 114q0 55 -30 90t-95 35q-108 0 -208.5 -152t-159 -374t-58.5 -431 q0 -59 6.5 -105.5t23.5 -87.5t44.5 -68t70.5 -42.5t101 -15.5q119 0 223.5 54.5t175.5 150.5l47 -21q-35 -88 -96.5 -159t-134 -115t-146 -69.5t-145.5 -33.5l-43 -106q66 16 108 16q63 0 95 -27t32 -75q0 -104 -74 -164.5t-171 -60.5q-71 0 -118.5 14.5t-101.5 42.5 q35 65 82 65q45 0 86 -27.5t66 -27.5q26 0 43.5 26.5t17.5 59.5q0 31 -19.5 52.5t-59.5 21.5q-50 0 -107 -25l76 211q-218 6 -320 123t-102 381z" />
|
||||
<glyph unicode="È" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138zM516 2048h215l60 -387h-154z" />
|
||||
<glyph unicode="É" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138zM676 1661l186 387h215l-248 -387h-153z " />
|
||||
<glyph unicode="Ê" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138zM414 1661l434 387h43l123 -387h-66 l-123 164l-321 -164h-90z" />
|
||||
<glyph unicode="Ë" horiz-adv-x="940" d="M14 324q0 110 44.5 219.5t133 196.5t201.5 114q-125 89 -125 246q0 106 56.5 206.5t161.5 167t231 66.5q127 0 210 -67.5t83 -188.5q0 -91 -44.5 -152t-107.5 -61q-55 0 -96 55q37 12 63.5 68t26.5 111q0 58 -33 86t-80 28q-88 0 -148.5 -88.5t-60.5 -200.5 q0 -96 51.5 -171t155.5 -103q-79 -18 -149 -70.5t-116.5 -122t-73.5 -147.5t-27 -149q0 -93 47.5 -150t146.5 -57q102 0 214.5 57t164.5 152l47 -21q-78 -177 -245 -281t-355 -104q-95 0 -169 29t-118.5 79.5t-67 114.5t-22.5 138zM487 1810q0 51 36.5 88t88.5 37 q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM856 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="Ì" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM350 2048h215l60 -387h-154z" />
|
||||
<glyph unicode="Í" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM551 1661l186 387h215l-247 -387h-154z" />
|
||||
<glyph unicode="Î" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM309 1661l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="Ï" horiz-adv-x="768" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l-326 -1532h-294l296 1391q-136 -57 -211 -171t-75 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM342 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36 t-36 88zM711 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="Ð" horiz-adv-x="1357" d="M12 1004q0 132 108 256.5t275 200t336 75.5q138 0 243 -30.5t169.5 -80.5t105.5 -123t56.5 -150t15.5 -169q0 -260 -78 -493.5t-209.5 -375t-279.5 -141.5q-124 0 -250 109l-17 -82h-294l151 707h-80l41 151h72l94 445l303 40l-104 -485h161l-40 -151h-154l-109 -512 q55 -41 117 -41q88 0 164 83t125 213t76.5 285t27.5 305q0 82 -6.5 141t-24.5 110.5t-49.5 83t-81.5 49.5t-120 18q-251 0 -402 -133.5t-151 -364.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153z" />
|
||||
<glyph unicode="Ñ" horiz-adv-x="1404" d="M12 1004q0 70 26 144.5t77.5 143.5t120 123.5t161.5 87.5t195 33q131 0 190 -29l162 -1085l223 1110h295l-325 -1532h-248l-174 1071l-228 -1071h-294l294 1391q-135 -56 -209.5 -171t-74.5 -276q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM631 1704 q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode="Ò" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM598 2048h215l59 -387h-153z" />
|
||||
<glyph unicode="Ó" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM813 1661l186 387h215l-247 -387h-154z" />
|
||||
<glyph unicode="Ô" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM557 1661l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="Õ" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM561 1704q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117 -140t-212 -65q-70 0 -180 38t-164 38q-80 0 -136 -53z" />
|
||||
<glyph unicode="Ö" horiz-adv-x="1347" d="M33 977q0 141 115 270.5t307 209t403 79.5q131 0 226.5 -38.5t151 -110t81.5 -164.5t26 -211q0 -87 -13.5 -181t-42.5 -194.5t-71.5 -193.5t-105 -176.5t-137.5 -145t-174.5 -97.5t-210.5 -36q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180 t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79 l10 -31q-207 0 -207 172zM590 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM958 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="×" horiz-adv-x="1054" d="M201 338l194 194l-194 195l133 131l192 -194l197 196l131 -133l-195 -195l195 -194l-131 -133l-197 194l-192 -192z" />
|
||||
<glyph unicode="Ø" horiz-adv-x="1347" d="M31 913q0 99 66.5 195.5t175 168.5t255.5 116.5t299 44.5q34 0 50 -2l22 100h107l-25 -115q313 -72 313 -475q0 -106 -21.5 -221.5t-70 -236.5t-118 -220.5t-174.5 -173.5t-230 -96l-31 -143h-106l28 133q-169 0 -263.5 92.5t-94.5 294.5q0 79 15.5 174t44.5 196 t75.5 195.5t103.5 169t134 119.5t162 45q49 0 92 -14l18 83q-40 7 -78 7q-153 0 -281.5 -59t-208 -173t-79.5 -262q0 -32 5 -54.5t10 -33t5 -14.5q-98 0 -149 37t-51 122zM506 367q0 -207 98 -224l234 1106q-83 -56 -161.5 -213t-124.5 -341t-46 -328zM719 180 q81 59 153.5 194.5t116 308t43.5 331.5q0 108 -17 177t-61 107z" />
|
||||
<glyph unicode="Ù" horiz-adv-x="1472" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z M840 2048h215l59 -387h-153z" />
|
||||
<glyph unicode="Ú" horiz-adv-x="1472" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z M924 1661l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="Û" horiz-adv-x="1472" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z M743 1661l435 387h43l122 -387h-65l-123 164l-321 -164h-91z" />
|
||||
<glyph unicode="Ü" horiz-adv-x="1472" d="M0 993q0 85 42 167t116.5 149t171 118.5t212 80t232.5 28.5q44 0 98 -10q-32 -120 -74.5 -275t-64 -236t-49 -184t-40.5 -158.5t-26 -118.5t-17.5 -104.5t-4.5 -78.5q0 -185 141 -185q50 0 80.5 6t66.5 30t62.5 69.5t57.5 125t62 197t67 285.5l137 633h289l-136 -639 q-30 -145 -56.5 -247.5t-61 -204t-72 -168t-88.5 -125.5t-113 -92t-141 -51.5t-178 -18.5q-213 0 -304.5 81.5t-91.5 243.5q0 59 7 123.5t29.5 167.5t33.5 151t50 202t48 192l68 270q-170 -45 -266.5 -167t-96.5 -304q0 -48 6.5 -78.5t14.5 -44.5t8 -18q-219 0 -219 188z M764 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM1133 1810q0 51 36 88t88 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -88.5 36t-35.5 88z" />
|
||||
<glyph unicode="Ý" horiz-adv-x="1269" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l178 -733l303 727h136l-398 -895l-135 -631h-328l136 631l-197 768q-144 -53 -224.5 -170.5t-80.5 -284.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM928 1661l186 387h215l-248 -387h-153z " />
|
||||
<glyph unicode="Þ" horiz-adv-x="1193" d="M12 778q0 146 129.5 281t319.5 203l51 233l303 41l-49 -225q113 -5 200.5 -40t140.5 -91.5t80 -125.5t27 -147q0 -99 -41.5 -196t-115 -176t-187.5 -128t-247 -49h-60l-76 -358h-294l245 1151q-115 -60 -175 -170t-60 -262q0 -46 6.5 -76t14 -44.5t7.5 -18.5 q-110 0 -164.5 45.5t-54.5 152.5zM580 434h28q104 0 193 79.5t137.5 195t48.5 229.5q0 77 -23.5 137t-78.5 99.5t-137 42.5z" />
|
||||
<glyph unicode="ß" horiz-adv-x="921" d="M-174 -512l340 1597q18 84 50 148.5t68.5 101.5t82.5 60t86.5 31t85.5 8q135 0 207.5 -75.5t72.5 -184.5q0 -92 -49.5 -175.5t-138.5 -124.5q103 -35 150.5 -112.5t47.5 -177.5q0 -120 -26.5 -220.5t-68 -166t-94 -111.5t-103.5 -66t-97 -20q-21 0 -67 4l26 121 q60 0 106 51t70.5 131.5t36 161.5t11.5 158q0 70 -34 113.5t-114 64.5l37 119q73 30 121.5 87t48.5 140q0 58 -18.5 80t-59.5 22q-43 0 -83 -38t-60 -130l-326 -1523z" />
|
||||
<glyph unicode="à" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM442 1536h215l60 -387h-154z" />
|
||||
<glyph unicode="á" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM502 1149l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="â" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM299 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z " />
|
||||
<glyph unicode="ã" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM209 1192q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5 q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="ä" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM369 1298q0 51 36.5 88t88.5 37q51 0 87.5 -37t36.5 -88 q0 -52 -36.5 -88t-87.5 -36q-53 0 -89 36t-36 88zM737 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="å" horiz-adv-x="1077" d="M-27 317q0 71 14.5 149.5t42.5 159t73.5 153.5t101.5 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-15l22 109h295l-147 -696q-9 -40 -9 -66q0 -88 84 -88q56 0 98 54t68 139h86q-133 -379 -411 -379q-87 0 -139.5 48.5t-61.5 139.5q-126 -188 -305 -188 q-43 0 -82 10.5t-74 35t-61 62t-41 94t-15 127.5zM276 346q0 -56 10 -91.5t28.5 -48.5t31.5 -16.5t33 -3.5q49 0 101 49t69 132l98 462q0 26 -20 52t-64 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5zM479 1274q0 29 10.5 60.5t30 60t54.5 47t79 18.5 q72 0 110 -44t38 -113q0 -74 -44.5 -123.5t-133.5 -49.5q-64 0 -104 42t-40 102zM561 1278q0 -76 70 -76q46 0 70 25t24 69q0 20 -1.5 32.5t-7.5 29.5t-21 26t-38 9q-49 0 -72.5 -35t-23.5 -80z" />
|
||||
<glyph unicode="æ" horiz-adv-x="1331" d="M-16 254q0 93 34 160t97 105.5t142.5 56.5t180.5 18q57 0 107 -8q43 91 43 180q0 65 -31.5 105t-93.5 40q-84 0 -140 -24.5t-108 -71.5l-57 59q55 56 104 90.5t98 48.5t82.5 17.5t87.5 3.5q166 0 267 -82q121 90 276 90q66 0 111.5 -15.5t69 -45t33 -63.5t9.5 -78 q0 -110 -66.5 -206.5t-174.5 -154.5t-230 -65q-4 -46 -4 -66q0 -108 31 -148t92 -40q71 0 125.5 13t102 45.5t77 61.5t82.5 87h70q-48 -77 -99 -137.5t-120.5 -117.5t-157.5 -88.5t-188 -31.5q-246 0 -301 192q-49 -93 -114 -144.5t-177 -51.5q-49 0 -91.5 11.5t-82.5 39.5 t-63 83t-23 132zM272 289q0 -33 10.5 -55.5t28 -31.5t30.5 -12.5t26 -3.5q55 0 94.5 61.5t68.5 219.5q-12 2 -34 2q-100 0 -162 -49.5t-62 -130.5zM840 512q119 7 209 108.5t90 223.5q0 90 -49 90q-50 0 -101.5 -65.5t-88.5 -159.5t-60 -197z" />
|
||||
<glyph unicode="ç" horiz-adv-x="786" d="M-27 313q0 54 8.5 115.5t28.5 133t49 138.5t74 130t99.5 109.5t130 74.5t161.5 28q125 0 176.5 -53t51.5 -135q0 -72 -31 -110.5t-78 -38.5q-35 0 -72 24q25 70 25 121q0 84 -57 84q-61 0 -124 -103.5t-101 -244t-38 -254.5q0 -100 35 -136t113 -36q77 0 144.5 29.5 t113 69.5t104.5 108h70q-181 -289 -410 -357l-57 -145q66 16 109 16q63 0 95 -27t32 -75q0 -104 -74.5 -164.5t-171.5 -60.5q-70 0 -117.5 14t-101.5 43q35 65 82 65q45 0 85.5 -27.5t65.5 -27.5q26 0 44 26.5t18 59.5q0 31 -20 52.5t-60 21.5q-49 0 -107 -25l84 236 q-20 -4 -59 -4q-151 0 -235.5 79t-84.5 246z" />
|
||||
<glyph unicode="è" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5zM354 1536h215l60 -387h-154z" />
|
||||
<glyph unicode="é" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5zM397 1149l187 387h215l-248 -387h-154z" />
|
||||
<glyph unicode="ê" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM215 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5z" />
|
||||
<glyph unicode="ë" horiz-adv-x="786" d="M-27 313q0 53 8.5 114.5t28 133t48.5 138.5t74 130t100.5 110t131 75t162.5 28q226 0 226 -196q0 -115 -66.5 -212.5t-174 -155t-230.5 -64.5q-5 -72 -5 -82q0 -100 35 -136t113 -36q77 0 144.5 29.5t113 69.5t104.5 108h70q-236 -379 -563 -379q-151 0 -235.5 79 t-84.5 246zM244 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM297 512q119 8 208 109.5t89 228.5q0 84 -51 84q-50 0 -100.5 -64t-87.5 -158.5t-58 -199.5zM612 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88 t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="ì" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM135 1536h215l60 -387h-154z" />
|
||||
<glyph unicode="í" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM248 1149l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="î" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM12 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="ï" horiz-adv-x="546" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-8 -36 -8 -66q0 -42 19.5 -59t63.5 -17q59 0 110 51.5t75 129.5h86q-33 -95 -79 -167t-90.5 -111t-95.5 -63t-88.5 -31t-76.5 -7q-98 0 -158.5 61t-60.5 184zM35 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88 q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM403 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="ð" horiz-adv-x="1101" d="M-2 317q0 71 14.5 149.5t42.5 159t73 153.5t101 130t132.5 91t162.5 34q84 0 125 -28.5t41 -75.5v-12l17 75q19 91 22 156h-158l17 82h141q-12 104 -86 203h92q79 -44 133 -90t86 -113h129l-16 -82h-86q12 -50 12 -109q0 -77 -22 -190l-111 -522q-8 -36 -8 -66 q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-33 -96 -77.5 -166t-85.5 -107.5t-90 -60t-84 -28t-75 -5.5q-84 0 -136.5 45.5t-64.5 132.5q-123 -190 -305 -190q-55 0 -101.5 17.5t-86 55t-62 103t-22.5 153.5zM301 346q0 -45 7.5 -77t17 -47.5t26 -24t26.5 -10t25 -1.5 q49 0 101 49t69 132l99 462q0 26 -19.5 52t-64.5 26q-62 0 -118 -60t-91.5 -148.5t-56.5 -183t-21 -169.5z" />
|
||||
<glyph unicode="ñ" d="M-78 0l217 1024h295l-22 -106q100 114 239 114q104 0 166 -56.5t62 -174.5q0 -89 -49.5 -283.5t-49.5 -245.5q0 -86 82 -86q61 0 96.5 43t75.5 138h86q-33 -97 -73.5 -168t-78.5 -110.5t-81.5 -63t-76.5 -30.5t-71 -7q-124 0 -182.5 66.5t-58.5 164.5q0 71 46 264t46 258 q0 105 -74 105q-52 0 -93.5 -53t-66.5 -136l-139 -657h-295zM180 1192q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117 -140t-212 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode="ò" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM264 342 q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5zM367 1536h215l59 -387h-154z" />
|
||||
<glyph unicode="ó" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM264 342 q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5zM449 1149l186 387h215l-248 -387h-153z" />
|
||||
<glyph unicode="ô" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM195 1149l434 387h43 l123 -387h-66l-123 164l-321 -164h-90zM264 342q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5z" />
|
||||
<glyph unicode="õ" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM82 1192 q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53zM264 342q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27 q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5z" />
|
||||
<glyph unicode="ö" horiz-adv-x="886" d="M-41 315q0 53 8.5 114.5t29 132.5t50 138.5t75.5 130t102 109t132.5 74.5t163.5 28q262 0 262 -327q8 -4 23 -4q66 0 154.5 36.5t160.5 86.5l19 -56q-58 -62 -153.5 -106.5t-209.5 -63.5q-24 -280 -160.5 -448t-328.5 -168q-152 0 -240 78t-88 245zM215 1298 q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM264 342q0 -99 22.5 -132.5t82.5 -33.5q75 0 143.5 122.5t93.5 303.5q-55 13 -55 86q0 84 63 111q-3 65 -20.5 92t-56.5 27q-68 0 -133.5 -103t-102.5 -236.5t-37 -236.5zM584 1298 q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="÷" horiz-adv-x="966" d="M104 422l37 162h717l-35 -162h-719zM301 164q0 68 47.5 116t114.5 48t115.5 -48t48.5 -116q0 -67 -48 -114.5t-116 -47.5q-67 0 -114.5 47t-47.5 115zM424 850q0 68 47.5 116t114.5 48q68 0 116 -48t48 -116q0 -67 -48 -114.5t-116 -47.5t-115 47t-47 115z" />
|
||||
<glyph unicode="ø" horiz-adv-x="876" d="M-29 315q0 52 8 112t27 130t47.5 136t72 128.5t97 109.5t127.5 76.5t158 32.5l41 199h106l-43 -209q170 -46 176 -299q70 3 156.5 41.5t147.5 81.5h65q-46 -66 -151 -131.5t-222 -95.5q-17 -251 -123.5 -417t-267.5 -206l-45 -215h-106l43 203q-145 3 -229.5 81 t-84.5 242zM276 342q0 -99 22.5 -132.5t82.5 -33.5q75 0 142.5 124.5t90.5 307.5q-57 15 -57 94q0 41 17.5 69.5t46.5 39.5q-4 58 -22.5 82.5t-55.5 24.5q-68 0 -132 -103t-99.5 -236t-35.5 -237z" />
|
||||
<glyph unicode="ù" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184zM379 1536h215l59 -387h-153z" />
|
||||
<glyph unicode="ú" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184zM489 1149l187 387h215l-248 -387h-154z" />
|
||||
<glyph unicode="û" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184zM258 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="ü" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184zM279 1298q0 51 36 88t88 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -88.5 36t-35.5 88zM647 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="ý" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -24 -6 -52q0 -90 61 -90q54 0 94.5 45.5t65.5 122.5l143 670h295l-203 -950q98 35 157.5 99.5t100.5 193.5h86q-23 -78 -56.5 -140t-68.5 -101.5t-78.5 -69t-79.5 -45t-79 -27.5l-31 -148q-72 -348 -369 -348 q-99 0 -164 51.5t-65 147.5q0 203 334 305l25 110q-102 -114 -246 -114q-98 0 -158.5 61t-60.5 184zM203 -285q0 -24 19 -45.5t50 -21.5q34 0 67 47.5t52 134.5l12 59q-200 -75 -200 -174zM487 1149l187 387h215l-248 -387h-154z" />
|
||||
<glyph unicode="þ" horiz-adv-x="968" d="M-188 -512l319 1505q25 116 25 191q0 132 -88 250h92q47 -26 79 -47.5t68.5 -56.5t58.5 -73t37 -94t15 -123q0 -51 -8 -100q105 94 247 94q114 0 182 -75.5t68 -247.5q0 -74 -10 -150t-32 -159.5t-61.5 -156t-93 -131t-132.5 -92.5t-175 -34q-138 0 -176 75l-106 -501z M260 217q16 -51 80 -51q61 0 112 39t82.5 99t53 136t30.5 145.5t9 131.5q0 170 -103 170q-36 0 -76.5 -28.5t-66.5 -78.5z" />
|
||||
<glyph unicode="ÿ" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -24 -6 -52q0 -90 61 -90q54 0 94.5 45.5t65.5 122.5l143 670h295l-203 -950q98 35 157.5 99.5t100.5 193.5h86q-23 -78 -56.5 -140t-68.5 -101.5t-78.5 -69t-79.5 -45t-79 -27.5l-31 -148q-72 -348 -369 -348 q-99 0 -164 51.5t-65 147.5q0 203 334 305l25 110q-102 -114 -246 -114q-98 0 -158.5 61t-60.5 184zM203 -285q0 -24 19 -45.5t50 -21.5q34 0 67 47.5t52 134.5l12 59q-200 -75 -200 -174zM252 1298q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36 q-53 0 -89 36t-36 88zM621 1298q0 51 36 88t88 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -88.5 36t-35.5 88z" />
|
||||
<glyph unicode="Œ" horiz-adv-x="1732" d="M33 977q0 141 115 270.5t307 209t403 79.5q130 0 225 -39q109 39 265 39q35 0 152.5 -12.5t187.5 -12.5q105 0 184 25q-5 -14 -13.5 -45t-14.5 -50t-20.5 -46.5t-31.5 -45t-47 -34.5t-68 -25q-90 0 -232.5 34.5t-193.5 39.5q92 -131 92 -352q0 -66 -10 -154h273l-41 -151 h-260q-70 -301 -234 -490h498l-43 -217h-793q-86 -12 -145 -12q-171 0 -269 98.5t-98 312.5q0 85 16 186.5t45.5 210t78 209t107.5 180t139 127.5t169 48q55 0 107 -17q-89 -54 -174 -223.5t-135 -370.5t-50 -358q0 -110 29.5 -173.5t89.5 -63.5q100 0 201 145t164 362.5 t63 421.5q0 95 -11 157.5t-39.5 110.5t-80 69.5t-131.5 21.5q-118 0 -224.5 -37t-188 -103.5t-129.5 -167t-48 -219.5q0 -48 11 -79l10 -31q-207 0 -207 172z" />
|
||||
<glyph unicode="œ" horiz-adv-x="1357" d="M-14 324q0 183 44 324t119.5 226t170.5 127.5t204 42.5q182 0 230 -174q128 172 327 172q127 0 194.5 -53.5t67.5 -159.5q0 -89 -44 -166t-114.5 -127.5t-148.5 -82t-155 -41.5q-7 -58 -7 -88q0 -93 49 -135t119 -42q79 0 185.5 80.5t158.5 190.5h93 q-37 -66 -79.5 -124.5t-98.5 -113.5t-116 -94t-133.5 -62.5t-150.5 -23.5q-101 0 -171.5 48.5t-104.5 129.5q-66 -93 -151.5 -141.5t-174.5 -48.5q-166 0 -239.5 77.5t-73.5 258.5zM258 342q0 -87 35.5 -121.5t101.5 -34.5q45 0 83 33.5t64.5 90.5t44 125.5t27.5 146.5 q-42 0 -64 26.5t-22 65.5q0 46 25 87t65 56q-9 107 -86 107q-68 0 -123.5 -67t-86.5 -164t-47.5 -190t-16.5 -161zM897 510q90 1 165.5 48t116.5 117.5t42 143.5q0 27 -4.5 50t-22 44t-45.5 21q-54 0 -106.5 -68t-87.5 -159.5t-58 -196.5z" />
|
||||
<glyph unicode="Ÿ" horiz-adv-x="1269" d="M12 1004q0 132 108 256.5t275 200t336 75.5q28 0 82 -4l178 -733l303 727h136l-398 -895l-135 -631h-328l136 631l-197 768q-144 -53 -224.5 -170.5t-80.5 -284.5q0 -46 6.5 -76t14 -44.5t7.5 -18.5q-110 0 -164.5 46t-54.5 153zM776 1810q0 51 36.5 88t88.5 37 q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88zM1145 1810q0 51 36.5 88t88.5 37q51 0 88 -37t37 -88q0 -52 -37 -88t-88 -36q-53 0 -89 36t-36 88z" />
|
||||
<glyph unicode="ˆ" horiz-adv-x="600" d="M0 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
|
||||
<glyph unicode="˜" horiz-adv-x="825" d="M78 1192q49 98 137 149.5t205 51.5q79 0 180.5 -34t157.5 -34q85 0 129 47l12 -2q-18 -75 -117.5 -140t-212.5 -65q-70 0 -180 38t-164 38q-79 0 -135 -53z" />
|
||||
<glyph unicode=" " horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="2048" />
|
||||
<glyph unicode=" " horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="2048" />
|
||||
<glyph unicode=" " horiz-adv-x="682" />
|
||||
<glyph unicode=" " horiz-adv-x="512" />
|
||||
<glyph unicode=" " horiz-adv-x="341" />
|
||||
<glyph unicode=" " horiz-adv-x="341" />
|
||||
<glyph unicode=" " horiz-adv-x="256" />
|
||||
<glyph unicode=" " horiz-adv-x="409" />
|
||||
<glyph unicode=" " horiz-adv-x="113" />
|
||||
<glyph unicode="‐" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="‑" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="‒" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
|
||||
<glyph unicode="–" horiz-adv-x="720" d="M39 455l37 161h532l-35 -161h-534z" />
|
||||
<glyph unicode="—" horiz-adv-x="909" d="M39 455l37 161h717l-35 -161h-719z" />
|
||||
<glyph unicode="‘" horiz-adv-x="624" d="M172 1397q0 60 34.5 97.5t96.5 37.5q63 0 106.5 -42t43.5 -101q0 -74 -91 -129q-24 -15 -24 -35l100 -144q0 -18 -18 -28q-115 73 -181.5 168t-66.5 176z" />
|
||||
<glyph unicode="’" horiz-adv-x="624" d="M172 1389q0 59 43.5 101t106.5 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-19 11 -19 28l101 144q0 19 -25 35q-90 55 -90 129z" />
|
||||
<glyph unicode="‚" horiz-adv-x="579" d="M156 156q0 59 43 101t106 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144q0 20 -24 35q-90 55 -90 129z" />
|
||||
<glyph unicode="“" horiz-adv-x="1042" d="M156 1397q0 60 34.5 97.5t96.5 37.5q63 0 106 -42t43 -101q0 -74 -90 -129q-24 -15 -24 -35l100 -144q0 -17 -19 -28q-114 73 -180.5 167.5t-66.5 176.5zM606 1397q0 60 34.5 97.5t96.5 37.5q63 0 106.5 -42t43.5 -101q0 -74 -90 -129q-25 -16 -25 -35l100 -144 q0 -18 -18 -28q-115 73 -181.5 168t-66.5 176z" />
|
||||
<glyph unicode="”" horiz-adv-x="1042" d="M156 1389q0 59 43 101t106 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144q0 20 -24 35q-90 55 -90 129zM606 1389q0 59 43.5 101t106.5 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144 q0 19 -25 35q-90 55 -90 129z" />
|
||||
<glyph unicode="„" horiz-adv-x="1042" d="M156 156q0 59 43 101t106 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144q0 20 -24 35q-90 55 -90 129zM606 156q0 59 43.5 101t106.5 42q62 0 96.5 -37.5t34.5 -97.5q0 -81 -66.5 -176t-181.5 -168q-18 10 -18 28l100 144 q0 19 -25 35q-90 55 -90 129z" />
|
||||
<glyph unicode="•" horiz-adv-x="888" d="M246 547q0 82 57 140t139 58t140.5 -58t58.5 -140t-58.5 -139.5t-140.5 -57.5t-139 57.5t-57 139.5z" />
|
||||
<glyph unicode="…" horiz-adv-x="1437" d="M63 154q0 68 47.5 115.5t114.5 47.5q68 0 116 -48t48 -115t-48 -114.5t-116 -47.5q-67 0 -114.5 47t-47.5 115zM545 154q0 68 47.5 115.5t114.5 47.5t115 -47.5t48 -115.5q0 -67 -47.5 -114.5t-115.5 -47.5q-67 0 -114.5 47t-47.5 115zM1047 154q0 68 47 115.5t114 47.5 q68 0 116 -48t48 -115t-48 -114.5t-116 -47.5q-67 0 -114 47t-47 115z" />
|
||||
<glyph unicode=" " horiz-adv-x="409" />
|
||||
<glyph unicode="‹" horiz-adv-x="735" d="M41 487l18 84l629 349l-20 -113l-494 -287l397 -237l-24 -117z" />
|
||||
<glyph unicode="›" horiz-adv-x="704" d="M31 166l28 117l492 237l-397 287l18 113l504 -349l-23 -110z" />
|
||||
<glyph unicode=" " horiz-adv-x="512" />
|
||||
<glyph unicode="€" horiz-adv-x="1173" d="M-6 582l29 102h151q4 43 16 133h-145l29 103h137q24 103 59.5 190.5t90.5 167.5t124.5 136t165 89t207.5 33q59 0 110.5 -9t97 -30t78 -52.5t51.5 -78t19 -104.5q0 -87 -34.5 -138t-108.5 -51q-71 0 -117 53q35 10 62.5 67t27.5 101q0 14 -2 28.5t-10.5 37t-22.5 39 t-41.5 29t-64.5 12.5q-83 0 -158 -75t-126 -189t-84 -256h413l-28 -103h-406q-12 -66 -18 -133h401l-29 -102h-379v-27q0 -84 7 -143t24.5 -107t48.5 -76t78 -42.5t113 -14.5q119 0 186.5 17.5t128.5 64.5l4 -59q-88 -116 -198.5 -161.5t-264.5 -45.5q-67 0 -127 13.5 t-120.5 50t-103.5 93t-70 150.5t-27 215v72h-174z" />
|
||||
<glyph unicode="™" horiz-adv-x="776" d="M-47 1188q0 68 51.5 114t157.5 46q8 0 63.5 -5.5t89.5 -5.5q51 0 76 11q-13 -80 -51 -80q-19 0 -63.5 8t-57.5 10l-86 -399h-88l88 407q-58 -2 -90.5 -30t-32.5 -95q0 -17 8 -41q-65 0 -65 60zM305 887l139 430l84 12v-346l136 356h96l-78 -452h-88l55 319l-129 -319h-84 v268l-82 -268h-49z" />
|
||||
<glyph unicode="" horiz-adv-x="1136" d="M0 0v1137h1137v-1137h-1137z" />
|
||||
<glyph unicode="u" horiz-adv-x="1034" d="M-16 233q0 56 16 134l139 657h295l-147 -696q-6 -30 -6 -58q0 -84 61 -84q109 0 164 181l139 657h295l-147 -696q-9 -40 -9 -66q0 -42 20 -59t64 -17q57 0 99 49.5t67 131.5h86q-133 -379 -411 -379q-88 0 -141 49.5t-62 144.5q-118 -194 -303 -194q-98 0 -158.5 61 t-60.5 184z" />
|
||||
<glyph unicode=" " horiz-adv-x="768" />
|
||||
<glyph unicode=" " horiz-adv-x="1536" />
|
||||
<glyph unicode=" " horiz-adv-x="768" />
|
||||
<glyph unicode=" " horiz-adv-x="1536" />
|
||||
<glyph unicode=" " horiz-adv-x="512" />
|
||||
<glyph unicode=" " horiz-adv-x="384" />
|
||||
<glyph unicode=" " horiz-adv-x="256" />
|
||||
<glyph unicode=" " horiz-adv-x="256" />
|
||||
<glyph unicode=" " horiz-adv-x="192" />
|
||||
<glyph unicode=" " horiz-adv-x="307" />
|
||||
<glyph unicode=" " horiz-adv-x="85" />
|
||||
<glyph unicode=" " horiz-adv-x="307" />
|
||||
<glyph unicode=" " horiz-adv-x="384" />
|
||||
<glyph unicode="◼" horiz-adv-x="1140" d="M0 0v1140h1140v-1140h-1140z" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user