mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-17 21:44:48 -04:00
Compare commits
132 Commits
v1.25.4.48
...
v1.32.0.49
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53bc97b3be | ||
|
|
b09d4927cc | ||
|
|
328f3c0423 | ||
|
|
635e76526a | ||
|
|
790feed5ab | ||
|
|
59b5d2fc78 | ||
|
|
d5b12cf51a | ||
|
|
2d584f7eb6 | ||
|
|
0f1d647cd7 | ||
|
|
d6e8d89be4 | ||
|
|
8672129d5a | ||
|
|
44bdff8b8f | ||
|
|
4df8fc02f1 | ||
|
|
e101129cff | ||
|
|
147e732c9c | ||
|
|
a12381fb1d | ||
|
|
3a4de9cca1 | ||
|
|
43c988d951 | ||
|
|
a036e0fc37 | ||
|
|
56b9da16cf | ||
|
|
887c262589 | ||
|
|
12ff612775 | ||
|
|
0d3d27e46f | ||
|
|
d1846fde61 | ||
|
|
e6901506a0 | ||
|
|
08b4eddbc5 | ||
|
|
979db70e68 | ||
|
|
22834a852a | ||
|
|
f0540a5f8b | ||
|
|
1f7ac7d7d6 | ||
|
|
8ac68240ad | ||
|
|
b463a3f54b | ||
|
|
e15e57329e | ||
|
|
d8354408a4 | ||
|
|
6d2d49f7bd | ||
|
|
37610eec40 | ||
|
|
ed51208116 | ||
|
|
26e4dcad65 | ||
|
|
6eb21a02a1 | ||
|
|
8c2d5a404d | ||
|
|
3b83a00eaf | ||
|
|
a5a86a6f86 | ||
|
|
e7ed09a43d | ||
|
|
547bc2e58c | ||
|
|
8eb674c8d7 | ||
|
|
2c3621d25e | ||
|
|
2648f2c639 | ||
|
|
f4d621063b | ||
|
|
73494c462c | ||
|
|
36f6896f30 | ||
|
|
e01741a69e | ||
|
|
1dbff1235e | ||
|
|
1a9ad6b363 | ||
|
|
c88249300c | ||
|
|
7b8e352d87 | ||
|
|
81f7a6cbab | ||
|
|
523e46af2a | ||
|
|
2b4a6def2a | ||
|
|
9097c0ef6d | ||
|
|
4321c1d40c | ||
|
|
bb2548a08d | ||
|
|
3a9b841fad | ||
|
|
31203d1370 | ||
|
|
c8a910eaf4 | ||
|
|
9ab3c3e6c7 | ||
|
|
4659cb706a | ||
|
|
500759bf1f | ||
|
|
43c7c43257 | ||
|
|
9c2fced391 | ||
|
|
52ec5b6ff6 | ||
|
|
b46e657976 | ||
|
|
51fd30ba10 | ||
|
|
5fbb347108 | ||
|
|
54d3d44620 | ||
|
|
5ca18683ca | ||
|
|
6bdf5f5d69 | ||
|
|
7cba7152f1 | ||
|
|
cf012eb001 | ||
|
|
6b8a7993ff | ||
|
|
c6440bb21b | ||
|
|
b95eac98b9 | ||
|
|
0eb19ce834 | ||
|
|
4b8016d95d | ||
|
|
31d8d2419a | ||
|
|
d29ccd7749 | ||
|
|
e789f4ec54 | ||
|
|
58d495d618 | ||
|
|
f3328863e1 | ||
|
|
a23d792781 | ||
|
|
f066cf399d | ||
|
|
61e863cb31 | ||
|
|
b2afbc6872 | ||
|
|
aace65f88e | ||
|
|
9ab2d8b444 | ||
|
|
bc314061ef | ||
|
|
87b3dcd780 | ||
|
|
f3b99f68f6 | ||
|
|
c4a90e8ba4 | ||
|
|
41320ca2dc | ||
|
|
b8b32f8708 | ||
|
|
30c4bb24e8 | ||
|
|
b447db5d08 | ||
|
|
299001a513 | ||
|
|
2871f1f2a2 | ||
|
|
a9b93df0c9 | ||
|
|
2726787ee9 | ||
|
|
b917932f19 | ||
|
|
06ae85e6d1 | ||
|
|
b1c7e98664 | ||
|
|
62479737a7 | ||
|
|
8e69415d64 | ||
|
|
222dfb1821 | ||
|
|
94f439e238 | ||
|
|
903a88c121 | ||
|
|
9690ab6883 | ||
|
|
1e1a2b3b4a | ||
|
|
9dc2d3669c | ||
|
|
511c76e219 | ||
|
|
78329b7b92 | ||
|
|
4240048853 | ||
|
|
432af42ffd | ||
|
|
0d6c03f8d4 | ||
|
|
96830f975e | ||
|
|
13c538ff58 | ||
|
|
14250e9634 | ||
|
|
e2f7890d76 | ||
|
|
257d38de66 | ||
|
|
fd2a14e01b | ||
|
|
b4d76c7138 | ||
|
|
9655f37fa8 | ||
|
|
246fb9b855 | ||
|
|
25afadc9b2 |
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-1.3318" y1="43.7371" x2="67.0419" y2="26.0967">
|
||||
<stop offset="0.1237" style="stop-color:#7866FF"/>
|
||||
<stop offset="0.5376" style="stop-color:#FE2EB6"/>
|
||||
<stop offset="0.8548" style="stop-color:#FD0486"/>
|
||||
</linearGradient>
|
||||
<polygon style="fill:url(#SVGID_1_);" points="67.3,16 43.7,0 0,31.1 11.1,70 58.9,60.3 "/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="45.9148" y1="38.9098" x2="67.6577" y2="9.0989">
|
||||
<stop offset="0.1237" style="stop-color:#FF0080"/>
|
||||
<stop offset="0.2587" style="stop-color:#FE0385"/>
|
||||
<stop offset="0.4109" style="stop-color:#FA0C92"/>
|
||||
<stop offset="0.5713" style="stop-color:#F41BA9"/>
|
||||
<stop offset="0.7363" style="stop-color:#EB2FC8"/>
|
||||
<stop offset="0.8656" style="stop-color:#E343E6"/>
|
||||
</linearGradient>
|
||||
<polygon style="fill:url(#SVGID_2_);" points="67.3,16 43.7,0 38,15.7 38,47.8 70,47.8 "/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="13.4" y="13.4" style="fill:#000000;" width="43.2" height="43.2"/>
|
||||
<rect x="17.4" y="48.5" style="fill:#FFFFFF;" width="16.2" height="2.7"/>
|
||||
<g>
|
||||
<path style="fill:#FFFFFF;" d="M17.4,19.1h6.9c5.6,0,9.5,3.8,9.5,8.9V28c0,5-3.9,8.9-9.5,8.9h-6.9V19.1z M21.4,22.7v10.7h3
|
||||
c3.2,0,5.4-2.2,5.4-5.3V28c0-3.2-2.2-5.4-5.4-5.4H21.4z"/>
|
||||
<polygon style="fill:#FFFFFF;" points="40.3,22.7 34.9,22.7 34.9,19.1 49.6,19.1 49.6,22.7 44.2,22.7 44.2,37 40.3,37 "/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="120.1px" height="130.2px" viewBox="0 0 120.1 130.2" style="enable-background:new 0 0 120.1 130.2;" xml:space="preserve"
|
||||
>
|
||||
<g>
|
||||
<linearGradient id="XMLID_2_" gradientUnits="userSpaceOnUse" x1="31.8412" y1="120.5578" x2="110.2402" y2="73.24">
|
||||
<stop offset="0" style="stop-color:#FCEE39"/>
|
||||
<stop offset="1" style="stop-color:#F37B3D"/>
|
||||
</linearGradient>
|
||||
<path id="XMLID_3041_" style="fill:url(#XMLID_2_);" d="M118.6,71.8c0.9-0.8,1.4-1.9,1.5-3.2c0.1-2.6-1.8-4.7-4.4-4.9
|
||||
c-1.2-0.1-2.4,0.4-3.3,1.1l0,0l-83.8,45.9c-1.9,0.8-3.6,2.2-4.7,4.1c-2.9,4.8-1.3,11,3.6,13.9c3.4,2,7.5,1.8,10.7-0.2l0,0l0,0
|
||||
c0.2-0.2,0.5-0.3,0.7-0.5l78-54.8C117.3,72.9,118.4,72.1,118.6,71.8L118.6,71.8L118.6,71.8z"/>
|
||||
<linearGradient id="XMLID_3_" gradientUnits="userSpaceOnUse" x1="48.3607" y1="6.9083" x2="119.9179" y2="69.5546">
|
||||
<stop offset="0" style="stop-color:#EF5A6B"/>
|
||||
<stop offset="0.57" style="stop-color:#F26F4E"/>
|
||||
<stop offset="1" style="stop-color:#F37B3D"/>
|
||||
</linearGradient>
|
||||
<path id="XMLID_3049_" style="fill:url(#XMLID_3_);" d="M118.8,65.1L118.8,65.1L55,2.5C53.6,1,51.6,0,49.3,0
|
||||
c-4.3,0-7.7,3.5-7.7,7.7v0c0,2.1,0.8,3.9,2.1,5.3l0,0l0,0c0.4,0.4,0.8,0.7,1.2,1l67.4,57.7l0,0c0.8,0.7,1.8,1.2,3,1.3
|
||||
c2.6,0.1,4.7-1.8,4.9-4.4C120.2,67.3,119.7,66,118.8,65.1z"/>
|
||||
<linearGradient id="XMLID_4_" gradientUnits="userSpaceOnUse" x1="52.9467" y1="63.6407" x2="10.5379" y2="37.1562">
|
||||
<stop offset="0" style="stop-color:#7C59A4"/>
|
||||
<stop offset="0.3852" style="stop-color:#AF4C92"/>
|
||||
<stop offset="0.7654" style="stop-color:#DC4183"/>
|
||||
<stop offset="0.957" style="stop-color:#ED3D7D"/>
|
||||
</linearGradient>
|
||||
<path id="XMLID_3042_" style="fill:url(#XMLID_4_);" d="M57.1,59.5C57,59.5,17.7,28.5,16.9,28l0,0l0,0c-0.6-0.3-1.2-0.6-1.8-0.9
|
||||
c-5.8-2.2-12.2,0.8-14.4,6.6c-1.9,5.1,0.2,10.7,4.6,13.4l0,0l0,0C6,47.5,6.6,47.8,7.3,48c0.4,0.2,45.4,18.8,45.4,18.8l0,0
|
||||
c1.8,0.8,3.9,0.3,5.1-1.2C59.3,63.7,59,61,57.1,59.5z"/>
|
||||
<linearGradient id="XMLID_5_" gradientUnits="userSpaceOnUse" x1="52.1736" y1="3.7019" x2="10.7706" y2="37.8971">
|
||||
<stop offset="0" style="stop-color:#EF5A6B"/>
|
||||
<stop offset="0.364" style="stop-color:#EE4E72"/>
|
||||
<stop offset="1" style="stop-color:#ED3D7D"/>
|
||||
</linearGradient>
|
||||
<path id="XMLID_3057_" style="fill:url(#XMLID_5_);" d="M49.3,0c-1.7,0-3.3,0.6-4.6,1.5L4.9,28.3c-0.1,0.1-0.2,0.1-0.2,0.2l-0.1,0
|
||||
l0,0c-1.7,1.2-3.1,3-3.9,5.1C-1.5,39.4,1.5,45.9,7.3,48c3.6,1.4,7.5,0.7,10.4-1.4l0,0l0,0c0.7-0.5,1.3-1,1.8-1.6l34.6-31.2l0,0
|
||||
c1.8-1.4,3-3.6,3-6.1v0C57.1,3.5,53.6,0,49.3,0z"/>
|
||||
<g id="XMLID_3008_">
|
||||
<rect id="XMLID_3033_" x="34.6" y="37.4" style="fill:#000000;" width="51" height="51"/>
|
||||
<rect id="XMLID_3032_" x="39" y="78.8" style="fill:#FFFFFF;" width="19.1" height="3.2"/>
|
||||
<g id="XMLID_3009_">
|
||||
<path id="XMLID_3030_" style="fill:#FFFFFF;" d="M38.8,50.8l1.5-1.4c0.4,0.5,0.8,0.8,1.3,0.8c0.6,0,0.9-0.4,0.9-1.2l0-5.3l2.3,0
|
||||
l0,5.3c0,1-0.3,1.8-0.8,2.3c-0.5,0.5-1.3,0.8-2.3,0.8C40.2,52.2,39.4,51.6,38.8,50.8z"/>
|
||||
<path id="XMLID_3028_" style="fill:#FFFFFF;" d="M45.3,43.8l6.7,0v1.9l-4.4,0V47l4,0l0,1.8l-4,0l0,1.3l4.5,0l0,2l-6.7,0
|
||||
L45.3,43.8z"/>
|
||||
<path id="XMLID_3026_" style="fill:#FFFFFF;" d="M55,45.8l-2.5,0l0-2l7.3,0l0,2l-2.5,0l0,6.3l-2.3,0L55,45.8z"/>
|
||||
<path id="XMLID_3022_" style="fill:#FFFFFF;" d="M39,54l4.3,0c1,0,1.8,0.3,2.3,0.7c0.3,0.3,0.5,0.8,0.5,1.4v0
|
||||
c0,1-0.5,1.5-1.3,1.9c1,0.3,1.6,0.9,1.6,2v0c0,1.4-1.2,2.3-3.1,2.3l-4.3,0L39,54z M43.8,56.6c0-0.5-0.4-0.7-1-0.7l-1.5,0l0,1.5
|
||||
l1.4,0C43.4,57.3,43.8,57.1,43.8,56.6L43.8,56.6z M43,59l-1.8,0l0,1.5H43c0.7,0,1.1-0.3,1.1-0.8v0C44.1,59.2,43.7,59,43,59z"/>
|
||||
<path id="XMLID_3019_" style="fill:#FFFFFF;" d="M46.8,54l3.9,0c1.3,0,2.1,0.3,2.7,0.9c0.5,0.5,0.7,1.1,0.7,1.9v0
|
||||
c0,1.3-0.7,2.1-1.7,2.6l2,2.9l-2.6,0l-1.7-2.5h-1l0,2.5l-2.3,0L46.8,54z M50.6,58c0.8,0,1.2-0.4,1.2-1v0c0-0.7-0.5-1-1.2-1
|
||||
l-1.5,0v2H50.6z"/>
|
||||
<path id="XMLID_3016_" style="fill:#FFFFFF;" d="M56.8,54l2.2,0l3.5,8.4l-2.5,0l-0.6-1.5l-3.2,0l-0.6,1.5l-2.4,0L56.8,54z
|
||||
M58.8,59l-0.9-2.3L57,59L58.8,59z"/>
|
||||
<path id="XMLID_3014_" style="fill:#FFFFFF;" d="M62.8,54l2.3,0l0,8.3l-2.3,0L62.8,54z"/>
|
||||
<path id="XMLID_3012_" style="fill:#FFFFFF;" d="M65.7,54l2.1,0l3.4,4.4l0-4.4l2.3,0l0,8.3l-2,0L68,57.8l0,4.6l-2.3,0L65.7,54z"
|
||||
/>
|
||||
<path id="XMLID_3010_" style="fill:#FFFFFF;" d="M73.7,61.1l1.3-1.5c0.8,0.7,1.7,1,2.7,1c0.6,0,1-0.2,1-0.6v0
|
||||
c0-0.4-0.3-0.5-1.4-0.8c-1.8-0.4-3.1-0.9-3.1-2.6v0c0-1.5,1.2-2.7,3.2-2.7c1.4,0,2.5,0.4,3.4,1.1l-1.2,1.6
|
||||
c-0.8-0.5-1.6-0.8-2.3-0.8c-0.6,0-0.8,0.2-0.8,0.5v0c0,0.4,0.3,0.5,1.4,0.8c1.9,0.4,3.1,1,3.1,2.6v0c0,1.7-1.3,2.7-3.4,2.7
|
||||
C76.1,62.5,74.7,62,73.7,61.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB |
@@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="22.9451" y1="75.7869" x2="74.7868" y2="20.6415">
|
||||
<stop offset="1.612903e-002" style="stop-color:#B35BA3"/>
|
||||
<stop offset="0.4044" style="stop-color:#C41E57"/>
|
||||
<stop offset="0.4677" style="stop-color:#C41E57"/>
|
||||
<stop offset="0.6505" style="stop-color:#EB8523"/>
|
||||
<stop offset="0.9516" style="stop-color:#FEBD11"/>
|
||||
</linearGradient>
|
||||
<polygon style="fill:url(#SVGID_1_);" points="49.8,15.2 36,36.7 58.4,70 70,23.1 "/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="17.7187" y1="73.2922" x2="69.5556" y2="18.1519">
|
||||
<stop offset="1.612903e-002" style="stop-color:#B35BA3"/>
|
||||
<stop offset="0.4044" style="stop-color:#C41E57"/>
|
||||
<stop offset="0.4677" style="stop-color:#C41E57"/>
|
||||
<stop offset="0.7043" style="stop-color:#EB8523"/>
|
||||
</linearGradient>
|
||||
<polygon style="fill:url(#SVGID_2_);" points="51.1,15.7 49,0 18.8,33.6 27.6,42.3 20.8,70 58.4,70 "/>
|
||||
</g>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="1.8281" y1="53.4275" x2="48.8245" y2="9.2255">
|
||||
<stop offset="1.612903e-002" style="stop-color:#B35BA3"/>
|
||||
<stop offset="0.6613" style="stop-color:#C41E57"/>
|
||||
</linearGradient>
|
||||
<polygon style="fill:url(#SVGID_3_);" points="49,0 11.6,0 0,47.1 55.6,47.1 "/>
|
||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="49.8935" y1="-11.5569" x2="48.8588" y2="24.0352">
|
||||
<stop offset="0.5" style="stop-color:#C41E57"/>
|
||||
<stop offset="0.6668" style="stop-color:#D13F48"/>
|
||||
<stop offset="0.7952" style="stop-color:#D94F39"/>
|
||||
<stop offset="0.8656" style="stop-color:#DD5433"/>
|
||||
</linearGradient>
|
||||
<polygon style="fill:url(#SVGID_4_);" points="55.3,47.1 51.1,15.7 49,0 41.7,23 "/>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<rect x="13.4" y="13.5" transform="matrix(-1 2.577289e-003 -2.577289e-003 -1 70.0288 70.081)" style="fill:#000000;" width="43.2" height="43.2"/>
|
||||
|
||||
<rect x="17.6" y="48.6" transform="matrix(1 -2.577289e-003 2.577289e-003 1 -0.1287 6.634109e-002)" style="fill:#FFFFFF;" width="16.2" height="2.7"/>
|
||||
<path style="fill:#FFFFFF;" d="M17.4,19.1l8.2,0c2.3,0,4,0.6,5.2,1.8c1,1,1.5,2.4,1.5,4.1l0,0.1c0,1.5-0.3,2.6-1.1,3.5
|
||||
c-0.7,0.9-1.6,1.6-2.8,2l4.4,6.4l-4.6,0l-3.7-5.5l-3.3,0l0,5.5l-3.9,0L17.4,19.1z M25.3,27.8c1,0,1.7-0.2,2.2-0.7
|
||||
c0.5-0.5,0.8-1.1,0.8-1.8l0-0.1c0-0.9-0.3-1.5-0.8-1.9c-0.5-0.4-1.3-0.6-2.3-0.6l-3.9,0l0,5.1L25.3,27.8z"/>
|
||||
<path style="fill:#FFFFFF;" d="M36,33.2l-1.9,0l0-3.3l2.5,0l0.6-3.8l-2.3,0l0-3.3l2.8,0l0.6-3.7l3.4,0l-0.6,3.7l3.7,0l0.6-3.7
|
||||
l3.4,0l-0.6,3.7l1.9,0l0,3.3l-2.5,0L47,29.9l2.3,0l0,3.3l-2.8,0L45.8,37l-3.4,0l0.7-3.8l-3.7,0L38.7,37l-3.4,0L36,33.2z
|
||||
M43.7,29.9l0.6-3.8l-3.7,0L40,29.9L43.7,29.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
|
||||
<defs>
|
||||
<linearGradient id="linear-gradient" x1="70.22612" y1="27.79912" x2="-5.13024" y2="63.12242" gradientTransform="matrix(1, 0, 0, -1, 0, 71.27997)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#c90f5e"/>
|
||||
<stop offset="0.22111" stop-color="#c90f5e"/>
|
||||
<stop offset="0.2356" stop-color="#c90f5e"/>
|
||||
<stop offset="0.35559" stop-color="#ca135c"/>
|
||||
<stop offset="0.46633" stop-color="#ce1e57"/>
|
||||
<stop offset="0.5735" stop-color="#d4314e"/>
|
||||
<stop offset="0.67844" stop-color="#dc4b41"/>
|
||||
<stop offset="0.78179" stop-color="#e66d31"/>
|
||||
<stop offset="0.88253" stop-color="#f3961d"/>
|
||||
<stop offset="0.94241" stop-color="#fcb20f"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linear-gradient-2" x1="24.65904" y1="61.99608" x2="46.04762" y2="2.93445" gradientTransform="matrix(1, 0, 0, -1, 0, 71.27997)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.04188" stop-color="#077cfb"/>
|
||||
<stop offset="0.44503" stop-color="#c90f5e"/>
|
||||
<stop offset="0.95812" stop-color="#077cfb"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linear-gradient-3" x1="17.39552" y1="63.34592" x2="33.19389" y2="7.20092" gradientTransform="matrix(1, 0, 0, -1, 0, 71.27997)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.27749" stop-color="#c90f5e"/>
|
||||
<stop offset="0.97382" stop-color="#fcb20f"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<title>rider</title>
|
||||
<g>
|
||||
<polygon points="70 27.237 63.391 23.75 20.926 0 3.827 17.921 21.619 41.068 60.537 44.397 70 27.237" fill="url(#linear-gradient)"/>
|
||||
<polygon points="50.423 16.132 44.271 1.107 27.643 17.471 11.768 50.194 49.411 70 70 57.98 50.423 16.132" fill="url(#linear-gradient-2)"/>
|
||||
<polygon points="20.926 0 0 14.095 7.779 62.172 27.848 69.889 53.78 48.823 20.926 0" fill="url(#linear-gradient-3)"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="13.30219" y="13.19311" width="43.61371" height="43.61371"/>
|
||||
<g>
|
||||
<path d="M17.22741,18.86293h8.39564a7.38416,7.38416,0,0,1,5.34268,1.85358,5.86989,5.86989,0,0,1,1.52648,4.1433h0A5.74339,5.74339,0,0,1,28.567,30.5296l4.47041,6.54206H28.34891L24.42368,31.1838h-3.162v5.88785H17.22741V18.86293h0ZM25.296,27.69471c1.96262,0,3.053-1.09034,3.053-2.61682h0c0-1.74455-1.19938-2.61682-3.162-2.61682H21.15265v5.23365H25.296Z" fill="#fff"/>
|
||||
<path d="M36.09034,18.86293H43.2866c5.77882,0,9.70405,3.92523,9.70405,9.15888h0c0,5.12461-3.92523,9.15888-9.70405,9.15888H36.09034V18.86293Zm4.03427,3.59813V33.47352h3.162a5.23727,5.23727,0,0,0,5.56075-5.45171h0a5.26493,5.26493,0,0,0-5.56075-5.56075h-3.162Z" fill="#fff"/>
|
||||
</g>
|
||||
<rect x="17.22741" y="48.62925" width="16.35514" height="2.72586" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="25.0676" y1="1.4599" x2="43.1829" y2="66.675">
|
||||
<stop offset="0.2849" style="stop-color:#00CDD7"/>
|
||||
<stop offset="0.9409" style="stop-color:#2086D7"/>
|
||||
</linearGradient>
|
||||
<polygon style="fill:url(#SVGID_1_);" points="9.4,63.3 0,7.3 17.5,0.1 28.6,6.7 38.8,1.2 60.1,9.4 48.1,70 "/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="30.7199" y1="9.7343" x2="61.365" y2="54.6713">
|
||||
<stop offset="0.1398" style="stop-color:#FFF045"/>
|
||||
<stop offset="0.3656" style="stop-color:#00CDD7"/>
|
||||
</linearGradient>
|
||||
<polygon style="fill:url(#SVGID_2_);" points="70,23.7 61,1.4 44.6,0 19.3,24.3 26.1,55.6 38.8,64.6 70,46 62.3,31.7 "/>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="61.0819" y1="15.2899" x2="65.1065" y2="29.5436">
|
||||
<stop offset="0.2849" style="stop-color:#00CDD7"/>
|
||||
<stop offset="0.9409" style="stop-color:#2086D7"/>
|
||||
</linearGradient>
|
||||
<polygon style="fill:url(#SVGID_3_);" points="56,20.4 62.3,31.7 70,23.7 64.4,9.8 "/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="13.4" y="13.4" style="fill:#000000;" width="43.2" height="43.2"/>
|
||||
<rect x="17.5" y="48.5" style="fill:#FFFFFF;" width="16.2" height="2.7"/>
|
||||
<path style="fill:#FFFFFF;" d="M38.7,34.3l2.3-2.8c1.6,1.3,3.3,2.2,5.3,2.2c1.6,0,2.5-0.6,2.5-1.7v-0.1c0-1-0.6-1.5-3.6-2.3
|
||||
c-3.6-0.9-5.8-1.9-5.8-5.5v-0.1c0-3.3,2.6-5.4,6.2-5.4c2.6,0,4.8,0.8,6.6,2.3l-2,3c-1.6-1.1-3.1-1.8-4.6-1.8
|
||||
c-1.5,0-2.3,0.7-2.3,1.6v0.1c0,1.2,0.8,1.6,3.8,2.4c3.6,1,5.6,2.3,5.6,5.4v0.1c0,3.6-2.7,5.6-6.5,5.6
|
||||
C43.5,37.2,40.8,36.2,38.7,34.3"/>
|
||||
</g>
|
||||
<polygon style="fill:#FFFFFF;" points="35.2,19 32.5,29.4 29.5,19 26.5,19 23.4,29.4 20.7,19 16.6,19 21.7,36.9 25,36.9 28,26.5
|
||||
30.9,36.9 34.3,36.9 39.4,19 "/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
12
README.md
12
README.md
@@ -68,16 +68,16 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
|
||||
|
||||
## JetBrains
|
||||
|
||||
Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
|
||||
Thank you to [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.png" alt="JetBrains" width="96">](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
|
||||
|
||||
- [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
|
||||
- [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
|
||||
- [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
||||
- [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
|
||||
* [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/ReSharper_icon.png" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
|
||||
* [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/WebStorm_icon.png" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
|
||||
* [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/Rider_icon.png" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
||||
* [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/dotTrace_icon.png" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
|
||||
|
||||
### License
|
||||
|
||||
- [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
- Copyright 2010-2022
|
||||
- Copyright 2010-2024
|
||||
|
||||
Icon Credit - [Box vector created by freepik - www.freepik.com](https://www.freepik.com/vectors/box)
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '1.25.4'
|
||||
majorVersion: '1.32.0'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
@@ -1169,12 +1169,12 @@ stages:
|
||||
submodules: true
|
||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||
displayName: Enable Windows Test Service
|
||||
- task: SonarCloudPrepare@2
|
||||
- task: SonarCloudPrepare@3
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
inputs:
|
||||
SonarCloud: 'SonarCloud'
|
||||
organization: 'prowlarr'
|
||||
scannerMode: 'MSBuild'
|
||||
scannerMode: 'dotnet'
|
||||
projectKey: 'Prowlarr_Prowlarr'
|
||||
projectName: 'Prowlarr'
|
||||
projectVersion: '$(prowlarrVersion)'
|
||||
@@ -1187,10 +1187,10 @@ stages:
|
||||
./build.sh --backend -f net6.0 -r win-x64
|
||||
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
displayName: Coverage Unit Tests
|
||||
- task: SonarCloudAnalyze@2
|
||||
- task: SonarCloudAnalyze@3
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
displayName: Publish SonarCloud Results
|
||||
- task: reportgenerator@5
|
||||
- task: reportgenerator@5.3.11
|
||||
displayName: Generate Coverage Report
|
||||
inputs:
|
||||
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
|
||||
|
||||
9
docs.sh
9
docs.sh
@@ -3,15 +3,16 @@ set -e
|
||||
|
||||
FRAMEWORK="net6.0"
|
||||
PLATFORM=$1
|
||||
ARCHITECTURE="${2:-x64}"
|
||||
|
||||
if [ "$PLATFORM" = "Windows" ]; then
|
||||
RUNTIME="win-x64"
|
||||
RUNTIME="win-$ARCHITECTURE"
|
||||
elif [ "$PLATFORM" = "Linux" ]; then
|
||||
RUNTIME="linux-x64"
|
||||
RUNTIME="linux-$ARCHITECTURE"
|
||||
elif [ "$PLATFORM" = "Mac" ]; then
|
||||
RUNTIME="osx-x64"
|
||||
RUNTIME="osx-$ARCHITECTURE"
|
||||
else
|
||||
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
|
||||
echo "Platform must be provided as first argument: Windows, Linux or Mac"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ module.exports = (env) => {
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||
target: 'web',
|
||||
|
||||
stats: {
|
||||
children: false
|
||||
@@ -169,7 +170,7 @@ module.exports = (env) => {
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3
|
||||
corejs: '3.39'
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -20,6 +20,8 @@ import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
|
||||
import TextInput from './TextInput';
|
||||
import styles from './EnhancedSelectInput.css';
|
||||
|
||||
const MINIMUM_DISTANCE_FROM_EDGE = 10;
|
||||
|
||||
function isArrowKey(keyCode) {
|
||||
return keyCode === keyCodes.UP_ARROW || keyCode === keyCodes.DOWN_ARROW;
|
||||
}
|
||||
@@ -137,18 +139,9 @@ class EnhancedSelectInput extends Component {
|
||||
// Listeners
|
||||
|
||||
onComputeMaxHeight = (data) => {
|
||||
const {
|
||||
top,
|
||||
bottom
|
||||
} = data.offsets.reference;
|
||||
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
if ((/^botton/).test(data.placement)) {
|
||||
data.styles.maxHeight = windowHeight - bottom;
|
||||
} else {
|
||||
data.styles.maxHeight = top;
|
||||
}
|
||||
data.styles.maxHeight = windowHeight - MINIMUM_DISTANCE_FROM_EDGE;
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -460,6 +453,10 @@ class EnhancedSelectInput extends Component {
|
||||
order: 851,
|
||||
enabled: true,
|
||||
fn: this.onComputeMaxHeight
|
||||
},
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
boundariesElement: 'viewport'
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -257,6 +257,7 @@ class HistoryRow extends Component {
|
||||
key={parameter.key}
|
||||
title={parameter.title}
|
||||
value={data[parameter.key]}
|
||||
queryType={data.queryType}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { HistoryQueryType } from 'typings/History';
|
||||
import styles from './HistoryRowParameter.css';
|
||||
|
||||
interface HistoryRowParameterProps {
|
||||
title: string;
|
||||
value: string;
|
||||
queryType: HistoryQueryType;
|
||||
}
|
||||
|
||||
function HistoryRowParameter(props: HistoryRowParameterProps) {
|
||||
const { title, value } = props;
|
||||
const { title, value, queryType } = props;
|
||||
|
||||
const type = title.toLowerCase();
|
||||
|
||||
@@ -18,7 +20,13 @@ function HistoryRowParameter(props: HistoryRowParameterProps) {
|
||||
link = <Link to={`https://imdb.com/title/${value}/`}>{value}</Link>;
|
||||
} else if (type === 'tmdb') {
|
||||
link = (
|
||||
<Link to={`https://www.themoviedb.org/movie/${value}`}>{value}</Link>
|
||||
<Link
|
||||
to={`https://www.themoviedb.org/${
|
||||
queryType === 'tvsearch' ? 'tv' : 'movie'
|
||||
}/${value}`}
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
);
|
||||
} else if (type === 'tvdb') {
|
||||
link = (
|
||||
|
||||
@@ -68,6 +68,7 @@ function IndexerHistoryRow(props: IndexerHistoryRowProps) {
|
||||
key={parameter.key}
|
||||
title={parameter.title}
|
||||
value={data[parameter.key as keyof HistoryData].toString()}
|
||||
queryType={data.queryType}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -270,7 +270,7 @@ function Updates() {
|
||||
|
||||
{generalSettingsError ? (
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('FailedToUpdateSettings')}
|
||||
{translate('FailedToFetchSettings')}
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function fetchTranslations(): Promise<boolean> {
|
||||
translations = data.Strings;
|
||||
|
||||
resolve(true);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import ModelBase from 'App/ModelBase';
|
||||
|
||||
export type HistoryQueryType =
|
||||
| 'search'
|
||||
| 'tvsearch'
|
||||
| 'movie'
|
||||
| 'book'
|
||||
| 'music';
|
||||
|
||||
export interface HistoryData {
|
||||
source: string;
|
||||
host: string;
|
||||
@@ -7,7 +14,7 @@ export interface HistoryData {
|
||||
offset: number;
|
||||
elapsedTime: number;
|
||||
query: string;
|
||||
queryType: string;
|
||||
queryType: HistoryQueryType;
|
||||
}
|
||||
|
||||
interface History extends ModelBase {
|
||||
|
||||
28
package.json
28
package.json
@@ -23,10 +23,10 @@
|
||||
"defaults"
|
||||
],
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "6.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.6.0",
|
||||
"@fortawesome/free-regular-svg-icons": "6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.6.0",
|
||||
"@fortawesome/fontawesome-free": "6.7.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.7.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.7.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.7.1",
|
||||
"@fortawesome/react-fontawesome": "0.2.2",
|
||||
"@juggle/resize-observer": "3.4.0",
|
||||
"@microsoft/signalr": "6.0.25",
|
||||
@@ -81,30 +81,30 @@
|
||||
"redux-thunk": "2.4.2",
|
||||
"reselect": "4.1.8",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"typescript": "5.1.6"
|
||||
"typescript": "5.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.25.8",
|
||||
"@babel/eslint-parser": "7.25.8",
|
||||
"@babel/plugin-proposal-export-default-from": "7.25.8",
|
||||
"@babel/core": "7.26.0",
|
||||
"@babel/eslint-parser": "7.25.9",
|
||||
"@babel/plugin-proposal-export-default-from": "7.25.9",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/preset-env": "7.25.8",
|
||||
"@babel/preset-react": "7.25.7",
|
||||
"@babel/preset-typescript": "7.25.7",
|
||||
"@babel/preset-env": "7.26.0",
|
||||
"@babel/preset-react": "7.26.3",
|
||||
"@babel/preset-typescript": "7.26.0",
|
||||
"@types/lodash": "4.14.195",
|
||||
"@types/react-document-title": "2.0.10",
|
||||
"@types/react-router-dom": "5.3.3",
|
||||
"@types/react-text-truncate": "0.19.0",
|
||||
"@types/react-window": "1.8.8",
|
||||
"@types/webpack-livereload-plugin": "2.3.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||
"@typescript-eslint/parser": "6.21.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.18.1",
|
||||
"@typescript-eslint/parser": "8.18.1",
|
||||
"are-you-es5": "2.1.2",
|
||||
"autoprefixer": "10.4.20",
|
||||
"babel-loader": "9.2.1",
|
||||
"babel-plugin-inline-classnames": "2.0.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||
"core-js": "3.38.1",
|
||||
"core-js": "3.39.0",
|
||||
"css-loader": "6.7.3",
|
||||
"css-modules-typescript-loader": "4.0.1",
|
||||
"eslint": "8.57.1",
|
||||
|
||||
@@ -221,7 +221,7 @@
|
||||
<PropertyGroup Condition="'$(IsOSX)' == 'true' and
|
||||
'$(RuntimeIdentifier)' == ''">
|
||||
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
|
||||
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
|
||||
<RuntimeIdentifier>osx-$(Architecture)</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -21,9 +21,28 @@ namespace NzbDrone.Common.Test.ExtensionTests
|
||||
[TestCase("1.2.3.4")]
|
||||
[TestCase("172.55.0.1")]
|
||||
[TestCase("192.55.0.1")]
|
||||
[TestCase("100.64.0.1")]
|
||||
[TestCase("100.127.255.254")]
|
||||
public void should_return_false_for_public_ip_address(string ipAddress)
|
||||
{
|
||||
IPAddress.Parse(ipAddress).IsLocalAddress().Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestCase("100.64.0.1")]
|
||||
[TestCase("100.127.255.254")]
|
||||
[TestCase("100.100.100.100")]
|
||||
public void should_return_true_for_cgnat_ip_address(string ipAddress)
|
||||
{
|
||||
IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("1.2.3.4")]
|
||||
[TestCase("192.168.5.1")]
|
||||
[TestCase("100.63.255.255")]
|
||||
[TestCase("100.128.0.0")]
|
||||
public void should_return_false_for_non_cgnat_ip_address(string ipAddress)
|
||||
{
|
||||
IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[TestCase(@"https://anthelion.me/api.php?api_key=2b51db35e1910123321025a12b9933d2&o=json&t=movie&q=&tmdb=&imdb=&cat=&limit=100&offset=0")]
|
||||
[TestCase(@"https://avistaz.to/api/v1/jackett/auth: username=mySecret&password=mySecret&pid=mySecret")]
|
||||
[TestCase(@"https://www.sharewood.tv/api/2b51db35e1910123321025a12b9933d2/last-torrents")]
|
||||
[TestCase(@"https://example.org/rss/torrents?rsskey=2b51db35e1910123321025a12b9933d2&search=")]
|
||||
|
||||
// Indexer and Download Client Responses
|
||||
|
||||
|
||||
@@ -133,11 +133,16 @@ namespace NzbDrone.Common.Test
|
||||
|
||||
[TestCase(@"C:\test\", @"C:\Test\mydir")]
|
||||
[TestCase(@"C:\test", @"C:\Test\mydir\")]
|
||||
public void path_should_be_parent_on_windows_only(string parentPath, string childPath)
|
||||
public void windows_path_should_be_parent(string parentPath, string childPath)
|
||||
{
|
||||
var expectedResult = OsInfo.IsWindows;
|
||||
parentPath.IsParentPath(childPath).Should().Be(true);
|
||||
}
|
||||
|
||||
parentPath.IsParentPath(childPath).Should().Be(expectedResult);
|
||||
[TestCase("/test", "/test/mydir/")]
|
||||
[TestCase("/test/", "/test/mydir")]
|
||||
public void posix_path_should_be_parent(string parentPath, string childPath)
|
||||
{
|
||||
parentPath.IsParentPath(childPath).Should().Be(true);
|
||||
}
|
||||
|
||||
[TestCase(@"C:\Test\mydir", @"C:\Test")]
|
||||
@@ -145,20 +150,57 @@ namespace NzbDrone.Common.Test
|
||||
[TestCase(@"C:\", null)]
|
||||
[TestCase(@"\\server\share", null)]
|
||||
[TestCase(@"\\server\share\test", @"\\server\share")]
|
||||
public void path_should_return_parent_windows(string path, string parentPath)
|
||||
public void windows_path_should_return_parent(string path, string parentPath)
|
||||
{
|
||||
WindowsOnly();
|
||||
path.GetParentPath().Should().Be(parentPath);
|
||||
}
|
||||
|
||||
[TestCase(@"/", null)]
|
||||
[TestCase(@"/test", "/")]
|
||||
public void path_should_return_parent_mono(string path, string parentPath)
|
||||
[TestCase(@"/test/tv", "/test")]
|
||||
public void unix_path_should_return_parent(string path, string parentPath)
|
||||
{
|
||||
PosixOnly();
|
||||
path.GetParentPath().Should().Be(parentPath);
|
||||
}
|
||||
|
||||
[TestCase(@"C:\Test\mydir", "Test")]
|
||||
[TestCase(@"C:\Test\", @"C:\")]
|
||||
[TestCase(@"C:\Test", @"C:\")]
|
||||
[TestCase(@"C:\", null)]
|
||||
[TestCase(@"\\server\share", null)]
|
||||
[TestCase(@"\\server\share\test", @"\\server\share")]
|
||||
public void path_should_return_parent_name_windows(string path, string parentPath)
|
||||
{
|
||||
path.GetParentName().Should().Be(parentPath);
|
||||
}
|
||||
|
||||
[TestCase(@"/", null)]
|
||||
[TestCase(@"/test", "/")]
|
||||
[TestCase(@"/test/tv", "test")]
|
||||
public void path_should_return_parent_name_mono(string path, string parentPath)
|
||||
{
|
||||
path.GetParentName().Should().Be(parentPath);
|
||||
}
|
||||
|
||||
[TestCase(@"C:\Test\mydir", "mydir")]
|
||||
[TestCase(@"C:\Test\", "Test")]
|
||||
[TestCase(@"C:\Test", "Test")]
|
||||
[TestCase(@"C:\", "C:\\")]
|
||||
[TestCase(@"\\server\share", @"\\server\share")]
|
||||
[TestCase(@"\\server\share\test", "test")]
|
||||
public void path_should_return_directory_name_windows(string path, string parentPath)
|
||||
{
|
||||
path.GetDirectoryName().Should().Be(parentPath);
|
||||
}
|
||||
|
||||
[TestCase(@"/", "/")]
|
||||
[TestCase(@"/test", "test")]
|
||||
[TestCase(@"/test/tv", "tv")]
|
||||
public void path_should_return_directory_name_mono(string path, string parentPath)
|
||||
{
|
||||
path.GetDirectoryName().Should().Be(parentPath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void path_should_return_parent_for_oversized_path()
|
||||
{
|
||||
@@ -166,7 +208,7 @@ namespace NzbDrone.Common.Test
|
||||
|
||||
// This test will fail on Windows if long path support is not enabled: https://www.howtogeek.com/266621/how-to-make-windows-10-accept-file-paths-over-260-characters/
|
||||
// It will also fail if the app isn't configured to use long path (such as resharper): https://blogs.msdn.microsoft.com/jeremykuhne/2016/07/30/net-4-6-2-and-long-paths-on-windows-10/
|
||||
var path = @"C:\media\2e168617-f2ae-43fb-b88c-3663af1c8eea\downloads\sabnzbd\nzbdrone\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories".AsOsAgnostic();
|
||||
var path = @"C:\media\2e168617-f2ae-43fb-b88c-3663af1c8eea\downloads\sabnzbd\nzbdrone\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories".AsOsAgnostic();
|
||||
var parentPath = @"C:\media\2e168617-f2ae-43fb-b88c-3663af1c8eea\downloads\sabnzbd\nzbdrone\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing".AsOsAgnostic();
|
||||
|
||||
path.GetParentPath().Should().Be(parentPath);
|
||||
@@ -350,5 +392,46 @@ namespace NzbDrone.Common.Test
|
||||
PosixOnly();
|
||||
path.AsOsAgnostic().IsPathValid(PathValidationType.CurrentOs).Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestCase(@"C:\", @"C:\")]
|
||||
[TestCase(@"C:\\", @"C:\")]
|
||||
[TestCase(@"C:\Test", @"C:\Test")]
|
||||
[TestCase(@"C:\Test\", @"C:\Test")]
|
||||
[TestCase(@"\\server\share", @"\\server\share")]
|
||||
[TestCase(@"\\server\share\", @"\\server\share")]
|
||||
public void windows_path_should_return_clean_path(string path, string cleanPath)
|
||||
{
|
||||
path.GetCleanPath().Should().Be(cleanPath);
|
||||
}
|
||||
|
||||
[TestCase("/", "/")]
|
||||
[TestCase("//", "/")]
|
||||
[TestCase("/test", "/test")]
|
||||
[TestCase("/test/", "/test")]
|
||||
[TestCase("/test//", "/test")]
|
||||
public void unix_path_should_return_clean_path(string path, string cleanPath)
|
||||
{
|
||||
path.GetCleanPath().Should().Be(cleanPath);
|
||||
}
|
||||
|
||||
[TestCase(@"C:\Test\", @"C:\Test\Series Title", "Series Title")]
|
||||
[TestCase(@"C:\Test\", @"C:\Test\Collection\Series Title", @"Collection\Series Title")]
|
||||
[TestCase(@"C:\Test\mydir\", @"C:\Test\mydir\Collection\Series Title", @"Collection\Series Title")]
|
||||
[TestCase(@"\\server\share", @"\\server\share\Series Title", "Series Title")]
|
||||
[TestCase(@"\\server\share\mydir\", @"\\server\share\mydir\/Collection\Series Title", @"Collection\Series Title")]
|
||||
public void windows_path_should_return_relative_path(string parentPath, string childPath, string relativePath)
|
||||
{
|
||||
parentPath.GetRelativePath(childPath).Should().Be(relativePath);
|
||||
}
|
||||
|
||||
[TestCase(@"/test", "/test/Series Title", "Series Title")]
|
||||
[TestCase(@"/test/", "/test/Collection/Series Title", "Collection/Series Title")]
|
||||
[TestCase(@"/test/mydir", "/test/mydir/Series Title", "Series Title")]
|
||||
[TestCase(@"/test/mydir/", "/test/mydir/Collection/Series Title", "Collection/Series Title")]
|
||||
[TestCase(@"/test/mydir/", @"/test/mydir/\Collection/Series Title", "Collection/Series Title")]
|
||||
public void unix_path_should_return_relative_path(string parentPath, string childPath, string relativePath)
|
||||
{
|
||||
parentPath.GetRelativePath(childPath).Should().Be(relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,17 +42,18 @@ namespace NzbDrone.Common
|
||||
|
||||
public void CreateZip(string path, IEnumerable<string> files)
|
||||
{
|
||||
using (var zipFile = ZipFile.Create(path))
|
||||
_logger.Debug("Creating archive {0}", path);
|
||||
|
||||
using var zipFile = ZipFile.Create(path);
|
||||
|
||||
zipFile.BeginUpdate();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
zipFile.BeginUpdate();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
zipFile.Add(file, Path.GetFileName(file));
|
||||
}
|
||||
|
||||
zipFile.CommitUpdate();
|
||||
zipFile.Add(file, Path.GetFileName(file));
|
||||
}
|
||||
|
||||
zipFile.CommitUpdate();
|
||||
}
|
||||
|
||||
private void ExtractZip(string compressedFile, string destination)
|
||||
|
||||
@@ -189,6 +189,25 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
|
||||
var fi = new FileInfo(path);
|
||||
|
||||
try
|
||||
{
|
||||
// If the file is a symlink, resolve the target path and get the size of the target file.
|
||||
if (fi.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
{
|
||||
var targetPath = fi.ResolveLinkTarget(true)?.FullName;
|
||||
|
||||
if (targetPath != null)
|
||||
{
|
||||
fi = new FileInfo(targetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.Trace(ex, "Unable to resolve symlink target for {0}", path);
|
||||
}
|
||||
|
||||
return fi.Length;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
@@ -9,6 +10,8 @@ namespace NzbDrone.Common.Disk
|
||||
private readonly string _path;
|
||||
private readonly OsPathKind _kind;
|
||||
|
||||
private static readonly Regex UncPathRegex = new Regex(@"(?<unc>^\\\\(?:\?\\UNC\\)?[^\\]+\\[^\\]+)(?:\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public OsPath(string path)
|
||||
{
|
||||
if (path == null)
|
||||
@@ -96,6 +99,29 @@ namespace NzbDrone.Common.Disk
|
||||
return path;
|
||||
}
|
||||
|
||||
private static string TrimTrailingSlash(string path, OsPathKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case OsPathKind.Windows when !path.EndsWith(":\\"):
|
||||
while (!path.EndsWith(":\\") && path.EndsWith('\\'))
|
||||
{
|
||||
path = path[..^1];
|
||||
}
|
||||
|
||||
return path;
|
||||
case OsPathKind.Unix when path != "/":
|
||||
while (path != "/" && path.EndsWith('/'))
|
||||
{
|
||||
path = path[..^1];
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public OsPathKind Kind => _kind;
|
||||
|
||||
public bool IsWindowsPath => _kind == OsPathKind.Windows;
|
||||
@@ -130,7 +156,19 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
return new OsPath(null);
|
||||
return Null;
|
||||
}
|
||||
|
||||
var rootLength = GetRootLength();
|
||||
|
||||
if (rootLength == _path.Length)
|
||||
{
|
||||
return Null;
|
||||
}
|
||||
|
||||
if (rootLength > index + 1)
|
||||
{
|
||||
return new OsPath(_path.Substring(0, rootLength));
|
||||
}
|
||||
|
||||
return new OsPath(_path.Substring(0, index), _kind).AsDirectory();
|
||||
@@ -139,6 +177,8 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public string FullPath => _path;
|
||||
|
||||
public string PathWithoutTrailingSlash => TrimTrailingSlash(_path, _kind);
|
||||
|
||||
public string FileName
|
||||
{
|
||||
get
|
||||
@@ -161,6 +201,29 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
// Meant to behave similar to DirectoryInfo.Name
|
||||
get
|
||||
{
|
||||
var index = GetFileNameIndex();
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
return PathWithoutTrailingSlash;
|
||||
}
|
||||
|
||||
var rootLength = GetRootLength();
|
||||
|
||||
if (rootLength > index + 1)
|
||||
{
|
||||
return _path.Substring(0, rootLength);
|
||||
}
|
||||
|
||||
return TrimTrailingSlash(_path.Substring(index).TrimStart('/', '\\'), _kind);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid => _path.IsPathValid(PathValidationType.CurrentOs);
|
||||
|
||||
private int GetFileNameIndex()
|
||||
@@ -190,11 +253,50 @@ namespace NzbDrone.Common.Disk
|
||||
return index;
|
||||
}
|
||||
|
||||
private int GetRootLength()
|
||||
{
|
||||
if (!IsRooted)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_kind == OsPathKind.Unix)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (_kind == OsPathKind.Windows)
|
||||
{
|
||||
if (HasWindowsDriveLetter(_path))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
var uncMatch = UncPathRegex.Match(_path);
|
||||
|
||||
// \\?\UNC\server\share\ or \\server\share
|
||||
if (uncMatch.Success)
|
||||
{
|
||||
return uncMatch.Groups["unc"].Length;
|
||||
}
|
||||
|
||||
// \\?\C:\
|
||||
if (_path.StartsWith(@"\\?\"))
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private string[] GetFragments()
|
||||
{
|
||||
return _path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public static OsPath Null => new (null);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _path;
|
||||
@@ -267,6 +369,11 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
|
||||
public bool Equals(OsPath other)
|
||||
{
|
||||
return Equals(other, false);
|
||||
}
|
||||
|
||||
public bool Equals(OsPath other, bool ignoreTrailingSlash)
|
||||
{
|
||||
if (ReferenceEquals(other, null))
|
||||
{
|
||||
@@ -278,8 +385,8 @@ namespace NzbDrone.Common.Disk
|
||||
return true;
|
||||
}
|
||||
|
||||
var left = _path;
|
||||
var right = other._path;
|
||||
var left = ignoreTrailingSlash ? PathWithoutTrailingSlash : _path;
|
||||
var right = ignoreTrailingSlash ? other.PathWithoutTrailingSlash : other._path;
|
||||
|
||||
if (Kind == OsPathKind.Windows || other.Kind == OsPathKind.Windows)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
@@ -25,22 +24,25 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
static OsInfo()
|
||||
{
|
||||
var platform = Environment.OSVersion.Platform;
|
||||
|
||||
switch (platform)
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
case PlatformID.Win32NT:
|
||||
{
|
||||
Os = Os.Windows;
|
||||
break;
|
||||
}
|
||||
|
||||
case PlatformID.MacOSX:
|
||||
case PlatformID.Unix:
|
||||
{
|
||||
Os = GetPosixFlavour();
|
||||
break;
|
||||
}
|
||||
Os = Os.Windows;
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
Os = Os.Osx;
|
||||
}
|
||||
else if (OperatingSystem.IsFreeBSD())
|
||||
{
|
||||
Os = Os.Bsd;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if ISMUSL
|
||||
Os = Os.LinuxMusl;
|
||||
#else
|
||||
Os = Os.Linux;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,59 +86,6 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
IsDocker = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static Os GetPosixFlavour()
|
||||
{
|
||||
var output = RunAndCapture("uname", "-s");
|
||||
|
||||
if (output.StartsWith("Darwin"))
|
||||
{
|
||||
return Os.Osx;
|
||||
}
|
||||
else if (output.Contains("BSD"))
|
||||
{
|
||||
return Os.Bsd;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if ISMUSL
|
||||
return Os.LinuxMusl;
|
||||
#else
|
||||
return Os.Linux;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private static string RunAndCapture(string filename, string args)
|
||||
{
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = filename,
|
||||
Arguments = args,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true
|
||||
};
|
||||
|
||||
var output = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
using (var p = Process.Start(processStartInfo))
|
||||
{
|
||||
// To avoid deadlocks, always read the output stream first and then wait.
|
||||
output = p.StandardOutput.ReadToEnd();
|
||||
|
||||
p.WaitForExit(1000);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
output = string.Empty;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IOsInfo
|
||||
|
||||
@@ -39,18 +39,24 @@ namespace NzbDrone.Common.Extensions
|
||||
private static bool IsLocalIPv4(byte[] ipv4Bytes)
|
||||
{
|
||||
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
|
||||
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
|
||||
var isLinkLocal = ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
|
||||
|
||||
// Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
|
||||
bool IsClassA() => ipv4Bytes[0] == 10;
|
||||
var isClassA = ipv4Bytes[0] == 10;
|
||||
|
||||
// Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
|
||||
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
|
||||
var isClassB = ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
|
||||
|
||||
// Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
|
||||
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
|
||||
var isClassC = ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
|
||||
|
||||
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
|
||||
return isLinkLocal || isClassA || isClassC || isClassB;
|
||||
}
|
||||
|
||||
public static bool IsCgnatIpAddress(this IPAddress ipAddress)
|
||||
{
|
||||
var bytes = ipAddress.GetAddressBytes();
|
||||
return bytes.Length == 4 && bytes[0] == 100 && bytes[1] >= 64 && bytes[1] <= 127;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ namespace NzbDrone.Common.Extensions
|
||||
private static readonly string UPDATE_CLIENT_FOLDER_NAME = "Prowlarr.Update" + Path.DirectorySeparatorChar;
|
||||
private static readonly string UPDATE_LOG_FOLDER_NAME = "UpdateLogs" + Path.DirectorySeparatorChar;
|
||||
|
||||
private static readonly Regex PARENT_PATH_END_SLASH_REGEX = new Regex(@"(?<!:)\\$", RegexOptions.Compiled);
|
||||
|
||||
public static string CleanFilePath(this string path)
|
||||
{
|
||||
if (path.IsNotNullOrWhiteSpace())
|
||||
@@ -87,55 +85,50 @@ namespace NzbDrone.Common.Extensions
|
||||
throw new NotParentException("{0} is not a child of {1}", childPath, parentPath);
|
||||
}
|
||||
|
||||
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
|
||||
return childPath.Substring(parentPath.Length).Trim('\\', '/');
|
||||
}
|
||||
|
||||
public static string GetParentPath(this string childPath)
|
||||
{
|
||||
var cleanPath = OsInfo.IsWindows
|
||||
? PARENT_PATH_END_SLASH_REGEX.Replace(childPath, "")
|
||||
: childPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
var path = new OsPath(childPath).Directory;
|
||||
|
||||
if (cleanPath.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return path == OsPath.Null ? null : path.PathWithoutTrailingSlash;
|
||||
}
|
||||
|
||||
return Directory.GetParent(cleanPath)?.FullName;
|
||||
public static string GetParentName(this string childPath)
|
||||
{
|
||||
var path = new OsPath(childPath).Directory;
|
||||
|
||||
return path == OsPath.Null ? null : path.Name;
|
||||
}
|
||||
|
||||
public static string GetDirectoryName(this string childPath)
|
||||
{
|
||||
var path = new OsPath(childPath);
|
||||
|
||||
return path == OsPath.Null ? null : path.Name;
|
||||
}
|
||||
|
||||
public static string GetCleanPath(this string path)
|
||||
{
|
||||
var cleanPath = OsInfo.IsWindows
|
||||
? PARENT_PATH_END_SLASH_REGEX.Replace(path, "")
|
||||
: path.TrimEnd(Path.DirectorySeparatorChar);
|
||||
var osPath = new OsPath(path);
|
||||
|
||||
return cleanPath;
|
||||
return osPath == OsPath.Null ? null : osPath.PathWithoutTrailingSlash;
|
||||
}
|
||||
|
||||
public static bool IsParentPath(this string parentPath, string childPath)
|
||||
{
|
||||
if (parentPath != "/" && !parentPath.EndsWith(":\\"))
|
||||
{
|
||||
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
}
|
||||
var parent = new OsPath(parentPath);
|
||||
var child = new OsPath(childPath);
|
||||
|
||||
if (childPath != "/" && !parentPath.EndsWith(":\\"))
|
||||
while (child.Directory != OsPath.Null)
|
||||
{
|
||||
childPath = childPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
var parent = new DirectoryInfo(parentPath);
|
||||
var child = new DirectoryInfo(childPath);
|
||||
|
||||
while (child.Parent != null)
|
||||
{
|
||||
if (child.Parent.FullName.Equals(parent.FullName, DiskProviderBase.PathStringComparison))
|
||||
if (child.Directory.Equals(parent, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
child = child.Parent;
|
||||
child = child.Directory;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -150,14 +143,14 @@ namespace NzbDrone.Common.Extensions
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path.Trim() != path)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only check for leading or trailing spaces for path when running on Windows.
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
if (path.Trim() != path)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var directoryInfo = new DirectoryInfo(path);
|
||||
|
||||
while (directoryInfo != null)
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
private static readonly Regex[] CleansingRules =
|
||||
{
|
||||
// Url
|
||||
new (@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|rsskey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"rss(24h)?\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Layouts.ClefJsonLayout;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation;
|
||||
|
||||
public class CleansingClefLogLayout : CompactJsonLayout
|
||||
{
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
base.RenderFormattedMessage(logEvent, target);
|
||||
|
||||
if (RuntimeInfo.IsProduction)
|
||||
{
|
||||
var result = CleanseLogMessage.Cleanse(target.ToString());
|
||||
target.Clear();
|
||||
target.Append(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Layouts;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation;
|
||||
|
||||
public class CleansingConsoleLogLayout : SimpleLayout
|
||||
{
|
||||
public CleansingConsoleLogLayout(string format)
|
||||
: base(format)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
base.RenderFormattedMessage(logEvent, target);
|
||||
|
||||
if (RuntimeInfo.IsProduction)
|
||||
{
|
||||
var result = CleanseLogMessage.Cleanse(target.ToString());
|
||||
target.Clear();
|
||||
target.Append(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using NLog.Targets;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public class NzbDroneFileTarget : FileTarget
|
||||
public class CleansingFileTarget : FileTarget
|
||||
{
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
@@ -3,7 +3,6 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Layouts.ClefJsonLayout;
|
||||
using NLog.Targets;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -13,9 +12,11 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public static class NzbDroneLogger
|
||||
{
|
||||
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
public const string ConsoleLogLayout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
public static CompactJsonLayout ClefLogLayout = new CompactJsonLayout();
|
||||
private const string FileLogLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
private const string ConsoleFormat = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
|
||||
private static readonly CleansingConsoleLogLayout CleansingConsoleLayout = new (ConsoleFormat);
|
||||
private static readonly CleansingClefLogLayout ClefLogLayout = new ();
|
||||
|
||||
private static bool _isConfigured;
|
||||
|
||||
@@ -119,11 +120,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
? formatEnumValue
|
||||
: ConsoleLogFormat.Standard;
|
||||
|
||||
coloredConsoleTarget.Layout = logFormat switch
|
||||
{
|
||||
ConsoleLogFormat.Clef => ClefLogLayout,
|
||||
_ => ConsoleLogLayout
|
||||
};
|
||||
ConfigureConsoleLayout(coloredConsoleTarget, logFormat);
|
||||
|
||||
var loggingRule = new LoggingRule("*", level, coloredConsoleTarget);
|
||||
|
||||
@@ -140,7 +137,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
|
||||
private static void RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel)
|
||||
{
|
||||
var fileTarget = new NzbDroneFileTarget();
|
||||
var fileTarget = new CleansingFileTarget();
|
||||
|
||||
fileTarget.Name = name;
|
||||
fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), fileName);
|
||||
@@ -153,7 +150,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
fileTarget.MaxArchiveFiles = maxArchiveFiles;
|
||||
fileTarget.EnableFileDelete = true;
|
||||
fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;
|
||||
fileTarget.Layout = FILE_LOG_LAYOUT;
|
||||
fileTarget.Layout = FileLogLayout;
|
||||
|
||||
var loggingRule = new LoggingRule("*", minLogLevel, fileTarget);
|
||||
|
||||
@@ -172,7 +169,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
fileTarget.ConcurrentWrites = false;
|
||||
fileTarget.ConcurrentWriteAttemptDelay = 50;
|
||||
fileTarget.ConcurrentWriteAttempts = 100;
|
||||
fileTarget.Layout = FILE_LOG_LAYOUT;
|
||||
fileTarget.Layout = FileLogLayout;
|
||||
|
||||
var loggingRule = new LoggingRule("*", LogLevel.Trace, fileTarget);
|
||||
|
||||
@@ -217,6 +214,15 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
return GetLogger(obj.GetType());
|
||||
}
|
||||
|
||||
public static void ConfigureConsoleLayout(ColoredConsoleTarget target, ConsoleLogFormat format)
|
||||
{
|
||||
target.Layout = format switch
|
||||
{
|
||||
ConsoleLogFormat.Clef => NzbDroneLogger.ClefLogLayout,
|
||||
_ => NzbDroneLogger.CleansingConsoleLayout
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConsoleLogFormat
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
o.Environment = BuildInfo.Branch;
|
||||
|
||||
// Crash free run statistics (sends a ping for healthy and for crashes sessions)
|
||||
o.AutoSessionTracking = true;
|
||||
o.AutoSessionTracking = false;
|
||||
|
||||
// Caches files in the event device is offline
|
||||
// Sentry creates a 'sentry' sub directory, no need to concat here
|
||||
@@ -148,7 +148,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
_debounce = new SentryDebounce();
|
||||
|
||||
// initialize to true and reconfigure later
|
||||
// Otherwise it will default to false and any errors occuring
|
||||
// Otherwise it will default to false and any errors occurring
|
||||
// before config file gets read will not be filtered
|
||||
FilterEvents = true;
|
||||
SentryEnabled = true;
|
||||
@@ -207,9 +207,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
|
||||
private void OnError(Exception ex)
|
||||
{
|
||||
var webException = ex as WebException;
|
||||
|
||||
if (webException != null)
|
||||
if (ex is WebException webException)
|
||||
{
|
||||
var response = webException.Response as HttpWebResponse;
|
||||
var statusCode = response?.StatusCode;
|
||||
|
||||
@@ -6,4 +6,5 @@ public class AuthOptions
|
||||
public bool? Enabled { get; set; }
|
||||
public string Method { get; set; }
|
||||
public string Required { get; set; }
|
||||
public bool? TrustCgnatIpAddresses { get; set; }
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ namespace NzbDrone.Common.Processes
|
||||
processInfo = new ProcessInfo();
|
||||
processInfo.Id = process.Id;
|
||||
processInfo.Name = process.ProcessName;
|
||||
processInfo.StartPath = process.MainModule.FileName;
|
||||
processInfo.StartPath = process.MainModule?.FileName;
|
||||
|
||||
if (process.Id != GetCurrentProcessId() && process.HasExited)
|
||||
{
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
|
||||
<PackageReference Include="IPAddressRange" Version="6.0.0" />
|
||||
<PackageReference Include="IPAddressRange" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.3.3" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.12" />
|
||||
<PackageReference Include="Npgsql" Version="7.0.8" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.15" />
|
||||
<PackageReference Include="Npgsql" Version="7.0.9" />
|
||||
<PackageReference Include="Sentry" Version="4.0.2" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.10" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Data.SQLite;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Datastore.Converters;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Datastore.Converters;
|
||||
|
||||
[TestFixture]
|
||||
public class TimeSpanConverterFixture : CoreTest<TimeSpanConverter>
|
||||
{
|
||||
private SQLiteParameter _param;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_param = new SQLiteParameter();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_string_when_saving_timespan_to_db()
|
||||
{
|
||||
var span = TimeSpan.FromMilliseconds(10);
|
||||
|
||||
Subject.SetValue(_param, span);
|
||||
_param.Value.Should().Be(span.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_timespan_when_getting_string_from_db()
|
||||
{
|
||||
var span = TimeSpan.FromMilliseconds(10);
|
||||
|
||||
Subject.Parse(span.ToString()).Should().Be(span);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_zero_timespan_for_db_null_value_when_getting_from_db()
|
||||
{
|
||||
Subject.Parse(null).Should().Be(TimeSpan.Zero);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Test.Datastore;
|
||||
|
||||
[TestFixture]
|
||||
public class DatabaseVersionParserFixture
|
||||
{
|
||||
[TestCase("3.44.2", 3, 44, 2)]
|
||||
public void should_parse_sqlite_database_version(string serverVersion, int majorVersion, int minorVersion, int buildVersion)
|
||||
{
|
||||
var version = DatabaseVersionParser.ParseServerVersion(serverVersion);
|
||||
|
||||
version.Should().NotBeNull();
|
||||
version.Major.Should().Be(majorVersion);
|
||||
version.Minor.Should().Be(minorVersion);
|
||||
version.Build.Should().Be(buildVersion);
|
||||
}
|
||||
|
||||
[TestCase("14.8 (Debian 14.8-1.pgdg110+1)", 14, 8, null)]
|
||||
[TestCase("16.3 (Debian 16.3-1.pgdg110+1)", 16, 3, null)]
|
||||
[TestCase("16.3 - Percona Distribution", 16, 3, null)]
|
||||
[TestCase("17.0 - Percona Server", 17, 0, null)]
|
||||
public void should_parse_postgres_database_version(string serverVersion, int majorVersion, int minorVersion, int? buildVersion)
|
||||
{
|
||||
var version = DatabaseVersionParser.ParseServerVersion(serverVersion);
|
||||
|
||||
version.Should().NotBeNull();
|
||||
version.Major.Should().Be(majorVersion);
|
||||
version.Minor.Should().Be(minorVersion);
|
||||
|
||||
if (buildVersion.HasValue)
|
||||
{
|
||||
version.Build.Should().Be(buildVersion.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
|
||||
|
||||
var fifthTorrentInfo = releases.ElementAt(28) as TorrentInfo;
|
||||
|
||||
fifthTorrentInfo.Title.Should().Be("[-ZR-] Dr. STONE: STONE WARS S02 [Web][MKV][h264][1080p][AAC 2.0][Dual Audio][Softsubs (-ZR-)]");
|
||||
fifthTorrentInfo.Title.Should().Be("[-ZR-] Dr. STONE: STONE WARS 2021 S02 [Web][MKV][h264][1080p][AAC 2.0][Dual Audio][Softsubs (-ZR-)]");
|
||||
fifthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
||||
fifthTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/944509/download/somepass");
|
||||
fifthTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/944509/group");
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 19:20:19"));
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19"));
|
||||
torrentInfo.Size.Should().Be(8300512414);
|
||||
torrentInfo.InfoHash.Should().Be(null);
|
||||
torrentInfo.MagnetUrl.Should().Be(null);
|
||||
|
||||
@@ -26,15 +26,15 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new IndexerDefinition()
|
||||
Subject.Definition = new IndexerDefinition
|
||||
{
|
||||
Name = "HdBits",
|
||||
Settings = new HDBitsSettings() { ApiKey = "fakekey" }
|
||||
Settings = new HDBitsSettings { ApiKey = "fakekey" }
|
||||
};
|
||||
|
||||
_movieSearchCriteria = new MovieSearchCriteria
|
||||
{
|
||||
Categories = new int[] { 2000, 2010 },
|
||||
Categories = new[] { 2000, 2010 },
|
||||
ImdbId = "0076759"
|
||||
};
|
||||
}
|
||||
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
|
||||
var torrents = (await Subject.Fetch(_movieSearchCriteria)).Releases;
|
||||
|
||||
torrents.Should().HaveCount(2);
|
||||
torrents.First().Should().BeOfType<HDBitsInfo>();
|
||||
torrents.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
var first = torrents.First() as TorrentInfo;
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ namespace NzbDrone.Core.Test.IndexerTests.RedactedTests
|
||||
|
||||
torrentInfo.Title.Should().Be("Red Hot Chili Peppers - Californication (1999) [Album] [US / Reissue 2020] [FLAC 24bit Lossless / Vinyl]");
|
||||
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
||||
torrentInfo.DownloadUrl.Should().Be("https://redacted.ch/ajax.php?action=download&id=3892313");
|
||||
torrentInfo.InfoUrl.Should().Be("https://redacted.ch/torrents.php?id=16720&torrentid=3892313");
|
||||
torrentInfo.DownloadUrl.Should().Be("https://redacted.sh/ajax.php?action=download&id=3892313");
|
||||
torrentInfo.InfoUrl.Should().Be("https://redacted.sh/torrents.php?id=16720&torrentid=3892313");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-12-17 08:02:35"));
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Applications
|
||||
|
||||
foreach (var application in applications)
|
||||
{
|
||||
if (blockedApplications.TryGetValue(application.Definition.Id, out var blockedApplicationStatus))
|
||||
if (blockedApplications.TryGetValue(application.Definition.Id, out var blockedApplicationStatus) && blockedApplicationStatus.DisabledTill.HasValue)
|
||||
{
|
||||
_logger.Debug("Temporarily ignoring application {0} till {1} due to recent failures.", application.Definition.Name, blockedApplicationStatus.DisabledTill.Value.ToLocalTime());
|
||||
continue;
|
||||
|
||||
@@ -66,12 +66,19 @@ namespace NzbDrone.Core.Backup
|
||||
{
|
||||
_logger.ProgressInfo("Starting Backup");
|
||||
|
||||
var backupFolder = GetBackupFolder(backupType);
|
||||
|
||||
_diskProvider.EnsureFolder(_backupTempFolder);
|
||||
_diskProvider.EnsureFolder(GetBackupFolder(backupType));
|
||||
_diskProvider.EnsureFolder(backupFolder);
|
||||
|
||||
if (!_diskProvider.FolderWritable(backupFolder))
|
||||
{
|
||||
throw new UnauthorizedAccessException($"Backup folder {backupFolder} is not writable");
|
||||
}
|
||||
|
||||
var dateNow = DateTime.Now;
|
||||
var backupFilename = $"prowlarr_backup_v{BuildInfo.Version}_{dateNow:yyyy.MM.dd_HH.mm.ss}.zip";
|
||||
var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
|
||||
var backupPath = Path.Combine(backupFolder, backupFilename);
|
||||
|
||||
Cleanup();
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ namespace NzbDrone.Core.Configuration
|
||||
string PostgresPassword { get; }
|
||||
string PostgresMainDb { get; }
|
||||
string PostgresLogDb { get; }
|
||||
bool TrustCgnatIpAddresses { get; }
|
||||
}
|
||||
|
||||
public class ConfigFileProvider : IConfigFileProvider
|
||||
@@ -273,7 +274,7 @@ namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
var instanceName = _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
|
||||
|
||||
if (instanceName.ContainsIgnoreCase(BuildInfo.AppName))
|
||||
if (instanceName.Contains(BuildInfo.AppName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return instanceName;
|
||||
}
|
||||
@@ -479,5 +480,7 @@ namespace NzbDrone.Core.Configuration
|
||||
SetValue("ApiKey", GenerateApiKey());
|
||||
_eventAggregator.PublishEvent(new ApiKeyChangedEvent());
|
||||
}
|
||||
|
||||
public bool TrustCgnatIpAddresses => _authOptions.TrustCgnatIpAddresses ?? GetValueBoolean("TrustCgnatIpAddresses", false, persist: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,12 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
|
||||
|
||||
public bool TrustCgnatIpAddresses
|
||||
{
|
||||
get { return GetValueBoolean("TrustCgnatIpAddresses", false); }
|
||||
set { SetValue("TrustCgnatIpAddresses", value); }
|
||||
}
|
||||
|
||||
private string GetValue(string key)
|
||||
{
|
||||
return GetValue(key, string.Empty);
|
||||
|
||||
@@ -2,18 +2,17 @@ using System;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class DapperTimeSpanConverter : SqlMapper.TypeHandler<TimeSpan>
|
||||
{
|
||||
public override void SetValue(IDbDataParameter parameter, TimeSpan value)
|
||||
{
|
||||
parameter.Value = value.ToString();
|
||||
}
|
||||
namespace NzbDrone.Core.Datastore.Converters;
|
||||
|
||||
public override TimeSpan Parse(object value)
|
||||
{
|
||||
return TimeSpan.Parse((string)value);
|
||||
}
|
||||
public class TimeSpanConverter : SqlMapper.TypeHandler<TimeSpan>
|
||||
{
|
||||
public override void SetValue(IDbDataParameter parameter, TimeSpan value)
|
||||
{
|
||||
parameter.Value = value.ToString();
|
||||
}
|
||||
|
||||
public override TimeSpan Parse(object value)
|
||||
{
|
||||
return value is string str ? TimeSpan.Parse(str) : TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SQLite;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dapper;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
@@ -52,9 +51,8 @@ namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
using var db = _datamapperFactory();
|
||||
var dbConnection = db as DbConnection;
|
||||
var version = Regex.Replace(dbConnection.ServerVersion, @"\(.*?\)", "");
|
||||
|
||||
return new Version(version);
|
||||
return DatabaseVersionParser.ParseServerVersion(dbConnection.ServerVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
src/NzbDrone.Core/Datastore/DatabaseVersionParser.cs
Normal file
16
src/NzbDrone.Core/Datastore/DatabaseVersionParser.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NzbDrone.Core.Datastore;
|
||||
|
||||
public static class DatabaseVersionParser
|
||||
{
|
||||
private static readonly Regex VersionRegex = new (@"^[^ ]+", RegexOptions.Compiled);
|
||||
|
||||
public static Version ParseServerVersion(string serverVersion)
|
||||
{
|
||||
var match = VersionRegex.Match(serverVersion);
|
||||
|
||||
return match.Success ? new Version(match.Value) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(042)]
|
||||
public class myanonamouse_freeleech_wedge_options : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.WithConnection(MigrateIndexersToWedgeOptions);
|
||||
}
|
||||
|
||||
private void MigrateIndexersToWedgeOptions(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
var updated = new List<object>();
|
||||
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Indexers\" WHERE \"Implementation\" = 'MyAnonamouse'";
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = reader.GetInt32(0);
|
||||
var settings = Json.Deserialize<JObject>(reader.GetString(1));
|
||||
|
||||
if (settings.ContainsKey("freeleech") && settings.Value<JToken>("freeleech").Type == JTokenType.Boolean)
|
||||
{
|
||||
var optionValue = settings.Value<bool>("freeleech") switch
|
||||
{
|
||||
true => 2, // Required
|
||||
_ => 0 // Never
|
||||
};
|
||||
|
||||
settings.Remove("freeleech");
|
||||
settings.Add("useFreeleechWedge", optionValue);
|
||||
}
|
||||
|
||||
updated.Add(new
|
||||
{
|
||||
Id = id,
|
||||
Settings = settings.ToJson()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updateSql = "UPDATE \"Indexers\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
|
||||
conn.Execute(updateSql, updated, transaction: tran);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,6 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
SqlMapper.RemoveTypeMap(typeof(DateTime));
|
||||
SqlMapper.AddTypeHandler(new DapperUtcConverter());
|
||||
SqlMapper.AddTypeHandler(new DapperTimeSpanConverter());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<Dictionary<string, string>>());
|
||||
SqlMapper.AddTypeHandler(new CookieConverter());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
|
||||
@@ -123,6 +122,9 @@ namespace NzbDrone.Core.Datastore
|
||||
SqlMapper.RemoveTypeMap(typeof(Guid));
|
||||
SqlMapper.RemoveTypeMap(typeof(Guid?));
|
||||
SqlMapper.AddTypeHandler(new GuidConverter());
|
||||
SqlMapper.RemoveTypeMap(typeof(TimeSpan));
|
||||
SqlMapper.RemoveTypeMap(typeof(TimeSpan?));
|
||||
SqlMapper.AddTypeHandler(new TimeSpanConverter());
|
||||
SqlMapper.AddTypeHandler(new CommandConverter());
|
||||
SqlMapper.AddTypeHandler(new SystemVersionConverter());
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
Dictionary<string, QBittorrentLabel> GetLabels(QBittorrentSettings settings);
|
||||
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
|
||||
void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings);
|
||||
void PauseTorrent(string hash, QBittorrentSettings settings);
|
||||
void ResumeTorrent(string hash, QBittorrentSettings settings);
|
||||
void SetForceStart(string hash, bool enabled, QBittorrentSettings settings);
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
request.AddFormParameter("paused", false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
@@ -176,7 +176,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
request.AddFormParameter("paused", false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
@@ -212,7 +212,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// if setCategory fails due to method not being found, then try older setLabel command for qBittorrent < v.3.3.5
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.NotFound)
|
||||
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
var setLabelRequest = BuildRequest(settings).Resource("/command/setLabel")
|
||||
.Post()
|
||||
@@ -254,7 +254,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// qBittorrent rejects all Prio commands with 403: Forbidden if Options -> BitTorrent -> Torrent Queueing is not enabled
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -263,22 +263,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
}
|
||||
|
||||
public void PauseTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/pause")
|
||||
.Post()
|
||||
.AddFormParameter("hash", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void ResumeTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/resume")
|
||||
.Post()
|
||||
.AddFormParameter("hash", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/setForceStart")
|
||||
|
||||
@@ -246,14 +246,20 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
request.AddFormParameter("category", category);
|
||||
}
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
|
||||
// Avoid extraneous API version check if initial state is ForceStart
|
||||
if ((QBittorrentState)settings.InitialState is QBittorrentState.Start or QBittorrentState.Stop)
|
||||
{
|
||||
request.AddFormParameter("paused", false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
var stoppedParameterName = GetApiVersion(settings) >= new Version(2, 11, 0) ? "stopped" : "paused";
|
||||
|
||||
// Note: ForceStart is handled by separate api call
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
|
||||
{
|
||||
request.AddFormParameter(stoppedParameterName, false);
|
||||
}
|
||||
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
|
||||
{
|
||||
request.AddFormParameter(stoppedParameterName, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.SequentialOrder)
|
||||
@@ -291,7 +297,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// setShareLimits was added in api v2.0.1 so catch it case of the unlikely event that someone has api v2.0
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.NotFound)
|
||||
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -313,7 +319,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// qBittorrent rejects all Prio commands with 409: Conflict if Options -> BitTorrent -> Torrent Queueing is not enabled
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Conflict)
|
||||
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -322,22 +328,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
}
|
||||
|
||||
public void PauseTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/pause")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void ResumeTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/resume")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/setForceStart")
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
using NzbDrone.Core.Annotations;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
public enum QBittorrentState
|
||||
{
|
||||
[FieldOption(Label = "Started")]
|
||||
Start = 0,
|
||||
|
||||
[FieldOption(Label = "Force Started")]
|
||||
ForceStart = 1,
|
||||
Pause = 2
|
||||
|
||||
[FieldOption(Label = "Stopped")]
|
||||
Stop = 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Net;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
@@ -208,7 +209,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
|
||||
private void AuthenticateClient(HttpRequestBuilder requestBuilder, TransmissionSettings settings, bool reauthenticate = false)
|
||||
{
|
||||
var authKey = string.Format("{0}:{1}", requestBuilder.BaseUrl, settings.Password);
|
||||
var authKey = $"{requestBuilder.BaseUrl}:{settings.Password}";
|
||||
|
||||
var sessionId = _authSessionIDCache.Find(authKey);
|
||||
|
||||
@@ -220,24 +221,26 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
authLoginRequest.SuppressHttpError = true;
|
||||
|
||||
var response = _httpClient.Execute(authLoginRequest);
|
||||
if (response.StatusCode == HttpStatusCode.MovedPermanently)
|
||||
{
|
||||
var url = response.Headers.GetSingleValue("Location");
|
||||
|
||||
throw new DownloadClientException("Remote site redirected to " + url);
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.Conflict)
|
||||
switch (response.StatusCode)
|
||||
{
|
||||
sessionId = response.Headers.GetSingleValue("X-Transmission-Session-Id");
|
||||
case HttpStatusCode.MovedPermanently:
|
||||
var url = response.Headers.GetSingleValue("Location");
|
||||
|
||||
if (sessionId == null)
|
||||
{
|
||||
throw new DownloadClientException("Remote host did not return a Session Id.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with Transmission.");
|
||||
throw new DownloadClientException("Remote site redirected to " + url);
|
||||
case HttpStatusCode.Forbidden:
|
||||
throw new DownloadClientException($"Failed to authenticate with Transmission. It may be necessary to add {BuildInfo.AppName}'s IP address to RPC whitelist.");
|
||||
case HttpStatusCode.Conflict:
|
||||
sessionId = response.Headers.GetSingleValue("X-Transmission-Session-Id");
|
||||
|
||||
if (sessionId == null)
|
||||
{
|
||||
throw new DownloadClientException("Remote host did not return a Session Id.");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with Transmission.");
|
||||
}
|
||||
|
||||
_logger.Debug("Transmission authentication succeeded.");
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
if (blockedClients.TryGetValue(client.Definition.Id, out var downloadClientStatus))
|
||||
if (blockedClients.TryGetValue(client.Definition.Id, out var downloadClientStatus) && downloadClientStatus.DisabledTill.HasValue)
|
||||
{
|
||||
_logger.Debug("Temporarily ignoring download client {0} till {1} due to recent failures.", client.Definition.Name, downloadClientStatus.DisabledTill.Value.ToLocalTime());
|
||||
continue;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
private readonly IHttpRequestBuilderFactory _cloudRequestBuilder;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SystemTimeCheck(IHttpClient client, IProwlarrCloudRequestBuilder cloudRequestBuilder, ILocalizationService localizationService, Logger logger)
|
||||
public SystemTimeCheck(IHttpClient client, IProwlarrCloudRequestBuilder cloudRequestBuilder, Logger logger, ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_client = client;
|
||||
@@ -29,19 +29,26 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
var request = _cloudRequestBuilder.Create()
|
||||
.Resource("/time")
|
||||
.Build();
|
||||
|
||||
var response = _client.Execute(request);
|
||||
var result = Json.Deserialize<ServiceTimeResponse>(response.Content);
|
||||
var systemTime = DateTime.UtcNow;
|
||||
|
||||
// +/- more than 1 day
|
||||
if (Math.Abs(result.DateTimeUtc.Subtract(systemTime).TotalDays) >= 1)
|
||||
try
|
||||
{
|
||||
_logger.Error("System time mismatch. SystemTime: {0} Expected Time: {1}. Update system time", systemTime, result.DateTimeUtc);
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("SystemTimeCheckMessage"));
|
||||
var request = _cloudRequestBuilder.Create()
|
||||
.Resource("/time")
|
||||
.Build();
|
||||
|
||||
var response = _client.Execute(request);
|
||||
var result = Json.Deserialize<ServiceTimeResponse>(response.Content);
|
||||
var systemTime = DateTime.UtcNow;
|
||||
|
||||
// +/- more than 1 day
|
||||
if (Math.Abs(result.DateTimeUtc.Subtract(systemTime).TotalDays) >= 1)
|
||||
{
|
||||
_logger.Error("System time mismatch. SystemTime: {0} Expected Time: {1}. Update system time", systemTime, result.DateTimeUtc);
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("SystemTimeHealthCheckMessage"), "#system-time-off");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "Unable to verify system time");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
if (latestAvailable != null)
|
||||
{
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
BuildInfo.BuildDateTime.Before(DateTime.UtcNow.AddDays(-180)) ? HealthCheckResult.Error : HealthCheckResult.Warning,
|
||||
_localizationService.GetLocalizedString("UpdateAvailableHealthCheckMessage", new Dictionary<string, object>
|
||||
{
|
||||
{ "version", $"v{latestAvailable.Version}" }
|
||||
|
||||
@@ -301,6 +301,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
};
|
||||
private static readonly HashSet<string> ExcludedFileExtensions = new (StringComparer.OrdinalIgnoreCase) { ".mka", ".mds", ".md5", ".nfo", ".sfv", ".ass", ".mks", ".srt", ".ssa", ".sup", ".jpeg", ".jpg", ".png", ".otf", ".ttf" };
|
||||
|
||||
private static readonly string[] PropertiesSeparator = { " | ", " / " };
|
||||
|
||||
private readonly AnimeBytesSettings _settings;
|
||||
|
||||
public AnimeBytesParser(AnimeBytesSettings settings)
|
||||
@@ -324,6 +326,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var response = STJson.Deserialize<AnimeBytesResponse>(indexerResponse.Content);
|
||||
|
||||
if (response.Error.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
throw new IndexerException(indexerResponse, "Unexpected response from indexer request: {0}", response.Error);
|
||||
}
|
||||
|
||||
if (response.Matches == 0)
|
||||
{
|
||||
return releaseInfos.ToArray();
|
||||
@@ -393,38 +400,48 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var minimumSeedTime = 259200 + (int)(size / (int)Math.Pow(1024, 3) * 18000);
|
||||
|
||||
var propertyList = WebUtility.HtmlDecode(torrent.Property)
|
||||
.Split(new[] { " | ", " / " }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
|
||||
.Split(PropertiesSeparator, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
|
||||
.ToList();
|
||||
|
||||
propertyList.RemoveAll(p => ExcludedProperties.Any(p.ContainsIgnoreCase));
|
||||
var properties = propertyList.ToHashSet();
|
||||
|
||||
if (torrent.Files.Any(f => f.FileName.ContainsIgnoreCase("Remux")))
|
||||
{
|
||||
var resolutionProperty = properties.FirstOrDefault(RemuxResolutions.ContainsIgnoreCase);
|
||||
|
||||
if (resolutionProperty.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
properties.Add($"{resolutionProperty} Remux");
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.Any(p => p.StartsWithIgnoreCase("M2TS")))
|
||||
if (properties.Any(p => p.StartsWith("M2TS", StringComparison.Ordinal)))
|
||||
{
|
||||
properties.Add("BR-DISK");
|
||||
}
|
||||
|
||||
if (_settings.ExcludeRaw &&
|
||||
properties.Any(p => p.StartsWithIgnoreCase("RAW") || p.Contains("BR-DISK")))
|
||||
var isBluRayDisk = properties.Any(p => p.Equals("RAW", StringComparison.Ordinal) || p.StartsWith("M2TS", StringComparison.Ordinal) || p.StartsWith("ISO", StringComparison.Ordinal));
|
||||
|
||||
if (_settings.ExcludeRaw && isBluRayDisk)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
properties = properties
|
||||
.Select(property =>
|
||||
{
|
||||
if (isBluRayDisk)
|
||||
{
|
||||
property = Regex.Replace(property, @"\b(H\.?265)\b", "HEVC", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
property = Regex.Replace(property, @"\b(H\.?264)\b", "AVC", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
if (torrent.Files.Any(f => f.FileName.ContainsIgnoreCase("Remux"))
|
||||
&& RemuxResolutions.ContainsIgnoreCase(property))
|
||||
{
|
||||
property += " Remux";
|
||||
}
|
||||
|
||||
return property;
|
||||
})
|
||||
.ToHashSet();
|
||||
|
||||
int? season = null;
|
||||
int? episode = null;
|
||||
|
||||
var releaseInfo = _settings.EnableSonarrCompatibility && categoryName == "Anime" ? "S01" : "";
|
||||
var editionTitle = torrent.EditionData.EditionTitle;
|
||||
var editionTitle = torrent.EditionData?.EditionTitle;
|
||||
|
||||
if (editionTitle.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
@@ -569,7 +586,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (_settings.UseFilenameForSingleEpisodes)
|
||||
{
|
||||
var files = torrent.Files;
|
||||
var files = torrent.Files.ToList();
|
||||
|
||||
if (files.Count > 1)
|
||||
{
|
||||
@@ -607,11 +624,13 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
var useYearInTitle = year is > 0 && torrent.Files.Any(f => f.FileName.Contains(year.Value.ToString()));
|
||||
|
||||
foreach (var title in synonyms)
|
||||
{
|
||||
var releaseTitle = groupName is "Movie" or "Live Action Movie" ?
|
||||
$"{releaseGroup}{title} {year} {infoString}" :
|
||||
$"{releaseGroup}{title} {releaseInfo} {infoString}";
|
||||
$"{releaseGroup}{title}{(useYearInTitle ? $" {year}" : string.Empty)} {releaseInfo} {infoString}";
|
||||
|
||||
var guid = new Uri(details + "?nh=" + HashUtil.CalculateMd5(title));
|
||||
|
||||
@@ -650,7 +669,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var advancedSeasonRegex = new Regex(@"\b(?:(?<season>\d+)(?:st|nd|rd|th) Season|Season (?<season>\d+))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
var seasonCharactersRegex = new Regex(@"(I{2,})$", RegexOptions.Compiled);
|
||||
var seasonNumberRegex = new Regex(@"\b(?<!Part[- ._])(?:S)?(?<season>[2-9])$", RegexOptions.Compiled);
|
||||
var seasonNumberRegex = new Regex(@"\b(?<!Part[- ._])(?<!\d[/])(?:S)?(?<season>[2-9])$", RegexOptions.Compiled);
|
||||
|
||||
foreach (var title in titles)
|
||||
{
|
||||
@@ -755,7 +774,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public int Matches { get; set; }
|
||||
|
||||
[JsonPropertyName("Groups")]
|
||||
public AnimeBytesGroup[] Groups { get; set; }
|
||||
public IReadOnlyCollection<AnimeBytesGroup> Groups { get; set; }
|
||||
|
||||
public string Error { get; set; }
|
||||
}
|
||||
|
||||
public class AnimeBytesGroup
|
||||
@@ -783,16 +804,16 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public string Image { get; set; }
|
||||
|
||||
[JsonPropertyName("SynonymnsV2")]
|
||||
public Dictionary<string, string> Synonymns { get; set; }
|
||||
public IReadOnlyDictionary<string, string> Synonymns { get; set; }
|
||||
|
||||
[JsonPropertyName("Description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonPropertyName("Tags")]
|
||||
public List<string> Tags { get; set; }
|
||||
public IReadOnlyCollection<string> Tags { get; set; }
|
||||
|
||||
[JsonPropertyName("Torrents")]
|
||||
public List<AnimeBytesTorrent> Torrents { get; set; }
|
||||
public IReadOnlyCollection<AnimeBytesTorrent> Torrents { get; set; }
|
||||
}
|
||||
|
||||
public class AnimeBytesTorrent
|
||||
@@ -831,7 +852,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public int FileCount { get; set; }
|
||||
|
||||
[JsonPropertyName("FileList")]
|
||||
public List<AnimeBytesFile> Files { get; set; }
|
||||
public IReadOnlyCollection<AnimeBytesFile> Files { get; set; }
|
||||
|
||||
[JsonPropertyName("UploadTime")]
|
||||
public string UploadTime { get; set; }
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Html.Parser;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -44,46 +43,19 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return new AnimeTorrentsParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
UpdateCookies(null, null);
|
||||
|
||||
var loginUrl = Settings.BaseUrl + "login.php";
|
||||
|
||||
var loginPage = await ExecuteAuth(new HttpRequest(loginUrl));
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(loginUrl)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
AllowAutoRedirect = true
|
||||
};
|
||||
|
||||
var authLoginRequest = requestBuilder
|
||||
.Post()
|
||||
.SetCookies(loginPage.GetCookies())
|
||||
.AddFormParameter("username", Settings.Username)
|
||||
.AddFormParameter("password", Settings.Password)
|
||||
.AddFormParameter("form", "login")
|
||||
.AddFormParameter("rememberme[]", "1")
|
||||
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.SetHeader("Referer", loginUrl)
|
||||
.Build();
|
||||
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
|
||||
if (response.Content == null || !response.Content.Contains("logout.php"))
|
||||
{
|
||||
throw new IndexerAuthException("AnimeTorrents authentication failed");
|
||||
}
|
||||
|
||||
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
|
||||
|
||||
_logger.Debug("AnimeTorrents authentication succeeded");
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
return httpResponse.Content.Contains("Access Denied!") || httpResponse.Content.Contains("login.php");
|
||||
if (httpResponse.Content.Contains("Access Denied!") || httpResponse.Content.Contains("login.php"))
|
||||
{
|
||||
throw new IndexerAuthException("AnimeTorrents authentication with cookies failed.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetCookies()
|
||||
{
|
||||
return CookieUtil.CookieHeaderToDictionary(Settings.Cookie);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
@@ -119,6 +91,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.BooksComics, "Doujinshi");
|
||||
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.BooksComics, "Doujinshi 18+");
|
||||
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.Audio, "OST");
|
||||
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
|
||||
|
||||
return caps;
|
||||
}
|
||||
@@ -291,7 +264,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var qTitleLink = row.QuerySelector("td:nth-of-type(2) a:nth-of-type(1)");
|
||||
var title = qTitleLink?.TextContent.Trim();
|
||||
|
||||
// If we search an get no results, we still get a table just with no info.
|
||||
// If we search and get no results, we still get a table just with no info.
|
||||
if (title.IsNullOrWhiteSpace())
|
||||
{
|
||||
break;
|
||||
@@ -306,6 +279,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var connections = row.QuerySelector("td:nth-of-type(8)").TextContent.Trim().Split('/', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
var seeders = ParseUtil.CoerceInt(connections[0]);
|
||||
var leechers = ParseUtil.CoerceInt(connections[1]);
|
||||
var grabs = ParseUtil.CoerceInt(connections[2]);
|
||||
|
||||
var categoryLink = row.QuerySelector("td:nth-of-type(1) a")?.GetAttribute("href") ?? string.Empty;
|
||||
var categoryId = ParseUtil.GetArgumentFromQueryString(categoryLink, "cat");
|
||||
@@ -327,17 +302,17 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
PublishDate = publishedDate,
|
||||
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-of-type(6)").TextContent.Trim()),
|
||||
Seeders = seeders,
|
||||
Peers = ParseUtil.CoerceInt(connections[1]) + seeders,
|
||||
Grabs = ParseUtil.CoerceInt(connections[2]),
|
||||
Peers = leechers + seeders,
|
||||
Grabs = grabs,
|
||||
DownloadVolumeFactor = downloadVolumeFactor,
|
||||
UploadVolumeFactor = 1,
|
||||
Genres = row.QuerySelectorAll("td:nth-of-type(2) a.tortags").Select(t => t.TextContent.Trim()).ToList()
|
||||
};
|
||||
|
||||
var uLFactorImg = row.QuerySelector("img[alt*=\"x Multiplier Torrent\"]");
|
||||
if (uLFactorImg != null)
|
||||
var uploadFactor = row.QuerySelector("img[alt*=\"x Multiplier Torrent\"]")?.GetAttribute("alt");
|
||||
if (uploadFactor != null)
|
||||
{
|
||||
release.UploadVolumeFactor = ParseUtil.CoerceDouble(uLFactorImg.GetAttribute("alt").Split('x')[0]);
|
||||
release.UploadVolumeFactor = ParseUtil.CoerceDouble(uploadFactor.Split('x')[0]);
|
||||
}
|
||||
|
||||
releaseInfos.Add(release);
|
||||
@@ -349,7 +324,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnimeTorrentsSettings : UserPassTorrentBaseSettings
|
||||
public class AnimeTorrentsSettings : CookieTorrentBaseSettings
|
||||
{
|
||||
public AnimeTorrentsSettings()
|
||||
{
|
||||
@@ -360,7 +335,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
[FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Show freeleech torrents only")]
|
||||
public bool FreeleechOnly { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Downloadable Only", Type = FieldType.Checkbox, HelpText = "Search downloadable torrents only (enable this only if your account class is Newbie)")]
|
||||
[FieldDefinition(5, Label = "Downloadable Only", Type = FieldType.Checkbox, HelpText = "Search downloadable torrents only (enable this only if your account class is Newbie)", Advanced = true)]
|
||||
public bool DownloadableOnly { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return FilterReleasesByQuery(cleanReleases, searchCriteria).ToList();
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
private static IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
@@ -69,7 +69,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
},
|
||||
Flags = new List<IndexerFlag>
|
||||
{
|
||||
IndexerFlag.Internal
|
||||
IndexerFlag.Internal,
|
||||
IndexerFlag.Exclusive,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -91,7 +92,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
_capabilities = capabilities;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria, string term, string imdbId = null, int tmdbId = 0)
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria, string searchTerm, string imdbId = null, int tmdbId = 0)
|
||||
{
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
@@ -128,9 +129,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
body.Add("tmdb_id", tmdbId);
|
||||
}
|
||||
|
||||
if (term.IsNotNullOrWhiteSpace())
|
||||
if (searchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
body.Add("search", term);
|
||||
body.Add("search", searchTerm.Trim());
|
||||
}
|
||||
|
||||
var cats = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
||||
@@ -198,7 +199,16 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, searchCriteria.SanitizedTvSearchString, searchCriteria.FullImdbId));
|
||||
var searchTerm = searchCriteria.SanitizedTvSearchString;
|
||||
|
||||
if (searchCriteria.Season is > 0 &&
|
||||
searchCriteria.Episode.IsNotNullOrWhiteSpace() &&
|
||||
DateTime.TryParseExact($"{searchCriteria.Season} {searchCriteria.Episode}", "yyyy MM/dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var showDate))
|
||||
{
|
||||
searchTerm = $"{searchCriteria.SanitizedSearchTerm} {showDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, searchTerm, searchCriteria.FullImdbId));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -275,13 +285,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var details = row.InfoUrl;
|
||||
var link = row.DownloadLink;
|
||||
|
||||
var flags = new HashSet<IndexerFlag>();
|
||||
|
||||
if (row.Internal)
|
||||
{
|
||||
flags.Add(IndexerFlag.Internal);
|
||||
}
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Title = row.Name,
|
||||
@@ -291,7 +294,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Guid = details,
|
||||
Categories = _categories.MapTrackerCatDescToNewznab(row.Category),
|
||||
PublishDate = DateTime.Parse(row.CreatedAt, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal),
|
||||
IndexerFlags = flags,
|
||||
IndexerFlags = GetIndexerFlags(row),
|
||||
Size = row.Size,
|
||||
Grabs = row.Grabs,
|
||||
Seeders = row.Seeders,
|
||||
@@ -319,6 +322,23 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static HashSet<IndexerFlag> GetIndexerFlags(BeyondHDTorrent item)
|
||||
{
|
||||
var flags = new HashSet<IndexerFlag>();
|
||||
|
||||
if (item.Internal)
|
||||
{
|
||||
flags.Add(IndexerFlag.Internal);
|
||||
}
|
||||
|
||||
if (item.Exclusive)
|
||||
{
|
||||
flags.Add(IndexerFlag.Exclusive);
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
@@ -326,8 +346,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public BeyondHDSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.ApiKey).NotEmpty();
|
||||
RuleFor(c => c.RssKey).NotEmpty();
|
||||
RuleFor(c => c.ApiKey).NotEmpty().Length(32);
|
||||
RuleFor(c => c.RssKey).NotEmpty().Length(32);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,6 +498,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public bool Limited { get; set; }
|
||||
|
||||
public bool Exclusive { get; set; }
|
||||
|
||||
public bool Internal { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
Guid = $"BTN-{torrent.TorrentID}",
|
||||
InfoUrl = $"{protocol}//broadcasthe.net/torrents.php?id={torrent.GroupID}&torrentid={torrent.TorrentID}",
|
||||
DownloadUrl = RegexProtocol.Replace(torrent.DownloadURL, protocol),
|
||||
Title = CleanReleaseName(torrent.ReleaseName),
|
||||
Title = GetTitle(torrent),
|
||||
Categories = _categories.MapTrackerCatToNewznab(torrent.Resolution),
|
||||
InfoHash = torrent.InfoHash,
|
||||
Size = torrent.Size,
|
||||
@@ -136,9 +136,17 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
||||
return releaseInfos;
|
||||
}
|
||||
|
||||
private string CleanReleaseName(string releaseName)
|
||||
private static string GetTitle(BroadcastheNetTorrent torrent)
|
||||
{
|
||||
return releaseName.Replace("\\", "");
|
||||
var releaseName = torrent.ReleaseName.Replace("\\", "");
|
||||
|
||||
if (torrent.Container.ToUpperInvariant() is "M2TS" or "ISO")
|
||||
{
|
||||
releaseName = Regex.Replace(releaseName, @"\b(H\.?265)\b", "HEVC", RegexOptions.Compiled);
|
||||
releaseName = Regex.Replace(releaseName, @"\b(H\.?264)\b", "AVC", RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
return releaseName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class FileListParser : IParseIndexerResponse
|
||||
InfoUrl = GetInfoUrl(id),
|
||||
Seeders = row.Seeders,
|
||||
Peers = row.Leechers + row.Seeders,
|
||||
PublishDate = DateTime.Parse(row.UploadDate + " +0300", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
|
||||
PublishDate = DateTime.Parse(row.UploadDate + " +0200", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
|
||||
Description = row.SmallDescription,
|
||||
Genres = row.SmallDescription.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(),
|
||||
ImdbId = imdbId,
|
||||
@@ -101,7 +101,7 @@ public class FileListParser : IParseIndexerResponse
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/download.php")
|
||||
.AddQueryParam("id", torrentId.ToString())
|
||||
.AddQueryParam("passkey", _settings.Passkey);
|
||||
.AddQueryParam("passkey", _settings.Passkey.Trim());
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
@@ -95,3 +95,8 @@ public class GazelleIndexResponse
|
||||
public string Authkey { get; set; }
|
||||
public string Passkey { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleErrorResponse
|
||||
{
|
||||
public string Error { get; init; }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -32,7 +33,9 @@ public class GazelleParser : IParseIndexerResponse
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
|
||||
STJson.TryDeserialize<GazelleErrorResponse>(indexerResponse.Content, out var errorResponse);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request: {errorResponse?.Error ?? "Check the logs for more information."}");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
|
||||
@@ -7,6 +7,7 @@ using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.Gazelle;
|
||||
@@ -148,7 +149,9 @@ public class GreatPosterWallParser : GazelleParser
|
||||
throw new IndexerException(indexerResponse, $"Redirected to {indexerResponse.HttpResponse.RedirectUrl} from indexer request");
|
||||
}
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
|
||||
STJson.TryDeserialize<GazelleErrorResponse>(indexerResponse.Content, out var errorResponse);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request: {errorResponse?.Error ?? "Check the logs for more information."}");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
return new HDBitsParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
private static IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
@@ -43,6 +43,11 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
},
|
||||
Flags = new List<IndexerFlag>
|
||||
{
|
||||
IndexerFlag.Internal,
|
||||
IndexerFlag.Exclusive,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -85,6 +85,9 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
[JsonProperty(PropertyName = "type_origin")]
|
||||
public int TypeOrigin { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "type_exclusive")]
|
||||
public int TypeExclusive { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "imdb")]
|
||||
public ImdbInfo ImdbInfo { get; set; }
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
{
|
||||
public class HDBitsInfo : TorrentInfo
|
||||
{
|
||||
public bool? Internal { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -62,16 +62,8 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
}
|
||||
|
||||
var id = result.Id;
|
||||
var internalRelease = result.TypeOrigin == 1;
|
||||
|
||||
var flags = new HashSet<IndexerFlag>();
|
||||
|
||||
if (internalRelease)
|
||||
{
|
||||
flags.Add(IndexerFlag.Internal);
|
||||
}
|
||||
|
||||
releaseInfos.Add(new HDBitsInfo
|
||||
releaseInfos.Add(new TorrentInfo
|
||||
{
|
||||
Guid = $"HDBits-{id}",
|
||||
Title = GetTitle(result),
|
||||
@@ -85,28 +77,43 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
Files = (int)result.NumFiles,
|
||||
Peers = result.Leechers + result.Seeders,
|
||||
PublishDate = result.Added.ToUniversalTime(),
|
||||
Internal = internalRelease,
|
||||
Year = result.ImdbInfo?.Year ?? 0,
|
||||
ImdbId = result.ImdbInfo?.Id ?? 0,
|
||||
TvdbId = result.TvdbInfo?.Id ?? 0,
|
||||
DownloadVolumeFactor = GetDownloadVolumeFactor(result),
|
||||
UploadVolumeFactor = GetUploadVolumeFactor(result),
|
||||
IndexerFlags = flags
|
||||
IndexerFlags = GetIndexerFlags(result)
|
||||
});
|
||||
}
|
||||
|
||||
return releaseInfos.ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
private string GetTitle(TorrentQueryResponse item)
|
||||
{
|
||||
return _settings.UseFilenames && item.FileName.IsNotNullOrWhiteSpace()
|
||||
// Use release name for XXX content and full discs
|
||||
return item.TypeCategory != 7 && item.TypeMedium != 1 && _settings.UseFilenames && item.FileName.IsNotNullOrWhiteSpace()
|
||||
? item.FileName.Replace(".torrent", "", StringComparison.InvariantCultureIgnoreCase)
|
||||
: item.Name;
|
||||
}
|
||||
|
||||
private static HashSet<IndexerFlag> GetIndexerFlags(TorrentQueryResponse item)
|
||||
{
|
||||
var flags = new HashSet<IndexerFlag>();
|
||||
|
||||
if (item.TypeOrigin == 1)
|
||||
{
|
||||
flags.Add(IndexerFlag.Internal);
|
||||
}
|
||||
|
||||
if (item.TypeExclusive == 1)
|
||||
{
|
||||
flags.Add(IndexerFlag.Exclusive);
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
private double GetDownloadVolumeFactor(TorrentQueryResponse item)
|
||||
{
|
||||
if (item.FreeLeech == "yes")
|
||||
@@ -153,5 +160,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var qc = new NameValueCollection();
|
||||
|
||||
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories))
|
||||
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Distinct())
|
||||
{
|
||||
qc.Add(cat, string.Empty);
|
||||
}
|
||||
@@ -203,10 +203,13 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
// ipt uses sphinx, which supports boolean operators and grouping
|
||||
qc.Add("q", "+(" + imdbId + ")");
|
||||
|
||||
// search in description
|
||||
qc.Add("qf", "all");
|
||||
}
|
||||
|
||||
// changed from else if to if to support searching imdbid + season/episode in the same query
|
||||
if (!string.IsNullOrWhiteSpace(term))
|
||||
// changed from "else if" to "if" to support searching imdbid + season/episode in the same query
|
||||
if (term.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
// similar to above
|
||||
qc.Add("q", "+(" + term + ")");
|
||||
@@ -400,10 +403,13 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private static string CleanTitle(string title)
|
||||
{
|
||||
// drop invalid chars that seems to have cropped up in some titles. #6582
|
||||
// Drop invalid chars that seems to have cropped up in some titles. #6582
|
||||
title = Regex.Replace(title, @"[\u0000-\u0008\u000A-\u001F\u0100-\uFFFF]", string.Empty, RegexOptions.Compiled);
|
||||
title = Regex.Replace(title, @"[\(\[\{]REQ(UEST(ED)?)?[\)\]\}]", string.Empty, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
// Drop languages between brackets conflicting with anime release group parsing
|
||||
title = Regex.Replace(title, @"^\[[a-z0-9 ._-]+\][-._ ](?<title>.*-[a-z0-9]+)$", "${title}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
return title.Trim(' ', '-', ':');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
return httpResponse.Content.Contains("You do not have permission to access this page.");
|
||||
return !httpResponse.Content.Contains("logout.php");
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
|
||||
349
src/NzbDrone.Core/Indexers/Definitions/Knaben.cs
Normal file
349
src/NzbDrone.Core/Indexers/Definitions/Knaben.cs
Normal file
@@ -0,0 +1,349 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using static Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class Knaben : TorrentIndexerBase<NoAuthTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "Knaben";
|
||||
public override string[] IndexerUrls => new[] { "https://knaben.org/" };
|
||||
public override string[] LegacyUrls => new[] { "https://knaben.eu/" };
|
||||
public override string Description => "Knaben is a Public torrent meta-search engine";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Knaben(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new KnabenRequestGenerator(Capabilities);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new KnabenParser(Capabilities.Categories);
|
||||
}
|
||||
|
||||
private static IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1000000, NewznabStandardCategory.Audio, "Audio");
|
||||
caps.Categories.AddCategoryMapping(1001000, NewznabStandardCategory.AudioMP3, "MP3");
|
||||
caps.Categories.AddCategoryMapping(1002000, NewznabStandardCategory.AudioLossless, "Lossless");
|
||||
caps.Categories.AddCategoryMapping(1003000, NewznabStandardCategory.AudioAudiobook, "Audiobook");
|
||||
caps.Categories.AddCategoryMapping(1004000, NewznabStandardCategory.AudioVideo, "Audio Video");
|
||||
caps.Categories.AddCategoryMapping(1005000, NewznabStandardCategory.AudioOther, "Radio");
|
||||
caps.Categories.AddCategoryMapping(1006000, NewznabStandardCategory.AudioOther, "Audio Other");
|
||||
caps.Categories.AddCategoryMapping(2000000, NewznabStandardCategory.TV, "TV");
|
||||
caps.Categories.AddCategoryMapping(2001000, NewznabStandardCategory.TVHD, "TV HD");
|
||||
caps.Categories.AddCategoryMapping(2002000, NewznabStandardCategory.TVSD, "TV SD");
|
||||
caps.Categories.AddCategoryMapping(2003000, NewznabStandardCategory.TVUHD, "TV UHD");
|
||||
caps.Categories.AddCategoryMapping(2004000, NewznabStandardCategory.TVDocumentary, "Documentary");
|
||||
caps.Categories.AddCategoryMapping(2005000, NewznabStandardCategory.TVForeign, "TV Foreign");
|
||||
caps.Categories.AddCategoryMapping(2006000, NewznabStandardCategory.TVSport, "Sport");
|
||||
caps.Categories.AddCategoryMapping(2007000, NewznabStandardCategory.TVOther, "Cartoon");
|
||||
caps.Categories.AddCategoryMapping(2008000, NewznabStandardCategory.TVOther, "TV Other");
|
||||
caps.Categories.AddCategoryMapping(3000000, NewznabStandardCategory.Movies, "Movies");
|
||||
caps.Categories.AddCategoryMapping(3001000, NewznabStandardCategory.MoviesHD, "Movies HD");
|
||||
caps.Categories.AddCategoryMapping(3002000, NewznabStandardCategory.MoviesSD, "Movies SD");
|
||||
caps.Categories.AddCategoryMapping(3003000, NewznabStandardCategory.MoviesUHD, "Movies UHD");
|
||||
caps.Categories.AddCategoryMapping(3004000, NewznabStandardCategory.MoviesDVD, "Movies DVD");
|
||||
caps.Categories.AddCategoryMapping(3005000, NewznabStandardCategory.MoviesForeign, "Movies Foreign");
|
||||
caps.Categories.AddCategoryMapping(3006000, NewznabStandardCategory.MoviesForeign, "Movies Bollywood");
|
||||
caps.Categories.AddCategoryMapping(3007000, NewznabStandardCategory.Movies3D, "Movies 3D");
|
||||
caps.Categories.AddCategoryMapping(3008000, NewznabStandardCategory.MoviesOther, "Movies Other");
|
||||
caps.Categories.AddCategoryMapping(4000000, NewznabStandardCategory.PC, "PC");
|
||||
caps.Categories.AddCategoryMapping(4001000, NewznabStandardCategory.PCGames, "Games");
|
||||
caps.Categories.AddCategoryMapping(4002000, NewznabStandardCategory.PC0day, "Software");
|
||||
caps.Categories.AddCategoryMapping(4003000, NewznabStandardCategory.PCMac, "Mac");
|
||||
caps.Categories.AddCategoryMapping(4004000, NewznabStandardCategory.PCISO, "Unix");
|
||||
caps.Categories.AddCategoryMapping(5000000, NewznabStandardCategory.XXX, "XXX");
|
||||
caps.Categories.AddCategoryMapping(5001000, NewznabStandardCategory.XXXx264, "XXX Video");
|
||||
caps.Categories.AddCategoryMapping(5002000, NewznabStandardCategory.XXXImageSet, "XXX ImageSet");
|
||||
caps.Categories.AddCategoryMapping(5003000, NewznabStandardCategory.XXXOther, "XXX Games");
|
||||
caps.Categories.AddCategoryMapping(5004000, NewznabStandardCategory.XXXOther, "XXX Hentai");
|
||||
caps.Categories.AddCategoryMapping(5005000, NewznabStandardCategory.XXXOther, "XXX Other");
|
||||
caps.Categories.AddCategoryMapping(6000000, NewznabStandardCategory.TVAnime, "Anime");
|
||||
caps.Categories.AddCategoryMapping(6001000, NewznabStandardCategory.TVAnime, "Anime Subbed");
|
||||
caps.Categories.AddCategoryMapping(6002000, NewznabStandardCategory.TVAnime, "Anime Dubbed");
|
||||
caps.Categories.AddCategoryMapping(6003000, NewznabStandardCategory.TVAnime, "Anime Dual audio");
|
||||
caps.Categories.AddCategoryMapping(6004000, NewznabStandardCategory.TVAnime, "Anime Raw");
|
||||
caps.Categories.AddCategoryMapping(6005000, NewznabStandardCategory.AudioVideo, "Music Video");
|
||||
caps.Categories.AddCategoryMapping(6006000, NewznabStandardCategory.BooksOther, "Literature");
|
||||
caps.Categories.AddCategoryMapping(6007000, NewznabStandardCategory.AudioOther, "Music");
|
||||
caps.Categories.AddCategoryMapping(6008000, NewznabStandardCategory.TVAnime, "Anime non-english translated");
|
||||
caps.Categories.AddCategoryMapping(7000000, NewznabStandardCategory.Console, "Console");
|
||||
caps.Categories.AddCategoryMapping(7001000, NewznabStandardCategory.ConsolePS4, "PS4");
|
||||
caps.Categories.AddCategoryMapping(7002000, NewznabStandardCategory.ConsolePS3, "PS3");
|
||||
caps.Categories.AddCategoryMapping(7003000, NewznabStandardCategory.ConsolePS3, "PS2");
|
||||
caps.Categories.AddCategoryMapping(7004000, NewznabStandardCategory.ConsolePS3, "PS1");
|
||||
caps.Categories.AddCategoryMapping(7005000, NewznabStandardCategory.ConsolePSVita, "PS Vita");
|
||||
caps.Categories.AddCategoryMapping(7006000, NewznabStandardCategory.ConsolePSP, "PSP");
|
||||
caps.Categories.AddCategoryMapping(7007000, NewznabStandardCategory.ConsoleXBox360, "Xbox 360");
|
||||
caps.Categories.AddCategoryMapping(7008000, NewznabStandardCategory.ConsoleXBox, "Xbox");
|
||||
caps.Categories.AddCategoryMapping(7009000, NewznabStandardCategory.ConsoleNDS, "Switch");
|
||||
caps.Categories.AddCategoryMapping(7010000, NewznabStandardCategory.ConsoleNDS, "NDS");
|
||||
caps.Categories.AddCategoryMapping(7011000, NewznabStandardCategory.ConsoleWii, "Wii");
|
||||
caps.Categories.AddCategoryMapping(7012000, NewznabStandardCategory.ConsoleWiiU, "WiiU");
|
||||
caps.Categories.AddCategoryMapping(7013000, NewznabStandardCategory.Console3DS, "3DS");
|
||||
caps.Categories.AddCategoryMapping(7014000, NewznabStandardCategory.ConsoleWii, "GameCube");
|
||||
caps.Categories.AddCategoryMapping(7015000, NewznabStandardCategory.ConsoleOther, "Other");
|
||||
caps.Categories.AddCategoryMapping(8000000, NewznabStandardCategory.PCMobileOther, "Mobile");
|
||||
caps.Categories.AddCategoryMapping(8001000, NewznabStandardCategory.PCMobileAndroid, "Android");
|
||||
caps.Categories.AddCategoryMapping(8002000, NewznabStandardCategory.PCMobileiOS, "IOS");
|
||||
caps.Categories.AddCategoryMapping(8003000, NewznabStandardCategory.PCMobileOther, "PC Other");
|
||||
caps.Categories.AddCategoryMapping(9000000, NewznabStandardCategory.Books, "Books");
|
||||
caps.Categories.AddCategoryMapping(9001000, NewznabStandardCategory.BooksEBook, "EBooks");
|
||||
caps.Categories.AddCategoryMapping(9002000, NewznabStandardCategory.BooksComics, "Comics");
|
||||
caps.Categories.AddCategoryMapping(9003000, NewznabStandardCategory.BooksMags, "Magazines");
|
||||
caps.Categories.AddCategoryMapping(9004000, NewznabStandardCategory.BooksTechnical, "Technical");
|
||||
caps.Categories.AddCategoryMapping(9005000, NewznabStandardCategory.BooksOther, "Books Other");
|
||||
caps.Categories.AddCategoryMapping(10000000, NewznabStandardCategory.Other, "Other");
|
||||
caps.Categories.AddCategoryMapping(10001000, NewznabStandardCategory.OtherMisc, "Other Misc");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class KnabenRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private const string ApiSearchEndpoint = "https://api.knaben.org/v1";
|
||||
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
|
||||
public KnabenRequestGenerator(IndexerCapabilities capabilities)
|
||||
{
|
||||
_capabilities = capabilities;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(CreateRequest(searchCriteria, searchCriteria.SanitizedSearchTerm));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(CreateRequest(searchCriteria, searchCriteria.SanitizedSearchTerm));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(CreateRequest(searchCriteria, searchCriteria.SanitizedTvSearchString));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(CreateRequest(searchCriteria, searchCriteria.SanitizedSearchTerm));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(CreateRequest(searchCriteria, searchCriteria.SanitizedSearchTerm));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> CreateRequest(SearchCriteriaBase searchCriteria, string searchTerm)
|
||||
{
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
{ "order_by", "date" },
|
||||
{ "order_direction", "desc" },
|
||||
{ "from", 0 },
|
||||
{ "size", 100 },
|
||||
{ "hide_unsafe", true }
|
||||
};
|
||||
|
||||
var searchQuery = searchTerm.Trim();
|
||||
|
||||
if (searchQuery.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
body.Add("search_type", "100%");
|
||||
body.Add("search_field", "title");
|
||||
body.Add("query", searchQuery);
|
||||
}
|
||||
|
||||
var categories = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
||||
|
||||
if (categories is { Count: > 0 })
|
||||
{
|
||||
body.Add("categories", categories.Select(int.Parse).Distinct().ToArray());
|
||||
}
|
||||
|
||||
var request = new HttpRequest(ApiSearchEndpoint, HttpAccept.Json)
|
||||
{
|
||||
Headers =
|
||||
{
|
||||
ContentType = "application/json"
|
||||
},
|
||||
Method = HttpMethod.Post
|
||||
};
|
||||
request.SetContent(body.ToJson());
|
||||
request.ContentSummary = body.ToJson(None);
|
||||
|
||||
yield return new IndexerRequest(request);
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class KnabenParser : IParseIndexerResponse
|
||||
{
|
||||
private static readonly Regex DateTimezoneRegex = new (@"[+-]\d{2}:\d{2}$", RegexOptions.Compiled);
|
||||
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public KnabenParser(IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var indexerHttpResponse = indexerResponse.HttpResponse;
|
||||
|
||||
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerHttpResponse.StatusCode} code from indexer request");
|
||||
}
|
||||
|
||||
if (!indexerHttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerHttpResponse.Headers.ContentType} from indexer request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var jsonResponse = STJson.Deserialize<KnabenResponse>(indexerResponse.Content);
|
||||
|
||||
if (jsonResponse?.Hits == null)
|
||||
{
|
||||
return releaseInfos;
|
||||
}
|
||||
|
||||
var rows = jsonResponse.Hits.Where(r => r.Seeders > 0).ToList();
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
// Not all entries have the TZ in the "date" field
|
||||
var publishDate = row.Date.IsNotNullOrWhiteSpace() && !DateTimezoneRegex.IsMatch(row.Date) ? $"{row.Date}+01:00" : row.Date;
|
||||
|
||||
var releaseInfo = new TorrentInfo
|
||||
{
|
||||
Guid = row.InfoUrl,
|
||||
Title = row.Title,
|
||||
InfoUrl = row.InfoUrl,
|
||||
DownloadUrl = row.DownloadUrl.IsNotNullOrWhiteSpace() ? row.DownloadUrl : null,
|
||||
MagnetUrl = row.MagnetUrl.IsNotNullOrWhiteSpace() ? row.MagnetUrl : null,
|
||||
Categories = row.CategoryIds.SelectMany(cat => _categories.MapTrackerCatToNewznab(cat.ToString())).Distinct().ToList(),
|
||||
InfoHash = row.InfoHash,
|
||||
Size = row.Size,
|
||||
Seeders = row.Seeders,
|
||||
Peers = row.Leechers + row.Seeders,
|
||||
PublishDate = DateTime.Parse(publishDate, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal),
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1
|
||||
};
|
||||
|
||||
releaseInfos.Add(releaseInfo);
|
||||
}
|
||||
|
||||
// order by date
|
||||
return releaseInfos;
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class KnabenResponse
|
||||
{
|
||||
public IReadOnlyCollection<KnabenRelease> Hits { get; init; } = Array.Empty<KnabenRelease>();
|
||||
}
|
||||
|
||||
internal sealed class KnabenRelease
|
||||
{
|
||||
public string Title { get; init; }
|
||||
|
||||
[JsonPropertyName("categoryId")]
|
||||
public IReadOnlyCollection<int> CategoryIds { get; init; } = Array.Empty<int>();
|
||||
|
||||
[JsonPropertyName("hash")]
|
||||
public string InfoHash { get; init; }
|
||||
|
||||
[JsonPropertyName("details")]
|
||||
public string InfoUrl { get; init; }
|
||||
|
||||
[JsonPropertyName("link")]
|
||||
public string DownloadUrl { get; init; }
|
||||
|
||||
public string MagnetUrl { get; init; }
|
||||
|
||||
[JsonPropertyName("bytes")]
|
||||
public long Size { get; init; }
|
||||
|
||||
public int Seeders { get; init; }
|
||||
|
||||
[JsonPropertyName("peers")]
|
||||
public int Leechers { get; init; }
|
||||
|
||||
public string Date { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -385,11 +384,6 @@ public class MTeamTpParser : IParseIndexerResponse
|
||||
MinimumSeedTime = 172800 // 2 days
|
||||
};
|
||||
|
||||
if (torrent.Imdb.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
release.ImdbId = ParseUtil.GetImdbId(torrent.Imdb.TrimEnd('/').Split('/').LastOrDefault()).GetValueOrDefault();
|
||||
}
|
||||
|
||||
if (torrent.Status?.CreatedDate != null &&
|
||||
DateTime.TryParseExact($"{torrent.Status.CreatedDate} +08:00", "yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var publishDate))
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -36,8 +37,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public override bool SupportsPagination => true;
|
||||
public override int PageSize => 100;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
private readonly ICacheManager _cacheManager;
|
||||
private static readonly Regex TorrentIdRegex = new Regex(@"tor/download.php\?tid=(?<id>\d+)$");
|
||||
|
||||
public MyAnonamouse(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger, ICacheManager cacheManager)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
@@ -59,39 +60,66 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var downloadLink = link.RemoveQueryParam("canUseToken");
|
||||
|
||||
if (Settings.Freeleech && bool.TryParse(link.GetQueryParam("canUseToken"), out var canUseToken) && canUseToken)
|
||||
if (Settings.UseFreeleechWedge is (int)MyAnonamouseFreeleechWedgeAction.Preferred or (int)MyAnonamouseFreeleechWedgeAction.Required &&
|
||||
bool.TryParse(link.GetQueryParam("canUseToken"), out var canUseToken) && canUseToken)
|
||||
{
|
||||
_logger.Debug("Attempting to use freeleech token for {0}", downloadLink.AbsoluteUri);
|
||||
_logger.Debug("Attempting to use freeleech wedge for {0}", downloadLink.AbsoluteUri);
|
||||
|
||||
var idMatch = TorrentIdRegex.Match(downloadLink.AbsoluteUri);
|
||||
if (idMatch.Success)
|
||||
if (int.TryParse(link.GetQueryParam("tid"), out var torrentId) && torrentId > 0)
|
||||
{
|
||||
var id = int.Parse(idMatch.Groups["id"].Value);
|
||||
var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
var freeleechUrl = Settings.BaseUrl + $"json/bonusBuy.php/{timestamp}";
|
||||
|
||||
var freeleechRequest = new HttpRequestBuilder(freeleechUrl)
|
||||
var freeleechRequestBuilder = new HttpRequestBuilder(freeleechUrl)
|
||||
.Accept(HttpAccept.Json)
|
||||
.AddQueryParam("spendtype", "personalFL")
|
||||
.AddQueryParam("torrentid", id)
|
||||
.AddQueryParam("timestamp", timestamp.ToString())
|
||||
.Build();
|
||||
.AddQueryParam("torrentid", torrentId)
|
||||
.AddQueryParam("timestamp", timestamp.ToString());
|
||||
|
||||
var indexerReq = new IndexerRequest(freeleechRequest);
|
||||
var response = await FetchIndexerResponse(indexerReq).ConfigureAwait(false);
|
||||
var resource = Json.Deserialize<MyAnonamouseBuyPersonalFreeleechResponse>(response.Content);
|
||||
freeleechRequestBuilder.LogResponseContent = true;
|
||||
|
||||
var cookies = GetCookies();
|
||||
|
||||
if (cookies != null && cookies.Any())
|
||||
{
|
||||
freeleechRequestBuilder.SetCookies(cookies);
|
||||
}
|
||||
|
||||
var freeleechRequest = freeleechRequestBuilder.Build();
|
||||
|
||||
var freeleechResponse = await _httpClient.ExecuteProxiedAsync(freeleechRequest, Definition).ConfigureAwait(false);
|
||||
|
||||
var resource = Json.Deserialize<MyAnonamouseBuyPersonalFreeleechResponse>(freeleechResponse.Content);
|
||||
|
||||
if (resource.Success)
|
||||
{
|
||||
_logger.Debug("Successfully to used freeleech token for torrentid {0}", id);
|
||||
_logger.Debug("Successfully used freeleech wedge for torrentid {0}.", torrentId);
|
||||
}
|
||||
else if (resource.Error.IsNotNullOrWhiteSpace() && resource.Error.ContainsIgnoreCase("This Torrent is VIP"))
|
||||
{
|
||||
_logger.Debug("{0} is already VIP, continuing downloading: {1}", torrentId, resource.Error);
|
||||
}
|
||||
else if (resource.Error.IsNotNullOrWhiteSpace() && resource.Error.ContainsIgnoreCase("This is already a personal freeleech"))
|
||||
{
|
||||
_logger.Debug("{0} is already a personal freeleech, continuing downloading: {1}", torrentId, resource.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Failed to use freeleech token: {0}", resource.Error);
|
||||
_logger.Warn("Failed to purchase freeleech wedge for {0}: {1}", torrentId, resource.Error);
|
||||
|
||||
if (Settings.UseFreeleechWedge == (int)MyAnonamouseFreeleechWedgeAction.Preferred)
|
||||
{
|
||||
_logger.Debug("'Use Freeleech Wedge' option set to preferred, continuing downloading: '{0}'", downloadLink.AbsoluteUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ReleaseUnavailableException($"Failed to buy freeleech wedge and 'Use Freeleech Wedge' is set to required, aborting download: '{downloadLink.AbsoluteUri}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Could not get torrent id from link {0}, skipping freeleech", downloadLink.AbsoluteUri);
|
||||
_logger.Warn("Could not get torrent id from link {0}, skipping use of freeleech wedge.", downloadLink.AbsoluteUri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,14 +322,21 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
parameters.Set("tor[srchIn][filenames]", "true");
|
||||
}
|
||||
|
||||
var catList = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
||||
if (_settings.SearchLanguages.Any())
|
||||
{
|
||||
foreach (var (language, index) in _settings.SearchLanguages.Select((value, index) => (value, index)))
|
||||
{
|
||||
parameters.Set($"tor[browse_lang][{index}]", language.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
var catList = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Distinct().ToList();
|
||||
|
||||
if (catList.Any())
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var cat in catList)
|
||||
foreach (var (category, index) in catList.Select((value, index) => (value, index)))
|
||||
{
|
||||
parameters.Set("tor[cat][" + index + "]", cat);
|
||||
index++;
|
||||
parameters.Set($"tor[cat][{index}]", category);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -441,6 +476,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return releaseInfos.ToArray();
|
||||
}
|
||||
|
||||
if (jsonResponse.Data == null)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, "Unexpected response content from indexer request: {0}", jsonResponse.Message ?? "Check the logs for more information.");
|
||||
}
|
||||
|
||||
var hasUserVip = HasUserVip(httpResponse.GetCookies());
|
||||
|
||||
foreach (var item in jsonResponse.Data)
|
||||
@@ -523,7 +563,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
.CombinePath("/tor/download.php")
|
||||
.AddQueryParam("tid", torrentId);
|
||||
|
||||
if (_settings.Freeleech && canUseToken)
|
||||
if (_settings.UseFreeleechWedge is (int)MyAnonamouseFreeleechWedgeAction.Preferred or (int)MyAnonamouseFreeleechWedgeAction.Required && canUseToken)
|
||||
{
|
||||
url = url.AddQueryParam("canUseToken", "true");
|
||||
}
|
||||
@@ -548,8 +588,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
_logger.Debug("Fetching user data: {0}", request.Url.FullUri);
|
||||
|
||||
var response = _httpClient.ExecuteProxied(request, _definition);
|
||||
|
||||
var jsonResponse = JsonConvert.DeserializeObject<MyAnonamouseUserDataResponse>(response.Content);
|
||||
|
||||
_logger.Trace("Current user class: '{0}'", jsonResponse.UserClass);
|
||||
|
||||
return jsonResponse.UserClass?.Trim();
|
||||
},
|
||||
TimeSpan.FromHours(1));
|
||||
@@ -579,6 +622,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
SearchInDescription = false;
|
||||
SearchInSeries = false;
|
||||
SearchInFilenames = false;
|
||||
SearchLanguages = Array.Empty<int>();
|
||||
UseFreeleechWedge = (int)MyAnonamouseFreeleechWedgeAction.Never;
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Type = FieldType.Textbox, Label = "Mam Id", HelpText = "Mam Session Id (Created Under Preferences -> Security)")]
|
||||
@@ -587,18 +632,21 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
[FieldDefinition(3, Type = FieldType.Select, Label = "Search Type", SelectOptions = typeof(MyAnonamouseSearchType), HelpText = "Specify the desired search type")]
|
||||
public int SearchType { get; set; }
|
||||
|
||||
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "Use Freeleech Wedges", HelpText = "Use freeleech wedges to make grabbed torrents personal freeleech")]
|
||||
public bool Freeleech { get; set; }
|
||||
|
||||
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "Search in description", HelpText = "Search text in the description")]
|
||||
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "Search in description", HelpText = "Search text in the description")]
|
||||
public bool SearchInDescription { get; set; }
|
||||
|
||||
[FieldDefinition(6, Type = FieldType.Checkbox, Label = "Search in series", HelpText = "Search text in the series")]
|
||||
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "Search in series", HelpText = "Search text in the series")]
|
||||
public bool SearchInSeries { get; set; }
|
||||
|
||||
[FieldDefinition(7, Type = FieldType.Checkbox, Label = "Search in filenames", HelpText = "Search text in the filenames")]
|
||||
[FieldDefinition(6, Type = FieldType.Checkbox, Label = "Search in filenames", HelpText = "Search text in the filenames")]
|
||||
public bool SearchInFilenames { get; set; }
|
||||
|
||||
[FieldDefinition(7, Type = FieldType.Select, Label = "Search Languages", SelectOptions = typeof(MyAnonamouseSearchLanguages), HelpText = "Specify the desired languages. If unspecified, all options are used.")]
|
||||
public IEnumerable<int> SearchLanguages { get; set; }
|
||||
|
||||
[FieldDefinition(8, Type = FieldType.Select, Label = "Use Freeleech Wedges", SelectOptions = typeof(MyAnonamouseFreeleechWedgeAction), HelpText = "Use freeleech wedges to make grabbed torrents personal freeleech")]
|
||||
public int UseFreeleechWedge { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
@@ -626,6 +674,210 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
NotVip = 5,
|
||||
}
|
||||
|
||||
public enum MyAnonamouseSearchLanguages
|
||||
{
|
||||
[FieldOption(Label="English")]
|
||||
English = 1,
|
||||
|
||||
[FieldOption(Label="Afrikaans")]
|
||||
Afrikaans = 17,
|
||||
|
||||
[FieldOption(Label="Arabic")]
|
||||
Arabic = 32,
|
||||
|
||||
[FieldOption(Label="Bengali")]
|
||||
Bengali = 35,
|
||||
|
||||
[FieldOption(Label="Bosnian")]
|
||||
Bosnian = 51,
|
||||
|
||||
[FieldOption(Label="Bulgarian")]
|
||||
Bulgarian = 18,
|
||||
|
||||
[FieldOption(Label="Burmese")]
|
||||
Burmese = 6,
|
||||
|
||||
[FieldOption(Label="Cantonese")]
|
||||
Cantonese = 44,
|
||||
|
||||
[FieldOption(Label="Catalan")]
|
||||
Catalan = 19,
|
||||
|
||||
[FieldOption(Label="Chinese")]
|
||||
Chinese = 2,
|
||||
|
||||
[FieldOption(Label="Croatian")]
|
||||
Croatian = 49,
|
||||
|
||||
[FieldOption(Label="Czech")]
|
||||
Czech = 20,
|
||||
|
||||
[FieldOption(Label="Danish")]
|
||||
Danish = 21,
|
||||
|
||||
[FieldOption(Label="Dutch")]
|
||||
Dutch = 22,
|
||||
|
||||
[FieldOption(Label="Estonian")]
|
||||
Estonian = 61,
|
||||
|
||||
[FieldOption(Label="Farsi")]
|
||||
Farsi = 39,
|
||||
|
||||
[FieldOption(Label="Finnish")]
|
||||
Finnish = 23,
|
||||
|
||||
[FieldOption(Label="French")]
|
||||
French = 36,
|
||||
|
||||
[FieldOption(Label="German")]
|
||||
German = 37,
|
||||
|
||||
[FieldOption(Label="Greek")]
|
||||
Greek = 26,
|
||||
|
||||
[FieldOption(Label="Greek, Ancient")]
|
||||
GreekAncient = 59,
|
||||
|
||||
[FieldOption(Label="Gujarati")]
|
||||
Gujarati = 3,
|
||||
|
||||
[FieldOption(Label="Hebrew")]
|
||||
Hebrew = 27,
|
||||
|
||||
[FieldOption(Label="Hindi")]
|
||||
Hindi = 8,
|
||||
|
||||
[FieldOption(Label="Hungarian")]
|
||||
Hungarian = 28,
|
||||
|
||||
[FieldOption(Label="Icelandic")]
|
||||
Icelandic = 63,
|
||||
|
||||
[FieldOption(Label="Indonesian")]
|
||||
Indonesian = 53,
|
||||
|
||||
[FieldOption(Label="Irish")]
|
||||
Irish = 56,
|
||||
|
||||
[FieldOption(Label="Italian")]
|
||||
Italian = 43,
|
||||
|
||||
[FieldOption(Label="Japanese")]
|
||||
Japanese = 38,
|
||||
|
||||
[FieldOption(Label="Javanese")]
|
||||
Javanese = 12,
|
||||
|
||||
[FieldOption(Label="Kannada")]
|
||||
Kannada = 5,
|
||||
|
||||
[FieldOption(Label="Korean")]
|
||||
Korean = 41,
|
||||
|
||||
[FieldOption(Label="Lithuanian")]
|
||||
Lithuanian = 50,
|
||||
|
||||
[FieldOption(Label="Latin")]
|
||||
Latin = 46,
|
||||
|
||||
[FieldOption(Label="Latvian")]
|
||||
Latvian = 62,
|
||||
|
||||
[FieldOption(Label="Malay")]
|
||||
Malay = 33,
|
||||
|
||||
[FieldOption(Label="Malayalam")]
|
||||
Malayalam = 58,
|
||||
|
||||
[FieldOption(Label="Manx")]
|
||||
Manx = 57,
|
||||
|
||||
[FieldOption(Label="Marathi")]
|
||||
Marathi = 9,
|
||||
|
||||
[FieldOption(Label="Norwegian")]
|
||||
Norwegian = 48,
|
||||
|
||||
[FieldOption(Label="Polish")]
|
||||
Polish = 45,
|
||||
|
||||
[FieldOption(Label="Portuguese")]
|
||||
Portuguese = 34,
|
||||
|
||||
[FieldOption(Label="Brazilian Portuguese (BP)")]
|
||||
BrazilianPortuguese = 52,
|
||||
|
||||
[FieldOption(Label="Punjabi")]
|
||||
Punjabi = 14,
|
||||
|
||||
[FieldOption(Label="Romanian")]
|
||||
Romanian = 30,
|
||||
|
||||
[FieldOption(Label="Russian")]
|
||||
Russian = 16,
|
||||
|
||||
[FieldOption(Label="Scottish Gaelic")]
|
||||
ScottishGaelic = 24,
|
||||
|
||||
[FieldOption(Label="Sanskrit")]
|
||||
Sanskrit = 60,
|
||||
|
||||
[FieldOption(Label="Serbian")]
|
||||
Serbian = 31,
|
||||
|
||||
[FieldOption(Label="Slovenian")]
|
||||
Slovenian = 54,
|
||||
|
||||
[FieldOption(Label="Spanish")]
|
||||
Spanish = 4,
|
||||
|
||||
[FieldOption(Label="Castilian Spanish")]
|
||||
CastilianSpanish = 55,
|
||||
|
||||
[FieldOption(Label="Swedish")]
|
||||
Swedish = 40,
|
||||
|
||||
[FieldOption(Label="Tagalog")]
|
||||
Tagalog = 29,
|
||||
|
||||
[FieldOption(Label="Tamil")]
|
||||
Tamil = 11,
|
||||
|
||||
[FieldOption(Label="Telugu")]
|
||||
Telugu = 10,
|
||||
|
||||
[FieldOption(Label="Thai")]
|
||||
Thai = 7,
|
||||
|
||||
[FieldOption(Label="Turkish")]
|
||||
Turkish = 42,
|
||||
|
||||
[FieldOption(Label="Ukrainian")]
|
||||
Ukrainian = 25,
|
||||
|
||||
[FieldOption(Label="Urdu")]
|
||||
Urdu = 15,
|
||||
|
||||
[FieldOption(Label="Vietnamese")]
|
||||
Vietnamese = 13,
|
||||
|
||||
[FieldOption(Label="Other")]
|
||||
Other = 47,
|
||||
}
|
||||
|
||||
public enum MyAnonamouseFreeleechWedgeAction
|
||||
{
|
||||
[FieldOption(Label = "Never", Hint = "Do not buy as freeleech")]
|
||||
Never = 0,
|
||||
|
||||
[FieldOption(Label = "Preferred", Hint = "Buy and use wedge if possible")]
|
||||
Preferred = 1,
|
||||
|
||||
[FieldOption(Label = "Required", Hint = "Abort download if unable to buy wedge")]
|
||||
Required = 2,
|
||||
}
|
||||
|
||||
public class MyAnonamouseTorrent
|
||||
{
|
||||
public int Id { get; set; }
|
||||
@@ -655,7 +907,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class MyAnonamouseResponse
|
||||
{
|
||||
public string Error { get; set; }
|
||||
public List<MyAnonamouseTorrent> Data { get; set; }
|
||||
public IReadOnlyCollection<MyAnonamouseTorrent> Data { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class MyAnonamouseBuyPersonalFreeleechResponse
|
||||
@@ -666,7 +919,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public class MyAnonamouseUserDataResponse
|
||||
{
|
||||
[JsonProperty(PropertyName = "class")]
|
||||
[JsonProperty(PropertyName = "classname")]
|
||||
public string UserClass { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
ApiKey = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(4, Label = "ApiKey", HelpText = "IndexerNebulanceSettingsApiKeyHelpText")]
|
||||
[FieldDefinition(2, Label = "ApiKey", HelpText = "IndexerNebulanceSettingsApiKeyHelpText", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
parameters.Set("tvdbid", searchCriteria.TvdbId.Value.ToString());
|
||||
}
|
||||
|
||||
if (searchCriteria.TmdbId.HasValue && capabilities.TvSearchTvdbAvailable)
|
||||
if (searchCriteria.TmdbId.HasValue && capabilities.TvSearchTmdbAvailable)
|
||||
{
|
||||
parameters.Set("tmdbid", searchCriteria.TmdbId.Value.ToString());
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
RuleFor(c => c.VipExpiration).Must(c => c.IsFutureDate())
|
||||
.When(c => c.VipExpiration.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Must be a future date");
|
||||
.WithMessage("Must be a future date")
|
||||
.AsWarning();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,26 +129,14 @@ public class NorBits : TorrentIndexerBase<NorBitsSettings>
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=1&sub2_cat[]=49", NewznabStandardCategory.MoviesUHD, "Filmer - UHD-2160p");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=1&sub2_cat[]=19", NewznabStandardCategory.MoviesHD, "Filmer - HD-1080p/i");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=1&sub2_cat[]=20", NewznabStandardCategory.MoviesHD, "Filmer - HD-720p");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=1&sub2_cat[]=22", NewznabStandardCategory.MoviesSD, "Filmer - SD");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=2&sub2_cat[]=49", NewznabStandardCategory.TVUHD, "TV - UHD-2160p");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=2&sub2_cat[]=19", NewznabStandardCategory.TVHD, "TV - HD-1080p/i");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=2&sub2_cat[]=20", NewznabStandardCategory.TVHD, "TV - HD-720p");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=2&sub2_cat[]=22", NewznabStandardCategory.TVSD, "TV - SD");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=1", NewznabStandardCategory.Movies, "Filmer");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=2", NewznabStandardCategory.TV, "TV");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=3", NewznabStandardCategory.PC, "Programmer");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=4", NewznabStandardCategory.Console, "Spill");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=5&sub2_cat[]=42", NewznabStandardCategory.AudioMP3, "Musikk - 192");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=5&sub2_cat[]=43", NewznabStandardCategory.AudioMP3, "Musikk - 256");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=5&sub2_cat[]=44", NewznabStandardCategory.AudioMP3, "Musikk - 320");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=5&sub2_cat[]=45", NewznabStandardCategory.AudioMP3, "Musikk - VBR");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=5&sub2_cat[]=46", NewznabStandardCategory.AudioLossless, "Musikk - Lossless");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=5", NewznabStandardCategory.Audio, "Musikk");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=6", NewznabStandardCategory.Books, "Tidsskrift");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=7", NewznabStandardCategory.AudioAudiobook, "Lydbøker");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=8&sub2_cat[]=19", NewznabStandardCategory.AudioVideo, "Musikkvideoer - HD-1080p/i");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=8&sub2_cat[]=20", NewznabStandardCategory.AudioVideo, "Musikkvideoer - HD-720p");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=8&sub2_cat[]=22", NewznabStandardCategory.AudioVideo, "Musikkvideoer - SD");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=8", NewznabStandardCategory.AudioVideo, "Musikkvideoer");
|
||||
caps.Categories.AddCategoryMapping("main_cat[]=40", NewznabStandardCategory.AudioOther, "Podcasts");
|
||||
|
||||
return caps;
|
||||
@@ -190,7 +178,7 @@ public class NorBitsRequestGenerator : IIndexerRequestGenerator
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
searchTerm = "search=" + term.UrlEncode(Encoding.GetEncoding(28591));
|
||||
searchTerm = "search=" + term.UrlEncode(Encoding.UTF8);
|
||||
}
|
||||
|
||||
searchUrl += "?" + searchTerm + "&" + parameters.GetQueryString();
|
||||
@@ -277,20 +265,17 @@ public class NorBitsParser : IParseIndexerResponse
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var link = _settings.BaseUrl + row.QuerySelector("td:nth-of-type(2) > a[href*=\"download.php?id=\"]")?.GetAttribute("href").TrimStart('/');
|
||||
var link = _settings.BaseUrl + row.QuerySelector("td:nth-of-type(2) > a[href*=\"download.php?id=\"]")?.GetAttribute("href")?.TrimStart('/');
|
||||
var qDetails = row.QuerySelector("td:nth-of-type(2) > a[href*=\"details.php?id=\"]");
|
||||
|
||||
var title = qDetails?.GetAttribute("title").Trim();
|
||||
var details = _settings.BaseUrl + qDetails?.GetAttribute("href").TrimStart('/');
|
||||
var title = qDetails?.GetAttribute("title")?.Trim();
|
||||
var details = _settings.BaseUrl + qDetails?.GetAttribute("href")?.TrimStart('/');
|
||||
|
||||
var mainCategory = row.QuerySelector("td:nth-of-type(1) > div > a[href*=\"main_cat[]\"]")?.GetAttribute("href")?.Split('?').Last();
|
||||
var secondCategory = row.QuerySelector("td:nth-of-type(1) > div > a[href*=\"sub2_cat[]\"]")?.GetAttribute("href")?.Split('?').Last();
|
||||
var catQuery = row.QuerySelector("td:nth-of-type(1) a[href*=\"main_cat[]\"]")?.GetAttribute("href")?.Split('?').Last().Split('&', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
var category = catQuery?.FirstOrDefault(x => x.StartsWith("main_cat[]=", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var categoryList = new[] { mainCategory, secondCategory };
|
||||
var cat = string.Join("&", categoryList.Where(c => !string.IsNullOrWhiteSpace(c)));
|
||||
|
||||
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(9)").TextContent);
|
||||
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(10)").TextContent);
|
||||
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(9)")?.TextContent);
|
||||
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(10)")?.TextContent);
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
@@ -298,7 +283,7 @@ public class NorBitsParser : IParseIndexerResponse
|
||||
InfoUrl = details,
|
||||
DownloadUrl = link,
|
||||
Title = title,
|
||||
Categories = _categories.MapTrackerCatToNewznab(cat),
|
||||
Categories = _categories.MapTrackerCatToNewznab(category),
|
||||
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-of-type(7)")?.TextContent),
|
||||
Files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(3) > a")?.TextContent.Trim()),
|
||||
Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(8)")?.FirstChild?.TextContent.Trim()),
|
||||
|
||||
@@ -20,6 +20,7 @@ using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
[Obsolete("Site has removed API access.")]
|
||||
public class NzbIndex : UsenetIndexerBase<NzbIndexSettings>
|
||||
{
|
||||
public override string Name => "NZBIndex";
|
||||
|
||||
@@ -8,6 +8,7 @@ using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.Gazelle;
|
||||
@@ -252,7 +253,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
|
||||
STJson.TryDeserialize<GazelleErrorResponse>(indexerResponse.Content, out var errorResponse);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request: {errorResponse?.Error ?? "Check the logs for more information."}");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
@@ -15,6 +16,7 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
|
||||
public override bool SupportsSearch => true;
|
||||
public override bool SupportsPagination => true;
|
||||
public override int PageSize => 50;
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(4);
|
||||
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
|
||||
@@ -56,6 +56,19 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
|
||||
{
|
||||
foreach (var torrent in result.Torrents)
|
||||
{
|
||||
// skip non-freeleech results when freeleech only is set
|
||||
var downloadVolumeFactor = torrent.FreeleechType?.ToUpperInvariant() switch
|
||||
{
|
||||
"FREELEECH" => 0,
|
||||
"HALF LEECH" => 0.5,
|
||||
_ => 1
|
||||
};
|
||||
|
||||
if (_settings.FreeleechOnly && downloadVolumeFactor != 0.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var id = torrent.Id;
|
||||
var title = torrent.ReleaseName;
|
||||
|
||||
@@ -94,12 +107,7 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
|
||||
ImdbId = result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0,
|
||||
Scene = torrent.Scene,
|
||||
IndexerFlags = flags,
|
||||
DownloadVolumeFactor = torrent.FreeleechType?.ToUpperInvariant() switch
|
||||
{
|
||||
"FREELEECH" => 0,
|
||||
"HALF LEECH" => 0.5,
|
||||
_ => 1
|
||||
},
|
||||
DownloadVolumeFactor = downloadVolumeFactor,
|
||||
UploadVolumeFactor = 1,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 345600,
|
||||
|
||||
@@ -8,6 +8,7 @@ using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.Gazelle;
|
||||
@@ -24,7 +25,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class Redacted : TorrentIndexerBase<RedactedSettings>
|
||||
{
|
||||
public override string Name => "Redacted";
|
||||
public override string[] IndexerUrls => new[] { "https://redacted.ch/" };
|
||||
public override string[] IndexerUrls => new[] { "https://redacted.sh/" };
|
||||
public override string[] LegacyUrls => new[] { "https://redacted.ch/" };
|
||||
public override string Description => "REDActed (Aka.PassTheHeadPhones) is one of the most well-known music trackers.";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
@@ -251,7 +253,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
|
||||
STJson.TryDeserialize<GazelleErrorResponse>(indexerResponse.Content, out var errorResponse);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request: {errorResponse?.Error ?? "Check the logs for more information."}");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
|
||||
@@ -933,6 +933,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(1224, NewznabStandardCategory.AudioLossless, "|- Авторская песня (lossless)");
|
||||
caps.Categories.AddCategoryMapping(1225, NewznabStandardCategory.AudioMP3, "|- Авторская песня (lossy)");
|
||||
caps.Categories.AddCategoryMapping(1226, NewznabStandardCategory.Audio, "|- Менестрели и ролевики (lossy и lossless)");
|
||||
caps.Categories.AddCategoryMapping(782, NewznabStandardCategory.Audio, "Лейбл- и сцен-паки. Неофициальные сборники и ремастеринги. AI-музыка");
|
||||
caps.Categories.AddCategoryMapping(577, NewznabStandardCategory.Audio, "|- AI-Music - музыка ИИ, нейросетей (lossy и lossless)");
|
||||
caps.Categories.AddCategoryMapping(1842, NewznabStandardCategory.AudioLossless, "Label Packs (lossless)");
|
||||
caps.Categories.AddCategoryMapping(1648, NewznabStandardCategory.AudioMP3, "Label packs, Scene packs (lossy)");
|
||||
caps.Categories.AddCategoryMapping(134, NewznabStandardCategory.AudioLossless, "|- Неофициальные сборники и ремастеринги (lossless)");
|
||||
@@ -1292,7 +1294,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(650, NewznabStandardCategory.PCMobileOther, "Игры для мобильных устройств");
|
||||
caps.Categories.AddCategoryMapping(2149, NewznabStandardCategory.PCMobileAndroid, "|- Игры для Android");
|
||||
caps.Categories.AddCategoryMapping(2420, NewznabStandardCategory.ConsoleOther, "|- Игры для Oculus Quest");
|
||||
caps.Categories.AddCategoryMapping(1001, NewznabStandardCategory.PC, "|- Игры для Java");
|
||||
caps.Categories.AddCategoryMapping(1004, NewznabStandardCategory.PCMobileOther, "|- Игры для Symbian");
|
||||
caps.Categories.AddCategoryMapping(1002, NewznabStandardCategory.PCMobileOther, "|- Игры для Windows Mobile");
|
||||
caps.Categories.AddCategoryMapping(240, NewznabStandardCategory.OtherMisc, "Игровое видео");
|
||||
@@ -1308,7 +1309,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(1379, NewznabStandardCategory.PC, "|- Операционные системы (Linux, Unix)");
|
||||
caps.Categories.AddCategoryMapping(1381, NewznabStandardCategory.PC, "|- Программное обеспечение (Linux, Unix)");
|
||||
caps.Categories.AddCategoryMapping(1473, NewznabStandardCategory.PC, "|- Другие ОС и ПО под них");
|
||||
caps.Categories.AddCategoryMapping(1195, NewznabStandardCategory.PC, "Тестовые диски для настройки аудио/видео аппаратуры");
|
||||
caps.Categories.AddCategoryMapping(1013, NewznabStandardCategory.PC, "Системные программы");
|
||||
caps.Categories.AddCategoryMapping(1028, NewznabStandardCategory.PC, "|- Работа с жёстким диском");
|
||||
caps.Categories.AddCategoryMapping(1029, NewznabStandardCategory.PC, "|- Резервное копирование");
|
||||
@@ -1350,6 +1350,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(1018, NewznabStandardCategory.PC, "|- Шаблоны для сайтов и CMS");
|
||||
caps.Categories.AddCategoryMapping(1058, NewznabStandardCategory.PC, "|- Разное (Веб-разработка и программирование)");
|
||||
caps.Categories.AddCategoryMapping(1016, NewznabStandardCategory.PC, "Программы для работы с мультимедиа и 3D");
|
||||
caps.Categories.AddCategoryMapping(1195, NewznabStandardCategory.PC, "|- Тестовые диски для настройки аудио/видео аппаратуры");
|
||||
caps.Categories.AddCategoryMapping(1079, NewznabStandardCategory.PC, "|- Программные комплекты");
|
||||
caps.Categories.AddCategoryMapping(1080, NewznabStandardCategory.PC, "|- Плагины для программ компании Adobe");
|
||||
caps.Categories.AddCategoryMapping(1081, NewznabStandardCategory.PC, "|- Графические редакторы");
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Definitions.Gazelle;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
@@ -78,7 +79,9 @@ public class SecretCinemaParser : IParseIndexerResponse
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
|
||||
STJson.TryDeserialize<GazelleErrorResponse>(indexerResponse.Content, out var errorResponse);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request: {errorResponse?.Error ?? "Check the logs for more information."}");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
|
||||
@@ -262,7 +262,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
return jsonResponse.Resource.Select(torrent => new TorrentInfo
|
||||
{
|
||||
Guid = torrent.Id.ToString(),
|
||||
Guid = torrent.Url,
|
||||
Title = CleanTitle(torrent.Name),
|
||||
Description = torrent.ShortDescription,
|
||||
Size = torrent.Size,
|
||||
|
||||
@@ -7,6 +7,7 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -52,7 +53,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new TorrentDayParser(Settings, Capabilities.Categories);
|
||||
return new TorrentDayParser(Settings, Capabilities.Categories, _logger);
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetCookies()
|
||||
@@ -228,15 +229,29 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
private readonly TorrentDaySettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public TorrentDayParser(TorrentDaySettings settings, IndexerCapabilitiesCategories categories)
|
||||
public TorrentDayParser(TorrentDaySettings settings, IndexerCapabilitiesCategories categories, Logger logger)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
if (indexerResponse.HttpResponse.HasHttpRedirect)
|
||||
{
|
||||
_logger.Warn("Redirected to {0} from indexer request", indexerResponse.HttpResponse.RedirectUrl);
|
||||
|
||||
if (indexerResponse.HttpResponse.RedirectUrl.ContainsIgnoreCase("/login.php"))
|
||||
{
|
||||
throw new IndexerException(indexerResponse, "We are being redirected to the login page. Most likely your session expired or was killed. Recheck your cookie and try testing the indexer.");
|
||||
}
|
||||
|
||||
throw new IndexerException(indexerResponse, "Redirected to {0} from indexer request", indexerResponse.HttpResponse.RedirectUrl);
|
||||
}
|
||||
|
||||
var torrentInfos = new List<TorrentInfo>();
|
||||
|
||||
var rows = JsonConvert.DeserializeObject<dynamic>(indexerResponse.Content);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -44,6 +46,13 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentRss
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task<ValidationFailure> TestConnection()
|
||||
{
|
||||
UpdateCookies(null, null);
|
||||
|
||||
return base.TestConnection();
|
||||
}
|
||||
|
||||
private IndexerDefinition GetDefinition(string name, string description, TorrentRssIndexerSettings settings)
|
||||
{
|
||||
return new IndexerDefinition
|
||||
|
||||
@@ -121,8 +121,15 @@ public class XSpeeds : TorrentIndexerBase<XSpeedsSettings>
|
||||
caps.Categories.AddCategoryMapping(112, NewznabStandardCategory.MoviesOther, "Anime Movies");
|
||||
caps.Categories.AddCategoryMapping(111, NewznabStandardCategory.MoviesOther, "Anime TV");
|
||||
caps.Categories.AddCategoryMapping(150, NewznabStandardCategory.PC, "Apps");
|
||||
caps.Categories.AddCategoryMapping(80, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
|
||||
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.Books, "Books Magazines");
|
||||
caps.Categories.AddCategoryMapping(156, NewznabStandardCategory.TV, "AV1");
|
||||
caps.Categories.AddCategoryMapping(156, NewznabStandardCategory.Movies, "AV1");
|
||||
caps.Categories.AddCategoryMapping(159, NewznabStandardCategory.Movies, "Movie Boxsets AV1");
|
||||
caps.Categories.AddCategoryMapping(158, NewznabStandardCategory.Movies, "Movies AV1");
|
||||
caps.Categories.AddCategoryMapping(157, NewznabStandardCategory.TV, "TV AV1");
|
||||
caps.Categories.AddCategoryMapping(160, NewznabStandardCategory.TV, "TV Boxsets AV1");
|
||||
caps.Categories.AddCategoryMapping(153, NewznabStandardCategory.Books, "Books");
|
||||
caps.Categories.AddCategoryMapping(154, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
|
||||
caps.Categories.AddCategoryMapping(155, NewznabStandardCategory.Books, "Books & Magazines");
|
||||
caps.Categories.AddCategoryMapping(68, NewznabStandardCategory.MoviesOther, "Cams/TS");
|
||||
caps.Categories.AddCategoryMapping(140, NewznabStandardCategory.TVDocumentary, "Documentary");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.MoviesDVD, "DVDR");
|
||||
@@ -154,6 +161,7 @@ public class XSpeeds : TorrentIndexerBase<XSpeedsSettings>
|
||||
caps.Categories.AddCategoryMapping(146, NewznabStandardCategory.MoviesSD, "Movies SD");
|
||||
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.Audio, "Music");
|
||||
caps.Categories.AddCategoryMapping(135, NewznabStandardCategory.AudioLossless, "Music/FLAC");
|
||||
caps.Categories.AddCategoryMapping(151, NewznabStandardCategory.Audio, "Karaoke");
|
||||
caps.Categories.AddCategoryMapping(136, NewznabStandardCategory.Audio, "Music Boxset");
|
||||
caps.Categories.AddCategoryMapping(148, NewznabStandardCategory.AudioVideo, "Music Videos");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.Other, "Other");
|
||||
|
||||
@@ -650,7 +650,7 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
foreach (var cookie in Cookies)
|
||||
{
|
||||
request.HttpRequest.Cookies.Add(cookie.Key, cookie.Value);
|
||||
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,7 +761,7 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
if (releases.Releases.Empty())
|
||||
{
|
||||
return new ValidationFailure(string.Empty, "Query successful, but no results were returned from your indexer. This may be an issue with the indexer, your indexer category settings, or other indexer settings such as search freeleech only etc.");
|
||||
return new ValidationFailure(string.Empty, "Query successful, but no results were returned from your indexer. This may be an issue with the indexer, your indexer category settings, or other indexer settings such as search freeleech only etc. See the FAQ for details.");
|
||||
}
|
||||
}
|
||||
catch (IndexerAuthException ex)
|
||||
|
||||
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
foreach (var indexer in indexers)
|
||||
{
|
||||
if (blockedIndexers.TryGetValue(indexer.Definition.Id, out var blockedIndexerStatus))
|
||||
if (blockedIndexers.TryGetValue(indexer.Definition.Id, out var blockedIndexerStatus) && blockedIndexerStatus.DisabledTill.HasValue)
|
||||
{
|
||||
_logger.Debug("Temporarily ignoring indexer {0} till {1} due to recent failures.", indexer.Definition.Name, blockedIndexerStatus.DisabledTill.Value.ToLocalTime());
|
||||
continue;
|
||||
|
||||
@@ -63,6 +63,7 @@ namespace NzbDrone.Core.Indexers
|
||||
}
|
||||
|
||||
public static IndexerFlag Internal => new ("internal", "Uploader is an internal release group");
|
||||
public static IndexerFlag Exclusive => new ("exclusive", "An exclusive release that must not be uploaded anywhere else");
|
||||
public static IndexerFlag FreeLeech => new ("freeleech", "Download doesn't count toward ratio");
|
||||
public static IndexerFlag NeutralLeech => new ("neutralleech", "Download and upload doesn't count toward ratio");
|
||||
public static IndexerFlag HalfLeech => new ("halfleech", "Release counts 50% to ratio");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using MonoTorrent;
|
||||
using NLog;
|
||||
@@ -22,10 +23,10 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
Torrent.Load(fileData);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug("Invalid torrent file contents: {0}", Encoding.ASCII.GetString(fileData));
|
||||
throw;
|
||||
throw new NotSupportedException($"Invalid torrent file contents. Reason: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace NzbDrone.Core.Instrumentation
|
||||
|
||||
private void ReconfigureFile()
|
||||
{
|
||||
foreach (var target in LogManager.Configuration.AllTargets.OfType<NzbDroneFileTarget>())
|
||||
foreach (var target in LogManager.Configuration.AllTargets.OfType<CleansingFileTarget>())
|
||||
{
|
||||
target.MaxArchiveFiles = _configFileProvider.LogRotate;
|
||||
target.ArchiveAboveSize = _configFileProvider.LogSizeLimit.Megabytes();
|
||||
@@ -120,11 +120,7 @@ namespace NzbDrone.Core.Instrumentation
|
||||
{
|
||||
var format = _configFileProvider.ConsoleLogFormat;
|
||||
|
||||
consoleTarget.Layout = format switch
|
||||
{
|
||||
ConsoleLogFormat.Clef => NzbDroneLogger.ClefLogLayout,
|
||||
_ => NzbDroneLogger.ConsoleLogLayout
|
||||
};
|
||||
NzbDroneLogger.ConfigureConsoleLayout(consoleTarget, format);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"SelectAll": "اختر الكل",
|
||||
"SendAnonymousUsageData": "إرسال بيانات الاستخدام المجهولة",
|
||||
"Style": "أسلوب",
|
||||
"SystemTimeCheckMessage": "توقف وقت النظام بأكثر من يوم واحد. قد لا تعمل المهام المجدولة بشكل صحيح حتى يتم تصحيح الوقت",
|
||||
"SystemTimeHealthCheckMessage": "توقف وقت النظام بأكثر من يوم واحد. قد لا تعمل المهام المجدولة بشكل صحيح حتى يتم تصحيح الوقت",
|
||||
"TableOptionsColumnsMessage": "اختر الأعمدة المرئية والترتيب الذي تظهر به",
|
||||
"TagCannotBeDeletedWhileInUse": "لا يمكن حذفه أثناء الاستخدام",
|
||||
"Tasks": "مهام",
|
||||
@@ -370,5 +370,10 @@
|
||||
"ErrorRestoringBackup": "خطأ في استعادة النسخة الاحتياطية",
|
||||
"ExternalUpdater": "تم تكوين {appName} لاستخدام آلية تحديث خارجية",
|
||||
"InstallLatest": "تثبيت الأحدث",
|
||||
"AptUpdater": "استخدم apt لتثبيت التحديث"
|
||||
"AptUpdater": "استخدم apt لتثبيت التحديث",
|
||||
"Clone": "قريب",
|
||||
"Stats": "الحالة",
|
||||
"CurrentlyInstalled": "مثبتة حاليا",
|
||||
"Season": "السبب",
|
||||
"Mixed": "ثابت"
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
"SSLPort": "SSL порт",
|
||||
"Status": "Състояние",
|
||||
"System": "Система",
|
||||
"SystemTimeCheckMessage": "Системното време е изключено с повече от 1 ден. Планираните задачи може да не се изпълняват правилно, докато времето не бъде коригирано",
|
||||
"SystemTimeHealthCheckMessage": "Системното време е изключено с повече от 1 ден. Планираните задачи може да не се изпълняват правилно, докато времето не бъде коригирано",
|
||||
"TestAll": "Тествайте всички",
|
||||
"Title": "Заглавие",
|
||||
"Today": "Днес",
|
||||
@@ -370,5 +370,22 @@
|
||||
"RestartReloadNote": "Забележка: {appName} автоматично ще рестартира и презареди потребителския интерфейс по време на процеса на възстановяване.",
|
||||
"UpdateAppDirectlyLoadError": "Не може да се актуализира {appName} директно,",
|
||||
"AptUpdater": "Използвайте apt, за да инсталирате актуализацията",
|
||||
"InstallLatest": "Инсталирайте най-новите"
|
||||
"InstallLatest": "Инсталирайте най-новите",
|
||||
"Clone": "Близо",
|
||||
"ActiveApps": "Активни приложения",
|
||||
"ActiveIndexers": "Активни индиксатори",
|
||||
"AddApplication": "добави приложение",
|
||||
"Season": "Причина",
|
||||
"CurrentlyInstalled": "Понастоящем инсталиран",
|
||||
"DownloadClientSettingsAddPaused": "Добави на пауза",
|
||||
"Encoding": "Кодиране",
|
||||
"Episode": "епизод",
|
||||
"Applications": "Приложения",
|
||||
"Publisher": "Издател",
|
||||
"Id": "ИН",
|
||||
"Theme": "Тема",
|
||||
"Label": "Етикет",
|
||||
"Categories": "Категории",
|
||||
"Album": "албум",
|
||||
"Artist": "изпълнител"
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
"Priority": "Prioritat",
|
||||
"SendAnonymousUsageData": "Envia dades d'ús anònimes",
|
||||
"SetTags": "Estableix etiquetes",
|
||||
"SystemTimeCheckMessage": "L'hora del sistema està apagada durant més d'1 dia. És possible que les tasques programades no s'executin correctament fins que no es corregeixi l'hora",
|
||||
"SystemTimeHealthCheckMessage": "L'hora del sistema està apagada durant més d'1 dia. És possible que les tasques programades no s'executin correctament fins que no es corregeixi l'hora",
|
||||
"TableOptions": "Opcions de taula",
|
||||
"TableOptionsColumnsMessage": "Trieu quines columnes són visibles i en quin ordre apareixen",
|
||||
"Columns": "Columnes",
|
||||
@@ -499,5 +499,7 @@
|
||||
"TheLogLevelDefault": "El nivell de registre per defecte és \"Info\" i es pot canviar a [Configuració general](/configuració/general)",
|
||||
"UpdateAppDirectlyLoadError": "No es pot actualitzar {appName} directament,",
|
||||
"WouldYouLikeToRestoreBackup": "Voleu restaurar la còpia de seguretat '{name}'?",
|
||||
"InstallLatest": "Instal·la l'últim"
|
||||
"InstallLatest": "Instal·la l'últim",
|
||||
"CurrentlyInstalled": "Instal·lat actualment",
|
||||
"DownloadClientSettingsAddPaused": "Afegeix pausats"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Add": "Přidat",
|
||||
"CertificateValidation": "Ověření certifikátu",
|
||||
"DeleteBackupMessageText": "Opravdu chcete odstranit zálohu '{name}'?",
|
||||
"CertificateValidation": "Ověřování certifikátu",
|
||||
"DeleteBackupMessageText": "Opravdu chcete odstranit zálohu ‚{name}‘?",
|
||||
"YesCancel": "Ano, zrušit",
|
||||
"About": "O aplikaci",
|
||||
"Component": "Komponenta",
|
||||
@@ -18,12 +18,12 @@
|
||||
"Usenet": "Usenet",
|
||||
"AddDownloadClient": "Přidat klienta pro stahování",
|
||||
"Backups": "Zálohy",
|
||||
"CancelPendingTask": "Opravdu chcete zrušit tento nevyřízený úkol?",
|
||||
"CancelPendingTask": "Opravdu chcete zrušit tento úkol čekající na vyřízení?",
|
||||
"MovieIndexScrollBottom": "Rejstřík filmů: Posun dolů",
|
||||
"ProxyType": "Typ serveru proxy",
|
||||
"Reddit": "Reddit",
|
||||
"ErrorLoadingContents": "Chyba při načítání obsahu",
|
||||
"IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Všechny indexery nejsou k dispozici z důvodu selhání po dobu delší než 6 hodin",
|
||||
"IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Všechny indexery jsou nedostupné z důvodu selhání déle než 6 hodin",
|
||||
"RemovedFromTaskQueue": "Odebráno z fronty úkolů",
|
||||
"ResetAPIKey": "Resetovat klíč API",
|
||||
"SSLCertPassword": "Heslo SSL Cert",
|
||||
@@ -35,27 +35,27 @@
|
||||
"Warn": "Varovat",
|
||||
"Wiki": "Wiki",
|
||||
"Connections": "Připojení",
|
||||
"DeleteDownloadClientMessageText": "Opravdu chcete odstranit klienta pro stahování '{name}'?",
|
||||
"Details": "Detaily",
|
||||
"DeleteDownloadClientMessageText": "Opravdu chcete odstranit klienta pro stahování ‚{name}‘?",
|
||||
"Details": "Podrobnosti",
|
||||
"Disabled": "Zakázáno",
|
||||
"Docker": "Přístavní dělník",
|
||||
"Docker": "Docker",
|
||||
"Donations": "Dary",
|
||||
"DownloadClientSettings": "Stáhněte si nastavení klienta",
|
||||
"DownloadClientStatusAllClientHealthCheckMessage": "Všichni klienti pro stahování nejsou kvůli chybám k dispozici",
|
||||
"DownloadClientStatusSingleClientHealthCheckMessage": "Stahování klientů není k dispozici z důvodu selhání: {downloadClientNames}",
|
||||
"DownloadClientSettings": "Nastavení klienta pro stahování",
|
||||
"DownloadClientStatusAllClientHealthCheckMessage": "Všichni klienti pro stahování jsou nedostupní z důvodu selhání",
|
||||
"DownloadClientStatusSingleClientHealthCheckMessage": "Klienti pro stahování jsou nedostupní z důvodu selhání: {downloadClientNames}",
|
||||
"Folder": "Složka",
|
||||
"Grabs": "Urvat",
|
||||
"Grabs": "Získané",
|
||||
"NoIssuesWithYourConfiguration": "Žádné problémy s vaší konfigurací",
|
||||
"HideAdvanced": "Skrýt pokročilé",
|
||||
"Host": "Hostitel",
|
||||
"Hostname": "Název hostitele",
|
||||
"IncludeHealthWarningsHelpText": "Zahrnout zdravotní varování",
|
||||
"IncludeHealthWarningsHelpText": "Včetně varování ohledně zdraví",
|
||||
"Indexer": "Indexer",
|
||||
"IndexerFlags": "Příznaky indexeru",
|
||||
"IndexerPriority": "Priorita indexování",
|
||||
"IndexerPriorityHelpText": "Priorita indexování od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 25.",
|
||||
"IndexerPriority": "Priorita indexeru",
|
||||
"IndexerPriorityHelpText": "Priorita indexeru od 1 (Nejvyšší) do 50 (Nejnižší). Výchozí: 25.",
|
||||
"Indexers": "Indexery",
|
||||
"IndexerStatusAllUnavailableHealthCheckMessage": "Všechny indexery nejsou k dispozici z důvodu selhání",
|
||||
"IndexerStatusAllUnavailableHealthCheckMessage": "Všechny indexery jsou nedostupné z důvodu selhání",
|
||||
"LastWriteTime": "Čas posledního zápisu",
|
||||
"Level": "Úroveň",
|
||||
"LogLevel": "Úroveň protokolu",
|
||||
@@ -69,23 +69,23 @@
|
||||
"UnselectAll": "Odznačit vše",
|
||||
"UpdateStartupNotWritableHealthCheckMessage": "Aktualizaci nelze nainstalovat, protože spouštěcí složku „{startupFolder}“ nelze zapisovat uživatelem „{userName}“.",
|
||||
"Version": "Verze",
|
||||
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery {appName}u. To zahrnuje informace o vašem prohlížeči, které stránky {appName} WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.",
|
||||
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery {appName}u. To zahrnuje informace o vašem prohlížeči, které stránky webového rozhraní {appName}u používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k určení priorit funkcí a oprav chyb.",
|
||||
"ApiKey": "Klíč API",
|
||||
"AppDataDirectory": "Adresář AppData",
|
||||
"AppDataLocationHealthCheckMessage": "Aktualizace nebude možná, aby se zabránilo odstranění AppData při aktualizaci",
|
||||
"ApplicationStatusCheckAllClientMessage": "Všechny seznamy nejsou k dispozici z důvodu selhání",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Seznamy nejsou k dispozici z důvodu selhání: {0}",
|
||||
"ApplicationStatusCheckAllClientMessage": "Všechny aplikace jsou nedostupné z důvodu selhání",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Aplikace nedostupné z důvodu selhání: {0}",
|
||||
"Apply": "Použít",
|
||||
"Branch": "Větev",
|
||||
"BranchUpdate": "Pobočka, která se má použít k aktualizaci {appName}",
|
||||
"EditIndexer": "Upravit indexátor",
|
||||
"BranchUpdate": "Větev použitá k aktualizaci {appName}u",
|
||||
"EditIndexer": "Upravit indexer",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Další informace o jednotlivých klientech pro stahování získáte kliknutím na informační tlačítka.",
|
||||
"General": "Všeobecné",
|
||||
"CloseCurrentModal": "Zavřít aktuální modální",
|
||||
"General": "Obecné",
|
||||
"CloseCurrentModal": "Zavřít aktuální modální okno",
|
||||
"Columns": "Sloupce",
|
||||
"ConnectionLost": "Spojení ztraceno",
|
||||
"ConnectionLost": "Ztráta spojení",
|
||||
"ConnectSettings": "Nastavení připojení",
|
||||
"Custom": "Zvyk",
|
||||
"Custom": "Vlastní",
|
||||
"Error": "Chyba",
|
||||
"Failed": "Selhalo",
|
||||
"FeatureRequests": "Žádosti o funkce",
|
||||
@@ -93,9 +93,9 @@
|
||||
"Files": "Soubory",
|
||||
"Filter": "Filtr",
|
||||
"Fixed": "Pevný",
|
||||
"FocusSearchBox": "Zaostřovací vyhledávací pole",
|
||||
"FocusSearchBox": "Zaměřit vyhledávací pole",
|
||||
"GeneralSettingsSummary": "Port, SSL, uživatelské jméno / heslo, proxy, analytika a aktualizace",
|
||||
"History": "Dějiny",
|
||||
"History": "Historie",
|
||||
"HomePage": "Domovská stránka",
|
||||
"SettingsEnableColorImpairedModeHelpText": "Upravený styl umožňující uživatelům s barevným postižením lépe rozlišovat barevně kódované informace",
|
||||
"SettingsLongDateFormat": "Long Date Format",
|
||||
@@ -105,8 +105,8 @@
|
||||
"Tasks": "Úkoly",
|
||||
"Test": "Test",
|
||||
"UnableToLoadTags": "Značky nelze načíst",
|
||||
"IndexerProxyStatusAllUnavailableHealthCheckMessage": "Všechny indexery nejsou k dispozici z důvodu selhání",
|
||||
"ApplyTags": "Použít značky",
|
||||
"IndexerProxyStatusAllUnavailableHealthCheckMessage": "Všechny proxy indexerů jsou nedostupné z důvodu selhání",
|
||||
"ApplyTags": "Použít štítky",
|
||||
"MoreInfo": "Více informací",
|
||||
"System": "Systém",
|
||||
"Enabled": "Povoleno",
|
||||
@@ -121,7 +121,7 @@
|
||||
"NoLinks": "Žádné odkazy",
|
||||
"Presets": "Předvolby",
|
||||
"Priority": "Přednost",
|
||||
"Grabbed": "Popadl",
|
||||
"Grabbed": "Získáno",
|
||||
"Health": "Zdraví",
|
||||
"LogLevelTraceHelpTextWarning": "Trasování protokolování by mělo být povoleno pouze dočasně",
|
||||
"ProxyBadRequestHealthCheckMessage": "Nepodařilo se otestovat proxy. StatusCode: {statusCode}",
|
||||
@@ -171,19 +171,19 @@
|
||||
"UseProxy": "Použij proxy",
|
||||
"Username": "Uživatelské jméno",
|
||||
"Yesterday": "Včera",
|
||||
"AutomaticSearch": "Vyhledat automaticky",
|
||||
"BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti {appName}",
|
||||
"AutomaticSearch": "Automatické vyhledávání",
|
||||
"BackupFolderHelpText": "Relativní cesty budou v adresáři AppData {appName}u",
|
||||
"BackupIntervalHelpText": "Interval mezi automatickými zálohami",
|
||||
"BackupNow": "Ihned zálohovat",
|
||||
"BackupNow": "Zálohovat nyní",
|
||||
"BackupRetentionHelpText": "Automatické zálohy starší než doba uchovávání budou automaticky vyčištěny",
|
||||
"BeforeUpdate": "Před zálohováním",
|
||||
"BeforeUpdate": "Před aktualizací",
|
||||
"BindAddress": "Vázat adresu",
|
||||
"BindAddressHelpText": "Platná IP adresa, localhost nebo '*' pro všechna rozhraní",
|
||||
"BranchUpdateMechanism": "Větev používaná externím aktualizačním mechanismem",
|
||||
"BindAddressHelpText": "Platná IP adresa, localhost nebo ‚*‘ pro všechna rozhraní",
|
||||
"BranchUpdateMechanism": "Větev použitá externím aktualizačním mechanismem",
|
||||
"BypassProxyForLocalAddresses": "Obcházení proxy serveru pro místní adresy",
|
||||
"DeleteIndexerProxyMessageText": "Opravdu chcete smazat značku „{0}“?",
|
||||
"DeleteTag": "Smazat značku",
|
||||
"IndexerProxyStatusUnavailableHealthCheckMessage": "Indexery nedostupné z důvodu selhání: {indexerProxyNames}",
|
||||
"DeleteIndexerProxyMessageText": "Opravdu chcete odstranit proxy indexeru ‚{name}‘?",
|
||||
"DeleteTag": "Odstranit štítek",
|
||||
"IndexerProxyStatusUnavailableHealthCheckMessage": "Proxy indexerů nedostupné z důvodu selhání: {indexerProxyNames}",
|
||||
"Name": "název",
|
||||
"New": "Nový",
|
||||
"Protocol": "Protokol",
|
||||
@@ -201,9 +201,9 @@
|
||||
"BackupsLoadError": "Nelze načíst zálohy",
|
||||
"DownloadClientsLoadError": "Nelze načíst klienty pro stahování",
|
||||
"UnableToLoadGeneralSettings": "Nelze načíst obecná nastavení",
|
||||
"DeleteNotification": "Smazat oznámení",
|
||||
"DeleteNotification": "Odstranit oznámení",
|
||||
"EnableAutomaticSearch": "Povolit automatické vyhledávání",
|
||||
"EnableInteractiveSearchHelpText": "Bude použito při použití interaktivního vyhledávání",
|
||||
"EnableInteractiveSearchHelpText": "Použije se při interaktivním vyhledávání",
|
||||
"GeneralSettings": "Obecné nastavení",
|
||||
"InteractiveSearch": "Interaktivní vyhledávání",
|
||||
"Interval": "Interval",
|
||||
@@ -221,51 +221,51 @@
|
||||
"Restore": "Obnovit",
|
||||
"SettingsShowRelativeDates": "Zobrazit relativní data",
|
||||
"SettingsShowRelativeDatesHelpText": "Zobrazit relativní (dnes / včera / atd.) Nebo absolutní data",
|
||||
"SystemTimeCheckMessage": "Systémový čas je vypnutý o více než 1 den. Naplánované úlohy nemusí fungovat správně, dokud nebude čas opraven",
|
||||
"AddingTag": "Přidání značky",
|
||||
"SystemTimeHealthCheckMessage": "Systémový čas je vypnutý o více než 1 den. Naplánované úlohy nemusí fungovat správně, dokud nebude čas opraven",
|
||||
"AddingTag": "Přidávání štítku",
|
||||
"Age": "Stáří",
|
||||
"All": "Vše",
|
||||
"AllIndexersHiddenDueToFilter": "Všechny filmy jsou skryty kvůli použitému filtru.",
|
||||
"AllIndexersHiddenDueToFilter": "Všechny indexery jsou skryty kvůli použitému filtru.",
|
||||
"Analytics": "Analýzy",
|
||||
"EnableRss": "Povolit RSS",
|
||||
"NoChange": "Žádná změna",
|
||||
"Authentication": "Ověřování",
|
||||
"AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k {appName}",
|
||||
"AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k {appName}u",
|
||||
"Automatic": "Automatický",
|
||||
"Backup": "Záloha",
|
||||
"Cancel": "Zrušit",
|
||||
"CertificateValidationHelpText": "Změňte, jak přísné je ověření certifikace HTTPS",
|
||||
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikace HTTPS",
|
||||
"ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena",
|
||||
"Clear": "Vyčistit",
|
||||
"Clear": "Vymazat",
|
||||
"ClientPriority": "Priorita klienta",
|
||||
"CloneProfile": "Klonovat profil",
|
||||
"Close": "Zavřít",
|
||||
"CouldNotConnectSignalR": "Nelze se připojit k SignalR, uživatelské rozhraní se neaktualizuje",
|
||||
"CustomFilters": "Vlastní filtry",
|
||||
"Date": "datum",
|
||||
"Dates": "Termíny",
|
||||
"Date": "Datum",
|
||||
"Dates": "Data",
|
||||
"DatabaseMigration": "Migrace databáze",
|
||||
"Delete": "Vymazat",
|
||||
"DeleteApplicationMessageText": "Opravdu chcete smazat oznámení „{0}“?",
|
||||
"Delete": "Odstranit",
|
||||
"DeleteApplicationMessageText": "Opravdu chcete odstranit aplikaci ‚{name}‘?",
|
||||
"DeleteBackup": "Odstranit zálohu",
|
||||
"DeleteDownloadClient": "Odstranit staženého klienta",
|
||||
"DeleteNotificationMessageText": "Opravdu chcete smazat oznámení '{name}'?",
|
||||
"DeleteTagMessageText": "Opravdu chcete smazat značku „{0}“?",
|
||||
"Discord": "Svár",
|
||||
"DownloadClient": "Stáhnout klienta",
|
||||
"DownloadClients": "Stáhnout klienty",
|
||||
"DeleteDownloadClient": "Odstranit klienta pro stahování",
|
||||
"DeleteNotificationMessageText": "Opravdu chcete odstranit oznámení ‚{name}‘?",
|
||||
"DeleteTagMessageText": "Opravdu chcete odstranit štítek ‚{label}‘?",
|
||||
"Discord": "Discord",
|
||||
"DownloadClient": "Klient pro stahování",
|
||||
"DownloadClients": "Klienti pro stahování",
|
||||
"Edit": "Upravit",
|
||||
"Enable": "Umožnit",
|
||||
"EnableAutomaticSearchHelpText": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo {appName}",
|
||||
"Enable": "Povolit",
|
||||
"EnableAutomaticSearchHelpText": "Použije se při automatickém vyhledávání prostřednictvím uživatelského rozhraní nebo pomocí {appName}",
|
||||
"EnableInteractiveSearch": "Povolit interaktivní vyhledávání",
|
||||
"EnableSSL": "Povolit SSL",
|
||||
"EnableSslHelpText": " Vyžaduje restartování spuštěné jako správce, aby se projevilo",
|
||||
"Events": "Události",
|
||||
"EventType": "Typ události",
|
||||
"Exception": "Výjimka",
|
||||
"ExistingTag": "Stávající značka",
|
||||
"ExistingTag": "Stávající štítek",
|
||||
"IllRestartLater": "Restartuji později",
|
||||
"IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexery nedostupné z důvodu selhání po dobu delší než 6 hodin: {indexerNames}",
|
||||
"IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexery nedostupné z důvodu selhání déle než 6 hodin: {indexerNames}",
|
||||
"IndexerStatusUnavailableHealthCheckMessage": "Indexery nedostupné z důvodu selhání: {indexerNames}",
|
||||
"SettingsTimeFormat": "Časový formát",
|
||||
"ShowAdvanced": "Zobrazit pokročilé",
|
||||
@@ -307,9 +307,9 @@
|
||||
"UnsavedChanges": "Neuložené změny",
|
||||
"UpdateAutomaticallyHelpText": "Automaticky stahovat a instalovat aktualizace. Stále budete moci instalovat ze systému: Aktualizace",
|
||||
"NetCore": ".NET Core",
|
||||
"Filters": "Filtr",
|
||||
"HistoryCleanupDaysHelpText": "Nastavením na 0 zakážete automatické čištění",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Soubory v koši starší než vybraný počet dní budou automaticky vyčištěny",
|
||||
"Filters": "Filtry",
|
||||
"HistoryCleanupDaysHelpText": "Nastavte na 0 pro zakázání automatického čištění",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Položky historie starší než vybraný počet dní se vyčistí automaticky",
|
||||
"MaintenanceRelease": "Údržbové vydání: opravy chyb a další vylepšení. Další podrobnosti najdete v GitHub Commit History",
|
||||
"OnGrab": "Chyť",
|
||||
"OnHealthIssue": "K otázce zdraví",
|
||||
@@ -319,10 +319,10 @@
|
||||
"No": "Ne",
|
||||
"UnableToLoadIndexers": "Nelze načíst indexery",
|
||||
"Yes": "Ano",
|
||||
"GrabReleases": "Uchopte uvolnění",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání po dobu delší než 6 hodin: {0}",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "Všechny indexery nejsou k dispozici z důvodu selhání po dobu delší než 6 hodin",
|
||||
"Ended": "Skončil",
|
||||
"GrabReleases": "Získat vydání",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Aplikace nedostupné z důvodu selhání déle než 6 hodin: {0}",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "Všechny aplikace jsou nedostupné z důvodu selhání déle než 6 hodin",
|
||||
"Ended": "Ukončeno",
|
||||
"LastDuration": "lastDuration",
|
||||
"LastExecution": "Poslední poprava",
|
||||
"NextExecution": "Další spuštění",
|
||||
@@ -331,47 +331,47 @@
|
||||
"Replace": "Nahradit",
|
||||
"OnLatestVersion": "Nejnovější verze aplikace {appName} je již nainstalována",
|
||||
"More": "Více",
|
||||
"ApplyTagsHelpTextAdd": "Přidat: Přidá značky k již existujícímu seznamu",
|
||||
"ApplyTagsHelpTextHowToApplyApplications": "Jak použít značky na vybrané filmy",
|
||||
"DeleteSelectedDownloadClients": "Odstranit klienta pro stahování",
|
||||
"DeleteSelectedIndexersMessageText": "Opravdu chcete smazat {count} vybraný(ch) indexer(ů)?",
|
||||
"DeleteSelectedApplicationsMessageText": "Opravdu chcete odstranit indexer „{0}“?",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Opravdu chcete smazat {count} vybraných klientů pro stahování?",
|
||||
"ApplyTagsHelpTextAdd": "Přidat: Přidat štítky do existujícího seznamu štítků",
|
||||
"ApplyTagsHelpTextHowToApplyApplications": "Jak použít štítky na vybrané aplikace",
|
||||
"DeleteSelectedDownloadClients": "Odstranit klienty pro stahování",
|
||||
"DeleteSelectedIndexersMessageText": "Opravdu chcete odstranit {count} vybraných indexerů?",
|
||||
"DeleteSelectedApplicationsMessageText": "Opravdu chcete odstranit {count} vybraných aplikací?",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Opravdu chcete odstranit {count} vybraných klientů pro stahování?",
|
||||
"Year": "Rok",
|
||||
"ApplyTagsHelpTextRemove": "Odebrat: Odebrat zadané značky",
|
||||
"DownloadClientPriorityHelpText": "Upřednostněte více klientů pro stahování. Round-Robin se používá pro klienty se stejnou prioritou.",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít značky na vybrané indexery",
|
||||
"ApplyTagsHelpTextReplace": "Nahradit: Nahradit značky zadanými značkami (prázdné pole vymaže všechny značky)",
|
||||
"ApplyTagsHelpTextRemove": "Odebrat: Odebrat zadané štítky",
|
||||
"DownloadClientPriorityHelpText": "Upřednostněte více klientů pro stahování. Pro klienty se stejnou prioritou se používá funkce Round-Robin.",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít štítky na vybrané indexery",
|
||||
"ApplyTagsHelpTextReplace": "Nahradit: Nahradit štítky zadanými štítky (prázdné pole vymaže všechny štítky)",
|
||||
"Track": "Stopa",
|
||||
"Genre": "Žánry",
|
||||
"Genre": "Žánr",
|
||||
"ConnectionLostReconnect": "{appName} se pokusí připojit automaticky, nebo můžete kliknout na tlačítko znovunačtení níže.",
|
||||
"RecentChanges": "Nedávné změny",
|
||||
"WhatsNew": "Co je nového?",
|
||||
"DeleteAppProfileMessageText": "Opravdu chcete smazat kvalitní profil {0}",
|
||||
"ConnectionLostToBackend": "{appName} ztratil spojení s backendem a pro obnovení funkčnosti bude třebaho znovu načíst.",
|
||||
"DeleteAppProfileMessageText": "Opravdu chcete odstranit profil aplikace ‚{name}‘?",
|
||||
"ConnectionLostToBackend": "{appName} ztratil spojení s backendem a pro obnovení funkčnosti bude potřeba ho znovu načíst.",
|
||||
"minutes": "Minut",
|
||||
"ApplicationURL": "URL aplikace",
|
||||
"ApplicationUrlHelpText": "Externí adresa URL této aplikace včetně http(s)://, portu a základní adresy URL",
|
||||
"ApplicationUrlHelpText": "Externí adresa URL této aplikace včetně http(s)://, portu a základu URL",
|
||||
"ApplyChanges": "Použít změny",
|
||||
"ApiKeyValidationHealthCheckMessage": "Aktualizujte svůj klíč API tak, aby měl alespoň {length} znaků. Můžete to provést prostřednictvím nastavení nebo konfiguračního souboru",
|
||||
"AppUpdated": "{appName} aktualizován",
|
||||
"AddDownloadClientImplementation": "Přidat klienta pro stahování - {implementationName}",
|
||||
"AuthenticationRequired": "Vyžadované ověření",
|
||||
"AuthenticationRequiredHelpText": "Změnit, pro které požadavky je vyžadováno ověření. Pokud nerozumíte rizikům, neměňte je.",
|
||||
"AddDownloadClientImplementation": "Přidat klienta pro stahování – {implementationName}",
|
||||
"AuthenticationRequired": "Vyžadováno ověření",
|
||||
"AuthenticationRequiredHelpText": "Změnit, pro které požadavky je vyžadováno ověření. Neměňte, pokud nerozumíte rizikům.",
|
||||
"AddCustomFilter": "Přidat vlastní filtr",
|
||||
"AddConnection": "Přidat spojení",
|
||||
"AddConnectionImplementation": "Přidat spojení - {implementationName}",
|
||||
"AddIndexerImplementation": "Přidat indexer - {implementationName}",
|
||||
"AddConnectionImplementation": "Přidat spojení – {implementationName}",
|
||||
"AddIndexerImplementation": "Přidat indexer – {implementationName}",
|
||||
"Publisher": "Vydavatel",
|
||||
"Categories": "Kategorie",
|
||||
"Notification": "Oznámení",
|
||||
"AddApplicationImplementation": "Přidat spojení - {implementationName}",
|
||||
"AddIndexerProxyImplementation": "Přidat indexátor - {implementationName}",
|
||||
"AddApplicationImplementation": "Přidat aplikaci – {implementationName}",
|
||||
"AddIndexerProxyImplementation": "Přidat proxy server indexeru – {implementationName}",
|
||||
"Artist": "Umělec",
|
||||
"EditIndexerImplementation": "Upravit indexer - {implementationName}",
|
||||
"Episode": "epizoda",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Všechny seznamy nejsou k dispozici z důvodu selhání",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Seznamy nejsou k dispozici z důvodu selhání: {notificationNames}",
|
||||
"Episode": "Epizoda",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Všechna oznámení jsou nedostupná z důvodu selhání",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Oznámení nedostupná z důvodu selhání: {notificationNames}",
|
||||
"Application": "Aplikace",
|
||||
"AppUpdatedVersion": "{appName} byl aktualizován na verzi `{version}`, abyste získali nejnovější změny, musíte znovu načíst {appName}",
|
||||
"Encoding": "Kódování",
|
||||
@@ -382,49 +382,49 @@
|
||||
"Album": "Album",
|
||||
"Applications": "Aplikace",
|
||||
"Connect": "Oznámení",
|
||||
"EditConnectionImplementation": "Přidat spojení - {implementationName}",
|
||||
"EditConnectionImplementation": "Upravit připojení - {implementationName}",
|
||||
"EditDownloadClientImplementation": "Upravit klienta pro stahování - {implementationName}",
|
||||
"AuthForm": "Formuláře (přihlašovací stránka)",
|
||||
"Clone": "Klonovat",
|
||||
"DefaultNameCopiedProfile": "{name} - Kopírovat",
|
||||
"DisabledForLocalAddresses": "Zakázáno pro místní adresy",
|
||||
"EditApplicationImplementation": "Přidat spojení - {implementationName}",
|
||||
"EditApplicationImplementation": "Upravit aplikaci - {implementationName}",
|
||||
"None": "Žádný",
|
||||
"ResetAPIKeyMessageText": "Opravdu chcete resetovat klíč API?",
|
||||
"Database": "Databáze",
|
||||
"CountDownloadClientsSelected": "{count} vybraných klientů ke stahování",
|
||||
"CountDownloadClientsSelected": "{count} vybraných klientů pro stahování",
|
||||
"CountIndexersSelected": "{count} vybraných indexerů",
|
||||
"EditIndexerProxyImplementation": "Přidat indexátor - {implementationName}",
|
||||
"EditIndexerProxyImplementation": "Upravit proxy indexeru - {implementationName}",
|
||||
"AuthBasic": "Základní (vyskakovací okno prohlížeče)",
|
||||
"AuthenticationRequiredWarning": "Aby se zabránilo vzdálenému přístupu bez ověření, vyžaduje nyní {appName} povolení ověření. Ověřování z místních adres můžete volitelně zakázat.",
|
||||
"AuthenticationRequiredWarning": "Aby se zabránilo vzdálenému přístupu bez ověření, vyžaduje nyní {appName}, aby bylo povoleno ověřování. Volitelně můžete zakázat ověřování z místních adres.",
|
||||
"RestartProwlarr": "Restartujte {appName}",
|
||||
"Duration": "Trvání",
|
||||
"EditSelectedDownloadClients": "Upravit vybrané klienty pro stahování",
|
||||
"EditSelectedIndexers": "Upravit vybrané indexery",
|
||||
"AuthenticationMethod": "Metoda ověřování",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Vložte nové heslo",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Vložte nové uživatelské jméno",
|
||||
"AuthenticationMethodHelpTextWarning": "Prosím vyberte platnou metodu ověřování",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Zadejte nové heslo",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Zadejte nové uživatelské jméno",
|
||||
"AuthenticationMethodHelpTextWarning": "Vyberte platnou metodu ověřování",
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Potvrďte nové heslo",
|
||||
"days": "dnů",
|
||||
"Id": "ID",
|
||||
"CountApplicationsSelected": "Vybráno {0} kolekcí",
|
||||
"IndexerHDBitsSettingsCodecs": "Kodek",
|
||||
"CountApplicationsSelected": "{count} vybraných aplikací",
|
||||
"IndexerHDBitsSettingsCodecs": "Kodeky",
|
||||
"IndexerHDBitsSettingsMediums": "Střední",
|
||||
"Directory": "Adresář",
|
||||
"CustomFilter": "Vlastní filtry",
|
||||
"CustomFilter": "Vlastní filtr",
|
||||
"ProxyValidationBadRequest": "Nepodařilo se otestovat proxy. StatusCode: {statusCode}",
|
||||
"Default": "Výchozí",
|
||||
"GrabRelease": "Uchopte uvolnění",
|
||||
"GrabRelease": "Získat vydání",
|
||||
"Category": "Kategorie",
|
||||
"BlackholeFolderHelpText": "Složka do které {appName} uloží {extension} soubor",
|
||||
"DownloadClientSettingsUrlBaseHelpText": "Přidá předponu do {connectionName} url, jako např. {url}",
|
||||
"BlackholeFolderHelpText": "Složka, do které {appName} uloží soubor {extension}",
|
||||
"DownloadClientSettingsUrlBaseHelpText": "Přidá předponu k url {clientName}, například {url}",
|
||||
"Any": "Jakákoliv",
|
||||
"BuiltIn": "Vestavěný",
|
||||
"Script": "Skript",
|
||||
"PublishedDate": "Datum zveřejnění",
|
||||
"AllSearchResultsHiddenByFilter": "Všechny výsledky jsou schovány použitým filtrem",
|
||||
"DockerUpdater": "aktualizujte kontejner dockeru, abyste aktualizaci obdrželi",
|
||||
"AllSearchResultsHiddenByFilter": "Všechny výsledky vyhledávání jsou skryty použitým filtrem.",
|
||||
"DockerUpdater": "Aktualizujte kontejner dockeru, abyste získali aktualizaci",
|
||||
"Download": "Stažení",
|
||||
"ErrorRestoringBackup": "Chyba při obnovování zálohy",
|
||||
"ExternalUpdater": "{appName} je nakonfigurován pro použití externího aktualizačního mechanismu",
|
||||
@@ -432,6 +432,206 @@
|
||||
"NoEventsFound": "Nebyly nalezeny žádné události",
|
||||
"RestartReloadNote": "Poznámka: {appName} se během procesu obnovy automaticky restartuje a znovu načte uživatelské rozhraní.",
|
||||
"UpdateAppDirectlyLoadError": "{appName} nelze aktualizovat přímo,",
|
||||
"AptUpdater": "K instalaci aktualizace použijte apt",
|
||||
"InstallLatest": "Nainstalujte nejnovější"
|
||||
"AptUpdater": "K instalaci aktualizace používat apt",
|
||||
"InstallLatest": "Nainstalujte nejnovější",
|
||||
"Stats": "Postavení",
|
||||
"CurrentlyInstalled": "Aktuálně nainstalováno",
|
||||
"Mixed": "Pevný",
|
||||
"ActiveIndexers": "Aktivní indexery",
|
||||
"ActiveApps": "Aktivní aplikace",
|
||||
"AppSettingsSummary": "Aplikace a nastavení pro konfiguraci interakce {appName}u s vašimi programy PVR",
|
||||
"ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText": "Pokud je torrent blokován pomocí hash, nemusí být u některých indexerů správně odmítnut během RSS/vyhledávání. Povolení této funkce umožní jeho odmítnutí po zachycení torrentu, ale před jeho odesláním klientovi.",
|
||||
"ApplicationsLoadError": "Nelze načíst seznam aplikací",
|
||||
"AppProfileInUse": "Používaný profil aplikace",
|
||||
"AppsMinimumSeedersHelpText": "Minimální počet seederů požadovaných aplikacemi pro indexer, výchozí hodnota synchronizačního profilu je prázdná",
|
||||
"AverageGrabs": "Průměrné získání",
|
||||
"AverageQueries": "Průměrné dotazy",
|
||||
"AdvancedSettingsShownClickToHide": "Rozšířená nastavení jsou zobrazená, klikněte pro skrytí",
|
||||
"AdvancedSettingsHiddenClickToShow": "Rozšířená nastavení jsou skrytá, klikněte pro zobrazení",
|
||||
"AppsMinimumSeeders": "Minimální počet seederů aplikací",
|
||||
"AddNewIndexer": "Přidat nový indexer",
|
||||
"AddToDownloadClient": "Přidat vydání do klienta pro stahování",
|
||||
"AddIndexerProxy": "Přidat proxy server indexeru",
|
||||
"AppProfileSelectHelpText": "Profily aplikace slouží k ovládání nastavení RSS, automatického vyhledávání a interaktivního vyhledávání při synchronizaci aplikace",
|
||||
"BookSearch": "Vyhledávání knihy",
|
||||
"ClearHistory": "Vymazat historii",
|
||||
"Auth": "Ověřování",
|
||||
"ConnectSettingsSummary": "Oznámení a vlastní skripty",
|
||||
"AreYouSureYouWantToDeleteIndexer": "Opravdu chcete odstranit ‚{name}‘ z {appName}u?",
|
||||
"AuthQueries": "Ověřovací dotazy",
|
||||
"CountIndexersAvailable": "{count} dostupných indexerů",
|
||||
"ApplicationTagsHelpText": "Synchronizovat s touto aplikací indexery, které mají jeden nebo více shodných štítků. Pokud zde nejsou uvedeny žádné štítky, nebude synchronizace žádných indexerů znemožněna kvůli jejich štítkům.",
|
||||
"ApplicationTagsHelpTextWarning": "Štítky je potřeba používat opatrně, mohou mít nechtěné účinky. Aplikace se štítkem se bude synchronizovat pouze s indexery se stejným štítkem.",
|
||||
"BasicSearch": "Základní vyhledávání",
|
||||
"ClearHistoryMessageText": "Opravdu chcete vymazat celou historii {appName}u?",
|
||||
"AddDownloadClientToProwlarr": "Přidání klienta pro stahování umožňuje {appName} odesílat vydání přímo z uživatelského rozhraní při ručním vyhledávání.",
|
||||
"AddRemoveOnly": "Pouze přidat a odebrat",
|
||||
"AudioSearch": "Vyhledávání audia",
|
||||
"ApplicationSettingsSyncRejectBlocklistedTorrentHashes": "Synchronizovat odmítnuté blokované hashe torrentů při získávání",
|
||||
"Apps": "Aplikace",
|
||||
"ClickToChangeQueryOptions": "Kliknutím změníte možnosti dotazu",
|
||||
"Author": "Autor",
|
||||
"AverageResponseTimesMs": "Průměrné doby odezvy indexerů (ms)",
|
||||
"Book": "Kniha",
|
||||
"BookSearchTypes": "Typy vyhledávání knihy",
|
||||
"AddApplication": "Přidat aplikaci",
|
||||
"AddSyncProfile": "Přidat synchronizační profil",
|
||||
"AddedToDownloadClient": "Vydání přidáno do klienta",
|
||||
"AddCategory": "Přidat kategorii",
|
||||
"AreYouSureYouWantToDeleteCategory": "Opravdu chcete odstranit namapovanou kategorii?",
|
||||
"DownloadClientRTorrentSettingsUrlPath": "Cesta URL",
|
||||
"DefaultCategory": "Výchozí kategorie",
|
||||
"DownloadClientFloodSettingsUrlBaseHelpText": "Přidá předponu do Flood API, například {url}",
|
||||
"DownloadClientFreeboxSettingsApiUrl": "URL API",
|
||||
"DownloadClientSettingsInitialState": "Počáteční stav",
|
||||
"DownloadClientSettingsPriorityItemHelpText": "Priorita použitá při získávání položek",
|
||||
"FailedToFetchSettings": "Nepodařilo se načíst nastavení",
|
||||
"GrabTitle": "Získat název",
|
||||
"DownloadClientNzbgetSettingsAddPausedHelpText": "Tato volba vyžaduje NzbGet verze alespoň 16.0",
|
||||
"EnableRssHelpText": "Povolit kanál RSS pro indexer",
|
||||
"DeleteApplication": "Odstranit aplikaci",
|
||||
"DeleteSelectedApplications": "Odstranit vybrané aplikace",
|
||||
"DeleteSelectedIndexers": "Odstranit vybrané indexery",
|
||||
"DevelopmentSettings": "Nastavení pro vývoj",
|
||||
"DisabledUntil": "Zakázáno do",
|
||||
"DownloadClientCategory": "Kategorie klienta pro stahování",
|
||||
"DownloadClientDelugeSettingsUrlBaseHelpText": "Přidá předponu do url adresy json deluge, viz {url}",
|
||||
"DownloadClientDownloadStationSettingsDirectoryHelpText": "Volitelná sdílená složka pro stahování, ponechte prázdné pro použití výchozího umístění Download Station",
|
||||
"DownloadClientFloodSettingsAdditionalTags": "Další štítky",
|
||||
"DownloadClientFloodSettingsAdditionalTagsHelpText": "Přidá vlastnosti médií jako štítky. Nápovědy jsou příklady.",
|
||||
"DownloadClientFloodSettingsTagsHelpText": "Počáteční štítky stahování. Aby bylo stahování rozpoznáno, musí mít všechny počáteční štítky. Tím se zabrání konfliktům s nesouvisejícími stahováními.",
|
||||
"DownloadClientFreeboxSettingsAppId": "ID aplikace",
|
||||
"DownloadClientFreeboxSettingsAppToken": "Token aplikace",
|
||||
"DownloadClientFreeboxSettingsAppTokenHelpText": "Token aplikace získaný při vytváření přístupu k Freebox API (tj. ‚app_token‘)",
|
||||
"DownloadClientFreeboxSettingsHostHelpText": "Název hostitele nebo IP adresa hostitele Freeboxu, výchozí hodnota je ‚{url}‘ (funguje pouze ve stejné síti)",
|
||||
"DownloadClientFreeboxSettingsPortHelpText": "Port použitý pro přístup k rozhraní Freeboxu, výchozí hodnota je ‚{port}‘",
|
||||
"DownloadClientPneumaticSettingsNzbFolder": "Složka Nzb",
|
||||
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Tato složka bude muset být dostupná z XBMC",
|
||||
"DownloadClientQbittorrentSettingsContentLayout": "Rozvržení obsahu",
|
||||
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Stahovat v postupném pořadí (qBittorrent 4.1.0+)",
|
||||
"DownloadClientQbittorrentSettingsSequentialOrder": "Postupné pořadí",
|
||||
"DownloadClientRTorrentSettingsAddStoppedHelpText": "Povolení přidá torrenty a magnety do rTorrentu v zastaveném stavu. To může způsobit poškození souborů magnet.",
|
||||
"DownloadClientRTorrentSettingsDirectoryHelpText": "Volitelné umístění pro stahování, ponechte prázdné pro použití výchozího umístění rTorrentu",
|
||||
"DownloadClientSettingsAddPaused": "Přidat pozastavené",
|
||||
"DownloadClientSettingsDefaultCategorySubFolderHelpText": "Výchozí záložní kategorie, pokud pro vydání neexistuje žádná namapovaná kategorie. Přidáním kategorie specifické pro {appName} se zabrání konfliktům s nesouvisejícími stahováními, která nejsou {appName}. Použití kategorie je nepovinné, ale důrazně se doporučuje. Vytvoří podadresář [kategorie] ve výstupním adresáři.",
|
||||
"DownloadClientSettingsUseSslHelpText": "Při připojení k {clientName} použít zabezpečené připojení",
|
||||
"DownloadClientTransmissionSettingsDirectoryHelpText": "Volitelné umístění pro stahování, ponechte prázdné pro použití výchozího umístění Transmission",
|
||||
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Přidá předponu k url {clientName} rpc, např. {url}, výchozí hodnota je ‚{defaultUrl}‘",
|
||||
"DownloadClientsSettingsSummary": "Konfigurace klientů pro stahování pro integraci do vyhledávání v uživatelském rozhraní {appName}",
|
||||
"ElapsedTime": "Uplynulý čas",
|
||||
"EnableIndexer": "Povolit indexer",
|
||||
"External": "Externí",
|
||||
"FullSync": "Úplná synchronizace",
|
||||
"HealthMessagesInfoBox": "Další informace o příčině těchto zpráv o kontrole zdraví najdete kliknutím na odkaz wiki (ikona knihy) na konci řádku nebo kontrolou [logů]({link}). Pokud máte potíže s interpretací těchto zpráv, můžete se obrátit na naši podporu, a to na níže uvedených odkazech.",
|
||||
"Implementation": "Implementace",
|
||||
"DeleteClientCategory": "Odstranit kategorii klienta pro stahování",
|
||||
"DownloadClientRTorrentSettingsAddStopped": "Přidat zastavené",
|
||||
"DeleteIndexerProxy": "Odstranit proxy indexerů",
|
||||
"Description": "Popis",
|
||||
"IncludeManualGrabsHelpText": "Včetně ručních získání provedených v {appName}",
|
||||
"GoToApplication": "Přejít na aplikaci",
|
||||
"DownloadClientAriaSettingsDirectoryHelpText": "Volitelné umístění pro stahování, ponechte prázdné pro použití výchozího umístění Aria2",
|
||||
"DownloadClientPneumaticSettingsStrmFolderHelpText": "Soubory .strm v této složce budou importovány pomocí drone",
|
||||
"Destination": "Cíl",
|
||||
"DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Stahovat nejprve první a poslední kusy (qBittorrent 4.1.0+)",
|
||||
"DeleteSelectedIndexer": "Odstranit vybraný indexer",
|
||||
"DownloadClientPneumaticSettingsStrmFolder": "Složka Strm",
|
||||
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Nejprve první a poslední",
|
||||
"DownloadClientFreeboxSettingsApiUrlHelpText": "Definujte základní adresu URL Freebox API s verzí API, např. ‚{url}‘, výchozí hodnota je ‚{defaultApiUrl}‘",
|
||||
"HistoryCleanup": "Vyčištění historie",
|
||||
"DownloadClientFreeboxSettingsAppIdHelpText": "ID aplikace zadané při vytváření přístupu k Freebox API (tj. ‚app_id‘)",
|
||||
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Zda použít rozvržení obsahu nakonfigurované v qBittorrentu, původní rozvržení z torrentu nebo vždy vytvořit podsložku (qBittorrent 4.3.2+)",
|
||||
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Počáteční stav torrentů přidaných do qBittorrentu. Pamatujte, že vynucené torrenty nedodržují omezení týkající se seedů",
|
||||
"DownloadClientQbittorrentSettingsUseSslHelpText": "Používat zabezpečené připojení. Viz Možnosti -> WebUI -> Webové uživatelské rozhraní -> ‚Použít HTTPS místo HTTP‘ v qBittorrentu.",
|
||||
"FilterPlaceHolder": "Hledat indexery",
|
||||
"FoundCountReleases": "Nalezeno {itemCount} vydání",
|
||||
"DownloadClientRTorrentSettingsUrlPathHelpText": "Cesta ke koncovému bodu XMLRPC, viz {url}. Při použití ruTorrentu je to obvykle RPC2 nebo [cesta k ruTorrentu]{url2}.",
|
||||
"DownloadClientSettingsDefaultCategoryHelpText": "Výchozí záložní kategorie, pokud pro vydání neexistuje žádná namapovaná kategorie. Přidáním kategorie specifické pro {appName} se zabrání konfliktům s nesouvisejícími stahováními, která nejsou {appName}. Použití kategorie je nepovinné, ale důrazně se doporučuje.",
|
||||
"DownloadClientSettingsInitialStateHelpText": "Počáteční stav pro torrenty přidané do {clientName}",
|
||||
"EditCategory": "Upravit kategorii",
|
||||
"HistoryDetails": "Podrobnosti o historii",
|
||||
"Donate": "Darovat",
|
||||
"DownloadClientSettingsDestinationHelpText": "Ručně určuje cíl stahování, pro použití výchozího nastavení nechte prázdné",
|
||||
"EnabledRedirected": "Povoleno, Přesměrováno",
|
||||
"EditSyncProfile": "Upravit profil synchronizace",
|
||||
"DeleteAppProfile": "Odstranit profil aplikace",
|
||||
"IndexerSettingsAppsMinimumSeeders": "Minimální počet seederů aplikací",
|
||||
"UsenetBlackholeNzbFolder": "Složka Nzb",
|
||||
"SearchIndexers": "Hledat indexery",
|
||||
"IndexerSettingsAppsMinimumSeedersHelpText": "Minimální počet seederů požadovaných aplikacemi pro indexer, výchozí hodnota synchronizačního profilu je prázdná",
|
||||
"IndexerProxy": "Proxy indexeru",
|
||||
"IndexerBeyondHDSettingsRssKeyHelpText": "Klíč RSS ze stránky (Naleznete v Moje zabezpečení => Klíč RSS)",
|
||||
"IndexerHDBitsSettingsCodecsHelpText": "Pokud není zadáno, použijí se všechny možnosti.",
|
||||
"IndexerHDBitsSettingsUsernameHelpText": "Uživatelské jméno stránky",
|
||||
"IndexerAvistazSettingsUsernameHelpTextWarning": "Rozhraní API v tomto indexeru mohou používat pouze hodnosti člen a vyšší.",
|
||||
"IndexerBeyondHDSettingsApiKeyHelpText": "Klíč API ze stránky (Naleznete v Moje zabezpečení => Klíč API)",
|
||||
"IndexerBeyondHDSettingsFreeleechOnlyHelpText": "Hledat pouze freeleech vydání",
|
||||
"IndexerMTeamTpSettingsFreeleechOnlyHelpText": "Hledat pouze freeleech vydání",
|
||||
"IndexerProxies": "Proxy indexeru",
|
||||
"IndexerGazelleGamesSettingsFreeleechOnlyHelpText": "Hledat pouze freeleech vydání",
|
||||
"IndexerHistoryLoadError": "Chyba při načítání historie indexeru",
|
||||
"IndexerId": "ID indexeru",
|
||||
"IndexerNoDefinitionCheckHealthCheckMessage": "Indexery nemají žádnou definici a nebudou fungovat: {indexerNames}. Odeberte je a (nebo) znovu přidejte do {appName}.",
|
||||
"IndexerAlphaRatioSettingsExcludeSceneHelpText": "Vyloučit vydání SCENE z výsledků",
|
||||
"IndexerAlreadySetup": "Alespoň jedna instance indexeru je již nastavena",
|
||||
"IndexerAvistazSettingsPasswordHelpText": "Heslo stránky",
|
||||
"IndexerAvistazSettingsPidHelpText": "PID ze stránky Můj účet nebo Můj profil",
|
||||
"IndexerAvistazSettingsUsernameHelpText": "Uživatelské jméno stránky",
|
||||
"IndexerBeyondHDSettingsLimitedOnly": "Pouze omezené",
|
||||
"IndexerBeyondHDSettingsLimitedOnlyHelpText": "Hledat pouze freeleech (Omezené nahrávání)",
|
||||
"IndexerCategories": "Kategorie indexeru",
|
||||
"IndexerDisabled": "Indexer zakázán",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Indexery s neplatnými klienty pro stahování: {indexerNames}.",
|
||||
"IndexerDownloadClientHelpText": "Určete, který klient pro stahování se použije pro grabování v rámci {appName} z tohoto indexeru",
|
||||
"IndexerFailureRate": "Míra selhání indexeru",
|
||||
"IndexerFileListSettingsFreeleechOnlyHelpText": "Hledat pouze freeleech vydání",
|
||||
"IndexerFileListSettingsPasskeyHelpText": "Přístupový klíč stránky (Jedná se o alfanumerický řetězec v url adrese trackeru zobrazené v klientovi pro stahování)",
|
||||
"IndexerGazelleGamesSettingsApiKeyHelpText": "Klíč API ze stránky (Naleznete v Nastavení => Nastavení přístupu)",
|
||||
"IndexerGazelleGamesSettingsSearchGroupNames": "Hledat názvy skupin",
|
||||
"IndexerHDBitsSettingsFreeleechOnlyHelpText": "Zobrazit pouze freeleech vydání",
|
||||
"IndexerHDBitsSettingsOriginsHelpText": "Pokud není zadáno, použijí se všechny možnosti.",
|
||||
"IndexerHDBitsSettingsUseFilenames": "Použít názvy souborů",
|
||||
"IndexerHealthCheckNoIndexers": "Nejsou povoleny žádné indexery, {appName} nevrátí výsledky vyhledávání",
|
||||
"IndexerIPTorrentsSettingsCookieUserAgent": "Uživatelský agent cookie",
|
||||
"IndexerIPTorrentsSettingsCookieUserAgentHelpText": "Uživatelský agent přidružený cookie použitý z prohlížeče",
|
||||
"IndexerIPTorrentsSettingsFreeleechOnlyHelpText": "Hledat pouze freeleech vydání",
|
||||
"IndexerNebulanceSettingsApiKeyHelpText": "Klíč API z Nastavení uživatele > Klíče API. Klíč musí mít oprávnění Seznam a Stáhnout",
|
||||
"IndexerNewznabSettingsAdditionalParametersHelpText": "Dodatečné parametry Newznab",
|
||||
"IndexerNewznabSettingsApiKeyHelpText": "Klíč API stránky",
|
||||
"IndexerObsoleteCheckMessage": "Indexery jsou zastaralé nebo byly aktualizovány: {0}. Odeberte je a (nebo) znovu přidejte do {appName}",
|
||||
"IndexerOrpheusSettingsApiKeyHelpText": "Klíč API ze stránky (Naleznete v Nastavení => Nastavení přístupu)",
|
||||
"IndexerPassThePopcornSettingsApiUserHelpText": "Tato nastavení naleznete v nastavení zabezpečení PassThePopcorn (Upravit profil > Zabezpečení).",
|
||||
"IndexerPassThePopcornSettingsFreeleechOnlyHelpText": "Hledat pouze freeleech vydání",
|
||||
"IndexerRedactedSettingsApiKeyHelpText": "Klíč API ze stránky (Naleznete v Nastavení => Nastavení přístupu)",
|
||||
"IndexerRss": "RSS indexeru",
|
||||
"LastFailure": "Poslední selhání",
|
||||
"IndexerSettingsAdditionalParameters": "Dodatečné parametry",
|
||||
"IndexerSettingsApiPath": "Cesta k API",
|
||||
"IndexerSettingsApiUser": "Uživatel API",
|
||||
"IndexerAuth": "Ověření indexeru",
|
||||
"IndexerInfo": "Informace o indexeru",
|
||||
"IndexerName": "Název indexeru",
|
||||
"IndexerDetails": "Podrobnosti indexeru",
|
||||
"IndexerHDBitsSettingsPasskeyHelpText": "Přístupový klíč z Podrobnosti o uživateli",
|
||||
"IndexerQuery": "Dotaz na indexer",
|
||||
"IndexerAlphaRatioSettingsExcludeScene": "Vyloučit SCENE",
|
||||
"IndexerAlphaRatioSettingsFreeleechOnlyHelpText": "Hledat pouze freeleech vydání",
|
||||
"IndexerBeyondHDSettingsSearchTypes": "Hledat typy",
|
||||
"IndexerBeyondHDSettingsSearchTypesHelpText": "Vyberte typy vydání, které vás zajímají. Pokud není vybrán žádný, použijí se všechny možnosti.",
|
||||
"IndexerFileListSettingsUsernameHelpText": "Uživatelské jméno stránky",
|
||||
"IndexerGazelleGamesSettingsApiKeyHelpTextWarning": "Musí mít oprávnění Uživatel a Torrenty",
|
||||
"IndexerGazelleGamesSettingsSearchGroupNamesHelpText": "Hledat vydání podle názvů skupin",
|
||||
"IndexerHDBitsSettingsMediumsHelpText": "Pokud není zadáno, použijí se všechny možnosti.",
|
||||
"IndexerHDBitsSettingsUseFilenamesHelpText": "Zaškrtněte tuto možnost, pokud chcete používat názvy souborů torrentů jako názvy vydání",
|
||||
"IndexerNewznabSettingsVipExpirationHelpText": "Zadejte datum (rrrr-mm-dd) pro vypršení VIP nebo prázdné, {appName} bude upozorňovat 1 týden před vypršením VIP",
|
||||
"IndexerNzbIndexSettingsApiKeyHelpText": "Klíč API stránky",
|
||||
"IndexerPassThePopcornSettingsApiKeyHelpText": "Klíč API stránky",
|
||||
"IndexerMTeamTpSettingsApiKeyHelpText": "Klíč API ze stránky (Naleznete v Uživatelský ovládací panel => Zabezpečení => Laboratoř)",
|
||||
"IndexerPassThePopcornSettingsGoldenPopcornOnly": "Pouze Golden Popcorn",
|
||||
"IndexerPassThePopcornSettingsGoldenPopcornOnlyHelpText": "Hledat pouze vydání Golden Popcorn",
|
||||
"IndexerSettingsApiPathHelpText": "Cesta k api, obvykle {url}",
|
||||
"IndexerAvistazSettingsFreeleechOnlyHelpText": "Hledat pouze freeleech vydání",
|
||||
"InitialFailure": "Úvodní selhání",
|
||||
"IndexerTorrentSyndikatSettingsApiKeyHelpText": "Klíč API stránky",
|
||||
"SearchTypes": "Hledat typy"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user