Compare commits

...

210 Commits

Author SHA1 Message Date
Ruud
d4ed4791bf Merge branch 'develop' into redesign 2015-02-22 18:30:47 +01:00
Ruud
adb744a526 Don't show double updater type 2015-02-22 17:42:29 +01:00
Ruud
0f82cda811 Remove podnapisi from subtile list 2015-02-22 16:09:22 +01:00
Ruud
0d6c3c8ecb Yify, only use data when available 2015-02-22 16:06:07 +01:00
Ruud
6598f53fd4 Quality check improve 2015-02-22 15:55:54 +01:00
Ruud
6b8458d87f Hadouken apikey check not using correct settingskey
fix #4674
2015-02-22 14:49:37 +01:00
Ruud
99a0621238 Use keep-alive connection 2015-02-22 14:30:50 +01:00
Ruud Burger
c52666309a Merge pull request #4676 from peerster/develop
Update torrentshack with new URL
2015-02-22 13:39:21 +01:00
Ruud
84a458d40b Add user-agent and type to omdbapi 2015-02-22 13:06:29 +01:00
Ruud
f8631c6d53 Add extra category for TorrentLeech
fix #4683
2015-02-21 21:29:37 +01:00
Ruud
b19b0775c7 Force update to new poster on refresh
fix #4671
2015-02-20 22:16:12 +01:00
peerster
2dc1c1dd38 Update torrentshack with new URL 2015-02-19 20:07:22 +01:00
Ruud
7db8b233c8 Don't decode string if confidence isn't high enough 2015-02-18 17:21:24 +01:00
Ruud
427c77a9ef Remove podnapisi 2015-02-15 19:23:45 +01:00
Ruud
94c3969f10 Use https for yify proxy 2015-02-10 20:52:15 +01:00
Ruud
debd1855dd Move Yify to v2 2015-02-10 20:47:19 +01:00
Ruud
9f77597c11 Torrentz search on title
fix #4510
2015-02-10 17:15:53 +01:00
Ruud
afc9039625 Also search lower qualities on OMGWTF
fix #4527
2015-02-10 16:50:53 +01:00
Ruud
920d3cb44e Don't verify SYNO downloader thingymajig
fix #4641
2015-02-10 16:27:13 +01:00
Ruud
b1fc8ad862 Letterboxed new html markup
fix #4640
2015-02-10 16:21:32 +01:00
Ruud
11b9bc39ab Show tried to often error for TD 2015-02-10 15:40:55 +01:00
Ruud
6dcb3f3bf2 Change bitsoup category id
fixes #4629
2015-02-10 14:55:22 +01:00
Ruud
ce768f45c5 Make RottenTomato logging more clear
close #4618
2015-02-10 14:36:54 +01:00
Ruud
9b91d1d6c0 Remove favor, link to api key page 2015-02-10 14:10:55 +01:00
Ruud
d9c7a97604 Merge branch 'develop' of git://github.com/jonnyboy/CouchPotatoServer into jonnyboy-develop 2015-02-10 14:03:06 +01:00
Ruud
0fd01aa697 Cleanup 2015-02-10 14:01:51 +01:00
Ruud
58615e6f9b Merge branch 'develop' of git://github.com/grasshide/CouchPotatoServer into grasshide-develop 2015-02-10 13:54:13 +01:00
Ruud
2277322e57 Traceback import missing 2015-02-10 13:47:22 +01:00
Ruud Burger
18020e609e Merge pull request #4479 from sjlu/develop
Adding the ability to receive notifications through Webhooks
2015-02-10 13:19:59 +01:00
Ruud
6a31b920ac Merge branch 'develop' of github.com:RuudBurger/CouchPotatoServer into develop 2015-02-10 13:15:43 +01:00
Ruud
c1266a36e4 Re-use resursion code 2015-02-10 13:15:08 +01:00
Ruud
578effc538 Merge branch 'develop' of git://github.com/dumaresq/CouchPotatoServer into dumaresq-develop 2015-02-10 13:09:12 +01:00
Ruud Burger
d881120013 Merge pull request #4513 from starkers/remotes/origin/develop
added touch and chown to the $PID_FILE
2015-02-10 13:07:25 +01:00
Ruud Burger
da5318033a Merge pull request #4380 from mannkind/develop
Initial support for Plex Media Server w/Plex Home
2015-02-10 13:04:27 +01:00
Ruud Burger
31df5bce01 Merge pull request #4612 from maikhorma/maikhorma-#2782
Simple workaround for #2782
2015-02-10 13:02:24 +01:00
Ruud
d5622b7cba Remove www from torrentday domain 2015-02-10 13:01:19 +01:00
Ruud Burger
26ad1b354f Merge pull request #4552 from coolius/patch-1
Update torrentday url
2015-02-10 12:53:52 +01:00
Ruud
7a616a81f7 Remove www from iptorrents 2015-02-10 12:52:05 +01:00
Ruud Burger
275aefc3cc Merge pull request #4553 from coolius/patch-2
Update iptorrents url
2015-02-10 12:51:02 +01:00
Ruud Burger
2b32490f72 Merge pull request #4649 from sammy2142/patch-1
Update kickass url from kickass.so to kickass.to
2015-02-10 12:49:16 +01:00
sammy2142
7b9043c16b Update kickass url from kickass.so to kickass.to
Kickass has reverted back to the .to domain as the .so domain was seized:
http://torrentfreak.com/kickasstorrents-taken-domain-name-seizure-150209/
2015-02-10 11:11:30 +00:00
maikhorma
cf83f99be0 Updated UI
Tried to make it a bit cleaner.
2015-02-01 15:28:05 -05:00
maikhorma
fb8a66d207 Shortcut to address #2782
Until there is a more elegant solution to avoid unwanted white space
trimming, this will let users disable that feature if it is not
something they need.
2015-02-01 14:43:16 -05:00
Ruud
e8a3645bc6 Log failed folder getting 2015-02-01 12:18:31 +01:00
Ruud
592e40993c Merge branch 'develop' of github.com:RuudBurger/CouchPotatoServer into develop 2015-01-31 10:32:24 +01:00
Ruud
b00e69e222 TorrentBytes cut of longer titles
fix #4590
2015-01-31 10:32:15 +01:00
Ruud
c9b4c8167f Actual include host in log 2015-01-28 11:35:26 +01:00
coolius
cdb9cfe756 Update iptorrents.py
Updated iptorrents url to blockade-free iptorrents.eu
2015-01-19 17:18:56 +00:00
coolius
e52f50b204 Update torrentday.py
Updated torrentday url to blockade-free torrentday.eu
2015-01-19 17:17:31 +00:00
Ruud
770c2be14c Create detail url if permalink is false 2015-01-17 13:04:47 +01:00
Ruud
ab61961a64 Use detail url 2015-01-14 16:59:29 +01:00
Ruud
6aca799bbb Newznab: use guid for detail url 2015-01-14 16:55:30 +01:00
David Stark
89836be1d1 added touch and chown to the $PID_FILE 2015-01-12 17:37:26 +01:00
Andrew Dumaresq
20e1283627 better way to find the folder 2015-01-11 11:57:14 -05:00
Andrew Dumaresq
ee8406e026 Minor text change 2015-01-11 11:45:29 -05:00
Andrew Dumaresq
514941b785 Merge branch 'develop' of https://github.com/dumaresq/CouchPotatoServer into develop 2015-01-11 11:42:52 -05:00
Ruud
1510e37652 Update Tornado 2015-01-11 16:18:22 +01:00
Ruud
e1e39cd3f4 Update requests 2015-01-11 16:17:33 +01:00
Ruud
e1bb8c5419 Update Chardet 2015-01-11 16:15:52 +01:00
Ruud
17fa33a496 Update user agent 2015-01-11 00:25:58 +01:00
Ruud
601f0b54cf Send CP header when downloading from newznab 2015-01-11 00:25:51 +01:00
dumaresq
51d44bfc3e Merge pull request #1 from RuudBurger/develop
Develop
2015-01-10 17:01:43 -05:00
Ruud
12148217a2 Log failed notification 2015-01-10 13:41:17 +01:00
Ruud
132fa12ef4 Late list not loaded on home 2015-01-10 12:17:47 +01:00
Ruud
1827c2e4cd Don't parse omgwtfnzb if no results are returned 2015-01-10 12:17:30 +01:00
Ruud
f423bca06b Merge branch 'develop' of github.com:RuudBurger/CouchPotatoServer into develop 2015-01-09 20:13:42 +01:00
Ruud
e7b089edf5 Give better XML issues 2015-01-09 20:13:17 +01:00
Ruud Burger
b8b7d94a6a Merge pull request #4456 from dumaresq/develop
Bug fixes and new features for putio
2015-01-09 20:08:30 +01:00
Ruud
2c080fec3d TorrentBytes nbsp issue
fix #4026
2015-01-08 16:56:38 +01:00
Ruud
4c68566c77 Use new OMGWTFNZB api
fix #4471
2015-01-08 14:59:53 +01:00
Steven Lu
a3af784c18 Adding the ability to receive notifications through Webhooks 2015-01-06 18:47:19 -05:00
grasshide
ac6f295c93 New algogithm to use some kind of crowd logic on newznab powered
providers.
2015-01-05 15:00:40 +01:00
Andrew Dumaresq
2c72cd7d9f Added new folder option and fixed but in callback url 2015-01-04 17:10:40 -05:00
Andrew Dumaresq
d012dc5c85 Added new folder option 2015-01-04 17:10:16 -05:00
Andrew Dumaresq
038b4c63ee Updated to follow putio API changes 2015-01-04 17:09:36 -05:00
Ruud Burger
17e37996c4 Add remux category for TorrentShack
close #4427
2015-01-02 18:18:08 +01:00
jonnyboy
9318e19347 New torrent search provider hdaccess.net 2014-12-31 08:21:58 -05:00
Ruud
045c8f4dc8 Trailer 2014-12-28 11:29:41 +01:00
Ruud
02e25a9e25 Releases 2014-12-28 00:53:37 +01:00
Ruud
819f619297 Search 2014-12-27 21:10:33 +01:00
Ruud
c303789817 Merge branch 'develop' into redesign 2014-12-27 14:02:15 +01:00
Ruud
8f4e03d04b Use detected encoding
#4388
2014-12-27 13:46:25 +01:00
Ruud
229d67c086 Don't toUnicode loop 2014-12-22 22:01:47 +01:00
Dustin Brewer
d84897ff33 Initial support for Plex Media Server w/Plex Home 2014-12-21 16:18:11 -08:00
Ruud
387a711538 TorrentBytes not encoding name
fix #4377
2014-12-21 21:14:38 +01:00
Ruud
7a1b914824 Return nonblock results in main thread 2014-12-21 20:19:53 +01:00
Ruud
5e62801666 Send data through finish not write 2014-12-21 20:19:30 +01:00
Ruud
00d887153f Return data in main thread 2014-12-21 19:39:16 +01:00
Ruud
1a2d79f719 Merge branch 'develop' into redesign 2014-12-21 14:49:25 +01:00
Ruud
6d5882001a Notificaton.list not returning anything
fix #4348
2014-12-20 22:32:18 +01:00
Ruud
4a6b45c65c SCC not finding seeders 2014-12-20 22:24:00 +01:00
Ruud
b0d1fe5c33 Return false if no media is found on try_next
fix #4345
2014-12-20 22:17:43 +01:00
Ruud
a6e49098c8 Add robots.txt 2014-12-20 22:15:27 +01:00
Ruud
ffcd36cbf4 IOLoop callback hanging 2014-12-20 21:45:15 +01:00
Ruud
3bf2d844a0 Release api lock on connection close or finish
fix #4372
2014-12-20 20:13:49 +01:00
Ruud
dd24eb8893 Revert "Give response back to the main thread on api calls"
This reverts commit 576bcb9f4b.

