Compare commits
409 Commits
1.2.2
...
2.2.0Alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01dfb7538d | ||
|
|
3f0d4675b6 | ||
|
|
f23c5caf80 | ||
|
|
bd22430b26 | ||
|
|
1189a7fdbc | ||
|
|
f3aa4f84fc | ||
|
|
ea26ce4700 | ||
|
|
a1e649b7e2 | ||
|
|
3b9f2b2cf0 | ||
|
|
7333d19e1c | ||
|
|
232d537d23 | ||
|
|
c6e17e7bcb | ||
|
|
54c6fd55dd | ||
|
|
0625aa1ca8 | ||
|
|
83643f3298 | ||
|
|
ff3c46fe1f | ||
|
|
0930f0dcee | ||
|
|
3221257310 | ||
|
|
8048a73156 | ||
|
|
ea552cd402 | ||
|
|
dcb925f621 | ||
|
|
cce91e1985 | ||
|
|
e17d417c2e | ||
|
|
a69f5bd2df | ||
|
|
97e53eb4d3 | ||
|
|
a6da2b7bee | ||
|
|
4a21e7c217 | ||
|
|
9bd3c7be44 | ||
|
|
434f5c4b2d | ||
|
|
d3cc4f9f07 | ||
|
|
a16aa17c17 | ||
|
|
68445d0409 | ||
|
|
32b68a45cc | ||
|
|
345f8359cc | ||
|
|
81f9886584 | ||
|
|
adbc618808 | ||
|
|
41eafc6b4b | ||
|
|
9f18d8e8c1 | ||
|
|
8c2c853166 | ||
|
|
97914906a0 | ||
|
|
f1ce4ed19b | ||
|
|
99185d8151 | ||
|
|
385b6b7ade | ||
|
|
81ea513f8c | ||
|
|
336b1ddba3 | ||
|
|
7274973322 | ||
|
|
af132965de | ||
|
|
5586742886 | ||
|
|
5868b51490 | ||
|
|
7f17a38b9b | ||
|
|
415e843ebb | ||
|
|
7ffc1192bb | ||
|
|
945e769a03 | ||
|
|
86c7fb86cc | ||
|
|
ff20f3f620 | ||
|
|
e8bef94706 | ||
|
|
d05fe2d680 | ||
|
|
4f8cc3f697 | ||
|
|
6fa619fa37 | ||
|
|
a43f5369ea | ||
|
|
2040173dc2 | ||
|
|
a15b7ec7ac | ||
|
|
6adcf2ce10 | ||
|
|
e756b9b5c1 | ||
|
|
b3de745849 | ||
|
|
77f3dc18b5 | ||
|
|
6b2f15f82e | ||
|
|
570e58611d | ||
|
|
6b69010aec | ||
|
|
e3e2fb7057 | ||
|
|
ece04909e7 | ||
|
|
963920eb88 | ||
|
|
cf5fa542b6 | ||
|
|
1be7e99754 | ||
|
|
14e3334682 | ||
|
|
b1e033dd55 | ||
|
|
111feb1b57 | ||
|
|
886b23d034 | ||
|
|
f2590792b3 | ||
|
|
02a497ed74 | ||
|
|
48df0eed84 | ||
|
|
0f58cbb671 | ||
|
|
9d71670f59 | ||
|
|
7f838ebb38 | ||
|
|
ef1cb05bc8 | ||
|
|
c14b3ed82a | ||
|
|
792e337936 | ||
|
|
6cd2e66052 | ||
|
|
728022b86d | ||
|
|
7718446313 | ||
|
|
66dea54053 | ||
|
|
f19b60bd41 | ||
|
|
09f1c92856 | ||
|
|
589715901d | ||
|
|
3f1a5ff5e0 | ||
|
|
49cd956d4c | ||
|
|
f9acde862f | ||
|
|
503e1dd899 | ||
|
|
c8e12b948d | ||
|
|
18949d68c0 | ||
|
|
0c51b6c016 | ||
|
|
63a5c22c1f | ||
|
|
f76e2a7b56 | ||
|
|
bab151d6f5 | ||
|
|
d43fec088b | ||
|
|
a8ca1cbcd7 | ||
|
|
ada3494483 | ||
|
|
43c238b7f1 | ||
|
|
128d10c51e | ||
|
|
1a1e01f9f6 | ||
|
|
8483e4ab8a | ||
|
|
f6c163b505 | ||
|
|
8f30173db0 | ||
|
|
0372ff95bb | ||
|
|
6fa29c7877 | ||
|
|
d4c9121593 | ||
|
|
76a8df0282 | ||
|
|
0b6d8309a0 | ||
|
|
10a9bc0817 | ||
|
|
2a14af4ffa | ||
|
|
d1a4a292e3 | ||
|
|
14c0efa151 | ||
|
|
4fc03f2581 | ||
|
|
3205b9fda9 | ||
|
|
953e0d6c22 | ||
|
|
b50ce54ca9 | ||
|
|
5e7558ce4a | ||
|
|
8aa6362432 | ||
|
|
02ebb97a8b | ||
|
|
b36063403d | ||
|
|
526ffa2afb | ||
|
|
5b3fd812d8 | ||
|
|
af6dac9cdc | ||
|
|
bc25d936bb | ||
|
|
b497fe1444 | ||
|
|
3f456cce05 | ||
|
|
4dd2f089ec | ||
|
|
b1b1bc248d | ||
|
|
d9e675469c | ||
|
|
ede0ca1772 | ||
|
|
2d098a1477 | ||
|
|
e5f014b68e | ||
|
|
b3a9dc9eeb | ||
|
|
2a06cec27c | ||
|
|
19230c889d | ||
|
|
c969ce552c | ||
|
|
2def600d21 | ||
|
|
02aa8f18c8 | ||
|
|
fcd9522dae | ||
|
|
72d3ce885e | ||
|
|
b428996eb7 | ||
|
|
2b4eb58fad | ||
|
|
240e8dff60 | ||
|
|
1c286afde6 | ||
|
|
2eeb908540 | ||
|
|
562e6ecce9 | ||
|
|
4bd0d32508 | ||
|
|
6f2ccbef80 | ||
|
|
61a6cb6d96 | ||
|
|
443efb5eda | ||
|
|
ba3c731fee | ||
|
|
e55f72dd1d | ||
|
|
b28c0a60a1 | ||
|
|
b7a80bf026 | ||
|
|
fe7218e64b | ||
|
|
0857a9046d | ||
|
|
354131b78a | ||
|
|
e3ae91a4f8 | ||
|
|
52cc5e2e4f | ||
|
|
0c04451442 | ||
|
|
f5ab4a2253 | ||
|
|
1303dfe17a | ||
|
|
55d80f26fa | ||
|
|
7aee585748 | ||
|
|
196858409c | ||
|
|
21467dd62f | ||
|
|
bb30eb7d11 | ||
|
|
fb9f4a7373 | ||
|
|
ccd5c1c75e | ||
|
|
015c578cdd | ||
|
|
8eb4ce2914 | ||
|
|
a6b8108ee6 | ||
|
|
181a56218a | ||
|
|
24feaaebd6 | ||
|
|
e1945e7a35 | ||
|
|
072f65dd9c | ||
|
|
bde03ecc63 | ||
|
|
73c2e23da4 | ||
|
|
e6233831d1 | ||
|
|
82ccbdaa7b | ||
|
|
bf5212a81c | ||
|
|
5c6cc932cf | ||
|
|
ed4430a7e0 | ||
|
|
eb73f78b1f | ||
|
|
314aad0009 | ||
|
|
98d0c5c52f | ||
|
|
4d4da889ec | ||
|
|
4a622f59ba | ||
|
|
913b92088a | ||
|
|
80f4690df8 | ||
|
|
f552531703 | ||
|
|
707d4a7a0c | ||
|
|
be5bebb574 | ||
|
|
4ca2a7a65e | ||
|
|
eed8c9bf50 | ||
|
|
11d5855430 | ||
|
|
b13413b1e5 | ||
|
|
9809474615 | ||
|
|
9ee3a61ae9 | ||
|
|
c71ffa02f8 | ||
|
|
2e862da292 | ||
|
|
08d762c6c9 | ||
|
|
2ef6f9e0e7 | ||
|
|
b8f84cf18d | ||
|
|
7e88af7047 | ||
|
|
2fc365dd57 | ||
|
|
434170862c | ||
|
|
1eb6c426fd | ||
|
|
e2c46d73e4 | ||
|
|
eef02ac7ce | ||
|
|
0d9614755e | ||
|
|
aa0557656c | ||
|
|
2c750f98cb | ||
|
|
82cf2b33cf | ||
|
|
b9cfe0d6f0 | ||
|
|
a0166a4011 | ||
|
|
c9f765813c | ||
|
|
2f52590587 | ||
|
|
fd581fffa5 | ||
|
|
fcdac1cb32 | ||
|
|
a824fa617b | ||
|
|
1fd0c23e55 | ||
|
|
884d4ee91b | ||
|
|
0c7d303568 | ||
|
|
a8ac8609d6 | ||
|
|
0d98a24c67 | ||
|
|
31eeb5e539 | ||
|
|
82dacda359 | ||
|
|
c3f82e49bf | ||
|
|
86a0e734b1 | ||
|
|
934db752bf | ||
|
|
ed379da657 | ||
|
|
55c4bef524 | ||
|
|
ccb329160d | ||
|
|
424626d64b | ||
|
|
026893e10d | ||
|
|
840b03c875 | ||
|
|
325f876010 | ||
|
|
91606a24b8 | ||
|
|
f001d8b749 | ||
|
|
31c0c239f9 | ||
|
|
918c4dbfce | ||
|
|
f587319ef0 | ||
|
|
b4dd942899 | ||
|
|
97a6720fba | ||
|
|
f05c1ef9e8 | ||
|
|
91c1ea97fd | ||
|
|
74faa159e7 | ||
|
|
7e9892bb8d | ||
|
|
0a9e54e5c5 | ||
|
|
0f0d16a104 | ||
|
|
7d7ee6ca6a | ||
|
|
0d96cd3fe8 | ||
|
|
596244543c | ||
|
|
237d6b9414 | ||
|
|
a7c42779f8 | ||
|
|
c49e5f2054 | ||
|
|
6ca6037aa0 | ||
|
|
cc7f360c04 | ||
|
|
75991bffea | ||
|
|
afd1b1968c | ||
|
|
746e9d2a6d | ||
|
|
36b5b5d0f3 | ||
|
|
dd603cfcc8 | ||
|
|
cefce9913a | ||
|
|
0b29f27fcd | ||
|
|
f3f3e27bfe | ||
|
|
519c44a72a | ||
|
|
7e28da0530 | ||
|
|
af70d98b50 | ||
|
|
3f0b84ea22 | ||
|
|
e58abd45ec | ||
|
|
abeee263f0 | ||
|
|
30f68bd7b9 | ||
|
|
f13394d27f | ||
|
|
88e0617429 | ||
|
|
ab94ffc055 | ||
|
|
265ab99cc7 | ||
|
|
738adbe38e | ||
|
|
4e6862cef9 | ||
|
|
7aff60b24d | ||
|
|
8819e38073 | ||
|
|
2bbac91436 | ||
|
|
cf4dea432b | ||
|
|
bd70df1f05 | ||
|
|
b2638c1fac | ||
|
|
4aa9409f5d | ||
|
|
dfa863a54a | ||
|
|
626c04df48 | ||
|
|
6026fa57f0 | ||
|
|
0db28fb5e2 | ||
|
|
c9bf8ced99 | ||
|
|
18a55db245 | ||
|
|
efb9664761 | ||
|
|
3ee412c7a5 | ||
|
|
5afc00a502 | ||
|
|
e5ca0e6415 | ||
|
|
98f121258c | ||
|
|
b2474c51fd | ||
|
|
1bd6ebdb41 | ||
|
|
20ef99326d | ||
|
|
171a1b9ae3 | ||
|
|
cd2c9d151a | ||
|
|
8fbfe9a76a | ||
|
|
63cf0d4f97 | ||
|
|
faa98126f8 | ||
|
|
0dd70249b9 | ||
|
|
aadd99cac3 | ||
|
|
5bede842ba | ||
|
|
1f2ac77b5e | ||
|
|
3f4f35c6d1 | ||
|
|
9575ddbdb4 | ||
|
|
3ed918d98d | ||
|
|
89d5af3372 | ||
|
|
fa8f40eee3 | ||
|
|
8f06035500 | ||
|
|
8f7d969099 | ||
|
|
5422785feb | ||
|
|
911f82c00b | ||
|
|
fbff8c991b | ||
|
|
f1b139d55d | ||
|
|
9c7f196e20 | ||
|
|
4d8a37006e | ||
|
|
bc9d3d561f | ||
|
|
fd9e80bdf5 | ||
|
|
aee2f71170 | ||
|
|
102160d651 | ||
|
|
e33f26d33c | ||
|
|
57113fa02f | ||
|
|
2be7575f98 | ||
|
|
c5647b46e1 | ||
|
|
3450cff92f | ||
|
|
13a11411a9 | ||
|
|
6e7eb9dec4 | ||
|
|
c74eed7c0e | ||
|
|
2fc6811495 | ||
|
|
57e0dac45b | ||
|
|
537e31000e | ||
|
|
e0872f4536 | ||
|
|
9dddf6dd2e | ||
|
|
7a0c5feed3 | ||
|
|
42d154f0b7 | ||
|
|
44b0ab2203 | ||
|
|
4af59b50ad | ||
|
|
b309099f0b | ||
|
|
affea99cb1 | ||
|
|
9bc35a3026 | ||
|
|
57e9d499fb | ||
|
|
d53cf598a4 | ||
|
|
bdaca2bd37 | ||
|
|
53e3af9b30 | ||
|
|
9cfef895f8 | ||
|
|
56fa9644a5 | ||
|
|
8eff51a96b | ||
|
|
d130a1d44a | ||
|
|
98316fd282 | ||
|
|
59f1ea3073 | ||
|
|
270757f3bd | ||
|
|
843c6b36a8 | ||
|
|
f71a2a8fc2 | ||
|
|
63fc763958 | ||
|
|
9bc0aac63d | ||
|
|
ff529da874 | ||
|
|
ff40944f00 | ||
|
|
1d0ac46c7e | ||
|
|
d62bb1e5b6 | ||
|
|
89f91e46b7 | ||
|
|
bc2daa5f8b | ||
|
|
0fbf240a58 | ||
|
|
83d57b33f7 | ||
|
|
59ae23e315 | ||
|
|
df26adfe89 | ||
|
|
d1a92aeb36 | ||
|
|
c09f1b2f1c | ||
|
|
a70a1e6290 | ||
|
|
dde5258b59 | ||
|
|
dc438e6eb7 | ||
|
|
37a9a97f4f | ||
|
|
4fb6e3fe7b | ||
|
|
7884848c78 | ||
|
|
2ece328e50 | ||
|
|
b8ac3cd22f | ||
|
|
9b4bd7a3f0 | ||
|
|
facefc5c58 | ||
|
|
5d6a6b1af7 | ||
|
|
916e0ead99 | ||
|
|
aff7b07f33 | ||
|
|
8267b429ca | ||
|
|
5759bee1df | ||
|
|
f2648ec85c | ||
|
|
e083722f0b | ||
|
|
f03e63fa54 | ||
|
|
f33c3e30eb | ||
|
|
c9ee0b0fcb | ||
|
|
7aaa8036bc | ||
|
|
00f2410d2d | ||
|
|
be77a494db | ||
|
|
787a95bdd2 | ||
|
|
720ce591b7 |
@@ -1,5 +1,5 @@
|
||||
*******************************************
|
||||
*** This is SABnzbd 1.2.x ***
|
||||
*** This is SABnzbd 2.2.0 ***
|
||||
*******************************************
|
||||
SABnzbd is an open-source cross-platform binary newsreader.
|
||||
It simplifies the process of downloading from Usenet dramatically,
|
||||
@@ -12,9 +12,6 @@ and offers a complete API for third-party applications to hook into.
|
||||
There is an extensive Wiki on the use of SABnzbd.
|
||||
https://sabnzbd.org/wiki/
|
||||
|
||||
IMPORTANT INFORMATION about release 1.x.x:
|
||||
https://sabnzbd.org/wiki/new-features-and-changes
|
||||
|
||||
Please also read the file "ISSUES.txt"
|
||||
|
||||
The organization of the download queue is different from 0.7.x (and older).
|
||||
|
||||
27
INSTALL.txt
@@ -1,4 +1,4 @@
|
||||
SABnzbd 1.2.1
|
||||
SABnzbd 2.2.0
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
0) LICENSE
|
||||
@@ -21,7 +21,7 @@ along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
1) INSTALL with the Win32 installer
|
||||
1) INSTALL with the Windows installer
|
||||
-------------------------------------------------------------------------------
|
||||
Just run the downloaded EXE file and the installer will start.
|
||||
It's just a simple standard installer.
|
||||
@@ -32,7 +32,7 @@ Use the "Help" button in the web-interface to be directed to the Help Wiki.
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
2) INSTALL pre-built Win32 binaries
|
||||
2) INSTALL pre-built Windows binaries
|
||||
-------------------------------------------------------------------------------
|
||||
Unzip pre-built version to any folder of your liking.
|
||||
Start the SABnzbd.exe program.
|
||||
@@ -41,11 +41,11 @@ Use the "Help" button in the web-interface to be directed to the Help Wiki.
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
3) INSTALL pre-built OSX binaries
|
||||
3) INSTALL pre-built macOS binaries
|
||||
-------------------------------------------------------------------------------
|
||||
Download the DMG file, mount and drag the SABnzbd icon to Programs.
|
||||
Just like you do with so many apps.
|
||||
Make sure you pick the right folder, depending on your OSX version.
|
||||
Make sure you pick the right folder, depending on your macOS version.
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
@@ -55,33 +55,30 @@ Make sure you pick the right folder, depending on your OSX version.
|
||||
You need to have Python installed plus some non-standard Python modules
|
||||
and a few tools.
|
||||
|
||||
Unix/Linux/OSX
|
||||
Unix/Linux/macOS
|
||||
Python-2.7.latest http://www.python.org (2.7.9+ recommended)
|
||||
|
||||
OSX Mavericks or newer
|
||||
Apple Python 2.7 Included in OSX (default)
|
||||
|
||||
Windows
|
||||
Python-2.7.latest http://www.python.org (2.7.9+ recommended)
|
||||
PyWin32 use "pip install pypiwin32"
|
||||
|
||||
Essential modules
|
||||
cheetah-2.0.1+ use "pip install cheetah"
|
||||
par2cmdline >= 0.4 http://parchive.sourceforge.net/
|
||||
Note: https://sabnzbd.org/wiki/configuration/1.2/switches#par2cmdline
|
||||
par2cmdline >= 0.4 https://github.com/Parchive/par2cmdline/releases
|
||||
See also: https://sabnzbd.org/wiki/installation/multicore-par2
|
||||
unrar >= 5.00+ http://www.rarlab.com/rar_add.htm
|
||||
|
||||
Optional modules
|
||||
unzip >= 6.00 http://www.info-zip.org/
|
||||
7zip >= 9.20 http://www.7zip.org/
|
||||
yenc module >= 0.4 use "pip install yenc"
|
||||
https://sabnzbd.org/wiki/installation/yenc-0.4_py2.7.rar (Win32-only)
|
||||
openssl => 1.0.0 http://www.openssl.org/
|
||||
sabyenc == 3.0.2 use "pip install sabyenc"
|
||||
More information: https://sabnzbd.org/sabyenc
|
||||
openssl >= 1.0.0 http://www.openssl.org/
|
||||
v0.9.8 will work, but limits certificate validation
|
||||
cryptography >= 1.0 use "pip install cryptography"
|
||||
Enables certificate generation and detection of encrypted RAR-files
|
||||
|
||||
Optional modules Unix/Linux/OSX
|
||||
Optional modules Unix/Linux/macOS
|
||||
pynotify Should be part of GTK for Python support on Debian/Ubuntu
|
||||
If not, you cannot use the NotifyOSD feature.
|
||||
python-dbus Enable option to Shutdown/Restart/Standby PC on queue finish.
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
For these the server blocking method is not very favourable.
|
||||
There is an INI-only option that will limit blocks to 1 minute.
|
||||
no_penalties = 1
|
||||
See: https://sabnzbd.org/wiki/configuration/1.2/special
|
||||
See: https://sabnzbd.org/wiki/configuration/2.1/special
|
||||
|
||||
- Some third-party utilties try to probe SABnzbd API in such a way that you will
|
||||
often see warnings about unauthenticated access.
|
||||
If you are sure these probes are harmless, you can suppress the warnings by
|
||||
setting the option "api_warnings" to 0.
|
||||
See: https://sabnzbd.org/wiki/configuration/1.2/special
|
||||
See: https://sabnzbd.org/wiki/configuration/2.1/special
|
||||
|
||||
- On OSX you may encounter downloaded files with foreign characters.
|
||||
The par2 repair may fail when the files were created on a Windows system.
|
||||
@@ -41,7 +41,7 @@
|
||||
You will see this only when downloaded files contain accented characters.
|
||||
You need to fix it yourself by running the convmv utility (available for most Linux platforms).
|
||||
Possible the file system override setting 'fsys_type' might be solve things:
|
||||
See: https://sabnzbd.org/wiki/configuration/1.2/special
|
||||
See: https://sabnzbd.org/wiki/configuration/2.1/special
|
||||
|
||||
- The "Watched Folder" sometimes fails to delete the NZB files it has
|
||||
processed. This happens when other software still accesses these files.
|
||||
@@ -81,4 +81,4 @@
|
||||
- Squeeze Linux
|
||||
There is a "special" option that will allow you to select an alternative library.
|
||||
use_pickle = 1
|
||||
See: https://sabnzbd.org/wiki/configuration/1.2/special
|
||||
See: https://sabnzbd.org/wiki/configuration/2.1/special
|
||||
|
||||
6
PKG-INFO
@@ -1,8 +1,8 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SABnzbd
|
||||
Version: 1.2.RC1
|
||||
Summary: SABnzbd-1.2.1RC1
|
||||
Home-page: http://sabnzbd.org
|
||||
Version: 2.2.0Alpha3
|
||||
Summary: SABnzbd-2.2.0Alpha3
|
||||
Home-page: https://sabnzbd.org
|
||||
Author: The SABnzbd Team
|
||||
Author-email: team@sabnzbd.org
|
||||
License: GNU General Public License 2 (GPL2 or later)
|
||||
|
||||
28
README.md
@@ -1,37 +1,25 @@
|
||||
SABnzbd - The automated Usenet download tool
|
||||
============================================
|
||||
|
||||
This Unicode release is not compatible with 0.7.x queues!
|
||||
|
||||
There is also an issue with upgrading of the "sabnzbd.ini" file.
|
||||
Make sure that you have a backup!
|
||||
|
||||
Saved queues may not be compatible after updates.
|
||||
|
||||
----
|
||||
|
||||
SABnzbd is an Open Source Binary Newsreader written in Python.
|
||||
|
||||
It's totally free, incredibly easy to use, and works practically everywhere.
|
||||
|
||||
SABnzbd makes Usenet as simple and streamlined as possible by automating everything we can. All you have to do is add an .nzb. SABnzbd takes over from there, where it will be automatically downloaded, verified, repaired, extracted and filed away with zero human interaction.
|
||||
|
||||
If you want to know more you can head over to our website: http://sabnzbd.org.
|
||||
SABnzbd makes Usenet as simple and streamlined as possible by automating everything we can. All you have to do is add an `.nzb`. SABnzbd takes over from there, where it will be automatically downloaded, verified, repaired, extracted and filed away with zero human interaction.
|
||||
If you want to know more you can head over to our website: https://sabnzbd.org.
|
||||
|
||||
## Resolving Dependencies
|
||||
|
||||
SABnzbd has a good deal of dependencies you'll need before you can get running. If you've previously run SABnzbd from one of the various Linux packages floating around (Ubuntu, Debian, Fedora, etc), then you likely already have all the needed dependencies. If not, here's what you're looking for:
|
||||
SABnzbd has a good deal of dependencies you'll need before you can get running. If you've previously run SABnzbd from one of the various Linux packages, then you likely already have all the needed dependencies. If not, here's what you're looking for:
|
||||
|
||||
- `python` (only 2.7.x and higher, but not 3.x.x)
|
||||
- `python-cheetah`
|
||||
- `python-support`
|
||||
- `par2` (Multi-threaded par2 installation guide can be found [here](https://forums.sabnzbd.org/viewtopic.php?f=16&t=18793#p99702))
|
||||
- `par2` (Multi-threaded par2 installation guide can be found [here](https://sabnzbd.org/wiki/installation/multicore-par2))
|
||||
- `unrar` (Make sure you get the "official" non-free version of unrar)
|
||||
- `sabyenc` (installation guide can be found [here](https://sabnzbd.org/sabyenc))
|
||||
|
||||
Optional:
|
||||
|
||||
- `python-cryptography` (enables certificate generation and detection of encrypted RAR-files during download)
|
||||
- `python-yenc`
|
||||
- `python-dbus` (enable option to Shutdown/Restart/Standby PC on queue finish)
|
||||
- `7zip`
|
||||
- `unzip`
|
||||
@@ -43,13 +31,13 @@ Your package manager should supply these. If not, we've got links in our more in
|
||||
Once you've sorted out all the dependencies, simply run:
|
||||
|
||||
```
|
||||
python SABnzbd.py
|
||||
python -OO SABnzbd.py
|
||||
```
|
||||
|
||||
Or, if you want to run in the background:
|
||||
|
||||
```
|
||||
python SABnzbd.py -d -f /path/to/sabnzbd.ini
|
||||
python -OO SABnzbd.py -d -f /path/to/sabnzbd.ini
|
||||
```
|
||||
|
||||
If you want multi-language support, run:
|
||||
@@ -68,7 +56,7 @@ Basically:
|
||||
- `develop` is the target for integration and is **not** intended for end-users.
|
||||
- `1.1.x` is a release and maintenance branch for 1.1.x (1.1.0 -> 1.1.1 -> 1.1.2) and is **not** intended for end-users.
|
||||
- `feature/my_feature` is a temporary feature branch based on `develop`.
|
||||
- `hotfix/my_hotfix` is an optional temporary branch for bugfix(es) based on `develop`.
|
||||
- `bugfix/my_bugfix` is an optional temporary branch for bugfix(es) based on `develop`.
|
||||
|
||||
Conditions:
|
||||
- Merging of a stable release into `master` will be simple: the release branch is always right.
|
||||
|
||||
122
README.mkd
@@ -1,63 +1,79 @@
|
||||
Release Notes - SABnzbd 1.2.1RC1
|
||||
==============================================
|
||||
Release Notes - SABnzbd 2.2.0 Alpha 3
|
||||
=========================================================
|
||||
|
||||
## What's new in 1.2.1 RC1
|
||||
- On Windows paths passed to scripts were in \\?\ notation
|
||||
- New self-signed certificates now list local IP in SAN-list
|
||||
- Retry rename 3x before falling back to copy during "Moving"
|
||||
- Fix RSS page problems with missing fields
|
||||
- Passwords were not parsed correctly from filenames
|
||||
- Catch several SSL errors of the webserver
|
||||
- Multi-feed RSS will not stop if only 1 feed is not functioning
|
||||
- More incorrectly labeled some downloads as Encrypted
|
||||
NOTE: Due to changes in this release, the queue will be converted when 2.2.0
|
||||
is started for the first time. Job order, settings and data will be
|
||||
preserved, but all jobs will be unpaused and URL's that did not finish
|
||||
fetching before the upgrade will be lost!
|
||||
|
||||
## Changes since 2.1.0
|
||||
- Direct Unpack: Jobs will start unpacking during the download, reduces
|
||||
post-processing time but requires capable hard drive. Only works for jobs that
|
||||
do not need repair. Will be enabled if your incomplete folder-speed > 60MB/s
|
||||
- Reduced memory usage, especially with larger queues
|
||||
- Removed 5 second delay between fetching URLs
|
||||
- Notifications can now be limited to certain Categories
|
||||
- Each item in the Queue and Filelist now has Move to Top/Bottom buttons
|
||||
- Smoother animations in Firefox (disabled previously due to FF high-CPU usage)
|
||||
- Jobs outside server retention are processed faster
|
||||
- Show missing articles in MB instead of number of articles
|
||||
- Obfuscated filenames are renamed during downloading, if possible
|
||||
- If enable_par_cleanup is disabled all par2 files be downloaded
|
||||
- If enabled, replace dots in filenames also when there are spaces already
|
||||
- Update GNTP bindings to 1.0.3
|
||||
- max_art_opt and replace_illegal moved from Switches to Specials
|
||||
- Removed Specials enable_meta, par2_multicore and allow_streaming
|
||||
- Windows: Full unicode support when calling repair and unpack
|
||||
- Windows: Move enable_multipar to Specials
|
||||
- Windows: Better indication of verification process before and after repair
|
||||
- Windows: MultiPar verification of a job is skipped after blocks are fetched
|
||||
- Windows & macOS: removed par2cmdline in favor of par2tbb/Multipar
|
||||
|
||||
## What's new in 1.2.1 Beta 1
|
||||
- QuickCheck will perform fast rename of obfuscated posts
|
||||
- RSS Downloaded page now shows icon to indicate source
|
||||
- HTML tags are filtered from single-line script output
|
||||
## Bugfixes since 2.1.0
|
||||
- Shutdown/suspend did not work on some Linux systems
|
||||
- Deleting a job could result in write errors
|
||||
- Display warning if custom par2 parameters are wrong
|
||||
- RSS URLs with commas were broken
|
||||
- Fixed some "Saving failed" errors
|
||||
- Fixed crashing URLGrabber
|
||||
- Jobs with renamed files are now correctly handled when using Retry
|
||||
- Disk-space readings could be updated incorrectly
|
||||
- Correct redirect after enabling HTTPS in the Config
|
||||
- Fix race-condition in Post-processing
|
||||
- History would not always show latest changes
|
||||
- Convert HTML in error messages
|
||||
- Fixed unicode error during Sorting
|
||||
- Not all texts were shown in the selected Language
|
||||
- Windows: Fix error in MultiPar-code when first par2-file was damaged
|
||||
- macOS: Catch 'Protocol wrong type for socket' errors
|
||||
|
||||
## Bug fixes in 1.2.1 Beta 1
|
||||
- Fix crashing Assembler
|
||||
- Cloaked files (RAR within RAR) were not detected anymore
|
||||
- 'Only Download Top of Queue' was broken for a long time
|
||||
- Incorrectly labeled some downloads as Encrypted
|
||||
- RSS reading could fail on newznab attributes
|
||||
- Incorrectly marking jobs with folders inside as failed
|
||||
- Handle jobs on Windows with forbidden names (Con.*, Aux.*,..)
|
||||
- Categories were not matched properly if a list of tags was set
|
||||
- PostProcessing-script was not called on Accept&Fail or Dupe detect
|
||||
- Support for newer par2cmdline(-mt) versions that need -B parameter
|
||||
- Some newsservers would timeout when connecting
|
||||
- Duplicate detection set to Fail would not work for RSS feeds
|
||||
- More robust detection of execute permissions for scripts
|
||||
- CPU type reporting on Windows and macOS
|
||||
- Failed to start with some localhost configs
|
||||
- Removed some more stalling issues
|
||||
## Translations
|
||||
- Added Hebrew translation by ION IL, many other languages updated.
|
||||
|
||||
## About
|
||||
SABnzbd is an open-source cross-platform binary newsreader.
|
||||
It simplifies the process of downloading from Usenet dramatically,
|
||||
thanks to its web-based user interface and advanced
|
||||
built-in post-processing options that automatically verify, repair,
|
||||
extract and clean up posts downloaded from Usenet.
|
||||
|
||||
(c) Copyright 2007-2017 by "The SABnzbd-team" \<team@sabnzbd.org\>
|
||||
|
||||
### IMPORTANT INFORMATION about release 1.x.x
|
||||
<https://sabnzbd.org/wiki/new-features-and-changes>
|
||||
|
||||
### Known problems and solutions
|
||||
- Read the file "ISSUES.txt"
|
||||
|
||||
### Upgrading from 0.7.x and older
|
||||
## Upgrading from 0.7.x and older
|
||||
- Finish queue
|
||||
- Stop SABnzbd
|
||||
- Install new version
|
||||
- Start SABnzbd
|
||||
|
||||
The organization of the download queue is different from older versions.
|
||||
1.x.x will not see the existing queue, but you can go to
|
||||
Status->QueueRepair and "Repair" the old queue.
|
||||
Also, your sabnzbd.ini file will be upgraded, making it
|
||||
incompatible with releases older than 0.7.9
|
||||
## Upgrade notices (from pre-2.x.x)
|
||||
- Windows: When starting the Post-Processing script, the path to the job folder
|
||||
is no longer in short-path notation but includes the full path. To support
|
||||
long paths (>255), you might need to alter them to long-path notation (\\?\).
|
||||
- Schedule items are converted when upgrading to 2.x.x and will break when
|
||||
reverted back to pre-2.x.x releases.
|
||||
- The organization of the download queue is different from 0.7.x releases.
|
||||
So 2.x.x will not see the existing queue, but you can go to Status->Queue Repair
|
||||
and "Repair" the old queue.
|
||||
|
||||
## Known problems and solutions
|
||||
- Read the file "ISSUES.txt"
|
||||
|
||||
## About
|
||||
SABnzbd is an open-source cross-platform binary newsreader.
|
||||
It simplifies the process of downloading from Usenet dramatically, thanks
|
||||
to its web-based user interface and advanced built-in post-processing options
|
||||
that automatically verify, repair, extract and clean up posts downloaded
|
||||
from Usenet.
|
||||
|
||||
(c) Copyright 2007-2017 by "The SABnzbd-team" \<team@sabnzbd.org\>
|
||||
|
||||
285
SABnzbd.py
@@ -56,8 +56,6 @@ if [int(n) for n in cherrypy.__version__.split('.')] < [8, 1, 2]:
|
||||
print 'Sorry, requires Python module Cherrypy 8.1.2+ (use the included version)'
|
||||
sys.exit(1)
|
||||
|
||||
from cherrypy import _cpserver
|
||||
|
||||
SQLITE_DLL = True
|
||||
try:
|
||||
from sqlite3 import version as sqlite3_version
|
||||
@@ -90,8 +88,8 @@ from sabnzbd.misc import real_path, \
|
||||
check_latest_version, exit_sab, \
|
||||
split_host, get_ext, create_https_certificates, \
|
||||
windows_variant, ip_extract, set_serv_parms, get_serv_parms, globber_full
|
||||
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, panic_fwall, \
|
||||
panic_sqlite, panic, launch_a_browser, panic_xport
|
||||
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, \
|
||||
panic_sqlite, panic, launch_a_browser
|
||||
import sabnzbd.scheduler as scheduler
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg
|
||||
@@ -130,24 +128,6 @@ def guard_loglevel():
|
||||
LOG_FLAG = True
|
||||
|
||||
|
||||
class FilterCP3:
|
||||
# Filter out all CherryPy3-Access logging that we receive,
|
||||
# because we have the root logger
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def filter(self, record):
|
||||
_cplogging = record.module == '_cplogging'
|
||||
# Python2.4 fix
|
||||
# record has no attribute called funcName under python 2.4
|
||||
if hasattr(record, 'funcName'):
|
||||
access = record.funcName == 'access'
|
||||
else:
|
||||
access = True
|
||||
return not (_cplogging and access)
|
||||
|
||||
|
||||
class guiHandler(logging.Handler):
|
||||
""" Logging handler collects the last warnings/errors/exceptions
|
||||
to be displayed in the web-gui
|
||||
@@ -202,10 +182,9 @@ def print_help():
|
||||
print " -f --config-file <ini> Location of config file"
|
||||
print " -s --server <srv:port> Listen on server:port [*]"
|
||||
print " -t --templates <templ> Template directory [*]"
|
||||
print " -2 --template2 <templ> Secondary template dir [*]"
|
||||
print
|
||||
print " -l --logging <0..2> Set logging level (-1=off, 0= least, 2= most) [*]"
|
||||
print " -w --weblogging <0..2> Set cherrypy logging (0= off, 1= on, 2= file-only) [*]"
|
||||
print " -w --weblogging Enable cherrypy access logging"
|
||||
print
|
||||
print " -b --browser <0..1> Auto browser launch (0= off, 1= on) [*]"
|
||||
if sabnzbd.WIN32:
|
||||
@@ -215,7 +194,6 @@ def print_help():
|
||||
print " --pid <path> Create a PID file in the given folder (full path)"
|
||||
print " --pidfile <path> Create a PID file with the given name (full path)"
|
||||
print
|
||||
print " --force Discard web-port timeout (see Wiki!)"
|
||||
print " -h --help Print this message"
|
||||
print " -v --version Print version information"
|
||||
print " -c --clean Remove queue, cache and logs"
|
||||
@@ -224,6 +202,7 @@ def print_help():
|
||||
print " --repair-all Try to reconstruct the queue from the incomplete folder"
|
||||
print " with full data reconstruction"
|
||||
print " --https <port> Port to use for HTTPS server"
|
||||
print " --no-login Start with username and password reset"
|
||||
print " --log-all Log all article handling (for developers)"
|
||||
print " --console Force console logging for OSX app"
|
||||
print " --new Run a new instance of SABnzbd"
|
||||
@@ -274,9 +253,7 @@ def Bail_Out(browserhost, cherryport, err=''):
|
||||
""" Abort program because of CherryPy troubles """
|
||||
logging.error(T('Failed to start web-interface') + ' : ' + str(err))
|
||||
if not sabnzbd.DAEMON:
|
||||
if '13' in err:
|
||||
panic_xport(browserhost, cherryport)
|
||||
elif '49' in err:
|
||||
if '49' in err:
|
||||
panic_host(browserhost, cherryport)
|
||||
else:
|
||||
panic_port(browserhost, cherryport)
|
||||
@@ -304,9 +281,6 @@ def Web_Template(key, defweb, wdir):
|
||||
logging.info("Web dir is %s", full_dir)
|
||||
|
||||
if not os.path.exists(full_main):
|
||||
# Temporarily fix that allows missing Config
|
||||
if defweb == DEF_STDCONFIG:
|
||||
return ''
|
||||
# end temp fix
|
||||
logging.warning(T('Cannot find web template: %s, trying standard template'), full_main)
|
||||
full_dir = real_path(sabnzbd.DIR_INTERFACES, DEF_STDINTF)
|
||||
@@ -316,8 +290,6 @@ def Web_Template(key, defweb, wdir):
|
||||
panic_tmpl(full_dir)
|
||||
exit_sab(1)
|
||||
|
||||
# sabnzbd.lang.install_language(real_path(full_dir, DEF_INT_LANGUAGE), sabnzbd.cfg.language(), wdir)
|
||||
|
||||
return real_path(full_dir, "templates")
|
||||
|
||||
|
||||
@@ -428,10 +400,23 @@ def GetProfileInfo(vista_plus):
|
||||
|
||||
def print_modules():
|
||||
""" Log all detected optional or external modules """
|
||||
if sabnzbd.decoder.HAVE_YENC:
|
||||
logging.info("_yenc module... found!")
|
||||
if sabnzbd.decoder.SABYENC_ENABLED:
|
||||
# Yes, we have SABYenc, and it's the correct version, so it's enabled
|
||||
logging.info("SABYenc module (v%s)... found!", sabnzbd.constants.SABYENC_VERSION_REQUIRED)
|
||||
else:
|
||||
logging.warning(T('_yenc module... NOT found!'))
|
||||
# Something wrong with SABYenc, so let's determine and print what:
|
||||
if sabnzbd.decoder.SABYENC_VERSION:
|
||||
# We have a VERSION, thus a SABYenc module, but it's not the correct version
|
||||
logging.warning(T("SABYenc disabled: no correct version found! (Found v%s, expecting v%s)") % (sabnzbd.decoder.SABYENC_VERSION, sabnzbd.constants.SABYENC_VERSION_REQUIRED))
|
||||
else:
|
||||
# No SABYenc module at all
|
||||
logging.warning(T("SABYenc module... NOT found! Expecting v%s - https://sabnzbd.org/sabyenc") % sabnzbd.constants.SABYENC_VERSION_REQUIRED)
|
||||
|
||||
# No correct SABYenc version or no SABYenc at all, so now we care about old-yEnc
|
||||
if sabnzbd.decoder.HAVE_YENC:
|
||||
logging.info("_yenc module... found!")
|
||||
else:
|
||||
logging.error(T('_yenc module... NOT found!'))
|
||||
|
||||
if sabnzbd.HAVE_CRYPTOGRAPHY:
|
||||
logging.info('Cryptography module (v%s)... found!', sabnzbd.HAVE_CRYPTOGRAPHY)
|
||||
@@ -443,14 +428,21 @@ def print_modules():
|
||||
else:
|
||||
logging.error(T('par2 binary... NOT found!'))
|
||||
|
||||
if sabnzbd.newsunpack.PAR2C_COMMAND:
|
||||
logging.info("par2cmdline binary... found (%s)", sabnzbd.newsunpack.PAR2C_COMMAND)
|
||||
if sabnzbd.newsunpack.MULTIPAR_COMMAND:
|
||||
logging.info("MultiPar binary... found (%s)", sabnzbd.newsunpack.MULTIPAR_COMMAND)
|
||||
|
||||
if sabnzbd.newsunpack.RAR_COMMAND:
|
||||
logging.info("UNRAR binary... found (%s)", sabnzbd.newsunpack.RAR_COMMAND)
|
||||
|
||||
# Report problematic unrar
|
||||
if sabnzbd.newsunpack.RAR_PROBLEM and not sabnzbd.cfg.ignore_wrong_unrar():
|
||||
have_str = '%.2f' % (float(sabnzbd.newsunpack.RAR_VERSION) / 100)
|
||||
want_str = '%.2f' % (float(sabnzbd.constants.REC_RAR_VERSION) / 100)
|
||||
logging.warning(T('Your UNRAR version is %s, we recommend version %s or higher.<br />') % (have_str, want_str))
|
||||
elif not (sabnzbd.WIN32 or sabnzbd.DARWIN):
|
||||
logging.debug('UNRAR binary version %.2f', (float(sabnzbd.newsunpack.RAR_VERSION) / 100))
|
||||
else:
|
||||
logging.warning(T('unrar binary... NOT found'))
|
||||
logging.error(T('unrar binary... NOT found'))
|
||||
|
||||
if sabnzbd.newsunpack.ZIP_COMMAND:
|
||||
logging.info("unzip binary... found (%s)", sabnzbd.newsunpack.ZIP_COMMAND)
|
||||
@@ -651,7 +643,7 @@ def attach_server(host, port, cert=None, key=None, chain=None):
|
||||
http_server = cherrypy._cpserver.Server()
|
||||
http_server.bind_addr = (host, port)
|
||||
if cert and key:
|
||||
http_server.ssl_provider = 'builtin'
|
||||
http_server.ssl_module = 'builtin'
|
||||
http_server.ssl_certificate = cert
|
||||
http_server.ssl_private_key = key
|
||||
http_server.ssl_certificate_chain = chain
|
||||
@@ -730,24 +722,6 @@ def evaluate_inipath(path):
|
||||
return path
|
||||
|
||||
|
||||
def cherrypy_logging(log_path, log_handler):
|
||||
""" Setup CherryPy logging """
|
||||
log = cherrypy.log
|
||||
log.access_file = ''
|
||||
log.error_file = ''
|
||||
# Max size of 512KB
|
||||
maxBytes = getattr(log, "rot_maxBytes", 524288)
|
||||
# cherrypy.log cherrypy.log.1 cherrypy.log.2
|
||||
backupCount = getattr(log, "rot_backupCount", 3)
|
||||
|
||||
# Make a new RotatingFileHandler for the error log.
|
||||
fname = getattr(log, "rot_error_file", log_path)
|
||||
h = log_handler(fname, 'a', maxBytes, backupCount)
|
||||
h.setLevel(logging.DEBUG)
|
||||
h.setFormatter(cherrypy._cplogging.logfmt)
|
||||
log.error_log.addHandler(h)
|
||||
|
||||
|
||||
def commandline_handler(frozen=True):
|
||||
""" Split win32-service commands are true parameters
|
||||
Returns:
|
||||
@@ -778,9 +752,9 @@ def commandline_handler(frozen=True):
|
||||
info.extend(sys.argv[slice:])
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(info, "phdvncw:l:s:f:t:b:2:",
|
||||
opts, args = getopt.getopt(info, "phdvncwl:s:f:t:b:2:",
|
||||
['pause', 'help', 'daemon', 'nobrowser', 'clean', 'logging=',
|
||||
'weblogging=', 'server=', 'templates', 'ipv6_hosting=',
|
||||
'weblogging', 'server=', 'templates', 'ipv6_hosting=',
|
||||
'template2', 'browser=', 'config-file=', 'force',
|
||||
'version', 'https=', 'autorestarted', 'repair', 'repair-all',
|
||||
'log-all', 'no-login', 'pid=', 'new', 'console', 'pidfile=',
|
||||
@@ -845,10 +819,8 @@ def main():
|
||||
clean_up = False
|
||||
logging_level = None
|
||||
web_dir = None
|
||||
web_dir2 = None
|
||||
vista_plus = False
|
||||
vista64 = False
|
||||
force_web = False
|
||||
repair = 0
|
||||
api_url = None
|
||||
no_login = False
|
||||
@@ -879,8 +851,6 @@ def main():
|
||||
exit_sab(0)
|
||||
elif opt in ('-t', '--templates'):
|
||||
web_dir = arg
|
||||
elif opt in ('-2', '--template2'):
|
||||
web_dir2 = arg
|
||||
elif opt in ('-s', '--server'):
|
||||
(cherryhost, cherryport) = split_host(arg)
|
||||
elif opt in ('-n', '--nobrowser'):
|
||||
@@ -895,13 +865,7 @@ def main():
|
||||
elif opt in ('-c', '--clean'):
|
||||
clean_up = True
|
||||
elif opt in ('-w', '--weblogging'):
|
||||
try:
|
||||
cherrypylogging = int(arg)
|
||||
except:
|
||||
cherrypylogging = -1
|
||||
if cherrypylogging < 0 or cherrypylogging > 2:
|
||||
print_help()
|
||||
exit_sab(1)
|
||||
cherrypylogging = True
|
||||
elif opt in ('-l', '--logging'):
|
||||
try:
|
||||
logging_level = int(arg)
|
||||
@@ -915,9 +879,6 @@ def main():
|
||||
exit_sab(0)
|
||||
elif opt in ('-p', '--pause'):
|
||||
pause = True
|
||||
elif opt in ('--force',):
|
||||
force_web = True
|
||||
sabnzbd.RESTART_ARGS.append(opt)
|
||||
elif opt in ('--https',):
|
||||
https_port = int(arg)
|
||||
sabnzbd.RESTART_ARGS.append(opt)
|
||||
@@ -1035,13 +996,13 @@ def main():
|
||||
if sabnzbd.DAEMON:
|
||||
if enable_https and https_port:
|
||||
try:
|
||||
cherrypy.process.servers.check_port(cherryhost, https_port, timeout=0.025)
|
||||
cherrypy.process.servers.check_port(cherryhost, https_port, timeout=0.05)
|
||||
except IOError, error:
|
||||
Bail_Out(browserhost, cherryport)
|
||||
except:
|
||||
Bail_Out(browserhost, cherryport, '49')
|
||||
try:
|
||||
cherrypy.process.servers.check_port(cherryhost, cherryport, timeout=0.025)
|
||||
cherrypy.process.servers.check_port(cherryhost, cherryport, timeout=0.05)
|
||||
except IOError, error:
|
||||
Bail_Out(browserhost, cherryport)
|
||||
except:
|
||||
@@ -1054,58 +1015,62 @@ def main():
|
||||
if url and check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
exit_sab(0)
|
||||
|
||||
# If an instance of sabnzbd(same version) is already running on this port, launch the browser
|
||||
# If another program or sabnzbd version is on this port, try 10 other ports going up in a step of 5
|
||||
# If 'Port is not bound' (firewall) do not do anything (let the script further down deal with that).
|
||||
notify_port_change = False
|
||||
|
||||
# SSL
|
||||
if enable_https:
|
||||
port = https_port or cherryport
|
||||
try:
|
||||
cherrypy.process.servers.check_port(browserhost, port, timeout=0.025)
|
||||
cherrypy.process.servers.check_port(browserhost, port, timeout=0.05)
|
||||
except IOError, error:
|
||||
if str(error) == 'Port not bound.':
|
||||
pass
|
||||
else:
|
||||
if not url:
|
||||
url = 'https://%s:%s/sabnzbd/api?' % (browserhost, port)
|
||||
if not sabnzbd.cfg.fixed_ports():
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
newport = find_free_port(browserhost, port)
|
||||
if newport > 0:
|
||||
notify_port_change = True
|
||||
# Save the new port
|
||||
if https_port:
|
||||
https_port = newport
|
||||
sabnzbd.cfg.https_port.set(newport)
|
||||
else:
|
||||
# In case HTTPS == HTTP port
|
||||
http_port = newport
|
||||
sabnzbd.cfg.port.set(newport)
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
# Bail out if we have fixed our ports after first start-up
|
||||
if sabnzbd.cfg.fixed_ports():
|
||||
Bail_Out(browserhost, cherryport)
|
||||
# Find free port to bind
|
||||
newport = find_free_port(browserhost, port)
|
||||
if newport > 0:
|
||||
# Save the new port
|
||||
if https_port:
|
||||
https_port = newport
|
||||
sabnzbd.cfg.https_port.set(newport)
|
||||
else:
|
||||
# In case HTTPS == HTTP port
|
||||
cherryport = newport
|
||||
sabnzbd.cfg.port.set(newport)
|
||||
except:
|
||||
# Something else wrong, probably badly specified host
|
||||
Bail_Out(browserhost, cherryport, '49')
|
||||
|
||||
# NonSSL check if there's no HTTPS or we only use 1 port
|
||||
if not (enable_https and not https_port):
|
||||
try:
|
||||
cherrypy.process.servers.check_port(browserhost, cherryport, timeout=0.025)
|
||||
cherrypy.process.servers.check_port(browserhost, cherryport, timeout=0.05)
|
||||
except IOError, error:
|
||||
if str(error) == 'Port not bound.':
|
||||
pass
|
||||
else:
|
||||
if not url:
|
||||
url = 'http://%s:%s/sabnzbd/api?' % (browserhost, cherryport)
|
||||
if not sabnzbd.cfg.fixed_ports():
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
port = find_free_port(browserhost, cherryport)
|
||||
if port > 0:
|
||||
sabnzbd.cfg.cherryport.set(port)
|
||||
notify_port_change = True
|
||||
cherryport = port
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
# Bail out if we have fixed our ports after first start-up
|
||||
if sabnzbd.cfg.fixed_ports():
|
||||
Bail_Out(browserhost, cherryport)
|
||||
# Find free port to bind
|
||||
port = find_free_port(browserhost, cherryport)
|
||||
if port > 0:
|
||||
sabnzbd.cfg.cherryport.set(port)
|
||||
cherryport = port
|
||||
except:
|
||||
# Something else wrong, probably badly specified host
|
||||
Bail_Out(browserhost, cherryport, '49')
|
||||
|
||||
# We found a port, now we never check again
|
||||
sabnzbd.cfg.fixed_ports.set(True)
|
||||
|
||||
if logging_level is None:
|
||||
logging_level = sabnzbd.cfg.log_level()
|
||||
else:
|
||||
@@ -1139,7 +1104,6 @@ def main():
|
||||
|
||||
logformat = '%(asctime)s::%(levelname)s::[%(module)s:%(lineno)d] %(message)s'
|
||||
rollover_log.setFormatter(logging.Formatter(logformat))
|
||||
rollover_log.addFilter(FilterCP3())
|
||||
sabnzbd.LOGHANDLER = rollover_log
|
||||
logger.addHandler(rollover_log)
|
||||
logger.setLevel(LOGLEVELS[logging_level + 1])
|
||||
@@ -1169,7 +1133,6 @@ def main():
|
||||
|
||||
if consoleLogging:
|
||||
console = logging.StreamHandler()
|
||||
console.addFilter(FilterCP3())
|
||||
console.setLevel(LOGLEVELS[logging_level + 1])
|
||||
console.setFormatter(logging.Formatter(logformat))
|
||||
logger.addHandler(console)
|
||||
@@ -1198,11 +1161,18 @@ def main():
|
||||
logging.info('Platform = %s', os.name)
|
||||
logging.info('Python-version = %s', sys.version)
|
||||
logging.info('Arguments = %s', sabnzbd.CMDLINE)
|
||||
|
||||
# Find encoding; relevant for unrar activities
|
||||
try:
|
||||
logging.info('Preferred encoding = %s', locale.getpreferredencoding())
|
||||
preferredencoding = locale.getpreferredencoding()
|
||||
logging.info('Preferred encoding = %s', preferredencoding)
|
||||
except:
|
||||
logging.info('Preferred encoding = ERROR')
|
||||
preferredencoding = ''
|
||||
|
||||
# On Linux/FreeBSD/Unix "UTF-8" is strongly, strongly adviced:
|
||||
if not sabnzbd.WIN32 and not sabnzbd.DARWIN and not ('utf' in preferredencoding.lower() and '8' in preferredencoding.lower()):
|
||||
logging.warning(T("SABnzbd was started with encoding %s, this should be UTF-8. Expect problems with Unicoded file and directory names in downloads.") % preferredencoding)
|
||||
|
||||
if sabnzbd.cfg.log_level() > 1:
|
||||
from sabnzbd.getipaddress import localipv4, publicipv4, ipv6
|
||||
@@ -1236,17 +1206,6 @@ def main():
|
||||
if cpumodel:
|
||||
logging.debug('CPU model name is %s', cpumodel)
|
||||
|
||||
# OSX 10.5 I/O priority setting
|
||||
if sabnzbd.DARWIN:
|
||||
logging.info('[osx] IO priority setting')
|
||||
try:
|
||||
from ctypes import cdll
|
||||
libc = cdll.LoadLibrary('/usr/lib/libc.dylib')
|
||||
boolSetResult = libc.setiopolicy_np(0, 1, 3)
|
||||
logging.info('[osx] IO priority set to throttle for process scope')
|
||||
except:
|
||||
logging.info('[osx] IO priority setting not supported')
|
||||
|
||||
logging.info('Read INI file %s', inifile)
|
||||
|
||||
if autobrowser is not None:
|
||||
@@ -1262,21 +1221,12 @@ def main():
|
||||
|
||||
os.chdir(sabnzbd.DIR_PROG)
|
||||
|
||||
web_dir = Web_Template(sabnzbd.cfg.web_dir, DEF_STDINTF, fix_webname(web_dir))
|
||||
web_dir2 = Web_Template(sabnzbd.cfg.web_dir2, '', fix_webname(web_dir2))
|
||||
web_dirc = Web_Template(None, DEF_STDCONFIG, '')
|
||||
sabnzbd.WEB_DIR = Web_Template(sabnzbd.cfg.web_dir, DEF_STDINTF, fix_webname(web_dir))
|
||||
sabnzbd.WEB_DIR_CONFIG = Web_Template(None, DEF_STDCONFIG, '')
|
||||
sabnzbd.WIZARD_DIR = os.path.join(sabnzbd.DIR_INTERFACES, 'wizard')
|
||||
|
||||
wizard_dir = os.path.join(sabnzbd.DIR_INTERFACES, 'wizard')
|
||||
|
||||
sabnzbd.WEB_DIR = web_dir
|
||||
sabnzbd.WEB_DIR2 = web_dir2
|
||||
sabnzbd.WEB_DIRC = web_dirc
|
||||
sabnzbd.WIZARD_DIR = wizard_dir
|
||||
|
||||
sabnzbd.WEB_COLOR = CheckColor(sabnzbd.cfg.web_color(), web_dir)
|
||||
sabnzbd.WEB_COLOR = CheckColor(sabnzbd.cfg.web_color(), sabnzbd.WEB_DIR)
|
||||
sabnzbd.cfg.web_color.set(sabnzbd.WEB_COLOR)
|
||||
sabnzbd.WEB_COLOR2 = CheckColor(sabnzbd.cfg.web_color2(), web_dir2)
|
||||
sabnzbd.cfg.web_color2.set(sabnzbd.WEB_COLOR2)
|
||||
|
||||
if fork and not sabnzbd.WIN32:
|
||||
daemonize()
|
||||
@@ -1304,23 +1254,6 @@ def main():
|
||||
logging.info("SSL version %s", sabnzbd.utils.sslinfo.ssl_version())
|
||||
logging.info("SSL supported protocols %s", str(sabnzbd.utils.sslinfo.ssl_protocols_labels()))
|
||||
|
||||
cherrylogtoscreen = False
|
||||
sabnzbd.WEBLOGFILE = None
|
||||
|
||||
if cherrypylogging:
|
||||
if logdir:
|
||||
sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_CHERRY)
|
||||
# Define our custom logger for cherrypy errors
|
||||
cherrypy_logging(sabnzbd.WEBLOGFILE, logging.handlers.RotatingFileHandler)
|
||||
if not fork:
|
||||
try:
|
||||
x = sys.stderr.fileno
|
||||
x = sys.stdout.fileno
|
||||
if cherrypylogging == 1:
|
||||
cherrylogtoscreen = True
|
||||
except:
|
||||
pass
|
||||
|
||||
https_cert = sabnzbd.cfg.https_cert.get_path()
|
||||
https_key = sabnzbd.cfg.https_key.get_path()
|
||||
https_chain = sabnzbd.cfg.https_chain.get_path()
|
||||
@@ -1360,7 +1293,7 @@ def main():
|
||||
cherryport = https_port
|
||||
elif multilocal:
|
||||
# Extra HTTPS port for secondary localhost
|
||||
attach_server(hosts[1], cherryport, https_cert, https_key)
|
||||
attach_server(hosts[1], cherryport, https_cert, https_key, https_chain)
|
||||
|
||||
cherrypy.config.update({'server.ssl_module': 'builtin',
|
||||
'server.ssl_certificate': https_cert,
|
||||
@@ -1387,60 +1320,48 @@ def main():
|
||||
'server.socket_host': cherryhost,
|
||||
'server.socket_port': cherryport,
|
||||
'server.shutdown_timeout': 0,
|
||||
'log.screen': cherrylogtoscreen,
|
||||
'log.screen': False,
|
||||
'engine.autoreload.on': False,
|
||||
'tools.encode.on': True,
|
||||
'tools.gzip.on': True,
|
||||
'tools.gzip.mime_types': mime_gzip,
|
||||
'request.show_tracebacks': True,
|
||||
'checker.check_localhost': bool(consoleLogging),
|
||||
'error_page.401': sabnzbd.panic.error_page_401,
|
||||
'error_page.404': sabnzbd.panic.error_page_404
|
||||
})
|
||||
|
||||
forced_mime_types = {'css': 'text/css', 'js': 'application/javascript'}
|
||||
static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(web_dir, 'static'), 'tools.staticdir.content_types': forced_mime_types}
|
||||
staticcfg = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(web_dirc, 'staticcfg'), 'tools.staticdir.content_types': forced_mime_types}
|
||||
wizard_static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(wizard_dir, 'static'), 'tools.staticdir.content_types': forced_mime_types}
|
||||
# Do we want CherryPy Logging? Cannot be done via the config
|
||||
if cherrypylogging:
|
||||
sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_CHERRY)
|
||||
cherrypy.log.screen = True
|
||||
cherrypy.log.access_log.propagate = True
|
||||
cherrypy.log.access_file = str(sabnzbd.WEBLOGFILE)
|
||||
else:
|
||||
cherrypy.log.access_log.propagate = False
|
||||
|
||||
appconfig = {'/sabnzbd/api': {'tools.basic_auth.on': False},
|
||||
'/api': {'tools.basic_auth.on': False},
|
||||
'/m/api': {'tools.basic_auth.on': False},
|
||||
# Force mimetypes (OS might overwrite them)
|
||||
forced_mime_types = {'css': 'text/css', 'js': 'application/javascript'}
|
||||
|
||||
static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(sabnzbd.WEB_DIR, 'static'), 'tools.staticdir.content_types': forced_mime_types}
|
||||
staticcfg = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(sabnzbd.WEB_DIR_CONFIG, 'staticcfg'), 'tools.staticdir.content_types': forced_mime_types}
|
||||
wizard_static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(sabnzbd.WIZARD_DIR, 'static'), 'tools.staticdir.content_types': forced_mime_types}
|
||||
|
||||
appconfig = {'/api': {'tools.basic_auth.on': False},
|
||||
'/rss': {'tools.basic_auth.on': False},
|
||||
'/sabnzbd/rss': {'tools.basic_auth.on': False},
|
||||
'/m/rss': {'tools.basic_auth.on': False},
|
||||
'/sabnzbd/shutdown': {'streamResponse': True},
|
||||
'/sabnzbd/static': static,
|
||||
'/static': static,
|
||||
'/sabnzbd/wizard/static': wizard_static,
|
||||
'/wizard/static': wizard_static,
|
||||
'/favicon.ico': {'tools.staticfile.on': True, 'tools.staticfile.filename': os.path.join(web_dirc, 'staticcfg', 'ico', 'favicon.ico')},
|
||||
'/sabnzbd/staticcfg': staticcfg,
|
||||
'/favicon.ico': {'tools.staticfile.on': True, 'tools.staticfile.filename': os.path.join(sabnzbd.WEB_DIR_CONFIG, 'staticcfg', 'ico', 'favicon.ico')},
|
||||
'/staticcfg': staticcfg
|
||||
}
|
||||
|
||||
if web_dir2:
|
||||
static2 = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(web_dir2, 'static'), 'tools.staticdir.content_types': forced_mime_types}
|
||||
appconfig['/sabnzbd/m/api'] = {'tools.basic_auth.on': False}
|
||||
appconfig['/sabnzbd/m/rss'] = {'tools.basic_auth.on': False}
|
||||
appconfig['/sabnzbd/m/shutdown'] = {'streamResponse': True}
|
||||
appconfig['/sabnzbd/m/static'] = static2
|
||||
appconfig['/m/static'] = static2
|
||||
appconfig['/sabnzbd/m/wizard/static'] = wizard_static
|
||||
appconfig['/m/wizard/static'] = wizard_static
|
||||
appconfig['/sabnzbd/m/staticcfg'] = staticcfg
|
||||
appconfig['/m/staticcfg'] = staticcfg
|
||||
|
||||
login_page = sabnzbd.interface.MainPage(web_dir, '/', web_dir2, '/m/', web_dirc, first=2)
|
||||
cherrypy.tree.mount(login_page, '/', config=appconfig)
|
||||
# Make available from both URLs
|
||||
main_page = sabnzbd.interface.MainPage()
|
||||
cherrypy.tree.mount(main_page, '/', config=appconfig)
|
||||
cherrypy.tree.mount(main_page, '/sabnzbd/', config=appconfig)
|
||||
|
||||
# Set authentication for CherryPy
|
||||
sabnzbd.interface.set_auth(cherrypy.config)
|
||||
|
||||
# Notify if port was changed
|
||||
if notify_port_change:
|
||||
logging.warning(T('Could not bind to configured port. Port changed to %s') % cherryport)
|
||||
|
||||
logging.info('Starting web-interface on %s:%s', cherryhost, cherryport)
|
||||
|
||||
sabnzbd.cfg.log_level.callback(guard_loglevel)
|
||||
|
||||
@@ -214,18 +214,7 @@ class HTTPRedirect(CherryPyException):
|
||||
if isinstance(urls, text_or_bytes):
|
||||
urls = [urls]
|
||||
|
||||
abs_urls = []
|
||||
for url in urls:
|
||||
url = tonative(url, encoding or self.encoding)
|
||||
|
||||
# Note that urljoin will "do the right thing" whether url is:
|
||||
# 1. a complete URL with host (e.g. "http://www.example.com/test")
|
||||
# 2. a URL relative to root (e.g. "/dummy")
|
||||
# 3. a URL relative to the current path
|
||||
# Note that any query string in cherrypy.request is discarded.
|
||||
url = _urljoin(cherrypy.url(), url)
|
||||
abs_urls.append(url)
|
||||
self.urls = abs_urls
|
||||
self.urls = [tonative(url, encoding or self.encoding) for url in urls]
|
||||
|
||||
# RFC 2616 indicates a 301 response code fits our goal; however,
|
||||
# browser support for 301 is quite messy. Do 302/303 instead. See
|
||||
@@ -241,7 +230,7 @@ class HTTPRedirect(CherryPyException):
|
||||
raise ValueError('status must be between 300 and 399.')
|
||||
|
||||
self.status = status
|
||||
CherryPyException.__init__(self, abs_urls, status)
|
||||
CherryPyException.__init__(self, self.urls, status)
|
||||
|
||||
def set_response(self):
|
||||
"""Modify cherrypy.response status, headers, and body to represent
|
||||
|
||||
@@ -196,12 +196,13 @@ socket_errors_to_ignore = plat_specific_errors(
|
||||
)
|
||||
socket_errors_to_ignore.append('timed out')
|
||||
socket_errors_to_ignore.append('The read operation timed out')
|
||||
if sys.platform == 'darwin':
|
||||
socket_errors_to_ignore.append(plat_specific_errors('EPROTOTYPE'))
|
||||
|
||||
socket_errors_nonblocking = plat_specific_errors(
|
||||
'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
socket_errors_to_ignore.append(plat_specific_errors('EPROTOTYPE'))
|
||||
socket_errors_nonblocking.append(plat_specific_errors('EPROTOTYPE'))
|
||||
|
||||
comma_separated_headers = [
|
||||
ntob(h) for h in
|
||||
['Accept', 'Accept-Charset', 'Accept-Encoding',
|
||||
|
||||
@@ -85,8 +85,8 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
||||
|
||||
# Check if it's one of the known errors
|
||||
# Errors that are caught by PyOpenSSL, but thrown by built-in ssl
|
||||
_block_errors = ('unknown protocol', 'unknown ca', 'unknown_ca',
|
||||
'inappropriate fallback', 'wrong version number',
|
||||
_block_errors = ('unknown protocol', 'unknown ca', 'unknown_ca', 'unknown error',
|
||||
'https proxy request', 'inappropriate fallback', 'wrong version number',
|
||||
'no shared cipher', 'certificate unknown', 'ccs received early')
|
||||
for error_text in _block_errors:
|
||||
if error_text in e.args[1].lower():
|
||||
|
||||
509
gntp/__init__.py
@@ -1,509 +0,0 @@
|
||||
import re
|
||||
import hashlib
|
||||
import time
|
||||
import StringIO
|
||||
|
||||
__version__ = '0.8'
|
||||
|
||||
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
||||
GNTP_INFO_LINE = re.compile(
|
||||
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' +
|
||||
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?' +
|
||||
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n',
|
||||
re.IGNORECASE
|
||||
)
|
||||
|
||||
GNTP_INFO_LINE_SHORT = re.compile(
|
||||
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',
|
||||
re.IGNORECASE
|
||||
)
|
||||
|
||||
GNTP_HEADER = re.compile('([\w-]+):(.+)')
|
||||
|
||||
GNTP_EOL = '\r\n'
|
||||
|
||||
|
||||
class BaseError(Exception):
|
||||
def gntp_error(self):
|
||||
error = GNTPError(self.errorcode, self.errordesc)
|
||||
return error.encode()
|
||||
|
||||
|
||||
class ParseError(BaseError):
|
||||
errorcode = 500
|
||||
errordesc = 'Error parsing the message'
|
||||
|
||||
|
||||
class AuthError(BaseError):
|
||||
errorcode = 400
|
||||
errordesc = 'Error with authorization'
|
||||
|
||||
|
||||
class UnsupportedError(BaseError):
|
||||
errorcode = 500
|
||||
errordesc = 'Currently unsupported by gntp.py'
|
||||
|
||||
|
||||
class _GNTPBuffer(StringIO.StringIO):
|
||||
"""GNTP Buffer class"""
|
||||
def writefmt(self, message="", *args):
|
||||
"""Shortcut function for writing GNTP Headers"""
|
||||
self.write((message % args).encode('utf8', 'replace'))
|
||||
self.write(GNTP_EOL)
|
||||
|
||||
|
||||
class _GNTPBase(object):
|
||||
"""Base initilization
|
||||
|
||||
:param string messagetype: GNTP Message type
|
||||
:param string version: GNTP Protocol version
|
||||
:param string encription: Encryption protocol
|
||||
"""
|
||||
def __init__(self, messagetype=None, version='1.0', encryption=None):
|
||||
self.info = {
|
||||
'version': version,
|
||||
'messagetype': messagetype,
|
||||
'encryptionAlgorithmID': encryption
|
||||
}
|
||||
self.headers = {}
|
||||
self.resources = {}
|
||||
|
||||
def __str__(self):
|
||||
return self.encode()
|
||||
|
||||
def _parse_info(self, data):
|
||||
"""Parse the first line of a GNTP message to get security and other info values
|
||||
|
||||
:param string data: GNTP Message
|
||||
:return dict: Parsed GNTP Info line
|
||||
"""
|
||||
|
||||
match = GNTP_INFO_LINE.match(data)
|
||||
|
||||
if not match:
|
||||
raise ParseError('ERROR_PARSING_INFO_LINE')
|
||||
|
||||
info = match.groupdict()
|
||||
if info['encryptionAlgorithmID'] == 'NONE':
|
||||
info['encryptionAlgorithmID'] = None
|
||||
|
||||
return info
|
||||
|
||||
def set_password(self, password, encryptAlgo='MD5'):
|
||||
"""Set a password for a GNTP Message
|
||||
|
||||
:param string password: Null to clear password
|
||||
:param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512
|
||||
"""
|
||||
hash = {
|
||||
'MD5': hashlib.md5,
|
||||
'SHA1': hashlib.sha1,
|
||||
'SHA256': hashlib.sha256,
|
||||
'SHA512': hashlib.sha512,
|
||||
}
|
||||
|
||||
self.password = password
|
||||
self.encryptAlgo = encryptAlgo.upper()
|
||||
if not password:
|
||||
self.info['encryptionAlgorithmID'] = None
|
||||
self.info['keyHashAlgorithm'] = None
|
||||
return
|
||||
if not self.encryptAlgo in hash.keys():
|
||||
raise UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo)
|
||||
|
||||
hashfunction = hash.get(self.encryptAlgo)
|
||||
|
||||
password = password.encode('utf8')
|
||||
seed = time.ctime()
|
||||
salt = hashfunction(seed).hexdigest()
|
||||
saltHash = hashfunction(seed).digest()
|
||||
keyBasis = password + saltHash
|
||||
key = hashfunction(keyBasis).digest()
|
||||
keyHash = hashfunction(key).hexdigest()
|
||||
|
||||
self.info['keyHashAlgorithmID'] = self.encryptAlgo
|
||||
self.info['keyHash'] = keyHash.upper()
|
||||
self.info['salt'] = salt.upper()
|
||||
|
||||
def _decode_hex(self, value):
|
||||
"""Helper function to decode hex string to `proper` hex string
|
||||
|
||||
:param string value: Human readable hex string
|
||||
:return string: Hex string
|
||||
"""
|
||||
result = ''
|
||||
for i in range(0, len(value), 2):
|
||||
tmp = int(value[i:i + 2], 16)
|
||||
result += chr(tmp)
|
||||
return result
|
||||
|
||||
def _decode_binary(self, rawIdentifier, identifier):
|
||||
rawIdentifier += '\r\n\r\n'
|
||||
dataLength = int(identifier['Length'])
|
||||
pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier)
|
||||
pointerEnd = pointerStart + dataLength
|
||||
data = self.raw[pointerStart:pointerEnd]
|
||||
if not len(data) == dataLength:
|
||||
raise ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data)))
|
||||
return data
|
||||
|
||||
def _validate_password(self, password):
|
||||
"""Validate GNTP Message against stored password"""
|
||||
self.password = password
|
||||
if password == None:
|
||||
raise AuthError('Missing password')
|
||||
keyHash = self.info.get('keyHash', None)
|
||||
if keyHash is None and self.password is None:
|
||||
return True
|
||||
if keyHash is None:
|
||||
raise AuthError('Invalid keyHash')
|
||||
if self.password is None:
|
||||
raise AuthError('Missing password')
|
||||
|
||||
password = self.password.encode('utf8')
|
||||
saltHash = self._decode_hex(self.info['salt'])
|
||||
|
||||
keyBasis = password + saltHash
|
||||
key = hashlib.md5(keyBasis).digest()
|
||||
keyHash = hashlib.md5(key).hexdigest()
|
||||
|
||||
if not keyHash.upper() == self.info['keyHash'].upper():
|
||||
raise AuthError('Invalid Hash')
|
||||
return True
|
||||
|
||||
def validate(self):
|
||||
"""Verify required headers"""
|
||||
for header in self._requiredHeaders:
|
||||
if not self.headers.get(header, False):
|
||||
raise ParseError('Missing Notification Header: ' + header)
|
||||
|
||||
def _format_info(self):
|
||||
"""Generate info line for GNTP Message
|
||||
|
||||
:return string:
|
||||
"""
|
||||
info = u'GNTP/%s %s' % (
|
||||
self.info.get('version'),
|
||||
self.info.get('messagetype'),
|
||||
)
|
||||
if self.info.get('encryptionAlgorithmID', None):
|
||||
info += ' %s:%s' % (
|
||||
self.info.get('encryptionAlgorithmID'),
|
||||
self.info.get('ivValue'),
|
||||
)
|
||||
else:
|
||||
info += ' NONE'
|
||||
|
||||
if self.info.get('keyHashAlgorithmID', None):
|
||||
info += ' %s:%s.%s' % (
|
||||
self.info.get('keyHashAlgorithmID'),
|
||||
self.info.get('keyHash'),
|
||||
self.info.get('salt')
|
||||
)
|
||||
|
||||
return info
|
||||
|
||||
def _parse_dict(self, data):
|
||||
"""Helper function to parse blocks of GNTP headers into a dictionary
|
||||
|
||||
:param string data:
|
||||
:return dict:
|
||||
"""
|
||||
dict = {}
|
||||
for line in data.split('\r\n'):
|
||||
match = GNTP_HEADER.match(line)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
key = unicode(match.group(1).strip(), 'utf8', 'replace')
|
||||
val = unicode(match.group(2).strip(), 'utf8', 'replace')
|
||||
dict[key] = val
|
||||
return dict
|
||||
|
||||
def add_header(self, key, value):
|
||||
if isinstance(value, unicode):
|
||||
self.headers[key] = value
|
||||
else:
|
||||
self.headers[key] = unicode('%s' % value, 'utf8', 'replace')
|
||||
|
||||
def add_resource(self, data):
|
||||
"""Add binary resource
|
||||
|
||||
:param string data: Binary Data
|
||||
"""
|
||||
identifier = hashlib.md5(data).hexdigest()
|
||||
self.resources[identifier] = data
|
||||
return 'x-growl-resource://%s' % identifier
|
||||
|
||||
def decode(self, data, password=None):
|
||||
"""Decode GNTP Message
|
||||
|
||||
:param string data:
|
||||
"""
|
||||
self.password = password
|
||||
self.raw = data
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(data)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
def encode(self):
|
||||
"""Encode a generic GNTP Message
|
||||
|
||||
:return string: GNTP Message ready to be sent
|
||||
"""
|
||||
|
||||
buffer = _GNTPBuffer()
|
||||
|
||||
buffer.writefmt(self._format_info())
|
||||
|
||||
#Headers
|
||||
for k, v in self.headers.iteritems():
|
||||
buffer.writefmt('%s: %s', k, v)
|
||||
buffer.writefmt()
|
||||
|
||||
#Resources
|
||||
for resource, data in self.resources.iteritems():
|
||||
buffer.writefmt('Identifier: %s', resource)
|
||||
buffer.writefmt('Length: %d', len(data))
|
||||
buffer.writefmt()
|
||||
buffer.write(data)
|
||||
buffer.writefmt()
|
||||
buffer.writefmt()
|
||||
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
class GNTPRegister(_GNTPBase):
|
||||
"""Represents a GNTP Registration Command
|
||||
|
||||
:param string data: (Optional) See decode()
|
||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||
"""
|
||||
_requiredHeaders = [
|
||||
'Application-Name',
|
||||
'Notifications-Count'
|
||||
]
|
||||
_requiredNotificationHeaders = ['Notification-Name']
|
||||
|
||||
def __init__(self, data=None, password=None):
|
||||
_GNTPBase.__init__(self, 'REGISTER')
|
||||
self.notifications = []
|
||||
|
||||
if data:
|
||||
self.decode(data, password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
self.add_header('Application-Name', 'pygntp')
|
||||
self.add_header('Notifications-Count', 0)
|
||||
|
||||
def validate(self):
|
||||
'''Validate required headers and validate notification headers'''
|
||||
for header in self._requiredHeaders:
|
||||
if not self.headers.get(header, False):
|
||||
raise ParseError('Missing Registration Header: ' + header)
|
||||
for notice in self.notifications:
|
||||
for header in self._requiredNotificationHeaders:
|
||||
if not notice.get(header, False):
|
||||
raise ParseError('Missing Notification Header: ' + header)
|
||||
|
||||
def decode(self, data, password):
|
||||
"""Decode existing GNTP Registration message
|
||||
|
||||
:param string data: Message to decode
|
||||
"""
|
||||
self.raw = data
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(data)
|
||||
self._validate_password(password)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
if i == 0:
|
||||
continue # Skip Header
|
||||
if part.strip() == '':
|
||||
continue
|
||||
notice = self._parse_dict(part)
|
||||
if notice.get('Notification-Name', False):
|
||||
self.notifications.append(notice)
|
||||
elif notice.get('Identifier', False):
|
||||
notice['Data'] = self._decode_binary(part, notice)
|
||||
#open('register.png','wblol').write(notice['Data'])
|
||||
self.resources[notice.get('Identifier')] = notice
|
||||
|
||||
def add_notification(self, name, enabled=True):
|
||||
"""Add new Notification to Registration message
|
||||
|
||||
:param string name: Notification Name
|
||||
:param boolean enabled: Enable this notification by default
|
||||
"""
|
||||
notice = {}
|
||||
notice['Notification-Name'] = u'%s' % name
|
||||
notice['Notification-Enabled'] = u'%s' % enabled
|
||||
|
||||
self.notifications.append(notice)
|
||||
self.add_header('Notifications-Count', len(self.notifications))
|
||||
|
||||
def encode(self):
|
||||
"""Encode a GNTP Registration Message
|
||||
|
||||
:return string: Encoded GNTP Registration message
|
||||
"""
|
||||
|
||||
buffer = _GNTPBuffer()
|
||||
|
||||
buffer.writefmt(self._format_info())
|
||||
|
||||
#Headers
|
||||
for k, v in self.headers.iteritems():
|
||||
buffer.writefmt('%s: %s', k, v)
|
||||
buffer.writefmt()
|
||||
|
||||
#Notifications
|
||||
if len(self.notifications) > 0:
|
||||
for notice in self.notifications:
|
||||
for k, v in notice.iteritems():
|
||||
buffer.writefmt('%s: %s', k, v)
|
||||
buffer.writefmt()
|
||||
|
||||
#Resources
|
||||
for resource, data in self.resources.iteritems():
|
||||
buffer.writefmt('Identifier: %s', resource)
|
||||
buffer.writefmt('Length: %d', len(data))
|
||||
buffer.writefmt()
|
||||
buffer.write(data)
|
||||
buffer.writefmt()
|
||||
buffer.writefmt()
|
||||
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
class GNTPNotice(_GNTPBase):
|
||||
"""Represents a GNTP Notification Command
|
||||
|
||||
:param string data: (Optional) See decode()
|
||||
:param string app: (Optional) Set Application-Name
|
||||
:param string name: (Optional) Set Notification-Name
|
||||
:param string title: (Optional) Set Notification Title
|
||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||
"""
|
||||
_requiredHeaders = [
|
||||
'Application-Name',
|
||||
'Notification-Name',
|
||||
'Notification-Title'
|
||||
]
|
||||
|
||||
def __init__(self, data=None, app=None, name=None, title=None, password=None):
|
||||
_GNTPBase.__init__(self, 'NOTIFY')
|
||||
|
||||
if data:
|
||||
self.decode(data, password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
if app:
|
||||
self.add_header('Application-Name', app)
|
||||
if name:
|
||||
self.add_header('Notification-Name', name)
|
||||
if title:
|
||||
self.add_header('Notification-Title', title)
|
||||
|
||||
def decode(self, data, password):
|
||||
"""Decode existing GNTP Notification message
|
||||
|
||||
:param string data: Message to decode.
|
||||
"""
|
||||
self.raw = data
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(data)
|
||||
self._validate_password(password)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
if i == 0:
|
||||
continue # Skip Header
|
||||
if part.strip() == '':
|
||||
continue
|
||||
notice = self._parse_dict(part)
|
||||
if notice.get('Identifier', False):
|
||||
notice['Data'] = self._decode_binary(part, notice)
|
||||
#open('notice.png','wblol').write(notice['Data'])
|
||||
self.resources[notice.get('Identifier')] = notice
|
||||
|
||||
|
||||
class GNTPSubscribe(_GNTPBase):
|
||||
"""Represents a GNTP Subscribe Command
|
||||
|
||||
:param string data: (Optional) See decode()
|
||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||
"""
|
||||
_requiredHeaders = [
|
||||
'Subscriber-ID',
|
||||
'Subscriber-Name',
|
||||
]
|
||||
|
||||
def __init__(self, data=None, password=None):
|
||||
_GNTPBase.__init__(self, 'SUBSCRIBE')
|
||||
if data:
|
||||
self.decode(data, password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
|
||||
|
||||
class GNTPOK(_GNTPBase):
|
||||
"""Represents a GNTP OK Response
|
||||
|
||||
:param string data: (Optional) See _GNTPResponse.decode()
|
||||
:param string action: (Optional) Set type of action the OK Response is for
|
||||
"""
|
||||
_requiredHeaders = ['Response-Action']
|
||||
|
||||
def __init__(self, data=None, action=None):
|
||||
_GNTPBase.__init__(self, '-OK')
|
||||
if data:
|
||||
self.decode(data)
|
||||
if action:
|
||||
self.add_header('Response-Action', action)
|
||||
|
||||
|
||||
class GNTPError(_GNTPBase):
|
||||
"""Represents a GNTP Error response
|
||||
|
||||
:param string data: (Optional) See _GNTPResponse.decode()
|
||||
:param string errorcode: (Optional) Error code
|
||||
:param string errordesc: (Optional) Error Description
|
||||
"""
|
||||
_requiredHeaders = ['Error-Code', 'Error-Description']
|
||||
|
||||
def __init__(self, data=None, errorcode=None, errordesc=None):
|
||||
_GNTPBase.__init__(self, '-ERROR')
|
||||
if data:
|
||||
self.decode(data)
|
||||
if errorcode:
|
||||
self.add_header('Error-Code', errorcode)
|
||||
self.add_header('Error-Description', errordesc)
|
||||
|
||||
def error(self):
|
||||
return (self.headers.get('Error-Code', None),
|
||||
self.headers.get('Error-Description', None))
|
||||
|
||||
|
||||
def parse_gntp(data, password=None):
|
||||
"""Attempt to parse a message as a GNTP message
|
||||
|
||||
:param string data: Message to be parsed
|
||||
:param string password: Optional password to be used to verify the message
|
||||
"""
|
||||
match = GNTP_INFO_LINE_SHORT.match(data)
|
||||
if not match:
|
||||
raise ParseError('INVALID_GNTP_INFO')
|
||||
info = match.groupdict()
|
||||
if info['messagetype'] == 'REGISTER':
|
||||
return GNTPRegister(data, password=password)
|
||||
elif info['messagetype'] == 'NOTIFY':
|
||||
return GNTPNotice(data, password=password)
|
||||
elif info['messagetype'] == 'SUBSCRIBE':
|
||||
return GNTPSubscribe(data, password=password)
|
||||
elif info['messagetype'] == '-OK':
|
||||
return GNTPOK(data)
|
||||
elif info['messagetype'] == '-ERROR':
|
||||
return GNTPError(data)
|
||||
raise ParseError('INVALID_GNTP_MESSAGE')
|
||||
|
||||
141
gntp/cli.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# Copyright: 2013 Paul Traylor
|
||||
# These sources are released under the terms of the MIT license: see LICENSE
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from optparse import OptionParser, OptionGroup
|
||||
|
||||
from gntp.notifier import GrowlNotifier
|
||||
from gntp.shim import RawConfigParser
|
||||
from gntp.version import __version__
|
||||
|
||||
DEFAULT_CONFIG = os.path.expanduser('~/.gntp')
|
||||
|
||||
config = RawConfigParser({
|
||||
'hostname': 'localhost',
|
||||
'password': None,
|
||||
'port': 23053,
|
||||
})
|
||||
config.read([DEFAULT_CONFIG])
|
||||
if not config.has_section('gntp'):
|
||||
config.add_section('gntp')
|
||||
|
||||
|
||||
class ClientParser(OptionParser):
|
||||
def __init__(self):
|
||||
OptionParser.__init__(self, version="%%prog %s" % __version__)
|
||||
|
||||
group = OptionGroup(self, "Network Options")
|
||||
group.add_option("-H", "--host",
|
||||
dest="host", default=config.get('gntp', 'hostname'),
|
||||
help="Specify a hostname to which to send a remote notification. [%default]")
|
||||
group.add_option("--port",
|
||||
dest="port", default=config.getint('gntp', 'port'), type="int",
|
||||
help="port to listen on [%default]")
|
||||
group.add_option("-P", "--password",
|
||||
dest='password', default=config.get('gntp', 'password'),
|
||||
help="Network password")
|
||||
self.add_option_group(group)
|
||||
|
||||
group = OptionGroup(self, "Notification Options")
|
||||
group.add_option("-n", "--name",
|
||||
dest="app", default='Python GNTP Test Client',
|
||||
help="Set the name of the application [%default]")
|
||||
group.add_option("-s", "--sticky",
|
||||
dest='sticky', default=False, action="store_true",
|
||||
help="Make the notification sticky [%default]")
|
||||
group.add_option("--image",
|
||||
dest="icon", default=None,
|
||||
help="Icon for notification (URL or /path/to/file)")
|
||||
group.add_option("-m", "--message",
|
||||
dest="message", default=None,
|
||||
help="Sets the message instead of using stdin")
|
||||
group.add_option("-p", "--priority",
|
||||
dest="priority", default=0, type="int",
|
||||
help="-2 to 2 [%default]")
|
||||
group.add_option("-d", "--identifier",
|
||||
dest="identifier",
|
||||
help="Identifier for coalescing")
|
||||
group.add_option("-t", "--title",
|
||||
dest="title", default=None,
|
||||
help="Set the title of the notification [%default]")
|
||||
group.add_option("-N", "--notification",
|
||||
dest="name", default='Notification',
|
||||
help="Set the notification name [%default]")
|
||||
group.add_option("--callback",
|
||||
dest="callback",
|
||||
help="URL callback")
|
||||
self.add_option_group(group)
|
||||
|
||||
# Extra Options
|
||||
self.add_option('-v', '--verbose',
|
||||
dest='verbose', default=0, action='count',
|
||||
help="Verbosity levels")
|
||||
|
||||
def parse_args(self, args=None, values=None):
|
||||
values, args = OptionParser.parse_args(self, args, values)
|
||||
|
||||
if values.message is None:
|
||||
print('Enter a message followed by Ctrl-D')
|
||||
try:
|
||||
message = sys.stdin.read()
|
||||
except KeyboardInterrupt:
|
||||
exit()
|
||||
else:
|
||||
message = values.message
|
||||
|
||||
if values.title is None:
|
||||
values.title = ' '.join(args)
|
||||
|
||||
# If we still have an empty title, use the
|
||||
# first bit of the message as the title
|
||||
if values.title == '':
|
||||
values.title = message[:20]
|
||||
|
||||
values.verbose = logging.WARNING - values.verbose * 10
|
||||
|
||||
return values, message
|
||||
|
||||
|
||||
def main():
|
||||
(options, message) = ClientParser().parse_args()
|
||||
logging.basicConfig(level=options.verbose)
|
||||
if not os.path.exists(DEFAULT_CONFIG):
|
||||
logging.info('No config read found at %s', DEFAULT_CONFIG)
|
||||
|
||||
growl = GrowlNotifier(
|
||||
applicationName=options.app,
|
||||
notifications=[options.name],
|
||||
defaultNotifications=[options.name],
|
||||
hostname=options.host,
|
||||
password=options.password,
|
||||
port=options.port,
|
||||
)
|
||||
result = growl.register()
|
||||
if result is not True:
|
||||
exit(result)
|
||||
|
||||
# This would likely be better placed within the growl notifier
|
||||
# class but until I make _checkIcon smarter this is "easier"
|
||||
if options.icon and growl._checkIcon(options.icon) is False:
|
||||
logging.info('Loading image %s', options.icon)
|
||||
f = open(options.icon, 'rb')
|
||||
options.icon = f.read()
|
||||
f.close()
|
||||
|
||||
result = growl.notify(
|
||||
noteType=options.name,
|
||||
title=options.title,
|
||||
description=message,
|
||||
icon=options.icon,
|
||||
sticky=options.sticky,
|
||||
priority=options.priority,
|
||||
callback=options.callback,
|
||||
identifier=options.identifier,
|
||||
)
|
||||
if result is not True:
|
||||
exit(result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
77
gntp/config.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# Copyright: 2013 Paul Traylor
|
||||
# These sources are released under the terms of the MIT license: see LICENSE
|
||||
|
||||
"""
|
||||
The gntp.config module is provided as an extended GrowlNotifier object that takes
|
||||
advantage of the ConfigParser module to allow us to setup some default values
|
||||
(such as hostname, password, and port) in a more global way to be shared among
|
||||
programs using gntp
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
import gntp.notifier
|
||||
import gntp.shim
|
||||
|
||||
__all__ = [
|
||||
'mini',
|
||||
'GrowlNotifier'
|
||||
]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GrowlNotifier(gntp.notifier.GrowlNotifier):
|
||||
"""
|
||||
ConfigParser enhanced GrowlNotifier object
|
||||
|
||||
For right now, we are only interested in letting users overide certain
|
||||
values from ~/.gntp
|
||||
|
||||
::
|
||||
|
||||
[gntp]
|
||||
hostname = ?
|
||||
password = ?
|
||||
port = ?
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
config = gntp.shim.RawConfigParser({
|
||||
'hostname': kwargs.get('hostname', 'localhost'),
|
||||
'password': kwargs.get('password'),
|
||||
'port': kwargs.get('port', 23053),
|
||||
})
|
||||
|
||||
config.read([os.path.expanduser('~/.gntp')])
|
||||
|
||||
# If the file does not exist, then there will be no gntp section defined
|
||||
# and the config.get() lines below will get confused. Since we are not
|
||||
# saving the config, it should be safe to just add it here so the
|
||||
# code below doesn't complain
|
||||
if not config.has_section('gntp'):
|
||||
logger.info('Error reading ~/.gntp config file')
|
||||
config.add_section('gntp')
|
||||
|
||||
kwargs['password'] = config.get('gntp', 'password')
|
||||
kwargs['hostname'] = config.get('gntp', 'hostname')
|
||||
kwargs['port'] = config.getint('gntp', 'port')
|
||||
|
||||
super(GrowlNotifier, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def mini(description, **kwargs):
|
||||
"""Single notification function
|
||||
|
||||
Simple notification function in one line. Has only one required parameter
|
||||
and attempts to use reasonable defaults for everything else
|
||||
:param string description: Notification message
|
||||
"""
|
||||
kwargs['notifierFactory'] = GrowlNotifier
|
||||
gntp.notifier.mini(description, **kwargs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# If we're running this module directly we're likely running it as a test
|
||||
# so extra debugging is useful
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
mini('Testing mini notification')
|
||||
518
gntp/core.py
Normal file
@@ -0,0 +1,518 @@
|
||||
# Copyright: 2013 Paul Traylor
|
||||
# These sources are released under the terms of the MIT license: see LICENSE
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
import time
|
||||
|
||||
import gntp.shim
|
||||
import gntp.errors as errors
|
||||
|
||||
__all__ = [
|
||||
'GNTPRegister',
|
||||
'GNTPNotice',
|
||||
'GNTPSubscribe',
|
||||
'GNTPOK',
|
||||
'GNTPError',
|
||||
'parse_gntp',
|
||||
]
|
||||
|
||||
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
||||
GNTP_INFO_LINE = re.compile(
|
||||
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' +
|
||||
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?' +
|
||||
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n',
|
||||
re.IGNORECASE
|
||||
)
|
||||
|
||||
GNTP_INFO_LINE_SHORT = re.compile(
|
||||
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',
|
||||
re.IGNORECASE
|
||||
)
|
||||
|
||||
GNTP_HEADER = re.compile('([\w-]+):(.+)')
|
||||
|
||||
GNTP_EOL = gntp.shim.b('\r\n')
|
||||
GNTP_SEP = gntp.shim.b(': ')
|
||||
|
||||
|
||||
class _GNTPBuffer(gntp.shim.StringIO):
|
||||
"""GNTP Buffer class"""
|
||||
def writeln(self, value=None):
|
||||
if value:
|
||||
self.write(gntp.shim.b(value))
|
||||
self.write(GNTP_EOL)
|
||||
|
||||
def writeheader(self, key, value):
|
||||
if not isinstance(value, str):
|
||||
value = str(value)
|
||||
self.write(gntp.shim.b(key))
|
||||
self.write(GNTP_SEP)
|
||||
self.write(gntp.shim.b(value))
|
||||
self.write(GNTP_EOL)
|
||||
|
||||
|
||||
class _GNTPBase(object):
|
||||
"""Base initilization
|
||||
|
||||
:param string messagetype: GNTP Message type
|
||||
:param string version: GNTP Protocol version
|
||||
:param string encription: Encryption protocol
|
||||
"""
|
||||
def __init__(self, messagetype=None, version='1.0', encryption=None):
|
||||
self.info = {
|
||||
'version': version,
|
||||
'messagetype': messagetype,
|
||||
'encryptionAlgorithmID': encryption
|
||||
}
|
||||
self.hash_algo = {
|
||||
'MD5': hashlib.md5,
|
||||
'SHA1': hashlib.sha1,
|
||||
'SHA256': hashlib.sha256,
|
||||
'SHA512': hashlib.sha512,
|
||||
}
|
||||
self.headers = {}
|
||||
self.resources = {}
|
||||
|
||||
# For Python2 we can just return the bytes as is without worry
|
||||
# but on Python3 we want to make sure we return the packet as
|
||||
# a unicode string so that things like logging won't get confused
|
||||
if gntp.shim.PY2:
|
||||
def __str__(self):
|
||||
return self.encode()
|
||||
else:
|
||||
def __str__(self):
|
||||
return gntp.shim.u(self.encode())
|
||||
|
||||
def _parse_info(self, data):
|
||||
"""Parse the first line of a GNTP message to get security and other info values
|
||||
|
||||
:param string data: GNTP Message
|
||||
:return dict: Parsed GNTP Info line
|
||||
"""
|
||||
|
||||
match = GNTP_INFO_LINE.match(data)
|
||||
|
||||
if not match:
|
||||
raise errors.ParseError('ERROR_PARSING_INFO_LINE')
|
||||
|
||||
info = match.groupdict()
|
||||
if info['encryptionAlgorithmID'] == 'NONE':
|
||||
info['encryptionAlgorithmID'] = None
|
||||
|
||||
return info
|
||||
|
||||
def set_password(self, password, encryptAlgo='MD5'):
|
||||
"""Set a password for a GNTP Message
|
||||
|
||||
:param string password: Null to clear password
|
||||
:param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512
|
||||
"""
|
||||
if not password:
|
||||
self.info['encryptionAlgorithmID'] = None
|
||||
self.info['keyHashAlgorithm'] = None
|
||||
return
|
||||
|
||||
self.password = gntp.shim.b(password)
|
||||
self.encryptAlgo = encryptAlgo.upper()
|
||||
|
||||
if not self.encryptAlgo in self.hash_algo:
|
||||
raise errors.UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo)
|
||||
|
||||
hashfunction = self.hash_algo.get(self.encryptAlgo)
|
||||
|
||||
password = password.encode('utf8')
|
||||
seed = time.ctime().encode('utf8')
|
||||
salt = hashfunction(seed).hexdigest()
|
||||
saltHash = hashfunction(seed).digest()
|
||||
keyBasis = password + saltHash
|
||||
key = hashfunction(keyBasis).digest()
|
||||
keyHash = hashfunction(key).hexdigest()
|
||||
|
||||
self.info['keyHashAlgorithmID'] = self.encryptAlgo
|
||||
self.info['keyHash'] = keyHash.upper()
|
||||
self.info['salt'] = salt.upper()
|
||||
|
||||
def _decode_hex(self, value):
|
||||
"""Helper function to decode hex string to `proper` hex string
|
||||
|
||||
:param string value: Human readable hex string
|
||||
:return string: Hex string
|
||||
"""
|
||||
result = ''
|
||||
for i in range(0, len(value), 2):
|
||||
tmp = int(value[i:i + 2], 16)
|
||||
result += chr(tmp)
|
||||
return result
|
||||
|
||||
def _decode_binary(self, rawIdentifier, identifier):
|
||||
rawIdentifier += '\r\n\r\n'
|
||||
dataLength = int(identifier['Length'])
|
||||
pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier)
|
||||
pointerEnd = pointerStart + dataLength
|
||||
data = self.raw[pointerStart:pointerEnd]
|
||||
if not len(data) == dataLength:
|
||||
raise errors.ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data)))
|
||||
return data
|
||||
|
||||
def _validate_password(self, password):
|
||||
"""Validate GNTP Message against stored password"""
|
||||
self.password = password
|
||||
if password is None:
|
||||
raise errors.AuthError('Missing password')
|
||||
keyHash = self.info.get('keyHash', None)
|
||||
if keyHash is None and self.password is None:
|
||||
return True
|
||||
if keyHash is None:
|
||||
raise errors.AuthError('Invalid keyHash')
|
||||
if self.password is None:
|
||||
raise errors.AuthError('Missing password')
|
||||
|
||||
keyHashAlgorithmID = self.info.get('keyHashAlgorithmID','MD5')
|
||||
|
||||
password = self.password.encode('utf8')
|
||||
saltHash = self._decode_hex(self.info['salt'])
|
||||
|
||||
keyBasis = password + saltHash
|
||||
self.key = self.hash_algo[keyHashAlgorithmID](keyBasis).digest()
|
||||
keyHash = self.hash_algo[keyHashAlgorithmID](self.key).hexdigest()
|
||||
|
||||
if not keyHash.upper() == self.info['keyHash'].upper():
|
||||
raise errors.AuthError('Invalid Hash')
|
||||
return True
|
||||
|
||||
def validate(self):
|
||||
"""Verify required headers"""
|
||||
for header in self._requiredHeaders:
|
||||
if not self.headers.get(header, False):
|
||||
raise errors.ParseError('Missing Notification Header: ' + header)
|
||||
|
||||
def _format_info(self):
|
||||
"""Generate info line for GNTP Message
|
||||
|
||||
:return string:
|
||||
"""
|
||||
info = 'GNTP/%s %s' % (
|
||||
self.info.get('version'),
|
||||
self.info.get('messagetype'),
|
||||
)
|
||||
if self.info.get('encryptionAlgorithmID', None):
|
||||
info += ' %s:%s' % (
|
||||
self.info.get('encryptionAlgorithmID'),
|
||||
self.info.get('ivValue'),
|
||||
)
|
||||
else:
|
||||
info += ' NONE'
|
||||
|
||||
if self.info.get('keyHashAlgorithmID', None):
|
||||
info += ' %s:%s.%s' % (
|
||||
self.info.get('keyHashAlgorithmID'),
|
||||
self.info.get('keyHash'),
|
||||
self.info.get('salt')
|
||||
)
|
||||
|
||||
return info
|
||||
|
||||
def _parse_dict(self, data):
|
||||
"""Helper function to parse blocks of GNTP headers into a dictionary
|
||||
|
||||
:param string data:
|
||||
:return dict: Dictionary of parsed GNTP Headers
|
||||
"""
|
||||
d = {}
|
||||
for line in data.split('\r\n'):
|
||||
match = GNTP_HEADER.match(line)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
key = match.group(1).strip()
|
||||
val = match.group(2).strip()
|
||||
d[key] = val
|
||||
return d
|
||||
|
||||
def add_header(self, key, value):
|
||||
self.headers[key] = value
|
||||
|
||||
def add_resource(self, data):
|
||||
"""Add binary resource
|
||||
|
||||
:param string data: Binary Data
|
||||
"""
|
||||
data = gntp.shim.b(data)
|
||||
identifier = hashlib.md5(data).hexdigest()
|
||||
self.resources[identifier] = data
|
||||
return 'x-growl-resource://%s' % identifier
|
||||
|
||||
def decode(self, data, password=None):
|
||||
"""Decode GNTP Message
|
||||
|
||||
:param string data:
|
||||
"""
|
||||
self.password = password
|
||||
self.raw = gntp.shim.u(data)
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(self.raw)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
def encode(self):
|
||||
"""Encode a generic GNTP Message
|
||||
|
||||
:return string: GNTP Message ready to be sent. Returned as a byte string
|
||||
"""
|
||||
|
||||
buff = _GNTPBuffer()
|
||||
|
||||
buff.writeln(self._format_info())
|
||||
|
||||
#Headers
|
||||
for k, v in self.headers.items():
|
||||
buff.writeheader(k, v)
|
||||
buff.writeln()
|
||||
|
||||
#Resources
|
||||
for resource, data in self.resources.items():
|
||||
buff.writeheader('Identifier', resource)
|
||||
buff.writeheader('Length', len(data))
|
||||
buff.writeln()
|
||||
buff.write(data)
|
||||
buff.writeln()
|
||||
buff.writeln()
|
||||
|
||||
return buff.getvalue()
|
||||
|
||||
|
||||
class GNTPRegister(_GNTPBase):
|
||||
"""Represents a GNTP Registration Command
|
||||
|
||||
:param string data: (Optional) See decode()
|
||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||
"""
|
||||
_requiredHeaders = [
|
||||
'Application-Name',
|
||||
'Notifications-Count'
|
||||
]
|
||||
_requiredNotificationHeaders = ['Notification-Name']
|
||||
|
||||
def __init__(self, data=None, password=None):
|
||||
_GNTPBase.__init__(self, 'REGISTER')
|
||||
self.notifications = []
|
||||
|
||||
if data:
|
||||
self.decode(data, password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
self.add_header('Application-Name', 'pygntp')
|
||||
self.add_header('Notifications-Count', 0)
|
||||
|
||||
def validate(self):
|
||||
'''Validate required headers and validate notification headers'''
|
||||
for header in self._requiredHeaders:
|
||||
if not self.headers.get(header, False):
|
||||
raise errors.ParseError('Missing Registration Header: ' + header)
|
||||
for notice in self.notifications:
|
||||
for header in self._requiredNotificationHeaders:
|
||||
if not notice.get(header, False):
|
||||
raise errors.ParseError('Missing Notification Header: ' + header)
|
||||
|
||||
def decode(self, data, password):
|
||||
"""Decode existing GNTP Registration message
|
||||
|
||||
:param string data: Message to decode
|
||||
"""
|
||||
self.raw = gntp.shim.u(data)
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(self.raw)
|
||||
self._validate_password(password)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
if i == 0:
|
||||
continue # Skip Header
|
||||
if part.strip() == '':
|
||||
continue
|
||||
notice = self._parse_dict(part)
|
||||
if notice.get('Notification-Name', False):
|
||||
self.notifications.append(notice)
|
||||
elif notice.get('Identifier', False):
|
||||
notice['Data'] = self._decode_binary(part, notice)
|
||||
#open('register.png','wblol').write(notice['Data'])
|
||||
self.resources[notice.get('Identifier')] = notice
|
||||
|
||||
def add_notification(self, name, enabled=True):
|
||||
"""Add new Notification to Registration message
|
||||
|
||||
:param string name: Notification Name
|
||||
:param boolean enabled: Enable this notification by default
|
||||
"""
|
||||
notice = {}
|
||||
notice['Notification-Name'] = name
|
||||
notice['Notification-Enabled'] = enabled
|
||||
|
||||
self.notifications.append(notice)
|
||||
self.add_header('Notifications-Count', len(self.notifications))
|
||||
|
||||
def encode(self):
|
||||
"""Encode a GNTP Registration Message
|
||||
|
||||
:return string: Encoded GNTP Registration message. Returned as a byte string
|
||||
"""
|
||||
|
||||
buff = _GNTPBuffer()
|
||||
|
||||
buff.writeln(self._format_info())
|
||||
|
||||
#Headers
|
||||
for k, v in self.headers.items():
|
||||
buff.writeheader(k, v)
|
||||
buff.writeln()
|
||||
|
||||
#Notifications
|
||||
if len(self.notifications) > 0:
|
||||
for notice in self.notifications:
|
||||
for k, v in notice.items():
|
||||
buff.writeheader(k, v)
|
||||
buff.writeln()
|
||||
|
||||
#Resources
|
||||
for resource, data in self.resources.items():
|
||||
buff.writeheader('Identifier', resource)
|
||||
buff.writeheader('Length', len(data))
|
||||
buff.writeln()
|
||||
buff.write(data)
|
||||
buff.writeln()
|
||||
buff.writeln()
|
||||
|
||||
return buff.getvalue()
|
||||
|
||||
|
||||
class GNTPNotice(_GNTPBase):
|
||||
"""Represents a GNTP Notification Command
|
||||
|
||||
:param string data: (Optional) See decode()
|
||||
:param string app: (Optional) Set Application-Name
|
||||
:param string name: (Optional) Set Notification-Name
|
||||
:param string title: (Optional) Set Notification Title
|
||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||
"""
|
||||
_requiredHeaders = [
|
||||
'Application-Name',
|
||||
'Notification-Name',
|
||||
'Notification-Title'
|
||||
]
|
||||
|
||||
def __init__(self, data=None, app=None, name=None, title=None, password=None):
|
||||
_GNTPBase.__init__(self, 'NOTIFY')
|
||||
|
||||
if data:
|
||||
self.decode(data, password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
if app:
|
||||
self.add_header('Application-Name', app)
|
||||
if name:
|
||||
self.add_header('Notification-Name', name)
|
||||
if title:
|
||||
self.add_header('Notification-Title', title)
|
||||
|
||||
def decode(self, data, password):
|
||||
"""Decode existing GNTP Notification message
|
||||
|
||||
:param string data: Message to decode.
|
||||
"""
|
||||
self.raw = gntp.shim.u(data)
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(self.raw)
|
||||
self._validate_password(password)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
if i == 0:
|
||||
continue # Skip Header
|
||||
if part.strip() == '':
|
||||
continue
|
||||
notice = self._parse_dict(part)
|
||||
if notice.get('Identifier', False):
|
||||
notice['Data'] = self._decode_binary(part, notice)
|
||||
#open('notice.png','wblol').write(notice['Data'])
|
||||
self.resources[notice.get('Identifier')] = notice
|
||||
|
||||
|
||||
class GNTPSubscribe(_GNTPBase):
|
||||
"""Represents a GNTP Subscribe Command
|
||||
|
||||
:param string data: (Optional) See decode()
|
||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||
"""
|
||||
_requiredHeaders = [
|
||||
'Subscriber-ID',
|
||||
'Subscriber-Name',
|
||||
]
|
||||
|
||||
def __init__(self, data=None, password=None):
|
||||
_GNTPBase.__init__(self, 'SUBSCRIBE')
|
||||
if data:
|
||||
self.decode(data, password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
|
||||
|
||||
class GNTPOK(_GNTPBase):
|
||||
"""Represents a GNTP OK Response
|
||||
|
||||
:param string data: (Optional) See _GNTPResponse.decode()
|
||||
:param string action: (Optional) Set type of action the OK Response is for
|
||||
"""
|
||||
_requiredHeaders = ['Response-Action']
|
||||
|
||||
def __init__(self, data=None, action=None):
|
||||
_GNTPBase.__init__(self, '-OK')
|
||||
if data:
|
||||
self.decode(data)
|
||||
if action:
|
||||
self.add_header('Response-Action', action)
|
||||
|
||||
|
||||
class GNTPError(_GNTPBase):
|
||||
"""Represents a GNTP Error response
|
||||
|
||||
:param string data: (Optional) See _GNTPResponse.decode()
|
||||
:param string errorcode: (Optional) Error code
|
||||
:param string errordesc: (Optional) Error Description
|
||||
"""
|
||||
_requiredHeaders = ['Error-Code', 'Error-Description']
|
||||
|
||||
def __init__(self, data=None, errorcode=None, errordesc=None):
|
||||
_GNTPBase.__init__(self, '-ERROR')
|
||||
if data:
|
||||
self.decode(data)
|
||||
if errorcode:
|
||||
self.add_header('Error-Code', errorcode)
|
||||
self.add_header('Error-Description', errordesc)
|
||||
|
||||
def error(self):
|
||||
return (self.headers.get('Error-Code', None),
|
||||
self.headers.get('Error-Description', None))
|
||||
|
||||
|
||||
def parse_gntp(data, password=None):
|
||||
"""Attempt to parse a message as a GNTP message
|
||||
|
||||
:param string data: Message to be parsed
|
||||
:param string password: Optional password to be used to verify the message
|
||||
"""
|
||||
data = gntp.shim.u(data)
|
||||
match = GNTP_INFO_LINE_SHORT.match(data)
|
||||
if not match:
|
||||
raise errors.ParseError('INVALID_GNTP_INFO')
|
||||
info = match.groupdict()
|
||||
if info['messagetype'] == 'REGISTER':
|
||||
return GNTPRegister(data, password=password)
|
||||
elif info['messagetype'] == 'NOTIFY':
|
||||
return GNTPNotice(data, password=password)
|
||||
elif info['messagetype'] == 'SUBSCRIBE':
|
||||
return GNTPSubscribe(data, password=password)
|
||||
elif info['messagetype'] == '-OK':
|
||||
return GNTPOK(data)
|
||||
elif info['messagetype'] == '-ERROR':
|
||||
return GNTPError(data)
|
||||
raise errors.ParseError('INVALID_GNTP_MESSAGE')
|
||||
25
gntp/errors.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Copyright: 2013 Paul Traylor
|
||||
# These sources are released under the terms of the MIT license: see LICENSE
|
||||
|
||||
class BaseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ParseError(BaseError):
|
||||
errorcode = 500
|
||||
errordesc = 'Error parsing the message'
|
||||
|
||||
|
||||
class AuthError(BaseError):
|
||||
errorcode = 400
|
||||
errordesc = 'Error with authorization'
|
||||
|
||||
|
||||
class UnsupportedError(BaseError):
|
||||
errorcode = 500
|
||||
errordesc = 'Currently unsupported by gntp.py'
|
||||
|
||||
|
||||
class NetworkError(BaseError):
|
||||
errorcode = 500
|
||||
errordesc = "Error connecting to growl server"
|
||||
161
gntp/notifier.py
@@ -1,3 +1,6 @@
|
||||
# Copyright: 2013 Paul Traylor
|
||||
# These sources are released under the terms of the MIT license: see LICENSE
|
||||
|
||||
"""
|
||||
The gntp.notifier module is provided as a simple way to send notifications
|
||||
using GNTP
|
||||
@@ -9,10 +12,15 @@ using GNTP
|
||||
`Original Python bindings <http://code.google.com/p/growl/source/browse/Bindings/python/Growl.py>`_
|
||||
|
||||
"""
|
||||
import gntp
|
||||
import socket
|
||||
import logging
|
||||
import platform
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from gntp.version import __version__
|
||||
import gntp.core
|
||||
import gntp.errors as errors
|
||||
import gntp.shim
|
||||
|
||||
__all__ = [
|
||||
'mini',
|
||||
@@ -22,45 +30,6 @@ __all__ = [
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def mini(description, applicationName='PythonMini', noteType="Message",
|
||||
title="Mini Message", applicationIcon=None, hostname='localhost',
|
||||
password=None, port=23053, sticky=False, priority=None,
|
||||
callback=None, notificationIcon=None, identifier=None):
|
||||
"""Single notification function
|
||||
|
||||
Simple notification function in one line. Has only one required parameter
|
||||
and attempts to use reasonable defaults for everything else
|
||||
:param string description: Notification message
|
||||
|
||||
.. warning::
|
||||
For now, only URL callbacks are supported. In the future, the
|
||||
callback argument will also support a function
|
||||
"""
|
||||
growl = GrowlNotifier(
|
||||
applicationName=applicationName,
|
||||
notifications=[noteType],
|
||||
defaultNotifications=[noteType],
|
||||
applicationIcon=applicationIcon,
|
||||
hostname=hostname,
|
||||
password=password,
|
||||
port=port,
|
||||
)
|
||||
result = growl.register()
|
||||
if result is not True:
|
||||
return result
|
||||
|
||||
return growl.notify(
|
||||
noteType=noteType,
|
||||
title=title,
|
||||
description=description,
|
||||
icon=notificationIcon,
|
||||
sticky=sticky,
|
||||
priority=priority,
|
||||
callback=callback,
|
||||
identifier=identifier,
|
||||
)
|
||||
|
||||
|
||||
class GrowlNotifier(object):
|
||||
"""Helper class to simplfy sending Growl messages
|
||||
|
||||
@@ -99,8 +68,9 @@ class GrowlNotifier(object):
|
||||
If it's a simple URL icon, then we return True. If it's a data icon
|
||||
then we return False
|
||||
'''
|
||||
logger.debug('Checking icon')
|
||||
return data.startswith('http')
|
||||
logger.info('Checking icon')
|
||||
|
||||
return gntp.shim.u(data)[:4] in ['http', 'file']
|
||||
|
||||
def register(self):
|
||||
"""Send GNTP Registration
|
||||
@@ -109,8 +79,8 @@ class GrowlNotifier(object):
|
||||
Before sending notifications to Growl, you need to have
|
||||
sent a registration message at least once
|
||||
"""
|
||||
logger.debug('Sending registration to %s:%s', self.hostname, self.port)
|
||||
register = gntp.GNTPRegister()
|
||||
logger.info('Sending registration to %s:%s', self.hostname, self.port)
|
||||
register = gntp.core.GNTPRegister()
|
||||
register.add_header('Application-Name', self.applicationName)
|
||||
for notification in self.notifications:
|
||||
enabled = notification in self.defaultNotifications
|
||||
@@ -119,8 +89,8 @@ class GrowlNotifier(object):
|
||||
if self._checkIcon(self.applicationIcon):
|
||||
register.add_header('Application-Icon', self.applicationIcon)
|
||||
else:
|
||||
id = register.add_resource(self.applicationIcon)
|
||||
register.add_header('Application-Icon', id)
|
||||
resource = register.add_resource(self.applicationIcon)
|
||||
register.add_header('Application-Icon', resource)
|
||||
if self.password:
|
||||
register.set_password(self.password, self.passwordHash)
|
||||
self.add_origin_info(register)
|
||||
@@ -128,7 +98,7 @@ class GrowlNotifier(object):
|
||||
return self._send('register', register)
|
||||
|
||||
def notify(self, noteType, title, description, icon=None, sticky=False,
|
||||
priority=None, callback=None, identifier=None):
|
||||
priority=None, callback=None, identifier=None, custom={}):
|
||||
"""Send a GNTP notifications
|
||||
|
||||
.. warning::
|
||||
@@ -141,14 +111,16 @@ class GrowlNotifier(object):
|
||||
:param boolean sticky: Sticky notification
|
||||
:param integer priority: Message priority level from -2 to 2
|
||||
:param string callback: URL callback
|
||||
:param dict custom: Custom attributes. Key names should be prefixed with X-
|
||||
according to the spec but this is not enforced by this class
|
||||
|
||||
.. warning::
|
||||
For now, only URL callbacks are supported. In the future, the
|
||||
callback argument will also support a function
|
||||
"""
|
||||
logger.debug('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
|
||||
logger.info('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
|
||||
assert noteType in self.notifications
|
||||
notice = gntp.GNTPNotice()
|
||||
notice = gntp.core.GNTPNotice()
|
||||
notice.add_header('Application-Name', self.applicationName)
|
||||
notice.add_header('Notification-Name', noteType)
|
||||
notice.add_header('Notification-Title', title)
|
||||
@@ -162,8 +134,8 @@ class GrowlNotifier(object):
|
||||
if self._checkIcon(icon):
|
||||
notice.add_header('Notification-Icon', icon)
|
||||
else:
|
||||
id = notice.add_resource(icon)
|
||||
notice.add_header('Notification-Icon', id)
|
||||
resource = notice.add_resource(icon)
|
||||
notice.add_header('Notification-Icon', resource)
|
||||
|
||||
if description:
|
||||
notice.add_header('Notification-Text', description)
|
||||
@@ -172,6 +144,9 @@ class GrowlNotifier(object):
|
||||
if identifier:
|
||||
notice.add_header('Notification-Coalescing-ID', identifier)
|
||||
|
||||
for key in custom:
|
||||
notice.add_header(key, custom[key])
|
||||
|
||||
self.add_origin_info(notice)
|
||||
self.notify_hook(notice)
|
||||
|
||||
@@ -179,7 +154,7 @@ class GrowlNotifier(object):
|
||||
|
||||
def subscribe(self, id, name, port):
|
||||
"""Send a Subscribe request to a remote machine"""
|
||||
sub = gntp.GNTPSubscribe()
|
||||
sub = gntp.core.GNTPSubscribe()
|
||||
sub.add_header('Subscriber-ID', id)
|
||||
sub.add_header('Subscriber-Name', name)
|
||||
sub.add_header('Subscriber-Port', port)
|
||||
@@ -195,7 +170,7 @@ class GrowlNotifier(object):
|
||||
"""Add optional Origin headers to message"""
|
||||
packet.add_header('Origin-Machine-Name', platform.node())
|
||||
packet.add_header('Origin-Software-Name', 'gntp.py')
|
||||
packet.add_header('Origin-Software-Version', gntp.__version__)
|
||||
packet.add_header('Origin-Software-Version', __version__)
|
||||
packet.add_header('Origin-Platform-Name', platform.system())
|
||||
packet.add_header('Origin-Platform-Version', platform.platform())
|
||||
|
||||
@@ -214,34 +189,78 @@ class GrowlNotifier(object):
|
||||
packet.validate()
|
||||
data = packet.encode()
|
||||
|
||||
#logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
|
||||
#Less verbose
|
||||
logger.debug('To : %s:%s <%s>', self.hostname, self.port, packet.__class__)
|
||||
logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(self.socketTimeout)
|
||||
s.connect((self.hostname, self.port))
|
||||
s.send(data)
|
||||
recv_data = s.recv(1024)
|
||||
while not recv_data.endswith("\r\n\r\n"):
|
||||
recv_data += s.recv(1024)
|
||||
response = gntp.parse_gntp(recv_data)
|
||||
try:
|
||||
s.connect((self.hostname, self.port))
|
||||
s.send(data)
|
||||
recv_data = s.recv(1024)
|
||||
while not recv_data.endswith(gntp.shim.b("\r\n\r\n")):
|
||||
recv_data += s.recv(1024)
|
||||
except socket.error:
|
||||
# Python2.5 and Python3 compatibile exception
|
||||
exc = sys.exc_info()[1]
|
||||
raise errors.NetworkError(exc)
|
||||
|
||||
response = gntp.core.parse_gntp(recv_data)
|
||||
s.close()
|
||||
|
||||
#logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
|
||||
#Less verbose
|
||||
logger.debug('From : %s:%s <%s>', self.hostname, self.port, response.__class__)
|
||||
logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
|
||||
|
||||
if type(response) == gntp.GNTPOK:
|
||||
return True
|
||||
if response.error()[0] == '404' and 'disabled' in response.error()[1]:
|
||||
# Ignore message saying that user has disabled this class
|
||||
if type(response) == gntp.core.GNTPOK:
|
||||
return True
|
||||
logger.error('Invalid response: %s', response.error())
|
||||
return response.error()
|
||||
|
||||
|
||||
def mini(description, applicationName='PythonMini', noteType="Message",
|
||||
title="Mini Message", applicationIcon=None, hostname='localhost',
|
||||
password=None, port=23053, sticky=False, priority=None,
|
||||
callback=None, notificationIcon=None, identifier=None,
|
||||
notifierFactory=GrowlNotifier):
|
||||
"""Single notification function
|
||||
|
||||
Simple notification function in one line. Has only one required parameter
|
||||
and attempts to use reasonable defaults for everything else
|
||||
:param string description: Notification message
|
||||
|
||||
.. warning::
|
||||
For now, only URL callbacks are supported. In the future, the
|
||||
callback argument will also support a function
|
||||
"""
|
||||
try:
|
||||
growl = notifierFactory(
|
||||
applicationName=applicationName,
|
||||
notifications=[noteType],
|
||||
defaultNotifications=[noteType],
|
||||
applicationIcon=applicationIcon,
|
||||
hostname=hostname,
|
||||
password=password,
|
||||
port=port,
|
||||
)
|
||||
result = growl.register()
|
||||
if result is not True:
|
||||
return result
|
||||
|
||||
return growl.notify(
|
||||
noteType=noteType,
|
||||
title=title,
|
||||
description=description,
|
||||
icon=notificationIcon,
|
||||
sticky=sticky,
|
||||
priority=priority,
|
||||
callback=callback,
|
||||
identifier=identifier,
|
||||
)
|
||||
except Exception:
|
||||
# We want the "mini" function to be simple and swallow Exceptions
|
||||
# in order to be less invasive
|
||||
logger.exception("Growl error")
|
||||
|
||||
if __name__ == '__main__':
|
||||
# If we're running this module directly we're likely running it as a test
|
||||
# so extra debugging is useful
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
mini('Testing mini notification')
|
||||
|
||||
46
gntp/shim.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# Copyright: 2013 Paul Traylor
|
||||
# These sources are released under the terms of the MIT license: see LICENSE
|
||||
|
||||
"""
|
||||
Python2.5 and Python3.3 compatibility shim
|
||||
|
||||
Heavily inspirted by the "six" library.
|
||||
https://pypi.python.org/pypi/six
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
def b(s):
|
||||
if isinstance(s, bytes):
|
||||
return s
|
||||
return s.encode('utf8', 'replace')
|
||||
|
||||
def u(s):
|
||||
if isinstance(s, bytes):
|
||||
return s.decode('utf8', 'replace')
|
||||
return s
|
||||
|
||||
from io import BytesIO as StringIO
|
||||
from configparser import RawConfigParser
|
||||
else:
|
||||
def b(s):
|
||||
if isinstance(s, unicode):
|
||||
return s.encode('utf8', 'replace')
|
||||
return s
|
||||
|
||||
def u(s):
|
||||
if isinstance(s, unicode):
|
||||
return s
|
||||
if isinstance(s, int):
|
||||
s = str(s)
|
||||
return unicode(s, "utf8", "replace")
|
||||
|
||||
from StringIO import StringIO
|
||||
from ConfigParser import RawConfigParser
|
||||
|
||||
b.__doc__ = "Ensure we have a byte string"
|
||||
u.__doc__ = "Ensure we have a unicode string"
|
||||
4
gntp/version.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright: 2013 Paul Traylor
|
||||
# These sources are released under the terms of the MIT license: see LICENSE
|
||||
|
||||
__version__ = '1.0.3'
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Config"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/configure"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/configure"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<!--#from locale import getpreferredencoding#-->
|
||||
@@ -19,10 +19,6 @@
|
||||
<th scope="row">$T('confgFile'): </th>
|
||||
<td>$configfn</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('cache').capitalize(): </th>
|
||||
<td><!--#set $msg=$T('ft-buffer@2')%($cache_art, $cache_size)#-->$msg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('parameters'): </th>
|
||||
<td>$cmdline</td>
|
||||
@@ -34,12 +30,7 @@
|
||||
<tr>
|
||||
<th scope="row">OpenSSL:</th>
|
||||
<td>
|
||||
<!--#if $have_ssl#-->
|
||||
$ssl_version [$ssl_protocols]
|
||||
<!--#else#-->
|
||||
<span class="label label-danger">$T('notAvailable')</span>
|
||||
<a href="$helpuri$help_uri#no_ssl" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
<!--#end if#-->
|
||||
$ssl_version [$ssl_protocols]
|
||||
</td>
|
||||
</tr>
|
||||
<!--#if not $have_ssl_context#-->
|
||||
@@ -50,6 +41,19 @@
|
||||
</td>
|
||||
</tr>
|
||||
<!--#end if#-->
|
||||
<!--#if not $nt and not $darwin#-->
|
||||
<tr>
|
||||
<th scope="row">$T('opt-multicore-par2')</th>
|
||||
<td>
|
||||
<!--#if $have_mt_par2#-->
|
||||
<span class="glyphicon glyphicon-ok"></span>
|
||||
<!--#else#-->
|
||||
<span class="label label-warning">$T('notAvailable')</span> $T('explain-getpar2mt')
|
||||
<a href="${helpuri}installation/multicore-par2" target="_blank">${helpuri}installation/multicore-par2</a>
|
||||
<!--#end if#-->
|
||||
</td>
|
||||
</tr>
|
||||
<!--#end if#-->
|
||||
<!--#if not $have_cryptography #-->
|
||||
<tr>
|
||||
<th scope="row">Python Cryptography:</th>
|
||||
@@ -59,7 +63,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<!--#end if#-->
|
||||
<!--#if not $have_yenc#-->
|
||||
<!--#if not $have_yenc and not $have_sabyenc#-->
|
||||
<tr>
|
||||
<th scope="row">yEnc:</th>
|
||||
<td>
|
||||
@@ -68,6 +72,15 @@
|
||||
</td>
|
||||
</tr>
|
||||
<!--#end if#-->
|
||||
<!--#if not $have_sabyenc#-->
|
||||
<tr>
|
||||
<th scope="row">SABYenc:</th>
|
||||
<td>
|
||||
<span class="label label-danger">$T('notAvailable')</span>
|
||||
<a href="$helpuri$help_uri#no_sabyenc" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<!--#end if#-->
|
||||
<!--#if not $have_unzip #-->
|
||||
<tr>
|
||||
<th scope="row">$T('opt-enable_unzip'):</th>
|
||||
@@ -96,7 +109,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">$T('homePage') </th>
|
||||
<td><a href="http://sabnzbd.org/" target="_blank">http://sabnzbd.org/</a></td>
|
||||
<td><a href="https://sabnzbd.org/" target="_blank">https://sabnzbd.org/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-wiki') </th>
|
||||
@@ -104,7 +117,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-forums') </th>
|
||||
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
|
||||
<td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('source') </th>
|
||||
@@ -118,6 +131,10 @@
|
||||
<th scope="row">$T('menu-issues') </th>
|
||||
<td><a href="https://sabnzbd.org/wiki/introduction/known-issues" target="_blank">https://sabnzbd.org/wiki/introduction/known-issues</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-donate') </th>
|
||||
<td><a href="https://sabnzbd.org/donate" target="_blank">https://sabnzbd.org/donate</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Categories"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/categories"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/categories"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
<div class="colmask">
|
||||
<div class="section">
|
||||
@@ -17,9 +17,7 @@
|
||||
<th>$T('category')</th>
|
||||
<th>$T('priority')</th>
|
||||
<th>$T('mode')</th>
|
||||
<!--#if $script_list#-->
|
||||
<th>$T('script')</th>
|
||||
<!--#end if#-->
|
||||
<th>$T('catFolderPath')</th>
|
||||
<th colspan="2">$T('catTags')</th>
|
||||
</tr>
|
||||
@@ -61,17 +59,21 @@
|
||||
<option value="3" <!--#if $slot.pp=="3" then 'selected="selected"' else ""#-->>$T('pp-delete')</option>
|
||||
</select>
|
||||
</td>
|
||||
<!--#if $script_list#-->
|
||||
<td>
|
||||
<!--#if $scripts#-->
|
||||
<select name="script">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<!--#if not ($sc == 'Default' and $slot.name == '*')#-->
|
||||
<option value="$sc" <!--#if $slot.script.lower()==$sc.lower() then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||
<!--#end if#-->
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<!--#else#-->
|
||||
<select name="script" disabled>
|
||||
|
||||
</select>
|
||||
<!--#end if#-->
|
||||
</td>
|
||||
<!--#end if#-->
|
||||
<td class="nowrap">
|
||||
<input type="text" name="dir" class="fileBrowserSmall" value="$slot.dir" size="20" data-initialdir="$defdir" data-title="$T('catFolderPath')" title="$T('explain-catTags2')" />
|
||||
</td>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Folders"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/folders"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/folders"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="General"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/general"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/general"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
@@ -23,9 +23,9 @@
|
||||
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" />
|
||||
<span class="desc">$T('explain-port')</span>
|
||||
</div>
|
||||
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
|
||||
<div class="field-pair">
|
||||
<label class="config" for="enable_https">$T('opt-enable_https')</label>
|
||||
<input type="checkbox" name="enable_https" id="enable_https" value="1" <!--#if int($enable_https) > 0 then 'checked="checked"' else ""#--> <!--#if int($have_ssl) == 0 then "disabled" else ""#--> />
|
||||
<input type="checkbox" name="enable_https" id="enable_https" value="1" <!--#if int($enable_https) > 0 then 'checked="checked" data-original="1"' else ""#-->/>
|
||||
<span class="desc">$T('explain-enable_https')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
@@ -39,21 +39,7 @@
|
||||
<!--#end if#-->
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<span class="desc">$T('explain-web_dir') <a href="$caller_url1">$caller_url1</a></span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="web_dir2">$T('opt-web_dir2')</label>
|
||||
<select name="web_dir2" id="web_dir2">
|
||||
<option value="None" selected="selected">$T("None")</option>
|
||||
<!--#for $webline in $web_list#-->
|
||||
<!--#if $webline.lower() == $web_dir2.lower()#-->
|
||||
<option value="$webline" selected="selected">$webline</option>
|
||||
<!--#else#-->
|
||||
<option value="$webline">$webline</option>
|
||||
<!--#end if#-->
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<span class="desc">$T('explain-web_dir2') <a href="$caller_url2">$caller_url2</a></span>
|
||||
<span class="desc">$T('explain-web_dir') <a href="$caller_url">$caller_url</a></span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="language">$T('opt-language')</label>
|
||||
@@ -254,7 +240,17 @@
|
||||
\$('.alert-translate').show()
|
||||
}
|
||||
}
|
||||
\$('#language').on('change', hideOrShowTranslate)
|
||||
\$('#language').on('change', function() {
|
||||
// Show message
|
||||
hideOrShowTranslate()
|
||||
// Re-load page on submit
|
||||
\$('.fullform').submit(function() {
|
||||
// Skip the fancy stuff, just submit
|
||||
this.submit()
|
||||
})
|
||||
// No JSON reponse
|
||||
\$('#ajax').val('')
|
||||
})
|
||||
hideOrShowTranslate()
|
||||
|
||||
\$('#apikey, #nzbkey').click(function () { \$(this).select() });
|
||||
@@ -328,14 +324,20 @@
|
||||
if(bandwidthLimit) {
|
||||
var bandwithLimitNumber = parseFloat(bandwidthLimit)
|
||||
var bandwithLimitText = bandwidthLimit.replace(/[^a-zA-Z]+/g, '');
|
||||
\$('#bandwidth_max_value').val(bandwithLimitNumber)
|
||||
\$('#bandwidth_max_dropdown').val(bandwithLimitText)
|
||||
if(bandwithLimitNumber) {
|
||||
\$('#bandwidth_max_value').val(bandwithLimitNumber)
|
||||
\$('#bandwidth_max_dropdown').val(bandwithLimitText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update the value
|
||||
\$('#bandwidth_max_value, #bandwidth_max_dropdown').on('change', function() {
|
||||
\$('#bandwidth_max').val(\$('#bandwidth_max_value').val() + \$('#bandwidth_max_dropdown').val())
|
||||
if(\$('#bandwidth_max_value').val()) {
|
||||
\$('#bandwidth_max').val(\$('#bandwidth_max_value').val() + \$('#bandwidth_max_dropdown').val())
|
||||
} else {
|
||||
\$('#bandwidth_max').val('')
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Email"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/notifications"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/notifications"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<!--#def show_notify_checkboxes($section_label)#-->
|
||||
@@ -13,6 +13,18 @@
|
||||
<!--#end for#-->
|
||||
<!--#end def#-->
|
||||
|
||||
<!--#def show_cat_box($section_label)#-->
|
||||
<div class="col2-cats" <!--#if int($getVar($section_label + '_enable')) > 0 then '' else 'style="display:none"'#-->>
|
||||
<hr>
|
||||
<b>$T('affectedCat')</b><br/>
|
||||
<select name="${section_label}_cats" multiple="multiple" class="multiple_cats">
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $ct in $getVar($section_label + '_cats') then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
</div>
|
||||
<!--#end def#-->
|
||||
|
||||
<div class="colmask">
|
||||
<form action="saveEmail" method="post" name="fullform" class="fullform" autocomplete="off" novalidate>
|
||||
<input type="hidden" id="session" name="session" value="$session" />
|
||||
@@ -20,7 +32,15 @@
|
||||
<div class="section" id="email">
|
||||
<div class="col2">
|
||||
<h3>$T('cmenu-email') <a href="$helpuri$help_uri#toc0" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||
</div><!-- /col2 -->
|
||||
<div class="col2-cats" <!--#if int($email_endjob) > 0 then '' else 'style="display:none"'#-->>
|
||||
<b>$T('affectedCat')</b><br/>
|
||||
<select name="email_cats" multiple="multiple" class="multiple_cats">
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $ct in $email_cats then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col1">
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
@@ -79,8 +99,8 @@
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</div>
|
||||
</div>
|
||||
<!--#if $have_ncenter#-->
|
||||
<div class="section">
|
||||
<div class="col2">
|
||||
@@ -91,7 +111,7 @@
|
||||
<td><label for="ncenter_enable"> $T('opt-ncenter_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><!-- /col2 -->
|
||||
</div>
|
||||
<div class="col1" <!--#if int($ncenter_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
$show_notify_checkboxes('ncenter')
|
||||
@@ -103,8 +123,8 @@
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</div>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
<!--#if $nt#-->
|
||||
<div class="section">
|
||||
@@ -116,7 +136,8 @@
|
||||
<td><label for="acenter_enable"> $T('opt-acenter_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><!-- /col2 -->
|
||||
$show_cat_box('acenter')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($acenter_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
$show_notify_checkboxes('acenter')
|
||||
@@ -128,8 +149,8 @@
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</div>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
<!--#if $have_ntfosd#-->
|
||||
<div class="section">
|
||||
@@ -141,7 +162,8 @@
|
||||
<td><label for="ntfosd_enable"> $T('opt-ntfosd_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><!-- /col2 -->
|
||||
$show_cat_box('ntfosd')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($ntfosd_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
$show_notify_checkboxes('ntfosd')
|
||||
@@ -153,8 +175,8 @@
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</div>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
<div class="section" id="growl">
|
||||
<div class="col2">
|
||||
@@ -165,7 +187,8 @@
|
||||
<td><label for="growl_enable"> $T('opt-growl_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><!-- /col2 -->
|
||||
$show_cat_box('growl')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($growl_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
@@ -187,8 +210,8 @@
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="prowl">
|
||||
<div class="col2">
|
||||
<h3>$T('section-Prowl')</h3>
|
||||
@@ -199,7 +222,8 @@
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-prowl_enable')</em>
|
||||
</div><!-- /col2 -->
|
||||
$show_cat_box('prowl')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($prowl_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
@@ -231,8 +255,8 @@
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" id="pushover">
|
||||
<div class="col2">
|
||||
@@ -244,7 +268,8 @@
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-pushover_enable')</em>
|
||||
</div><!-- /col2 -->
|
||||
$show_cat_box('pushover')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($pushover_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
@@ -286,8 +311,8 @@
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="pushbullet">
|
||||
<div class="col2">
|
||||
<h3>$T('section-Pushbullet')</h3>
|
||||
@@ -298,7 +323,8 @@
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-pushbullet_enable')</em>
|
||||
</div><!-- /col2 -->
|
||||
$show_cat_box('pushbullet')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($pushbullet_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
@@ -322,25 +348,26 @@
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="nscript">
|
||||
<div class="col2">
|
||||
<h3>$T('section-NScript')</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-nscript_enable')</em>
|
||||
</div><!-- /col2 -->
|
||||
<h3>$T('section-NScript')</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-nscript_enable')</em>
|
||||
$show_cat_box('nscript')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($nscript_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="nscript_script">$T('opt-nscript_script')</label>
|
||||
<select name="nscript_script">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<option value="$sc" <!--#if $nscript_script == $sc then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
@@ -360,8 +387,8 @@
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- /colmask -->
|
||||
|
||||
@@ -374,11 +401,20 @@
|
||||
\$('.col2 input[name$="enable"]').change(function() {
|
||||
if(this.checked) {
|
||||
\$(this).parents('.section').find('.col1').show()
|
||||
\$(this).parents('.col2').find('.col2-cats').show()
|
||||
} else {
|
||||
\$(this).parents('.section').find('.col1').hide()
|
||||
\$(this).parents('.col2').find('.col2-cats').hide()
|
||||
}
|
||||
\$('form').submit()
|
||||
})
|
||||
\$('#email_endjob').change(function() {
|
||||
if(\$(this).val() > 0) {
|
||||
\$(this).parents('.section').find('.col2-cats').show()
|
||||
} else {
|
||||
\$(this).parents('.section').find('.col2-cats').hide()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
Testing functions
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="RSS"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/rss"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/rss"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
<div class="colmask">
|
||||
<!--#if not $active_feed#-->
|
||||
@@ -163,7 +163,7 @@
|
||||
<!--#if $rss[$feed]['pick_cat']#-->
|
||||
<td>
|
||||
<select name="cat">
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $ct==$rss[$feed]['cat'] then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
@@ -191,7 +191,7 @@
|
||||
<!--#if $rss[$feed]['pick_script']#-->
|
||||
<td>
|
||||
<select name="script">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<option value="$sc" <!--#if $sc==$rss[$feed]['script'] then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
@@ -236,7 +236,7 @@
|
||||
<!--#if $rss[$feed]['pick_cat']#-->
|
||||
<td>
|
||||
<select name="cat">
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $ct=='Default' then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
@@ -264,7 +264,7 @@
|
||||
<!--#if $rss[$feed]['pick_script']#-->
|
||||
<td>
|
||||
<select name="script">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<option value="$sc" <!--#if $sc=='Default' then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
@@ -312,7 +312,7 @@
|
||||
<!--#if $rss[$feed]['pick_cat']#-->
|
||||
<td>
|
||||
<select name="cat">
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $ct==$filter[0] then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
@@ -340,7 +340,7 @@
|
||||
<!--#if $rss[$feed]['pick_script']#-->
|
||||
<td>
|
||||
<select name="script">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<option value="$sc" <!--#if $sc==$filter[2] then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Scheduling"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/scheduling"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/scheduling"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<%
|
||||
@@ -80,21 +80,20 @@ else:
|
||||
<div class="col1">
|
||||
<fieldset>
|
||||
<!--#if $schedlines#-->
|
||||
<!--#set $schednum = 0#-->
|
||||
<!--#set $odd = True#-->
|
||||
<!--#for $line in $schedlines#-->
|
||||
<!--#for $schednum, $line in enumerate($schedlines)#-->
|
||||
<!--#set $odd = not $odd#-->
|
||||
<form action="delSchedule" method="post">
|
||||
<input type="hidden" name="session" value="$session"/>
|
||||
<input type="hidden" name="line" id="line" value="$line"/>
|
||||
<div class="field-pair infoTableSeperator <!--#if $odd then "" else " alt"#-->">
|
||||
<input type="checkbox" name="schedenabled" value="$line" <!--#if int($taskinfo[$schednum][5]) > 0 then 'checked="checked"' else ""#-->>
|
||||
<button class="btn btn-default float-left"><span class="glyphicon glyphicon-trash"></span></button>
|
||||
<div class="scheduleEntry">
|
||||
<span class="time">$taskinfo[$schednum][1]:$taskinfo[$schednum][2]</span><span class="frequency">$taskinfo[$schednum][3]</span> <span class="darkred">$taskinfo[$schednum][4]</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!--#set $schednum = $schednum+1#-->
|
||||
<!--#end for#-->
|
||||
<!--#else#-->
|
||||
<div class="field-pair">
|
||||
@@ -126,5 +125,18 @@ else:
|
||||
\$('#hidden_arguments').show()
|
||||
}*/
|
||||
})
|
||||
|
||||
\$('[name="schedenabled"]').click(function() {
|
||||
\$.ajax({
|
||||
type: "POST",
|
||||
url: "toggleSchedule",
|
||||
data: {line: \$(this).val(), session: "$session" }
|
||||
}).done(function() {
|
||||
// Let us leave!
|
||||
formWasSubmitted = true;
|
||||
formHasChanged = false;
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Servers"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/servers"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/servers"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
@@ -29,9 +29,9 @@
|
||||
<label class="config" for="port">$T('srv-port')</label>
|
||||
<input type="number" name="port" id="port" size="8" value="119" />
|
||||
</div>
|
||||
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
|
||||
<div class="field-pair">
|
||||
<label class="config" for="ssl">$T('srv-ssl')</label>
|
||||
<input type="checkbox" name="ssl" id="ssl" value="1" <!--#if int($have_ssl) == 0 then "disabled=\"disabled\"" else ""#--> />
|
||||
<input type="checkbox" name="ssl" id="ssl" value="1" />
|
||||
<span class="desc">$T('explain-ssl')</span>
|
||||
</div>
|
||||
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
||||
@@ -62,9 +62,9 @@
|
||||
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#--> advanced-settings">
|
||||
<label class="config" for="ssl_verify">$T('opt-ssl_verify')</label>
|
||||
<select name="ssl_verify" id="ssl_verify" <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#-->>
|
||||
<option value="0">$T('ssl_verify-disabled')</option>
|
||||
<option value="1" selected>$T('ssl_verify-normal')</option>
|
||||
<option value="2">$T('ssl_verify-strict')</option>
|
||||
<option value="2" selected>$T('ssl_verify-strict')</option>
|
||||
<option value="1">$T('ssl_verify-normal')</option>
|
||||
<option value="0">$T('ssl_verify-disabled')</option>
|
||||
</select>
|
||||
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
|
||||
</div>
|
||||
@@ -149,9 +149,9 @@
|
||||
<label class="config" for="port$cur">$T('srv-port')</label>
|
||||
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" />
|
||||
</div>
|
||||
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
|
||||
<div class="field-pair">
|
||||
<label class="config" for="ssl$cur">$T('srv-ssl')</label>
|
||||
<input type="checkbox" name="ssl" id="ssl$cur" value="1" <!--#if int($server['ssl']) != 0 and int($have_ssl) == 1 then 'checked="checked"' else ""#--> <!--#if int($have_ssl) == 0 then "disabled=\"disabled\"" else ""#--> />
|
||||
<input type="checkbox" name="ssl" id="ssl$cur" value="1" <!--#if int($server['ssl']) != 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-ssl')</span>
|
||||
</div>
|
||||
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
||||
@@ -183,9 +183,9 @@
|
||||
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#--> advanced-settings">
|
||||
<label class="config" for="ssl_verify$cur">$T('opt-ssl_verify')</label>
|
||||
<select name="ssl_verify" id="ssl_verify$cur" <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#-->>
|
||||
<option value="0" <!--#if $server['ssl_verify'] == 0 then 'selected="selected"' else ""#--> >$T('ssl_verify-disabled')</option>
|
||||
<option value="1" <!--#if $server['ssl_verify'] == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-normal')</option>
|
||||
<option value="2" <!--#if $server['ssl_verify'] == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
|
||||
<option value="2" <!--#if $server['ssl_verify'] == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
|
||||
<option value="1" <!--#if $server['ssl_verify'] == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-normal')</option>
|
||||
<option value="0" <!--#if $server['ssl_verify'] == 0 then 'selected="selected"' else ""#--> >$T('ssl_verify-disabled')</option>
|
||||
</select>
|
||||
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
|
||||
</div>
|
||||
@@ -338,6 +338,7 @@
|
||||
}).then(function(data) {
|
||||
// Let's replace the link
|
||||
msg = data.value.message.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="alert-link" target="_blank">https://sabnzbd.org/certificate-errors</a>')
|
||||
msg = msg.replace('-', '<br>')
|
||||
// Fill the box and enable the button
|
||||
resultBox.removeClass('alert-success alert-danger').show()
|
||||
resultBox.html(msg)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Sorting"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/sorting"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/sorting"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
@@ -12,7 +12,7 @@
|
||||
<p>
|
||||
<b>$T('affectedCat')</b><br/>
|
||||
<select name="tv_cat" multiple="multiple" class="multiple_cats">
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $ct in $tv_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
@@ -165,7 +165,7 @@
|
||||
<p>
|
||||
<b>$T('affectedCat')</b><br/>
|
||||
<select name="movie_cat" multiple="multiple" class="multiple_cats">
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $ct in $movie_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
@@ -303,7 +303,7 @@
|
||||
<p>
|
||||
<b>$T('affectedCat')</b><br/>
|
||||
<select name="date_cat" multiple="multiple" class="multiple_cats">
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $ct in $date_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Special"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/special"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/special"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Switches"#-->
|
||||
<!--#set global $help_uri="configuration/1.2/switches"#-->
|
||||
<!--#set global $help_uri="configuration/2.1/switches"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
@@ -20,9 +20,9 @@
|
||||
</select>
|
||||
<span class="desc">$T('explain-load_balancing')</span>
|
||||
</div>
|
||||
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
|
||||
<div class="field-pair">
|
||||
<label class="config" for="ssl_ciphers">$T('opt-ssl_ciphers')</label>
|
||||
<input type="text" name="ssl_ciphers" id="ssl_ciphers" value="$ssl_ciphers"<!--#if int($have_ssl) == 0 then "disabled=\"disabled\"" else ""#--> />
|
||||
<input type="text" name="ssl_ciphers" id="ssl_ciphers" value="$ssl_ciphers" />
|
||||
<span class="desc">$T('explain-ssl_ciphers') <br>$T('readwiki')
|
||||
<a href="${helpuri}advanced/ssl-ciphers" target="_blank">${helpuri}advanced/ssl-ciphers</a></span>
|
||||
</div>
|
||||
@@ -31,12 +31,6 @@
|
||||
<input type="number" name="max_art_tries" id="max_art_tries" value="$max_art_tries" min="2" max="2000" />
|
||||
<span class="desc">$T('explain-max_art_tries')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="max_art_opt">$T('opt-max_art_opt')</label>
|
||||
<input type="checkbox" name="max_art_opt" id="max_art_opt" value="1" <!--#if int($max_art_opt) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-max_art_opt')</span>
|
||||
</div>
|
||||
|
||||
<div class="field-pair">
|
||||
<label class="config" for="auto_disconnect">$T('opt-auto_disconnect')</label>
|
||||
<input type="checkbox" name="auto_disconnect" id="auto_disconnect" value="1" <!--#if int($auto_disconnect) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -58,7 +52,7 @@
|
||||
<div class="field-pair">
|
||||
<label class="config" for="pre_script">$T('opt-pre_script')</label>
|
||||
<select name="pre_script" id="pre_script">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<!--#if $sc.lower() == $pre_script.lower()#-->
|
||||
<option value="$sc" selected="selected">$Tspec($sc)</option>
|
||||
<!--#else#-->
|
||||
@@ -136,6 +130,11 @@
|
||||
<input type="checkbox" name="auto_sort" id="auto_sort" value="1" <!--#if int($auto_sort) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-auto_sort')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="direct_unpack">$T('opt-direct_unpack')</label>
|
||||
<input type="checkbox" name="direct_unpack" id="direct_unpack" value="1" <!--#if int($direct_unpack) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-direct_unpack')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||
@@ -159,18 +158,6 @@
|
||||
<input type="checkbox" name="enable_all_par" id="enable_all_par" value="1" <!--#if int($enable_all_par) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-enable_all_par').replace('. ', '.<br/>')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="quick_check">$T('opt-quick_check')</label>
|
||||
<input type="checkbox" name="quick_check" id="quick_check" value="1" <!--#if int($quick_check) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-quick_check')</span>
|
||||
</div>
|
||||
<!--#if $have_multicore#-->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="par2_multicore">$T('opt-par2_multicore')</label>
|
||||
<input type="checkbox" name="par2_multicore" id="par2_multicore" value="1" <!--#if int($par2_multicore) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-par2_multicore')</span>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="par_option">$T('opt-par_option')</label>
|
||||
<input type="text" name="par_option" id="par_option" value="$par_option" />
|
||||
@@ -196,11 +183,6 @@
|
||||
<input type="checkbox" name="flat_unpack" id="flat_unpack" value="1" <!--#if int($flat_unpack) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-flat_unpack')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="overwrite_files">$T('opt-overwrite_files')</label>
|
||||
<input type="checkbox" name="overwrite_files" id="overwrite_files" value="1" <!--#if int($overwrite_files) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-overwrite_files')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="script_can_fail">$T('opt-script_can_fail')</label>
|
||||
<input type="checkbox" name="script_can_fail" id="script_can_fail" value="1" <!--#if int($script_can_fail) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -261,11 +243,6 @@
|
||||
<input type="checkbox" name="replace_dots" id="replace_dots" value="1" <!--#if int($replace_dots) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-replace_dots')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="replace_illegal">$T('opt-replace_illegal')</label>
|
||||
<input type="checkbox" name="replace_illegal" id="replace_illegal" value="1" <!--#if int($replace_illegal) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-replace_illegal')</span>
|
||||
</div>
|
||||
<!--#if not $nt#-->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="sanitize_safe">$T('opt-sanitize_safe')</label>
|
||||
@@ -273,11 +250,6 @@
|
||||
<span class="desc">$T('explain-sanitize_safe')</span>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="enable_meta">$T('opt-enable_meta')</label>
|
||||
<input type="checkbox" name="enable_meta" id="enable_meta" value="1" <!--#if int($enable_meta) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-enable_meta')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||
@@ -331,16 +303,7 @@
|
||||
<div class="field-pair">
|
||||
<label class="config" for="rating_enable">$T('opt-rating_enable')</label>
|
||||
<input type="checkbox" name="rating_enable" id="rating_enable" value="1" <!--#if int($rating_enable) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-rating_enable')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="rating_feedback">$T('opt-rating_feedback')</label>
|
||||
<input type="checkbox" name="rating_feedback" id="rating_feedback" value="1" <!--#if int($rating_feedback) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-rating_feedback')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="rating_host">$T('opt-rating_host')</label>
|
||||
<input type="text" name="rating_host" id="rating_host" value="$rating_host" />
|
||||
<span class="desc">$T('explain-rating_enable').replace('. ', '.<br/>')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="rating_api_key">$T('opt-rating_api_key')</label>
|
||||
|
||||
@@ -137,7 +137,8 @@ input[type="checkbox"]+.desc {
|
||||
font-style: italic;
|
||||
padding: 0 1px;
|
||||
}
|
||||
.col2 p {
|
||||
.col2 p,
|
||||
.col2-cats {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin: 1em 0;
|
||||
@@ -865,11 +866,17 @@ input[type="checkbox"] {
|
||||
max-width: 150px !important;
|
||||
}
|
||||
|
||||
.Scheduling input[type="checkbox"] {
|
||||
.Scheduling form[action="addSchedule"] input[type="checkbox"] {
|
||||
margin-top: 0px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
.Scheduling form[action="delSchedule"] input[type="checkbox"] {
|
||||
position: initial;
|
||||
float: left;
|
||||
margin: 9px 10px 0px 5px;
|
||||
}
|
||||
|
||||
.navbar .container {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 982 B |
@@ -228,16 +228,13 @@ function do_restart() {
|
||||
// What template
|
||||
var arrPath = window.location.pathname.split('/');
|
||||
var urlPath = (arrPath[1] == "m" || arrPath[2] == "m") ? '/sabnzbd/m/' : '/sabnzbd/';
|
||||
var switchedHTTPS = !$('#enable_https').is(':checked') && window.location.protocol == 'https:'
|
||||
var switchedHTTPS = ($('#enable_https').is(':checked') == ($('#enable_https').data('original') === undefined))
|
||||
var portsUnchanged = ($('#port').val() == $('#port').data('original')) && ($('#https_port').val() == $('#https_port').data('original'))
|
||||
|
||||
// Are we on settings page?
|
||||
if(!$('body').hasClass('General')) {
|
||||
// Same as before, with fall-back in case location.origin is not supported (<IE9)
|
||||
var urlTotal = window.location.origin ? (window.location.origin + urlPath) : window.location;
|
||||
} else if (!switchedHTTPS && ($('#port').val() == $('#port').data('original')) && ($('#https_port').val() == $('#https_port').data('original'))) {
|
||||
// If the http/https port config didn't change, don't try and guess the URL/port to redirect to
|
||||
// This solves some incorrect behavior if running behind a reverse proxy
|
||||
var urlTotal = window.location.origin ? (window.location.origin + urlPath) : window.location;
|
||||
// Are we on settings page or did nothing change?
|
||||
if(!$('body').hasClass('General') || (!switchedHTTPS && !portsUnchanged)) {
|
||||
// Same as before
|
||||
var urlTotal = window.location.origin + urlPath
|
||||
} else {
|
||||
// Protocol and port depend on http(s) setting
|
||||
if($('#enable_https').is(':checked') && (window.location.protocol == 'https:' || !$('#https_port').val())) {
|
||||
@@ -263,8 +260,13 @@ function do_restart() {
|
||||
// Keep counter of failures
|
||||
var failureCounter = 0;
|
||||
|
||||
// Now we try untill we can connect
|
||||
// Now we try until we can connect
|
||||
var refreshInterval = setInterval(function() {
|
||||
// We skip the first one
|
||||
if(failureCounter == 0) {
|
||||
failureCounter = failureCounter+1;
|
||||
return
|
||||
}
|
||||
$.ajax({ url: urlTotal,
|
||||
success: function() {
|
||||
// Back to base
|
||||
@@ -273,7 +275,7 @@ function do_restart() {
|
||||
error: function(status, text) {
|
||||
failureCounter = failureCounter+1;
|
||||
// Too many failuers and we give up
|
||||
if(failureCounter >= 7) {
|
||||
if(failureCounter >= 6) {
|
||||
// If the port has changed 'Access-Control-Allow-Origin' header will not allow
|
||||
// us to check if the server is back up. So after 7 failures we redirect
|
||||
// anyway in the hopes it works anyway..
|
||||
@@ -281,7 +283,7 @@ function do_restart() {
|
||||
}
|
||||
}
|
||||
})
|
||||
}, 3000)
|
||||
}, 4000)
|
||||
|
||||
// Exception if we go from HTTPS to HTTP
|
||||
// (this is not allowed by browsers and all of the above will be ignored)
|
||||
@@ -404,6 +406,9 @@ $(document).ready(function () {
|
||||
$('#enable_https').on('change', function() {
|
||||
$('.enable_https_options').toggle()
|
||||
})
|
||||
if(!$('#enable_https').is(':checked')) {
|
||||
$('.enable_https_options').hide()
|
||||
}
|
||||
|
||||
$('.advancedButton').click(function(event){
|
||||
$('.advanced-settings').toggle()
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
<!-- ko if: historyStatus.has_rating -->
|
||||
<div class="dropdown history-ratings">
|
||||
<a href="#" class="name-ratings hover-button" data-toggle="dropdown" onclick="keepOpen(this)">
|
||||
<a href="#" class="name-icons hover-button" data-toggle="dropdown" onclick="keepOpen(this)">
|
||||
<span class="glyphicon glyphicon-facetime-video"></span> <span data-bind="text: historyStatus.rating_avg_video"></span>
|
||||
<span class="glyphicon glyphicon-volume-up"></span> <span data-bind="text: historyStatus.rating_avg_audio"></span>
|
||||
</a>
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
</a>
|
||||
<ul class="dropdown-menu menu-options">
|
||||
<li><a href="#modal-help" data-toggle="modal"><span class="glyphicon glyphicon-question-sign"></span> $T('menu-help')</a></li>
|
||||
<li><a href="https://sabnzbd.org/donate" target="_blank"><span class="glyphicon glyphicon-heart"></span> $T('menu-donate')</a></li>
|
||||
<!--#if $have_logout or $have_quota or $have_rss_defined or $have_watched_dir or $pp_pause_event#--><li class="divider"></li><!--#end if#-->
|
||||
<!--#if $have_logout#--><li><a href="./login/?logout=1"><span class="glyphicon glyphicon-log-out"></span> $T('logout')</a></li><!--#end if#-->
|
||||
<!--#if $have_quota#--><li><a href="#" data-bind="click: doQueueAction" data-mode="reset_quota">$T('link-resetQuota')</a></li><!--#end if#-->
|
||||
@@ -101,7 +102,7 @@
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header"><span class="glyphicon glyphicon-off"></span> $T('Glitter-onFinish'):</li>
|
||||
<li>
|
||||
<select data-bind="value: onQueueFinish" class="form-control">
|
||||
<select data-bind="value: onQueueFinish, event: { change: setOnQueueFinish }" class="form-control">
|
||||
<option value=""></option>
|
||||
<optgroup label="$T('eoq-actions')">
|
||||
<option value="shutdown_program">$T('shutdownSab')</option>
|
||||
@@ -111,9 +112,11 @@
|
||||
<option value="hibernate_pc">$T('hibernatePc')</option>
|
||||
<!--#end if#-->
|
||||
</optgroup>
|
||||
<optgroup label="$T('eoq-scripts')">
|
||||
<optgroup label="$T('eoq-scripts')" data-bind="visible: queue.scriptsList().length > 1">
|
||||
<!-- ko foreach: queue.scriptsList -->
|
||||
<option data-bind="text: \$data, attr: { value: 'script_'+\$data } " ></option>
|
||||
<!-- ko if: \$data != glitterTranslate.noneText -->
|
||||
<option data-bind="text: \$data, attr: { value: 'script_'+\$data } " ></option>
|
||||
<!-- /ko -->
|
||||
<!-- /ko -->
|
||||
</optgroup>
|
||||
</select>
|
||||
|
||||
@@ -60,13 +60,13 @@
|
||||
<p><strong>If anything is not working as expected, or could be improved, let us know!</strong></p>
|
||||
<p><strong>If you encounter an error, please include the log file (click on <span class="glyphicon glyphicon-wrench"></span> ) when contacting us.</strong></p>
|
||||
<h4>General</h4>
|
||||
<span class="glyphicon glyphicon-home"></span> <a href="http://forums.sabnzbd.org/" target="_blank">SABnzbd Forum</a><br />
|
||||
<span class="glyphicon glyphicon-home"></span> <a href="https://forums.sabnzbd.org/" target="_blank">SABnzbd Forum</a><br />
|
||||
<span class="glyphicon glyphicon-plane"></span> <a href="https://github.com/sabnzbd/sabnzbd/" target="_blank">SABnzbd on Github</a><br />
|
||||
<span class="glyphicon glyphicon-globe"></span> <a href="https://translations.launchpad.net/sabnzbd" target="_blank">Translations of SABnzbd</a><br />
|
||||
<span class="glyphicon glyphicon-envelope"></span> <a href="mailto:bugs@sabnzbd.org?body=Version:%20$version%20Skin:%20Glitter">Email bugs@sabnzbd.org</a>
|
||||
|
||||
<h4>Interface (Glitter)</h4>
|
||||
<span class="glyphicon glyphicon-home"></span> <a href="http://forums.sabnzbd.org/viewtopic.php?f=5&t=18880" target="_blank">Glitter at SABnzbd Forum</a><br />
|
||||
<span class="glyphicon glyphicon-home"></span> <a href="https://forums.sabnzbd.org/viewtopic.php?f=5&t=18880" target="_blank">Glitter at SABnzbd Forum</a><br />
|
||||
<span class="glyphicon glyphicon-envelope"></span> <a href="mailto:safihre@sabnzbd.org?body=Version:%20$version">Email safihre@sabnzbd.org</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -112,37 +112,36 @@
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('cache')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasStatusInfo">
|
||||
<span data-bind="text: statusInfo.cache_size"></span> (<span data-bind="text: statusInfo.cache_art"></span> $T('Glitter-articles'))
|
||||
<div class="col-sm-6">
|
||||
<span data-bind="text: cacheSize"></span> (<span data-bind="text: cacheArticles"></span> $T('Glitter-articles'))
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('dashboard-systemPerformance')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasStatusInfo">
|
||||
<div class="col-sm-6" data-bind="visible: hasPerformanceInfo">
|
||||
<span data-bind="text: statusInfo.pystone"></span>
|
||||
<a href="#" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<a href="#" data-bind="click: testDiskSpeed" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<small data-bind="truncatedText: statusInfo.cpumodel, length: 25, attr: { 'data-original-title': statusInfo.cpumodel }" data-tooltip="true"></small>
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('dashboard-downloadDirSpeed')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasDiskStatusInfo">
|
||||
<div class="col-sm-6" data-bind="visible: hasPerformanceInfo">
|
||||
<span data-bind="text: statusInfo.downloaddirspeed()"></span> MB/s
|
||||
<a href="#" class="diskspeed-button" data-bind="click: testDiskSpeed" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<small>(<span data-bind="truncatedText: statusInfo.downloaddir, length: 24"></span>)</small>
|
||||
<small>(<span data-bind="truncatedText: statusInfo.downloaddir, length: 24, attr: { 'data-original-title': statusInfo.downloaddir }" data-tooltip="true"></span>)</small>
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasDiskStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('dashboard-completeDirSpeed')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasDiskStatusInfo">
|
||||
<div class="col-sm-6" data-bind="visible: hasPerformanceInfo">
|
||||
<span data-bind="text: statusInfo.completedirspeed()"></span> MB/s
|
||||
<a href="#" class="diskspeed-button" data-bind="click: testDiskSpeed" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<small>(<span data-bind="truncatedText: statusInfo.completedir, length: 24"></span>)</small>
|
||||
<small>(<span data-bind="truncatedText: statusInfo.completedir, length: 24, attr: { 'data-original-title': statusInfo.completedir }" data-tooltip="true"></span>)</small>
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasDiskStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row options-function-box">
|
||||
@@ -185,7 +184,7 @@
|
||||
<div class="col-sm-6">$T('swtag-server')</div>
|
||||
<div class="col-sm-6">
|
||||
<span data-bind="text: servername"></span>
|
||||
<span class="label label-default" data-bind="visible: serveroptional">$T('optional').capitalize()</span>
|
||||
<span class="label label-warning" data-bind="visible: serveroptional">$T('optional').capitalize()</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -208,7 +207,7 @@
|
||||
<div class="row" data-bind="visible: servererror()">
|
||||
<div class="col-sm-12">
|
||||
<div class="alert alert-danger">
|
||||
<a href="#" data-bind="visible: !serveractive(), click: function() { \$parent.unblockServer(servername()) }" class="btn btn-default"><span class="glyphicon glyphicon-share-alt"></span> $T('Glitter-unblockServer')</a>
|
||||
<a href="#" data-bind="visible: !serveractive(), click: function() { \$parent.unblockServer(servername) }" class="btn btn-default"><span class="glyphicon glyphicon-share-alt"></span> $T('Glitter-unblockServer')</a>
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
<span data-bind="text: servererror()"></span>
|
||||
</div>
|
||||
@@ -526,10 +525,14 @@
|
||||
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: '+percentage()+'; background-color: ' + \$parent.filelist.currentItem.progressColor() + ';' }">
|
||||
<input type="checkbox" data-bind="attr: { 'name' : nzf_id }, disable: !canselect(), click : \$parent.filelist.checkSelectRange" title="$T('Glitter-multiSelect')" />
|
||||
<strong data-bind="text: percentage"></strong>
|
||||
<span>
|
||||
<div class="fileDetails">
|
||||
<span data-bind="truncatedTextCenter: filename"></span>
|
||||
<div class="fileControls">
|
||||
<a href="#" data-bind="click: \$parent.filelist.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-top')"><span class="glyphicon glyphicon-chevron-up"></span></a>
|
||||
<a href="#" data-bind="click: \$parent.filelist.moveButton" class="hover-button buttonMoveToBottom" title="$T('Glitter-bottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
|
||||
</div>
|
||||
<small>(<span data-bind="text: file_age"></span> - <span data-bind="text: mb"></span> MB)</small>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -622,7 +625,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>$T('menu-forums'):</strong></td>
|
||||
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
|
||||
<td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>GitHub:</strong></td>
|
||||
@@ -630,7 +633,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>$T('menu-irc'):</strong></td>
|
||||
<td><a href="http://www.sabnzbd.org/live-chat/" target="_blank">http://www.sabnzbd.org/live-chat/</a></td>
|
||||
<td><a href="https://sabnzbd.org/live-chat" target="_blank">https://sabnzbd.org/live-chat</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -95,17 +95,16 @@
|
||||
<span data-bind="text: password"></span>
|
||||
</small>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: (rating_avg_video() !== false) -->
|
||||
<div class="name-ratings hover-button">
|
||||
<span class="glyphicon glyphicon-facetime-video"></span> <span data-bind="text: rating_avg_video"></span>
|
||||
<span class="glyphicon glyphicon-volume-up"></span> <span data-bind="text: rating_avg_audio"></span>
|
||||
<div class="name-icons direct-unpack hover-button" data-bind="visible: direct_unpack">
|
||||
<span class="glyphicon glyphicon-compressed "></span> <span data-bind="text: direct_unpack">5</span>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
<form data-bind="submit: editingNameSubmit">
|
||||
<input type="text" data-bind="value: nameForEdit, visible: editingName(), hasfocus: editingName" />
|
||||
</form>
|
||||
<div class="name-options" data-bind="visible: !editingName()">
|
||||
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-MoveToTop')"><span class="glyphicon glyphicon-chevron-up"></span></a>
|
||||
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToBottom" title="$T('Glitter-MoveToBottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
|
||||
<a href="#" data-bind="click: editName, css: { disabled: isGrabbing() }" class="hover-button"><span class="glyphicon glyphicon-pencil"></span></a>
|
||||
<a href="#" data-bind="click: showFiles, css: { disabled: isGrabbing() }" class="hover-button" title="$T('nzoDetails') - $T('srv-password')"><span class="glyphicon glyphicon-folder-open"></span></a>
|
||||
<small data-bind="text: avg_age"></small>
|
||||
@@ -152,7 +151,7 @@
|
||||
</li>
|
||||
<li title="$T('eoq-scripts')" data-tooltip="true" data-placement="left">
|
||||
<span class="glyphicon glyphicon-flash"></span>
|
||||
<select name="Post-processing" class="form-control" data-bind="options: parent.scriptsList, value: script, event: { change: changeScript }, enable: (parent.scriptsList().length > 0)"></select>
|
||||
<select name="Post-processing" class="form-control" data-bind="options: parent.scriptsList, value: script, event: { change: changeScript }, enable: (parent.scriptsList().length > 1)"></select>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- /ko -->
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
glitterTranslate.pauseFor = "$T('pauseFor')"
|
||||
glitterTranslate.minutes = "$T('mins')"
|
||||
glitterTranslate.shutdown = "$T('shutdownOK?')";
|
||||
glitterTranslate.restart = "$T('explain-Restart')".replace(/<br \/>/g, "\n");
|
||||
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n");
|
||||
glitterTranslate.restart = "$T('explain-Restart') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
|
||||
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n").replace(/"/g,'"');
|
||||
glitterTranslate.removeDown = "$T('Glitter-confirmClearDownloads')";
|
||||
glitterTranslate.removeDow1 = "$T('Glitter-confirmClear1Download')";
|
||||
glitterTranslate.encrypted = "$T('Glitter-encrypted')";
|
||||
@@ -125,7 +125,7 @@
|
||||
<!--#include raw $webdir + "/static/javascripts/jquery.min.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/jquery-ui.min.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/jquery.peity.min.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/moment.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/moment.min.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/knockout-latest.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/knockout-extensions.js"#-->
|
||||
<!--#include raw $webdir + "/static/bootstrap/js/bootstrap.min.js"#-->
|
||||
|
||||
@@ -35,6 +35,39 @@ function Fileslisting(parent) {
|
||||
})
|
||||
}
|
||||
|
||||
// Move to top and bottom buttons
|
||||
self.moveButton = function (item,event) {
|
||||
var ITEMKEY = "ko_sortItem",
|
||||
INDEXKEY = "ko_sourceIndex",
|
||||
LISTKEY = "ko_sortList",
|
||||
PARENTKEY = "ko_parentList",
|
||||
DRAGKEY = "ko_dragItem",
|
||||
unwrap = ko.utils.unwrapObservable,
|
||||
dataGet = ko.utils.domData.get,
|
||||
dataSet = ko.utils.domData.set;
|
||||
var targetRow,sourceRow,tbody;
|
||||
sourceRow = $(event.currentTarget).parents("tr").filter(":first");
|
||||
tbody = sourceRow.parents("tbody").filter(":first");
|
||||
//debugger;
|
||||
dataSet(sourceRow[0], INDEXKEY, ko.utils.arrayIndexOf(sourceRow.parent().children(), sourceRow[0]));
|
||||
sourceRow = sourceRow.detach();
|
||||
if ($(event.currentTarget).is(".buttonMoveToTop")) {
|
||||
// we are moving to the top
|
||||
targetRow = tbody.children(".files-done").filter(":last");
|
||||
} else {
|
||||
//we are moving to the bottom
|
||||
targetRow = tbody.children(".files-sortable").filter(":last");
|
||||
}
|
||||
if(targetRow.length < 1 ){
|
||||
// we found an edge case and need to do something special
|
||||
targetRow = tbody.children(".files-sortable").filter(":first");
|
||||
sourceRow.insertBefore(targetRow[0]);
|
||||
} else {
|
||||
sourceRow.insertAfter($(targetRow[0]));
|
||||
}
|
||||
tbody.sortable('option', 'update').call(tbody[0],null, { item: sourceRow });
|
||||
};
|
||||
|
||||
// Trigger update
|
||||
self.triggerUpdate = function() {
|
||||
// Call API
|
||||
@@ -55,15 +88,10 @@ function Fileslisting(parent) {
|
||||
$.each(response.files, function(index, slot) {
|
||||
// Existing or updating?
|
||||
var existingItem = ko.utils.arrayFirst(self.fileItems(), function(i) {
|
||||
return i.filename() == slot.filename;
|
||||
return i.nzf_id() == slot.nzf_id;
|
||||
});
|
||||
|
||||
if(existingItem) {
|
||||
// We skip queued files!
|
||||
// They cause problems because they can have the same filename
|
||||
// as files that we do want to be updated.. The slot.id is not unique!
|
||||
if(slot.status == "queued") return false;
|
||||
|
||||
// Update the rest
|
||||
existingItem.updateFromData(slot);
|
||||
} else {
|
||||
@@ -203,7 +231,7 @@ function FileslistingModel(parent, data) {
|
||||
self.file_age = ko.observable(data.age);
|
||||
self.mb = ko.observable(data.mb);
|
||||
self.percentage = ko.observable(fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)));
|
||||
self.canselect = ko.observable(data.nzf_id !== undefined);
|
||||
self.canselect = ko.observable(data.status != "finished" && data.status != "queued");
|
||||
self.isdone = ko.observable(data.status == "finished");
|
||||
|
||||
// Update internally
|
||||
@@ -213,7 +241,7 @@ function FileslistingModel(parent, data) {
|
||||
self.file_age(data.age)
|
||||
self.mb(data.mb)
|
||||
self.percentage(fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)));
|
||||
self.canselect(data.nzf_id !== undefined)
|
||||
self.canselect(data.status != "finished" && data.status != "queued")
|
||||
self.isdone(data.status == "finished")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,11 +297,11 @@ function HistoryModel(parent, data) {
|
||||
self.processingDownload = ko.pureComputed(function() {
|
||||
var status = self.status();
|
||||
// When we can cancel
|
||||
if (status === 'Extracting' || status === 'Verifying' || status == 'Repairing') {
|
||||
if (status === 'Extracting' || status === 'Verifying' || status == 'Repairing' || status === 'Running') {
|
||||
return 2
|
||||
}
|
||||
// These cannot be cancelled
|
||||
if(status === 'Moving' || status === 'Running') {
|
||||
if(status === 'Moving') {
|
||||
return 1
|
||||
}
|
||||
return false;
|
||||
@@ -356,10 +356,17 @@ function HistoryModel(parent, data) {
|
||||
return displayDateTime(self.completed(), parent.parent.dateFormat(), 'X')
|
||||
});
|
||||
|
||||
// Subscribe to retryEvent so we can load the password
|
||||
self.canRetry.subscribe(function() {
|
||||
self.updateAllHistory = true;
|
||||
})
|
||||
|
||||
// Re-try button
|
||||
self.retry = function() {
|
||||
// Set JOB-id
|
||||
$('#modal-retry-job input[name="retry_job_id"]').val(self.nzo_id)
|
||||
// Set password
|
||||
$('#retry_job_password').val(self.historyStatus.password())
|
||||
// Open modal
|
||||
$('#modal-retry-job').modal("show")
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ function ViewModel() {
|
||||
self.isRestarting = ko.observable(false);
|
||||
self.useGlobalOptions = ko.observable(true).extend({ persist: 'useGlobalOptions' });
|
||||
self.refreshRate = ko.observable(1).extend({ persist: 'pageRefreshRate' });
|
||||
self.dateFormat = ko.observable('DD/MM/YYYY HH:mm').extend({ persist: 'pageDateFormat' });
|
||||
self.dateFormat = ko.observable('fromNow').extend({ persist: 'pageDateFormat' });
|
||||
self.displayTabbed = ko.observable().extend({ persist: 'displayTabbed' });
|
||||
self.displayCompact = ko.observable(false).extend({ persist: 'displayCompact' });
|
||||
self.confirmDeleteQueue = ko.observable(true).extend({ persist: 'confirmDeleteQueue' });
|
||||
@@ -40,6 +40,8 @@ function ViewModel() {
|
||||
self.quotaLimit = ko.observable();
|
||||
self.quotaLimitLeft = ko.observable();
|
||||
self.systemLoad = ko.observable();
|
||||
self.cacheSize = ko.observable();
|
||||
self.cacheArticles = ko.observable();
|
||||
self.nrWarnings = ko.observable(0);
|
||||
self.allWarnings = ko.observableArray([]);
|
||||
self.allMessages = ko.observableArray([]);
|
||||
@@ -48,7 +50,7 @@ function ViewModel() {
|
||||
|
||||
// Statusinfo container
|
||||
self.hasStatusInfo = ko.observable(false);
|
||||
self.hasDiskStatusInfo = ko.observable(false);
|
||||
self.hasPerformanceInfo = ko.observable(false);
|
||||
self.statusInfo = {};
|
||||
self.statusInfo.folders = ko.observableArray([]);
|
||||
self.statusInfo.servers = ko.observableArray([]);
|
||||
@@ -59,8 +61,6 @@ function ViewModel() {
|
||||
self.statusInfo.pystone = ko.observable();
|
||||
self.statusInfo.cpumodel = ko.observable();
|
||||
self.statusInfo.loglevel = ko.observable();
|
||||
self.statusInfo.cache_size = ko.observable();
|
||||
self.statusInfo.cache_art = ko.observable();
|
||||
self.statusInfo.downloaddir = ko.observable();
|
||||
self.statusInfo.downloaddirspeed = ko.observable();
|
||||
self.statusInfo.completedir = ko.observable();
|
||||
@@ -173,8 +173,8 @@ function ViewModel() {
|
||||
}
|
||||
|
||||
// Did we exceed the space?
|
||||
self.diskSpaceExceeded1(parseInt(response.queue.mbleft)/1024 > parseInt(response.queue.diskspace1))
|
||||
self.diskSpaceExceeded2(parseInt(response.queue.mbleft)/1024 > parseInt(response.queue.diskspace2))
|
||||
self.diskSpaceExceeded1(parseInt(response.queue.mbleft)/1024 > parseFloat(response.queue.diskspace1))
|
||||
self.diskSpaceExceeded2(parseInt(response.queue.mbleft)/1024 > parseFloat(response.queue.diskspace2))
|
||||
|
||||
// Quota
|
||||
self.quotaLimit(response.queue.quota)
|
||||
@@ -183,6 +183,10 @@ function ViewModel() {
|
||||
// System load
|
||||
self.systemLoad(response.queue.loadavg)
|
||||
|
||||
// Cache
|
||||
self.cacheSize(response.queue.cache_size)
|
||||
self.cacheArticles(response.queue.cache_art)
|
||||
|
||||
// Warnings (new warnings will trigger an update of allMessages)
|
||||
self.nrWarnings(response.queue.have_warnings)
|
||||
|
||||
@@ -467,6 +471,12 @@ function ViewModel() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix DateJS bug it has some strange problem with the current day-of-month + 1
|
||||
// Removing the space makes DateJS work properly
|
||||
newValue = newValue.replace(/\s*h|\s*m|\s*d/g, function(match) {
|
||||
return match.trim()
|
||||
});
|
||||
|
||||
// Parse
|
||||
var pauseParsed = Date.parse(newValue);
|
||||
|
||||
@@ -528,7 +538,7 @@ function ViewModel() {
|
||||
// Go over all warnings and add
|
||||
$.each(response.warnings, function(index, warning) {
|
||||
// Split warning into parts
|
||||
var warningSplit = warning.split(/\n/);
|
||||
var warningSplit = convertHTMLtoText(warning).split(/\n/);
|
||||
|
||||
// Reformat CSS label and date
|
||||
var warningData = {
|
||||
@@ -589,7 +599,7 @@ function ViewModel() {
|
||||
}
|
||||
|
||||
// Shutdown options
|
||||
self.onQueueFinish.subscribe(function(newValue) {
|
||||
self.setOnQueueFinish = function(model, event) {
|
||||
// Ignore updates before the page is done
|
||||
if(!self.hasStatusInfo()) return;
|
||||
|
||||
@@ -597,9 +607,12 @@ function ViewModel() {
|
||||
callAPI({
|
||||
mode: 'queue',
|
||||
name: 'change_complete_action',
|
||||
value: newValue
|
||||
value: $(event.target).val()
|
||||
})
|
||||
})
|
||||
|
||||
// Top stop blinking while the API is calling
|
||||
self.onQueueFinish($(event.target).val())
|
||||
}
|
||||
|
||||
// Use global settings or device-specific?
|
||||
self.useGlobalOptions.subscribe(function(newValue) {
|
||||
@@ -749,8 +762,6 @@ function ViewModel() {
|
||||
callAPI({ mode: 'fullstatus', skip_dashboard: (!statusFullRefresh)*1 }).then(function(data) {
|
||||
// Update basic
|
||||
self.statusInfo.loglevel(data.status.loglevel)
|
||||
self.statusInfo.cache_art(data.status.cache_art)
|
||||
self.statusInfo.cache_size(data.status.cache_size)
|
||||
self.statusInfo.folders(data.status.folders)
|
||||
|
||||
// Update the full set
|
||||
@@ -766,7 +777,7 @@ function ViewModel() {
|
||||
self.statusInfo.publicipv4(data.status.publicipv4)
|
||||
self.statusInfo.ipv6(data.status.ipv6 || glitterTranslate.noneText)
|
||||
// Loaded disk info
|
||||
self.hasDiskStatusInfo(true)
|
||||
self.hasPerformanceInfo(true)
|
||||
}
|
||||
|
||||
// Update the servers
|
||||
@@ -816,7 +827,7 @@ function ViewModel() {
|
||||
|
||||
// Do a disk-speedtest
|
||||
self.testDiskSpeed = function(item, event) {
|
||||
self.hasDiskStatusInfo(false)
|
||||
self.hasPerformanceInfo(false)
|
||||
|
||||
// Run it and then display it
|
||||
callSpecialAPI('./status/dashrefresh/').then(function() {
|
||||
|
||||
@@ -148,7 +148,6 @@ function QueueListModel(parent) {
|
||||
// See what the actual index is of the queue-object
|
||||
// This way we can see how we move up and down independent of pagination
|
||||
var itemReplaced = self.queueItems()[event.targetIndex+corTerm];
|
||||
|
||||
callAPI({
|
||||
mode: "switch",
|
||||
value: itemMoved.id,
|
||||
@@ -156,6 +155,25 @@ function QueueListModel(parent) {
|
||||
}).then(self.parent.refresh);
|
||||
};
|
||||
|
||||
// Move button clicked
|
||||
self.moveButton = function(event,ui) {
|
||||
var itemMoved = event;
|
||||
var targetIndex;
|
||||
if($(ui.currentTarget).is(".buttonMoveToTop")){
|
||||
//we want to move to the top
|
||||
targetIndex = 0;
|
||||
} else {
|
||||
// we want to move to the bottom
|
||||
targetIndex = self.totalItems() - 1;
|
||||
}
|
||||
callAPI({
|
||||
mode: "switch",
|
||||
value: itemMoved.id,
|
||||
value2: targetIndex
|
||||
}).then(self.parent.refresh);
|
||||
|
||||
}
|
||||
|
||||
// Save pagination state
|
||||
self.paginationLimit.subscribe(function(newValue) {
|
||||
// Save in config if global
|
||||
@@ -171,7 +189,7 @@ function QueueListModel(parent) {
|
||||
|
||||
// Do we show search box. So it doesn't dissapear when nothing is found
|
||||
self.hasQueueSearch = ko.pureComputed(function() {
|
||||
return (self.pagination.hasPagination() || self.searchTerm())
|
||||
return (self.pagination.hasPagination() || self.searchTerm() || (self.parent.hasQueue() && self.isMultiEditing()))
|
||||
})
|
||||
|
||||
// Searching in queue (rate-limited in decleration)
|
||||
@@ -464,7 +482,8 @@ function QueueModel(parent, data) {
|
||||
self.totalMB = ko.observable(parseFloat(data.mb));
|
||||
self.remainingMB = ko.observable(parseFloat(data.mbleft));
|
||||
self.avg_age = ko.observable(data.avg_age)
|
||||
self.missing = ko.observable(data.missing)
|
||||
self.missing = ko.observable(parseFloat(data.mbmissing))
|
||||
self.direct_unpack = ko.observable(data.direct_unpack)
|
||||
self.category = ko.observable(data.cat);
|
||||
self.priority = ko.observable(parent.priorityName[data.priority]);
|
||||
self.script = ko.observable(data.script);
|
||||
@@ -476,8 +495,6 @@ function QueueModel(parent, data) {
|
||||
self.nameForEdit = ko.observable();
|
||||
self.editingName = ko.observable(false);
|
||||
self.hasDropdown = ko.observable(false);
|
||||
self.rating_avg_video = ko.observable(false)
|
||||
self.rating_avg_audio = ko.observable(false)
|
||||
|
||||
// Color of the progress bar
|
||||
self.progressColor = ko.computed(function() {
|
||||
@@ -485,8 +502,8 @@ function QueueModel(parent, data) {
|
||||
if(self.status() == 'Checking') {
|
||||
return '#58A9FA'
|
||||
}
|
||||
// Check for missing data, the value is arbitrary!
|
||||
if(self.missing() > 50) {
|
||||
// Check for missing data, the value is arbitrary! (3%)
|
||||
if(self.missing()/self.totalMB() > 0.03) {
|
||||
return '#F8A34E'
|
||||
}
|
||||
// Set to grey, only when not Force download
|
||||
@@ -510,9 +527,9 @@ function QueueModel(parent, data) {
|
||||
|
||||
// Texts
|
||||
self.missingText= ko.pureComputed(function() {
|
||||
// Check for missing data, the value is arbitrary!
|
||||
if(self.missing() > 50) {
|
||||
return self.missing() + ' ' + glitterTranslate.misingArt
|
||||
// Check for missing data, the value is arbitrary! (3%)
|
||||
if(self.missing()/self.totalMB() > 0.03) {
|
||||
return self.missing().toFixed(0) + ' MB ' + glitterTranslate.misingArt
|
||||
}
|
||||
return;
|
||||
})
|
||||
@@ -565,19 +582,14 @@ function QueueModel(parent, data) {
|
||||
self.totalMB(parseFloat(data.mb));
|
||||
self.remainingMB(parseFloat(data.mbleft));
|
||||
self.avg_age(data.avg_age)
|
||||
self.missing(data.missing)
|
||||
self.missing(parseFloat(data.mbmissing))
|
||||
self.direct_unpack(data.direct_unpack)
|
||||
self.category(data.cat);
|
||||
self.priority(parent.priorityName[data.priority]);
|
||||
self.script(data.script);
|
||||
self.unpackopts(parseInt(data.unpackopts)) // UnpackOpts fails if not parseInt'd!
|
||||
self.pausedStatus(data.status == 'Paused');
|
||||
self.timeLeft(data.timeleft);
|
||||
|
||||
// If exists, otherwise false
|
||||
if(data.rating_avg_video !== undefined) {
|
||||
self.rating_avg_video(data.rating_avg_video === 0 ? '-' : data.rating_avg_video);
|
||||
self.rating_avg_audio(data.rating_avg_audio === 0 ? '-' : data.rating_avg_audio);
|
||||
}
|
||||
};
|
||||
|
||||
// Pause individual download
|
||||
@@ -652,7 +664,7 @@ function QueueModel(parent, data) {
|
||||
}
|
||||
self.changeScript = function(item) {
|
||||
// Not on empty handlers
|
||||
if(!item.script()) return;
|
||||
if(!item.script() || parent.scriptsList().length <= 1) return;
|
||||
callAPI({
|
||||
mode: 'change_script',
|
||||
value: item.id,
|
||||
|
||||
7
interfaces/Glitter/templates/static/javascripts/moment.min.js
vendored
Normal file
@@ -1,59 +1,60 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : danish (da)
|
||||
//! locale : Danish [da]
|
||||
//! author : Ulrik Nielsen : https://github.com/mrbase
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var da = moment.defineLocale('da', {
|
||||
months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'),
|
||||
monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
|
||||
weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
|
||||
weekdaysShort : 'søn_man_tir_ons_tor_fre_lør'.split('_'),
|
||||
weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD/MM/YYYY',
|
||||
LL : 'D. MMMM YYYY',
|
||||
LLL : 'D. MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd [d.] D. MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay : '[I dag kl.] LT',
|
||||
nextDay : '[I morgen kl.] LT',
|
||||
nextWeek : 'dddd [kl.] LT',
|
||||
lastDay : '[I går kl.] LT',
|
||||
lastWeek : '[sidste] dddd [kl] LT',
|
||||
sameElse : 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'om %s',
|
||||
past : '%s siden',
|
||||
s : 'få sekunder',
|
||||
m : 'et minut',
|
||||
mm : '%d minutter',
|
||||
h : 'en time',
|
||||
hh : '%d timer',
|
||||
d : 'en dag',
|
||||
dd : '%d dage',
|
||||
M : 'en måned',
|
||||
MM : '%d måneder',
|
||||
y : 'et år',
|
||||
yy : '%d år'
|
||||
},
|
||||
ordinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
var da = moment.defineLocale('da', {
|
||||
months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'),
|
||||
monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
|
||||
weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
|
||||
weekdaysShort : 'søn_man_tir_ons_tor_fre_lør'.split('_'),
|
||||
weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD/MM/YYYY',
|
||||
LL : 'D. MMMM YYYY',
|
||||
LLL : 'D. MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd [d.] D. MMMM YYYY [kl.] HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay : '[i dag kl.] LT',
|
||||
nextDay : '[i morgen kl.] LT',
|
||||
nextWeek : 'på dddd [kl.] LT',
|
||||
lastDay : '[i går kl.] LT',
|
||||
lastWeek : '[i] dddd[s kl.] LT',
|
||||
sameElse : 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'om %s',
|
||||
past : '%s siden',
|
||||
s : 'få sekunder',
|
||||
m : 'et minut',
|
||||
mm : '%d minutter',
|
||||
h : 'en time',
|
||||
hh : '%d timer',
|
||||
d : 'en dag',
|
||||
dd : '%d dage',
|
||||
M : 'en måned',
|
||||
MM : '%d måneder',
|
||||
y : 'et år',
|
||||
yy : '%d år'
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return da;
|
||||
return da;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,74 +1,78 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : german (de)
|
||||
//! locale : German [de]
|
||||
//! author : lluchs : https://github.com/lluchs
|
||||
//! author: Menelion Elensúle: https://github.com/Oire
|
||||
//! author : Mikolaj Dadela : https://github.com/mik01aj
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
function processRelativeTime(number, withoutSuffix, key, isFuture) {
|
||||
var format = {
|
||||
'm': ['eine Minute', 'einer Minute'],
|
||||
'h': ['eine Stunde', 'einer Stunde'],
|
||||
'd': ['ein Tag', 'einem Tag'],
|
||||
'dd': [number + ' Tage', number + ' Tagen'],
|
||||
'M': ['ein Monat', 'einem Monat'],
|
||||
'MM': [number + ' Monate', number + ' Monaten'],
|
||||
'y': ['ein Jahr', 'einem Jahr'],
|
||||
'yy': [number + ' Jahre', number + ' Jahren']
|
||||
};
|
||||
return withoutSuffix ? format[key][0] : format[key][1];
|
||||
function processRelativeTime(number, withoutSuffix, key, isFuture) {
|
||||
var format = {
|
||||
'm': ['eine Minute', 'einer Minute'],
|
||||
'h': ['eine Stunde', 'einer Stunde'],
|
||||
'd': ['ein Tag', 'einem Tag'],
|
||||
'dd': [number + ' Tage', number + ' Tagen'],
|
||||
'M': ['ein Monat', 'einem Monat'],
|
||||
'MM': [number + ' Monate', number + ' Monaten'],
|
||||
'y': ['ein Jahr', 'einem Jahr'],
|
||||
'yy': [number + ' Jahre', number + ' Jahren']
|
||||
};
|
||||
return withoutSuffix ? format[key][0] : format[key][1];
|
||||
}
|
||||
|
||||
var de = moment.defineLocale('de', {
|
||||
months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
|
||||
monthsShort : 'Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'),
|
||||
monthsParseExact : true,
|
||||
weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'),
|
||||
weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'),
|
||||
weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'),
|
||||
weekdaysParseExact : true,
|
||||
longDateFormat : {
|
||||
LT: 'HH:mm',
|
||||
LTS: 'HH:mm:ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D. MMMM YYYY',
|
||||
LLL : 'D. MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd, D. MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[heute um] LT [Uhr]',
|
||||
sameElse: 'L',
|
||||
nextDay: '[morgen um] LT [Uhr]',
|
||||
nextWeek: 'dddd [um] LT [Uhr]',
|
||||
lastDay: '[gestern um] LT [Uhr]',
|
||||
lastWeek: '[letzten] dddd [um] LT [Uhr]'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'in %s',
|
||||
past : 'vor %s',
|
||||
s : 'ein paar Sekunden',
|
||||
m : processRelativeTime,
|
||||
mm : '%d Minuten',
|
||||
h : processRelativeTime,
|
||||
hh : '%d Stunden',
|
||||
d : processRelativeTime,
|
||||
dd : processRelativeTime,
|
||||
M : processRelativeTime,
|
||||
MM : processRelativeTime,
|
||||
y : processRelativeTime,
|
||||
yy : processRelativeTime
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
var de = moment.defineLocale('de', {
|
||||
months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
|
||||
monthsShort : 'Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'),
|
||||
weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'),
|
||||
weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'),
|
||||
weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'),
|
||||
longDateFormat : {
|
||||
LT: 'HH:mm',
|
||||
LTS: 'HH:mm:ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D. MMMM YYYY',
|
||||
LLL : 'D. MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd, D. MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Heute um] LT [Uhr]',
|
||||
sameElse: 'L',
|
||||
nextDay: '[Morgen um] LT [Uhr]',
|
||||
nextWeek: 'dddd [um] LT [Uhr]',
|
||||
lastDay: '[Gestern um] LT [Uhr]',
|
||||
lastWeek: '[letzten] dddd [um] LT [Uhr]'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'in %s',
|
||||
past : 'vor %s',
|
||||
s : 'ein paar Sekunden',
|
||||
m : processRelativeTime,
|
||||
mm : '%d Minuten',
|
||||
h : processRelativeTime,
|
||||
hh : '%d Stunden',
|
||||
d : processRelativeTime,
|
||||
dd : processRelativeTime,
|
||||
M : processRelativeTime,
|
||||
MM : processRelativeTime,
|
||||
y : processRelativeTime,
|
||||
yy : processRelativeTime
|
||||
},
|
||||
ordinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
return de;
|
||||
|
||||
return de;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,78 +1,83 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : spanish (es)
|
||||
//! locale : Spanish [es]
|
||||
//! author : Julio Napurí : https://github.com/julionc
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var monthsShortDot = 'Ene._Feb._Mar._Abr._May._Jun._Jul._Ago._Sep._Oct._Nov._Dic.'.split('_'),
|
||||
monthsShort = 'Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Sep_Oct_Nov_Dic'.split('_');
|
||||
var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_');
|
||||
var monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_');
|
||||
|
||||
var es = moment.defineLocale('es', {
|
||||
months : 'Enero_Febrero_Marzo_Abril_Mayo_Junio_Julio_Agosto_Septiembre_Octubre_Noviembre_Diciembre'.split('_'),
|
||||
monthsShort : function (m, format) {
|
||||
if (/-MMM-/.test(format)) {
|
||||
return monthsShort[m.month()];
|
||||
} else {
|
||||
return monthsShortDot[m.month()];
|
||||
}
|
||||
},
|
||||
weekdays : 'Domingo_Lunes_Martes_Miércoles_Jueves_Viernes_Sábado'.split('_'),
|
||||
weekdaysShort : 'Dom._Lun._Mar._Mié._Jue._Vie._Sáb.'.split('_'),
|
||||
weekdaysMin : 'Do_Lu_Ma_Mi_Ju_Vi_Sá'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'H:mm',
|
||||
LTS : 'H:mm:ss',
|
||||
L : 'DD/MM/YYYY',
|
||||
LL : 'D [de] MMMM [de] YYYY',
|
||||
LLL : 'D [de] MMMM [de] YYYY H:mm',
|
||||
LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay : function () {
|
||||
return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
nextDay : function () {
|
||||
return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
nextWeek : function () {
|
||||
return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
lastDay : function () {
|
||||
return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
lastWeek : function () {
|
||||
return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
sameElse : 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'en %s',
|
||||
past : 'hace %s',
|
||||
s : 'unos segundos',
|
||||
m : 'un minuto',
|
||||
mm : '%d minutos',
|
||||
h : 'una hora',
|
||||
hh : '%d horas',
|
||||
d : 'un día',
|
||||
dd : '%d días',
|
||||
M : 'un mes',
|
||||
MM : '%d meses',
|
||||
y : 'un año',
|
||||
yy : '%d años'
|
||||
},
|
||||
ordinalParse : /\d{1,2}º/,
|
||||
ordinal : '%dº',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
var es = moment.defineLocale('es', {
|
||||
months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
|
||||
monthsShort : function (m, format) {
|
||||
if (!m) {
|
||||
return monthsShortDot;
|
||||
} else if (/-MMM-/.test(format)) {
|
||||
return monthsShort[m.month()];
|
||||
} else {
|
||||
return monthsShortDot[m.month()];
|
||||
}
|
||||
});
|
||||
},
|
||||
monthsParseExact : true,
|
||||
weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
|
||||
weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
|
||||
weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'),
|
||||
weekdaysParseExact : true,
|
||||
longDateFormat : {
|
||||
LT : 'H:mm',
|
||||
LTS : 'H:mm:ss',
|
||||
L : 'DD/MM/YYYY',
|
||||
LL : 'D [de] MMMM [de] YYYY',
|
||||
LLL : 'D [de] MMMM [de] YYYY H:mm',
|
||||
LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay : function () {
|
||||
return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
nextDay : function () {
|
||||
return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
nextWeek : function () {
|
||||
return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
lastDay : function () {
|
||||
return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
lastWeek : function () {
|
||||
return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
|
||||
},
|
||||
sameElse : 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'en %s',
|
||||
past : 'hace %s',
|
||||
s : 'unos segundos',
|
||||
m : 'un minuto',
|
||||
mm : '%d minutos',
|
||||
h : 'una hora',
|
||||
hh : '%d horas',
|
||||
d : 'un día',
|
||||
dd : '%d días',
|
||||
M : 'un mes',
|
||||
MM : '%d meses',
|
||||
y : 'un año',
|
||||
yy : '%d años'
|
||||
},
|
||||
dayOfMonthOrdinalParse : /\d{1,2}º/,
|
||||
ordinal : '%dº',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return es;
|
||||
return es;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : finnish (fi)
|
||||
//! locale : Finnish [fi]
|
||||
//! author : Tarmo Aidantausta : https://github.com/bleadof
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' '),
|
||||
numbersFuture = [
|
||||
'nolla', 'yhden', 'kahden', 'kolmen', 'neljän', 'viiden', 'kuuden',
|
||||
numbersPast[7], numbersPast[8], numbersPast[9]
|
||||
];
|
||||
function translate(number, withoutSuffix, key, isFuture) {
|
||||
var result = '';
|
||||
switch (key) {
|
||||
var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' ');
|
||||
var numbersFuture = [
|
||||
'nolla', 'yhden', 'kahden', 'kolmen', 'neljän', 'viiden', 'kuuden',
|
||||
numbersPast[7], numbersPast[8], numbersPast[9]
|
||||
];
|
||||
function translate(number, withoutSuffix, key, isFuture) {
|
||||
var result = '';
|
||||
switch (key) {
|
||||
case 's':
|
||||
return isFuture ? 'muutaman sekunnin' : 'muutama sekunti';
|
||||
case 'm':
|
||||
@@ -44,63 +45,63 @@
|
||||
case 'yy':
|
||||
result = isFuture ? 'vuoden' : 'vuotta';
|
||||
break;
|
||||
}
|
||||
result = verbalNumber(number, isFuture) + ' ' + result;
|
||||
return result;
|
||||
}
|
||||
function verbalNumber(number, isFuture) {
|
||||
return number < 10 ? (isFuture ? numbersFuture[number] : numbersPast[number]) : number;
|
||||
result = verbalNumber(number, isFuture) + ' ' + result;
|
||||
return result;
|
||||
}
|
||||
function verbalNumber(number, isFuture) {
|
||||
return number < 10 ? (isFuture ? numbersFuture[number] : numbersPast[number]) : number;
|
||||
}
|
||||
|
||||
var fi = moment.defineLocale('fi', {
|
||||
months : 'tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu'.split('_'),
|
||||
monthsShort : 'tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu'.split('_'),
|
||||
weekdays : 'sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai'.split('_'),
|
||||
weekdaysShort : 'su_ma_ti_ke_to_pe_la'.split('_'),
|
||||
weekdaysMin : 'su_ma_ti_ke_to_pe_la'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH.mm',
|
||||
LTS : 'HH.mm.ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'Do MMMM[ta] YYYY',
|
||||
LLL : 'Do MMMM[ta] YYYY, [klo] HH.mm',
|
||||
LLLL : 'dddd, Do MMMM[ta] YYYY, [klo] HH.mm',
|
||||
l : 'D.M.YYYY',
|
||||
ll : 'Do MMM YYYY',
|
||||
lll : 'Do MMM YYYY, [klo] HH.mm',
|
||||
llll : 'ddd, Do MMM YYYY, [klo] HH.mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay : '[tänään] [klo] LT',
|
||||
nextDay : '[huomenna] [klo] LT',
|
||||
nextWeek : 'dddd [klo] LT',
|
||||
lastDay : '[eilen] [klo] LT',
|
||||
lastWeek : '[viime] dddd[na] [klo] LT',
|
||||
sameElse : 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : '%s päästä',
|
||||
past : '%s sitten',
|
||||
s : translate,
|
||||
m : translate,
|
||||
mm : translate,
|
||||
h : translate,
|
||||
hh : translate,
|
||||
d : translate,
|
||||
dd : translate,
|
||||
M : translate,
|
||||
MM : translate,
|
||||
y : translate,
|
||||
yy : translate
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
var fi = moment.defineLocale('fi', {
|
||||
months : 'tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu'.split('_'),
|
||||
monthsShort : 'tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu'.split('_'),
|
||||
weekdays : 'sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai'.split('_'),
|
||||
weekdaysShort : 'su_ma_ti_ke_to_pe_la'.split('_'),
|
||||
weekdaysMin : 'su_ma_ti_ke_to_pe_la'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH.mm',
|
||||
LTS : 'HH.mm.ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'Do MMMM[ta] YYYY',
|
||||
LLL : 'Do MMMM[ta] YYYY, [klo] HH.mm',
|
||||
LLLL : 'dddd, Do MMMM[ta] YYYY, [klo] HH.mm',
|
||||
l : 'D.M.YYYY',
|
||||
ll : 'Do MMM YYYY',
|
||||
lll : 'Do MMM YYYY, [klo] HH.mm',
|
||||
llll : 'ddd, Do MMM YYYY, [klo] HH.mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay : '[tänään] [klo] LT',
|
||||
nextDay : '[huomenna] [klo] LT',
|
||||
nextWeek : 'dddd [klo] LT',
|
||||
lastDay : '[eilen] [klo] LT',
|
||||
lastWeek : '[viime] dddd[na] [klo] LT',
|
||||
sameElse : 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : '%s päästä',
|
||||
past : '%s sitten',
|
||||
s : translate,
|
||||
m : translate,
|
||||
mm : translate,
|
||||
h : translate,
|
||||
hh : translate,
|
||||
d : translate,
|
||||
dd : translate,
|
||||
M : translate,
|
||||
MM : translate,
|
||||
y : translate,
|
||||
yy : translate
|
||||
},
|
||||
ordinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
return fi;
|
||||
|
||||
return fi;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,61 +1,83 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : french (fr)
|
||||
//! locale : French [fr]
|
||||
//! author : John Fischer : https://github.com/jfroffice
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var fr = moment.defineLocale('fr', {
|
||||
months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
|
||||
monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
|
||||
weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'),
|
||||
weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
|
||||
weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD/MM/YYYY',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd D MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Aujourd\'hui à] LT',
|
||||
nextDay: '[Demain à] LT',
|
||||
nextWeek: 'dddd [à] LT',
|
||||
lastDay: '[Hier à] LT',
|
||||
lastWeek: 'dddd [dernier à] LT',
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'dans %s',
|
||||
past : 'il y a %s',
|
||||
s : 'quelques secondes',
|
||||
m : 'une minute',
|
||||
mm : '%d minutes',
|
||||
h : 'une heure',
|
||||
hh : '%d heures',
|
||||
d : 'un jour',
|
||||
dd : '%d jours',
|
||||
M : 'un mois',
|
||||
MM : '%d mois',
|
||||
y : 'un an',
|
||||
yy : '%d ans'
|
||||
},
|
||||
ordinalParse: /\d{1,2}(er|)/,
|
||||
ordinal : function (number) {
|
||||
return number + (number === 1 ? 'er' : '');
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
var fr = moment.defineLocale('fr', {
|
||||
months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
|
||||
monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
|
||||
monthsParseExact : true,
|
||||
weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'),
|
||||
weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
|
||||
weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
|
||||
weekdaysParseExact : true,
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD/MM/YYYY',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd D MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay : '[Aujourd’hui à] LT',
|
||||
nextDay : '[Demain à] LT',
|
||||
nextWeek : 'dddd [à] LT',
|
||||
lastDay : '[Hier à] LT',
|
||||
lastWeek : 'dddd [dernier à] LT',
|
||||
sameElse : 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'dans %s',
|
||||
past : 'il y a %s',
|
||||
s : 'quelques secondes',
|
||||
m : 'une minute',
|
||||
mm : '%d minutes',
|
||||
h : 'une heure',
|
||||
hh : '%d heures',
|
||||
d : 'un jour',
|
||||
dd : '%d jours',
|
||||
M : 'un mois',
|
||||
MM : '%d mois',
|
||||
y : 'un an',
|
||||
yy : '%d ans'
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}(er|)/,
|
||||
ordinal : function (number, period) {
|
||||
switch (period) {
|
||||
// TODO: Return 'e' when day of month > 1. Move this case inside
|
||||
// block for masculine words below.
|
||||
// See https://github.com/moment/moment/issues/3375
|
||||
case 'D':
|
||||
return number + (number === 1 ? 'er' : '');
|
||||
|
||||
// Words with masculine grammatical gender: mois, trimestre, jour
|
||||
default:
|
||||
case 'M':
|
||||
case 'Q':
|
||||
case 'DDD':
|
||||
case 'd':
|
||||
return number + (number === 1 ? 'er' : 'e');
|
||||
|
||||
// Words with feminine grammatical gender: semaine
|
||||
case 'w':
|
||||
case 'W':
|
||||
return number + (number === 1 ? 're' : 'e');
|
||||
}
|
||||
});
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return fr;
|
||||
return fr;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,60 +1,63 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : norwegian bokmål (nb)
|
||||
//! locale : Norwegian Bokmål [nb]
|
||||
//! authors : Espen Hovlandsdal : https://github.com/rexxars
|
||||
//! Sigurd Gartmann : https://github.com/sigurdga
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var nb = moment.defineLocale('nb', {
|
||||
months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
|
||||
monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'),
|
||||
weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
|
||||
weekdaysShort : 'søn_man_tirs_ons_tors_fre_lør'.split('_'),
|
||||
weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'H.mm',
|
||||
LTS : 'H.mm.ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D. MMMM YYYY',
|
||||
LLL : 'D. MMMM YYYY [kl.] H.mm',
|
||||
LLLL : 'dddd D. MMMM YYYY [kl.] H.mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[i dag kl.] LT',
|
||||
nextDay: '[i morgen kl.] LT',
|
||||
nextWeek: 'dddd [kl.] LT',
|
||||
lastDay: '[i går kl.] LT',
|
||||
lastWeek: '[forrige] dddd [kl.] LT',
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'om %s',
|
||||
past : 'for %s siden',
|
||||
s : 'noen sekunder',
|
||||
m : 'ett minutt',
|
||||
mm : '%d minutter',
|
||||
h : 'en time',
|
||||
hh : '%d timer',
|
||||
d : 'en dag',
|
||||
dd : '%d dager',
|
||||
M : 'en måned',
|
||||
MM : '%d måneder',
|
||||
y : 'ett år',
|
||||
yy : '%d år'
|
||||
},
|
||||
ordinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
var nb = moment.defineLocale('nb', {
|
||||
months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
|
||||
monthsShort : 'jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.'.split('_'),
|
||||
monthsParseExact : true,
|
||||
weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
|
||||
weekdaysShort : 'sø._ma._ti._on._to._fr._lø.'.split('_'),
|
||||
weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
|
||||
weekdaysParseExact : true,
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D. MMMM YYYY',
|
||||
LLL : 'D. MMMM YYYY [kl.] HH:mm',
|
||||
LLLL : 'dddd D. MMMM YYYY [kl.] HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[i dag kl.] LT',
|
||||
nextDay: '[i morgen kl.] LT',
|
||||
nextWeek: 'dddd [kl.] LT',
|
||||
lastDay: '[i går kl.] LT',
|
||||
lastWeek: '[forrige] dddd [kl.] LT',
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'om %s',
|
||||
past : '%s siden',
|
||||
s : 'noen sekunder',
|
||||
m : 'ett minutt',
|
||||
mm : '%d minutter',
|
||||
h : 'en time',
|
||||
hh : '%d timer',
|
||||
d : 'en dag',
|
||||
dd : '%d dager',
|
||||
M : 'en måned',
|
||||
MM : '%d måneder',
|
||||
y : 'ett år',
|
||||
yy : '%d år'
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return nb;
|
||||
return nb;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,70 +1,88 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : dutch (nl)
|
||||
//! author : Joris Röling : https://github.com/jjupiter
|
||||
//! locale : Dutch [nl]
|
||||
//! author : Joris Röling : https://github.com/jorisroling
|
||||
//! author : Jacob Middag : https://github.com/middagj
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_'),
|
||||
monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_');
|
||||
var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_');
|
||||
var monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_');
|
||||
|
||||
var nl = moment.defineLocale('nl', {
|
||||
months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'),
|
||||
monthsShort : function (m, format) {
|
||||
if (/-MMM-/.test(format)) {
|
||||
return monthsShortWithoutDots[m.month()];
|
||||
} else {
|
||||
return monthsShortWithDots[m.month()];
|
||||
}
|
||||
},
|
||||
weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'),
|
||||
weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'),
|
||||
weekdaysMin : 'Zo_Ma_Di_Wo_Do_Vr_Za'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD-MM-YYYY',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd D MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[vandaag om] LT',
|
||||
nextDay: '[morgen om] LT',
|
||||
nextWeek: 'dddd [om] LT',
|
||||
lastDay: '[gisteren om] LT',
|
||||
lastWeek: '[afgelopen] dddd [om] LT',
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'over %s',
|
||||
past : '%s geleden',
|
||||
s : 'een paar seconden',
|
||||
m : 'één minuut',
|
||||
mm : '%d minuten',
|
||||
h : 'één uur',
|
||||
hh : '%d uur',
|
||||
d : 'één dag',
|
||||
dd : '%d dagen',
|
||||
M : 'één maand',
|
||||
MM : '%d maanden',
|
||||
y : 'één jaar',
|
||||
yy : '%d jaar'
|
||||
},
|
||||
ordinalParse: /\d{1,2}(ste|de)/,
|
||||
ordinal : function (number) {
|
||||
return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de');
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
var monthsParse = [/^jan/i, /^feb/i, /^maart|mrt.?$/i, /^apr/i, /^mei$/i, /^jun[i.]?$/i, /^jul[i.]?$/i, /^aug/i, /^sep/i, /^okt/i, /^nov/i, /^dec/i];
|
||||
var monthsRegex = /^(januari|februari|maart|april|mei|april|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i;
|
||||
|
||||
var nl = moment.defineLocale('nl', {
|
||||
months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'),
|
||||
monthsShort : function (m, format) {
|
||||
if (!m) {
|
||||
return monthsShortWithDots;
|
||||
} else if (/-MMM-/.test(format)) {
|
||||
return monthsShortWithoutDots[m.month()];
|
||||
} else {
|
||||
return monthsShortWithDots[m.month()];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
return nl;
|
||||
monthsRegex: monthsRegex,
|
||||
monthsShortRegex: monthsRegex,
|
||||
monthsStrictRegex: /^(januari|februari|maart|mei|ju[nl]i|april|augustus|september|oktober|november|december)/i,
|
||||
monthsShortStrictRegex: /^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,
|
||||
|
||||
}));
|
||||
monthsParse : monthsParse,
|
||||
longMonthsParse : monthsParse,
|
||||
shortMonthsParse : monthsParse,
|
||||
|
||||
weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'),
|
||||
weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'),
|
||||
weekdaysMin : 'Zo_Ma_Di_Wo_Do_Vr_Za'.split('_'),
|
||||
weekdaysParseExact : true,
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD-MM-YYYY',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd D MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[vandaag om] LT',
|
||||
nextDay: '[morgen om] LT',
|
||||
nextWeek: 'dddd [om] LT',
|
||||
lastDay: '[gisteren om] LT',
|
||||
lastWeek: '[afgelopen] dddd [om] LT',
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'over %s',
|
||||
past : '%s geleden',
|
||||
s : 'een paar seconden',
|
||||
m : 'één minuut',
|
||||
mm : '%d minuten',
|
||||
h : 'één uur',
|
||||
hh : '%d uur',
|
||||
d : 'één dag',
|
||||
dd : '%d dagen',
|
||||
M : 'één maand',
|
||||
MM : '%d maanden',
|
||||
y : 'één jaar',
|
||||
yy : '%d jaar'
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}(ste|de)/,
|
||||
ordinal : function (number) {
|
||||
return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de');
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return nl;
|
||||
|
||||
})));
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : polish (pl)
|
||||
//! locale : Polish [pl]
|
||||
//! author : Rafal Hirsz : https://github.com/evoL
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_'),
|
||||
monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_');
|
||||
function plural(n) {
|
||||
return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1);
|
||||
}
|
||||
function translate(number, withoutSuffix, key) {
|
||||
var result = number + ' ';
|
||||
switch (key) {
|
||||
var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_');
|
||||
var monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_');
|
||||
function plural(n) {
|
||||
return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1);
|
||||
}
|
||||
function translate(number, withoutSuffix, key) {
|
||||
var result = number + ' ';
|
||||
switch (key) {
|
||||
case 'm':
|
||||
return withoutSuffix ? 'minuta' : 'minutę';
|
||||
case 'mm':
|
||||
@@ -29,41 +30,43 @@
|
||||
return result + (plural(number) ? 'miesiące' : 'miesięcy');
|
||||
case 'yy':
|
||||
return result + (plural(number) ? 'lata' : 'lat');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pl = moment.defineLocale('pl', {
|
||||
months : function (momentToFormat, format) {
|
||||
if (format === '') {
|
||||
// Hack: if format empty we know this is used to generate
|
||||
// RegExp by moment. Give then back both valid forms of months
|
||||
// in RegExp ready format.
|
||||
return '(' + monthsSubjective[momentToFormat.month()] + '|' + monthsNominative[momentToFormat.month()] + ')';
|
||||
} else if (/D MMMM/.test(format)) {
|
||||
return monthsSubjective[momentToFormat.month()];
|
||||
} else {
|
||||
return monthsNominative[momentToFormat.month()];
|
||||
}
|
||||
},
|
||||
monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'),
|
||||
weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'),
|
||||
weekdaysShort : 'nie_pon_wt_śr_czw_pt_sb'.split('_'),
|
||||
weekdaysMin : 'N_Pn_Wt_Śr_Cz_Pt_So'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd, D MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Dziś o] LT',
|
||||
nextDay: '[Jutro o] LT',
|
||||
nextWeek: '[W] dddd [o] LT',
|
||||
lastDay: '[Wczoraj o] LT',
|
||||
lastWeek: function () {
|
||||
switch (this.day()) {
|
||||
var pl = moment.defineLocale('pl', {
|
||||
months : function (momentToFormat, format) {
|
||||
if (!momentToFormat) {
|
||||
return monthsNominative;
|
||||
} else if (format === '') {
|
||||
// Hack: if format empty we know this is used to generate
|
||||
// RegExp by moment. Give then back both valid forms of months
|
||||
// in RegExp ready format.
|
||||
return '(' + monthsSubjective[momentToFormat.month()] + '|' + monthsNominative[momentToFormat.month()] + ')';
|
||||
} else if (/D MMMM/.test(format)) {
|
||||
return monthsSubjective[momentToFormat.month()];
|
||||
} else {
|
||||
return monthsNominative[momentToFormat.month()];
|
||||
}
|
||||
},
|
||||
monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'),
|
||||
weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'),
|
||||
weekdaysShort : 'ndz_pon_wt_śr_czw_pt_sob'.split('_'),
|
||||
weekdaysMin : 'Nd_Pn_Wt_Śr_Cz_Pt_So'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd, D MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Dziś o] LT',
|
||||
nextDay: '[Jutro o] LT',
|
||||
nextWeek: '[W] dddd [o] LT',
|
||||
lastDay: '[Wczoraj o] LT',
|
||||
lastWeek: function () {
|
||||
switch (this.day()) {
|
||||
case 0:
|
||||
return '[W zeszłą niedzielę o] LT';
|
||||
case 3:
|
||||
@@ -72,33 +75,33 @@
|
||||
return '[W zeszłą sobotę o] LT';
|
||||
default:
|
||||
return '[W zeszły] dddd [o] LT';
|
||||
}
|
||||
},
|
||||
sameElse: 'L'
|
||||
}
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'za %s',
|
||||
past : '%s temu',
|
||||
s : 'kilka sekund',
|
||||
m : translate,
|
||||
mm : translate,
|
||||
h : translate,
|
||||
hh : translate,
|
||||
d : '1 dzień',
|
||||
dd : '%d dni',
|
||||
M : 'miesiąc',
|
||||
MM : translate,
|
||||
y : 'rok',
|
||||
yy : translate
|
||||
},
|
||||
ordinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'za %s',
|
||||
past : '%s temu',
|
||||
s : 'kilka sekund',
|
||||
m : translate,
|
||||
mm : translate,
|
||||
h : translate,
|
||||
hh : translate,
|
||||
d : '1 dzień',
|
||||
dd : '%d dni',
|
||||
M : 'miesiąc',
|
||||
MM : translate,
|
||||
y : 'rok',
|
||||
yy : translate
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return pl;
|
||||
return pl;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,59 +1,61 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : brazilian portuguese (pt-br)
|
||||
//! locale : Portuguese (Brazil) [pt-br]
|
||||
//! author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var pt_br = moment.defineLocale('pt-br', {
|
||||
months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'),
|
||||
monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'),
|
||||
weekdays : 'Domingo_Segunda-Feira_Terça-Feira_Quarta-Feira_Quinta-Feira_Sexta-Feira_Sábado'.split('_'),
|
||||
weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'),
|
||||
weekdaysMin : 'Dom_2ª_3ª_4ª_5ª_6ª_Sáb'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD/MM/YYYY',
|
||||
LL : 'D [de] MMMM [de] YYYY',
|
||||
LLL : 'D [de] MMMM [de] YYYY [às] HH:mm',
|
||||
LLLL : 'dddd, D [de] MMMM [de] YYYY [às] HH:mm'
|
||||
var ptBr = moment.defineLocale('pt-br', {
|
||||
months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'),
|
||||
monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'),
|
||||
weekdays : 'Domingo_Segunda-feira_Terça-feira_Quarta-feira_Quinta-feira_Sexta-feira_Sábado'.split('_'),
|
||||
weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'),
|
||||
weekdaysMin : 'Do_2ª_3ª_4ª_5ª_6ª_Sá'.split('_'),
|
||||
weekdaysParseExact : true,
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD/MM/YYYY',
|
||||
LL : 'D [de] MMMM [de] YYYY',
|
||||
LLL : 'D [de] MMMM [de] YYYY [às] HH:mm',
|
||||
LLLL : 'dddd, D [de] MMMM [de] YYYY [às] HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Hoje às] LT',
|
||||
nextDay: '[Amanhã às] LT',
|
||||
nextWeek: 'dddd [às] LT',
|
||||
lastDay: '[Ontem às] LT',
|
||||
lastWeek: function () {
|
||||
return (this.day() === 0 || this.day() === 6) ?
|
||||
'[Último] dddd [às] LT' : // Saturday + Sunday
|
||||
'[Última] dddd [às] LT'; // Monday - Friday
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Hoje às] LT',
|
||||
nextDay: '[Amanhã às] LT',
|
||||
nextWeek: 'dddd [às] LT',
|
||||
lastDay: '[Ontem às] LT',
|
||||
lastWeek: function () {
|
||||
return (this.day() === 0 || this.day() === 6) ?
|
||||
'[Último] dddd [às] LT' : // Saturday + Sunday
|
||||
'[Última] dddd [às] LT'; // Monday - Friday
|
||||
},
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'em %s',
|
||||
past : '%s atrás',
|
||||
s : 'poucos segundos',
|
||||
m : 'um minuto',
|
||||
mm : '%d minutos',
|
||||
h : 'uma hora',
|
||||
hh : '%d horas',
|
||||
d : 'um dia',
|
||||
dd : '%d dias',
|
||||
M : 'um mês',
|
||||
MM : '%d meses',
|
||||
y : 'um ano',
|
||||
yy : '%d anos'
|
||||
},
|
||||
ordinalParse: /\d{1,2}º/,
|
||||
ordinal : '%dº'
|
||||
});
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'em %s',
|
||||
past : '%s atrás',
|
||||
s : 'poucos segundos',
|
||||
m : 'um minuto',
|
||||
mm : '%d minutos',
|
||||
h : 'uma hora',
|
||||
hh : '%d horas',
|
||||
d : 'um dia',
|
||||
dd : '%d dias',
|
||||
M : 'um mês',
|
||||
MM : '%d meses',
|
||||
y : 'um ano',
|
||||
yy : '%d anos'
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}º/,
|
||||
ordinal : '%dº'
|
||||
});
|
||||
|
||||
return pt_br;
|
||||
return ptBr;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,73 +1,75 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : romanian (ro)
|
||||
//! locale : Romanian [ro]
|
||||
//! author : Vlad Gurdiga : https://github.com/gurdiga
|
||||
//! author : Valentin Agachi : https://github.com/avaly
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
function relativeTimeWithPlural(number, withoutSuffix, key) {
|
||||
var format = {
|
||||
'mm': 'minute',
|
||||
'hh': 'ore',
|
||||
'dd': 'zile',
|
||||
'MM': 'luni',
|
||||
'yy': 'ani'
|
||||
},
|
||||
separator = ' ';
|
||||
if (number % 100 >= 20 || (number >= 100 && number % 100 === 0)) {
|
||||
separator = ' de ';
|
||||
}
|
||||
return number + separator + format[key];
|
||||
function relativeTimeWithPlural(number, withoutSuffix, key) {
|
||||
var format = {
|
||||
'mm': 'minute',
|
||||
'hh': 'ore',
|
||||
'dd': 'zile',
|
||||
'MM': 'luni',
|
||||
'yy': 'ani'
|
||||
},
|
||||
separator = ' ';
|
||||
if (number % 100 >= 20 || (number >= 100 && number % 100 === 0)) {
|
||||
separator = ' de ';
|
||||
}
|
||||
return number + separator + format[key];
|
||||
}
|
||||
|
||||
var ro = moment.defineLocale('ro', {
|
||||
months : 'ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie'.split('_'),
|
||||
monthsShort : 'ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.'.split('_'),
|
||||
weekdays : 'duminică_luni_marți_miercuri_joi_vineri_sâmbătă'.split('_'),
|
||||
weekdaysShort : 'Dum_Lun_Mar_Mie_Joi_Vin_Sâm'.split('_'),
|
||||
weekdaysMin : 'Du_Lu_Ma_Mi_Jo_Vi_Sâ'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'H:mm',
|
||||
LTS : 'H:mm:ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY H:mm',
|
||||
LLLL : 'dddd, D MMMM YYYY H:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[azi la] LT',
|
||||
nextDay: '[mâine la] LT',
|
||||
nextWeek: 'dddd [la] LT',
|
||||
lastDay: '[ieri la] LT',
|
||||
lastWeek: '[fosta] dddd [la] LT',
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'peste %s',
|
||||
past : '%s în urmă',
|
||||
s : 'câteva secunde',
|
||||
m : 'un minut',
|
||||
mm : relativeTimeWithPlural,
|
||||
h : 'o oră',
|
||||
hh : relativeTimeWithPlural,
|
||||
d : 'o zi',
|
||||
dd : relativeTimeWithPlural,
|
||||
M : 'o lună',
|
||||
MM : relativeTimeWithPlural,
|
||||
y : 'un an',
|
||||
yy : relativeTimeWithPlural
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
||||
}
|
||||
});
|
||||
var ro = moment.defineLocale('ro', {
|
||||
months : 'ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie'.split('_'),
|
||||
monthsShort : 'ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.'.split('_'),
|
||||
monthsParseExact: true,
|
||||
weekdays : 'duminică_luni_marți_miercuri_joi_vineri_sâmbătă'.split('_'),
|
||||
weekdaysShort : 'Dum_Lun_Mar_Mie_Joi_Vin_Sâm'.split('_'),
|
||||
weekdaysMin : 'Du_Lu_Ma_Mi_Jo_Vi_Sâ'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'H:mm',
|
||||
LTS : 'H:mm:ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY H:mm',
|
||||
LLLL : 'dddd, D MMMM YYYY H:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[azi la] LT',
|
||||
nextDay: '[mâine la] LT',
|
||||
nextWeek: 'dddd [la] LT',
|
||||
lastDay: '[ieri la] LT',
|
||||
lastWeek: '[fosta] dddd [la] LT',
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'peste %s',
|
||||
past : '%s în urmă',
|
||||
s : 'câteva secunde',
|
||||
m : 'un minut',
|
||||
mm : relativeTimeWithPlural,
|
||||
h : 'o oră',
|
||||
hh : relativeTimeWithPlural,
|
||||
d : 'o zi',
|
||||
dd : relativeTimeWithPlural,
|
||||
M : 'o lună',
|
||||
MM : relativeTimeWithPlural,
|
||||
y : 'un an',
|
||||
yy : relativeTimeWithPlural
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return ro;
|
||||
return ro;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,90 +1,110 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : russian (ru)
|
||||
//! locale : Russian [ru]
|
||||
//! author : Viktorminator : https://github.com/Viktorminator
|
||||
//! Author : Menelion Elensúle : https://github.com/Oire
|
||||
//! author : Коренберг Марк : https://github.com/socketpair
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
function plural(word, num) {
|
||||
var forms = word.split('_');
|
||||
return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]);
|
||||
function plural(word, num) {
|
||||
var forms = word.split('_');
|
||||
return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]);
|
||||
}
|
||||
function relativeTimeWithPlural(number, withoutSuffix, key) {
|
||||
var format = {
|
||||
'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут',
|
||||
'hh': 'час_часа_часов',
|
||||
'dd': 'день_дня_дней',
|
||||
'MM': 'месяц_месяца_месяцев',
|
||||
'yy': 'год_года_лет'
|
||||
};
|
||||
if (key === 'm') {
|
||||
return withoutSuffix ? 'минута' : 'минуту';
|
||||
}
|
||||
function relativeTimeWithPlural(number, withoutSuffix, key) {
|
||||
var format = {
|
||||
'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут',
|
||||
'hh': 'час_часа_часов',
|
||||
'dd': 'день_дня_дней',
|
||||
'MM': 'месяц_месяца_месяцев',
|
||||
'yy': 'год_года_лет'
|
||||
};
|
||||
if (key === 'm') {
|
||||
return withoutSuffix ? 'минута' : 'минуту';
|
||||
}
|
||||
else {
|
||||
return number + ' ' + plural(format[key], +number);
|
||||
}
|
||||
}
|
||||
function monthsCaseReplace(m, format) {
|
||||
var months = {
|
||||
'nominative': 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'),
|
||||
'accusative': 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_')
|
||||
},
|
||||
nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ?
|
||||
'accusative' :
|
||||
'nominative';
|
||||
return months[nounCase][m.month()];
|
||||
}
|
||||
function monthsShortCaseReplace(m, format) {
|
||||
var monthsShort = {
|
||||
'nominative': 'янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек'.split('_'),
|
||||
'accusative': 'янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек'.split('_')
|
||||
},
|
||||
nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ?
|
||||
'accusative' :
|
||||
'nominative';
|
||||
return monthsShort[nounCase][m.month()];
|
||||
}
|
||||
function weekdaysCaseReplace(m, format) {
|
||||
var weekdays = {
|
||||
'nominative': 'воскресенье_понедельник_вторник_среда_четверг_пятница_суббота'.split('_'),
|
||||
'accusative': 'воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу'.split('_')
|
||||
},
|
||||
nounCase = (/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/).test(format) ?
|
||||
'accusative' :
|
||||
'nominative';
|
||||
return weekdays[nounCase][m.day()];
|
||||
else {
|
||||
return number + ' ' + plural(format[key], +number);
|
||||
}
|
||||
}
|
||||
var monthsParse = [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[йя]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i];
|
||||
|
||||
var ru = moment.defineLocale('ru', {
|
||||
months : monthsCaseReplace,
|
||||
monthsShort : monthsShortCaseReplace,
|
||||
weekdays : weekdaysCaseReplace,
|
||||
weekdaysShort : 'вс_пн_вт_ср_чт_пт_сб'.split('_'),
|
||||
weekdaysMin : 'вс_пн_вт_ср_чт_пт_сб'.split('_'),
|
||||
monthsParse : [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[й|я]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i],
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D MMMM YYYY г.',
|
||||
LLL : 'D MMMM YYYY г., HH:mm',
|
||||
LLLL : 'dddd, D MMMM YYYY г., HH:mm'
|
||||
// http://new.gramota.ru/spravka/rules/139-prop : § 103
|
||||
// Сокращения месяцев: http://new.gramota.ru/spravka/buro/search-answer?s=242637
|
||||
// CLDR data: http://www.unicode.org/cldr/charts/28/summary/ru.html#1753
|
||||
var ru = moment.defineLocale('ru', {
|
||||
months : {
|
||||
format: 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_'),
|
||||
standalone: 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_')
|
||||
},
|
||||
monthsShort : {
|
||||
// по CLDR именно "июл." и "июн.", но какой смысл менять букву на точку ?
|
||||
format: 'янв._февр._мар._апр._мая_июня_июля_авг._сент._окт._нояб._дек.'.split('_'),
|
||||
standalone: 'янв._февр._март_апр._май_июнь_июль_авг._сент._окт._нояб._дек.'.split('_')
|
||||
},
|
||||
weekdays : {
|
||||
standalone: 'воскресенье_понедельник_вторник_среда_четверг_пятница_суббота'.split('_'),
|
||||
format: 'воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу'.split('_'),
|
||||
isFormat: /\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/
|
||||
},
|
||||
weekdaysShort : 'вс_пн_вт_ср_чт_пт_сб'.split('_'),
|
||||
weekdaysMin : 'вс_пн_вт_ср_чт_пт_сб'.split('_'),
|
||||
monthsParse : monthsParse,
|
||||
longMonthsParse : monthsParse,
|
||||
shortMonthsParse : monthsParse,
|
||||
|
||||
// полные названия с падежами, по три буквы, для некоторых, по 4 буквы, сокращения с точкой и без точки
|
||||
monthsRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,
|
||||
|
||||
// копия предыдущего
|
||||
monthsShortRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,
|
||||
|
||||
// полные названия с падежами
|
||||
monthsStrictRegex: /^(январ[яь]|феврал[яь]|марта?|апрел[яь]|ма[яй]|июн[яь]|июл[яь]|августа?|сентябр[яь]|октябр[яь]|ноябр[яь]|декабр[яь])/i,
|
||||
|
||||
// Выражение, которое соотвествует только сокращённым формам
|
||||
monthsShortStrictRegex: /^(янв\.|февр?\.|мар[т.]|апр\.|ма[яй]|июн[ья.]|июл[ья.]|авг\.|сент?\.|окт\.|нояб?\.|дек\.)/i,
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD.MM.YYYY',
|
||||
LL : 'D MMMM YYYY г.',
|
||||
LLL : 'D MMMM YYYY г., HH:mm',
|
||||
LLLL : 'dddd, D MMMM YYYY г., HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Сегодня в] LT',
|
||||
nextDay: '[Завтра в] LT',
|
||||
lastDay: '[Вчера в] LT',
|
||||
nextWeek: function (now) {
|
||||
if (now.week() !== this.week()) {
|
||||
switch (this.day()) {
|
||||
case 0:
|
||||
return '[В следующее] dddd [в] LT';
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
return '[В следующий] dddd [в] LT';
|
||||
case 3:
|
||||
case 5:
|
||||
case 6:
|
||||
return '[В следующую] dddd [в] LT';
|
||||
}
|
||||
} else {
|
||||
if (this.day() === 2) {
|
||||
return '[Во] dddd [в] LT';
|
||||
} else {
|
||||
return '[В] dddd [в] LT';
|
||||
}
|
||||
}
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Сегодня в] LT',
|
||||
nextDay: '[Завтра в] LT',
|
||||
lastDay: '[Вчера в] LT',
|
||||
nextWeek: function () {
|
||||
return this.day() === 2 ? '[Во] dddd [в] LT' : '[В] dddd [в] LT';
|
||||
},
|
||||
lastWeek: function (now) {
|
||||
if (now.week() !== this.week()) {
|
||||
switch (this.day()) {
|
||||
lastWeek: function (now) {
|
||||
if (now.week() !== this.week()) {
|
||||
switch (this.day()) {
|
||||
case 0:
|
||||
return '[В прошлое] dddd [в] LT';
|
||||
case 1:
|
||||
@@ -95,50 +115,50 @@
|
||||
case 5:
|
||||
case 6:
|
||||
return '[В прошлую] dddd [в] LT';
|
||||
}
|
||||
} else {
|
||||
if (this.day() === 2) {
|
||||
return '[Во] dddd [в] LT';
|
||||
} else {
|
||||
return '[В] dddd [в] LT';
|
||||
}
|
||||
}
|
||||
},
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'через %s',
|
||||
past : '%s назад',
|
||||
s : 'несколько секунд',
|
||||
m : relativeTimeWithPlural,
|
||||
mm : relativeTimeWithPlural,
|
||||
h : 'час',
|
||||
hh : relativeTimeWithPlural,
|
||||
d : 'день',
|
||||
dd : relativeTimeWithPlural,
|
||||
M : 'месяц',
|
||||
MM : relativeTimeWithPlural,
|
||||
y : 'год',
|
||||
yy : relativeTimeWithPlural
|
||||
},
|
||||
meridiemParse: /ночи|утра|дня|вечера/i,
|
||||
isPM : function (input) {
|
||||
return /^(дня|вечера)$/.test(input);
|
||||
},
|
||||
meridiem : function (hour, minute, isLower) {
|
||||
if (hour < 4) {
|
||||
return 'ночи';
|
||||
} else if (hour < 12) {
|
||||
return 'утра';
|
||||
} else if (hour < 17) {
|
||||
return 'дня';
|
||||
} else {
|
||||
return 'вечера';
|
||||
if (this.day() === 2) {
|
||||
return '[Во] dddd [в] LT';
|
||||
} else {
|
||||
return '[В] dddd [в] LT';
|
||||
}
|
||||
}
|
||||
},
|
||||
ordinalParse: /\d{1,2}-(й|го|я)/,
|
||||
ordinal: function (number, period) {
|
||||
switch (period) {
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'через %s',
|
||||
past : '%s назад',
|
||||
s : 'несколько секунд',
|
||||
m : relativeTimeWithPlural,
|
||||
mm : relativeTimeWithPlural,
|
||||
h : 'час',
|
||||
hh : relativeTimeWithPlural,
|
||||
d : 'день',
|
||||
dd : relativeTimeWithPlural,
|
||||
M : 'месяц',
|
||||
MM : relativeTimeWithPlural,
|
||||
y : 'год',
|
||||
yy : relativeTimeWithPlural
|
||||
},
|
||||
meridiemParse: /ночи|утра|дня|вечера/i,
|
||||
isPM : function (input) {
|
||||
return /^(дня|вечера)$/.test(input);
|
||||
},
|
||||
meridiem : function (hour, minute, isLower) {
|
||||
if (hour < 4) {
|
||||
return 'ночи';
|
||||
} else if (hour < 12) {
|
||||
return 'утра';
|
||||
} else if (hour < 17) {
|
||||
return 'дня';
|
||||
} else {
|
||||
return 'вечера';
|
||||
}
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}-(й|го|я)/,
|
||||
ordinal: function (number, period) {
|
||||
switch (period) {
|
||||
case 'M':
|
||||
case 'd':
|
||||
case 'DDD':
|
||||
@@ -150,14 +170,14 @@
|
||||
return number + '-я';
|
||||
default:
|
||||
return number;
|
||||
}
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
||||
}
|
||||
});
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return ru;
|
||||
return ru;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,107 +1,110 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : Serbian-cyrillic (sr-cyrl)
|
||||
//! locale : Serbian [sr]
|
||||
//! author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var translator = {
|
||||
words: { //Different grammatical cases
|
||||
m: ['један минут', 'једне минуте'],
|
||||
mm: ['минут', 'минуте', 'минута'],
|
||||
h: ['један сат', 'једног сата'],
|
||||
hh: ['сат', 'сата', 'сати'],
|
||||
dd: ['дан', 'дана', 'дана'],
|
||||
MM: ['месец', 'месеца', 'месеци'],
|
||||
yy: ['година', 'године', 'година']
|
||||
},
|
||||
correctGrammaticalCase: function (number, wordKey) {
|
||||
return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]);
|
||||
},
|
||||
translate: function (number, withoutSuffix, key) {
|
||||
var wordKey = translator.words[key];
|
||||
if (key.length === 1) {
|
||||
return withoutSuffix ? wordKey[0] : wordKey[1];
|
||||
} else {
|
||||
return number + ' ' + translator.correctGrammaticalCase(number, wordKey);
|
||||
}
|
||||
var translator = {
|
||||
words: { //Different grammatical cases
|
||||
m: ['jedan minut', 'jedne minute'],
|
||||
mm: ['minut', 'minute', 'minuta'],
|
||||
h: ['jedan sat', 'jednog sata'],
|
||||
hh: ['sat', 'sata', 'sati'],
|
||||
dd: ['dan', 'dana', 'dana'],
|
||||
MM: ['mesec', 'meseca', 'meseci'],
|
||||
yy: ['godina', 'godine', 'godina']
|
||||
},
|
||||
correctGrammaticalCase: function (number, wordKey) {
|
||||
return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]);
|
||||
},
|
||||
translate: function (number, withoutSuffix, key) {
|
||||
var wordKey = translator.words[key];
|
||||
if (key.length === 1) {
|
||||
return withoutSuffix ? wordKey[0] : wordKey[1];
|
||||
} else {
|
||||
return number + ' ' + translator.correctGrammaticalCase(number, wordKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var sr_cyrl = moment.defineLocale('sr-cyrl', {
|
||||
months: ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'],
|
||||
monthsShort: ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'],
|
||||
weekdays: ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'],
|
||||
weekdaysShort: ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'],
|
||||
weekdaysMin: ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'],
|
||||
longDateFormat: {
|
||||
LT: 'H:mm',
|
||||
LTS : 'H:mm:ss',
|
||||
L: 'DD. MM. YYYY',
|
||||
LL: 'D. MMMM YYYY',
|
||||
LLL: 'D. MMMM YYYY H:mm',
|
||||
LLLL: 'dddd, D. MMMM YYYY H:mm'
|
||||
},
|
||||
calendar: {
|
||||
sameDay: '[данас у] LT',
|
||||
nextDay: '[сутра у] LT',
|
||||
nextWeek: function () {
|
||||
switch (this.day()) {
|
||||
var sr = moment.defineLocale('sr', {
|
||||
months: 'januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar'.split('_'),
|
||||
monthsShort: 'jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.'.split('_'),
|
||||
monthsParseExact: true,
|
||||
weekdays: 'nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota'.split('_'),
|
||||
weekdaysShort: 'ned._pon._uto._sre._čet._pet._sub.'.split('_'),
|
||||
weekdaysMin: 'ne_po_ut_sr_če_pe_su'.split('_'),
|
||||
weekdaysParseExact : true,
|
||||
longDateFormat: {
|
||||
LT: 'H:mm',
|
||||
LTS : 'H:mm:ss',
|
||||
L: 'DD.MM.YYYY',
|
||||
LL: 'D. MMMM YYYY',
|
||||
LLL: 'D. MMMM YYYY H:mm',
|
||||
LLLL: 'dddd, D. MMMM YYYY H:mm'
|
||||
},
|
||||
calendar: {
|
||||
sameDay: '[danas u] LT',
|
||||
nextDay: '[sutra u] LT',
|
||||
nextWeek: function () {
|
||||
switch (this.day()) {
|
||||
case 0:
|
||||
return '[у] [недељу] [у] LT';
|
||||
return '[u] [nedelju] [u] LT';
|
||||
case 3:
|
||||
return '[у] [среду] [у] LT';
|
||||
return '[u] [sredu] [u] LT';
|
||||
case 6:
|
||||
return '[у] [суботу] [у] LT';
|
||||
return '[u] [subotu] [u] LT';
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
case 5:
|
||||
return '[у] dddd [у] LT';
|
||||
}
|
||||
},
|
||||
lastDay : '[јуче у] LT',
|
||||
lastWeek : function () {
|
||||
var lastWeekDays = [
|
||||
'[прошле] [недеље] [у] LT',
|
||||
'[прошлог] [понедељка] [у] LT',
|
||||
'[прошлог] [уторка] [у] LT',
|
||||
'[прошле] [среде] [у] LT',
|
||||
'[прошлог] [четвртка] [у] LT',
|
||||
'[прошлог] [петка] [у] LT',
|
||||
'[прошле] [суботе] [у] LT'
|
||||
];
|
||||
return lastWeekDays[this.day()];
|
||||
},
|
||||
sameElse : 'L'
|
||||
return '[u] dddd [u] LT';
|
||||
}
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'за %s',
|
||||
past : 'пре %s',
|
||||
s : 'неколико секунди',
|
||||
m : translator.translate,
|
||||
mm : translator.translate,
|
||||
h : translator.translate,
|
||||
hh : translator.translate,
|
||||
d : 'дан',
|
||||
dd : translator.translate,
|
||||
M : 'месец',
|
||||
MM : translator.translate,
|
||||
y : 'годину',
|
||||
yy : translator.translate
|
||||
lastDay : '[juče u] LT',
|
||||
lastWeek : function () {
|
||||
var lastWeekDays = [
|
||||
'[prošle] [nedelje] [u] LT',
|
||||
'[prošlog] [ponedeljka] [u] LT',
|
||||
'[prošlog] [utorka] [u] LT',
|
||||
'[prošle] [srede] [u] LT',
|
||||
'[prošlog] [četvrtka] [u] LT',
|
||||
'[prošlog] [petka] [u] LT',
|
||||
'[prošle] [subote] [u] LT'
|
||||
];
|
||||
return lastWeekDays[this.day()];
|
||||
},
|
||||
ordinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
||||
}
|
||||
});
|
||||
sameElse : 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'za %s',
|
||||
past : 'pre %s',
|
||||
s : 'nekoliko sekundi',
|
||||
m : translator.translate,
|
||||
mm : translator.translate,
|
||||
h : translator.translate,
|
||||
hh : translator.translate,
|
||||
d : 'dan',
|
||||
dd : translator.translate,
|
||||
M : 'mesec',
|
||||
MM : translator.translate,
|
||||
y : 'godinu',
|
||||
yy : translator.translate
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}\./,
|
||||
ordinal : '%d.',
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return sr_cyrl;
|
||||
return sr;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,66 +1,69 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : swedish (sv)
|
||||
//! locale : Swedish [sv]
|
||||
//! author : Jens Alm : https://github.com/ulmus
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var sv = moment.defineLocale('sv', {
|
||||
months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'),
|
||||
monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
|
||||
weekdays : 'söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag'.split('_'),
|
||||
weekdaysShort : 'sön_mån_tis_ons_tor_fre_lör'.split('_'),
|
||||
weekdaysMin : 'sö_må_ti_on_to_fr_lö'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'YYYY-MM-DD',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd D MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Idag] LT',
|
||||
nextDay: '[Imorgon] LT',
|
||||
lastDay: '[Igår] LT',
|
||||
nextWeek: '[På] dddd LT',
|
||||
lastWeek: '[I] dddd[s] LT',
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'om %s',
|
||||
past : 'för %s sedan',
|
||||
s : 'några sekunder',
|
||||
m : 'en minut',
|
||||
mm : '%d minuter',
|
||||
h : 'en timme',
|
||||
hh : '%d timmar',
|
||||
d : 'en dag',
|
||||
dd : '%d dagar',
|
||||
M : 'en månad',
|
||||
MM : '%d månader',
|
||||
y : 'ett år',
|
||||
yy : '%d år'
|
||||
},
|
||||
ordinalParse: /\d{1,2}(e|a)/,
|
||||
ordinal : function (number) {
|
||||
var b = number % 10,
|
||||
output = (~~(number % 100 / 10) === 1) ? 'e' :
|
||||
(b === 1) ? 'a' :
|
||||
(b === 2) ? 'a' :
|
||||
(b === 3) ? 'e' : 'e';
|
||||
return number + output;
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
var sv = moment.defineLocale('sv', {
|
||||
months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'),
|
||||
monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
|
||||
weekdays : 'söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag'.split('_'),
|
||||
weekdaysShort : 'sön_mån_tis_ons_tor_fre_lör'.split('_'),
|
||||
weekdaysMin : 'sö_må_ti_on_to_fr_lö'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'YYYY-MM-DD',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY [kl.] HH:mm',
|
||||
LLLL : 'dddd D MMMM YYYY [kl.] HH:mm',
|
||||
lll : 'D MMM YYYY HH:mm',
|
||||
llll : 'ddd D MMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay: '[Idag] LT',
|
||||
nextDay: '[Imorgon] LT',
|
||||
lastDay: '[Igår] LT',
|
||||
nextWeek: '[På] dddd LT',
|
||||
lastWeek: '[I] dddd[s] LT',
|
||||
sameElse: 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'om %s',
|
||||
past : 'för %s sedan',
|
||||
s : 'några sekunder',
|
||||
m : 'en minut',
|
||||
mm : '%d minuter',
|
||||
h : 'en timme',
|
||||
hh : '%d timmar',
|
||||
d : 'en dag',
|
||||
dd : '%d dagar',
|
||||
M : 'en månad',
|
||||
MM : '%d månader',
|
||||
y : 'ett år',
|
||||
yy : '%d år'
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}(e|a)/,
|
||||
ordinal : function (number) {
|
||||
var b = number % 10,
|
||||
output = (~~(number % 100 / 10) === 1) ? 'e' :
|
||||
(b === 1) ? 'a' :
|
||||
(b === 2) ? 'a' :
|
||||
(b === 3) ? 'e' : 'e';
|
||||
return number + output;
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return sv;
|
||||
return sv;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -1,91 +1,76 @@
|
||||
//! moment.js locale configuration
|
||||
//! locale : chinese (zh-cn)
|
||||
//! locale : Chinese (China) [zh-cn]
|
||||
//! author : suupic : https://github.com/suupic
|
||||
//! author : Zeno Zeng : https://github.com/zenozeng
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['moment'], factory) :
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
&& typeof require === 'function' ? factory(require('../moment')) :
|
||||
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
|
||||
factory(global.moment)
|
||||
}(this, function (moment) { 'use strict';
|
||||
}(this, (function (moment) { 'use strict';
|
||||
|
||||
|
||||
var zh_cn = moment.defineLocale('zh-cn', {
|
||||
months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
|
||||
monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
|
||||
weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
|
||||
weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'),
|
||||
weekdaysMin : '日_一_二_三_四_五_六'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'Ah点mm分',
|
||||
LTS : 'Ah点m分s秒',
|
||||
L : 'YYYY-MM-DD',
|
||||
LL : 'YYYY年MMMD日',
|
||||
LLL : 'YYYY年MMMD日Ah点mm分',
|
||||
LLLL : 'YYYY年MMMD日ddddAh点mm分',
|
||||
l : 'YYYY-MM-DD',
|
||||
ll : 'YYYY年MMMD日',
|
||||
lll : 'YYYY年MMMD日Ah点mm分',
|
||||
llll : 'YYYY年MMMD日ddddAh点mm分'
|
||||
},
|
||||
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
|
||||
meridiemHour: function (hour, meridiem) {
|
||||
if (hour === 12) {
|
||||
hour = 0;
|
||||
}
|
||||
if (meridiem === '凌晨' || meridiem === '早上' ||
|
||||
meridiem === '上午') {
|
||||
return hour;
|
||||
} else if (meridiem === '下午' || meridiem === '晚上') {
|
||||
return hour + 12;
|
||||
} else {
|
||||
// '中午'
|
||||
return hour >= 11 ? hour : hour + 12;
|
||||
}
|
||||
},
|
||||
meridiem : function (hour, minute, isLower) {
|
||||
var hm = hour * 100 + minute;
|
||||
if (hm < 600) {
|
||||
return '凌晨';
|
||||
} else if (hm < 900) {
|
||||
return '早上';
|
||||
} else if (hm < 1130) {
|
||||
return '上午';
|
||||
} else if (hm < 1230) {
|
||||
return '中午';
|
||||
} else if (hm < 1800) {
|
||||
return '下午';
|
||||
} else {
|
||||
return '晚上';
|
||||
}
|
||||
},
|
||||
calendar : {
|
||||
sameDay : function () {
|
||||
return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT';
|
||||
},
|
||||
nextDay : function () {
|
||||
return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT';
|
||||
},
|
||||
lastDay : function () {
|
||||
return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT';
|
||||
},
|
||||
nextWeek : function () {
|
||||
var startOfWeek, prefix;
|
||||
startOfWeek = moment().startOf('week');
|
||||
prefix = this.unix() - startOfWeek.unix() >= 7 * 24 * 3600 ? '[下]' : '[本]';
|
||||
return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
|
||||
},
|
||||
lastWeek : function () {
|
||||
var startOfWeek, prefix;
|
||||
startOfWeek = moment().startOf('week');
|
||||
prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]';
|
||||
return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
|
||||
},
|
||||
sameElse : 'LL'
|
||||
},
|
||||
ordinalParse: /\d{1,2}(日|月|周)/,
|
||||
ordinal : function (number, period) {
|
||||
switch (period) {
|
||||
var zhCn = moment.defineLocale('zh-cn', {
|
||||
months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
|
||||
monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
|
||||
weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
|
||||
weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'),
|
||||
weekdaysMin : '日_一_二_三_四_五_六'.split('_'),
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'YYYY年MMMD日',
|
||||
LL : 'YYYY年MMMD日',
|
||||
LLL : 'YYYY年MMMD日Ah点mm分',
|
||||
LLLL : 'YYYY年MMMD日ddddAh点mm分',
|
||||
l : 'YYYY年MMMD日',
|
||||
ll : 'YYYY年MMMD日',
|
||||
lll : 'YYYY年MMMD日 HH:mm',
|
||||
llll : 'YYYY年MMMD日dddd HH:mm'
|
||||
},
|
||||
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
|
||||
meridiemHour: function (hour, meridiem) {
|
||||
if (hour === 12) {
|
||||
hour = 0;
|
||||
}
|
||||
if (meridiem === '凌晨' || meridiem === '早上' ||
|
||||
meridiem === '上午') {
|
||||
return hour;
|
||||
} else if (meridiem === '下午' || meridiem === '晚上') {
|
||||
return hour + 12;
|
||||
} else {
|
||||
// '中午'
|
||||
return hour >= 11 ? hour : hour + 12;
|
||||
}
|
||||
},
|
||||
meridiem : function (hour, minute, isLower) {
|
||||
var hm = hour * 100 + minute;
|
||||
if (hm < 600) {
|
||||
return '凌晨';
|
||||
} else if (hm < 900) {
|
||||
return '早上';
|
||||
} else if (hm < 1130) {
|
||||
return '上午';
|
||||
} else if (hm < 1230) {
|
||||
return '中午';
|
||||
} else if (hm < 1800) {
|
||||
return '下午';
|
||||
} else {
|
||||
return '晚上';
|
||||
}
|
||||
},
|
||||
calendar : {
|
||||
sameDay : '[今天]LT',
|
||||
nextDay : '[明天]LT',
|
||||
nextWeek : '[下]ddddLT',
|
||||
lastDay : '[昨天]LT',
|
||||
lastWeek : '[上]ddddLT',
|
||||
sameElse : 'L'
|
||||
},
|
||||
dayOfMonthOrdinalParse: /\d{1,2}(日|月|周)/,
|
||||
ordinal : function (number, period) {
|
||||
switch (period) {
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'DDD':
|
||||
@@ -97,30 +82,30 @@
|
||||
return number + '周';
|
||||
default:
|
||||
return number;
|
||||
}
|
||||
},
|
||||
relativeTime : {
|
||||
future : '%s内',
|
||||
past : '%s前',
|
||||
s : '几秒',
|
||||
m : '1 分钟',
|
||||
mm : '%d 分钟',
|
||||
h : '1 小时',
|
||||
hh : '%d 小时',
|
||||
d : '1 天',
|
||||
dd : '%d 天',
|
||||
M : '1 个月',
|
||||
MM : '%d 个月',
|
||||
y : '1 年',
|
||||
yy : '%d 年'
|
||||
},
|
||||
week : {
|
||||
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
},
|
||||
relativeTime : {
|
||||
future : '%s内',
|
||||
past : '%s前',
|
||||
s : '几秒',
|
||||
m : '1 分钟',
|
||||
mm : '%d 分钟',
|
||||
h : '1 小时',
|
||||
hh : '%d 小时',
|
||||
d : '1 天',
|
||||
dd : '%d 天',
|
||||
M : '1 个月',
|
||||
MM : '%d 个月',
|
||||
y : '1 年',
|
||||
yy : '%d 年'
|
||||
},
|
||||
week : {
|
||||
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
return zh_cn;
|
||||
return zhCn;
|
||||
|
||||
}));
|
||||
})));
|
||||
|
||||
@@ -50,7 +50,8 @@ legend,
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.hover-button {
|
||||
.hover-button,
|
||||
.fileControls a:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@@ -81,7 +82,8 @@ legend,
|
||||
|
||||
.max-speed-input-clear,
|
||||
.max-speed-input-clear:hover,
|
||||
.nav-tabs>li>a:hover {
|
||||
.nav-tabs>li>a:hover,
|
||||
.fileControls a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@@ -175,7 +177,7 @@ tbody .caret {
|
||||
color: #D6D6D6;
|
||||
}
|
||||
|
||||
td.name .name-ratings span,
|
||||
td.name .name-icons span,
|
||||
.navbar-nav .open .dropdown-menu>li>a,
|
||||
.dropdown-header,
|
||||
#modal-help small,
|
||||
|
||||
@@ -52,8 +52,8 @@ h2 {
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #cccccc;
|
||||
margin-left: -150px;
|
||||
margin-top: 4px;
|
||||
margin-left: -220px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.navbar-collapse.in .dropdown-menu a,
|
||||
@@ -617,6 +617,7 @@ td.name .row-wrap-text {
|
||||
}
|
||||
|
||||
.queue-table td.name .name-options small,
|
||||
.queue-table td.name .direct-unpack,
|
||||
.queue-item-password {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -626,7 +627,7 @@ td.name .row-wrap-text {
|
||||
}
|
||||
|
||||
.queue-table td.name:hover .row-wrap-text {
|
||||
max-width: calc(100% - 85px);
|
||||
max-width: calc(100% - 125px);
|
||||
/* Change for each size! */
|
||||
}
|
||||
|
||||
@@ -648,18 +649,18 @@ td.name .row-wrap-text {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
td.name .name-ratings {
|
||||
td.name .name-icons {
|
||||
display: inline;
|
||||
margin-left: 5px;
|
||||
color: black !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.queue-table td.name:hover .name-ratings {
|
||||
.queue-table td.name:hover .name-icons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
td.name .name-ratings .glyphicon {
|
||||
td.name .name-icons .glyphicon {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
@@ -769,6 +770,35 @@ tr.queue-item>td:first-child>a {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.item-files-table tr .fileControls{
|
||||
float:right;
|
||||
display:none;
|
||||
}
|
||||
|
||||
.item-files-table tr.files-sortable:hover .fileControls{
|
||||
float:right;
|
||||
display:block;
|
||||
margin-left:5px;
|
||||
}
|
||||
|
||||
.progress .progress-bar .fileDetails {
|
||||
display:inline;
|
||||
text-align: left;
|
||||
margin-left: 70px;
|
||||
line-height: 25px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
font-size: 12px;
|
||||
color: #404040;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.progress .progress-bar .fileDetails>span {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.progress strong {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -1035,7 +1065,7 @@ tr.queue-item>td:first-child>a {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.history-ratings .name-ratings {
|
||||
.history-ratings .name-icons {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
@@ -1623,6 +1653,11 @@ input[name="nzbURL"] {
|
||||
|
||||
#modal-item-files .item-files-table .progress small {
|
||||
color: #727272 !important;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#modal-item-files .item-files-table tr.files-sortable:hover .progress small {
|
||||
display:none;
|
||||
}
|
||||
|
||||
#modal-item-files .item-files-table td {
|
||||
@@ -1810,7 +1845,7 @@ input[name="nzbURL"] {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
td.name .name-ratings {
|
||||
td.name .name-icons {
|
||||
margin-left: 0px;
|
||||
margin-right: -5px;
|
||||
display: block;
|
||||
@@ -1857,6 +1892,11 @@ input[name="nzbURL"] {
|
||||
.queue .sortable-placeholder td {
|
||||
padding: 9px 0px 8px !important;
|
||||
}
|
||||
|
||||
.queue-table .buttonMoveToBottom,
|
||||
.queue-table .buttonMoveToTop {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-height: 800px) {
|
||||
@@ -1921,49 +1961,6 @@ input[name="nzbURL"] {
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
SPECIAL FOR FIREFOX
|
||||
It uses very high CPU for anything animated (Sep 2015)
|
||||
Disable animations on progress-bar and make the History-'processing' a block animation
|
||||
Can be removed if it's performance gets better in the future..
|
||||
***/
|
||||
@supports (-moz-transform: translate(0, 0)) {
|
||||
.progress-bar {
|
||||
transform: none !important;
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
@keyframes stretchdelay {
|
||||
0%, 60% {
|
||||
transform: scaleY(0.4);
|
||||
}
|
||||
|
||||
61%, 100% {
|
||||
transform: scaleY(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
.processing-download > div {
|
||||
animation: stretchdelay 2s infinite linear;
|
||||
}
|
||||
|
||||
.processing-download .loader-bar-two {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.processing-download .loader-bar-three {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.processing-download .loader-bar-four {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.queue-table td.name input {
|
||||
margin-left: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
Bootstrap overwrites
|
||||
|
||||
@@ -132,6 +132,11 @@ h2 {
|
||||
max-width: calc(100% - 45px);
|
||||
}
|
||||
|
||||
.queue-table .buttonMoveToBottom,
|
||||
.queue-table .buttonMoveToTop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
tr.queue-item>td:first-child>a {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
Plush for SABnzbd 0.6.x | Feb. 21 2010
|
||||
assembled by pairofdimes - see LICENSE-CC.txt
|
||||
http://forums.sabnzbd.org contributions welcome
|
||||
https://forums.sabnzbd.org contributions welcome
|
||||
|
||||
======================
|
||||
THANKS TO CONTRIBUTORS
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="${path}staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="${path}staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="${path}staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${path}staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${path}staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="${path}staticcfg/ico/android-192x192.png" />
|
||||
|
||||
|
||||
<script type="text/javascript" src="${path}static/javascripts/lib.js?$version"></script>
|
||||
|
||||
#if $pane=="Main"#
|
||||
@@ -102,7 +102,6 @@
|
||||
<span id="warning_box"><b><a href="${path}status/#tabs-warnings" id="last_warning" title="#echo $last_warning.replace("\n"," ").replace('"',"'") #"><span id="have_warnings">$have_warnings</span> $T('warnings')</a></b></span>
|
||||
#if $pane=="Main"#
|
||||
#if $new_release#⋅ <a href="$new_rel_url" id="new_release" target="_blank">$T('Plush-updateAvailable').replace(' ',' ')</a>#end if#
|
||||
#if $warning#⋅ <a id="warning_message">$warning.replace(' ',' ')</a>#end if#
|
||||
#end if#
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<div id="help_modal">
|
||||
<table>
|
||||
<tr><td><strong>$T('menu-wiki'):</strong></td><td><a href="$helpuri$help_uri" target="_blank">$helpuri$help_uri</a></td></tr>
|
||||
<tr><td><strong>$T('menu-forums'):</strong></td><td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td></tr>
|
||||
<tr><td><strong>$T('menu-irc'):</strong></td><td><a href="http://www.sabnzbd.org/live-chat/" target="_blank">http://www.sabnzbd.org/live-chat/</a></td></tr>
|
||||
<tr><td><strong>$T('menu-forums'):</strong></td><td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td></tr>
|
||||
<tr><td><strong>$T('menu-irc'):</strong></td><td><a href="https://sabnzbd.org/live-chat.html" target="_blank">https://sabnzbd.org/live-chat.html</a></td></tr>
|
||||
</table>
|
||||
<div class="sabnzbd_logo main_sprite_container sprite_sabnzbdplus_logo"></div>
|
||||
<p><strong>SABnzbd $T('version'):</strong> $version</p>
|
||||
@@ -84,10 +84,10 @@ $T('Plush-containerWidth'):
|
||||
<div id="add_nzb_hr"><hr></div>
|
||||
<i>$T('pp'):</i>
|
||||
<table id="add_nzb_pp">
|
||||
#if $cat_list#
|
||||
#if $categories#
|
||||
<tr><td style="text-align:right">$T('category'):</td>
|
||||
<td><select id="addID_cat" name="cat">
|
||||
#for $ct in $cat_list#
|
||||
#for $ct in $categories#
|
||||
<option value="$ct">$Tspec($ct)</option>
|
||||
#end for#
|
||||
</select>
|
||||
@@ -111,10 +111,10 @@ $T('Plush-containerWidth'):
|
||||
<option value="3">$T('pp-delete')</option>
|
||||
</select>
|
||||
</td></tr>
|
||||
#if $script_list#
|
||||
#if $scripts#
|
||||
<tr><td style="text-align:right">$T('script'):</td>
|
||||
<td><select id="addID_script" name="script">
|
||||
#for $sc in $script_list#
|
||||
#for $sc in $scripts#
|
||||
<option value="$sc">$Tspec($sc)</option>
|
||||
#end for#
|
||||
</select>
|
||||
@@ -186,7 +186,7 @@ $T('Plush-containerWidth'):
|
||||
<option value='www.newshosting.com'>NewsHosting</option>
|
||||
<option value='www.readnews.com'>Readnews</option>
|
||||
<option value='www.supernews.com'>SuperNews</option>
|
||||
<option value='www.thundernews.com'>ThunderNews</option>
|
||||
<option value='www.thundernews.com'>ThunderNews</option>
|
||||
<option value='www.tweaknews.eu'>Tweaknews</option>
|
||||
<option value='www.usenetserver.com'>UsenetServer</option>
|
||||
<option value='www.xentech.net'>XenTech</option>
|
||||
|
||||
@@ -46,9 +46,9 @@
|
||||
<option value="hibernate_pc" <!--#if $finishaction == 'hibernate_pc' then 'selected' else ''#-->>$T('hibernatePc')</option>
|
||||
<!--#end if#-->
|
||||
</optgroup>
|
||||
<!--#if $script_list#-->
|
||||
<!--#if $scripts#-->
|
||||
<optgroup label="$T('eoq-scripts')">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<!--#if $sc != 'None'#-->
|
||||
<option value="script_$sc" <!--#if $finishaction == 'script_'+$sc then 'selected' else ''#-->>$sc</option>
|
||||
<!--#end if#-->
|
||||
@@ -85,7 +85,7 @@
|
||||
<ul>
|
||||
<li>
|
||||
$T('Plush-maxSpeed'):
|
||||
<input type="text" id="maxSpeed-option" size="4" />
|
||||
<input type="text" id="maxSpeed-option" size="4" />
|
||||
<select id="maxSpeed-label">
|
||||
<option value="%">%</option>
|
||||
<option value="K">KB/s</option>
|
||||
@@ -136,10 +136,10 @@
|
||||
<option value="pause">$T('sch-pause')</option>
|
||||
<option value="resume">$T('sch-resume')</option>
|
||||
</optgroup></select>
|
||||
<!--#if $cat_list#-->
|
||||
<!--#if $categories#-->
|
||||
<select id="multi_cat"><optgroup label="$T('category')">
|
||||
<option value="">$T('category')</option>
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct">$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</optgroup></select>
|
||||
@@ -158,10 +158,10 @@
|
||||
<option value="2">$T('pp-unpack')</option>
|
||||
<option value="3">$T('pp-delete')</option>
|
||||
</optgroup></select>
|
||||
<!--#if $script_list#-->
|
||||
<!--#if $scripts#-->
|
||||
<select id="multi_script"><optgroup label="$T('script')">
|
||||
<option value="">$T('script')</option>
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<!--#if $sc != "Default"#--><option value="$sc">$Tspec($sc)</option><!--#end if#-->
|
||||
<!--#end for#-->
|
||||
</optgroup></select>
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<option value=$i <!--#if $i == $index then "selected" else ""#-->>$i</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<!--#if $cat_list#-->
|
||||
<!--#if $categories#-->
|
||||
<select name="cat"><optgroup label="$T('category')">
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $slot.cat.lower() == $ct.lower() then "selected" else ""#-->>$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</optgroup></select>
|
||||
@@ -35,9 +35,9 @@
|
||||
<option value="2" <!--#if $slot.unpackopts == "2" then "selected" else ""#-->>$T('pp-unpack')</option>
|
||||
<option value="3" <!--#if $slot.unpackopts == "3" then "selected" else ""#-->>$T('pp-delete')</option>
|
||||
</optgroup></select>
|
||||
<!--#if $script_list#-->
|
||||
<!--#if $scripts#-->
|
||||
<select name="script"><optgroup label="$T('script')">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<option value="$sc" <!--#if $slot.script.lower() == $sc.lower() then "selected" else ""#-->>$Tspec($sc)</option>
|
||||
<!--#end for#-->
|
||||
</optgroup></select>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<td></td>
|
||||
<!--#end if#-->
|
||||
<!--#end if#-->
|
||||
|
||||
|
||||
<td>
|
||||
<div class="main_sprite_container sprite_progressbar_bg">
|
||||
<div class="main_sprite_container sprite_progress_done" style="background-position: -<!--#if $slot.mb == "0.00" then "120" else int(120 - 120.0 / 100.0 * int(100 - float($slot.mbleft) / float($slot.mb) * 100))#-->px -401px">
|
||||
@@ -94,9 +94,9 @@
|
||||
</td>
|
||||
|
||||
<td class="options nowrap">
|
||||
<!--#if $cat_list#-->
|
||||
<!--#if $categories#-->
|
||||
<select class="change_cat"><optgroup label="$T('category')">
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#for $ct in $categories#-->
|
||||
<option value="$ct" <!--#if $slot.cat.lower() == $ct.lower() then "selected" else ""#-->>$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</optgroup></select>
|
||||
@@ -114,9 +114,9 @@
|
||||
<option value="2" <!--#if $slot.unpackopts == "2" then "selected " else ""#-->>$T('pp-unpack')</option>
|
||||
<option value="3" <!--#if $slot.unpackopts == "3" then "selected " else ""#-->>$T('pp-delete')</option>
|
||||
</optgroup></select>
|
||||
<!--#if $script_list#-->
|
||||
<!--#if $scripts#-->
|
||||
<select class="change_script"><optgroup label="$T('script')">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<!--#for $sc in $scripts#-->
|
||||
<option value="$sc" <!--#if $slot.script == $sc then "selected" else ""#-->>$Tspec($sc)</option>
|
||||
<!--#end for#-->
|
||||
</optgroup></select>
|
||||
|
||||
|
Before Width: | Height: | Size: 112 B After Width: | Height: | Size: 78 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 729 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 932 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 256 B |
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 980 B After Width: | Height: | Size: 618 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 454 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 854 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 276 B |
|
Before Width: | Height: | Size: 698 B After Width: | Height: | Size: 524 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 446 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 809 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 811 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 479 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 710 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 363 B |
|
Before Width: | Height: | Size: 764 B After Width: | Height: | Size: 617 B |
|
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 195 B |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 216 B After Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 347 B After Width: | Height: | Size: 218 B |
|
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 221 B |
|
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 99 B |