Conflicts:
	couchpotato/api.py
2014-12-20 18:49:35 +01:00
Ruud
ac382d5131 Search and login 2014-12-20 10:49:01 +01:00
Ruud
abc9e78027 Merge branch 'develop' into redesign 2014-12-19 14:16:36 +01:00
Ruud
538f51dd5b Log ipv6 failed bind 2014-12-19 14:16:20 +01:00
Ruud
c94d79cc6c Popups over pages 2014-12-19 14:14:20 +01:00
Ruud
9883a7a85a Merge branch 'develop' into redesign 2014-12-19 09:11:54 +01:00
Ruud
eea9f40501 Use current 2014-12-19 09:01:52 +01:00
Ruud
576bcb9f4b Give response back to the main thread on api calls
fix #4337
2014-12-19 08:57:24 +01:00
Ruud
f4a486c47b Menu 2014-12-18 13:31:12 +01:00
Ruud
80cf144e8b Don't load async 2014-12-18 09:58:52 +01:00
Ruud
cf5a774313 Don't destroy events 2014-12-18 09:56:10 +01:00
Ruud
b9b77042dc Remove Async 2014-12-18 09:55:58 +01:00
Ruud
9e96aa14b7 Update Mootools 2014-12-18 09:55:48 +01:00
Ruud
6a0220b496 Filters 2014-12-17 22:00:43 +01:00
Ruud
02ff0acc64 Update page actions 2014-12-17 17:29:54 +01:00
Ruud
ae6affdb52 Movie details page 2014-12-17 17:10:40 +01:00
Ruud
a08df704be Update fonts 2014-12-17 17:10:25 +01:00
Ruud
af9a47d528 Add dev packages 2014-12-17 14:20:13 +01:00
Ruud Burger
62c5365329 Merge pull request #4356 from rtaibah/ReaddTypoFix
Change Readd in tooltip to Re-add. Former is confusing and not an Englis...
2014-12-17 11:36:53 +01:00
Rami Taibah
ddf575a86e Change Readd in tooltip to Re-add. Former is confusing and not an English word 2014-12-17 13:00:54 +03:00
Ruud
0155c8de2d Movie lists 2014-12-16 23:55:26 +01:00
Ruud Burger
6b9383ce92 Merge pull request #4342 from mano3m/develop_fixsize
Fix TorrentShack size
2014-12-16 07:52:08 +01:00
mano3m
cb8d24ef1f Fix TorrentShack size 2014-12-15 22:26:29 +01:00
Ruud
5bfdb121df Merge branch 'develop' into redesign 2014-12-14 13:05:47 +01:00
Ruud
814ddfb79f Don't return password fields
fix #4300
2014-12-14 12:33:28 +01:00
Ruud
766f819c0b Userscript for RT not parsing URL correctly 2014-12-14 12:06:03 +01:00
Ruud
b8b6024592 Styling 2014-12-14 12:04:26 +01:00
Ruud
d77cfb3e69 Start CP via grunt 2014-12-05 22:30:19 +01:00
Ruud
858d8b4291 Ignore vendor scripts 2014-12-05 15:14:36 +01:00
Ruud
3852fc720d Remove scss lib 2014-12-05 15:13:40 +01:00
Ruud
5145618c39 Damn semicolons 2014-12-05 14:44:12 +01:00
Ruud
d6cfcae45b Move to vendor folder 2014-12-05 11:29:25 +01:00
Ruud
5609536f46 Cleanup 2014-12-05 11:19:00 +01:00
Ruud
f992c00eb7 Remove unused 2014-12-04 23:31:45 +01:00
Ruud
87086a0336 Rename to scss 2014-12-04 23:22:14 +01:00
Ruud
62cb57f217 Concat 2014-12-03 23:30:14 +01:00
Ruud
2a0e46fe00 Dev tools 2014-12-03 23:19:22 +01:00
Ruud
1f7555e8fd Merge branch 'develop' into redesign
Conflicts:
	couchpotato/templates/login.html
2014-12-03 20:46:11 +01:00
Ruud
ff43df9ef1 Comments comments comments 2014-12-02 15:38:55 +01:00
Ruud
2e907e93e7 Whiteline 2014-12-02 12:02:49 +01:00
Ruud
4d329d6a36 Revert "Remove torrentleech"
This reverts commit dacc3d8f47.
2014-12-02 11:45:17 +01:00
Ruud
752191bc23 Comments 2014-12-02 11:43:10 +01:00
Ruud
1d73fd9d7e Import optimize 2014-12-02 11:15:29 +01:00
Ruud
79688c412a Merge branch 'develop' of git://github.com/hadouken/CouchPotatoServer into hadouken-develop 2014-12-02 11:07:54 +01:00
Ruud
fc1c95fefb Description 2014-12-01 23:00:59 +01:00
Ruud
6a174716af underscored variables 2014-12-01 22:52:10 +01:00
Ruud
defe256f1b Correct url 2014-12-01 16:52:43 +01:00
Ruud
8a5f154d9e Merge branch 'develop' of github.com:RuudBurger/CouchPotatoServer into develop 2014-12-01 16:52:04 +01:00
Ruud
fe56a69e8f Put.IO cleanup 2014-12-01 16:49:27 +01:00
Ruud
c6d326f973 Move put.io API 2014-12-01 15:50:03 +01:00
Ruud
9e5f670feb Merge branch 'putio' of git://github.com/dumaresq/CouchPotatoServer into develop 2014-12-01 15:42:43 +01:00
Ruud Burger
9ebacf8816 Merge pull request #4258 from glibix/develop
Transmission status 16 is for "Stopped". So we need to detect a download...
2014-12-01 15:41:41 +01:00
Ruud
df2d7ec9c2 Remove debug code 2014-12-01 15:39:33 +01:00
Ruud
ddab74582b Merge branch 'develop' of git://github.com/psaab/CouchPotatoServer into psaab-develop 2014-12-01 15:28:11 +01:00
Ruud
2801079bc8 Merge branch 'develop_3845' of git://github.com/voidstarstar/CouchPotatoServer into voidstarstar-develop_3845 2014-12-01 15:14:26 +01:00
Ruud Burger
1deb49b524 Merge pull request #4261 from voidstarstar/develop_4211
Added renamer.progress API function. Fixes #4211.
2014-12-01 15:12:21 +01:00
Ruud Burger
49d550f652 Merge pull request #4270 from sammy2142/patch-1
Update Kickass url to https://kickass.so
2014-11-30 22:05:36 +01:00
sammy2142
1a43ce6ecc Update Kickass url to https://kickass.so
Kickass has recently changed its web address from https://kickass.to 
to https://kickass.so
2014-11-30 20:48:31 +00:00
voidstarstar
15a0131587 Added renamer.progress API function. Fixes #4211.
This function reports the status of the renamer.
Progress value True means the renamer is currently running.
Progress value False means the renamer is not currently running.
2014-11-27 21:51:30 -05:00
voidstarstar
0dca34958c Added a parameter to the renamer API. Fixes #3845.
The renamer now has a new 'to_folder' parameter.
This parameter specifies where movies are moved to.
2014-11-27 21:43:19 -05:00
Mathew Paret
4b231e36ea Merge branch 'feature/3967_add_imdb_link_to_tweet' into develop 2014-11-27 18:16:41 +05:30
Mathew Paret
52478a00db Revert "Feature #3967 - Added IMDB link to download complete tweet"
This reverts commit 87338760ad.
2014-11-27 18:13:41 +05:30
Mathew Paret
e177766270 Merge branch 'feature/3967_add_imdb_link_to_tweet' into develop 2014-11-27 18:06:38 +05:30
Ruud Burger
ff8da7c8f8 Merge pull request #4068 from ofir123/subscenter_support
Added support for subscenter.
2014-11-26 21:51:14 +01:00
Ruud Burger
89c8c5a0c7 Merge pull request #4203 from rkokkelk/develop
Fix startup script Debian/Ubuntu
2014-11-26 21:48:40 +01:00
Ruud
38c6266f9c Use single quotes 2014-11-26 21:47:39 +01:00
Ruud Burger
16f8e7e123 Merge pull request #4205 from kamillus/develop
adding a fix to handle missing directories in the file browser in webkit browsers
2014-11-26 21:46:10 +01:00
Ruud Burger
7110c7a11f Merge pull request #4249 from clinton-hall/patch-1
NZBGet 13 includes more status information
2014-11-25 07:55:39 +01:00
Clinton Hall
6d79f316a6 NZBGet 13 includes more status information
nzb['Status'] returns total (SUCESS/ALL) status and also failed status in V13+
This is particularly important when using fake detector scripts or stopping download due to health checks etc.
http://nzbget.net/RPC_API_reference#Method_.22history.22
https://couchpota.to/forum/viewtopic.php?f=5&t=4644
2014-11-25 11:02:47 +10:30
Paul Saab
c1b6811b8a Tornado requires two sockets to support IPv6
Tornado sets setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
to force IPv6 sockets to only be used for IPv6 connections.  create a
separate socket to allow for CouchPotato to be used over IPv6.
2014-11-17 22:54:56 -08:00
Kamil
7d7b76b2e9 adding a fix to handle missing directories in the file browser in webkit browsers 2014-11-10 20:18:38 -05:00
Roy Kokkelkoren
657aa52fa7 Merge branch 'develop' of https://github.com/rkokkelk/CouchPotatoServer into develop 2014-11-10 15:40:41 +01:00
Roy Kokkelkoren
8e9ef8db39 Merge remote-tracking branch 'upstream/develop' into develop 2014-11-10 15:39:45 +01:00
test
92a0096b54 Merge remote-tracking branch 'upstream/develop' into develop 2014-11-10 08:29:27 -06:00
Mathew Paret
87338760ad Feature #3967 - Added IMDB link to download complete tweet 2014-11-10 18:47:37 +05:30
Mathew Paret
28019b0a09 Transmission status 16 is for "Stopped". So we need to detect a download as completed even if it is stopped but percent done is 100 2014-11-10 18:39:58 +05:30
Ruud Burger
248b007f4a Merge pull request #4094 from georgewhewell/hdbits-add-internal-only
add option for internal-only for hdbits provider
2014-11-09 14:39:50 +01:00
Ruud Burger
9e31c59de8 Merge pull request #4188 from DjSlash/patch-2
Update SSL protocol for Deluge connections
2014-11-09 14:37:23 +01:00
Ruud
269e785888 Yify, don't include quality in search
fix #4190
2014-11-09 14:30:22 +01:00
Ruud
3669aef42d is_movie param 2014-11-09 14:14:06 +01:00
Ruud
1087eb3a06 Add adding parameter to is_movie 2014-11-09 14:10:23 +01:00
Rutger van Sleen
43af80a137 Update SSL protocol for Deluge connections
Since Deluge 1.3.10 the SSL protocol is updated to TLSv1 instead of SSLv3. This resulted in CP not being able to add new torrents. Link to change in Deluge: http://git.deluge-torrent.org/deluge/commit/?h=1.3-stable&id=26f5be17609a8312c4ba06aa120ed208cd7876f2
2014-11-06 14:33:38 +01:00
Roy Kokkelkoren
0766a27a71 Fixed bug in init.d script which prevented the writing of the PID file.
Altered default value of DATA_DIR to /var/opt/couchpotato in order to comply to linux file structure
2014-11-06 04:40:39 -06:00
Viktor Elofsson
61f634a21e Refactored Hadouken downloader. 2014-10-21 16:52:28 +02:00
georgewhewell
d626fda710 add option for internal only for hdbits provider 2014-10-17 15:14:44 +01:00
Viktor Elofsson
b40d1f3463 Merge pull request #1 from RuudBurger/develop
Sync upstream
2014-10-14 13:18:48 +02:00
Ofir Brukner
1030d0d748 Added support for subscenter.
Updated both plugin and lib.
2014-10-13 00:50:29 +03:00
Viktor Elofsson
2e52c8124a Implemented a downloader for Hadouken. 2014-10-02 20:34:43 +02:00
Andrew Dumaresq
8de5fcdac6 fixed button name 2014-09-20 19:39:35 -04:00
Andrew Dumaresq
4aa9801be4 general code cleanup 2014-09-20 19:39:12 -04:00
dumaresq
3e58378490 figured out how to make the check work better 2014-09-19 21:41:58 -04:00
Andrew Dumaresq
2c40db3074 removed un-needed variable 2014-09-19 20:28:03 -04:00
dumaresq
fba228fd9d fixing check function 2014-09-19 20:26:54 -04:00
Andrew Dumaresq
ef2b8e88b4 better download checking 2014-09-19 07:07:23 -04:00
Andrew Dumaresq
c77b270fa8 Cleaned up OAUTH and made the download asyc 2014-09-18 06:00:09 -04:00
dumaresq
872a4f4650 Worked on geting Oauth and adding download status 2014-09-07 17:59:16 -04:00
Ruud
d0f1e7c6a3 Update put.io code 2014-08-29 12:30:31 +02:00
Ruud
53e7e383a3 put.io rename 2014-08-29 11:38:28 +02:00
Ruud
c06e1f3135 Merge branch 'develop' of git://github.com/dumaresq/CouchPotatoServer into dumaresq-develop 2014-08-29 11:37:49 +02:00
dumaresq
bb73cb8eec Fixed missing library 2014-08-24 18:19:01 -04:00
dumaresq
5acab98025 fixed hardcoded directory 2014-08-19 19:32:00 -04:00
dumaresq
ed6a46e9c0 Added putioDownloader 2014-08-17 16:28:47 -04:00
Ruud
4291e2233d Releader class 2014-07-13 12:23:27 +02:00
Ruud
6ccbad031f Use own style reloader 2014-07-13 12:23:14 +02:00
Ruud
d1dfed2833 Merge branch 'refs/heads/develop' into redesign 2014-07-12 20:39:26 +02:00
Ruud
3986de4ebc Merge branch 'refs/heads/develop' into redesign 2014-07-06 22:38:51 +02:00
Ruud
d80fe99609 Load subpages 2014-05-13 22:37:00 +02:00
Ruud
43b6e3ac07 Use proper extend 2014-05-11 20:46:00 +02:00
Ruud
58acd53a9a Merge branch 'refs/heads/develop' into redesign
Conflicts:
	couchpotato/static/scripts/couchpotato.js
2014-05-11 20:25:15 +02:00
Ruud
05a97a19ab Mixins 2014-05-05 20:41:29 +02:00
Ruud
db23f5cdef Update 2014-05-05 20:40:55 +02:00
Ruud
85163443e3 Re-use original paths 2014-04-09 19:54:01 +02:00
Ruud
6ea49405f4 Make clientside ordered 2014-04-09 19:29:30 +02:00
Ruud
4776cef473 Reinit css 2014-04-09 16:08:10 +02:00
Ruud
e8fe9da602 Livereload css 2014-04-09 16:07:59 +02:00
197 changed files with 9557 additions and 6899 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@
/_source/
.project
.pydevproject
node_modules
.tmp

121
Gruntfile.js Normal file
View 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
View 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} : {}

View File

@@ -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())

View File

@@ -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):

View File

@@ -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))

View File

@@ -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);
}
});
}

View File

@@ -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();
}
});
},

View File

@@ -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'):

View File

@@ -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.')

View 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.'
}
]
}
]
}]

View File

@@ -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'])
})

View File

@@ -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')

View File

@@ -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):

View 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.',
},
],
}
],
}]

View 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,
}

View 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();
});

View File

@@ -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():

View File

@@ -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():

View File

@@ -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.')

View File

@@ -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']:

View File

@@ -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'

View File

@@ -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.')

View File

@@ -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

View File

@@ -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)]

View File

@@ -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()))

View File

@@ -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:

View File

@@ -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 = [{

View 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.',
}
],
},
],
}]

View File

@@ -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'
}
],
},
],

View File

@@ -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': [

View File

@@ -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,
})

View File

@@ -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]),

View File

@@ -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': [

View 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.',
}
],
},
],
}]

View File

@@ -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),
})

View File

@@ -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')

View File

@@ -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()))

View File

@@ -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;
}

View File

@@ -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(){

View 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;
}
}
}

View File

@@ -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)

View 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();
}
});

View File

@@ -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']();

View File

@@ -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();
}
});

View File

@@ -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

View File

@@ -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(){

View 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;
}
}
}
}
}

View 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());
}
});

View File

@@ -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;
}
});

View File

@@ -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();

View File

@@ -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

View 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',
},
],
},
],
}]

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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']),
]

View 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

View File

@@ -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)])
)

View File

@@ -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']),
]

View File

@@ -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']))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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],
})

View File

@@ -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);
}
});
}

View File

@@ -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': '',

View File

@@ -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:

View File

@@ -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');
})
});
}

View 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.',
}
]
}
]
}]

View File

@@ -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:

View File

@@ -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))

View File

@@ -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;
}
});

View File

@@ -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;

View File

@@ -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){

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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',

View File

@@ -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)

View File

@@ -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 ;)');
}
}
}),

View File

@@ -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);
}
});
});

View File

@@ -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):

View File

@@ -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()

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 213 KiB

View File

@@ -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="&#xfb01;" 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="&#xfb02;" 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="&#xfb03;" 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="&#xfb04;" 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="&#xd;" horiz-adv-x="2048" />
<glyph />
<glyph />
<glyph unicode="&#xd;" />
<glyph unicode=" " horiz-adv-x="444" />
<glyph unicode="&#x09;" horiz-adv-x="444" />
<glyph unicode="&#xa0;" 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="&#x22;" 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="&#x26;" 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="&#x3c;" 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="&#x3e;" 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="&#xa1;" 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="&#xa2;" 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="&#xa3;" 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="&#xa4;" 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="&#xa5;" 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="&#xa6;" horiz-adv-x="1089" d="M449 455h190v-967h-190v967zM449 616v922h190v-922h-190z" />
<glyph unicode="&#xa7;" 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="&#xa8;" 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="&#xa9;" 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="&#xaa;" 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="&#xab;" 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="&#xac;" horiz-adv-x="909" d="M39 455l37 161h741l-104 -512h-232l72 351h-514z" />
<glyph unicode="&#xad;" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
<glyph unicode="&#xae;" 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="&#xb0;" 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="&#xb1;" 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="&#xb2;" 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="&#xb3;" 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="&#xb4;" horiz-adv-x="647" d="M246 1149l186 387h215l-248 -387h-153z" />
<glyph unicode="&#xb5;" 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="&#xb6;" 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="&#xb7;" 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="&#xb8;" 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="&#xb9;" horiz-adv-x="444" d="M45 707l158 690h-113l14 51q148 0 302 86l-189 -827h-172z" />
<glyph unicode="&#xba;" 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="&#xbb;" 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="&#xbc;" 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="&#xbd;" 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="&#xbe;" 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="&#xbf;" 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="&#xc0;" 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="&#xc1;" 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="&#xc2;" 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="&#xc3;" 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="&#xc4;" 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="&#xc5;" 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="&#xc6;" 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="&#xc7;" 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="&#xc8;" 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="&#xc9;" 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="&#xca;" 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="&#xcb;" 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="&#xcc;" 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="&#xcd;" 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="&#xce;" 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="&#xcf;" 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="&#xd0;" 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="&#xd1;" 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="&#xd2;" 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="&#xd3;" 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="&#xd4;" 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="&#xd5;" 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="&#xd6;" 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="&#xd7;" 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="&#xd8;" 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="&#xd9;" 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="&#xda;" 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="&#xdb;" 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="&#xdc;" 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="&#xdd;" 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="&#xde;" 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="&#xdf;" 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="&#xe0;" 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="&#xe1;" 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="&#xe2;" 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="&#xe3;" 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="&#xe4;" 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="&#xe5;" 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="&#xe6;" 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="&#xe7;" 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="&#xe8;" 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="&#xe9;" 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="&#xea;" 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="&#xeb;" 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="&#xec;" 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="&#xed;" 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="&#xee;" 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="&#xef;" 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="&#xf0;" 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="&#xf1;" 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="&#xf2;" 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="&#xf3;" 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="&#xf4;" 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="&#xf5;" 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="&#xf6;" 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="&#xf7;" 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="&#xf8;" 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="&#xf9;" 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="&#xfa;" 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="&#xfb;" 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="&#xfc;" 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="&#xfd;" 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="&#xfe;" 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="&#xff;" 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="&#x152;" 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="&#x153;" 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="&#x178;" 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="&#x2c6;" horiz-adv-x="600" d="M0 1149l434 387h43l123 -387h-65l-123 164l-322 -164h-90z" />
<glyph unicode="&#x2dc;" 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="&#x2000;" horiz-adv-x="1024" />
<glyph unicode="&#x2001;" horiz-adv-x="2048" />
<glyph unicode="&#x2002;" horiz-adv-x="1024" />
<glyph unicode="&#x2003;" horiz-adv-x="2048" />
<glyph unicode="&#x2004;" horiz-adv-x="682" />
<glyph unicode="&#x2005;" horiz-adv-x="512" />
<glyph unicode="&#x2006;" horiz-adv-x="341" />
<glyph unicode="&#x2007;" horiz-adv-x="341" />
<glyph unicode="&#x2008;" horiz-adv-x="256" />
<glyph unicode="&#x2009;" horiz-adv-x="409" />
<glyph unicode="&#x200a;" horiz-adv-x="113" />
<glyph unicode="&#x2010;" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
<glyph unicode="&#x2011;" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
<glyph unicode="&#x2012;" horiz-adv-x="593" d="M39 455l37 161h409l-34 -161h-412z" />
<glyph unicode="&#x2013;" horiz-adv-x="720" d="M39 455l37 161h532l-35 -161h-534z" />
<glyph unicode="&#x2014;" horiz-adv-x="909" d="M39 455l37 161h717l-35 -161h-719z" />
<glyph unicode="&#x2018;" 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="&#x2019;" 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="&#x201a;" 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="&#x201c;" 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="&#x201d;" 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="&#x201e;" 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="&#x2022;" 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="&#x2026;" 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="&#x202f;" horiz-adv-x="409" />
<glyph unicode="&#x2039;" horiz-adv-x="735" d="M41 487l18 84l629 349l-20 -113l-494 -287l397 -237l-24 -117z" />
<glyph unicode="&#x203a;" horiz-adv-x="704" d="M31 166l28 117l492 237l-397 287l18 113l504 -349l-23 -110z" />
<glyph unicode="&#x205f;" horiz-adv-x="512" />
<glyph unicode="&#x20ac;" 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="&#x2122;" 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="&#xe000;" 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="&#x2000;" horiz-adv-x="768" />
<glyph unicode="&#x2001;" horiz-adv-x="1536" />
<glyph unicode="&#x2002;" horiz-adv-x="768" />
<glyph unicode="&#x2003;" horiz-adv-x="1536" />
<glyph unicode="&#x2004;" horiz-adv-x="512" />
<glyph unicode="&#x2005;" horiz-adv-x="384" />
<glyph unicode="&#x2006;" horiz-adv-x="256" />
<glyph unicode="&#x2007;" horiz-adv-x="256" />
<glyph unicode="&#x2008;" horiz-adv-x="192" />
<glyph unicode="&#x2009;" horiz-adv-x="307" />
<glyph unicode="&#x200a;" horiz-adv-x="85" />
<glyph unicode="&#x202f;" horiz-adv-x="307" />
<glyph unicode="&#x205f;" horiz-adv-x="384" />
<glyph unicode="&#x25fc;" 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

Some files were not shown because too many files have changed in this diff Show More